Merge branch 'ripplebiz:dev' into dev

This commit is contained in:
Rastislav Vysoky
2025-07-16 20:05:21 +02:00
committed by GitHub
18 changed files with 289 additions and 93 deletions

View File

@@ -154,7 +154,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.freq, sizeof(_prefs.freq)); // 56
file.read((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60 file.read((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60
file.read((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61 file.read((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61
file.read((uint8_t *)&_prefs.reserved1, sizeof(_prefs.reserved1)); // 62 file.read(pad, 1); // 62
file.read((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63 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.bw, sizeof(_prefs.bw)); // 64
file.read((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 file.read((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68
@@ -163,7 +163,8 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
file.read((uint8_t *)&_prefs.telemetry_mode_env, sizeof(_prefs.telemetry_mode_env)); // 71 file.read((uint8_t *)&_prefs.telemetry_mode_env, sizeof(_prefs.telemetry_mode_env)); // 71
file.read((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 file.read((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72
file.read((uint8_t *)&_prefs.advert_loc_policy, sizeof(_prefs.advert_loc_policy)); // 76 file.read((uint8_t *)&_prefs.advert_loc_policy, sizeof(_prefs.advert_loc_policy)); // 76
file.read(pad, 3); // 77 file.read((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77
file.read(pad, 2); // 78
file.read((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 file.read((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80
file.close(); file.close();
@@ -184,7 +185,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.freq, sizeof(_prefs.freq)); // 56
file.write((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60 file.write((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60
file.write((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61 file.write((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61
file.write((uint8_t *)&_prefs.reserved1, sizeof(_prefs.reserved1)); // 62 file.write(pad, 1); // 62
file.write((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63 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.bw, sizeof(_prefs.bw)); // 64
file.write((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 file.write((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68
@@ -193,7 +194,8 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
file.write((uint8_t *)&_prefs.telemetry_mode_env, sizeof(_prefs.telemetry_mode_env)); // 71 file.write((uint8_t *)&_prefs.telemetry_mode_env, sizeof(_prefs.telemetry_mode_env)); // 71
file.write((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 file.write((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72
file.write((uint8_t *)&_prefs.advert_loc_policy, sizeof(_prefs.advert_loc_policy)); // 76 file.write((uint8_t *)&_prefs.advert_loc_policy, sizeof(_prefs.advert_loc_policy)); // 76
file.write(pad, 3); // 77 file.write((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77
file.write(pad, 2); // 78
file.write((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 file.write((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80
file.close(); file.close();

View File

@@ -210,6 +210,10 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
return (int)((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time); return (int)((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time);
} }
uint8_t MyMesh::getExtraAckTransmitCount() const {
return _prefs.multi_acks;
}
void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) { void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) {
if (_serial->isConnected() && len + 3 <= MAX_FRAME_SIZE) { if (_serial->isConnected() && len + 3 <= MAX_FRAME_SIZE) {
int i = 0; int i = 0;
@@ -719,7 +723,7 @@ void MyMesh::handleCmdFrame(size_t len) {
i += 4; i += 4;
memcpy(&out_frame[i], &lon, 4); memcpy(&out_frame[i], &lon, 4);
i += 4; i += 4;
out_frame[i++] = 0; // reserved out_frame[i++] = _prefs.multi_acks; // new v7+
out_frame[i++] = _prefs.advert_loc_policy; out_frame[i++] = _prefs.advert_loc_policy;
out_frame[i++] = (_prefs.telemetry_mode_env << 4) | (_prefs.telemetry_mode_loc << 2) | out_frame[i++] = (_prefs.telemetry_mode_env << 4) | (_prefs.telemetry_mode_loc << 2) |
(_prefs.telemetry_mode_base); // v5+ (_prefs.telemetry_mode_base); // v5+
@@ -1050,6 +1054,9 @@ void MyMesh::handleCmdFrame(size_t len) {
if (len >= 4) { if (len >= 4) {
_prefs.advert_loc_policy = cmd_frame[3]; _prefs.advert_loc_policy = cmd_frame[3];
if (len >= 5) {
_prefs.multi_acks = cmd_frame[4];
}
} }
} }
savePrefs(); savePrefs();

View File

@@ -97,6 +97,7 @@ protected:
float getAirtimeBudgetFactor() const override; float getAirtimeBudgetFactor() const override;
int getInterferenceThreshold() const override; int getInterferenceThreshold() const override;
int calcRxDelay(float score, uint32_t air_time) const override; int calcRxDelay(float score, uint32_t air_time) const override;
uint8_t getExtraAckTransmitCount() const override;
void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override; void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override;
bool isAutoAddEnabled() const override; bool isAutoAddEnabled() const override;

View File

@@ -14,7 +14,7 @@ struct NodePrefs { // persisted to file
float freq; float freq;
uint8_t sf; uint8_t sf;
uint8_t cr; uint8_t cr;
uint8_t reserved1; uint8_t multi_acks;
uint8_t manual_add_contacts; uint8_t manual_add_contacts;
float bw; float bw;
uint8_t tx_power_dbm; uint8_t tx_power_dbm;

View File

@@ -408,8 +408,12 @@ void UITask::handleButtonQuadruplePress() {
if (strcmp(_sensors->getSettingName(i), "gps") == 0) { if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
if (strcmp(_sensors->getSettingValue(i), "1") == 0) { if (strcmp(_sensors->getSettingValue(i), "1") == 0) {
_sensors->setSettingValue("gps", "0"); _sensors->setSettingValue("gps", "0");
soundBuzzer(UIEventType::ack);
sprintf(_alert, "GPS: Disabled");
} else { } else {
_sensors->setSettingValue("gps", "1"); _sensors->setSettingValue("gps", "1");
soundBuzzer(UIEventType::ack);
sprintf(_alert, "GPS: Enabled");
} }
break; break;
} }

View File

@@ -339,6 +339,9 @@ protected:
int getAGCResetInterval() const override { int getAGCResetInterval() const override {
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
} }
uint8_t getExtraAckTransmitCount() const override {
return _prefs.multi_acks;
}
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override { void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override {
if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage) if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage)

View File

@@ -424,6 +424,9 @@ protected:
int getAGCResetInterval() const override { int getAGCResetInterval() const override {
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
} }
uint8_t getExtraAckTransmitCount() const override {
return _prefs.multi_acks;
}
bool allowPacketForward(const mesh::Packet* packet) override { bool allowPacketForward(const mesh::Packet* packet) override {
if (_prefs.disable_fwd) return false; if (_prefs.disable_fwd) return false;
@@ -578,15 +581,22 @@ protected:
uint32_t delay_millis; uint32_t delay_millis;
if (send_ack) { if (send_ack) {
mesh::Packet* ack = createAck(ack_hash); if (client->out_path_len < 0) {
if (ack) { mesh::Packet* ack = createAck(ack_hash);
if (client->out_path_len < 0) { if (ack) sendFlood(ack, TXT_ACK_DELAY);
sendFlood(ack, TXT_ACK_DELAY); delay_millis = TXT_ACK_DELAY + REPLY_DELAY_MILLIS;
} else { } else {
sendDirect(ack, client->out_path, client->out_path_len, TXT_ACK_DELAY); uint32_t d = TXT_ACK_DELAY;
if (getExtraAckTransmitCount() > 0) {
mesh::Packet* a1 = createMultiAck(ack_hash, 1);
if (a1) sendDirect(a1, client->out_path, client->out_path_len, d);
d += 300;
} }
mesh::Packet* a2 = createAck(ack_hash);
if (a2) sendDirect(a2, client->out_path, client->out_path_len, d);
delay_millis = d + REPLY_DELAY_MILLIS;
} }
delay_millis = TXT_ACK_DELAY + REPLY_DELAY_MILLIS;
} else { } else {
delay_millis = 0; delay_millis = 0;
} }

View File

@@ -243,7 +243,7 @@ static uint8_t putFloat(uint8_t * dest, float value, uint8_t size, uint32_t mult
uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len) { uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len) {
memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag') memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag')
if (req_type == REQ_TYPE_GET_TELEMETRY_DATA && (perms & PERM_GET_TELEMETRY) != 0) { if (req_type == REQ_TYPE_GET_TELEMETRY_DATA) { // allow all
telemetry.reset(); telemetry.reset();
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
// query other sensors -- target specific // query other sensors -- target specific
@@ -254,7 +254,7 @@ uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint
memcpy(&reply_data[4], telemetry.getBuffer(), tlen); memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
return 4 + tlen; // reply_len return 4 + tlen; // reply_len
} }
if (req_type == REQ_TYPE_GET_AVG_MIN_MAX && (perms & PERM_GET_OTHER_STATS) != 0) { if (req_type == REQ_TYPE_GET_AVG_MIN_MAX && (perms & PERM_ACL_ROLE_MASK) >= PERM_ACL_READ_ONLY) {
uint32_t start_secs_ago, end_secs_ago; uint32_t start_secs_ago, end_secs_ago;
memcpy(&start_secs_ago, &payload[0], 4); memcpy(&start_secs_ago, &payload[0], 4);
memcpy(&end_secs_ago, &payload[4], 4); memcpy(&end_secs_ago, &payload[4], 4);
@@ -288,13 +288,14 @@ uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint
} }
return ofs; return ofs;
} }
if (req_type == REQ_TYPE_GET_ACCESS_LIST && (perms & PERM_ACL_ROLE_MASK) == PERM_ACL_LEVEL3) { if (req_type == REQ_TYPE_GET_ACCESS_LIST && (perms & PERM_ACL_ROLE_MASK) == PERM_ACL_ADMIN) {
uint8_t res1 = payload[0]; // reserved for future (extra query params) uint8_t res1 = payload[0]; // reserved for future (extra query params)
uint8_t res2 = payload[1]; uint8_t res2 = payload[1];
if (res1 == 0 && res2 == 0) { if (res1 == 0 && res2 == 0) {
uint8_t ofs = 4; uint8_t ofs = 4;
for (int i = 0; i < num_contacts && ofs + 7 <= sizeof(reply_data) - 4; i++) { for (int i = 0; i < num_contacts && ofs + 7 <= sizeof(reply_data) - 4; i++) {
auto c = &contacts[i]; auto c = &contacts[i];
if (c->permissions == 0) continue; // skip deleted entries
memcpy(&reply_data[ofs], c->id.pub_key, 6); ofs += 6; // just 6-byte pub_key prefix memcpy(&reply_data[ofs], c->id.pub_key, 6); ofs += 6; // just 6-byte pub_key prefix
reply_data[ofs++] = c->permissions; reply_data[ofs++] = c->permissions;
} }
@@ -315,7 +316,14 @@ mesh::Packet* SensorMesh::createSelfAdvert() {
return createAdvert(self_id, app_data, app_data_len); return createAdvert(self_id, app_data, app_data_len);
} }
ContactInfo* SensorMesh::putContact(const mesh::Identity& id) { ContactInfo* SensorMesh::getContact(const uint8_t* pubkey, int key_len) {
for (int i = 0; i < num_contacts; i++) {
if (memcmp(pubkey, contacts[i].id.pub_key, key_len) == 0) return &contacts[i]; // already known
}
return NULL; // not found
}
ContactInfo* SensorMesh::putContact(const mesh::Identity& id, uint8_t init_perms) {
uint32_t min_time = 0xFFFFFFFF; uint32_t min_time = 0xFFFFFFFF;
ContactInfo* oldest = &contacts[MAX_CONTACTS - 1]; ContactInfo* oldest = &contacts[MAX_CONTACTS - 1];
for (int i = 0; i < num_contacts; i++) { for (int i = 0; i < num_contacts; i++) {
@@ -333,22 +341,35 @@ ContactInfo* SensorMesh::putContact(const mesh::Identity& id) {
c = oldest; // evict least active contact c = oldest; // evict least active contact
} }
memset(c, 0, sizeof(*c)); memset(c, 0, sizeof(*c));
c->permissions = init_perms;
c->id = id; c->id = id;
c->out_path_len = -1; // initially out_path is unknown c->out_path_len = -1; // initially out_path is unknown
return c; return c;
} }
void SensorMesh::applyContactPermissions(const uint8_t* pubkey, uint8_t perms) { bool SensorMesh::applyContactPermissions(const uint8_t* pubkey, int key_len, uint8_t perms) {
mesh::Identity id(pubkey); ContactInfo* c;
auto c = putContact(id);
if ((perms & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { // guest role is not persisted in contacts if ((perms & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { // guest role is not persisted in contacts
memset(c, 0, sizeof(*c)); c = getContact(pubkey, key_len);
if (c == NULL) return false; // partial pubkey not found
num_contacts--; // delete from contacts[]
int i = c - contacts;
while (i < num_contacts) {
contacts[i] = contacts[i + 1];
i++;
}
} else { } else {
if (key_len < PUB_KEY_SIZE) return false; // need complete pubkey when adding/modifying
mesh::Identity id(pubkey);
c = putContact(id, 0);
c->permissions = perms; // update their permissions c->permissions = perms; // update their permissions
self_id.calcSharedSecret(c->shared_secret, pubkey); self_id.calcSharedSecret(c->shared_secret, pubkey);
} }
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // trigger saveContacts() dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // trigger saveContacts()
return true;
} }
void SensorMesh::sendAlert(ContactInfo* c, Trigger* t) { void SensorMesh::sendAlert(ContactInfo* c, Trigger* t) {
@@ -434,32 +455,43 @@ int SensorMesh::getAGCResetInterval() const {
} }
uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) { uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) {
if (strcmp((char *) data, _prefs.password) != 0) { // check for valid password ContactInfo* client;
#if MESH_DEBUG if (data[0] == 0) { // blank password, just check if sender is in ACL
MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]); client = getContact(sender.pub_key, PUB_KEY_SIZE);
#endif if (client == NULL) {
return 0; #if MESH_DEBUG
MESH_DEBUG_PRINTLN("Login, sender not in ACL");
#endif
return 0;
}
} else {
if (strcmp((char *) data, _prefs.password) != 0) { // check for valid admin password
#if MESH_DEBUG
MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]);
#endif
return 0;
}
client = putContact(sender, PERM_RECV_ALERTS_HI | PERM_RECV_ALERTS_LO); // add to contacts (if not already known)
if (sender_timestamp <= client->last_timestamp) {
MESH_DEBUG_PRINTLN("Possible login replay attack!");
return 0; // FATAL: client table is full -OR- replay attack
}
MESH_DEBUG_PRINTLN("Login success!");
client->last_timestamp = sender_timestamp;
client->last_activity = getRTCClock()->getCurrentTime();
client->permissions |= PERM_ACL_ADMIN;
memcpy(client->shared_secret, secret, PUB_KEY_SIZE);
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
} }
auto client = putContact(sender); // add to contacts (if not already known)
if (sender_timestamp <= client->last_timestamp) {
MESH_DEBUG_PRINTLN("Possible login replay attack!");
return 0; // FATAL: client table is full -OR- replay attack
}
MESH_DEBUG_PRINTLN("Login success!");
client->last_timestamp = sender_timestamp;
client->last_activity = getRTCClock()->getCurrentTime();
client->permissions = PERM_ACL_LEVEL3 | PERM_RECV_ALERTS_HI | PERM_RECV_ALERTS_LO; // initially opt-in to receive alerts (can opt out)
memcpy(client->shared_secret, secret, PUB_KEY_SIZE);
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
uint32_t now = getRTCClock()->getCurrentTimeUnique(); uint32_t now = getRTCClock()->getCurrentTimeUnique();
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
reply_data[4] = RESP_SERVER_LOGIN_OK; reply_data[4] = RESP_SERVER_LOGIN_OK;
reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16) reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16)
reply_data[6] = 1; // 1 = is admin reply_data[6] = client->isAdmin() ? 1 : 0;
reply_data[7] = client->permissions; reply_data[7] = client->permissions;
getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness
@@ -484,16 +516,20 @@ void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* r
if (memcmp(command, "setperm ", 8) == 0) { // format: setperm {pubkey-hex} {permissions-int8} if (memcmp(command, "setperm ", 8) == 0) { // format: setperm {pubkey-hex} {permissions-int8}
char* hex = &command[8]; char* hex = &command[8];
char* sp = strchr(hex, ' '); // look for separator char char* sp = strchr(hex, ' '); // look for separator char
if (sp == NULL || sp - hex != PUB_KEY_SIZE*2) { if (sp == NULL) {
strcpy(reply, "Err - bad pubkey len"); strcpy(reply, "Err - bad params");
} else { } else {
*sp++ = 0; // replace space with null terminator *sp++ = 0; // replace space with null terminator
uint8_t pubkey[PUB_KEY_SIZE]; uint8_t pubkey[PUB_KEY_SIZE];
if (mesh::Utils::fromHex(pubkey, PUB_KEY_SIZE, hex)) { int hex_len = min(sp - hex, PUB_KEY_SIZE*2);
if (mesh::Utils::fromHex(pubkey, hex_len / 2, hex)) {
uint8_t perms = atoi(sp); uint8_t perms = atoi(sp);
applyContactPermissions(pubkey, perms); if (applyContactPermissions(pubkey, hex_len / 2, perms)) {
strcpy(reply, "OK"); strcpy(reply, "OK");
} else {
strcpy(reply, "Err - invalid params");
}
} else { } else {
strcpy(reply, "Err - bad pubkey"); strcpy(reply, "Err - bad pubkey");
} }
@@ -502,6 +538,7 @@ void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* r
Serial.println("ACL:"); Serial.println("ACL:");
for (int i = 0; i < num_contacts; i++) { for (int i = 0; i < num_contacts; i++) {
auto c = &contacts[i]; auto c = &contacts[i];
if (c->permissions == 0) continue; // skip deleted entries
Serial.printf("%02X ", c->permissions); Serial.printf("%02X ", c->permissions);
mesh::Utils::printHex(Serial, c->id.pub_key, PUB_KEY_SIZE); mesh::Utils::printHex(Serial, c->id.pub_key, PUB_KEY_SIZE);
@@ -569,7 +606,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
memcpy(&timestamp, data, 4); memcpy(&timestamp, data, 4);
if (timestamp > from.last_timestamp) { // prevent replay attacks if (timestamp > from.last_timestamp) { // prevent replay attacks
uint8_t reply_len = handleRequest(from.isAdmin() ? 0xFFFF : from.permissions, timestamp, data[4], &data[5], len - 5); uint8_t reply_len = handleRequest(from.isAdmin() ? 0xFF : from.permissions, timestamp, data[4], &data[5], len - 5);
if (reply_len == 0) return; // invalid command if (reply_len == 0) return; // invalid command
from.last_timestamp = timestamp; from.last_timestamp = timestamp;

View File

@@ -25,14 +25,14 @@
#define PERM_ACL_ROLE_MASK 3 // lower 2 bits #define PERM_ACL_ROLE_MASK 3 // lower 2 bits
#define PERM_ACL_GUEST 0 #define PERM_ACL_GUEST 0
#define PERM_ACL_LEVEL1 1 #define PERM_ACL_READ_ONLY 1
#define PERM_ACL_LEVEL2 2 #define PERM_ACL_READ_WRITE 2
#define PERM_ACL_LEVEL3 3 // admin #define PERM_ACL_ADMIN 3
#define PERM_GET_TELEMETRY (1 << 2) #define PERM_RESERVED1 (1 << 2)
#define PERM_GET_OTHER_STATS (1 << 3) #define PERM_RESERVED2 (1 << 3)
#define PERM_RESERVED1 (1 << 4) #define PERM_RESERVED3 (1 << 4)
#define PERM_RESERVED2 (1 << 5) #define PERM_RESERVED4 (1 << 5)
#define PERM_RECV_ALERTS_LO (1 << 6) // low priority alerts #define PERM_RECV_ALERTS_LO (1 << 6) // low priority alerts
#define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts #define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts
@@ -45,7 +45,7 @@ struct ContactInfo {
uint32_t last_timestamp; // by THEIR clock (transient) uint32_t last_timestamp; // by THEIR clock (transient)
uint32_t last_activity; // by OUR clock (transient) uint32_t last_activity; // by OUR clock (transient)
bool isAdmin() const { return (permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_LEVEL3; } bool isAdmin() const { return (permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_ADMIN; }
}; };
#ifndef FIRMWARE_BUILD_DATE #ifndef FIRMWARE_BUILD_DATE
@@ -160,8 +160,9 @@ private:
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data); uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data);
uint8_t handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len); uint8_t handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len);
mesh::Packet* createSelfAdvert(); mesh::Packet* createSelfAdvert();
ContactInfo* putContact(const mesh::Identity& id); ContactInfo* getContact(const uint8_t* pubkey, int key_len);
void applyContactPermissions(const uint8_t* pubkey, uint8_t perms); ContactInfo* putContact(const mesh::Identity& id, uint8_t init_perms);
bool applyContactPermissions(const uint8_t* pubkey, int key_len, uint8_t perms);
void sendAlert(ContactInfo* c, Trigger* t); void sendAlert(ContactInfo* c, Trigger* t);

View File

@@ -22,6 +22,9 @@ uint32_t Mesh::getRetransmitDelay(const mesh::Packet* packet) {
uint32_t Mesh::getDirectRetransmitDelay(const Packet* packet) { uint32_t Mesh::getDirectRetransmitDelay(const Packet* packet) {
return 0; // by default, no delay return 0; // by default, no delay
} }
uint8_t Mesh::getExtraAckTransmitCount() const {
return 0;
}
uint32_t Mesh::getCADFailRetryDelay() const { uint32_t Mesh::getCADFailRetryDelay() const {
return _rng->nextInt(1, 4)*120; return _rng->nextInt(1, 4)*120;
@@ -67,22 +70,22 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) { if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) {
if (self_id.isHashMatch(pkt->path) && allowPacketForward(pkt)) { if (self_id.isHashMatch(pkt->path) && allowPacketForward(pkt)) {
if (_tables->hasSeen(pkt)) return ACTION_RELEASE; // don't retransmit! if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) {
return forwardMultipartDirect(pkt);
// remove our hash from 'path', then re-broadcast } else if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) {
pkt->path_len -= PATH_HASH_SIZE; if (!_tables->hasSeen(pkt)) { // don't retransmit!
#if 0 removeSelfFromPath(pkt);
memcpy(pkt->path, &pkt->path[PATH_HASH_SIZE], pkt->path_len); routeDirectRecvAcks(pkt, 0);
#elif PATH_HASH_SIZE == 1 }
for (int k = 0; k < pkt->path_len; k++) { // shuffle bytes by 1 return ACTION_RELEASE;
pkt->path[k] = pkt->path[k + 1];
} }
#else
#error "need path remove impl"
#endif
uint32_t d = getDirectRetransmitDelay(pkt); if (!_tables->hasSeen(pkt)) {
return ACTION_RETRANSMIT_DELAYED(0, d); // Routed traffic is HIGHEST priority removeSelfFromPath(pkt);
uint32_t d = getDirectRetransmitDelay(pkt);
return ACTION_RETRANSMIT_DELAYED(0, d); // Routed traffic is HIGHEST priority
}
} }
return ACTION_RELEASE; // this node is NOT the next hop (OR this packet has already been forwarded), so discard. return ACTION_RELEASE; // this node is NOT the next hop (OR this packet has already been forwarded), so discard.
} }
@@ -261,6 +264,32 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
} }
break; break;
} }
case PAYLOAD_TYPE_MULTIPART:
if (pkt->payload_len > 2) {
uint8_t remaining = pkt->payload[0] >> 4; // num of packets in this multipart sequence still to be sent
uint8_t type = pkt->payload[0] & 0x0F;
if (type == PAYLOAD_TYPE_ACK && pkt->payload_len >= 5) { // a multipart ACK
Packet tmp;
tmp.header = pkt->header;
tmp.path_len = pkt->path_len;
memcpy(tmp.path, pkt->path, pkt->path_len);
tmp.payload_len = pkt->payload_len - 1;
memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len);
if (!_tables->hasSeen(&tmp)) {
uint32_t ack_crc;
memcpy(&ack_crc, tmp.payload, 4);
onAckRecv(&tmp, ack_crc);
//action = routeRecvPacket(&tmp); // NOTE: currently not needed, as multipart ACKs not sent Flood
}
} else {
// FUTURE: other multipart types??
}
}
break;
default: default:
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): unknown payload type, header: %d", getLogDateTime(), (int) pkt->header); MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): unknown payload type, header: %d", getLogDateTime(), (int) pkt->header);
// Don't flood route unknown packet types! action = routeRecvPacket(pkt); // Don't flood route unknown packet types! action = routeRecvPacket(pkt);
@@ -269,6 +298,20 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
return action; return action;
} }
void Mesh::removeSelfFromPath(Packet* pkt) {
// remove our hash from 'path'
pkt->path_len -= PATH_HASH_SIZE;
#if 0
memcpy(pkt->path, &pkt->path[PATH_HASH_SIZE], pkt->path_len);
#elif PATH_HASH_SIZE == 1
for (int k = 0; k < pkt->path_len; k++) { // shuffle bytes by 1
pkt->path[k] = pkt->path[k + 1];
}
#else
#error "need path remove impl"
#endif
}
DispatcherAction Mesh::routeRecvPacket(Packet* packet) { DispatcherAction Mesh::routeRecvPacket(Packet* packet) {
if (packet->isRouteFlood() && !packet->isMarkedDoNotRetransmit() if (packet->isRouteFlood() && !packet->isMarkedDoNotRetransmit()
&& packet->path_len + PATH_HASH_SIZE <= MAX_PATH_SIZE && allowPacketForward(packet)) { && packet->path_len + PATH_HASH_SIZE <= MAX_PATH_SIZE && allowPacketForward(packet)) {
@@ -282,6 +325,54 @@ DispatcherAction Mesh::routeRecvPacket(Packet* packet) {
return ACTION_RELEASE; return ACTION_RELEASE;
} }
DispatcherAction Mesh::forwardMultipartDirect(Packet* pkt) {
uint8_t remaining = pkt->payload[0] >> 4; // num of packets in this multipart sequence still to be sent
uint8_t type = pkt->payload[0] & 0x0F;
if (type == PAYLOAD_TYPE_ACK && pkt->payload_len >= 5) { // a multipart ACK
Packet tmp;
tmp.header = pkt->header;
tmp.path_len = pkt->path_len;
memcpy(tmp.path, pkt->path, pkt->path_len);
tmp.payload_len = pkt->payload_len - 1;
memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len);
if (!_tables->hasSeen(&tmp)) { // don't retransmit!
removeSelfFromPath(&tmp);
routeDirectRecvAcks(&tmp, ((uint32_t)remaining + 1) * 300); // expect multipart ACKs 300ms apart (x2)
}
}
return ACTION_RELEASE;
}
void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) {
if (!packet->isMarkedDoNotRetransmit()) {
uint32_t crc;
memcpy(&crc, packet->payload, 4);
uint8_t extra = getExtraAckTransmitCount();
while (extra > 0) {
delay_millis += getDirectRetransmitDelay(packet) + 300;
auto a1 = createMultiAck(crc, extra);
if (a1) {
memcpy(a1->path, packet->path, a1->path_len = packet->path_len);
a1->header &= ~PH_ROUTE_MASK;
a1->header |= ROUTE_TYPE_DIRECT;
sendPacket(a1, 0, delay_millis);
}
extra--;
}
auto a2 = createAck(crc);
if (a2) {
memcpy(a2->path, packet->path, a2->path_len = packet->path_len);
a2->header &= ~PH_ROUTE_MASK;
a2->header |= ROUTE_TYPE_DIRECT;
sendPacket(a2, 0, delay_millis);
}
}
}
Packet* Mesh::createAdvert(const LocalIdentity& id, const uint8_t* app_data, size_t app_data_len) { Packet* Mesh::createAdvert(const LocalIdentity& id, const uint8_t* app_data, size_t app_data_len) {
if (app_data_len > MAX_ADVERT_DATA_SIZE) return NULL; if (app_data_len > MAX_ADVERT_DATA_SIZE) return NULL;
@@ -449,6 +540,21 @@ Packet* Mesh::createAck(uint32_t ack_crc) {
return packet; return packet;
} }
Packet* Mesh::createMultiAck(uint32_t ack_crc, uint8_t remaining) {
Packet* packet = obtainNewPacket();
if (packet == NULL) {
MESH_DEBUG_PRINTLN("%s Mesh::createMultiAck(): error, packet pool empty", getLogDateTime());
return NULL;
}
packet->header = (PAYLOAD_TYPE_MULTIPART << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later
packet->payload[0] = (remaining << 4) | PAYLOAD_TYPE_ACK;
memcpy(&packet->payload[1], &ack_crc, 4);
packet->payload_len = 5;
return packet;
}
Packet* Mesh::createRawData(const uint8_t* data, size_t len) { Packet* Mesh::createRawData(const uint8_t* data, size_t len) {
if (len > sizeof(Packet::payload)) return NULL; // invalid arg if (len > sizeof(Packet::payload)) return NULL; // invalid arg

View File

@@ -28,6 +28,11 @@ class Mesh : public Dispatcher {
RNG* _rng; RNG* _rng;
MeshTables* _tables; MeshTables* _tables;
void removeSelfFromPath(Packet* packet);
void routeDirectRecvAcks(Packet* packet, uint32_t delay_millis);
//void routeRecvAcks(Packet* packet, uint32_t delay_millis);
DispatcherAction forwardMultipartDirect(Packet* pkt);
protected: protected:
DispatcherAction onRecvPacket(Packet* pkt) override; DispatcherAction onRecvPacket(Packet* pkt) override;
@@ -54,6 +59,11 @@ protected:
*/ */
virtual uint32_t getDirectRetransmitDelay(const Packet* packet); virtual uint32_t getDirectRetransmitDelay(const Packet* packet);
/**
* \returns number of extra (Direct) ACK transmissions wanted.
*/
virtual uint8_t getExtraAckTransmitCount() const;
/** /**
* \brief Perform search of local DB of peers/contacts. * \brief Perform search of local DB of peers/contacts.
* \returns Number of peers with matching hash * \returns Number of peers with matching hash
@@ -165,6 +175,7 @@ public:
Packet* createAnonDatagram(uint8_t type, const LocalIdentity& sender, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len); Packet* createAnonDatagram(uint8_t type, const LocalIdentity& sender, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len);
Packet* createGroupDatagram(uint8_t type, const GroupChannel& channel, const uint8_t* data, size_t data_len); Packet* createGroupDatagram(uint8_t type, const GroupChannel& channel, const uint8_t* data, size_t data_len);
Packet* createAck(uint32_t ack_crc); Packet* createAck(uint32_t ack_crc);
Packet* createMultiAck(uint32_t ack_crc, uint8_t remaining);
Packet* createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len); Packet* createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len);
Packet* createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len); Packet* createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len);
Packet* createRawData(const uint8_t* data, size_t len); Packet* createRawData(const uint8_t* data, size_t len);

View File

@@ -26,6 +26,7 @@ namespace mesh {
#define PAYLOAD_TYPE_ANON_REQ 0x07 // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...) #define PAYLOAD_TYPE_ANON_REQ 0x07 // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...)
#define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra) #define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra)
#define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNI for each hop #define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNI for each hop
#define PAYLOAD_TYPE_MULTIPART 0x0A // packet is one of a set of packets
//... //...
#define PAYLOAD_TYPE_RAW_CUSTOM 0x0F // custom packet as raw bytes, for applications with custom encryption, payloads, etc #define PAYLOAD_TYPE_RAW_CUSTOM 0x0F // custom packet as raw bytes, for applications with custom encryption, payloads, etc

View File

@@ -31,6 +31,23 @@ mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name, double lat, doubl
return createAdvert(self_id, app_data, app_data_len); return createAdvert(self_id, app_data, app_data_len);
} }
void BaseChatMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) {
if (dest.out_path_len < 0) {
mesh::Packet* ack = createAck(ack_hash);
if (ack) sendFlood(ack, TXT_ACK_DELAY);
} else {
uint32_t d = TXT_ACK_DELAY;
if (getExtraAckTransmitCount() > 0) {
mesh::Packet* a1 = createMultiAck(ack_hash, 1);
if (a1) sendDirect(a1, dest.out_path, dest.out_path_len, d);
d += 300;
}
mesh::Packet* a2 = createAck(ack_hash);
if (a2) sendDirect(a2, dest.out_path, dest.out_path_len, d);
}
}
void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) { void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) {
AdvertDataParser parser(app_data, app_data_len); AdvertDataParser parser(app_data, app_data_len);
if (!(parser.isValid() && parser.hasName())) { if (!(parser.isValid() && parser.hasName())) {
@@ -152,14 +169,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
if (path) sendFlood(path, TXT_ACK_DELAY); if (path) sendFlood(path, TXT_ACK_DELAY);
} else { } else {
mesh::Packet* ack = createAck(ack_hash); sendAckTo(from, ack_hash);
if (ack) {
if (from.out_path_len < 0) {
sendFlood(ack, TXT_ACK_DELAY);
} else {
sendDirect(ack, from.out_path, from.out_path_len, TXT_ACK_DELAY);
}
}
} }
} else if (flags == TXT_TYPE_CLI_DATA) { } else if (flags == TXT_TYPE_CLI_DATA) {
onCommandDataRecv(from, packet, timestamp, (const char *) &data[5]); // let UI know onCommandDataRecv(from, packet, timestamp, (const char *) &data[5]); // let UI know
@@ -185,14 +195,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
if (path) sendFlood(path, TXT_ACK_DELAY); if (path) sendFlood(path, TXT_ACK_DELAY);
} else { } else {
mesh::Packet* ack = createAck(ack_hash); sendAckTo(from, ack_hash);
if (ack) {
if (from.out_path_len < 0) {
sendFlood(ack, TXT_ACK_DELAY);
} else {
sendDirect(ack, from.out_path, from.out_path_len, TXT_ACK_DELAY);
}
}
} }
} else { } else {
MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported message type: %u", (uint32_t) flags); MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported message type: %u", (uint32_t) flags);

View File

@@ -72,6 +72,7 @@ class BaseChatMesh : public mesh::Mesh {
ConnectionInfo connections[MAX_CONNECTIONS]; ConnectionInfo connections[MAX_CONNECTIONS];
mesh::Packet* composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack); mesh::Packet* composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack);
void sendAckTo(const ContactInfo& dest, uint32_t ack_hash);
protected: protected:
BaseChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::PacketManager& mgr, mesh::MeshTables& tables) BaseChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::PacketManager& mgr, mesh::MeshTables& tables)

View File

@@ -51,7 +51,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
file.read((uint8_t *) &_prefs->sf, sizeof(_prefs->sf)); // 112 file.read((uint8_t *) &_prefs->sf, sizeof(_prefs->sf)); // 112
file.read((uint8_t *) &_prefs->cr, sizeof(_prefs->cr)); // 113 file.read((uint8_t *) &_prefs->cr, sizeof(_prefs->cr)); // 113
file.read((uint8_t *) &_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114 file.read((uint8_t *) &_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
file.read((uint8_t *) &_prefs->reserved2, sizeof(_prefs->reserved2)); // 115 file.read((uint8_t *) &_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115
file.read((uint8_t *) &_prefs->bw, sizeof(_prefs->bw)); // 116 file.read((uint8_t *) &_prefs->bw, sizeof(_prefs->bw)); // 116
file.read((uint8_t *) &_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120 file.read((uint8_t *) &_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
file.read(pad, 3); // 121 file.read(pad, 3); // 121
@@ -69,6 +69,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
_prefs->sf = constrain(_prefs->sf, 7, 12); _prefs->sf = constrain(_prefs->sf, 7, 12);
_prefs->cr = constrain(_prefs->cr, 5, 8); _prefs->cr = constrain(_prefs->cr, 5, 8);
_prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30); _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30);
_prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1);
file.close(); file.close();
} }
@@ -106,7 +107,7 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
file.write((uint8_t *) &_prefs->sf, sizeof(_prefs->sf)); // 112 file.write((uint8_t *) &_prefs->sf, sizeof(_prefs->sf)); // 112
file.write((uint8_t *) &_prefs->cr, sizeof(_prefs->cr)); // 113 file.write((uint8_t *) &_prefs->cr, sizeof(_prefs->cr)); // 113
file.write((uint8_t *) &_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114 file.write((uint8_t *) &_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
file.write((uint8_t *) &_prefs->reserved2, sizeof(_prefs->reserved2)); // 115 file.write((uint8_t *) &_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115
file.write((uint8_t *) &_prefs->bw, sizeof(_prefs->bw)); // 116 file.write((uint8_t *) &_prefs->bw, sizeof(_prefs->bw)); // 116
file.write((uint8_t *) &_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120 file.write((uint8_t *) &_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
file.write(pad, 3); // 121 file.write(pad, 3); // 121
@@ -180,6 +181,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold); sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold);
} else if (memcmp(config, "agc.reset.interval", 18) == 0) { } else if (memcmp(config, "agc.reset.interval", 18) == 0) {
sprintf(reply, "> %d", ((uint32_t) _prefs->agc_reset_interval) * 4); sprintf(reply, "> %d", ((uint32_t) _prefs->agc_reset_interval) * 4);
} else if (memcmp(config, "multi.acks", 10) == 0) {
sprintf(reply, "> %d", (uint32_t) _prefs->multi_acks);
} else if (memcmp(config, "allow.read.only", 15) == 0) { } else if (memcmp(config, "allow.read.only", 15) == 0) {
sprintf(reply, "> %s", _prefs->allow_read_only ? "on" : "off"); sprintf(reply, "> %s", _prefs->allow_read_only ? "on" : "off");
} else if (memcmp(config, "flood.advert.interval", 21) == 0) { } else if (memcmp(config, "flood.advert.interval", 21) == 0) {
@@ -235,6 +238,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
_prefs->agc_reset_interval = atoi(&config[19]) / 4; _prefs->agc_reset_interval = atoi(&config[19]) / 4;
savePrefs(); savePrefs();
strcpy(reply, "OK"); strcpy(reply, "OK");
} else if (memcmp(config, "multi.acks ", 11) == 0) {
_prefs->multi_acks = atoi(&config[11]);
savePrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "allow.read.only ", 16) == 0) { } else if (memcmp(config, "allow.read.only ", 16) == 0) {
_prefs->allow_read_only = memcmp(&config[16], "on", 2) == 0; _prefs->allow_read_only = memcmp(&config[16], "on", 2) == 0;
savePrefs(); savePrefs();

View File

@@ -21,7 +21,7 @@ struct NodePrefs { // persisted to file
uint8_t sf; uint8_t sf;
uint8_t cr; uint8_t cr;
uint8_t allow_read_only; uint8_t allow_read_only;
uint8_t reserved2; uint8_t multi_acks;
float bw; float bw;
uint8_t flood_max; uint8_t flood_max;
uint8_t interference_threshold; uint8_t interference_threshold;

View File

@@ -162,7 +162,7 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen
#if ENV_INCLUDE_BMP280 #if ENV_INCLUDE_BMP280
if (BMP280_initialized) { if (BMP280_initialized) {
telemetry.addTemperature(TELEM_CHANNEL_SELF, BMP280.readTemperature()); telemetry.addTemperature(TELEM_CHANNEL_SELF, BMP280.readTemperature());
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BMP280.readPressure()); telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BMP280.readPressure()/100);
telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA)); telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA));
} }
#endif #endif
@@ -332,4 +332,4 @@ void EnvironmentSensorManager::loop() {
next_gps_update = millis() + 1000; next_gps_update = millis() + 1000;
} }
} }
#endif #endif

View File

@@ -70,6 +70,8 @@
#define P_LORA_NSS (4) #define P_LORA_NSS (4)
#define SX126X_RXEN (5) #define SX126X_RXEN (5)
#define SX126X_TXEN RADIOLIB_NC #define SX126X_TXEN RADIOLIB_NC
#define SX126X_DIO2_AS_RF_SWITCH true
#define SX126X_DIO3_TCXO_VOLTAGE (1.8f)
// Wire Interfaces // Wire Interfaces
#define WIRE_INTERFACES_COUNT (2) #define WIRE_INTERFACES_COUNT (2)