Эх сурвалжийг харах

* new CLI commands: region, region load, region save, region get, region allow

Scott Powell 9 сар өмнө
parent
commit
ecd30f4d36

+ 80 - 3
examples/simple_repeater/MyMesh.cpp

@@ -610,7 +610,7 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t
 MyMesh::MyMesh(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),
-      _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store)
+      _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store)
 #if defined(WITH_RS232_BRIDGE)
       , bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc)
 #endif
@@ -624,6 +624,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
   dirty_contacts_expiry = 0;
   set_radio_at = revert_radio_at = 0;
   _logging = false;
+  region_load_active = false;
 
 #if MAX_NEIGHBOURS
   memset(neighbours, 0, sizeof(neighbours));
@@ -846,9 +847,46 @@ void MyMesh::clearStats() {
   ((SimpleMeshTables *)getTables())->resetStats();
 }
 
+static bool is_name_char(char c) {
+  return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= 'z') || c == '-' || c == '.' || c == '_' || c == '#';
+}
+
 void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) {
-  while (*command == ' ')
-    command++; // skip leading spaces
+  if (region_load_active) {
+    if (*command == 0) {  // empty 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 (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) {
+        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' ? REGION_ALLOW_FLOOD : 0);   // 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 == ' ') command++; // skip leading spaces
 
   if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI)
     memcpy(reply, command, 3);                    // reflect the prefix back
@@ -890,6 +928,45 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
       Serial.printf("\n");
     }
     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 && sender_timestamp == 0) {
+      region_map.exportTo(Serial);
+    } 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) {
+      bool success = region_map.save(_fs);
+      strcpy(reply, success ? "OK" : "Err - save failed");
+    } else if (n >= 3 && strcmp(parts[1], "allow") == 0) {
+      auto region = n >= 4 ? region_map.findByNamePrefix(parts[3]) : &region_map.getWildcard();
+      if (region) {
+        strcpy(reply, "OK");
+        if (strcmp(parts[2], "F") == 0) {
+          region->flags = REGION_ALLOW_FLOOD;
+        } else if (strcmp(parts[2], "0") == 0) {
+          region->flags = 0;
+        } else {
+          sprintf(reply, "Err - invalid flag: %s", parts[2]);
+        }
+      } else {
+        strcpy(reply, "Err - unknown region");
+      }
+    } else if (n >= 3 && strcmp(parts[1], "get") == 0) {
+      auto region = region_map.findByNamePrefix(parts[2]);
+      if (region) {
+        sprintf(reply, " %s %s", region->name, region->flags == REGION_ALLOW_FLOOD ? "F" : "");
+      } else {
+        strcpy(reply, "Err - unknown region");
+      }
+    } else {
+      strcpy(reply, "Err - ??");
+    }
   } else{
     _cli.handleCommand(sender_timestamp, command, reply);  // common CLI commands
   }

+ 3 - 1
examples/simple_repeater/MyMesh.h

@@ -89,7 +89,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
   uint8_t reply_data[MAX_PACKET_PAYLOAD];
   ClientACL  acl;
   TransportKeyStore key_store;
-  RegionMap region_map;
+  RegionMap region_map, temp_map;
+  RegionEntry* load_stack[8];
+  bool region_load_active;
   unsigned long dirty_contacts_expiry;
 #if MAX_NEIGHBOURS
   NeighbourInfo neighbours[MAX_NEIGHBOURS];

+ 3 - 1
examples/simple_repeater/main.cpp

