Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
1d2d504
Merge pull request #1372 from justcallmekoko/develop
justcallmekoko Jul 13, 2026
a4849a9
Merge pull request #1373 from justcallmekoko/develop
justcallmekoko Jul 13, 2026
6356e9d
Merge pull request #1375 from justcallmekoko/develop
justcallmekoko Jul 14, 2026
8b47244
Merge pull request #1382 from justcallmekoko/develop
justcallmekoko Jul 15, 2026
aea6e98
Merge pull request #1390 from justcallmekoko/develop
justcallmekoko Jul 18, 2026
5282737
Merge pull request #1391 from justcallmekoko/develop
justcallmekoko Jul 18, 2026
f379cd5
Merge pull request #1392 from justcallmekoko/develop
justcallmekoko Jul 20, 2026
41ceb57
Merge pull request #1394 from justcallmekoko/develop
justcallmekoko Jul 20, 2026
97e8256
Merge pull request #1397 from justcallmekoko/develop
justcallmekoko Jul 21, 2026
43aa8bc
Merge pull request #1414 from justcallmekoko/develop
justcallmekoko Aug 1, 2026
de42658
Merge pull request #1452 from justcallmekoko/develop
justcallmekoko Aug 11, 2026
14d279f
Merge develop into master
justcallmekoko Aug 14, 2026
2bc5d7b
Promote v1.14.2 fixes to master (#1468)
justcallmekoko Aug 15, 2026
db338df
Promote installer release automation repair (#1470)
justcallmekoko Aug 16, 2026
7543feb
Release v1.14.3 (#1473)
justcallmekoko Aug 16, 2026
b19ef19
Merge pull request #1486 from justcallmekoko/develop
justcallmekoko Aug 20, 2026
bc66029
fix(build): scope T-Dongle partition layout
justcallmekoko Aug 20, 2026
6e375e3
Merge pull request #1488 from justcallmekoko/hotfix/v1.15.0-partition…
justcallmekoko Aug 20, 2026
ea234f4
Promote v1.15.1 changes to master (#1492)
justcallmekoko Aug 22, 2026
801cc75
feat: back up SPIFFS content to SD
justcallmekoko Aug 22, 2026
3ae6c17
test: exclude hardware-only backup paths
justcallmekoko Aug 22, 2026
a4e1bda
Back up SPIFFS content to SD card (#1494)
justcallmekoko Aug 22, 2026
1a4e5a4
Add SPIFFS restore from SD backup
justcallmekoko Aug 22, 2026
243533a
Restore SPIFFS content from SD backup (#1495)
justcallmekoko Aug 22, 2026
1210198
Add machine protocol for SPIFFS migration
justcallmekoko Aug 23, 2026
cd7d912
Avoid undefined board identity in protocol response
justcallmekoko Aug 23, 2026
8df054b
Exclude serial help from native coverage
justcallmekoko Aug 23, 2026
9fd74cb
Merge pull request #1496 from feature/spiffs-machine-protocol
justcallmekoko Aug 23, 2026
8076b2f
Revert "Promote v1.15.1 changes to master (#1492)"
justcallmekoko Aug 23, 2026
082e693
feat: back up SPIFFS content to SD
justcallmekoko Aug 22, 2026
53ca4ea
test: exclude hardware-only backup paths
justcallmekoko Aug 22, 2026
94c93b6
Add SPIFFS restore from SD backup
justcallmekoko Aug 22, 2026
ccb255e
Add machine protocol for SPIFFS migration
justcallmekoko Aug 23, 2026
33aeed3
Avoid undefined board identity in protocol response
justcallmekoko Aug 23, 2026
3aa19a0
Exclude serial help from native coverage
justcallmekoko Aug 23, 2026
34f9b67
Prepare v1.15.1 SPIFFS migration release
justcallmekoko Aug 23, 2026
207d67b
Normalize menu source line endings
justcallmekoko Aug 23, 2026
7da49a4
Reduce SPIFFS migration firmware footprint
justcallmekoko Aug 23, 2026
eea1950
Use fixed SPIFFS migration errors
justcallmekoko Aug 23, 2026
fb02b07
Fix compact SPIFFS error assignment
justcallmekoko Aug 23, 2026
7e356bb
Compact SPIFFS protocol and backup inspection
justcallmekoko Aug 23, 2026
527a9f9
Avoid duplicate SPIFFS helper specialization
justcallmekoko Aug 23, 2026
90e756f
Revert "Avoid duplicate SPIFFS helper specialization"
justcallmekoko Aug 23, 2026
ebfe507
Share SPIFFS command dispatch logic
justcallmekoko Aug 23, 2026
a865837
Keep SPIFFS migration controls on the CLI
justcallmekoko Aug 23, 2026
9f6fe8f
Consolidate SPIFFS migration implementation
justcallmekoko Aug 23, 2026
5da89b6
Reduce legacy-layout migration footprint
justcallmekoko Aug 23, 2026
ee05b44
Finalize legacy min_spiffs footprint
justcallmekoko Aug 23, 2026
1d99177
Finalize compact machine protocol dispatch
justcallmekoko Aug 23, 2026
91724fd
Prepare v1.15.1 SPIFFS migration release (#1498)
justcallmekoko Aug 24, 2026
ad848b4
Reduce automatic firmware matrix runs
justcallmekoko Aug 24, 2026
5d40da5
Reduce automatic firmware matrix runs (#1500)
justcallmekoko Aug 24, 2026
c72d769
Merge master into develop for v1.16.0
justcallmekoko Aug 24, 2026
edd4f12
Keep Marauder v8 within legacy app partition
justcallmekoko Aug 25, 2026
488f14e
Restore retained ARP and SPIFFS menu features
justcallmekoko Aug 25, 2026
bed3f4f
Restore complete CLI help on Marauder v8
justcallmekoko Aug 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions .github/workflows/build_parallel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@ name: Build and Push Parallel

on:
workflow_dispatch:
push:
branches:
- master
tags:
- "*"
inputs:
create_release:
description: "Create the draft release after the full matrix passes"
required: true
type: boolean
default: true
pull_request:
types: [labeled]

concurrency:
group: full-hardware-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
compile_sketch:
name: build ${{ matrix.board.name }}
if: ${{ github.event_name == 'workflow_dispatch' || github.event.label.name == 'full-hardware-ci' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
Expand Down Expand Up @@ -44,6 +51,8 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}

- name: Install Arduino CLI
run: |
Expand Down Expand Up @@ -286,7 +295,7 @@ jobs:
name: Create Release
runs-on: ubuntu-latest
needs: [compile_sketch]
if: ${{ github.event_name == 'workflow_dispatch' }}
if: ${{ github.event_name == 'workflow_dispatch' && inputs.create_release }}
permissions:
contents: write
steps:
Expand Down
39 changes: 39 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Development workflow

ESP32 Marauder supports many hardware targets, so the full firmware matrix is
reserved for final validation and releases. It is not an iteration loop.

## Iteration

1. Work on a local branch without opening a pull request.
2. Run native unit tests and static checks locally.
3. Compile only the hardware targets requested for physical testing.
4. Clearly identify locally compiled test binaries by target and exact commit.
5. Revise locally until the requested hardware behavior is accepted.

## Pull request gate

Push the branch and open a pull request only when the change is ready for
review. Pull requests run the comparatively inexpensive native unit-test
workflow. Additional commits should be batched instead of pushed after every
small edit.

## Full-matrix gate

When a pull request is merge-ready, a maintainer applies the
`full-hardware-ci` label. That label runs **Build and Push Parallel** against the
pull request's exact current head. Any later source push invalidates that
evidence, so the label must be removed and applied again to validate the new
head before merge.

The workflow can also be dispatched manually against a final candidate branch
with `create_release` disabled. Do not use the full matrix for ordinary
iteration or to obtain one or two test binaries.

Untrusted fork code must not receive protected-build credentials. Private V8,
Mini V3, and Dual Mini C5 validation is performed only after review, from an
exact-head branch controlled by the maintainers.

For a firmware release, dispatch the same workflow against the exact release
source commit with `create_release` enabled. The workflow remains the
authoritative producer of the draft release and its public binaries.
129 changes: 126 additions & 3 deletions esp32_marauder/CommandLine.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,58 @@
#include "CommandLine.h"

// GCOVR_EXCL_START -- serial protocol output depends on Arduino Serial.
namespace {
bool validTransactionId(const String& transaction_id) {
if (transaction_id.length() == 0 || transaction_id.length() > 40)
return false;

for (size_t i = 0; i < transaction_id.length(); i++) {
char c = transaction_id.charAt(i);
if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') || c == '-' || c == '_' || c == '.'))
return false;
}
return true;
}

void machineResult(
const String& transaction_id,
const char* command,
const char* status,
const char* code,
size_t files = 0,
size_t bytes = 0,
bool rebooting = false
) {
Serial.printf(
"@MARAUDER:{\"protocol\":1,\"tx\":\"%s\",\"command\":\"%s\","
"\"status\":\"%s\",\"code\":\"%s\",\"files\":%u,\"bytes\":%u,"
"\"rebooting\":%s}\n",
transaction_id.c_str(), command, status, code,
(unsigned)files, (unsigned)bytes, rebooting ? "true" : "false"
);
}

const char* storageErrorCode(uint8_t error, const char* fallback) {
if (error == 1)
return "SD_NOT_READY";
if (error == 2)
return "BACKUP_NOT_FOUND";
return fallback;
}
}
// GCOVR_EXCL_STOP

// Brightness functions defined in esp32_marauder.ino
#ifndef HAS_MINI_SCREEN
extern void brightnessCycle();
extern uint8_t getBrightnessLevel();
#endif

void CommandLine::RunSetup() {
Serial.println(this->ascii_art);
#ifndef MARAUDER_V8
Serial.println(this->ascii_art);
#endif

Serial.println(F("\n\n--------------------------------\n"));
Serial.println(F(" ESP32 Marauder \n"));
Expand Down Expand Up @@ -231,6 +276,14 @@ void CommandLine::runCommand(String input) {
Serial.println(HELP_REBOOT_CMD);
Serial.println(HELP_UPDATE_CMD_A);
Serial.println(HELP_LS_CMD);
// GCOVR_EXCL_START -- hardware-only command help entry.
Serial.println(HELP_PROTOCOL_INFO_CMD);
#ifdef HAS_SD
Serial.println(HELP_BACKUP_SPIFFS_CMD);
Serial.println(HELP_BACKUP_STATUS_CMD);
Serial.println(HELP_RESTORE_SPIFFS_CMD);
#endif
// GCOVR_EXCL_STOP
Serial.println(HELP_LED_CMD);
Serial.println(HELP_GPS_DATA_CMD);
Serial.println(HELP_GPS_CMD);
Expand Down Expand Up @@ -461,6 +514,76 @@ void CommandLine::runCommand(String input) {
sd_obj.listDir(cmd_args.get(1));
#endif
}
// GCOVR_EXCL_START -- requires mounted SPIFFS and SD filesystems.
else if (cmd_args.get(0) == PROTOCOL_INFO_CMD) {
int machine_arg = this->argSearch(&cmd_args, "--machine");
String transaction_id = machine_arg >= 0 && machine_arg + 1 < cmd_args.size()
? cmd_args.get(machine_arg + 1) : "";
if (machine_arg >= 0 && !validTransactionId(transaction_id))
machineResult(transaction_id, PROTOCOL_INFO_CMD, "error", "INVALID_TRANSACTION");
else if (machine_arg >= 0) {
#ifdef HAS_SD
Serial.printf(
"@MARAUDER:{\"protocol\":1,\"tx\":\"%s\",\"command\":\"protocolinfo\","
"\"status\":\"success\",\"code\":\"OK\",\"firmware\":\"%s\","
"\"capabilities\":[\"spiffs-backup\",\"spiffs-backup-status\","
"\"spiffs-restore\"],\"backupPath\":\"/spiffs\"}\n",
transaction_id.c_str(), version_number.c_str()
);
#else
Serial.printf(
"@MARAUDER:{\"protocol\":1,\"tx\":\"%s\",\"command\":\"protocolinfo\","
"\"status\":\"success\",\"code\":\"OK\",\"firmware\":\"%s\","
"\"capabilities\":[]}\n",
transaction_id.c_str(), version_number.c_str()
);
#endif
}
}
else if (cmd_args.get(0) == BACKUP_SPIFFS_CMD ||
cmd_args.get(0) == BACKUP_STATUS_CMD ||
cmd_args.get(0) == RESTORE_SPIFFS_CMD) {
uint8_t operation = cmd_args.get(0) == BACKUP_SPIFFS_CMD ? 0 :
cmd_args.get(0) == BACKUP_STATUS_CMD ? 1 : 2;
const char* command = operation == 0 ? BACKUP_SPIFFS_CMD :
operation == 1 ? BACKUP_STATUS_CMD : RESTORE_SPIFFS_CMD;
int machine_arg = this->argSearch(&cmd_args, "--machine");
String transaction_id = machine_arg >= 0 && machine_arg + 1 < cmd_args.size()
? cmd_args.get(machine_arg + 1) : "";
bool machine = machine_arg >= 0;
if (machine && !validTransactionId(transaction_id)) {
machineResult(transaction_id, command, "error", "INVALID_TRANSACTION");
return;
}
#ifdef HAS_SD
size_t files = 0;
size_t bytes = 0;
uint8_t error = 0;
if (machine && operation != 1)
machineResult(transaction_id, command, "started", "OK");

bool success = sd_obj.migrateSPIFFS(operation, files, bytes, error);
if (machine) {
if (success)
machineResult(transaction_id, command, "success", "OK", files, bytes, operation == 2);
else {
const char* fallback = operation == 0 ? "BACKUP_FAILED" :
operation == 1 ? "BACKUP_INSPECTION_FAILED" : "RESTORE_FAILED";
machineResult(transaction_id, command, "error", storageErrorCode(error, fallback));
}
}
if (success && operation == 2) {
delay(1000);
ESP.restart();
}
#else
if (machine)
machineResult(transaction_id, command, "error", "SD_NOT_SUPPORTED");
else
Serial.println(F("SD Card NOT Supported"));
#endif
}
// GCOVR_EXCL_STOP

// Channel command
else if (cmd_args.get(0) == CH_CMD) {
Expand Down Expand Up @@ -1289,11 +1412,11 @@ void CommandLine::runCommand(String input) {
this->startScanFromCLI(WIFI_PING_SCAN, TFT_GREEN, "Ping Scan");
}

// GCOVR_EXCL_START -- command dispatch requires the firmware CLI runtime.
// ARP discovery uses the active station netif on both legacy and C5
// dual-band hardware.
if (cmd_args.get(0) == ARP_SCAN_CMD) {
this->startScanFromCLI(WIFI_ARP_SCAN, TFT_CYAN, "ARP Scan");
}
// GCOVR_EXCL_STOP

// GPS POI
if (cmd_args.get(0) == GPS_POI_CMD) {
Expand Down
12 changes: 11 additions & 1 deletion esp32_marauder/CommandLine.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ const char PROGMEM UPDATE_CMD[] = "update";
const char PROGMEM HELP_CMD[] = "help";
const char PROGMEM SETTINGS_CMD[] = "settings";
const char PROGMEM LS_CMD[] = "ls";
const char PROGMEM PROTOCOL_INFO_CMD[] = "protocolinfo";
const char PROGMEM BACKUP_SPIFFS_CMD[] = "backupspiffs";
const char PROGMEM BACKUP_STATUS_CMD[] = "backupstatus";
const char PROGMEM RESTORE_SPIFFS_CMD[] = "restorespiffs";
const char PROGMEM LED_CMD[] = "led";
const char PROGMEM GPS_DATA_CMD[] = "gpsdata";
const char PROGMEM GPS_CMD[] = "gps";
Expand Down Expand Up @@ -133,6 +137,10 @@ const char PROGMEM HELP_REBOOT_CMD[] = "reboot";
const char PROGMEM HELP_UPDATE_CMD_A[] = "update -s/-w";
const char PROGMEM HELP_SETTINGS_CMD[] = "settings [-s <setting> enable/disable>]/[-r]";
const char PROGMEM HELP_LS_CMD[] = "ls <directory>";
const char PROGMEM HELP_PROTOCOL_INFO_CMD[] = "protocolinfo [--machine <transaction-id>]";
const char PROGMEM HELP_BACKUP_SPIFFS_CMD[] = "backupspiffs [--machine <transaction-id>] - copy SPIFFS to /spiffs on SD";
const char PROGMEM HELP_BACKUP_STATUS_CMD[] = "backupstatus [--machine <transaction-id>] - inspect /spiffs on SD";
const char PROGMEM HELP_RESTORE_SPIFFS_CMD[] = "restorespiffs [--machine <transaction-id>] - restore SPIFFS from /spiffs on SD";
const char PROGMEM HELP_LED_CMD[] = "led -s <hex color>/-p <rainbow>";
const char PROGMEM HELP_GPS_DATA_CMD[] = "gpsdata";
const char PROGMEM HELP_GPS_CMD[] = "gps [-t] [-g] <fix/sat/lon/lat/alt/date/accuracy/text/nmea>\r\n [-n] <native/all/gps/glonass/galileo/navic/qzss/beidou>\r\n [-b = use BD vs GB for beidou]";
Expand Down Expand Up @@ -217,7 +225,6 @@ class CommandLine {
LinkedList<String> parseCommand(String input, char* delim);
String toLowerCase(String str);
void filterAccessPoints(String filter);
void runCommand(String input);
bool checkValueExists(LinkedList<String>* cmd_args_list, int index);
bool inRange(int max, int index);
//bool apSelected();
Expand All @@ -226,6 +233,7 @@ class CommandLine {
int argSearch(LinkedList<String>* cmd_args, const char* key);
void startScanFromCLI(int scan_mode, uint16_t color, const char* scan_name);

#ifndef MARAUDER_V8
const char* ascii_art =
"\r\n"
" @@@@@@ \r\n"
Expand All @@ -252,11 +260,13 @@ class CommandLine {
" @@@@@@ \r\n"
" @@@@ \r\n"
"\r\n";
#endif

public:

void RunSetup();
void main(uint32_t currentTime);
void runCommand(String input);
};

#endif
18 changes: 13 additions & 5 deletions esp32_marauder/MenuFunctions.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "MenuFunctions.h"
#include "CommandLine.h"
#include "lang_var.h"

#ifdef HAS_SCREEN
Expand All @@ -9,6 +10,7 @@ extern LinkedList<Station>* stations;
extern LinkedList<AirTag>* airtags;
extern LinkedList<Flipper>* flippers;
extern LinkedList<BleDevice>* ble_devices;
extern CommandLine cli_obj;

#ifdef HAS_MINI_SCREEN
void MenuFunctions::drawMiniMenuButton(int b, int x, bool selected) {
Expand Down Expand Up @@ -2002,13 +2004,13 @@ void MenuFunctions::RunSetup()
this->drawStatusBar();
wifi_scan_obj.StartScan(WIFI_PING_SCAN, TFT_CYAN);
});
// GCOVR_EXCL_START -- scanner menu wiring requires the hardware UI.
// The C5 implementation uses the active station netif with lwIP core
// locking, so dual-band hardware supports the same ARP scanner.
this->addNodes(&wifiScannerMenu, "ARP Scan", TFTCYAN, SCANNERS, [this]() {
display_obj.clearScreen();
this->drawStatusBar();
wifi_scan_obj.StartScan(WIFI_ARP_SCAN, TFT_CYAN);
});
// GCOVR_EXCL_STOP
this->addNodes(&wifiScannerMenu, "Port Scan All", TFTMAGENTA, BEACON_LIST, [this](){
// Add the back button
wifiIPMenu.list->clear();
Expand Down Expand Up @@ -3486,6 +3488,15 @@ void MenuFunctions::RunSetup()

this->changeMenu(&sdDeleteMenu, true);
});

this->addNodes(&deviceMenu, "Backup SPIFFS", TFTGREEN, SD_UPDATE, [this]() {
cli_obj.runCommand(BACKUP_SPIFFS_CMD);
});

this->addNodes(&deviceMenu, "Restore SPIFFS", TFTORANGE, SD_UPDATE, [this]() {
cli_obj.runCommand(RESTORE_SPIFFS_CMD);
});

}
#endif

Expand Down Expand Up @@ -4658,6 +4669,3 @@ void MenuFunctions::displayCurrentMenu(int start_index)
#endif

#endif



2 changes: 1 addition & 1 deletion esp32_marauder/MenuFunctions.h
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ class MenuFunctions

void buildUploadFileMenu();
void setupSDFileList(bool update = false);
void buildSDFileMenu(bool update = false);
void buildSDFileMenu(bool update = false);
void displayMenuButtons();
uint16_t getColor(uint16_t color);
void drawAvgLine(int16_t value);
Expand Down
Loading
Loading