mirror of
https://github.com/pelgraine/Meck.git
synced 2026-10-31 04:02:01 +01:00
Companion repeat mode (upstream v9) and path hash mode in device info
- Device info reply now ends with byte 80 (repeat on/off) and byte 81 (path hash mode), as upstream v9/v10, so the app can show Meck's path hash size.
- Companion repeat mode, switched on only from the app: forwards packets while on. The app's radio command refuses repeat except on 433.000, 869.495 or 918.000 MHz; command 60 returns those frequencies. Stored at prefs byte 62 (spare, so existing devices start with it off).
- While repeat is on, Settings, the serial CLI and import accept only those three frequencies. Settings shows a pop-up (English and French).
- main.cpp: meckShowAlert now defined in every display build (was WiFi builds only), for the Settings pop-up.
EOF
Updated firmware versions for the Max and firmware build date.
This commit is contained in:
1 parent
666e4a575f
commit
f7f5a3e10b
8 files changed
+125
-25
No files matched your search
@@ -195,7 +195,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
||||
file.read((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56
|
||||
file.read((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60
|
||||
file.read((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61
|
||||
file.read(pad, 1); // 62
|
||||
file.read((uint8_t *)&_prefs.client_repeat, sizeof(_prefs.client_repeat)); // 62
|
||||
file.read((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63
|
||||
file.read((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64
|
||||
file.read((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68
|
||||
@@ -353,7 +353,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
||||
file.write((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56
|
||||
file.write((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60
|
||||
file.write((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61
|
||||
file.write(pad, 1); // 62
|
||||
file.write((uint8_t *)&_prefs.client_repeat, sizeof(_prefs.client_repeat)); // 62
|
||||
file.write((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63
|
||||
file.write((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64
|
||||
file.write((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68
|
||||
|
||||
@@ -316,8 +316,12 @@ static int meckImportConfig(MyMesh& mesh,
|
||||
double freq = atof(val);
|
||||
// Auto-detect units: >3000 = kHz, <=3000 = MHz
|
||||
if (freq > 3000.0) freq /= 1000.0;
|
||||
prefs->freq = (float)freq;
|
||||
prefsChanged = true;
|
||||
if (mesh.isFreqChangeAllowed((float)freq)) {
|
||||
prefs->freq = (float)freq;
|
||||
prefsChanged = true;
|
||||
} else {
|
||||
Serial.printf("Config Import: frequency %.3f skipped (repeat mode is on)\n", freq);
|
||||
}
|
||||
}
|
||||
else if (strcmp(key, "bandwidth") == 0) {
|
||||
double bw = atof(val);
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
#define CMD_SEND_ANON_REQ 57
|
||||
#define CMD_SET_AUTOADD_CONFIG 58
|
||||
#define CMD_GET_AUTOADD_CONFIG 59
|
||||
#define CMD_GET_ALLOWED_REPEAT_FREQ 60
|
||||
#define CMD_SET_PATH_HASH_MODE 61
|
||||
#define CMD_SEND_CHANNEL_DATA 62
|
||||
#define CMD_SET_DEFAULT_FLOOD_SCOPE 63 // v1.15+ (device-wide default scope name+key)
|
||||
@@ -696,6 +697,10 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* packet) {
|
||||
return false; // never filter  let normal processing continue
|
||||
}
|
||||
|
||||
bool MyMesh::allowPacketForward(const mesh::Packet* packet) {
|
||||
return _prefs.client_repeat != 0;
|
||||
}
|
||||
|
||||
uint8_t MyMesh::getHeardBy(const uint8_t* payload, uint16_t payload_len,
|
||||
uint8_t& out_bph, uint8_t& out_count,
|
||||
uint8_t* out_hash, int8_t* out_snr, uint8_t max_src) const {
|
||||
@@ -1872,6 +1877,37 @@ uint32_t MyMesh::getBLEPin() {
|
||||
return _active_ble_pin;
|
||||
}
|
||||
|
||||
struct FreqRange {
|
||||
uint32_t lower_freq, upper_freq;
|
||||
};
|
||||
|
||||
// Companion repeat mode: the only frequencies it may be on (kHz), as
|
||||
// upstream MeshCore. The Settings alert and serial CLI messages list them.
|
||||
static FreqRange repeat_freq_ranges[] = {
|
||||
{ 433000, 433000 },
|
||||
{ 869495, 869495 },
|
||||
{ 918000, 918000 }
|
||||
};
|
||||
|
||||
bool MyMesh::isValidClientRepeatFreq(uint32_t f) const {
|
||||
for (int i = 0; i < sizeof(repeat_freq_ranges)/sizeof(repeat_freq_ranges[0]); i++) {
|
||||
auto r = &repeat_freq_ranges[i];
|
||||
if (f >= r->lower_freq && f <= r->upper_freq) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MyMesh::isFreqChangeAllowed(float freq_mhz) const {
|
||||
if (_prefs.client_repeat == 0) return true;
|
||||
return isValidClientRepeatFreq((uint32_t)(freq_mhz * 1000.0f + 0.5f)); // MHz to kHz, rounded
|
||||
}
|
||||
|
||||
// Serial CLI reply when a frequency change is refused (repeat mode is on)
|
||||
static void printRepeatFreqRefusal() {
|
||||
Serial.println(" Error: repeat mode is on, so freq must be 433.000, 869.495 or 918.000");
|
||||
Serial.println(" (turn repeat mode off in the companion app to use other frequencies)");
|
||||
}
|
||||
|
||||
void MyMesh::startInterface(BaseSerialInterface &serial) {
|
||||
_serial = &serial;
|
||||
#if defined(ESP32) && defined(BLE_PIN_CODE) && !defined(WIFI_SSID) && !defined(MECK_WIFI_COMPANION)
|
||||
@@ -1904,6 +1940,8 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
i += 40;
|
||||
StrHelper::strzcpy((char *)&out_frame[i], FIRMWARE_VERSION, 20);
|
||||
i += 20;
|
||||
out_frame[i++] = _prefs.client_repeat ? 1 : 0; // v9+
|
||||
out_frame[i++] = _prefs.path_hash_mode; // v10+
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else if (cmd_frame[0] == CMD_APP_START &&
|
||||
len >= 8) { // sent when app establishes connection, respond with node ID
|
||||
@@ -2457,13 +2495,20 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
i += 4;
|
||||
uint8_t sf = cmd_frame[i++];
|
||||
uint8_t cr = cmd_frame[i++];
|
||||
uint8_t repeat = 0; // default - false
|
||||
if (len > i) {
|
||||
repeat = cmd_frame[i++]; // FIRMWARE_VER_CODE 9+
|
||||
}
|
||||
|
||||
if (freq >= 300000 && freq <= 2500000 && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7000 &&
|
||||
if (repeat && !isValidClientRepeatFreq(freq)) {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
} else if (freq >= 300000 && freq <= 2500000 && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7000 &&
|
||||
bw <= 500000) {
|
||||
_prefs.sf = sf;
|
||||
_prefs.cr = cr;
|
||||
_prefs.freq = (float)freq / 1000.0;
|
||||
_prefs.bw = (float)bw / 1000.0;
|
||||
_prefs.client_repeat = repeat ? 1 : 0;
|
||||
savePrefs();
|
||||
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
@@ -3056,6 +3101,15 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
out_frame[i++] = RESP_CODE_AUTOADD_CONFIG;
|
||||
out_frame[i++] = _prefs.autoadd_config;
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else if (cmd_frame[0] == CMD_GET_ALLOWED_REPEAT_FREQ) {
|
||||
int i = 0;
|
||||
out_frame[i++] = RESP_ALLOWED_REPEAT_FREQ;
|
||||
for (int k = 0; k < sizeof(repeat_freq_ranges)/sizeof(repeat_freq_ranges[0]) && i + 8 < sizeof(out_frame); k++) {
|
||||
auto r = &repeat_freq_ranges[k];
|
||||
memcpy(&out_frame[i], &r->lower_freq, 4); i += 4;
|
||||
memcpy(&out_frame[i], &r->upper_freq, 4); i += 4;
|
||||
}
|
||||
_serial->writeFrame(out_frame, i);
|
||||
#if defined(BLE_PIN_CODE) && defined(MECK_WIFI_COMPANION)
|
||||
} else if (cmd_frame[0] == CMD_RUN_CLI_COMMAND && len >= 3) {
|
||||
// App command line (upstream MeshCore, v14+). This build answers
|
||||
@@ -3369,7 +3423,9 @@ void MyMesh::checkCLIRescueCmd() {
|
||||
|
||||
} else if (memcmp(config, "freq ", 5) == 0) {
|
||||
float f = atof(&config[5]);
|
||||
if (f >= 400.0f && f <= 928.0f) {
|
||||
if (f >= 400.0f && f <= 928.0f && !isFreqChangeAllowed(f)) {
|
||||
printRepeatFreqRefusal();
|
||||
} else if (f >= 400.0f && f <= 928.0f) {
|
||||
_prefs.freq = f;
|
||||
savePrefs();
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
@@ -3472,7 +3528,9 @@ void MyMesh::checkCLIRescueCmd() {
|
||||
float bw = strtof(parts[1], nullptr);
|
||||
int sf = atoi(parts[2]);
|
||||
int cr = atoi(parts[3]);
|
||||
if (freq >= 400.0f && freq <= 928.0f && bw >= 7.8f && bw <= 500.0f
|
||||
if (freq >= 400.0f && freq <= 928.0f && !isFreqChangeAllowed(freq)) {
|
||||
printRepeatFreqRefusal();
|
||||
} else if (freq >= 400.0f && freq <= 928.0f && bw >= 7.8f && bw <= 500.0f
|
||||
&& sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8) {
|
||||
_prefs.freq = freq;
|
||||
_prefs.bw = bw;
|
||||
@@ -3496,6 +3554,11 @@ void MyMesh::checkCLIRescueCmd() {
|
||||
bool found = false;
|
||||
for (int i = 0; i < (int)NUM_RADIO_PRESETS; i++) {
|
||||
if (strcasecmp(RADIO_PRESETS[i].name, name) == 0) {
|
||||
if (!isFreqChangeAllowed(RADIO_PRESETS[i].freq)) {
|
||||
printRepeatFreqRefusal();
|
||||
found = true; // matched, but not applied
|
||||
break;
|
||||
}
|
||||
_prefs.freq = RADIO_PRESETS[i].freq;
|
||||
_prefs.bw = RADIO_PRESETS[i].bw;
|
||||
_prefs.sf = RADIO_PRESETS[i].sf;
|
||||
@@ -3515,7 +3578,11 @@ void MyMesh::checkCLIRescueCmd() {
|
||||
if (!found) {
|
||||
char* endp;
|
||||
long idx = strtol(name, &endp, 10);
|
||||
if (endp != name && *endp == '\0' && idx >= 0 && idx < (int)NUM_RADIO_PRESETS) {
|
||||
if (endp != name && *endp == '\0' && idx >= 0 && idx < (int)NUM_RADIO_PRESETS
|
||||
&& !isFreqChangeAllowed(RADIO_PRESETS[idx].freq)) {
|
||||
printRepeatFreqRefusal();
|
||||
found = true; // matched, but not applied
|
||||
} else if (endp != name && *endp == '\0' && idx >= 0 && idx < (int)NUM_RADIO_PRESETS) {
|
||||
_prefs.freq = RADIO_PRESETS[idx].freq;
|
||||
_prefs.bw = RADIO_PRESETS[idx].bw;
|
||||
_prefs.sf = RADIO_PRESETS[idx].sf;
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
#define FIRMWARE_VER_CODE 11
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "10 Oct 2026"
|
||||
#define FIRMWARE_BUILD_DATE "14 Oct 2026"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "Meck v1.15"
|
||||
#define FIRMWARE_VERSION "Meck v1.15.1"
|
||||
#endif
|
||||
|
||||
#if defined(STM32_PLATFORM)
|
||||
@@ -238,6 +238,12 @@ public:
|
||||
// otherwise the candidate region name. Used by the channel path-detail overlay.
|
||||
const char* getScopeName(uint8_t idx) const;
|
||||
|
||||
// Companion repeat mode (switched on from the app): while it is on, the
|
||||
// frequency may only change to an allowed repeat frequency. True when a
|
||||
// change to freq_mhz is allowed (repeat off, or an allowed frequency).
|
||||
// Public: SettingsScreen and MeckImport call it before changing freq.
|
||||
bool isFreqChangeAllowed(float freq_mhz) const;
|
||||
|
||||
|
||||
protected:
|
||||
float getAirtimeBudgetFactor() const override;
|
||||
@@ -254,6 +260,7 @@ protected:
|
||||
uint8_t getExtraAckTransmitCount() const override;
|
||||
uint8_t getAutoAddMaxHops() const override;
|
||||
bool filterRecvFloodPacket(mesh::Packet* packet) override;
|
||||
bool allowPacketForward(const mesh::Packet* packet) override;
|
||||
|
||||
uint8_t getPathHashSize() const override { return _prefs.path_hash_mode + 1; }
|
||||
void sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0) override;
|
||||
@@ -364,6 +371,7 @@ private:
|
||||
|
||||
void checkCLIRescueCmd();
|
||||
void checkSerialInterface();
|
||||
bool isValidClientRepeatFreq(uint32_t f) const;
|
||||
|
||||
DataStore* _store;
|
||||
NodePrefs _prefs;
|
||||
|
||||
@@ -118,6 +118,12 @@ struct NodePrefs { // persisted to file
|
||||
// final field and keep the default 0 (off).
|
||||
uint8_t agc_reset_interval; // default 0
|
||||
|
||||
// Companion repeat mode (upstream MeshCore, protocol v9): 1 = this
|
||||
// companion also forwards packets, as a repeater does. Switched on only
|
||||
// from the app, on an allowed repeat frequency. Stored at prefs byte 62,
|
||||
// which was spare, so older prefs files load it as 0 (off).
|
||||
uint8_t client_repeat; // default 0
|
||||
|
||||
// --- Font helpers (inline, no overhead) ---
|
||||
// Returns the DisplayDriver text-size index for "small/body" text.
|
||||
// T-Deck Pro: 0 = built-in 6×8 (or 7pt with custom fonts), 1 = 9pt.
|
||||
|
||||
@@ -884,11 +884,11 @@ static uint32_t _atoi(const char* sp) {
|
||||
// the lock state without pulling in the full UITask header.
|
||||
bool meck_alarm_is_locked() { return ui_task.isLocked(); }
|
||||
#endif
|
||||
#ifdef MECK_WIFI_COMPANION
|
||||
// Lets the Settings WiFi setup (which only forward-declares UITask) show
|
||||
// its Connecting / IP / Could not connect popups.
|
||||
// Lets the Settings screen (which only forward-declares UITask) show its
|
||||
// popups: WiFi Connecting / IP / Could not connect, and the repeat mode
|
||||
// frequency refusal, which any display build can show.
|
||||
void meckShowAlert(const char* text, int duration_millis) { ui_task.showAlert(text, duration_millis); }
|
||||
|
||||
#ifdef MECK_WIFI_COMPANION
|
||||
// Why the last WiFi connection attempt failed. The WiFi driver reports a
|
||||
// reason code each time a connection attempt drops; it is recorded here so
|
||||
// the "Could not connect" popups and the serial log can say why.
|
||||
|
||||
@@ -734,6 +734,14 @@ private:
|
||||
_prefs->freq, _prefs->bw, _prefs->sf, _prefs->cr, _prefs->tx_power_dbm);
|
||||
}
|
||||
|
||||
// Companion repeat mode is on (switched on from the app): a frequency
|
||||
// change was refused because it is not an allowed repeat frequency
|
||||
void alertRepeatFreq() {
|
||||
extern void meckShowAlert(const char* text, int duration_millis);
|
||||
meckShowAlert(MECK_TR("Repeat mode on: only\n433, 869.495 or 918 MHz",
|
||||
"R\xC3\xA9p\xC3\xA9teur actif : seuls\n433, 869.495, 918 MHz"), 2500);
|
||||
}
|
||||
|
||||
public:
|
||||
SettingsScreen(UITask* task, mesh::RTCClock* rtc, NodePrefs* prefs)
|
||||
: _task(task), _rtc(rtc), _prefs(prefs),
|
||||
@@ -3351,9 +3359,13 @@ public:
|
||||
if (_editPos > 0) {
|
||||
float f = strtof(_editBuf, nullptr);
|
||||
f = constrain(f, 400.0f, 2500.0f);
|
||||
_prefs->freq = f;
|
||||
_radioChanged = true;
|
||||
Serial.printf("Settings: Freq typed to %.3f\n", f);
|
||||
if (!the_mesh.isFreqChangeAllowed(f)) {
|
||||
alertRepeatFreq();
|
||||
} else {
|
||||
_prefs->freq = f;
|
||||
_radioChanged = true;
|
||||
Serial.printf("Settings: Freq typed to %.3f\n", f);
|
||||
}
|
||||
}
|
||||
_editMode = EDIT_NONE;
|
||||
} else if (type == ROW_ADD_CHANNEL) {
|
||||
@@ -3552,7 +3564,10 @@ public:
|
||||
_prefs->ui_font_style);
|
||||
} else {
|
||||
// Apply radio preset
|
||||
if (_editPickerIdx >= 0 && _editPickerIdx < (int)NUM_RADIO_PRESETS) {
|
||||
if (_editPickerIdx >= 0 && _editPickerIdx < (int)NUM_RADIO_PRESETS
|
||||
&& !the_mesh.isFreqChangeAllowed(RADIO_PRESETS[_editPickerIdx].freq)) {
|
||||
alertRepeatFreq();
|
||||
} else if (_editPickerIdx >= 0 && _editPickerIdx < (int)NUM_RADIO_PRESETS) {
|
||||
const RadioPreset& p = RADIO_PRESETS[_editPickerIdx];
|
||||
_prefs->freq = p.freq;
|
||||
_prefs->bw = p.bw;
|
||||
|
||||
Reference in new issue
Block a user