* repeater: refactored 'region' CLI commands -> CommonCLI

* room server: added RegionMap, and new CommonCLI wiring, default_scope handling
* sensor: only minimal RegionMap wiring. Still needs work to handle default-scope
This commit is contained in:
Scott Powell
2026-04-15 13:32:49 +10:00
parent 569cfe177a
commit 4131a455a2
8 changed files with 777 additions and 564 deletions

View File

@@ -844,7 +844,9 @@ void MyMesh::sendNodeDiscoverReq() {
MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng,
mesh::RTCClock &rtc, mesh::MeshTables &tables) mesh::RTCClock &rtc, mesh::MeshTables &tables)
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
_cli(board, rtc, sensors, acl, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store), region_map(key_store), temp_map(key_store),
_cli(board, rtc, sensors, region_map, acl, &_prefs, this),
telemetry(MAX_PACKET_PAYLOAD - 4),
discover_limiter(4, 120), // max 4 every 2 minutes discover_limiter(4, 120), // max 4 every 2 minutes
anon_limiter(4, 180) // max 4 every 3 minutes anon_limiter(4, 180) // max 4 every 3 minutes
#if defined(WITH_RS232_BRIDGE) #if defined(WITH_RS232_BRIDGE)
@@ -1112,6 +1114,25 @@ void MyMesh::removeNeighbor(const uint8_t *pubkey, int key_len) {
#endif #endif
} }
void MyMesh::startRegionsLoad() {
temp_map.resetFrom(region_map); // rebuild regions in a temp instance
memset(load_stack, 0, sizeof(load_stack));
load_stack[0] = &temp_map.getWildcard();
region_load_active = true;
}
bool MyMesh::saveRegions() {
return region_map.save(_fs);
}
void MyMesh::onDefaultRegionChanged(const RegionEntry* r) {
if (r) {
region_map.getTransportKeysFor(*r, &default_scope, 1);
} else {
memset(default_scope.key, 0, sizeof(default_scope.key));
}
}
void MyMesh::formatStatsReply(char *reply) { void MyMesh::formatStatsReply(char *reply) {
StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr); StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr);
} }
@@ -1221,125 +1242,6 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
Serial.printf("\n"); Serial.printf("\n");
} }
reply[0] = 0; reply[0] = 0;
} else if (memcmp(command, "region", 6) == 0) {
reply[0] = 0;
const char* parts[4];
int n = mesh::Utils::parseTextParts(command, parts, 4, ' ');
if (n == 1) {
region_map.exportTo(reply, 160);
} else if (n >= 2 && strcmp(parts[1], "load") == 0) {
temp_map.resetFrom(region_map); // rebuild regions in a temp instance
memset(load_stack, 0, sizeof(load_stack));
load_stack[0] = &temp_map.getWildcard();
region_load_active = true;
} else if (n >= 2 && strcmp(parts[1], "save") == 0) {
_prefs.discovery_mod_timestamp = rtc_clock.getCurrentTime(); // this node is now 'modified' (for discovery info)
savePrefs();
bool success = region_map.save(_fs);
strcpy(reply, success ? "OK" : "Err - save failed");
} else if (n >= 3 && strcmp(parts[1], "allowf") == 0) {
auto region = region_map.findByNamePrefix(parts[2]);
if (region) {
region->flags &= ~REGION_DENY_FLOOD;
strcpy(reply, "OK");
} else {
strcpy(reply, "Err - unknown region");
}
} else if (n >= 3 && strcmp(parts[1], "denyf") == 0) {
auto region = region_map.findByNamePrefix(parts[2]);
if (region) {
region->flags |= REGION_DENY_FLOOD;
strcpy(reply, "OK");
} else {
strcpy(reply, "Err - unknown region");
}
} else if (n >= 3 && strcmp(parts[1], "get") == 0) {
auto region = region_map.findByNamePrefix(parts[2]);
if (region) {
auto parent = region_map.findById(region->parent);
if (parent && parent->id != 0) {
sprintf(reply, " %s (%s) %s", region->name, parent->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F");
} else {
sprintf(reply, " %s %s", region->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F");
}
} else {
strcpy(reply, "Err - unknown region");
}
} else if (n >= 3 && strcmp(parts[1], "home") == 0) {
auto home = region_map.findByNamePrefix(parts[2]);
if (home) {
region_map.setHomeRegion(home);
sprintf(reply, " home is now %s", home->name);
} else {
strcpy(reply, "Err - unknown region");
}
} else if (n == 2 && strcmp(parts[1], "home") == 0) {
auto home = region_map.getHomeRegion();
sprintf(reply, " home is %s", home ? home->name : "*");
} else if (n >= 3 && strcmp(parts[1], "default") == 0) {
if (strcmp(parts[2], "<null>") == 0) {
region_map.setDefaultRegion(NULL);
memset(default_scope.key, 0, sizeof(default_scope.key));
sprintf(reply, " default scope is now <null>");
} else {
auto def = region_map.findByNamePrefix(parts[2]);
if (def) {
region_map.setDefaultRegion(def);
region_map.getTransportKeysFor(*def, &default_scope, 1);
sprintf(reply, " default scope is now %s", def->name);
} else {
strcpy(reply, "Err - unknown region");
}
}
} else if (n == 2 && strcmp(parts[1], "default") == 0) {
auto def = region_map.getDefaultRegion();
sprintf(reply, " default scope is %s", def ? def->name : "<null>");
} else if (n >= 3 && strcmp(parts[1], "put") == 0) {
auto parent = n >= 4 ? region_map.findByNamePrefix(parts[3]) : &region_map.getWildcard();
if (parent == NULL) {
strcpy(reply, "Err - unknown parent");
} else {
auto region = region_map.putRegion(parts[2], parent->id);
if (region == NULL) {
strcpy(reply, "Err - unable to put");
} else {
strcpy(reply, "OK");
}
}
} else if (n >= 3 && strcmp(parts[1], "remove") == 0) {
auto region = region_map.findByName(parts[2]);
if (region) {
if (region_map.removeRegion(*region)) {
strcpy(reply, "OK");
} else {
strcpy(reply, "Err - not empty");
}
} else {
strcpy(reply, "Err - not found");
}
} else if (n >= 3 && strcmp(parts[1], "list") == 0) {
uint8_t mask = 0;
bool invert = false;
if (strcmp(parts[2], "allowed") == 0) {
mask = REGION_DENY_FLOOD;
invert = false; // list regions that DON'T have DENY flag
} else if (strcmp(parts[2], "denied") == 0) {
mask = REGION_DENY_FLOOD;
invert = true; // list regions that DO have DENY flag
} else {
strcpy(reply, "Err - use 'allowed' or 'denied'");
return;
}
int len = region_map.exportNamesTo(reply, 160, mask, invert);
if (len == 0) {
strcpy(reply, "-none-");
}
} else {
strcpy(reply, "Err - ??");
}
} else if (memcmp(command, "discover.neighbors", 18) == 0) { } else if (memcmp(command, "discover.neighbors", 18) == 0) {
const char* sub = command + 18; const char* sub = command + 18;
while (*sub == ' ') sub++; while (*sub == ' ') sub++;

View File

@@ -193,6 +193,8 @@ public:
} }
void sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint32_t delay_millis, uint8_t path_hash_size); void sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint32_t delay_millis, uint8_t path_hash_size);
// CommonCLICallbacks
void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override; void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override;
bool formatFileSystem() override; bool formatFileSystem() override;
void sendSelfAdvertisement(int delay_millis, bool flood) override; void sendSelfAdvertisement(int delay_millis, bool flood) override;
@@ -212,11 +214,15 @@ public:
void formatStatsReply(char *reply) override; void formatStatsReply(char *reply) override;
void formatRadioStatsReply(char *reply) override; void formatRadioStatsReply(char *reply) override;
void formatPacketStatsReply(char *reply) override; void formatPacketStatsReply(char *reply) override;
void startRegionsLoad() override;
bool saveRegions() override;
void onDefaultRegionChanged(const RegionEntry* r) override;
mesh::LocalIdentity& getSelfId() override { return self_id; } mesh::LocalIdentity& getSelfId() override { return self_id; }
void saveIdentity(const mesh::LocalIdentity& new_id) override; void saveIdentity(const mesh::LocalIdentity& new_id) override;
void clearStats() override; void clearStats() override;
void handleCommand(uint32_t sender_timestamp, char* command, char* reply); void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
void loop(); void loop();

