Merge remote-tracking branch 'origin/dev' into awolden/t3lora

This commit is contained in:
Alex Wolden 2025-03-04 22:09:57 -08:00
commit 86389579eb
19 changed files with 745 additions and 84 deletions

View file

@ -0,0 +1,119 @@
#include "UITask.h"
#include <Arduino.h>
#include <helpers/TxtDataHelpers.h>
#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();
}
}
}

View file

@ -0,0 +1,25 @@
#pragma once
#include <helpers/ui/DisplayDriver.h>
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();
};

View file

@ -88,6 +88,15 @@
#error "need to provide a 'board' object"
#endif
#ifdef DISPLAY_CLASS
#include <helpers/ui/SSD1306Display.h>
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() {

View file

@ -0,0 +1,79 @@
#include "UITask.h"
#include <Arduino.h>
#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();
}
}
}

View file

@ -0,0 +1,18 @@
#pragma once
#include <helpers/ui/DisplayDriver.h>
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();
};

View file

@ -88,6 +88,15 @@
#error "need to provide a 'board' object"
#endif
#ifdef DISPLAY_CLASS
#include <helpers/ui/SSD1306Display.h>
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, &timestamp, 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);
}

View file

@ -0,0 +1,79 @@
#include "UITask.h"
#include <Arduino.h>
#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();
}
}
}

View file

@ -0,0 +1,18 @@
#pragma once
#include <helpers/ui/DisplayDriver.h>
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();
};

View file

@ -76,7 +76,7 @@
#include <helpers/CustomSX1262Wrapper.h>
#include <helpers/CustomSX1268Wrapper.h>
static XiaoC3Board board;
#elif defined(SEEED_XIAO_S3) || defined(LILYGO_T3S3)
#elif defined(SEEED_XIAO_S3)
#include <helpers/ESP32Board.h>
#include <helpers/CustomSX1262Wrapper.h>
static ESP32Board board;
@ -92,6 +92,15 @@
#error "need to provide a 'board' object"
#endif
#ifdef DISPLAY_CLASS
#include <helpers/ui/SSD1306Display.h>
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);
}

View file

@ -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)