diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dcbb213b..49a2bf3c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,5 +31,6 @@ jobs: run: platformio run -e heltec - name: Build for lora-relay-v1 run: platformio run -e lora-relay-v1 - - name: Build for linux - run: platformio run -e linux + # Turn off linux for now + #- name: Build for linux + # run: platformio run -e linux diff --git a/bin/install-eink.sh b/bin/install-eink.sh index 5bfb6f1a..b1c3d87c 100755 --- a/bin/install-eink.sh +++ b/bin/install-eink.sh @@ -6,6 +6,8 @@ BOOTDIR=/home/kevinh/development/meshtastic/Adafruit_nRF52_Bootloader nrfjprog --eraseall -f nrf52 +# to get tool run "sudo apt-get install srecord" + # this generates an intel hex file that can be programmed into a NRF52 to tell the adafruit bootloader that the current app image is valid # Bootloader settings are at BOOTLOADER_SETTINGS (rw) : ORIGIN = 0xFF000, LENGTH = 0x1000 # first 4 bytes should be 0x01 to indicate valid app image @@ -14,7 +16,7 @@ echo "01 00 00 00 00 00 00 00" | xxd -r -p - >/tmp/bootconf.bin srec_cat /tmp/bootconf.bin -binary -offset 0xff000 -output /tmp/bootconf.hex -intel echo Generating merged hex file -mergehex -m $BOOTDIR/_build/build-ttgo_eink/ttgo_eink_bootloader-0.3.2-125-gf38f8f4-dirty_s140_6.1.1.hex .pio/build/eink/firmware.hex /tmp/bootconf.hex -o ttgo_eink_full.hex +mergehex -m $BOOTDIR/_build/build-ttgo_eink/ttgo_eink_bootloader-0.3.2-213-gf67f592-dirty_s140_6.1.1.hex .pio/build/eink/firmware.hex /tmp/bootconf.hex -o ttgo_eink_full.hex echo Telling bootloader app region is valid and telling CPU to run nrfjprog --program ttgo_eink_full.hex -f nrf52 --reset diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 1649250d..9b1e77f6 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -4,6 +4,8 @@ You probably don't care about this section - skip to the next one. ## 1.2 cleanup & multichannel support: +* cdcacm bug on nrf52: emittx thinks it emitted but client sees nothing. works again later +* nrf52: segger logs have errors in formatting that should be impossible (because not going through serial, try stalling on segger) * DONE call RouterPlugin for *all* packets - not just Router packets * DONE generate channel hash from the name of the channel+the psk (not just one or the other) * DONE send a hint that can be used to select which channel to try and hash against with each message @@ -40,6 +42,10 @@ You probably don't care about this section - skip to the next one. * DONE warn in android app about unset regions * DONE use set-channel from android * DONE add gui in android app for setting region +* DONE clean up python channel usage +* DONE use bindToChannel to limit admin access for remote nodes +* DONE move channels and radio config out of device settings +* test remote info and remote settings changes * make python tests more exhaustive * pick default random admin key * exclude admin channels from URL? @@ -51,6 +57,7 @@ You probably don't care about this section - skip to the next one. * stress test multi channel * investigate @mc-hamster report of heap corruption * DONE use set-user from android +* generalize the concept of "shortstrings" use it for both PSKs and well known channel names. Possibly use a ShortString class. * use portuino TCP connection to debug with python API * document the relationship between want_response (indicating remote node received it) and want_ack (indicating that this message should be sent reliably - and also get acks from the first rx node and naks if it is never delivered) * DONE android should stop fetching channels once we've reached our first empty channel definition (hasSettings == true) @@ -80,6 +87,8 @@ You probably don't care about this section - skip to the next one. * DONE move setCrypto call into packet send and packet decode code * implement 'small location diffs' change * move battery level out of position? +* consider "A special exception (FIXME, not sure if this is a good idea) - packets that arrive on the local interface + are allowed on any channel (this lets the local user do anything)." Probably by adding a "secure_local_interface" settings bool. * DOUBLE CHECK android app can still upgrade 1.1 and 1.0 loads eink: diff --git a/platformio.ini b/platformio.ini index 742bc6db..28697631 100644 --- a/platformio.ini +++ b/platformio.ini @@ -45,13 +45,6 @@ build_flags = -Wno-missing-field-initializers -DUSE_THREAD_NAMES -DTINYGPS_OPTION_NO_CUSTOM_FIELDS -; leave this commented out to avoid breaking Windows -;upload_port = /dev/ttyUSB0 -;monitor_port = /dev/ttyUSB0 - -;upload_port = /dev/cu.SLAB_USBtoUART -;monitor_port = /dev/cu.SLAB_USBtoUART - ; the default is esptool ; upload_protocol = esp-prog @@ -74,7 +67,7 @@ lib_deps = https://github.com/meshtastic/esp8266-oled-ssd1306.git#35d796226b853b0c0ff818b2f1aa3d35e7296a96 ; ESP8266_SSD1306 https://github.com/geeksville/OneButton.git ; OneButton library for non-blocking button debounce 1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib - https://github.com/meshtastic/arduino-fsm.git#2f106146071fc7bc620e1e8d4b88dc4e0266ce39 + https://github.com/meshtastic/arduino-fsm.git#55c47b6cded91645aff05a27b6e5821d8d0f64be https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git#31015a55e630a2df77d9d714669c621a5bf355ad https://github.com/meshtastic/RadioLib.git#07de964e929238949035fb0d5887026a3058df1a https://github.com/meshtastic/TinyGPSPlus.git#f0f47067ef2f67c856475933188251c1ef615e79 @@ -118,6 +111,13 @@ lib_ignore = segger_rtt platform_packages = framework-arduinoespressif32@https://github.com/meshtastic/arduino-esp32.git#352c8ea7cb73f10433ed139f34251979c470ad56 +; leave this commented out to avoid breaking Windows +upload_port = /dev/ttyUSB0 +;monitor_port = /dev/ttyUSB0 + +;upload_port = /dev/cu.SLAB_USBtoUART +;monitor_port = /dev/cu.SLAB_USBtoUART + ; customize the partition table ; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables board_build.partitions = partition-table.csv @@ -206,7 +206,8 @@ debug_port = :2331 # Note: the ARGUMENTS MUST BE on multiple lines. Otherwise platformio/commands/debug/helpers.py misparses everything into the "executable" # attribute and leaves "arguments" empty # /home/kevinh/.platformio/packages/tool-jlink/JLinkGDBServerCLExe -debug_server = +# This doesn't work yet, so not using for now +disabled_debug_server = /usr/bin/JLinkGDBServerCLExe -singlerun -if diff --git a/proto b/proto index e56f2770..e63f9713 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit e56f2770c33216ba94f289e2fb7f0b2dfd33aca2 +Subproject commit e63f9713f73ea5c9308b7822602ea75f506b6b00 diff --git a/src/main.cpp b/src/main.cpp index 3282098b..d4eaafae 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -279,7 +279,7 @@ void setup() concurrency::hasBeenSetup = true; #ifdef SEGGER_STDOUT_CH - auto mode = true ? SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL : SEGGER_RTT_MODE_NO_BLOCK_TRIM; + auto mode = false ? SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL : SEGGER_RTT_MODE_NO_BLOCK_TRIM; #ifdef NRF52840_XXAA auto buflen = 4096; // this board has a fair amount of ram #else diff --git a/src/memtest.cpp b/src/memtest.cpp new file mode 100644 index 00000000..f7e3eb3e --- /dev/null +++ b/src/memtest.cpp @@ -0,0 +1,318 @@ +/* + * mtest - Perform a memory test + * + * (C) Copyright 2000 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ +#include "configuration.h" + +/* + * Perform a memory test. A more complete alternative test can be + * configured using CONFIG_CMD_MTEST_ALTERNATIVE. The complete test + * loops until interrupted by ctrl-c or by a failure of one of the + * sub-tests. + */ +#ifdef CONFIG_CMD_MTEST_ALTERNATIVE +static int mem_test(uint32_t _start, uint32_t _end, uint32_t pattern_unused) +{ + volatile uint32_t *start = (volatile uint32_t *)_start; + volatile uint32_t *end = (volatile uint32_t *)_end; + volatile uint32_t *addr; + uint32_t val; + uint32_t readback; + vu_long addr_mask; + vu_long offset; + vu_long test_offset; + vu_long pattern; + vu_long temp; + vu_long anti_pattern; + vu_long num_words; +#ifdef CFG_MEMTEST_SCRATCH + volatile uint32_t *dummy = (vu_long *)CFG_MEMTEST_SCRATCH; +#else + volatile uint32_t *dummy = start; +#endif + int j; + int iterations = 1; + static const uint32_t bitpattern[] = { + 0x00000001, /* single bit */ + 0x00000003, /* two adjacent bits */ + 0x00000007, /* three adjacent bits */ + 0x0000000F, /* four adjacent bits */ + 0x00000005, /* two non-adjacent bits */ + 0x00000015, /* three non-adjacent bits */ + 0x00000055, /* four non-adjacent bits */ + 0xaaaaaaaa, /* alternating 1/0 */ + }; + /* XXX: enforce alignment of start and end? */ + for (;;) { + if (ctrlc()) { + putchar('\n'); + return 1; + } + printf("Iteration: %6d\r", iterations); + iterations++; + /* + * Data line test: write a pattern to the first + * location, write the 1's complement to a 'parking' + * address (changes the state of the data bus so a + * floating bus doen't give a false OK), and then + * read the value back. Note that we read it back + * into a variable because the next time we read it, + * it might be right (been there, tough to explain to + * the quality guys why it prints a failure when the + * "is" and "should be" are obviously the same in the + * error message). + * + * Rather than exhaustively testing, we test some + * patterns by shifting '1' bits through a field of + * '0's and '0' bits through a field of '1's (i.e. + * pattern and ~pattern). + */ + addr = start; + /* XXX */ + if (addr == dummy) + ++addr; + for (j = 0; j < sizeof(bitpattern) / sizeof(bitpattern[0]); j++) { + val = bitpattern[j]; + for (; val != 0; val <<= 1) { + *addr = val; + *dummy = ~val; /* clear the test data off of the bus */ + readback = *addr; + if (readback != val) { + printf("FAILURE (data line): " + "expected 0x%08lx, actual 0x%08lx at address 0x%p\n", + val, readback, addr); + } + *addr = ~val; + *dummy = val; + readback = *addr; + if (readback != ~val) { + printf("FAILURE (data line): " + "Is 0x%08lx, should be 0x%08lx at address 0x%p\n", + readback, ~val, addr); + } + } + } + /* + * Based on code whose Original Author and Copyright + * information follows: Copyright (c) 1998 by Michael + * Barr. This software is placed into the public + * domain and may be used for any purpose. However, + * this notice must not be changed or removed and no + * warranty is either expressed or implied by its + * publication or distribution. + */ + /* + * Address line test + * + * Description: Test the address bus wiring in a + * memory region by performing a walking + * 1's test on the relevant bits of the + * address and checking for aliasing. + * This test will find single-bit + * address failures such as stuck -high, + * stuck-low, and shorted pins. The base + * address and size of the region are + * selected by the caller. + * + * Notes: For best results, the selected base + * address should have enough LSB 0's to + * guarantee single address bit changes. + * For example, to test a 64-Kbyte + * region, select a base address on a + * 64-Kbyte boundary. Also, select the + * region size as a power-of-two if at + * all possible. + * + * Returns: 0 if the test succeeds, 1 if the test fails. + * + * ## NOTE ## Be sure to specify start and end + * addresses such that addr_mask has + * lots of bits set. For example an + * address range of 01000000 02000000 is + * bad while a range of 01000000 + * 01ffffff is perfect. + */ + addr_mask = ((uint32_t)end - (uint32_t)start) / sizeof(vu_long); + pattern = (vu_long)0xaaaaaaaa; + anti_pattern = (vu_long)0x55555555; + debug("%s:%d: addr mask = 0x%.8lx\n", __FUNCTION__, __LINE__, addr_mask); + /* + * Write the default pattern at each of the + * power-of-two offsets. + */ + for (offset = 1; (offset & addr_mask) != 0; offset <<= 1) + start[offset] = pattern; + /* + * Check for address bits stuck high. + */ + test_offset = 0; + start[test_offset] = anti_pattern; + for (offset = 1; (offset & addr_mask) != 0; offset <<= 1) { + temp = start[offset]; + if (temp != pattern) { + printf("\nFAILURE: Address bit stuck high @ 0x%.8lx:" + " expected 0x%.8lx, actual 0x%.8lx\n", + (uint32_t)&start[offset], pattern, temp); + return 1; + } + } + start[test_offset] = pattern; + /* + * Check for addr bits stuck low or shorted. + */ + for (test_offset = 1; (test_offset & addr_mask) != 0; test_offset <<= 1) { + start[test_offset] = anti_pattern; + for (offset = 1; (offset & addr_mask) != 0; offset <<= 1) { + temp = start[offset]; + if ((temp != pattern) && (offset != test_offset)) { + printf("\nFAILURE: Address bit stuck low or shorted @" + " 0x%.8lx: expected 0x%.8lx, actual 0x%.8lx\n", + (uint32_t)&start[offset], pattern, temp); + return 1; + } + } + start[test_offset] = pattern; + } + /* + * Description: Test the integrity of a physical + * memory device by performing an + * increment/decrement test over the + * entire region. In the process every + * storage bit in the device is tested + * as a zero and a one. The base address + * and the size of the region are + * selected by the caller. + * + * Returns: 0 if the test succeeds, 1 if the test fails. + */ + num_words = ((uint32_t)end - (uint32_t)start) / sizeof(vu_long) + 1; + /* + * Fill memory with a known pattern. + */ + for (pattern = 1, offset = 0; offset < num_words; pattern++, offset++) { + start[offset] = pattern; + } + /* + * Check each location and invert it for the second pass. + */ + for (pattern = 1, offset = 0; offset < num_words; pattern++, offset++) { + temp = start[offset]; + if (temp != pattern) { + printf("\nFAILURE (read/write) @ 0x%.8lx:" + " expected 0x%.8lx, actual 0x%.8lx)\n", + (uint32_t)&start[offset], pattern, temp); + return 1; + } + anti_pattern = ~pattern; + start[offset] = anti_pattern; + } + /* + * Check each location for the inverted pattern and zero it. + */ + for (pattern = 1, offset = 0; offset < num_words; pattern++, offset++) { + anti_pattern = ~pattern; + temp = start[offset]; + if (temp != anti_pattern) { + printf("\nFAILURE (read/write): @ 0x%.8lx:" + " expected 0x%.8lx, actual 0x%.8lx)\n", + (uint32_t)&start[offset], anti_pattern, temp); + return 1; + } + start[offset] = 0; + } + } +} +#else +static int mem_test(uint32_t *_start, size_t len, bool doRead = true, bool doWrite = true) +{ + volatile uint32_t *addr; + volatile uint32_t *start = (volatile uint32_t *)_start; + volatile uint32_t *end = start + len / sizeof(uint32_t); + uint32_t pattern = 0; + uint32_t val; + uint32_t readback; + uint32_t incr; + int rcode = 0; + incr = 1; + + //DEBUG_MSG("memtest read=%d, write=%d\n", doRead, doWrite); + + if (doWrite) { + //DEBUG_MSG("writing\n"); + for (addr = start, val = pattern; addr < end; addr++) { + *addr = val; + val += incr; + } + } + + if (doRead) { + //DEBUG_MSG("reading\n"); + for (addr = start, val = pattern; addr < end; addr++) { + readback = *addr; + if (readback != val) { + DEBUG_MSG("Mem error @ 0x%08X: " + "found 0x%08lX, expected 0x%08lX\n", + addr, readback, val); + rcode++; + } + val += incr; + } + } + +#if 0 + /* + * Flip the pattern each time to make lots of zeros and + * then, the next time, lots of ones. We decrement + * the "negative" patterns and increment the "positive" + * patterns to preserve this feature. + */ + if(pattern & 0x80000000) { + pattern = -pattern; /* complement & increment */ + } + else { + pattern = ~pattern; + } +#endif + + return rcode; +} +#endif + +#define TESTBUF_LEN 16384 + +#include + +void doMemTest() +{ + static uint32_t *testBuf; + static int iter; + + if (!testBuf) + testBuf = (uint32_t *)malloc(TESTBUF_LEN); + + assert(testBuf); + if (mem_test(testBuf, TESTBUF_LEN, iter % 2 == 1, iter % 2 == 0) > 0) + assert(0); // FIXME report error better + + iter++; +} \ No newline at end of file diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index ab0a64b3..2a04c9a7 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -161,8 +161,8 @@ int16_t Channels::setCrypto(ChannelIndex chIndex) void Channels::initDefaults() { - devicestate.channels_count = MAX_NUM_CHANNELS; - for (int i = 0; i < devicestate.channels_count; i++) + channelFile.channels_count = MAX_NUM_CHANNELS; + for (int i = 0; i < channelFile.channels_count; i++) fixupChannel(i); initDefaultChannel(0); } @@ -170,7 +170,7 @@ void Channels::initDefaults() void Channels::onConfigChanged() { // Make sure the phone hasn't mucked anything up - for (int i = 0; i < devicestate.channels_count; i++) { + for (int i = 0; i < channelFile.channels_count; i++) { Channel &ch = fixupChannel(i); if (ch.role == Channel_Role_PRIMARY) @@ -180,8 +180,8 @@ void Channels::onConfigChanged() Channel &Channels::getByIndex(ChannelIndex chIndex) { - assert(chIndex < devicestate.channels_count); - Channel *ch = devicestate.channels + chIndex; + assert(chIndex < channelFile.channels_count); + Channel *ch = channelFile.channels + chIndex; return *ch; } @@ -192,8 +192,8 @@ void Channels::setChannel(const Channel &c) // if this is the new primary, demote any existing roles if (c.role == Channel_Role_PRIMARY) for (int i = 0; i < getNumChannels(); i++) - if (devicestate.channels[i].role == Channel_Role_PRIMARY) - devicestate.channels[i].role = Channel_Role_SECONDARY; + if (channelFile.channels[i].role == Channel_Role_PRIMARY) + channelFile.channels[i].role = Channel_Role_SECONDARY; old = c; // slam in the new settings/role } diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h index 547b0a58..4fbafe42 100644 --- a/src/mesh/Channels.h +++ b/src/mesh/Channels.h @@ -44,7 +44,7 @@ class Channels /** The index of the primary channel */ ChannelIndex getPrimaryIndex() const { return primaryIndex; } - ChannelIndex getNumChannels() { return devicestate.channels_count; } + ChannelIndex getNumChannels() { return channelFile.channels_count; } /** * Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different diff --git a/src/mesh/MeshPlugin.cpp b/src/mesh/MeshPlugin.cpp index e17cc547..99bf7643 100644 --- a/src/mesh/MeshPlugin.cpp +++ b/src/mesh/MeshPlugin.cpp @@ -1,6 +1,8 @@ #include "MeshPlugin.h" +#include "Channels.h" #include "MeshService.h" #include "NodeDB.h" +#include "plugins/RoutingPlugin.h" #include std::vector *MeshPlugin::plugins; @@ -11,7 +13,7 @@ const MeshPacket *MeshPlugin::currentRequest; * If any of the current chain of plugins has already sent a reply, it will be here. This is useful to allow * the RoutingPlugin to avoid sending redundant acks */ - MeshPacket *MeshPlugin::currentReply; +MeshPacket *MeshPlugin::currentReply; MeshPlugin::MeshPlugin(const char *_name) : name(_name) { @@ -46,8 +48,15 @@ void MeshPlugin::callPlugins(const MeshPacket &mp) pi.currentRequest = ∓ - // We only call plugins that are interested in the packet (and the message is destined to us or we are promiscious) - bool wantsPacket = (pi.isPromiscuous || toUs) && pi.wantPacket(&mp); + /// received channel + auto ch = channels.getByIndex(mp.channel); + assert(ch.has_settings); + + /// Is the channel this packet arrived on acceptable? (security check) + bool rxChannelOk = !pi.boundChannel || (mp.from == 0) || (strcmp(ch.settings.name, pi.boundChannel) == 0); + + /// We only call plugins that are interested in the packet (and the message is destined to us or we are promiscious) + bool wantsPacket = rxChannelOk && (pi.isPromiscuous || toUs) && pi.wantPacket(&mp); // DEBUG_MSG("Plugin %s wantsPacket=%d\n", pi.name, wantsPacket); if (wantsPacket) { pluginFound = true; @@ -76,10 +85,17 @@ void MeshPlugin::callPlugins(const MeshPacket &mp) pi.currentRequest = NULL; } - if(currentReply) { - DEBUG_MSG("Sending response\n"); - service.sendToMesh(currentReply); - currentReply = NULL; + if (mp.decoded.want_response && toUs) { + if (currentReply) { + DEBUG_MSG("Sending response\n"); + service.sendToMesh(currentReply); + currentReply = NULL; + } + else { + // No one wanted to reply to this requst, tell the requster that happened + DEBUG_MSG("No one responded, send a nak\n"); + routingPlugin->sendAckNak(Routing_Error_NO_RESPONSE, getFrom(&mp), mp.id, mp.channel); + } } if (!pluginFound) @@ -95,7 +111,7 @@ void MeshPlugin::sendResponse(const MeshPacket &req) auto r = allocReply(); if (r) { setReplyTo(r, req); - currentReply = r; + currentReply = r; } else { // Ignore - this is now expected behavior for routing plugin (because it ignores some replies) // DEBUG_MSG("WARNING: Client requested response but this plugin did not provide\n"); @@ -109,10 +125,11 @@ void setReplyTo(MeshPacket *p, const MeshPacket &to) { assert(p->which_payloadVariant == MeshPacket_decoded_tag); // Should already be set by now p->to = getFrom(&to); + p->channel = to.channel; // Use the same channel that the request came in on // No need for an ack if we are just delivering locally (it just generates an ignored ack) p->want_ack = (to.from != 0) ? to.want_ack : false; - if(p->priority == MeshPacket_Priority_UNSET) + if (p->priority == MeshPacket_Priority_UNSET) p->priority = MeshPacket_Priority_RELIABLE; p->decoded.request_id = to.id; } diff --git a/src/mesh/MeshPlugin.h b/src/mesh/MeshPlugin.h index 7234b6b0..dc905a0e 100644 --- a/src/mesh/MeshPlugin.h +++ b/src/mesh/MeshPlugin.h @@ -1,9 +1,9 @@ #pragma once #include "mesh/MeshTypes.h" -#include #include #include +#include /** A baseclass for any mesh "plugin". * * A plugin allows you to add new features to meshtastic device code, without needing to know messaging details. @@ -16,7 +16,7 @@ */ class MeshPlugin { - static std::vector *plugins; + static std::vector *plugins; public: /** Constructor @@ -37,16 +37,24 @@ class MeshPlugin protected: const char *name; - /* Most plugins only care about packets that are destined for their node (i.e. broadcasts or has their node as the specific recipient) - But some plugs might want to 'sniff' packets that are merely being routed (passing through the current node). Those plugins can set this to - true and their handleReceived() will be called for every packet. + /* Most plugins only care about packets that are destined for their node (i.e. broadcasts or has their node as the specific + recipient) But some plugs might want to 'sniff' packets that are merely being routed (passing through the current node). Those + plugins can set this to true and their handleReceived() will be called for every packet. */ bool isPromiscuous = false; + /** If a bound channel name is set, we will only accept received packets that come in on that channel. + * A special exception (FIXME, not sure if this is a good idea) - packets that arrive on the local interface + * are allowed on any channel (this lets the local user do anything). + * + * We will send responses on the same channel that the request arrived on. + */ + const char *boundChannel = NULL; + /** * If this plugin is currently handling a request currentRequest will be preset * to the packet with the request. This is mostly useful for reply handlers. - * + * * Note: this can be static because we are guaranteed to be processing only one * plugin at a time. */ @@ -78,16 +86,13 @@ class MeshPlugin */ virtual bool wantUIFrame() { return false; } - - private: - /** - * If any of the current chain of plugins has already sent a reply, it will be here. This is useful to allow + * If any of the current chain of plugins has already sent a reply, it will be here. This is useful to allow * the RoutingPlugin to avoid sending redundant acks */ static MeshPacket *currentReply; - friend class ReliableRouter; + friend class ReliableRouter; /** Messages can be received that have the want_response bit set. If set, this callback will be invoked * so that subclasses can (optionally) send a response back to the original sender. This method calls allocReply() @@ -96,7 +101,7 @@ class MeshPlugin void sendResponse(const MeshPacket &req); }; -/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet +/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet * This ensures that if the request packet was sent reliably, the reply is sent that way as well. -*/ + */ void setReplyTo(MeshPacket *p, const MeshPacket &to); \ No newline at end of file diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 5590fa9c..d542b21c 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -104,7 +104,7 @@ bool MeshService::reloadConfig() // This will also update the region as needed bool didReset = nodeDB.resetRadioConfig(); // Don't let the phone send us fatally bad settings - configChanged.notifyObservers(NULL); + configChanged.notifyObservers(NULL); // This will cause radio hardware to change freqs etc nodeDB.saveToDisk(); return didReset; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 166e159f..1840aa36 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -30,7 +30,8 @@ NodeDB nodeDB; // we have plenty of ram so statically alloc this tempbuf (for now) EXT_RAM_ATTR DeviceState devicestate; MyNodeInfo &myNodeInfo = devicestate.my_node; -RadioConfig &radioConfig = devicestate.radio; +RadioConfig radioConfig; +ChannelFile channelFile; /** The current change # for radio settings. Starts at 0 on boot and any time the radio settings * might have changed is incremented. Allows others to detect they might now be on a new channel. @@ -67,10 +68,11 @@ NodeNum displayedNodeNum; NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_count) {} /** - * Most (but not always) of the time we want to treat packets 'from' the local phone (where from == 0), as if they originated on the local node. - * If from is zero this function returns our node number instead + * Most (but not always) of the time we want to treat packets 'from' the local phone (where from == 0), as if they originated on + * the local node. If from is zero this function returns our node number instead */ -NodeNum getFrom(const MeshPacket *p) { +NodeNum getFrom(const MeshPacket *p) +{ return (p->from == 0) ? nodeDB.getNodeNum() : p->from; } @@ -84,7 +86,7 @@ bool NodeDB::resetRadioConfig() DEBUG_MSG("Performing factory reset!\n"); installDefaultDeviceState(); didFactoryReset = true; - } else if (devicestate.channels_count == 0) { + } else if (channelFile.channels_count == 0) { DEBUG_MSG("Setting default channel and radio preferences!\n"); channels.initDefaults(); @@ -117,6 +119,18 @@ bool NodeDB::resetRadioConfig() return didFactoryReset; } +void NodeDB::installDefaultRadioConfig() +{ + memset(&radioConfig, 0, sizeof(radioConfig)); + radioConfig.has_preferences = true; + resetRadioConfig(); +} + +void NodeDB::installDefaultChannels() +{ + memset(&channelFile, 0, sizeof(channelFile)); +} + void NodeDB::installDefaultDeviceState() { // We try to preserve the region setting because it will really bum users out if we discard it @@ -129,14 +143,11 @@ void NodeDB::installDefaultDeviceState() // init our devicestate with valid flags so protobuf writing/reading will work devicestate.has_my_node = true; - devicestate.has_radio = true; devicestate.has_owner = true; - devicestate.radio.has_preferences = true; devicestate.node_db_count = 0; + devicestate.version = DEVICESTATE_CUR_VER; devicestate.receive_queue_count = 0; // Not yet implemented FIXME - resetRadioConfig(); - // default to no GPS, until one has been found by probing myNodeInfo.has_gps = false; myNodeInfo.message_timeout_msec = FLOOD_EXPIRE_TIME; @@ -158,6 +169,9 @@ void NodeDB::installDefaultDeviceState() radioConfig.preferences.region = oldRegionCode; if (oldRegion.length()) // If the old style region was set, try to keep it up-to-date strcpy(myNodeInfo.region, oldRegion.c_str()); + + installDefaultChannels(); + installDefaultRadioConfig(); } void NodeDB::init() @@ -186,12 +200,16 @@ void NodeDB::init() info->user = owner; info->has_user = true; + // removed from 1.2 (though we do use old values if found) // We set these _after_ loading from disk - because they come from the build and are more trusted than // what is stored in flash - if (xstr(HW_VERSION)[0]) - strncpy(myNodeInfo.region, optstr(HW_VERSION), sizeof(myNodeInfo.region)); - else - DEBUG_MSG("This build does not specify a HW_VERSION\n"); // Eventually new builds will no longer include this build flag + //if (xstr(HW_VERSION)[0]) + // strncpy(myNodeInfo.region, optstr(HW_VERSION), sizeof(myNodeInfo.region)); + // else DEBUG_MSG("This build does not specify a HW_VERSION\n"); // Eventually new builds will no longer include this build flag + + // DEBUG_MSG("legacy region %d\n", devicestate.legacyRadio.preferences.region); + if(radioConfig.preferences.region == RegionCode_Unset) + radioConfig.preferences.region = devicestate.legacyRadio.preferences.region; // Check for the old style of region code strings, if found, convert to the new enum. // Those strings will look like "1.0-EU433" @@ -209,8 +227,7 @@ void NodeDB::init() resetRadioConfig(); // If bogus settings got saved, then fix them - DEBUG_MSG("legacy_region=%s, region=%d, NODENUM=0x%x, dbsize=%d\n", myNodeInfo.region, radioConfig.preferences.region, - myNodeInfo.my_node_num, *numNodes); + DEBUG_MSG("region=%d, NODENUM=0x%x, dbsize=%d\n", radioConfig.preferences.region, myNodeInfo.my_node_num, *numNodes); } // We reserve a few nodenums for future use @@ -240,84 +257,134 @@ void NodeDB::pickNewNodeNum() myNodeInfo.my_node_num = r; } -const char *preffile = "/db.proto"; -const char *preftmp = "/db.proto.tmp"; +static const char *preffileOld = "/db.proto"; +static const char *preffile = "/prefs/db.proto"; +static const char *radiofile = "/prefs/radio.proto"; +static const char *channelfile = "/prefs/channels.proto"; +// const char *preftmp = "/db.proto.tmp"; -void NodeDB::loadFromDisk() +/** Load a protobuf from a file, return true for success */ +bool loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void *dest_struct) { #ifdef FS // static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM - auto f = FS.open(preffile); + auto f = FS.open(filename); + + // FIXME, temporary hack until every node in the universe is 1.2 or later - look for prefs in the old location (so we can + // preserve region) + if (!f && filename == preffile) { + filename = preffileOld; + f = FS.open(filename); + } + + bool okay = false; if (f) { - DEBUG_MSG("Loading saved preferences\n"); - pb_istream_t stream = {&readcb, &f, DeviceState_size}; + DEBUG_MSG("Loading %s\n", filename); + pb_istream_t stream = {&readcb, &f, protoSize}; // DEBUG_MSG("Preload channel name=%s\n", channelSettings.name); - memset(&devicestate, 0, sizeof(devicestate)); - if (!pb_decode(&stream, DeviceState_fields, &devicestate)) { + memset(dest_struct, 0, objSize); + if (!pb_decode(&stream, fields, dest_struct)) { DEBUG_MSG("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream)); - installDefaultDeviceState(); // Our in RAM copy might now be corrupt - // FIXME - report failure to phone } else { - if (devicestate.version < DEVICESTATE_MIN_VER) { - DEBUG_MSG("Warn: devicestate is old, discarding\n"); - installDefaultDeviceState(); - } else { - DEBUG_MSG("Loaded saved preferences version %d\n", devicestate.version); - } - - // DEBUG_MSG("Postload channel name=%s\n", channelSettings.name); + okay = true; } f.close(); } else { - DEBUG_MSG("No saved preferences found\n"); + DEBUG_MSG("No %s preferences found\n", filename); } - #else DEBUG_MSG("ERROR: Filesystem not implemented\n"); #endif + return okay; +} + +void NodeDB::loadFromDisk() +{ + // static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM + if (!loadProto(preffile, DeviceState_size, sizeof(devicestate), DeviceState_fields, &devicestate)) { + installDefaultDeviceState(); // Our in RAM copy might now be corrupt + } else { + if (devicestate.version < DEVICESTATE_MIN_VER) { + DEBUG_MSG("Warn: devicestate %d is old, discarding\n", devicestate.version); + installDefaultDeviceState(); + } else { + DEBUG_MSG("Loaded saved preferences version %d\n", devicestate.version); + } + } + + if (!loadProto(radiofile, RadioConfig_size, sizeof(RadioConfig), RadioConfig_fields, &radioConfig)) { + installDefaultRadioConfig(); // Our in RAM copy might now be corrupt + } + + if (!loadProto(channelfile, ChannelFile_size, sizeof(ChannelFile), ChannelFile_fields, &channelFile)) { + installDefaultChannels(); // Our in RAM copy might now be corrupt + } +} + +/** Save a protobuf from a file, return true for success */ +bool saveProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, const void *dest_struct) +{ +#ifdef FS + // static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM + String filenameTmp = filename; + filenameTmp += ".tmp"; + auto f = FS.open(filenameTmp.c_str(), FILE_O_WRITE); + bool okay = false; + if (f) { + DEBUG_MSG("Saving %s\n", filename); + pb_ostream_t stream = {&writecb, &f, protoSize}; + + if (!pb_encode(&stream, fields, dest_struct)) { + DEBUG_MSG("Error: can't encode protobuf %s\n", PB_GET_ERROR(&stream)); + } else { + okay = true; + } + + f.close(); + + // brief window of risk here ;-) + if (!FS.remove(filename)) + DEBUG_MSG("Warning: Can't remove old pref file\n"); + if (!FS.rename(filenameTmp.c_str(), filename)) + DEBUG_MSG("Error: can't rename new pref file\n"); + } else { + DEBUG_MSG("Can't write prefs\n"); + } +#else + DEBUG_MSG("ERROR: Filesystem not implemented\n"); +#endif + return okay; +} + +void NodeDB::saveChannelsToDisk() +{ + if (!devicestate.no_save) { +#ifdef FS + FS.mkdir("/prefs"); +#endif + saveProto(channelfile, ChannelFile_size, sizeof(ChannelFile), ChannelFile_fields, &channelFile); + } } void NodeDB::saveToDisk() { -#ifdef FS if (!devicestate.no_save) { - auto f = FS.open(preftmp, FILE_O_WRITE); - if (f) { - DEBUG_MSG("Writing preferences\n"); +#ifdef FS + FS.mkdir("/prefs"); +#endif + bool okay = saveProto(preffile, DeviceState_size, sizeof(devicestate), DeviceState_fields, &devicestate); + okay &= saveProto(radiofile, RadioConfig_size, sizeof(RadioConfig), RadioConfig_fields, &radioConfig); + saveChannelsToDisk(); - pb_ostream_t stream = {&writecb, &f, SIZE_MAX, 0}; - - // DEBUG_MSG("Presave channel name=%s\n", channelSettings.name); - - devicestate.version = DEVICESTATE_CUR_VER; - if (!pb_encode(&stream, DeviceState_fields, &devicestate)) { - DEBUG_MSG("Error: can't write protobuf %s\n", PB_GET_ERROR(&stream)); - // FIXME - report failure to phone - - f.close(); - } else { - // Success - replace the old file - f.close(); - - // brief window of risk here ;-) - if (!FS.remove(preffile)) - DEBUG_MSG("Warning: Can't remove old pref file\n"); - if (!FS.rename(preftmp, preffile)) - DEBUG_MSG("Error: can't rename new pref file\n"); - } - } else { - DEBUG_MSG("ERROR: can't write prefs\n"); // FIXME report to app - } + // remove any pre 1.2 pref files, turn on after 1.2 is in beta + // if(okay) FS.remove(preffileOld); } else { DEBUG_MSG("***** DEVELOPMENT MODE - DO NOT RELEASE - not saving to flash *****\n"); } -#else - DEBUG_MSG("ERROR filesystem not implemented\n"); -#endif } const NodeInfo *NodeDB::readNextInfo() @@ -370,7 +437,7 @@ void NodeDB::updatePosition(uint32_t nodeId, const Position &p) // recorded based on the packet rxTime if (!info->position.time && p.time) info->position.time = p.time; - if(p.battery_level) + if (p.battery_level) info->position.battery_level = p.battery_level; if (p.latitude_i || p.longitude_i) { info->position.latitude_i = p.latitude_i; diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index e5f1b656..bd734645 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -9,8 +9,9 @@ #include "mesh-pb-constants.h" extern DeviceState devicestate; +extern ChannelFile channelFile; extern MyNodeInfo &myNodeInfo; -extern RadioConfig &radioConfig; +extern RadioConfig radioConfig; extern User &owner; /// Given a node, return how many seconds in the past (vs now) that we last heard from it @@ -42,7 +43,7 @@ class NodeDB void init(); /// write to flash - void saveToDisk(); + void saveToDisk(), saveChannelsToDisk(); /** Reinit radio config if needed, because either: * a) sometimes a buggy android app might send us bogus settings or @@ -117,7 +118,7 @@ class NodeDB void loadFromDisk(); /// Reinit device state from scratch (not loading from disk) - void installDefaultDeviceState(); + void installDefaultDeviceState(), installDefaultRadioConfig(), installDefaultChannels(); }; /** diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 6e2ac479..b50381e7 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -156,7 +156,6 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) state = STATE_SEND_PACKETS; break; - case STATE_LEGACY: // Treat as the same as send packets case STATE_SEND_PACKETS: // Do we have a message from the mesh? if (packetForPhone) { @@ -208,7 +207,6 @@ bool PhoneAPI::available() case STATE_SEND_COMPLETE_ID: return true; - case STATE_LEGACY: // Treat as the same as send packets case STATE_SEND_PACKETS: { // Try to pull a new packet from the service (if we haven't already) if (!packetForPhone) @@ -236,7 +234,7 @@ int PhoneAPI::onNotify(uint32_t newValue) checkConnectionTimeout(); // a handy place to check if we've heard from the phone (since the BLE version doesn't call this // from idle) - if (state == STATE_SEND_PACKETS || state == STATE_LEGACY) { + if (state == STATE_SEND_PACKETS) { DEBUG_MSG("Telling client we have new packets %u\n", newValue); onNowHasData(newValue); } else diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 094e9830..6d03d2c8 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -20,7 +20,7 @@ class PhoneAPI : public Observer // FIXME, we shouldn't be inheriting from Observer, instead use CallbackObserver as a member { enum State { - STATE_LEGACY, // (no longer used) old default state - until Android apps are all updated, uses the old BLE API + STATE_UNUSED, // (no longer used) old default state - until Android apps are all updated, uses the old BLE API STATE_SEND_NOTHING, // (Eventual) Initial state, don't send anything until the client starts asking for config STATE_SEND_MY_INFO, // send our my info record // STATE_SEND_RADIO, // in 1.2 we now send this as a regular mesh packet diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 49dc6992..0ee23529 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -153,6 +153,9 @@ void printPacket(const char *prefix, const MeshPacket *p) if (s.dest != 0) DEBUG_MSG(" dest=%08x", s.dest); + if(s.request_id) + DEBUG_MSG(" requestId=%0x", s.request_id); + /* now inside Data and therefore kinda opaque if (s.which_ackVariant == SubPacket_success_id_tag) DEBUG_MSG(" successId=%08x", s.ackVariant.success_id); diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 962aabeb..4d484594 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -38,7 +38,7 @@ bool ReliableRouter::shouldFilterReceived(const MeshPacket *p) DEBUG_MSG("generating implicit ack\n"); // NOTE: we do NOT check p->wantAck here because p is the INCOMING rebroadcast and that packet is not expected to be // marked as wantAck - sendAckNak(Routing_Error_NONE, getFrom(p), p->id); + sendAckNak(Routing_Error_NONE, getFrom(p), p->id, p->channel); } } @@ -65,9 +65,9 @@ void ReliableRouter::sniffReceived(const MeshPacket *p, const Routing *c) // - not DSR routing) if (p->want_ack) { if (MeshPlugin::currentReply) - DEBUG_MSG("Someone else has replied to this message, no need for a 2nd ack"); + DEBUG_MSG("Someone else has replied to this message, no need for a 2nd ack\n"); else - sendAckNak(Routing_Error_NONE, getFrom(p), p->id); + sendAckNak(Routing_Error_NONE, getFrom(p), p->id, p->channel); } // We consider an ack to be either a !routing packet with a request ID or a routing packet with !error @@ -166,7 +166,7 @@ int32_t ReliableRouter::doRetransmissions() if (p.numRetransmissions == 0) { DEBUG_MSG("Reliable send failed, returning a nak for fr=0x%x,to=0x%x,id=0x%x\n", p.packet->from, p.packet->to, p.packet->id); - sendAckNak(Routing_Error_MAX_RETRANSMIT, getFrom(p.packet), p.packet->id); + sendAckNak(Routing_Error_MAX_RETRANSMIT, getFrom(p.packet), p.packet->id, p.packet->channel); // Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived - which // allows the DSR version to still be able to look at the PendingPacket stopRetransmission(it->first); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 8f566dff..52b28eb5 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -103,15 +103,15 @@ MeshPacket *Router::allocForSending() /** * Send an ack or a nak packet back towards whoever sent idFrom */ -void Router::sendAckNak(Routing_Error err, NodeNum to, PacketId idFrom) +void Router::sendAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex) { - routingPlugin->sendAckNak(err, to, idFrom); + routingPlugin->sendAckNak(err, to, idFrom, chIndex); } void Router::abortSendAndNak(Routing_Error err, MeshPacket *p) { DEBUG_MSG("Error=%d, returning NAK and dropping packet.\n", err); - sendAckNak(Routing_Error_NO_INTERFACE, getFrom(p), p->id); + sendAckNak(Routing_Error_NO_INTERFACE, getFrom(p), p->id, p->channel); packetPool.release(p); } diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 6e217017..3600af36 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -6,6 +6,7 @@ #include "PointerQueue.h" #include "RadioInterface.h" #include "concurrency/OSThread.h" +#include "Channels.h" /** * A mesh aware router that supports multiple interfaces. @@ -106,7 +107,7 @@ class Router : protected concurrency::OSThread /** * Send an ack or a nak packet back towards whoever sent idFrom */ - void sendAckNak(Routing_Error err, NodeNum to, PacketId idFrom); + void sendAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex); private: /** diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 8015b951..a5e36d46 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -71,12 +71,18 @@ void StreamAPI::writeStream() void StreamAPI::emitTxBuffer(size_t len) { if (len != 0) { + DEBUG_MSG("emit tx %d\n", len); txBuf[0] = START1; txBuf[1] = START2; txBuf[2] = (len >> 8) & 0xff; txBuf[3] = len & 0xff; - stream->write(txBuf, len + HEADER_LEN); + auto totalLen = len + HEADER_LEN; + stream->write(txBuf, totalLen); + /* for(size_t i = 0; i < totalLen; i++) { + stream->write(txBuf[i]); + // stream->flush(); + } */ } } diff --git a/src/mesh/generated/admin.pb.h b/src/mesh/generated/admin.pb.h index 7c0ac914..d68103ce 100644 --- a/src/mesh/generated/admin.pb.h +++ b/src/mesh/generated/admin.pb.h @@ -23,6 +23,8 @@ typedef struct _AdminMessage { RadioConfig get_radio_response; uint32_t get_channel_request; Channel get_channel_response; + bool confirm_set_channel; + bool confirm_set_radio; }; } AdminMessage; @@ -43,6 +45,8 @@ extern "C" { #define AdminMessage_get_radio_response_tag 5 #define AdminMessage_get_channel_request_tag 6 #define AdminMessage_get_channel_response_tag 7 +#define AdminMessage_confirm_set_channel_tag 32 +#define AdminMessage_confirm_set_radio_tag 33 /* Struct field encoding specification for nanopb */ #define AdminMessage_FIELDLIST(X, a) \ @@ -52,7 +56,9 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,set_channel,set_channel), 3) \ X(a, STATIC, ONEOF, BOOL, (variant,get_radio_request,get_radio_request), 4) \ X(a, STATIC, ONEOF, MESSAGE, (variant,get_radio_response,get_radio_response), 5) \ X(a, STATIC, ONEOF, UINT32, (variant,get_channel_request,get_channel_request), 6) \ -X(a, STATIC, ONEOF, MESSAGE, (variant,get_channel_response,get_channel_response), 7) +X(a, STATIC, ONEOF, MESSAGE, (variant,get_channel_response,get_channel_response), 7) \ +X(a, STATIC, ONEOF, BOOL, (variant,confirm_set_channel,confirm_set_channel), 32) \ +X(a, STATIC, ONEOF, BOOL, (variant,confirm_set_radio,confirm_set_radio), 33) #define AdminMessage_CALLBACK NULL #define AdminMessage_DEFAULT NULL #define AdminMessage_variant_set_radio_MSGTYPE RadioConfig diff --git a/src/mesh/generated/channel.pb.h b/src/mesh/generated/channel.pb.h index 151495be..962e452e 100644 --- a/src/mesh/generated/channel.pb.h +++ b/src/mesh/generated/channel.pb.h @@ -40,7 +40,7 @@ typedef struct _ChannelSettings { } ChannelSettings; typedef struct _Channel { - uint8_t index; + int8_t index; bool has_settings; ChannelSettings settings; Channel_Role role; @@ -100,7 +100,7 @@ X(a, STATIC, SINGULAR, BOOL, downlink_enabled, 17) #define ChannelSettings_DEFAULT NULL #define Channel_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, index, 1) \ +X(a, STATIC, SINGULAR, INT32, index, 1) \ X(a, STATIC, OPTIONAL, MESSAGE, settings, 2) \ X(a, STATIC, SINGULAR, UENUM, role, 3) #define Channel_CALLBACK NULL @@ -116,7 +116,7 @@ extern const pb_msgdesc_t Channel_msg; /* Maximum encoded size of messages (where known) */ #define ChannelSettings_size 87 -#define Channel_size 94 +#define Channel_size 102 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/deviceonly.pb.c b/src/mesh/generated/deviceonly.pb.c index cfb836b0..dd2bb751 100644 --- a/src/mesh/generated/deviceonly.pb.c +++ b/src/mesh/generated/deviceonly.pb.c @@ -6,7 +6,16 @@ #error Regenerate this file with the current version of nanopb generator. #endif +PB_BIND(LegacyRadioConfig, LegacyRadioConfig, AUTO) + + +PB_BIND(LegacyRadioConfig_LegacyPreferences, LegacyRadioConfig_LegacyPreferences, AUTO) + + PB_BIND(DeviceState, DeviceState, 2) +PB_BIND(ChannelFile, ChannelFile, 2) + + diff --git a/src/mesh/generated/deviceonly.pb.h b/src/mesh/generated/deviceonly.pb.h index dd345184..9c40e211 100644 --- a/src/mesh/generated/deviceonly.pb.h +++ b/src/mesh/generated/deviceonly.pb.h @@ -5,17 +5,31 @@ #define PB_DEVICEONLY_PB_H_INCLUDED #include #include "mesh.pb.h" -#include "radioconfig.pb.h" #include "channel.pb.h" +#include "radioconfig.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. #endif /* Struct definitions */ +typedef struct _ChannelFile { + pb_size_t channels_count; + Channel channels[8]; +} ChannelFile; + +typedef struct _LegacyRadioConfig_LegacyPreferences { + RegionCode region; +} LegacyRadioConfig_LegacyPreferences; + +typedef struct _LegacyRadioConfig { + bool has_preferences; + LegacyRadioConfig_LegacyPreferences preferences; +} LegacyRadioConfig; + typedef struct _DeviceState { - bool has_radio; - RadioConfig radio; + bool has_legacyRadio; + LegacyRadioConfig legacyRadio; bool has_my_node; MyNodeInfo my_node; bool has_owner; @@ -29,8 +43,6 @@ typedef struct _DeviceState { uint32_t version; bool no_save; bool did_gps_reset; - pb_size_t channels_count; - Channel channels[8]; } DeviceState; @@ -39,11 +51,20 @@ extern "C" { #endif /* Initializer values for message structs */ -#define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default}, false, MeshPacket_init_default, 0, 0, 0, 0, {Channel_init_default, Channel_init_default, Channel_init_default, Channel_init_default, Channel_init_default, Channel_init_default, Channel_init_default, Channel_init_default}} -#define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero}, false, MeshPacket_init_zero, 0, 0, 0, 0, {Channel_init_zero, Channel_init_zero, Channel_init_zero, Channel_init_zero, Channel_init_zero, Channel_init_zero, Channel_init_zero, Channel_init_zero}} +#define LegacyRadioConfig_init_default {false, LegacyRadioConfig_LegacyPreferences_init_default} +#define LegacyRadioConfig_LegacyPreferences_init_default {_RegionCode_MIN} +#define DeviceState_init_default {false, LegacyRadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default}, false, MeshPacket_init_default, 0, 0, 0} +#define ChannelFile_init_default {0, {Channel_init_default, Channel_init_default, Channel_init_default, Channel_init_default, Channel_init_default, Channel_init_default, Channel_init_default, Channel_init_default}} +#define LegacyRadioConfig_init_zero {false, LegacyRadioConfig_LegacyPreferences_init_zero} +#define LegacyRadioConfig_LegacyPreferences_init_zero {_RegionCode_MIN} +#define DeviceState_init_zero {false, LegacyRadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero}, false, MeshPacket_init_zero, 0, 0, 0} +#define ChannelFile_init_zero {0, {Channel_init_zero, Channel_init_zero, Channel_init_zero, Channel_init_zero, Channel_init_zero, Channel_init_zero, Channel_init_zero, Channel_init_zero}} /* Field tags (for use in manual encoding/decoding) */ -#define DeviceState_radio_tag 1 +#define ChannelFile_channels_tag 1 +#define LegacyRadioConfig_LegacyPreferences_region_tag 15 +#define LegacyRadioConfig_preferences_tag 1 +#define DeviceState_legacyRadio_tag 1 #define DeviceState_my_node_tag 2 #define DeviceState_owner_tag 3 #define DeviceState_node_db_tag 4 @@ -52,11 +73,21 @@ extern "C" { #define DeviceState_version_tag 8 #define DeviceState_no_save_tag 9 #define DeviceState_did_gps_reset_tag 11 -#define DeviceState_channels_tag 13 /* Struct field encoding specification for nanopb */ +#define LegacyRadioConfig_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, preferences, 1) +#define LegacyRadioConfig_CALLBACK NULL +#define LegacyRadioConfig_DEFAULT NULL +#define LegacyRadioConfig_preferences_MSGTYPE LegacyRadioConfig_LegacyPreferences + +#define LegacyRadioConfig_LegacyPreferences_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, region, 15) +#define LegacyRadioConfig_LegacyPreferences_CALLBACK NULL +#define LegacyRadioConfig_LegacyPreferences_DEFAULT NULL + #define DeviceState_FIELDLIST(X, a) \ -X(a, STATIC, OPTIONAL, MESSAGE, radio, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, legacyRadio, 1) \ X(a, STATIC, OPTIONAL, MESSAGE, my_node, 2) \ X(a, STATIC, OPTIONAL, MESSAGE, owner, 3) \ X(a, STATIC, REPEATED, MESSAGE, node_db, 4) \ @@ -64,25 +95,38 @@ X(a, STATIC, REPEATED, MESSAGE, receive_queue, 5) \ X(a, STATIC, OPTIONAL, MESSAGE, rx_text_message, 7) \ X(a, STATIC, SINGULAR, UINT32, version, 8) \ X(a, STATIC, SINGULAR, BOOL, no_save, 9) \ -X(a, STATIC, SINGULAR, BOOL, did_gps_reset, 11) \ -X(a, STATIC, REPEATED, MESSAGE, channels, 13) +X(a, STATIC, SINGULAR, BOOL, did_gps_reset, 11) #define DeviceState_CALLBACK NULL #define DeviceState_DEFAULT NULL -#define DeviceState_radio_MSGTYPE RadioConfig +#define DeviceState_legacyRadio_MSGTYPE LegacyRadioConfig #define DeviceState_my_node_MSGTYPE MyNodeInfo #define DeviceState_owner_MSGTYPE User #define DeviceState_node_db_MSGTYPE NodeInfo #define DeviceState_receive_queue_MSGTYPE MeshPacket #define DeviceState_rx_text_message_MSGTYPE MeshPacket -#define DeviceState_channels_MSGTYPE Channel +#define ChannelFile_FIELDLIST(X, a) \ +X(a, STATIC, REPEATED, MESSAGE, channels, 1) +#define ChannelFile_CALLBACK NULL +#define ChannelFile_DEFAULT NULL +#define ChannelFile_channels_MSGTYPE Channel + +extern const pb_msgdesc_t LegacyRadioConfig_msg; +extern const pb_msgdesc_t LegacyRadioConfig_LegacyPreferences_msg; extern const pb_msgdesc_t DeviceState_msg; +extern const pb_msgdesc_t ChannelFile_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define LegacyRadioConfig_fields &LegacyRadioConfig_msg +#define LegacyRadioConfig_LegacyPreferences_fields &LegacyRadioConfig_LegacyPreferences_msg #define DeviceState_fields &DeviceState_msg +#define ChannelFile_fields &ChannelFile_msg /* Maximum encoded size of messages (where known) */ -#define DeviceState_size 6169 +#define LegacyRadioConfig_size 4 +#define LegacyRadioConfig_LegacyPreferences_size 2 +#define DeviceState_size 5056 +#define ChannelFile_size 832 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/mesh.pb.h b/src/mesh/generated/mesh.pb.h index b7df1079..10487e40 100644 --- a/src/mesh/generated/mesh.pb.h +++ b/src/mesh/generated/mesh.pb.h @@ -37,7 +37,8 @@ typedef enum _Routing_Error { Routing_Error_NO_INTERFACE = 4, Routing_Error_MAX_RETRANSMIT = 5, Routing_Error_NO_CHANNEL = 6, - Routing_Error_TOO_LARGE = 7 + Routing_Error_TOO_LARGE = 7, + Routing_Error_NO_RESPONSE = 8 } Routing_Error; typedef enum _MeshPacket_Priority { @@ -182,8 +183,8 @@ typedef struct _ToRadio { #define _CriticalErrorCode_ARRAYSIZE ((CriticalErrorCode)(CriticalErrorCode_Brownout+1)) #define _Routing_Error_MIN Routing_Error_NONE -#define _Routing_Error_MAX Routing_Error_TOO_LARGE -#define _Routing_Error_ARRAYSIZE ((Routing_Error)(Routing_Error_TOO_LARGE+1)) +#define _Routing_Error_MAX Routing_Error_NO_RESPONSE +#define _Routing_Error_ARRAYSIZE ((Routing_Error)(Routing_Error_NO_RESPONSE+1)) #define _MeshPacket_Priority_MIN MeshPacket_Priority_UNSET #define _MeshPacket_Priority_MAX MeshPacket_Priority_MAX diff --git a/src/mesh/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h index 0b2a0d6c..c0c90849 100644 --- a/src/mesh/mesh-pb-constants.h +++ b/src/mesh/mesh-pb-constants.h @@ -18,7 +18,7 @@ #define MAX_NUM_NODES (member_size(DeviceState, node_db) / member_size(DeviceState, node_db[0])) /// Max number of channels allowed -#define MAX_NUM_CHANNELS (member_size(DeviceState, channels) / member_size(DeviceState, channels[0])) +#define MAX_NUM_CHANNELS (member_size(ChannelFile, channels) / member_size(ChannelFile, channels[0])) /// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic /// returns the encoded packet size diff --git a/src/plugins/AdminPlugin.cpp b/src/plugins/AdminPlugin.cpp index 7e0a6707..060b1e63 100644 --- a/src/plugins/AdminPlugin.cpp +++ b/src/plugins/AdminPlugin.cpp @@ -8,8 +8,9 @@ AdminPlugin *adminPlugin; -void AdminPlugin::handleGetChannel(const MeshPacket &req, uint32_t channelIndex) { - if (req.decoded.want_response) { +void AdminPlugin::handleGetChannel(const MeshPacket &req, uint32_t channelIndex) +{ + if (req.decoded.want_response) { // We create the reply here AdminMessage r = AdminMessage_init_default; r.get_channel_response = channels.getByIndex(channelIndex); @@ -23,7 +24,7 @@ void AdminPlugin::handleGetRadio(const MeshPacket &req) if (req.decoded.want_response) { // We create the reply here AdminMessage r = AdminMessage_init_default; - r.get_radio_response = devicestate.radio; + r.get_radio_response = radioConfig; // NOTE: The phone app needs to know the ls_secs value so it can properly expect sleep behavior. // So even if we internally use 0 to represent 'use default' we still need to send the value we are @@ -65,6 +66,8 @@ bool AdminPlugin::handleReceivedProtobuf(const MeshPacket &mp, const AdminMessag break; default: + // Probably a message sent by us or sent to our local node. FIXME, we should avoid scanning these messages + DEBUG_MSG("Ignoring nonrelevant admin %d\n", r->which_variant); break; } return false; // Let others look at this message also if they want @@ -95,21 +98,22 @@ void AdminPlugin::handleSetChannel(const Channel &cc) { channels.setChannel(cc); - bool didReset = service.reloadConfig(); - /* FIXME - do we need this still? - if (didReset) { - state = STATE_SEND_MY_INFO; // Squirt a completely new set of configs to the client - } */ + // Just update and save the channels - no need to update the radio for ! primary channel changes + if (cc.index == 0) { + // FIXME, this updates the user preferences also, which isn't needed - we really just want to notify on configChanged + service.reloadConfig(); + } + else { + channels.onConfigChanged(); // tell the radios about this change + nodeDB.saveChannelsToDisk(); + } } void AdminPlugin::handleSetRadio(const RadioConfig &r) { radioConfig = r; - bool didReset = service.reloadConfig(); - /* FIXME - do we need this still? if (didReset) { - state = STATE_SEND_MY_INFO; // Squirt a completely new set of configs to the client - } */ + service.reloadConfig(); } MeshPacket *AdminPlugin::allocReply() @@ -121,5 +125,6 @@ MeshPacket *AdminPlugin::allocReply() AdminPlugin::AdminPlugin() : ProtobufPlugin("Admin", PortNum_ADMIN_APP, AdminMessage_fields) { - // FIXME, restrict to the admin channel for rx + // restrict to the admin channel for rx + boundChannel = "admin"; } diff --git a/src/plugins/RoutingPlugin.cpp b/src/plugins/RoutingPlugin.cpp index f6c2ea03..4fe0b88d 100644 --- a/src/plugins/RoutingPlugin.cpp +++ b/src/plugins/RoutingPlugin.cpp @@ -35,7 +35,7 @@ MeshPacket *RoutingPlugin::allocReply() return NULL; } -void RoutingPlugin::sendAckNak(Routing_Error err, NodeNum to, PacketId idFrom) +void RoutingPlugin::sendAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex) { Routing c = Routing_init_default; @@ -47,6 +47,7 @@ void RoutingPlugin::sendAckNak(Routing_Error err, NodeNum to, PacketId idFrom) p->hop_limit = 0; // Assume just immediate neighbors for now p->to = to; p->decoded.request_id = idFrom; + p->channel = chIndex; DEBUG_MSG("Sending an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id); router->sendLocal(p); // we sometimes send directly to the local node diff --git a/src/plugins/RoutingPlugin.h b/src/plugins/RoutingPlugin.h index a848bc9c..ff659de2 100644 --- a/src/plugins/RoutingPlugin.h +++ b/src/plugins/RoutingPlugin.h @@ -1,5 +1,6 @@ #pragma once #include "ProtobufPlugin.h" +#include "Channels.h" /** * Routing plugin for router control messages @@ -12,6 +13,8 @@ class RoutingPlugin : public ProtobufPlugin */ RoutingPlugin(); + void sendAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex); + protected: friend class Router; @@ -27,8 +30,6 @@ class RoutingPlugin : public ProtobufPlugin /// Override wantPacket to say we want to see all packets, not just those for our port number virtual bool wantPacket(const MeshPacket *p) { return true; } - - void sendAckNak(Routing_Error err, NodeNum to, PacketId idFrom); }; extern RoutingPlugin *routingPlugin; \ No newline at end of file diff --git a/src/plugins/esp32/StoreForwardPlugin.cpp b/src/plugins/esp32/StoreForwardPlugin.cpp index 5fec31ad..897a2c94 100644 --- a/src/plugins/esp32/StoreForwardPlugin.cpp +++ b/src/plugins/esp32/StoreForwardPlugin.cpp @@ -83,7 +83,8 @@ int32_t StoreForwardPlugin::runOnce() DEBUG_MSG("Store & Forward Plugin - Sending heartbeat\n"); // storeForwardPluginRadio->sendPayloadHeartbeat(); - storeForwardPluginRadio->sendPayload(); + if(storeForwardPluginRadio) + storeForwardPluginRadio->sendPayload(); return (1 * 60 * 1000); } diff --git a/version.properties b/version.properties index d0634458..8f23a55a 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 1 minor = 2 -build = 6 +build = 9