View File

@@ -75,7 +75,7 @@ void MyMesh::pushPostToClient(ClientInfo *client, PostInfo &post) {
if (reply) { if (reply) {
if (client->out_path_len == OUT_PATH_UNKNOWN) { if (client->out_path_len == OUT_PATH_UNKNOWN) {
unsigned long delay_millis = 0; unsigned long delay_millis = 0;
sendFlood(reply, delay_millis, _prefs.path_hash_mode + 1); sendFloodScoped(default_scope, reply, delay_millis, _prefs.path_hash_mode + 1); // REVISIT
client->extra.room.ack_timeout = futureMillis(PUSH_ACK_TIMEOUT_FLOOD); client->extra.room.ack_timeout = futureMillis(PUSH_ACK_TIMEOUT_FLOOD);
} else { } else {
sendDirect(reply, client->out_path, client->out_path_len); sendDirect(reply, client->out_path, client->out_path_len);
@@ -286,6 +286,23 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
return true; return true;
} }
bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
// just try to determine region for packet (apply later in allowPacketForward())
if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) {
recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD);
} else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) {
if (region_map.getWildcard().flags & REGION_DENY_FLOOD) {
recv_pkt_region = NULL;
} else {
recv_pkt_region = &region_map.getWildcard();
}
} else {
recv_pkt_region = NULL;
}
// do normal processing
return false;
}
void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender,
uint8_t *data, size_t len) { uint8_t *data, size_t len) {
if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin
@@ -361,14 +378,14 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
mesh::Packet *path = createPathReturn(sender, client->shared_secret, packet->path, packet->path_len, mesh::Packet *path = createPathReturn(sender, client->shared_secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, reply_data, 13); PAYLOAD_TYPE_RESPONSE, reply_data, 13);
if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); if (path) sendFloodReply(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
} else { } else {
mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->shared_secret, reply_data, 13); mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->shared_secret, reply_data, 13);
if (reply) { if (reply) {
if (client->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT if (client->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT
sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY);
} else { } else {
sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
} }
} }
} }
@@ -458,7 +475,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
if (send_ack) { if (send_ack) {
if (client->out_path_len == OUT_PATH_UNKNOWN) { if (client->out_path_len == OUT_PATH_UNKNOWN) {
mesh::Packet *ack = createAck(ack_hash); mesh::Packet *ack = createAck(ack_hash);
if (ack) sendFlood(ack, TXT_ACK_DELAY, packet->getPathHashSize()); if (ack) sendFloodReply(ack, TXT_ACK_DELAY, packet->getPathHashSize());
delay_millis = TXT_ACK_DELAY + REPLY_DELAY_MILLIS; delay_millis = TXT_ACK_DELAY + REPLY_DELAY_MILLIS;
} else { } else {
uint32_t d = TXT_ACK_DELAY; uint32_t d = TXT_ACK_DELAY;
@@ -491,7 +508,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len); auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len);
if (reply) { if (reply) {
if (client->out_path_len == OUT_PATH_UNKNOWN) { if (client->out_path_len == OUT_PATH_UNKNOWN) {
sendFlood(reply, delay_millis + SERVER_RESPONSE_DELAY, packet->getPathHashSize()); sendFloodReply(reply, delay_millis + SERVER_RESPONSE_DELAY, packet->getPathHashSize());
} else { } else {
sendDirect(reply, client->out_path, client->out_path_len, delay_millis + SERVER_RESPONSE_DELAY); sendDirect(reply, client->out_path, client->out_path_len, delay_millis + SERVER_RESPONSE_DELAY);
} }
@@ -546,14 +563,14 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
mesh::Packet *path = createPathReturn(client->id, secret, packet->path, packet->path_len, mesh::Packet *path = createPathReturn(client->id, secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); if (path) sendFloodReply(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
} else { } else {
mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len);
if (reply) { if (reply) {
if (client->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT if (client->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT
sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY);
} else { } else {
sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
} }
} }
} }
@@ -595,12 +612,16 @@ void MyMesh::onAckRecv(mesh::Packet *packet, uint32_t ack_crc) {
MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng,
mesh::RTCClock &rtc, mesh::MeshTables &tables) mesh::RTCClock &rtc, mesh::MeshTables &tables)
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
_cli(board, rtc, sensors, acl, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { region_map(key_store), temp_map(key_store),
_cli(board, rtc, sensors, region_map, acl, &_prefs, this),
telemetry(MAX_PACKET_PAYLOAD - 4)
{
last_millis = 0; last_millis = 0;
uptime_millis = 0; uptime_millis = 0;
next_local_advert = next_flood_advert = 0; next_local_advert = next_flood_advert = 0;
dirty_contacts_expiry = 0; dirty_contacts_expiry = 0;
_logging = false; _logging = false;
region_load_active = false;
set_radio_at = revert_radio_at = 0; set_radio_at = revert_radio_at = 0;
// defaults // defaults
@@ -637,6 +658,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
next_push = 0; next_push = 0;
memset(posts, 0, sizeof(posts)); memset(posts, 0, sizeof(posts));
_num_posted = _num_post_pushes = 0; _num_posted = _num_post_pushes = 0;
memset(default_scope.key, 0, sizeof(default_scope.key));
} }
void MyMesh::begin(FILESYSTEM *fs) { void MyMesh::begin(FILESYSTEM *fs) {
@@ -646,6 +669,27 @@ void MyMesh::begin(FILESYSTEM *fs) {
_cli.loadPrefs(_fs); _cli.loadPrefs(_fs);
acl.load(_fs, self_id); acl.load(_fs, self_id);
region_map.load(_fs);
// establish default-scope
{
RegionEntry* r = region_map.getDefaultRegion();
if (r) {
region_map.getTransportKeysFor(*r, &default_scope, 1);
} else {
#ifdef DEFAULT_FLOOD_SCOPE_NAME
r = region_map.findByName(DEFAULT_FLOOD_SCOPE_NAME);
if (r == NULL) {
r = region_map.putRegion(DEFAULT_FLOOD_SCOPE_NAME, 0); // auto-create the default scope region
if (r) { r->flags = 0; } // Allow-flood
}
if (r) {
region_map.setDefaultRegion(r);
region_map.getTransportKeysFor(*r, &default_scope, 1);
}
#endif
}
}
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
radio_set_tx_power(_prefs.tx_power_dbm); radio_set_tx_power(_prefs.tx_power_dbm);
@@ -660,6 +704,30 @@ void MyMesh::begin(FILESYSTEM *fs) {
#endif #endif
} }
void MyMesh::sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint32_t delay_millis, uint8_t path_hash_size) {
if (scope.isNull()) {
sendFlood(pkt, delay_millis, path_hash_size);
} else {
uint16_t codes[2];
codes[0] = scope.calcTransportCode(pkt);
codes[1] = 0; // REVISIT: set to 'home' Region, for sender/return region?
sendFlood(pkt, codes, delay_millis, path_hash_size);
}
}
void MyMesh::sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size) {
if (recv_pkt_region) { // if _request_ packet scope is known, send reply with same scope
TransportKey scope;
if (region_map.getTransportKeysFor(*recv_pkt_region, &scope, 1) == 0) {
sendFloodScoped(default_scope, packet, delay_millis, path_hash_size);
} else {
sendFloodScoped(scope, packet, delay_millis, path_hash_size);
}
} else {
sendFloodScoped(default_scope, packet, delay_millis, path_hash_size);
}
}
void MyMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) { void MyMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) {
set_radio_at = futureMillis(2000); // give CLI reply some time to be sent back, before applying temp radio params set_radio_at = futureMillis(2000); // give CLI reply some time to be sent back, before applying temp radio params
pending_freq = freq; pending_freq = freq;
@@ -687,7 +755,7 @@ void MyMesh::sendSelfAdvertisement(int delay_millis, bool flood) {
mesh::Packet *pkt = createSelfAdvert(); mesh::Packet *pkt = createSelfAdvert();
if (pkt) { if (pkt) {
if (flood) { if (flood) {
sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); sendFloodScoped(default_scope, pkt, delay_millis, _prefs.path_hash_mode + 1);
} else { } else {
sendZeroHop(pkt, delay_millis); sendZeroHop(pkt, delay_millis);
} }
@@ -744,6 +812,25 @@ void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) {
store.save("_main", new_id); store.save("_main", new_id);
} }
void MyMesh::startRegionsLoad() {
temp_map.resetFrom(region_map); // rebuild regions in a temp instance
memset(load_stack, 0, sizeof(load_stack));
load_stack[0] = &temp_map.getWildcard();
region_load_active = true;
}
bool MyMesh::saveRegions() {
return region_map.save(_fs);
}
void MyMesh::onDefaultRegionChanged(const RegionEntry* r) {
if (r) {
region_map.getTransportKeysFor(*r, &default_scope, 1);
} else {
memset(default_scope.key, 0, sizeof(default_scope.key));
}
}
void MyMesh::clearStats() { void MyMesh::clearStats() {
radio_driver.resetStats(); radio_driver.resetStats();
resetStats(); resetStats();
@@ -764,6 +851,40 @@ void MyMesh::formatPacketStatsReply(char *reply) {
} }
void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) { void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) {
if (region_load_active) {
if (StrHelper::isBlank(command)) { // empty/blank line, signal to terminate 'load' operation
region_map = temp_map; // copy over the temp instance as new current map
region_load_active = false;
sprintf(reply, "OK - loaded %d regions", region_map.getCount());
} else {
char *np = command;
while (*np == ' ') np++; // skip indent
int indent = np - command;
char *ep = np;
while (RegionMap::is_name_char(*ep)) ep++;
if (*ep) { *ep++ = 0; } // set null terminator for end of name
while (*ep && *ep != 'F') ep++; // look for (optional) flags
if (indent > 0 && indent < 8 && strlen(np) > 0) {
auto parent = load_stack[indent - 1];
if (parent) {
auto old = region_map.findByName(np);
auto nw = temp_map.putRegion(np, parent->id, old ? old->id : 0); // carry-over the current ID (if name already exists)
if (nw) {
nw->flags = old ? old->flags : (*ep == 'F' ? 0 : REGION_DENY_FLOOD); // carry-over flags from curr
load_stack[indent] = nw; // keep pointers to parent regions, to resolve parent_id's
}
}
}
reply[0] = 0;
}
return;
}
while (*command == ' ') while (*command == ' ')
command++; // skip leading spaces command++; // skip leading spaces
@@ -865,7 +986,7 @@ void MyMesh::loop() {
if (next_flood_advert && millisHasNowPassed(next_flood_advert)) { if (next_flood_advert && millisHasNowPassed(next_flood_advert)) {
mesh::Packet *pkt = createSelfAdvert(); mesh::Packet *pkt = createSelfAdvert();
uint32_t delay_millis = 0; uint32_t delay_millis = 0;
if (pkt) sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); if (pkt) sendFloodScoped(default_scope, pkt, delay_millis, _prefs.path_hash_mode + 1);
updateFloodAdvertTimer(); // schedule next flood advert updateFloodAdvertTimer(); // schedule next flood advert
updateAdvertTimer(); // also schedule local advert (so they don't overlap) updateAdvertTimer(); // also schedule local advert (so they don't overlap)

View File

@@ -20,6 +20,7 @@
#include <helpers/CommonCLI.h> #include <helpers/CommonCLI.h>
#include <helpers/StatsFormatHelper.h> #include <helpers/StatsFormatHelper.h>
#include <helpers/ClientACL.h> #include <helpers/ClientACL.h>
#include <helpers/RegionMap.h>
#include <RTClib.h> #include <RTClib.h>
#include <target.h> #include <target.h>
@@ -93,7 +94,10 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
uint64_t uptime_millis; uint64_t uptime_millis;
unsigned long next_local_advert, next_flood_advert; unsigned long next_local_advert, next_flood_advert;
bool _logging; bool _logging;
bool region_load_active;
NodePrefs _prefs; NodePrefs _prefs;
TransportKeyStore key_store;
RegionMap region_map, temp_map;
ClientACL acl; ClientACL acl;
CommonCLI _cli; CommonCLI _cli;
unsigned long dirty_contacts_expiry; unsigned long dirty_contacts_expiry;
@@ -104,6 +108,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
int next_post_idx; int next_post_idx;
PostInfo posts[MAX_UNSYNCED_POSTS]; // cyclic queue PostInfo posts[MAX_UNSYNCED_POSTS]; // cyclic queue
CayenneLPP telemetry; CayenneLPP telemetry;
RegionEntry* load_stack[8];
RegionEntry* recv_pkt_region;
TransportKey default_scope;
unsigned long set_radio_at, revert_radio_at; unsigned long set_radio_at, revert_radio_at;
float pending_freq; float pending_freq;
float pending_bw; float pending_bw;
@@ -144,6 +151,8 @@ protected:
return _prefs.multi_acks; return _prefs.multi_acks;
} }
bool filterRecvFloodPacket(mesh::Packet* pkt) override;
bool allowPacketForward(const mesh::Packet* packet) override; bool allowPacketForward(const mesh::Packet* packet) override;
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;
int searchPeersByHash(const uint8_t* hash) override ; int searchPeersByHash(const uint8_t* hash) override ;
@@ -158,6 +167,8 @@ protected:
} }
#endif #endif
void sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size);
public: public:
MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables); MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables);
@@ -175,6 +186,9 @@ public:
_cli.savePrefs(_fs); _cli.savePrefs(_fs);
} }
void sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint32_t delay_millis, uint8_t path_hash_size);
// CommonCLICallbacks
void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override; void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override;
bool formatFileSystem() override; bool formatFileSystem() override;
void sendSelfAdvertisement(int delay_millis, bool flood) override; void sendSelfAdvertisement(int delay_millis, bool flood) override;
@@ -196,6 +210,9 @@ public:
void formatStatsReply(char *reply) override; void formatStatsReply(char *reply) override;
void formatRadioStatsReply(char *reply) override; void formatRadioStatsReply(char *reply) override;
void formatPacketStatsReply(char *reply) override; void formatPacketStatsReply(char *reply) override;
void startRegionsLoad() override;
bool saveRegions() override;
void onDefaultRegionChanged(const RegionEntry* r) override;
mesh::LocalIdentity& getSelfId() override { return self_id; } mesh::LocalIdentity& getSelfId() override { return self_id; }