@@ -91,14 +91,16 @@ void loop() {
     if (c != '\n') {
       command[len++] = c;
       command[len] = 0;
+      Serial.print(c);
     }
-    Serial.print(c);
+    if (c == '\r') break;
   }
   if (len == sizeof(command)-1) {  // command buffer full
     command[sizeof(command)-1] = '\r';
   }
 
   if (len > 0 && command[len - 1] == '\r') {  // received complete line
+    Serial.print('\n');
     command[len - 1] = 0;  // replace newline with C string null terminator
     char reply[160];
     the_mesh.handleCommand(0, command, reply);  // NOTE: there is no sender_timestamp via serial!

+ 119 - 7
src/helpers/RegionMap.cpp

@@ -2,25 +2,106 @@
 #include <helpers/TxtDataHelpers.h>
 #include <SHA256.h>
 
-void RegionMap::load(FILESYSTEM* _fs) {
-  // TODO
+RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) {
+  next_id = 1; num_regions = 0;
+  wildcard.id = wildcard.parent = 0;
+  wildcard.flags = REGION_ALLOW_FLOOD;  // default behaviour, allow flood
+  strcpy(wildcard.name, "(*)");
 }
-void RegionMap::save(FILESYSTEM* _fs) {
-  // TODO
+
+static File openWrite(FILESYSTEM* _fs, const char* filename) {
+  #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
+    _fs->remove(filename);
+    return _fs->open(filename, FILE_O_WRITE);
+  #elif defined(RP2040_PLATFORM)
+    return _fs->open(filename, "w");
+  #else
+    return _fs->open(filename, "w", true);
+  #endif
+}
+
+bool RegionMap::load(FILESYSTEM* _fs) {
+  if (_fs->exists("/regions2")) {
+  #if defined(RP2040_PLATFORM)
+    File file = _fs->open("/regions2", "r");
+  #else
+    File file = _fs->open("/regions2");
+  #endif
+
+    if (file) {
+      uint8_t pad[128];
+
+      num_regions = 0; next_id = 1;
+
+      bool success = file.read(pad, 7) == 7;  // reserved header
+      success = success && file.read((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags);
+      success = success && file.read((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id);
+
+      if (success) {
+        while (num_regions < MAX_REGION_ENTRIES) {
+          auto r = &regions[num_regions];
+
+          success = file.read((uint8_t *) &r->id, sizeof(r->id)) == sizeof(r->id);
+          success = success && file.read((uint8_t *) &r->parent, sizeof(r->parent)) == sizeof(r->parent);
+          success = success && file.read((uint8_t *) r->name, sizeof(r->name)) == sizeof(r->name);
+          success = success && file.read((uint8_t *) &r->flags, sizeof(r->flags)) == sizeof(r->flags);
+          success = success && file.read(pad, sizeof(pad)) == sizeof(pad);
+
+          if (!success) break; // EOF
+
+          if (r->id >= next_id) {    // make sure next_id is valid
+            next_id = r->id + 1;
+          }
+          num_regions++;
+        }
+      }
+      file.close();
+      return true;
+    }
+  }
+  return false;  // failed
+}
+
+bool RegionMap::save(FILESYSTEM* _fs) {
+  File file = openWrite(_fs, "/regions2");
+  if (file) {
+    uint8_t pad[128];
+    memset(pad, 0, sizeof(pad));
+
+    bool success = file.write(pad, 7) == 7;  // reserved header
+    success = success && file.write((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags);
+    success = success && file.write((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id);
+
+    if (success) {
+      for (int i = 0; i < num_regions; i++) {
+        auto r = &regions[i];
+
+        success = file.write((uint8_t *) &r->id, sizeof(r->id)) == sizeof(r->id);
+        success = success && file.write((uint8_t *) &r->parent, sizeof(r->parent)) == sizeof(r->parent);
+        success = success && file.write((uint8_t *) r->name, sizeof(r->name)) == sizeof(r->name);
+        success = success && file.write((uint8_t *) &r->flags, sizeof(r->flags)) == sizeof(r->flags);
+        success = success && file.write(pad, sizeof(pad)) == sizeof(pad);
+        if (!success) break; // write failed
+      }
+    }
+    file.close();
+    return true;
+  }
+  return false;  // failed
 }
 
-RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id) {
+RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id, uint16_t id) {
   auto region = findByName(name);
   if (region) {
     if (region->id == parent_id) return NULL;   // ERROR: invalid parent!
 
     region->parent = parent_id;   // re-parent / move this region in the hierarchy
   } else {
-    if (num_regions >= MAX_REGION_ENTRIES) return NULL;  // full!
+    if (id == 0 && num_regions >= MAX_REGION_ENTRIES) return NULL;  // full!
 
     region = &regions[num_regions++];   // alloc new RegionEntry
     region->flags = 0;
-    region->id = next_id++;
+    region->id = id == 0 ? next_id++ : id;
     StrHelper::strncpy(region->name, name, sizeof(region->name));
     region->parent = parent_id;
   }
@@ -58,6 +139,14 @@ RegionEntry* RegionMap::findByName(const char* name) {
   return NULL;  // not found
 }
 
+RegionEntry* RegionMap::findByNamePrefix(const char* prefix) {
+  for (int i = 0; i < num_regions; i++) {
+    auto region = &regions[i];
+    if (memcmp(prefix, region->name, strlen(prefix)) == 0) return region;
+  }
+  return NULL;  // not found
+}
+
 RegionEntry* RegionMap::findById(uint16_t id) {
   if (id == 0) return &wildcard;   // special root Region
 
@@ -94,3 +183,26 @@ bool RegionMap::clear() {
   num_regions = 0;
   return true;  // success
 }
+
+void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream& out) const {
+  for (int i = 0; i < indent; i++) {
+    out.print(' ');
+  }
+
+  if (parent->flags & REGION_ALLOW_FLOOD) {
+    out.printf("%s F\n", parent->name);
+  } else {
+    out.printf("%s\n", parent->name);
+  }
+
+  for (int i = 0; i < num_regions; i++) {
+    auto r = &regions[i];
+    if (r->parent == parent->id) {
+      printChildRegions(indent + 1, r, out);
+    }
+  }
+}
+
+void RegionMap::exportTo(Stream& out) const {
+  printChildRegions(0, &wildcard, out);   // recursive
+}

+ 13 - 9
src/helpers/RegionMap.h

@@ -24,20 +24,24 @@ class RegionMap {
   RegionEntry regions[MAX_REGION_ENTRIES];
   RegionEntry wildcard;
 
+  void printChildRegions(int indent, const RegionEntry* parent, Stream& out) const;
+
 public:
-  RegionMap(TransportKeyStore& store) : _store(&store) {
-    next_id = 1; num_regions = 0;
-    wildcard.id = wildcard.parent = 0;
-    wildcard.flags = REGION_ALLOW_FLOOD;  // default behaviour, allow flood
-  }
-  void load(FILESYSTEM* _fs);
-  void save(FILESYSTEM* _fs);
-
-  RegionEntry* putRegion(const char* name, uint16_t parent_id);
+  RegionMap(TransportKeyStore& store);
+
+  bool load(FILESYSTEM* _fs);
+  bool save(FILESYSTEM* _fs);
+
+  RegionEntry* putRegion(const char* name, uint16_t parent_id, uint16_t id = 0);
   RegionEntry* findMatch(mesh::Packet* packet, uint8_t mask);
   RegionEntry& getWildcard() { return wildcard; }
   RegionEntry* findByName(const char* name);
+  RegionEntry* findByNamePrefix(const char* prefix);
   RegionEntry* findById(uint16_t id);
   bool removeRegion(const RegionEntry& region);
   bool clear();
+  void resetFrom(const RegionMap& src) { num_regions = 0; next_id = src.next_id; }
+  int getCount() const { return num_regions; }
+
+  void exportTo(Stream& out) const;
 };