diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp new file mode 100644 index 00000000..f003aa52 --- /dev/null +++ b/examples/companion_radio/UITask.cpp @@ -0,0 +1,119 @@ +#include "UITask.h" +#include +#include + +#define AUTO_OFF_MILLIS 15000 // 15 seconds + +// 'meshcore', 128x13px +static const uint8_t meshcore_logo [] PROGMEM = { + 0x3c, 0x01, 0xe3, 0xff, 0xc7, 0xff, 0x8f, 0x03, 0x87, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, + 0x3c, 0x03, 0xe3, 0xff, 0xc7, 0xff, 0x8e, 0x03, 0x8f, 0xfe, 0x3f, 0xfe, 0x1f, 0xff, 0x1f, 0xfe, + 0x3e, 0x03, 0xc3, 0xff, 0x8f, 0xff, 0x0e, 0x07, 0x8f, 0xfe, 0x7f, 0xfe, 0x1f, 0xff, 0x1f, 0xfc, + 0x3e, 0x07, 0xc7, 0x80, 0x0e, 0x00, 0x0e, 0x07, 0x9e, 0x00, 0x78, 0x0e, 0x3c, 0x0f, 0x1c, 0x00, + 0x3e, 0x0f, 0xc7, 0x80, 0x1e, 0x00, 0x0e, 0x07, 0x1e, 0x00, 0x70, 0x0e, 0x38, 0x0f, 0x3c, 0x00, + 0x7f, 0x0f, 0xc7, 0xfe, 0x1f, 0xfc, 0x1f, 0xff, 0x1c, 0x00, 0x70, 0x0e, 0x38, 0x0e, 0x3f, 0xf8, + 0x7f, 0x1f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x0e, 0x38, 0x0e, 0x3f, 0xf8, + 0x7f, 0x3f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x1e, 0x3f, 0xfe, 0x3f, 0xf0, + 0x77, 0x3b, 0x87, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xfc, 0x38, 0x00, + 0x77, 0xfb, 0x8f, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xf8, 0x38, 0x00, + 0x73, 0xf3, 0x8f, 0xff, 0x0f, 0xff, 0x1c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x78, 0x7f, 0xf8, + 0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfe, 0x3c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x3c, 0x7f, 0xf8, + 0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8, +}; + +void UITask::begin(const char* node_name, const char* build_date, uint32_t pin_code) { + _prevBtnState = HIGH; + _auto_off = millis() + AUTO_OFF_MILLIS; + clearMsgPreview(); + _node_name = node_name; + _build_date = build_date; + _pin_code = pin_code; + _display->turnOn(); +} + +void UITask::clearMsgPreview() { + _origin[0] = 0; + _msg[0] = 0; +} + +void UITask::showMsgPreview(uint8_t path_len, const char* from_name, const char* text) { + if (path_len == 0xFF) { + sprintf(_origin, "(F) %s", from_name); + } else { + sprintf(_origin, "(%d) %s", (uint32_t) path_len, from_name); + } + StrHelper::strncpy(_msg, text, sizeof(_msg)); + + if (!_display->isOn()) _display->turnOn(); + _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer +} + +void UITask::renderCurrScreen() { + char tmp[80]; + if (_origin[0] && _msg[0]) { + // render message preview + _display->setCursor(0, 0); + _display->setTextSize(1); + _display->print(_node_name); + + _display->setCursor(0, 12); + _display->print(_origin); + _display->setCursor(0, 24); + _display->print(_msg); + + //_display->setCursor(100, 9); TODO + //_display->setTextSize(2); + //_display->printf("%d", msgs); + } else { + // render 'home' screen + _display->drawXbm(0, 0, meshcore_logo, 128, 13); + _display->setCursor(0, 20); + _display->setTextSize(1); + _display->print(_node_name); + + sprintf(tmp, "Build: %s", _build_date); + _display->setCursor(0, 32); + _display->print(tmp); + + if (_connected) { + //_display->printf("freq : %03.2f sf %d\n", _prefs.freq, _prefs.sf); + //_display->printf("bw : %03.2f cr %d\n", _prefs.bw, _prefs.cr); + } else { + _display->setTextSize(2); + _display->setCursor(0, 43); + sprintf(tmp, "Pin:%d", _pin_code); + _display->print(tmp); + } + } +} + +void UITask::loop() { + if (millis() >= _next_read) { + int btnState = digitalRead(PIN_USER_BTN); + if (btnState != _prevBtnState) { + if (btnState == LOW) { // pressed? + if (_display->isOn()) { + clearMsgPreview(); + } else { + _display->turnOn(); + } + _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer + } + _prevBtnState = btnState; + } + _next_read = millis() + 100; // 10 reads per second + } + + if (_display->isOn()) { + if (millis() >= _next_refresh) { + _display->startFrame(); + renderCurrScreen(); + _display->endFrame(); + + _next_refresh = millis() + 1000; // refresh every second + } + if (millis() > _auto_off) { + _display->turnOff(); + } + } +} diff --git a/examples/companion_radio/UITask.h b/examples/companion_radio/UITask.h new file mode 100644 index 00000000..f1a63225 --- /dev/null +++ b/examples/companion_radio/UITask.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +class UITask { + DisplayDriver* _display; + unsigned long _next_read, _next_refresh, _auto_off; + int _prevBtnState; + bool _connected; + uint32_t _pin_code; + const char* _node_name; + const char* _build_date; + char _origin[62]; + char _msg[80]; + + void renderCurrScreen(); +public: + UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; _connected = false; } + void begin(const char* node_name, const char* build_date, uint32_t pin_code); + + void setHasConnection(bool connected) { _connected = connected; } + void clearMsgPreview(); + void showMsgPreview(uint8_t path_len, const char* from_name, const char* text); + void loop(); +}; \ No newline at end of file diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 3c561aba..403685d5 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -88,6 +88,15 @@ #error "need to provide a 'board' object" #endif +#ifdef DISPLAY_CLASS + #include + + static DISPLAY_CLASS display; + + #include "UITask.h" + static UITask ui_task(display); +#endif + // Believe it or not, this std C function is busted on some platforms! static uint32_t _atoi(const char* sp) { uint32_t n = 0; @@ -182,6 +191,7 @@ struct NodePrefs { // persisted to file uint8_t tx_power_dbm; uint8_t unused[3]; float rx_delay_base; + uint32_t ble_pin; }; class MyMesh : public BaseChatMesh { @@ -214,6 +224,11 @@ class MyMesh : public BaseChatMesh { if (!_identity_store->load("_main", self_id)) { self_id = mesh::LocalIdentity(&trng); // create new random identity saveMainIdentity(self_id); + + #if defined(BLE_PIN_CODE) && defined(DISPLAY_CLASS) + // start with randomly assigned BLE pin + _prefs.ble_pin = trng.nextInt(100000, 999999); + #endif } } @@ -475,6 +490,9 @@ protected: } else { soundBuzzer(); } + #ifdef DISPLAY_CLASS + ui_task.showMsgPreview(path_len, from.name, text); + #endif } void onMessageRecv(const ContactInfo& from, uint8_t path_len, uint32_t sender_timestamp, const char *text) override { @@ -514,6 +532,9 @@ protected: } else { soundBuzzer(); } + #ifdef DISPLAY_CLASS + ui_task.showMsgPreview(in_path_len < 0 ? 0xFF : in_path_len, "Public", text); + #endif } void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) override { @@ -600,6 +621,9 @@ public: _prefs.bw = LORA_BW; _prefs.cr = LORA_CR; _prefs.tx_power_dbm = LORA_TX_POWER; + #ifdef BLE_PIN_CODE + _prefs.ble_pin = BLE_PIN_CODE; + #endif //_prefs.rx_delay_base = 10.0f; enable once new algo fixed } @@ -620,7 +644,34 @@ public: if (_fs->exists("/node_prefs")) { File file = _fs->open("/node_prefs"); if (file) { - file.read((uint8_t *) &_prefs, sizeof(_prefs)); + uint8_t pad[8]; + + file.read((uint8_t *) &_prefs.airtime_factor, sizeof(float)); // 0 + file.read((uint8_t *) _prefs.node_name, sizeof(_prefs.node_name)); // 4 + file.read(pad, 4); // 36 + file.read((uint8_t *) &_prefs.node_lat, sizeof(_prefs.node_lat)); // 40 + file.read((uint8_t *) &_prefs.node_lon, sizeof(_prefs.node_lon)); // 48 + file.read((uint8_t *) &_prefs.freq, sizeof(_prefs.freq)); // 56 + file.read((uint8_t *) &_prefs.sf, sizeof(_prefs.sf)); // 60 + file.read((uint8_t *) &_prefs.cr, sizeof(_prefs.cr)); // 61 + file.read((uint8_t *) &_prefs.reserved1, sizeof(_prefs.reserved1)); // 62 + file.read((uint8_t *) &_prefs.reserved2, sizeof(_prefs.reserved2)); // 63 + file.read((uint8_t *) &_prefs.bw, sizeof(_prefs.bw)); // 64 + file.read((uint8_t *) &_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 + file.read((uint8_t *) _prefs.unused, sizeof(_prefs.unused)); // 69 + file.read((uint8_t *) &_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 + file.read(pad, 4); // 76 + file.read((uint8_t *) &_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 + + // sanitise bad pref values + _prefs.rx_delay_base = constrain(_prefs.rx_delay_base, 0, 20.0f); + _prefs.airtime_factor = constrain(_prefs.airtime_factor, 0, 9.0f); + _prefs.freq = constrain(_prefs.freq, 400.0f, 2500.0f); + _prefs.bw = constrain(_prefs.bw, 62.5f, 500.0f); + _prefs.sf = constrain(_prefs.sf, 7, 12); + _prefs.cr = constrain(_prefs.cr, 5, 8); + _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER); + file.close(); } } @@ -639,6 +690,7 @@ public: } const char* getNodeName() { return _prefs.node_name; } + uint32_t getBLEPin() { return _prefs.ble_pin; } void startInterface(BaseSerialInterface& serial) { _serial = &serial; @@ -653,7 +705,26 @@ public: File file = _fs->open("/node_prefs", "w", true); #endif if (file) { - file.write((const uint8_t *)&_prefs, sizeof(_prefs)); + uint8_t pad[8]; + memset(pad, 0, sizeof(pad)); + + file.write((uint8_t *) &_prefs.airtime_factor, sizeof(float)); // 0 + file.write((uint8_t *) _prefs.node_name, sizeof(_prefs.node_name)); // 4 + file.write(pad, 4); // 36 + file.write((uint8_t *) &_prefs.node_lat, sizeof(_prefs.node_lat)); // 40 + file.write((uint8_t *) &_prefs.node_lon, sizeof(_prefs.node_lon)); // 48 + file.write((uint8_t *) &_prefs.freq, sizeof(_prefs.freq)); // 56 + file.write((uint8_t *) &_prefs.sf, sizeof(_prefs.sf)); // 60 + file.write((uint8_t *) &_prefs.cr, sizeof(_prefs.cr)); // 61 + file.write((uint8_t *) &_prefs.reserved1, sizeof(_prefs.reserved1)); // 62 + file.write((uint8_t *) &_prefs.reserved2, sizeof(_prefs.reserved2)); // 63 + file.write((uint8_t *) &_prefs.bw, sizeof(_prefs.bw)); // 64 + file.write((uint8_t *) &_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 + file.write((uint8_t *) _prefs.unused, sizeof(_prefs.unused)); // 69 + file.write((uint8_t *) &_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 + file.write(pad, 4); // 76 + file.write((uint8_t *) &_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 + file.close(); } } @@ -1081,6 +1152,11 @@ public: } else if (!_serial->isWriteBusy()) { checkConnections(); } + + #ifdef DISPLAY_CLASS + ui_task.setHasConnection(_serial->isConnected()); + ui_task.loop(); + #endif } }; @@ -1112,7 +1188,7 @@ public: #if defined(NRF52_PLATFORM) RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); -#elif defined(LILYGO_TLORA) || defined(HELTEC_LORA_V2) // ESP32 with SX1276 +#elif defined(LILYGO_TLORA) SPIClass spi; RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1, spi); #elif defined(P_LORA_SCLK) @@ -1139,6 +1215,10 @@ void setup() { float tcxo = 1.6f; #endif +#ifdef DISPLAY_CLASS + display.begin(); +#endif + #if defined(NRF52_PLATFORM) SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); SPI.begin(); @@ -1173,7 +1253,7 @@ void setup() { #ifdef BLE_PIN_CODE char dev_name[32+10]; sprintf(dev_name, "MeshCore-%s", the_mesh.getNodeName()); - serial_interface.begin(dev_name, BLE_PIN_CODE); + serial_interface.begin(dev_name, the_mesh.getBLEPin()); #else pinMode(WB_IO2, OUTPUT); serial_interface.begin(Serial); @@ -1189,7 +1269,7 @@ void setup() { #elif defined(BLE_PIN_CODE) char dev_name[32+10]; sprintf(dev_name, "MeshCore-%s", the_mesh.getNodeName()); - serial_interface.begin(dev_name, BLE_PIN_CODE); + serial_interface.begin(dev_name, the_mesh.getBLEPin()); #else serial_interface.begin(Serial); #endif @@ -1197,6 +1277,10 @@ void setup() { #else #error "need to define filesystem" #endif + +#ifdef DISPLAY_CLASS + ui_task.begin(the_mesh.getNodeName(), FIRMWARE_BUILD_DATE, the_mesh.getBLEPin()); +#endif } void loop() { diff --git a/examples/simple_repeater/UITask.cpp b/examples/simple_repeater/UITask.cpp new file mode 100644 index 00000000..e8a204e3 --- /dev/null +++ b/examples/simple_repeater/UITask.cpp @@ -0,0 +1,79 @@ +#include "UITask.h" +#include + +#define AUTO_OFF_MILLIS 20000 // 20 seconds + +// 'meshcore', 128x13px +static const uint8_t meshcore_logo [] PROGMEM = { + 0x3c, 0x01, 0xe3, 0xff, 0xc7, 0xff, 0x8f, 0x03, 0x87, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, + 0x3c, 0x03, 0xe3, 0xff, 0xc7, 0xff, 0x8e, 0x03, 0x8f, 0xfe, 0x3f, 0xfe, 0x1f, 0xff, 0x1f, 0xfe, + 0x3e, 0x03, 0xc3, 0xff, 0x8f, 0xff, 0x0e, 0x07, 0x8f, 0xfe, 0x7f, 0xfe, 0x1f, 0xff, 0x1f, 0xfc, + 0x3e, 0x07, 0xc7, 0x80, 0x0e, 0x00, 0x0e, 0x07, 0x9e, 0x00, 0x78, 0x0e, 0x3c, 0x0f, 0x1c, 0x00, + 0x3e, 0x0f, 0xc7, 0x80, 0x1e, 0x00, 0x0e, 0x07, 0x1e, 0x00, 0x70, 0x0e, 0x38, 0x0f, 0x3c, 0x00, + 0x7f, 0x0f, 0xc7, 0xfe, 0x1f, 0xfc, 0x1f, 0xff, 0x1c, 0x00, 0x70, 0x0e, 0x38, 0x0e, 0x3f, 0xf8, + 0x7f, 0x1f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x0e, 0x38, 0x0e, 0x3f, 0xf8, + 0x7f, 0x3f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x1e, 0x3f, 0xfe, 0x3f, 0xf0, + 0x77, 0x3b, 0x87, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xfc, 0x38, 0x00, + 0x77, 0xfb, 0x8f, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xf8, 0x38, 0x00, + 0x73, 0xf3, 0x8f, 0xff, 0x0f, 0xff, 0x1c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x78, 0x7f, 0xf8, + 0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfe, 0x3c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x3c, 0x7f, 0xf8, + 0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8, +}; + +void UITask::begin(const char* node_name, const char* build_date) { + _prevBtnState = HIGH; + _auto_off = millis() + AUTO_OFF_MILLIS; + _node_name = node_name; + _build_date = build_date; + _display->turnOn(); +} + +void UITask::renderCurrScreen() { + char tmp[80]; + // render 'home' screen + _display->drawXbm(0, 0, meshcore_logo, 128, 13); + _display->setCursor(0, 20); + _display->setTextSize(1); + _display->print(_node_name); + + sprintf(tmp, "Build: %s", _build_date); + _display->setCursor(0, 32); + _display->print(tmp); + _display->setCursor(0, 43); + _display->print("< Repeater >"); + //_display->printf("freq : %03.2f sf %d\n", _prefs.freq, _prefs.sf); + //_display->printf("bw : %03.2f cr %d\n", _prefs.bw, _prefs.cr); +} + +void UITask::loop() { +#ifdef PIN_USER_BTN + if (millis() >= _next_read) { + int btnState = digitalRead(PIN_USER_BTN); + if (btnState != _prevBtnState) { + if (btnState == LOW) { // pressed? + if (_display->isOn()) { + // TODO: any action ? + } else { + _display->turnOn(); + } + _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer + } + _prevBtnState = btnState; + } + _next_read = millis() + 200; // 5 reads per second + } +#endif + + if (_display->isOn()) { + if (millis() >= _next_refresh) { + _display->startFrame(); + renderCurrScreen(); + _display->endFrame(); + + _next_refresh = millis() + 1000; // refresh every second + } + if (millis() > _auto_off) { + _display->turnOff(); + } + } +} diff --git a/examples/simple_repeater/UITask.h b/examples/simple_repeater/UITask.h new file mode 100644 index 00000000..e58d6111 --- /dev/null +++ b/examples/simple_repeater/UITask.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +class UITask { + DisplayDriver* _display; + unsigned long _next_read, _next_refresh, _auto_off; + int _prevBtnState; + const char* _node_name; + const char* _build_date; + + void renderCurrScreen(); +public: + UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; } + void begin(const char* node_name, const char* build_date); + + void loop(); +}; \ No newline at end of file diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 3eb16d26..16e86868 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -88,6 +88,15 @@ #error "need to provide a 'board' object" #endif +#ifdef DISPLAY_CLASS + #include + + static DISPLAY_CLASS display; + + #include "UITask.h" + static UITask ui_task(display); +#endif + #define PACKET_LOG_FILE "/packet_log" /* ------------------------------ Code -------------------------------- */ @@ -107,7 +116,8 @@ struct RepeaterStats { uint32_t total_up_time_secs; uint32_t n_sent_flood, n_sent_direct; uint32_t n_recv_flood, n_recv_direct; - uint16_t n_full_events, reserved1; + uint16_t n_full_events; + int16_t last_snr; // x 4 uint16_t n_direct_dups, n_flood_dups; }; @@ -175,7 +185,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { stats.n_recv_flood = getNumRecvFlood(); stats.n_recv_direct = getNumRecvDirect(); stats.n_full_events = getNumFullEvents(); - stats.reserved1 = 0; + stats.last_snr = (int16_t)(my_radio->getLastSNR() * 4); stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups(); stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups(); @@ -411,27 +421,34 @@ protected: if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) { MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported text type received: flags=%02x", (uint32_t)flags); - } else if (sender_timestamp > client->last_timestamp) { // prevent replay attacks + } else if (sender_timestamp >= client->last_timestamp) { // prevent replay attacks + bool is_retry = (sender_timestamp == client->last_timestamp); client->last_timestamp = sender_timestamp; client->last_activity = getRTCClock()->getCurrentTime(); // len can be > original length, but 'text' will be padded with zeroes data[len] = 0; // need to make a C string again, with null terminator - uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it - mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, 5 + strlen((char *)&data[5]), client->id.pub_key, PUB_KEY_SIZE); + if (flags == TXT_TYPE_PLAIN) { // for legacy CLI, send Acks + uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it + mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, 5 + strlen((char *)&data[5]), client->id.pub_key, PUB_KEY_SIZE); - mesh::Packet* ack = createAck(ack_hash); - if (ack) { - if (client->out_path_len < 0) { - sendFlood(ack); - } else { - sendDirect(ack, client->out_path, client->out_path_len); + mesh::Packet* ack = createAck(ack_hash); + if (ack) { + if (client->out_path_len < 0) { + sendFlood(ack); + } else { + sendDirect(ack, client->out_path, client->out_path_len); + } } } uint8_t temp[166]; - _cli.handleCommand(sender_timestamp, (const char *) &data[5], (char *) &temp[5]); + if (is_retry) { + temp[0] = 0; + } else { + _cli.handleCommand(sender_timestamp, (const char *) &data[5], (char *) &temp[5]); + } int text_len = strlen((char *) &temp[5]); if (text_len > 0) { uint32_t timestamp = getRTCClock()->getCurrentTimeUnique(); @@ -442,9 +459,6 @@ protected: memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique temp[4] = (TXT_TYPE_CLI_DATA << 2); // NOTE: legacy was: TXT_TYPE_PLAIN - // calc expected ACK reply - //mesh::Utils::sha256((uint8_t *)&expected_ack_crc, 4, temp, 5 + text_len, self_id.pub_key, PUB_KEY_SIZE); - auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len); if (reply) { if (client->out_path_len < 0) { @@ -509,13 +523,7 @@ public: mesh::Mesh::begin(); _fs = fs; // load persisted prefs - if (_fs->exists("/node_prefs")) { - File file = _fs->open("/node_prefs"); - if (file) { - file.read((uint8_t *) &_prefs, sizeof(_prefs)); - file.close(); - } - } + _cli.loadPrefs(_fs); _phy->setFrequency(_prefs.freq); _phy->setSpreadingFactor(_prefs.sf); @@ -528,18 +536,10 @@ public: const char* getFirmwareVer() override { return FIRMWARE_VERSION; } const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; } + const char* getNodeName() { return _prefs.node_name; } void savePrefs() override { -#if defined(NRF52_PLATFORM) - File file = _fs->open("/node_prefs", FILE_O_WRITE); - if (file) { file.seek(0); file.truncate(); } -#else - File file = _fs->open("/node_prefs", "w", true); -#endif - if (file) { - file.write((const uint8_t *)&_prefs, sizeof(_prefs)); - file.close(); - } + _cli.savePrefs(_fs); } bool formatFileSystem() override { @@ -603,12 +603,15 @@ public: updateAdvertTimer(); // schedule next local advert } + #ifdef DISPLAY_CLASS + ui_task.loop(); + #endif } }; #if defined(NRF52_PLATFORM) RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); -#elif defined(LILYGO_TLORA) || defined(HELTEC_LORA_V2) // ESP32 with SX1276 +#elif defined(LILYGO_TLORA) SPIClass spi; RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1, spi); #elif defined(P_LORA_SCLK) @@ -650,6 +653,11 @@ void setup() { #else float tcxo = 1.6f; #endif + +#ifdef DISPLAY_CLASS + display.begin(); +#endif + #if defined(NRF52_PLATFORM) SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); SPI.begin(); @@ -702,6 +710,10 @@ void setup() { the_mesh.begin(fs); +#ifdef DISPLAY_CLASS + ui_task.begin(the_mesh.getNodeName(), FIRMWARE_BUILD_DATE); +#endif + // send out initial Advertisement to the mesh the_mesh.sendSelfAdvertisement(2000); } diff --git a/examples/simple_room_server/UITask.cpp b/examples/simple_room_server/UITask.cpp new file mode 100644 index 00000000..2f1d8ae5 --- /dev/null +++ b/examples/simple_room_server/UITask.cpp @@ -0,0 +1,79 @@ +#include "UITask.h" +#include + +#define AUTO_OFF_MILLIS 20000 // 20 seconds + +// 'meshcore', 128x13px +static const uint8_t meshcore_logo [] PROGMEM = { + 0x3c, 0x01, 0xe3, 0xff, 0xc7, 0xff, 0x8f, 0x03, 0x87, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, + 0x3c, 0x03, 0xe3, 0xff, 0xc7, 0xff, 0x8e, 0x03, 0x8f, 0xfe, 0x3f, 0xfe, 0x1f, 0xff, 0x1f, 0xfe, + 0x3e, 0x03, 0xc3, 0xff, 0x8f, 0xff, 0x0e, 0x07, 0x8f, 0xfe, 0x7f, 0xfe, 0x1f, 0xff, 0x1f, 0xfc, + 0x3e, 0x07, 0xc7, 0x80, 0x0e, 0x00, 0x0e, 0x07, 0x9e, 0x00, 0x78, 0x0e, 0x3c, 0x0f, 0x1c, 0x00, + 0x3e, 0x0f, 0xc7, 0x80, 0x1e, 0x00, 0x0e, 0x07, 0x1e, 0x00, 0x70, 0x0e, 0x38, 0x0f, 0x3c, 0x00, + 0x7f, 0x0f, 0xc7, 0xfe, 0x1f, 0xfc, 0x1f, 0xff, 0x1c, 0x00, 0x70, 0x0e, 0x38, 0x0e, 0x3f, 0xf8, + 0x7f, 0x1f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x0e, 0x38, 0x0e, 0x3f, 0xf8, + 0x7f, 0x3f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x1e, 0x3f, 0xfe, 0x3f, 0xf0, + 0x77, 0x3b, 0x87, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xfc, 0x38, 0x00, + 0x77, 0xfb, 0x8f, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xf8, 0x38, 0x00, + 0x73, 0xf3, 0x8f, 0xff, 0x0f, 0xff, 0x1c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x78, 0x7f, 0xf8, + 0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfe, 0x3c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x3c, 0x7f, 0xf8, + 0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8, +}; + +void UITask::begin(const char* node_name, const char* build_date) { + _prevBtnState = HIGH; + _auto_off = millis() + AUTO_OFF_MILLIS; + _node_name = node_name; + _build_date = build_date; + _display->turnOn(); +} + +void UITask::renderCurrScreen() { + char tmp[80]; + // render 'home' screen + _display->drawXbm(0, 0, meshcore_logo, 128, 13); + _display->setCursor(0, 20); + _display->setTextSize(1); + _display->print(_node_name); + + sprintf(tmp, "Build: %s", _build_date); + _display->setCursor(0, 32); + _display->print(tmp); + _display->setCursor(0, 43); + _display->print("< Room Server >"); + //_display->printf("freq : %03.2f sf %d\n", _prefs.freq, _prefs.sf); + //_display->printf("bw : %03.2f cr %d\n", _prefs.bw, _prefs.cr); +} + +void UITask::loop() { +#ifdef PIN_USER_BTN + if (millis() >= _next_read) { + int btnState = digitalRead(PIN_USER_BTN); + if (btnState != _prevBtnState) { + if (btnState == LOW) { // pressed? + if (_display->isOn()) { + // TODO: any action ? + } else { + _display->turnOn(); + } + _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer + } + _prevBtnState = btnState; + } + _next_read = millis() + 200; // 5 reads per second + } +#endif + + if (_display->isOn()) { + if (millis() >= _next_refresh) { + _display->startFrame(); + renderCurrScreen(); + _display->endFrame(); + + _next_refresh = millis() + 1000; // refresh every second + } + if (millis() > _auto_off) { + _display->turnOff(); + } + } +} diff --git a/examples/simple_room_server/UITask.h b/examples/simple_room_server/UITask.h new file mode 100644 index 00000000..e58d6111 --- /dev/null +++ b/examples/simple_room_server/UITask.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +class UITask { + DisplayDriver* _display; + unsigned long _next_read, _next_refresh, _auto_off; + int _prevBtnState; + const char* _node_name; + const char* _build_date; + + void renderCurrScreen(); +public: + UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; } + void begin(const char* node_name, const char* build_date); + + void loop(); +}; \ No newline at end of file diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 84f1f552..a01dc999 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -76,7 +76,7 @@ #include #include static XiaoC3Board board; -#elif defined(SEEED_XIAO_S3) || defined(LILYGO_T3S3) +#elif defined(SEEED_XIAO_S3) #include #include static ESP32Board board; @@ -92,6 +92,15 @@ #error "need to provide a 'board' object" #endif +#ifdef DISPLAY_CLASS + #include + + static DISPLAY_CLASS display; + + #include "UITask.h" + static UITask ui_task(display); +#endif + /* ------------------------------ Code -------------------------------- */ struct ClientInfo { @@ -376,7 +385,8 @@ protected: if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) { MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported command flags received: flags=%02x", (uint32_t)flags); - } else if (sender_timestamp > client->last_timestamp) { // prevent replay attacks + } else if (sender_timestamp >= client->last_timestamp) { // prevent replay attacks, but send Acks for retries + bool is_retry = (sender_timestamp == client->last_timestamp); client->last_timestamp = sender_timestamp; uint32_t now = getRTCClock()->getCurrentTimeUnique(); @@ -393,19 +403,26 @@ protected: bool send_ack; if (flags == TXT_TYPE_CLI_DATA) { if (client->is_admin) { - _cli.handleCommand(sender_timestamp, (const char *) &data[5], (char *) &temp[5]); - temp[4] = (TXT_TYPE_CLI_DATA << 2); // attempt and flags, (NOTE: legacy was: TXT_TYPE_PLAIN) - send_ack = true; + if (is_retry) { + temp[5] = 0; // no reply + } else { + _cli.handleCommand(sender_timestamp, (const char *) &data[5], (char *) &temp[5]); + temp[4] = (TXT_TYPE_CLI_DATA << 2); // attempt and flags, (NOTE: legacy was: TXT_TYPE_PLAIN) + } + send_ack = false; } else { temp[5] = 0; // no reply send_ack = false; // and no ACK... user shoudn't be sending these } } else { // TXT_TYPE_PLAIN - addPost(client, (const char *) &data[5]); + if (!is_retry) { + addPost(client, (const char *) &data[5]); + } temp[5] = 0; // no reply (ACK is enough) send_ack = true; } + uint32_t delay_millis; if (send_ack) { mesh::Packet* ack = createAck(ack_hash); if (ack) { @@ -415,6 +432,9 @@ protected: sendDirect(ack, client->out_path, client->out_path_len); } } + delay_millis = REPLY_DELAY_MILLIS; + } else { + delay_millis = 0; } int text_len = strlen((char *) &temp[5]); @@ -431,9 +451,9 @@ protected: auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len); if (reply) { if (client->out_path_len < 0) { - sendFlood(reply, REPLY_DELAY_MILLIS); + sendFlood(reply, delay_millis); } else { - sendDirect(reply, client->out_path, client->out_path_len, REPLY_DELAY_MILLIS); + sendDirect(reply, client->out_path, client->out_path_len, delay_millis); } } } @@ -544,13 +564,7 @@ public: mesh::Mesh::begin(); _fs = fs; // load persisted prefs - if (_fs->exists("/node_prefs")) { - File file = _fs->open("/node_prefs"); - if (file) { - file.read((uint8_t *) &_prefs, sizeof(_prefs)); - file.close(); - } - } + _cli.loadPrefs(_fs); _phy->setFrequency(_prefs.freq); _phy->setSpreadingFactor(_prefs.sf); @@ -563,18 +577,10 @@ public: const char* getFirmwareVer() override { return FIRMWARE_VERSION; } const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; } + const char* getNodeName() { return _prefs.node_name; } void savePrefs() override { -#if defined(NRF52_PLATFORM) - File file = _fs->open("/node_prefs", FILE_O_WRITE); - if (file) { file.seek(0); file.truncate(); } -#else - File file = _fs->open("/node_prefs", "w", true); -#endif - if (file) { - file.write((const uint8_t *)&_prefs, sizeof(_prefs)); - file.close(); - } + _cli.savePrefs(_fs); } bool formatFileSystem() override { @@ -657,13 +663,17 @@ public: updateAdvertTimer(); // schedule next local advert } + #ifdef DISPLAY_CLASS + ui_task.loop(); + #endif + // TODO: periodically check for OLD/inactive entries in known_clients[], and evict } }; #if defined(NRF52_PLATFORM) RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); -#elif defined(LILYGO_TLORA) || defined(HELTEC_LORA_V2) // ESP32 with SX1276 +#elif defined(LILYGO_TLORA) SPIClass spi; RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1, spi); #elif defined(P_LORA_SCLK) @@ -706,6 +716,10 @@ void setup() { float tcxo = 1.6f; #endif +#ifdef DISPLAY_CLASS + display.begin(); +#endif + #if defined(NRF52_PLATFORM) SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); SPI.begin(); @@ -757,6 +771,10 @@ void setup() { the_mesh.begin(fs); +#ifdef DISPLAY_CLASS + ui_task.begin(the_mesh.getNodeName(), FIRMWARE_BUILD_DATE); +#endif + // send out initial Advertisement to the mesh the_mesh.sendSelfAdvertisement(2000); } diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index 3e280047..1f854773 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -545,7 +545,7 @@ public: #if defined(NRF52_PLATFORM) RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); -#elif defined(LILYGO_TLORA) || defined(HELTEC_LORA_V2) // ESP32 with SX1276 +#elif defined(LILYGO_TLORA) SPIClass spi; RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1, spi); #elif defined(P_LORA_SCLK) diff --git a/platformio.ini b/platformio.ini index d5b6bce4..099b3d93 100644 --- a/platformio.ini +++ b/platformio.ini @@ -113,28 +113,34 @@ build_flags = -D P_LORA_TX_LED=35 -D PIN_BOARD_SDA=17 -D PIN_BOARD_SCL=18 + -D PIN_USER_BTN=0 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=130.0f ; for best TX power! build_src_filter = ${esp32_base.build_src_filter} +lib_deps = + ${esp32_base.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 [env:Heltec_v3_repeater] extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} + -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME="\"Heltec Repeater\"" -D ADVERT_LAT=-37.0 -D ADVERT_LON=145.0 -D ADMIN_PASSWORD="\"password\"" -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/simple_repeater/main.cpp> +build_src_filter = ${Heltec_lora32_v3.build_src_filter} + +<../examples/simple_repeater> [env:Heltec_v3_room_server] extends = Heltec_lora32_v3 -build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/simple_room_server/main.cpp> +build_src_filter = ${Heltec_lora32_v3.build_src_filter} + +<../examples/simple_room_server> build_flags = ${Heltec_lora32_v3.build_flags} + -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME="\"Heltec Room\"" -D ADVERT_LAT=-37.0 -D ADVERT_LON=145.0 @@ -162,11 +168,12 @@ build_flags = ${Heltec_lora32_v3.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=1 + -D DISPLAY_CLASS=SSD1306Display ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/companion_radio/main.cpp> +build_src_filter = ${Heltec_lora32_v3.build_src_filter} + +<../examples/companion_radio> lib_deps = ${Heltec_lora32_v3.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -177,13 +184,14 @@ build_flags = ${Heltec_lora32_v3.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=1 + -D DISPLAY_CLASS=SSD1306Display -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v3.build_src_filter} + +<../examples/companion_radio/main.cpp> +build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + +<../examples/companion_radio> lib_deps = ${Heltec_lora32_v3.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -194,6 +202,7 @@ build_flags = ${Heltec_lora32_v3.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=1 + -D DISPLAY_CLASS=SSD1306Display -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID="\"myssid\"" -D WIFI_PWD="\"mypwd\"" @@ -201,7 +210,7 @@ build_flags = ; -D ENABLE_PRIVATE_KEY_EXPORT=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v3.build_src_filter} + +<../examples/companion_radio/main.cpp> +build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + +<../examples/companion_radio> lib_deps = ${Heltec_lora32_v3.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -619,6 +628,7 @@ board_build.ldscript = boards/nrf52840_s140_v7.ld build_flags = ${nrf52840_t1000e.build_flags} -Ivariants/t1000-e -DT1000_E + -D PIN_USER_BTN=6 -D RADIO_CLASS=CustomLR1110 -D WRAPPER_CLASS=CustomLR1110Wrapper build_src_filter = ${nrf52840_t1000e.build_src_filter} diff --git a/src/Identity.cpp b/src/Identity.cpp index aa035856..568f61b1 100644 --- a/src/Identity.cpp +++ b/src/Identity.cpp @@ -3,7 +3,7 @@ #define ED25519_NO_SEED 1 #include -// For ESP32, we use libsodium for cryptographic operations to reduce stack usage +// For weaker ESP32 boards, we use libsodium for cryptographic operations to reduce stack usage #ifdef USE_ESP32_ENCRYPTION #include #endif @@ -20,9 +20,6 @@ Identity::Identity(const char* pub_hex) { bool Identity::verify(const uint8_t* sig, const uint8_t* message, int msg_len) const { #ifdef USE_ESP32_ENCRYPTION - // Using libsodium for verification on ESP32 to reduce stack usage - // This function performs signature verification with much lower stack requirements - // than the default implementation return crypto_sign_ed25519_verify_detached(sig, message, msg_len, pub_key) == 0; #else return ed25519_verify(sig, message, msg_len, pub_key); @@ -108,7 +105,6 @@ void LocalIdentity::readFrom(const uint8_t* src, size_t len) { #ifdef USE_ESP32_ENCRYPTION // In libsodium, the private key already contains the public key in its last 32 bytes // We can just extract it directly, avoiding the expensive derivation calculation - // This significantly reduces stack usage on ESP32 memcpy(pub_key, prv_key + 32, 32); #else // now need to re-calculate the pub_key @@ -119,8 +115,6 @@ void LocalIdentity::readFrom(const uint8_t* src, size_t len) { void LocalIdentity::sign(uint8_t* sig, const uint8_t* message, int msg_len) const { #ifdef USE_ESP32_ENCRYPTION - // Use libsodium for signing on ESP32 to reduce stack usage - // The libsodium implementation uses less stack space than the default ed25519 implementation crypto_sign_ed25519_detached(sig, NULL, message, msg_len, prv_key); #else ed25519_sign(sig, message, msg_len, pub_key, prv_key); @@ -130,13 +124,11 @@ void LocalIdentity::sign(uint8_t* sig, const uint8_t* message, int msg_len) cons void LocalIdentity::calcSharedSecret(uint8_t* secret, const uint8_t* other_pub_key) { #ifdef USE_ESP32_ENCRYPTION // NOTE: To calculate a shared secret with Ed25519 keys and libsodium, we need to: - // 1. Convert the Ed25519 keys to Curve25519 (X25519) format - // 2. Perform the key exchange using the converted keys + // Convert the Ed25519 keys to Curve25519 (X25519) format + // Perform the key exchange using the converted keys // // The default implementation handles this conversion internally, - // but with libsodium we need to do these steps explicitly. - // This approach uses less stack space compared to the original implementation. - + // but with libsodium we need to do these steps explicitly. unsigned char x25519_pk[crypto_scalarmult_curve25519_BYTES]; unsigned char x25519_sk[crypto_scalarmult_curve25519_BYTES]; diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 9835bb7f..b61a0d37 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -104,7 +104,6 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender // len can be > original length, but 'text' will be padded with zeroes data[len] = 0; // need to make a C string again, with null terminator - //if ( ! alreadyReceived timestamp ) { if (flags == TXT_TYPE_PLAIN) { onMessageRecv(from, packet->isRouteFlood() ? packet->path_len : 0xFF, timestamp, (const char *) &data[5]); // let UI know @@ -131,7 +130,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender // NOTE: no ack expected for CLI_DATA replies if (packet->isRouteFlood()) { - // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK + // let this sender know path TO here, so they can use sendDirect() (NOTE: no ACK as extra) mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, 0, NULL, 0); if (path) sendFlood(path); } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index b436a1da..21cd690f 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -13,6 +13,89 @@ static uint32_t _atoi(const char* sp) { return n; } +void CommonCLI::loadPrefs(FILESYSTEM* fs) { + if (fs->exists("/node_prefs")) { + File file = fs->open("/node_prefs"); + if (file) { + uint8_t pad[8]; + + file.read((uint8_t *) &_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0 + file.read((uint8_t *) &_prefs->node_name, sizeof(_prefs->node_name)); // 4 + file.read(pad, 4); // 36 + file.read((uint8_t *) &_prefs->node_lat, sizeof(_prefs->node_lat)); // 40 + file.read((uint8_t *) &_prefs->node_lon, sizeof(_prefs->node_lon)); // 48 + file.read((uint8_t *) &_prefs->password[0], sizeof(_prefs->password)); // 56 + file.read((uint8_t *) &_prefs->freq, sizeof(_prefs->freq)); // 72 + file.read((uint8_t *) &_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76 + file.read((uint8_t *) &_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77 + file.read((uint8_t *) &_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78 + file.read((uint8_t *) &_prefs->unused, sizeof(_prefs->unused)); // 79 + file.read((uint8_t *) &_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80 + file.read((uint8_t *) &_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84 + file.read((uint8_t *) &_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88 + file.read((uint8_t *) &_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104 + file.read(pad, 4); // 108 + file.read((uint8_t *) &_prefs->sf, sizeof(_prefs->sf)); // 112 + file.read((uint8_t *) &_prefs->cr, sizeof(_prefs->cr)); // 113 + file.read((uint8_t *) &_prefs->reserved1, sizeof(_prefs->reserved1)); // 114 + file.read((uint8_t *) &_prefs->reserved2, sizeof(_prefs->reserved2)); // 115 + file.read((uint8_t *) &_prefs->bw, sizeof(_prefs->bw)); // 116 + file.read(pad, 4); // 120 + + // sanitise bad pref values + _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); + _prefs->tx_delay_factor = constrain(_prefs->tx_delay_factor, 0, 2.0f); + _prefs->direct_tx_delay_factor = constrain(_prefs->direct_tx_delay_factor, 0, 2.0f); + _prefs->airtime_factor = constrain(_prefs->airtime_factor, 0, 9.0f); + _prefs->freq = constrain(_prefs->freq, 400.0f, 2500.0f); + _prefs->bw = constrain(_prefs->bw, 62.5f, 500.0f); + _prefs->sf = constrain(_prefs->sf, 7, 12); + _prefs->cr = constrain(_prefs->cr, 5, 8); + _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30); + + file.close(); + } + } +} + +void CommonCLI::savePrefs(FILESYSTEM* fs) { +#if defined(NRF52_PLATFORM) + File file = fs->open("/node_prefs", FILE_O_WRITE); + if (file) { file.seek(0); file.truncate(); } +#else + File file = fs->open("/node_prefs", "w", true); +#endif + if (file) { + uint8_t pad[8]; + memset(pad, 0, sizeof(pad)); + + file.write((uint8_t *) &_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0 + file.write((uint8_t *) &_prefs->node_name, sizeof(_prefs->node_name)); // 4 + file.write(pad, 4); // 36 + file.write((uint8_t *) &_prefs->node_lat, sizeof(_prefs->node_lat)); // 40 + file.write((uint8_t *) &_prefs->node_lon, sizeof(_prefs->node_lon)); // 48 + file.write((uint8_t *) &_prefs->password[0], sizeof(_prefs->password)); // 56 + file.write((uint8_t *) &_prefs->freq, sizeof(_prefs->freq)); // 72 + file.write((uint8_t *) &_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76 + file.write((uint8_t *) &_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77 + file.write((uint8_t *) &_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78 + file.write((uint8_t *) &_prefs->unused, sizeof(_prefs->unused)); // 79 + file.write((uint8_t *) &_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80 + file.write((uint8_t *) &_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84 + file.write((uint8_t *) &_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88 + file.write((uint8_t *) &_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104 + file.write(pad, 4); // 108 + file.write((uint8_t *) &_prefs->sf, sizeof(_prefs->sf)); // 112 + file.write((uint8_t *) &_prefs->cr, sizeof(_prefs->cr)); // 113 + file.write((uint8_t *) &_prefs->reserved1, sizeof(_prefs->reserved1)); // 114 + file.write((uint8_t *) &_prefs->reserved2, sizeof(_prefs->reserved2)); // 115 + file.write((uint8_t *) &_prefs->bw, sizeof(_prefs->bw)); // 116 + file.write(pad, 4); // 120 + + file.close(); + } +} + #define MIN_LOCAL_ADVERT_INTERVAL 60 void CommonCLI::checkAdvertInterval() { diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index f384bd7f..ec038d07 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -1,6 +1,7 @@ #pragma once #include "Mesh.h" +#include struct NodePrefs { // persisted to file float airtime_factor; @@ -54,5 +55,7 @@ public: CommonCLI(mesh::MainBoard& board, mesh::Mesh* mesh, NodePrefs* prefs, CommonCLICallbacks* callbacks) : _board(&board), _mesh(mesh), _prefs(prefs), _callbacks(callbacks) { } + void loadPrefs(FILESYSTEM* _fs); + void savePrefs(FILESYSTEM* _fs); void handleCommand(uint32_t sender_timestamp, const char* command, char* reply); }; diff --git a/src/helpers/HeltecV3Board.h b/src/helpers/HeltecV3Board.h index 232ed414..ce742efa 100644 --- a/src/helpers/HeltecV3Board.h +++ b/src/helpers/HeltecV3Board.h @@ -17,6 +17,7 @@ #define PIN_ADC_CTRL_ACTIVE LOW #define PIN_ADC_CTRL_INACTIVE HIGH #define PIN_LED_BUILTIN 35 +#define PIN_VEXT_EN 36 #include "ESP32Board.h" @@ -28,6 +29,7 @@ public: ESP32Board::begin(); pinMode(PIN_ADC_CTRL, OUTPUT); + //pinMode(PIN_VEXT_EN, OUTPUT); esp_reset_reason_t reason = esp_reset_reason(); if (reason == ESP_RST_DEEPSLEEP) { diff --git a/src/helpers/ui/DisplayDriver.h b/src/helpers/ui/DisplayDriver.h new file mode 100644 index 00000000..1c8bebc7 --- /dev/null +++ b/src/helpers/ui/DisplayDriver.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +class DisplayDriver { + int _w, _h; +protected: + DisplayDriver(int w, int h) { _w = w; _h = h; } +public: + enum Color { DARK, LIGHT }; + + int width() const { return _w; } + int height() const { return _h; } + + virtual bool isOn() = 0; + virtual void turnOn() = 0; + virtual void turnOff() = 0; + virtual void startFrame(Color bkg = DARK) = 0; + virtual void setTextSize(int sz) = 0; + virtual void setColor(Color c) = 0; + virtual void setCursor(int x, int y) = 0; + virtual void print(const char* str) = 0; + virtual void fillRect(int x, int y, int w, int h) = 0; + virtual void drawRect(int x, int y, int w, int h) = 0; + virtual void drawXbm(int x, int y, const uint8_t* bits, int w, int h) = 0; + virtual void endFrame() = 0; +}; diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp new file mode 100644 index 00000000..84db7d13 --- /dev/null +++ b/src/helpers/ui/SSD1306Display.cpp @@ -0,0 +1,56 @@ +#include "SSD1306Display.h" + +bool SSD1306Display::begin() { + return display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_ADDRESS); +} + +void SSD1306Display::turnOn() { + display.ssd1306_command(SSD1306_DISPLAYON); + _isOn = true; +} + +void SSD1306Display::turnOff() { + display.ssd1306_command(SSD1306_DISPLAYOFF); + _isOn = false; +} + +void SSD1306Display::startFrame(Color bkg) { + display.clearDisplay(); // TODO: apply 'bkg' + _color = SSD1306_WHITE; + display.setTextColor(_color); + display.setTextSize(1); + display.cp437(true); // Use full 256 char 'Code Page 437' font +} + +void SSD1306Display::setTextSize(int sz) { + display.setTextSize(sz); +} + +void SSD1306Display::setColor(Color c) { + _color = (c == LIGHT) ? SSD1306_WHITE : SSD1306_BLACK; + display.setTextColor(_color); +} + +void SSD1306Display::setCursor(int x, int y) { + display.setCursor(x, y); +} + +void SSD1306Display::print(const char* str) { + display.print(str); +} + +void SSD1306Display::fillRect(int x, int y, int w, int h) { + display.fillRect(x, y, w, h, _color); +} + +void SSD1306Display::drawRect(int x, int y, int w, int h) { + display.drawRect(x, y, w, h, _color); +} + +void SSD1306Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { + display.drawBitmap(x, y, bits, w, h, SSD1306_WHITE); +} + +void SSD1306Display::endFrame() { + display.display(); +} diff --git a/src/helpers/ui/SSD1306Display.h b/src/helpers/ui/SSD1306Display.h new file mode 100644 index 00000000..a685eedf --- /dev/null +++ b/src/helpers/ui/SSD1306Display.h @@ -0,0 +1,37 @@ +#pragma once + +#include "DisplayDriver.h" +#include +#include +#include + +#ifndef PIN_OLED_RESET + #define PIN_OLED_RESET 21 // Reset pin # (or -1 if sharing Arduino reset pin) +#endif + +#ifndef DISPLAY_ADDRESS + #define DISPLAY_ADDRESS 0x3C +#endif + +class SSD1306Display : public DisplayDriver { + Adafruit_SSD1306 display; + bool _isOn; + uint8_t _color; + +public: + SSD1306Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; } + bool begin(); + + bool isOn() override { return _isOn; } + void turnOn() override; + void turnOff() override; + void startFrame(Color bkg = DARK) override; + void setTextSize(int sz) override; + void setColor(Color c) override; + void setCursor(int x, int y) override; + void print(const char* str) override; + void fillRect(int x, int y, int w, int h) override; + void drawRect(int x, int y, int w, int h) override; + void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override; + void endFrame() override; +};