View File

@@ -696,7 +696,9 @@ void SensorMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) {
SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables) SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
_cli(board, rtc, sensors, acl, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) region_map(key_store),
_cli(board, rtc, sensors, region_map, acl, &_prefs, this),
telemetry(MAX_PACKET_PAYLOAD - 4)
{ {
next_local_advert = next_flood_advert = 0; next_local_advert = next_flood_advert = 0;
dirty_contacts_expiry = 0; dirty_contacts_expiry = 0;
@@ -729,6 +731,8 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
_prefs.gps_enabled = 0; _prefs.gps_enabled = 0;
_prefs.gps_interval = 0; _prefs.gps_interval = 0;
_prefs.advert_loc_policy = ADVERT_LOC_PREFS; _prefs.advert_loc_policy = ADVERT_LOC_PREFS;
memset(default_scope.key, 0, sizeof(default_scope.key));
} }
void SensorMesh::begin(FILESYSTEM* fs) { void SensorMesh::begin(FILESYSTEM* fs) {
@@ -738,6 +742,27 @@ void SensorMesh::begin(FILESYSTEM* fs) {
_cli.loadPrefs(_fs); _cli.loadPrefs(_fs);
acl.load(_fs, self_id); acl.load(_fs, self_id);
region_map.load(_fs);
// establish default-scope
{
RegionEntry* r = region_map.getDefaultRegion();
if (r) {
region_map.getTransportKeysFor(*r, &default_scope, 1);
} else {
#ifdef DEFAULT_FLOOD_SCOPE_NAME
r = region_map.findByName(DEFAULT_FLOOD_SCOPE_NAME);
if (r == NULL) {
r = region_map.putRegion(DEFAULT_FLOOD_SCOPE_NAME, 0); // auto-create the default scope region
if (r) { r->flags = 0; } // Allow-flood
}
if (r) {
region_map.setDefaultRegion(r);
region_map.getTransportKeysFor(*r, &default_scope, 1);
}
#endif
}
}
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
radio_set_tx_power(_prefs.tx_power_dbm); radio_set_tx_power(_prefs.tx_power_dbm);

View File

@@ -22,6 +22,7 @@
#include <helpers/CommonCLI.h> #include <helpers/CommonCLI.h>
#include <helpers/StatsFormatHelper.h> #include <helpers/StatsFormatHelper.h>
#include <helpers/ClientACL.h> #include <helpers/ClientACL.h>
#include <helpers/RegionMap.h>
#include <RTClib.h> #include <RTClib.h>
#include <target.h> #include <target.h>
@@ -138,6 +139,9 @@ private:
uint8_t reply_data[MAX_PACKET_PAYLOAD]; uint8_t reply_data[MAX_PACKET_PAYLOAD];
unsigned long dirty_contacts_expiry; unsigned long dirty_contacts_expiry;
CayenneLPP telemetry; CayenneLPP telemetry;
TransportKeyStore key_store;
RegionMap region_map;
TransportKey default_scope;
uint32_t last_read_time; uint32_t last_read_time;
int matching_peer_indexes[MAX_SEARCH_RESULTS]; int matching_peer_indexes[MAX_SEARCH_RESULTS];
int num_alert_tasks; int num_alert_tasks;

View File

@@ -207,7 +207,7 @@ uint8_t CommonCLI::buildAdvertData(uint8_t node_type, uint8_t* app_data) {
} }
} }
void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) { void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* reply) {
if (memcmp(command, "poweroff", 8) == 0 || memcmp(command, "shutdown", 8) == 0) { if (memcmp(command, "poweroff", 8) == 0 || memcmp(command, "shutdown", 8) == 0) {
_board->powerOff(); // doesn't return _board->powerOff(); // doesn't return
} else if (memcmp(command, "reboot", 6) == 0) { } else if (memcmp(command, "reboot", 6) == 0) {
@@ -289,172 +289,180 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
} else if (memcmp(command, "clear stats", 11) == 0) { } else if (memcmp(command, "clear stats", 11) == 0) {
_callbacks->clearStats(); _callbacks->clearStats();
strcpy(reply, "(OK - stats reset)"); strcpy(reply, "(OK - stats reset)");
/*
* GET commands
*/
} else if (memcmp(command, "get ", 4) == 0) { } else if (memcmp(command, "get ", 4) == 0) {
const char* config = &command[4]; handleGetCmd(sender_timestamp, command, reply);
if (memcmp(config, "dutycycle", 9) == 0) {
float dc = 100.0f / (_prefs->airtime_factor + 1.0f);
int dc_int = (int)dc;
int dc_frac = (int)((dc - dc_int) * 10.0f + 0.5f);
sprintf(reply, "> %d.%d%%", dc_int, dc_frac);
} else if (memcmp(config, "af", 2) == 0) {
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->airtime_factor));
} else if (memcmp(config, "int.thresh", 10) == 0) {
sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold);
} else if (memcmp(config, "agc.reset.interval", 18) == 0) {
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) {
sprintf(reply, "> %s", _prefs->allow_read_only ? "on" : "off");
} else if (memcmp(config, "flood.advert.interval", 21) == 0) {
sprintf(reply, "> %d", ((uint32_t) _prefs->flood_advert_interval));
} else if (memcmp(config, "advert.interval", 15) == 0) {
sprintf(reply, "> %d", ((uint32_t) _prefs->advert_interval) * 2);
} else if (memcmp(config, "guest.password", 14) == 0) {
sprintf(reply, "> %s", _prefs->guest_password);
} else if (sender_timestamp == 0 && memcmp(config, "prv.key", 7) == 0) { // from serial command line only
uint8_t prv_key[PRV_KEY_SIZE];
int len = _callbacks->getSelfId().writeTo(prv_key, PRV_KEY_SIZE);
mesh::Utils::toHex(tmp, prv_key, len);
sprintf(reply, "> %s", tmp);
} else if (memcmp(config, "name", 4) == 0) {
sprintf(reply, "> %s", _prefs->node_name);
} else if (memcmp(config, "repeat", 6) == 0) {
sprintf(reply, "> %s", _prefs->disable_fwd ? "off" : "on");
} else if (memcmp(config, "lat", 3) == 0) {
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lat));
} else if (memcmp(config, "lon", 3) == 0) {
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lon));
#if defined(USE_SX1262) || defined(USE_SX1268)
} else if (memcmp(config, "radio.rxgain", 12) == 0) {
sprintf(reply, "> %s", _prefs->rx_boosted_gain ? "on" : "off");
#endif
} else if (memcmp(config, "radio", 5) == 0) {
char freq[16], bw[16];
strcpy(freq, StrHelper::ftoa(_prefs->freq));
strcpy(bw, StrHelper::ftoa3(_prefs->bw));
sprintf(reply, "> %s,%s,%d,%d", freq, bw, (uint32_t)_prefs->sf, (uint32_t)_prefs->cr);
} else if (memcmp(config, "rxdelay", 7) == 0) {
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->rx_delay_base));
} else if (memcmp(config, "txdelay", 7) == 0) {
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->tx_delay_factor));
} else if (memcmp(config, "flood.max", 9) == 0) {
sprintf(reply, "> %d", (uint32_t)_prefs->flood_max);
} else if (memcmp(config, "direct.txdelay", 14) == 0) {
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->direct_tx_delay_factor));
} else if (memcmp(config, "owner.info", 10) == 0) {
*reply++ = '>';
*reply++ = ' ';
const char* sp = _prefs->owner_info;
while (*sp) {
*reply++ = (*sp == '\n') ? '|' : *sp; // translate newline back to orig '|'
sp++;
}
*reply = 0; // set null terminator
} else if (memcmp(config, "path.hash.mode", 14) == 0) {
sprintf(reply, "> %d", (uint32_t)_prefs->path_hash_mode);
} else if (memcmp(config, "loop.detect", 11) == 0) {
if (_prefs->loop_detect == LOOP_DETECT_OFF) {
strcpy(reply, "> off");
} else if (_prefs->loop_detect == LOOP_DETECT_MINIMAL) {
strcpy(reply, "> minimal");
} else if (_prefs->loop_detect == LOOP_DETECT_MODERATE) {
strcpy(reply, "> moderate");
} else {
strcpy(reply, "> strict");
}
} else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) {
sprintf(reply, "> %d", (int32_t) _prefs->tx_power_dbm);
} else if (memcmp(config, "freq", 4) == 0) {
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->freq));
} else if (memcmp(config, "public.key", 10) == 0) {
strcpy(reply, "> ");
mesh::Utils::toHex(&reply[2], _callbacks->getSelfId().pub_key, PUB_KEY_SIZE);
} else if (memcmp(config, "role", 4) == 0) {
sprintf(reply, "> %s", _callbacks->getRole());
} else if (memcmp(config, "bridge.type", 11) == 0) {
sprintf(reply, "> %s",
#ifdef WITH_RS232_BRIDGE
"rs232"
#elif WITH_ESPNOW_BRIDGE
"espnow"
#else
"none"
#endif
);
#ifdef WITH_BRIDGE
} else if (memcmp(config, "bridge.enabled", 14) == 0) {
sprintf(reply, "> %s", _prefs->bridge_enabled ? "on" : "off");
} else if (memcmp(config, "bridge.delay", 12) == 0) {
sprintf(reply, "> %d", (uint32_t)_prefs->bridge_delay);
} else if (memcmp(config, "bridge.source", 13) == 0) {
sprintf(reply, "> %s", _prefs->bridge_pkt_src ? "logRx" : "logTx");
#endif
#ifdef WITH_RS232_BRIDGE
} else if (memcmp(config, "bridge.baud", 11) == 0) {
sprintf(reply, "> %d", (uint32_t)_prefs->bridge_baud);
#endif
#ifdef WITH_ESPNOW_BRIDGE
} else if (memcmp(config, "bridge.channel", 14) == 0) {
sprintf(reply, "> %d", (uint32_t)_prefs->bridge_channel);
} else if (memcmp(config, "bridge.secret", 13) == 0) {
sprintf(reply, "> %s", _prefs->bridge_secret);
#endif
} else if (memcmp(config, "bootloader.ver", 14) == 0) {
#ifdef NRF52_PLATFORM
char ver[32];
if (_board->getBootloaderVersion(ver, sizeof(ver))) {
sprintf(reply, "> %s", ver);
} else {
strcpy(reply, "> unknown");
}
#else
strcpy(reply, "ERROR: unsupported");
#endif
} else if (memcmp(config, "adc.multiplier", 14) == 0) {
float adc_mult = _board->getAdcMultiplier();
if (adc_mult == 0.0f) {
strcpy(reply, "Error: unsupported by this board");
} else {
sprintf(reply, "> %.3f", adc_mult);
}
// Power management commands
} else if (memcmp(config, "pwrmgt.support", 14) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
strcpy(reply, "> supported");
#else
strcpy(reply, "> unsupported");
#endif
} else if (memcmp(config, "pwrmgt.source", 13) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
strcpy(reply, _board->isExternalPowered() ? "> external" : "> battery");
#else
strcpy(reply, "ERROR: Power management not supported");
#endif
} else if (memcmp(config, "pwrmgt.bootreason", 17) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
sprintf(reply, "> Reset: %s; Shutdown: %s",
_board->getResetReasonString(_board->getResetReason()),
_board->getShutdownReasonString(_board->getShutdownReason()));
#else
strcpy(reply, "ERROR: Power management not supported");
#endif
} else if (memcmp(config, "pwrmgt.bootmv", 13) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
sprintf(reply, "> %u mV", _board->getBootVoltage());
#else
strcpy(reply, "ERROR: Power management not supported");
#endif
} else {
sprintf(reply, "??: %s", config);
}
/*
* SET commands
*/
} else if (memcmp(command, "set ", 4) == 0) { } else if (memcmp(command, "set ", 4) == 0) {
handleSetCmd(sender_timestamp, command, reply);
} else if (sender_timestamp == 0 && strcmp(command, "erase") == 0) {
bool s = _callbacks->formatFileSystem();
sprintf(reply, "File system erase: %s", s ? "OK" : "Err");
} else if (memcmp(command, "ver", 3) == 0) {
sprintf(reply, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate());
} else if (memcmp(command, "board", 5) == 0) {
sprintf(reply, "%s", _board->getManufacturerName());
} else if (memcmp(command, "sensor get ", 11) == 0) {
const char* key = command + 11;
const char* val = _sensors->getSettingByKey(key);
if (val != NULL) {
sprintf(reply, "> %s", val);
} else {
strcpy(reply, "null");
}
} else if (memcmp(command, "sensor set ", 11) == 0) {
strcpy(tmp, &command[11]);
const char *parts[2];
int num = mesh::Utils::parseTextParts(tmp, parts, 2, ' ');
const char *key = (num > 0) ? parts[0] : "";
const char *value = (num > 1) ? parts[1] : "null";
if (_sensors->setSettingValue(key, value)) {
strcpy(reply, "ok");
} else {
strcpy(reply, "can't find custom var");
}
} else if (memcmp(command, "sensor list", 11) == 0) {
char* dp = reply;
int start = 0;
int end = _sensors->getNumSettings();
if (strlen(command) > 11) {
start = _atoi(command+12);
}
if (start >= end) {
strcpy(reply, "no custom var");
} else {
sprintf(dp, "%d vars\n", end);
dp = strchr(dp, 0);
int i;
for (i = start; i < end && (dp-reply < 134); i++) {
sprintf(dp, "%s=%s\n",
_sensors->getSettingName(i),
_sensors->getSettingValue(i));
dp = strchr(dp, 0);
}
if (i < end) {
sprintf(dp, "... next:%d", i);
} else {
*(dp-1) = 0; // remove last CR
}
}
} else if (memcmp(command, "region", 6) == 0) {
handleRegionCmd(command, reply);
#if ENV_INCLUDE_GPS == 1
} else if (memcmp(command, "gps on", 6) == 0) {
if (_sensors->setSettingValue("gps", "1")) {
_prefs->gps_enabled = 1;
savePrefs();
strcpy(reply, "ok");
} else {
strcpy(reply, "gps toggle not found");
}
} else if (memcmp(command, "gps off", 7) == 0) {
if (_sensors->setSettingValue("gps", "0")) {
_prefs->gps_enabled = 0;
savePrefs();
strcpy(reply, "ok");
} else {
strcpy(reply, "gps toggle not found");
}
} else if (memcmp(command, "gps sync", 8) == 0) {
LocationProvider * l = _sensors->getLocationProvider();
if (l != NULL) {
l->syncTime();
strcpy(reply, "ok");
} else {
strcpy(reply, "gps provider not found");
}
} else if (memcmp(command, "gps setloc", 10) == 0) {
_prefs->node_lat = _sensors->node_lat;
_prefs->node_lon = _sensors->node_lon;
savePrefs();
strcpy(reply, "ok");
} else if (memcmp(command, "gps advert", 10) == 0) {
if (strlen(command) == 10) {
switch (_prefs->advert_loc_policy) {
case ADVERT_LOC_NONE:
strcpy(reply, "> none");
break;
case ADVERT_LOC_PREFS:
strcpy(reply, "> prefs");
break;
case ADVERT_LOC_SHARE:
strcpy(reply, "> share");
break;
default:
strcpy(reply, "error");
}
} else if (memcmp(command+11, "none", 4) == 0) {
_prefs->advert_loc_policy = ADVERT_LOC_NONE;
savePrefs();
strcpy(reply, "ok");
} else if (memcmp(command+11, "share", 5) == 0) {
_prefs->advert_loc_policy = ADVERT_LOC_SHARE;
savePrefs();
strcpy(reply, "ok");
} else if (memcmp(command+11, "prefs", 5) == 0) {
_prefs->advert_loc_policy = ADVERT_LOC_PREFS;
savePrefs();
strcpy(reply, "ok");
} else {
strcpy(reply, "error");
}
} else if (memcmp(command, "gps", 3) == 0) {
LocationProvider * l = _sensors->getLocationProvider();
if (l != NULL) {
bool enabled = l->isEnabled(); // is EN pin on ?
bool fix = l->isValid(); // has fix ?
int sats = l->satellitesCount();
bool active = !strcmp(_sensors->getSettingByKey("gps"), "1");
if (enabled) {
sprintf(reply, "on, %s, %s, %d sats",
active?"active":"deactivated",
fix?"fix":"no fix",
sats);
} else {
strcpy(reply, "off");
}
} else {
strcpy(reply, "Can't find GPS");
}
#endif
} else if (memcmp(command, "powersaving on", 14) == 0) {
_prefs->powersaving_enabled = 1;
savePrefs();
strcpy(reply, "ok"); // TODO: to return Not supported if required
} else if (memcmp(command, "powersaving off", 15) == 0) {
_prefs->powersaving_enabled = 0;
savePrefs();
strcpy(reply, "ok");
} else if (memcmp(command, "powersaving", 11) == 0) {
if (_prefs->powersaving_enabled) {
strcpy(reply, "on");
} else {
strcpy(reply, "off");
}
} else if (memcmp(command, "log start", 9) == 0) {
_callbacks->setLoggingOn(true);
strcpy(reply, " logging on");
} else if (memcmp(command, "log stop", 8) == 0) {
_callbacks->setLoggingOn(false);
strcpy(reply, " logging off");
} else if (memcmp(command, "log erase", 9) == 0) {
_callbacks->eraseLogFile();
strcpy(reply, " log erased");
} else if (sender_timestamp == 0 && memcmp(command, "log", 3) == 0) {
_callbacks->dumpLogFile();
strcpy(reply, " EOF");
} else if (sender_timestamp == 0 && memcmp(command, "stats-packets", 13) == 0 && (command[13] == 0 || command[13] == ' ')) {
_callbacks->formatPacketStatsReply(reply);
} else if (sender_timestamp == 0 && memcmp(command, "stats-radio", 11) == 0 && (command[11] == 0 || command[11] == ' ')) {
_callbacks->formatRadioStatsReply(reply);
} else if (sender_timestamp == 0 && memcmp(command, "stats-core", 10) == 0 && (command[10] == 0 || command[10] == ' ')) {
_callbacks->formatStatsReply(reply);
} else {
strcpy(reply, "Unknown command");
}
}
void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* reply) {
const char* config = &command[4]; const char* config = &command[4];
if (memcmp(config, "dutycycle ", 10) == 0) { if (memcmp(config, "dutycycle ", 10) == 0) {
float dc = atof(&config[10]); float dc = atof(&config[10]);
@@ -720,169 +728,283 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
} else { } else {
sprintf(reply, "unknown config: %s", config); sprintf(reply, "unknown config: %s", config);
} }
} else if (sender_timestamp == 0 && strcmp(command, "erase") == 0) { }
bool s = _callbacks->formatFileSystem();
sprintf(reply, "File system erase: %s", s ? "OK" : "Err"); void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* reply) {
} else if (memcmp(command, "ver", 3) == 0) { const char* config = &command[4];
sprintf(reply, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate()); if (memcmp(config, "dutycycle", 9) == 0) {
} else if (memcmp(command, "board", 5) == 0) { float dc = 100.0f / (_prefs->airtime_factor + 1.0f);
sprintf(reply, "%s", _board->getManufacturerName()); int dc_int = (int)dc;
} else if (memcmp(command, "sensor get ", 11) == 0) { int dc_frac = (int)((dc - dc_int) * 10.0f + 0.5f);
const char* key = command + 11; sprintf(reply, "> %d.%d%%", dc_int, dc_frac);
const char* val = _sensors->getSettingByKey(key); } else if (memcmp(config, "af", 2) == 0) {
if (val != NULL) { sprintf(reply, "> %s", StrHelper::ftoa(_prefs->airtime_factor));
sprintf(reply, "> %s", val); } else if (memcmp(config, "int.thresh", 10) == 0) {
} else { sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold);
strcpy(reply, "null"); } else if (memcmp(config, "agc.reset.interval", 18) == 0) {
} sprintf(reply, "> %d", ((uint32_t) _prefs->agc_reset_interval) * 4);
} else if (memcmp(command, "sensor set ", 11) == 0) { } else if (memcmp(config, "multi.acks", 10) == 0) {
strcpy(tmp, &command[11]); sprintf(reply, "> %d", (uint32_t) _prefs->multi_acks);
const char *parts[2]; } else if (memcmp(config, "allow.read.only", 15) == 0) {
int num = mesh::Utils::parseTextParts(tmp, parts, 2, ' '); sprintf(reply, "> %s", _prefs->allow_read_only ? "on" : "off");
const char *key = (num > 0) ? parts[0] : ""; } else if (memcmp(config, "flood.advert.interval", 21) == 0) {
const char *value = (num > 1) ? parts[1] : "null"; sprintf(reply, "> %d", ((uint32_t) _prefs->flood_advert_interval));
if (_sensors->setSettingValue(key, value)) { } else if (memcmp(config, "advert.interval", 15) == 0) {
strcpy(reply, "ok"); sprintf(reply, "> %d", ((uint32_t) _prefs->advert_interval) * 2);
} else { } else if (memcmp(config, "guest.password", 14) == 0) {
strcpy(reply, "can't find custom var"); sprintf(reply, "> %s", _prefs->guest_password);
} } else if (sender_timestamp == 0 && memcmp(config, "prv.key", 7) == 0) { // from serial command line only
} else if (memcmp(command, "sensor list", 11) == 0) { uint8_t prv_key[PRV_KEY_SIZE];
char* dp = reply; int len = _callbacks->getSelfId().writeTo(prv_key, PRV_KEY_SIZE);
int start = 0; mesh::Utils::toHex(tmp, prv_key, len);
int end = _sensors->getNumSettings(); sprintf(reply, "> %s", tmp);
if (strlen(command) > 11) { } else if (memcmp(config, "name", 4) == 0) {
start = _atoi(command+12); sprintf(reply, "> %s", _prefs->node_name);
} } else if (memcmp(config, "repeat", 6) == 0) {
if (start >= end) { sprintf(reply, "> %s", _prefs->disable_fwd ? "off" : "on");
strcpy(reply, "no custom var"); } else if (memcmp(config, "lat", 3) == 0) {
} else { sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lat));
sprintf(dp, "%d vars\n", end); } else if (memcmp(config, "lon", 3) == 0) {
dp = strchr(dp, 0); sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lon));
int i; #if defined(USE_SX1262) || defined(USE_SX1268)
for (i = start; i < end && (dp-reply < 134); i++) { } else if (memcmp(config, "radio.rxgain", 12) == 0) {
sprintf(dp, "%s=%s\n", sprintf(reply, "> %s", _prefs->rx_boosted_gain ? "on" : "off");
_sensors->getSettingName(i),
_sensors->getSettingValue(i));
dp = strchr(dp, 0);
}
if (i < end) {
sprintf(dp, "... next:%d", i);
} else {
*(dp-1) = 0; // remove last CR
}
}
#if ENV_INCLUDE_GPS == 1
} else if (memcmp(command, "gps on", 6) == 0) {
if (_sensors->setSettingValue("gps", "1")) {
_prefs->gps_enabled = 1;
savePrefs();
strcpy(reply, "ok");
} else {
strcpy(reply, "gps toggle not found");
}
} else if (memcmp(command, "gps off", 7) == 0) {
if (_sensors->setSettingValue("gps", "0")) {
_prefs->gps_enabled = 0;
savePrefs();
strcpy(reply, "ok");
} else {
strcpy(reply, "gps toggle not found");
}
} else if (memcmp(command, "gps sync", 8) == 0) {
LocationProvider * l = _sensors->getLocationProvider();
if (l != NULL) {
l->syncTime();
strcpy(reply, "ok");
} else {
strcpy(reply, "gps provider not found");
}
} else if (memcmp(command, "gps setloc", 10) == 0) {
_prefs->node_lat = _sensors->node_lat;
_prefs->node_lon = _sensors->node_lon;
savePrefs();
strcpy(reply, "ok");
} else if (memcmp(command, "gps advert", 10) == 0) {
if (strlen(command) == 10) {
switch (_prefs->advert_loc_policy) {
case ADVERT_LOC_NONE:
strcpy(reply, "> none");
break;
case ADVERT_LOC_PREFS:
strcpy(reply, "> prefs");
break;
case ADVERT_LOC_SHARE:
strcpy(reply, "> share");
break;
default:
strcpy(reply, "error");
}
} else if (memcmp(command+11, "none", 4) == 0) {
_prefs->advert_loc_policy = ADVERT_LOC_NONE;
savePrefs();
strcpy(reply, "ok");
} else if (memcmp(command+11, "share", 5) == 0) {
_prefs->advert_loc_policy = ADVERT_LOC_SHARE;
savePrefs();
strcpy(reply, "ok");
} else if (memcmp(command+11, "prefs", 5) == 0) {
_prefs->advert_loc_policy = ADVERT_LOC_PREFS;
savePrefs();
strcpy(reply, "ok");
} else {
strcpy(reply, "error");
}
} else if (memcmp(command, "gps", 3) == 0) {
LocationProvider * l = _sensors->getLocationProvider();
if (l != NULL) {
bool enabled = l->isEnabled(); // is EN pin on ?
bool fix = l->isValid(); // has fix ?
int sats = l->satellitesCount();
bool active = !strcmp(_sensors->getSettingByKey("gps"), "1");
if (enabled) {
sprintf(reply, "on, %s, %s, %d sats",
active?"active":"deactivated",
fix?"fix":"no fix",
sats);
} else {
strcpy(reply, "off");
}
} else {
strcpy(reply, "Can't find GPS");
}
#endif #endif
} else if (memcmp(command, "powersaving on", 14) == 0) { } else if (memcmp(config, "radio", 5) == 0) {
_prefs->powersaving_enabled = 1; char freq[16], bw[16];
savePrefs(); strcpy(freq, StrHelper::ftoa(_prefs->freq));
strcpy(reply, "ok"); // TODO: to return Not supported if required strcpy(bw, StrHelper::ftoa3(_prefs->bw));
} else if (memcmp(command, "powersaving off", 15) == 0) { sprintf(reply, "> %s,%s,%d,%d", freq, bw, (uint32_t)_prefs->sf, (uint32_t)_prefs->cr);
_prefs->powersaving_enabled = 0; } else if (memcmp(config, "rxdelay", 7) == 0) {
savePrefs(); sprintf(reply, "> %s", StrHelper::ftoa(_prefs->rx_delay_base));
strcpy(reply, "ok"); } else if (memcmp(config, "txdelay", 7) == 0) {
} else if (memcmp(command, "powersaving", 11) == 0) { sprintf(reply, "> %s", StrHelper::ftoa(_prefs->tx_delay_factor));
if (_prefs->powersaving_enabled) { } else if (memcmp(config, "flood.max", 9) == 0) {
strcpy(reply, "on"); sprintf(reply, "> %d", (uint32_t)_prefs->flood_max);
} else { } else if (memcmp(config, "direct.txdelay", 14) == 0) {
strcpy(reply, "off"); sprintf(reply, "> %s", StrHelper::ftoa(_prefs->direct_tx_delay_factor));
} else if (memcmp(config, "owner.info", 10) == 0) {
*reply++ = '>';
*reply++ = ' ';
const char* sp = _prefs->owner_info;
while (*sp) {
*reply++ = (*sp == '\n') ? '|' : *sp; // translate newline back to orig '|'
sp++;
} }
} else if (memcmp(command, "log start", 9) == 0) { *reply = 0; // set null terminator
_callbacks->setLoggingOn(true); } else if (memcmp(config, "path.hash.mode", 14) == 0) {
strcpy(reply, " logging on"); sprintf(reply, "> %d", (uint32_t)_prefs->path_hash_mode);
} else if (memcmp(command, "log stop", 8) == 0) { } else if (memcmp(config, "loop.detect", 11) == 0) {
_callbacks->setLoggingOn(false); if (_prefs->loop_detect == LOOP_DETECT_OFF) {
strcpy(reply, " logging off"); strcpy(reply, "> off");
} else if (memcmp(command, "log erase", 9) == 0) { } else if (_prefs->loop_detect == LOOP_DETECT_MINIMAL) {
_callbacks->eraseLogFile(); strcpy(reply, "> minimal");
strcpy(reply, " log erased"); } else if (_prefs->loop_detect == LOOP_DETECT_MODERATE) {
} else if (sender_timestamp == 0 && memcmp(command, "log", 3) == 0) { strcpy(reply, "> moderate");
_callbacks->dumpLogFile();
strcpy(reply, " EOF");
} else if (sender_timestamp == 0 && memcmp(command, "stats-packets", 13) == 0 && (command[13] == 0 || command[13] == ' ')) {
_callbacks->formatPacketStatsReply(reply);
} else if (sender_timestamp == 0 && memcmp(command, "stats-radio", 11) == 0 && (command[11] == 0 || command[11] == ' ')) {
_callbacks->formatRadioStatsReply(reply);
} else if (sender_timestamp == 0 && memcmp(command, "stats-core", 10) == 0 && (command[10] == 0 || command[10] == ' ')) {
_callbacks->formatStatsReply(reply);
} else { } else {
strcpy(reply, "Unknown command"); strcpy(reply, "> strict");
}
} else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) {
sprintf(reply, "> %d", (int32_t) _prefs->tx_power_dbm);
} else if (memcmp(config, "freq", 4) == 0) {
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->freq));
} else if (memcmp(config, "public.key", 10) == 0) {
strcpy(reply, "> ");
mesh::Utils::toHex(&reply[2], _callbacks->getSelfId().pub_key, PUB_KEY_SIZE);
} else if (memcmp(config, "role", 4) == 0) {
sprintf(reply, "> %s", _callbacks->getRole());
} else if (memcmp(config, "bridge.type", 11) == 0) {
sprintf(reply, "> %s",
#ifdef WITH_RS232_BRIDGE
"rs232"
#elif WITH_ESPNOW_BRIDGE
"espnow"
#else
"none"
#endif
);
#ifdef WITH_BRIDGE
} else if (memcmp(config, "bridge.enabled", 14) == 0) {
sprintf(reply, "> %s", _prefs->bridge_enabled ? "on" : "off");
} else if (memcmp(config, "bridge.delay", 12) == 0) {
sprintf(reply, "> %d", (uint32_t)_prefs->bridge_delay);
} else if (memcmp(config, "bridge.source", 13) == 0) {
sprintf(reply, "> %s", _prefs->bridge_pkt_src ? "logRx" : "logTx");
#endif
#ifdef WITH_RS232_BRIDGE
} else if (memcmp(config, "bridge.baud", 11) == 0) {
sprintf(reply, "> %d", (uint32_t)_prefs->bridge_baud);
#endif
#ifdef WITH_ESPNOW_BRIDGE
} else if (memcmp(config, "bridge.channel", 14) == 0) {
sprintf(reply, "> %d", (uint32_t)_prefs->bridge_channel);
} else if (memcmp(config, "bridge.secret", 13) == 0) {
sprintf(reply, "> %s", _prefs->bridge_secret);
#endif
} else if (memcmp(config, "bootloader.ver", 14) == 0) {
#ifdef NRF52_PLATFORM
char ver[32];
if (_board->getBootloaderVersion(ver, sizeof(ver))) {
sprintf(reply, "> %s", ver);
} else {
strcpy(reply, "> unknown");
}
#else
strcpy(reply, "ERROR: unsupported");
#endif
} else if (memcmp(config, "adc.multiplier", 14) == 0) {
float adc_mult = _board->getAdcMultiplier();
if (adc_mult == 0.0f) {
strcpy(reply, "Error: unsupported by this board");
} else {
sprintf(reply, "> %.3f", adc_mult);
}
// Power management commands
} else if (memcmp(config, "pwrmgt.support", 14) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
strcpy(reply, "> supported");
#else
strcpy(reply, "> unsupported");
#endif
} else if (memcmp(config, "pwrmgt.source", 13) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
strcpy(reply, _board->isExternalPowered() ? "> external" : "> battery");
#else
strcpy(reply, "ERROR: Power management not supported");
#endif
} else if (memcmp(config, "pwrmgt.bootreason", 17) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
sprintf(reply, "> Reset: %s; Shutdown: %s",
_board->getResetReasonString(_board->getResetReason()),
_board->getShutdownReasonString(_board->getShutdownReason()));
#else
strcpy(reply, "ERROR: Power management not supported");
#endif
} else if (memcmp(config, "pwrmgt.bootmv", 13) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
sprintf(reply, "> %u mV", _board->getBootVoltage());
#else
strcpy(reply, "ERROR: Power management not supported");
#endif
} else {
sprintf(reply, "??: %s", config);
}
}
void CommonCLI::handleRegionCmd(char* command, char* reply) {
reply[0] = 0;
const char* parts[4];
int n = mesh::Utils::parseTextParts(command, parts, 4, ' ');
if (n == 1) {
_region_map->exportTo(reply, 160);
} else if (n >= 2 && strcmp(parts[1], "load") == 0) {
_callbacks->startRegionsLoad();
} else if (n >= 2 && strcmp(parts[1], "save") == 0) {
_prefs->discovery_mod_timestamp = getRTCClock()->getCurrentTime(); // this node is now 'modified' (for discovery info)
savePrefs();
bool success = _callbacks->saveRegions();
strcpy(reply, success ? "OK" : "Err - save failed");
} else if (n >= 3 && strcmp(parts[1], "allowf") == 0) {
auto region = _region_map->findByNamePrefix(parts[2]);
if (region) {
region->flags &= ~REGION_DENY_FLOOD;
strcpy(reply, "OK");
} else {
strcpy(reply, "Err - unknown region");
}
} else if (n >= 3 && strcmp(parts[1], "denyf") == 0) {
auto region = _region_map->findByNamePrefix(parts[2]);
if (region) {
region->flags |= REGION_DENY_FLOOD;
strcpy(reply, "OK");
} else {
strcpy(reply, "Err - unknown region");
}
} else if (n >= 3 && strcmp(parts[1], "get") == 0) {
auto region = _region_map->findByNamePrefix(parts[2]);
if (region) {
auto parent = _region_map->findById(region->parent);
if (parent && parent->id != 0) {
sprintf(reply, " %s (%s) %s", region->name, parent->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F");
} else {
sprintf(reply, " %s %s", region->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F");
}
} else {
strcpy(reply, "Err - unknown region");
}
} else if (n >= 3 && strcmp(parts[1], "home") == 0) {
auto home = _region_map->findByNamePrefix(parts[2]);
if (home) {
_region_map->setHomeRegion(home);
sprintf(reply, " home is now %s", home->name);
} else {
strcpy(reply, "Err - unknown region");
}
} else if (n == 2 && strcmp(parts[1], "home") == 0) {
auto home = _region_map->getHomeRegion();
sprintf(reply, " home is %s", home ? home->name : "*");
} else if (n >= 3 && strcmp(parts[1], "default") == 0) {
if (strcmp(parts[2], "<null>") == 0) {
_region_map->setDefaultRegion(NULL);
_callbacks->onDefaultRegionChanged(NULL);
sprintf(reply, " default scope is now <null>");
} else {
auto def = _region_map->findByNamePrefix(parts[2]);
if (def) {
_region_map->setDefaultRegion(def);
_callbacks->onDefaultRegionChanged(def);
sprintf(reply, " default scope is now %s", def->name);
} else {
strcpy(reply, "Err - unknown region");
}
}
} else if (n == 2 && strcmp(parts[1], "default") == 0) {
auto def = _region_map->getDefaultRegion();
sprintf(reply, " default scope is %s", def ? def->name : "<null>");
} else if (n >= 3 && strcmp(parts[1], "put") == 0) {
auto parent = n >= 4 ? _region_map->findByNamePrefix(parts[3]) : &(_region_map->getWildcard());
if (parent == NULL) {
strcpy(reply, "Err - unknown parent");
} else {
auto region = _region_map->putRegion(parts[2], parent->id);
if (region == NULL) {
strcpy(reply, "Err - unable to put");
} else {
strcpy(reply, "OK");
}
}
} else if (n >= 3 && strcmp(parts[1], "remove") == 0) {
auto region = _region_map->findByName(parts[2]);
if (region) {
if (_region_map->removeRegion(*region)) {
strcpy(reply, "OK");
} else {
strcpy(reply, "Err - not empty");
}
} else {
strcpy(reply, "Err - not found");
}
} else if (n >= 3 && strcmp(parts[1], "list") == 0) {
uint8_t mask = 0;
bool invert = false;
if (strcmp(parts[2], "allowed") == 0) {
mask = REGION_DENY_FLOOD;
invert = false; // list regions that DON'T have DENY flag
} else if (strcmp(parts[2], "denied") == 0) {
mask = REGION_DENY_FLOOD;
invert = true; // list regions that DO have DENY flag
} else {
strcpy(reply, "Err - use 'allowed' or 'denied'");
return;
}
int len = _region_map->exportNamesTo(reply, 160, mask, invert);
if (len == 0) {
strcpy(reply, "-none-");
}
} else {
strcpy(reply, "Err - ??");
} }
} }

View File

@@ -4,6 +4,7 @@
#include <helpers/IdentityStore.h> #include <helpers/IdentityStore.h>
#include <helpers/SensorManager.h> #include <helpers/SensorManager.h>
#include <helpers/ClientACL.h> #include <helpers/ClientACL.h>
#include <helpers/RegionMap.h>
#if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) #if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE)
#define WITH_BRIDGE #define WITH_BRIDGE
@@ -88,6 +89,16 @@ public:
virtual void clearStats() = 0; virtual void clearStats() = 0;
virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0; virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0;
virtual void startRegionsLoad() {
// no op by default
}
virtual bool saveRegions() {
return false;
}
virtual void onDefaultRegionChanged(const RegionEntry* r) {
// no op by default
}
virtual void setBridgeState(bool enable) { virtual void setBridgeState(bool enable) {
// no op by default // no op by default
}; };
@@ -107,6 +118,7 @@ class CommonCLI {
CommonCLICallbacks* _callbacks; CommonCLICallbacks* _callbacks;
mesh::MainBoard* _board; mesh::MainBoard* _board;
SensorManager* _sensors; SensorManager* _sensors;
RegionMap* _region_map;
ClientACL* _acl; ClientACL* _acl;
char tmp[PRV_KEY_SIZE*2 + 4]; char tmp[PRV_KEY_SIZE*2 + 4];
@@ -114,12 +126,16 @@ class CommonCLI {
void savePrefs(); void savePrefs();
void loadPrefsInt(FILESYSTEM* _fs, const char* filename); void loadPrefsInt(FILESYSTEM* _fs, const char* filename);
void handleRegionCmd(char* command, char* reply);
void handleGetCmd(uint32_t sender_timestamp, char* command, char* reply);
void handleSetCmd(uint32_t sender_timestamp, char* command, char* reply);
public: public:
CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, SensorManager& sensors, ClientACL& acl, NodePrefs* prefs, CommonCLICallbacks* callbacks) CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, SensorManager& sensors, RegionMap& region_map, ClientACL& acl, NodePrefs* prefs, CommonCLICallbacks* callbacks)
: _board(&board), _rtc(&rtc), _sensors(&sensors), _acl(&acl), _prefs(prefs), _callbacks(callbacks) { } : _board(&board), _rtc(&rtc), _sensors(&sensors), _region_map(&region_map), _acl(&acl), _prefs(prefs), _callbacks(callbacks) { }
void loadPrefs(FILESYSTEM* _fs); void loadPrefs(FILESYSTEM* _fs);
void savePrefs(FILESYSTEM* _fs); void savePrefs(FILESYSTEM* _fs);
void handleCommand(uint32_t sender_timestamp, const char* command, char* reply); void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
uint8_t buildAdvertData(uint8_t node_type, uint8_t* app_data); uint8_t buildAdvertData(uint8_t node_type, uint8_t* app_data);
}; };