Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
45
boards/ebyte_eora-s3.json
Normal file
45
boards/ebyte_eora-s3.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"arduino": {
|
||||||
|
"ldscript": "esp32s3_out.ld",
|
||||||
|
"partitions": "default.csv",
|
||||||
|
"memory_type": "qio_qspi"
|
||||||
|
},
|
||||||
|
"core": "esp32",
|
||||||
|
"extra_flags": [
|
||||||
|
"-DARDUINO_LILYGO_T3_S3_V1_X",
|
||||||
|
"-DBOARD_HAS_PSRAM",
|
||||||
|
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||||
|
"-DARDUINO_RUNNING_CORE=1",
|
||||||
|
"-DARDUINO_EVENT_RUNNING_CORE=1",
|
||||||
|
"-DARDUINO_USB_MODE=1"
|
||||||
|
],
|
||||||
|
"f_cpu": "240000000L",
|
||||||
|
"f_flash": "80000000L",
|
||||||
|
"flash_mode": "qio",
|
||||||
|
"mcu": "esp32s3",
|
||||||
|
"variant": "esp32s3"
|
||||||
|
},
|
||||||
|
"connectivity": [
|
||||||
|
"wifi"
|
||||||
|
],
|
||||||
|
"debug": {
|
||||||
|
"openocd_target": "esp32s3.cfg"
|
||||||
|
},
|
||||||
|
"frameworks": [
|
||||||
|
"arduino",
|
||||||
|
"espidf"
|
||||||
|
],
|
||||||
|
"name": "Ebyte EoRa-S3-XXXTB Radio",
|
||||||
|
"upload": {
|
||||||
|
"flash_size": "4MB",
|
||||||
|
"maximum_ram_size": 327680,
|
||||||
|
"maximum_size": 4194304,
|
||||||
|
"use_1200bps_touch": true,
|
||||||
|
"wait_for_upload_port": true,
|
||||||
|
"require_upload_port": true,
|
||||||
|
"speed": 460800
|
||||||
|
},
|
||||||
|
"url": "https://www.cdebyte.com/products/EoRa-S3-900TB",
|
||||||
|
"vendor": "Chengdu Ebyte Electronic Technology Co., Ltd"
|
||||||
|
}
|
||||||
33
boards/tiny_relay.json
Normal file
33
boards/tiny_relay.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"arduino": {
|
||||||
|
"variant_h": "variant_RAK3172_MODULE.h"
|
||||||
|
},
|
||||||
|
"core": "stm32",
|
||||||
|
"cpu": "cortex-m4",
|
||||||
|
"extra_flags": "-DSTM32WL -DSTM32WLxx -DSTM32WLE5xx",
|
||||||
|
"framework_extra_flags": {
|
||||||
|
"arduino": "-DUSE_CM4_STARTUP_FILE -DARDUINO_RAK3172_MODULE"
|
||||||
|
},
|
||||||
|
"f_cpu": "48000000L",
|
||||||
|
"mcu": "stm32wle5ccu",
|
||||||
|
"product_line": "STM32WLE5xx",
|
||||||
|
"variant": "STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U"
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"default_tools": ["stlink"],
|
||||||
|
"jlink_device": "STM32WLE5CC",
|
||||||
|
"openocd_target": "stm32wlx",
|
||||||
|
"svd_path": "STM32WLE5_CM4.svd"
|
||||||
|
},
|
||||||
|
"frameworks": ["arduino"],
|
||||||
|
"name": "BB-STM32WL",
|
||||||
|
"upload": {
|
||||||
|
"maximum_ram_size": 65536,
|
||||||
|
"maximum_size": 262144,
|
||||||
|
"protocol": "stlink",
|
||||||
|
"protocols": ["stlink", "jlink"]
|
||||||
|
},
|
||||||
|
"url": "https://www.st.com/en/microcontrollers-microprocessors/stm32wle5cc.html",
|
||||||
|
"vendor": "YAOYAO"
|
||||||
|
}
|
||||||
@@ -41,6 +41,6 @@ public:
|
|||||||
void disableSerial() { _serial->disable(); }
|
void disableSerial() { _serial->disable(); }
|
||||||
virtual void msgRead(int msgcount) = 0;
|
virtual void msgRead(int msgcount) = 0;
|
||||||
virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) = 0;
|
virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) = 0;
|
||||||
virtual void soundBuzzer(UIEventType bet = UIEventType::none) = 0;
|
virtual void notify(UIEventType t = UIEventType::none) = 0;
|
||||||
virtual void loop() = 0;
|
virtual void loop() = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -42,12 +42,17 @@ static File openWrite(FILESYSTEM* fs, const char* filename) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||||
|
static uint32_t _ContactsChannelsTotalBlocks = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
void DataStore::begin() {
|
void DataStore::begin() {
|
||||||
#if defined(RP2040_PLATFORM)
|
#if defined(RP2040_PLATFORM)
|
||||||
identity_store.begin();
|
identity_store.begin();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||||
|
_ContactsChannelsTotalBlocks = _getContactsChannelsFS()->_getFS()->cfg->block_count;
|
||||||
checkAdvBlobFile();
|
checkAdvBlobFile();
|
||||||
#if defined(EXTRAFS) || defined(QSPIFLASH)
|
#if defined(EXTRAFS) || defined(QSPIFLASH)
|
||||||
migrateToSecondaryFS();
|
migrateToSecondaryFS();
|
||||||
@@ -74,14 +79,22 @@ void DataStore::begin() {
|
|||||||
|
|
||||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||||
int _countLfsBlock(void *p, lfs_block_t block){
|
int _countLfsBlock(void *p, lfs_block_t block){
|
||||||
|
if (block > _ContactsChannelsTotalBlocks) {
|
||||||
|
MESH_DEBUG_PRINTLN("ERROR: Block %d exceeds filesystem bounds - CORRUPTION DETECTED!", block);
|
||||||
|
return LFS_ERR_CORRUPT; // return error to abort lfs_traverse() gracefully
|
||||||
|
}
|
||||||
lfs_size_t *size = (lfs_size_t*) p;
|
lfs_size_t *size = (lfs_size_t*) p;
|
||||||
*size += 1;
|
*size += 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
lfs_ssize_t _getLfsUsedBlockCount(FILESYSTEM* fs) {
|
lfs_ssize_t _getLfsUsedBlockCount(FILESYSTEM* fs) {
|
||||||
lfs_size_t size = 0;
|
lfs_size_t size = 0;
|
||||||
lfs_traverse(fs->_getFS(), _countLfsBlock, &size);
|
int err = lfs_traverse(fs->_getFS(), _countLfsBlock, &size);
|
||||||
|
if (err) {
|
||||||
|
MESH_DEBUG_PRINTLN("ERROR: lfs_traverse() error: %d", err);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
if (_ui) _ui->soundBuzzer(UIEventType::newContactMessage);
|
if (_ui) _ui->notify(UIEventType::newContactMessage);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,7 +294,7 @@ void MyMesh::onContactPathUpdated(const ContactInfo &contact) {
|
|||||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MyMesh::processAck(const uint8_t *data) {
|
ContactInfo* MyMesh::processAck(const uint8_t *data) {
|
||||||
// see if matches any in a table
|
// see if matches any in a table
|
||||||
for (int i = 0; i < EXPECTED_ACK_TABLE_SIZE; i++) {
|
for (int i = 0; i < EXPECTED_ACK_TABLE_SIZE; i++) {
|
||||||
if (memcmp(data, &expected_ack_table[i].ack, 4) == 0) { // got an ACK from recipient
|
if (memcmp(data, &expected_ack_table[i].ack, 4) == 0) { // got an ACK from recipient
|
||||||
@@ -306,7 +306,7 @@ bool MyMesh::processAck(const uint8_t *data) {
|
|||||||
|
|
||||||
// NOTE: the same ACK can be received multiple times!
|
// NOTE: the same ACK can be received multiple times!
|
||||||
expected_ack_table[i].ack = 0; // clear expected hash, now that we have received ACK
|
expected_ack_table[i].ack = 0; // clear expected hash, now that we have received ACK
|
||||||
return true;
|
return expected_ack_table[i].contact;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return checkConnectionsAck(data);
|
return checkConnectionsAck(data);
|
||||||
@@ -353,7 +353,7 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
|
|||||||
if (should_display && _ui) {
|
if (should_display && _ui) {
|
||||||
_ui->newMsg(path_len, from.name, text, offline_queue_len);
|
_ui->newMsg(path_len, from.name, text, offline_queue_len);
|
||||||
if (!_serial->isConnected()) {
|
if (!_serial->isConnected()) {
|
||||||
_ui->soundBuzzer(UIEventType::contactMessage);
|
_ui->notify(UIEventType::contactMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -412,7 +412,7 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe
|
|||||||
_serial->writeFrame(frame, 1);
|
_serial->writeFrame(frame, 1);
|
||||||
} else {
|
} else {
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
if (_ui) _ui->soundBuzzer(UIEventType::channelMessage);
|
if (_ui) _ui->notify(UIEventType::channelMessage);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
@@ -825,6 +825,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
|||||||
if (expected_ack) {
|
if (expected_ack) {
|
||||||
expected_ack_table[next_ack_idx].msg_sent = _ms->getMillis(); // add to circular table
|
expected_ack_table[next_ack_idx].msg_sent = _ms->getMillis(); // add to circular table
|
||||||
expected_ack_table[next_ack_idx].ack = expected_ack;
|
expected_ack_table[next_ack_idx].ack = expected_ack;
|
||||||
|
expected_ack_table[next_ack_idx].contact = recipient;
|
||||||
next_ack_idx = (next_ack_idx + 1) % EXPECTED_ACK_TABLE_SIZE;
|
next_ack_idx = (next_ack_idx + 1) % EXPECTED_ACK_TABLE_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ protected:
|
|||||||
bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
||||||
void onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) override;
|
void onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) override;
|
||||||
void onContactPathUpdated(const ContactInfo &contact) override;
|
void onContactPathUpdated(const ContactInfo &contact) override;
|
||||||
bool processAck(const uint8_t *data) override;
|
ContactInfo* processAck(const uint8_t *data) override;
|
||||||
void queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packet *pkt, uint32_t sender_timestamp,
|
void queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packet *pkt, uint32_t sender_timestamp,
|
||||||
const uint8_t *extra, int extra_len, const char *text);
|
const uint8_t *extra, int extra_len, const char *text);
|
||||||
|
|
||||||
@@ -205,6 +205,7 @@ private:
|
|||||||
struct AckTableEntry {
|
struct AckTableEntry {
|
||||||
unsigned long msg_sent;
|
unsigned long msg_sent;
|
||||||
uint32_t ack;
|
uint32_t ack;
|
||||||
|
ContactInfo* contact;
|
||||||
};
|
};
|
||||||
#define EXPECTED_ACK_TABLE_SIZE 8
|
#define EXPECTED_ACK_TABLE_SIZE 8
|
||||||
AckTableEntry expected_ack_table[EXPECTED_ACK_TABLE_SIZE]; // circular table
|
AckTableEntry expected_ack_table[EXPECTED_ACK_TABLE_SIZE]; // circular table
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ class HomeScreen : public UIScreen {
|
|||||||
bool _shutdown_init;
|
bool _shutdown_init;
|
||||||
AdvertPath recent[UI_RECENT_LIST_SIZE];
|
AdvertPath recent[UI_RECENT_LIST_SIZE];
|
||||||
|
|
||||||
|
|
||||||
void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) {
|
void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) {
|
||||||
// Convert millivolts to percentage
|
// Convert millivolts to percentage
|
||||||
const int minMilliVolts = 3000; // Minimum voltage (e.g., 3.0V)
|
const int minMilliVolts = 3000; // Minimum voltage (e.g., 3.0V)
|
||||||
@@ -157,10 +158,12 @@ public:
|
|||||||
int render(DisplayDriver& display) override {
|
int render(DisplayDriver& display) override {
|
||||||
char tmp[80];
|
char tmp[80];
|
||||||
// node name
|
// node name
|
||||||
display.setCursor(0, 0);
|
|
||||||
display.setTextSize(1);
|
display.setTextSize(1);
|
||||||
display.setColor(DisplayDriver::GREEN);
|
display.setColor(DisplayDriver::GREEN);
|
||||||
display.print(_node_prefs->node_name);
|
char filtered_name[sizeof(_node_prefs->node_name)];
|
||||||
|
display.translateUTF8ToBlocks(filtered_name, _node_prefs->node_name, sizeof(filtered_name));
|
||||||
|
display.setCursor(0, 0);
|
||||||
|
display.print(filtered_name);
|
||||||
|
|
||||||
// battery voltage
|
// battery voltage
|
||||||
renderBatteryIndicator(display, _task->getBattMilliVolts());
|
renderBatteryIndicator(display, _task->getBattMilliVolts());
|
||||||
@@ -199,8 +202,6 @@ public:
|
|||||||
for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += 11) {
|
for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += 11) {
|
||||||
auto a = &recent[i];
|
auto a = &recent[i];
|
||||||
if (a->name[0] == 0) continue; // empty slot
|
if (a->name[0] == 0) continue; // empty slot
|
||||||
display.setCursor(0, y);
|
|
||||||
display.print(a->name);
|
|
||||||
int secs = _rtc->getCurrentTime() - a->recv_timestamp;
|
int secs = _rtc->getCurrentTime() - a->recv_timestamp;
|
||||||
if (secs < 60) {
|
if (secs < 60) {
|
||||||
sprintf(tmp, "%ds", secs);
|
sprintf(tmp, "%ds", secs);
|
||||||
@@ -209,7 +210,14 @@ public:
|
|||||||
} else {
|
} else {
|
||||||
sprintf(tmp, "%dh", secs / (60*60));
|
sprintf(tmp, "%dh", secs / (60*60));
|
||||||
}
|
}
|
||||||
display.setCursor(display.width() - display.getTextWidth(tmp) - 1, y);
|
|
||||||
|
int timestamp_width = display.getTextWidth(tmp);
|
||||||
|
int max_name_width = display.width() - timestamp_width - 1;
|
||||||
|
|
||||||
|
char filtered_recent_name[sizeof(a->name)];
|
||||||
|
display.translateUTF8ToBlocks(filtered_recent_name, a->name, sizeof(filtered_recent_name));
|
||||||
|
display.drawTextEllipsized(0, y, max_name_width, filtered_recent_name);
|
||||||
|
display.setCursor(display.width() - timestamp_width - 1, y);
|
||||||
display.print(tmp);
|
display.print(tmp);
|
||||||
}
|
}
|
||||||
} else if (_page == HomePage::RADIO) {
|
} else if (_page == HomePage::RADIO) {
|
||||||
@@ -348,9 +356,7 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (c == KEY_ENTER && _page == HomePage::ADVERT) {
|
if (c == KEY_ENTER && _page == HomePage::ADVERT) {
|
||||||
#ifdef PIN_BUZZER
|
_task->notify(UIEventType::ack);
|
||||||
_task->soundBuzzer(UIEventType::ack);
|
|
||||||
#endif
|
|
||||||
if (the_mesh.advert()) {
|
if (the_mesh.advert()) {
|
||||||
_task->showAlert("Advert sent!", 1000);
|
_task->showAlert("Advert sent!", 1000);
|
||||||
} else {
|
} else {
|
||||||
@@ -427,11 +433,15 @@ public:
|
|||||||
|
|
||||||
display.setCursor(0, 14);
|
display.setCursor(0, 14);
|
||||||
display.setColor(DisplayDriver::YELLOW);
|
display.setColor(DisplayDriver::YELLOW);
|
||||||
display.print(p->origin);
|
char filtered_origin[sizeof(p->origin)];
|
||||||
|
display.translateUTF8ToBlocks(filtered_origin, p->origin, sizeof(filtered_origin));
|
||||||
|
display.print(filtered_origin);
|
||||||
|
|
||||||
display.setCursor(0, 25);
|
display.setCursor(0, 25);
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
display.printWordWrap(p->msg, display.width());
|
char filtered_msg[sizeof(p->msg)];
|
||||||
|
display.translateUTF8ToBlocks(filtered_msg, p->msg, sizeof(filtered_msg));
|
||||||
|
display.printWordWrap(filtered_msg, display.width());
|
||||||
|
|
||||||
#if AUTO_OFF_MILLIS==0 // probably e-ink
|
#if AUTO_OFF_MILLIS==0 // probably e-ink
|
||||||
return 10000; // 10 s
|
return 10000; // 10 s
|
||||||
@@ -483,6 +493,10 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
|||||||
buzzer.begin();
|
buzzer.begin();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef PIN_VIBRATION
|
||||||
|
vibration.begin();
|
||||||
|
#endif
|
||||||
|
|
||||||
ui_started_at = millis();
|
ui_started_at = millis();
|
||||||
_alert_expiry = 0;
|
_alert_expiry = 0;
|
||||||
|
|
||||||
@@ -497,9 +511,9 @@ void UITask::showAlert(const char* text, int duration_millis) {
|
|||||||
_alert_expiry = millis() + duration_millis;
|
_alert_expiry = millis() + duration_millis;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UITask::soundBuzzer(UIEventType bet) {
|
void UITask::notify(UIEventType t) {
|
||||||
#if defined(PIN_BUZZER)
|
#if defined(PIN_BUZZER)
|
||||||
switch(bet){
|
switch(t){
|
||||||
case UIEventType::contactMessage:
|
case UIEventType::contactMessage:
|
||||||
// gemini's pick
|
// gemini's pick
|
||||||
buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7");
|
buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7");
|
||||||
@@ -517,8 +531,16 @@ switch(bet){
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef PIN_VIBRATION
|
||||||
|
// Trigger vibration for all UI events except none
|
||||||
|
if (t != UIEventType::none) {
|
||||||
|
vibration.trigger();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void UITask::msgRead(int msgcount) {
|
void UITask::msgRead(int msgcount) {
|
||||||
_msgcount = msgcount;
|
_msgcount = msgcount;
|
||||||
if (msgcount == 0) {
|
if (msgcount == 0) {
|
||||||
@@ -687,6 +709,10 @@ void UITask::loop() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef PIN_VIBRATION
|
||||||
|
vibration.loop();
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef AUTO_SHUTDOWN_MILLIVOLTS
|
#ifdef AUTO_SHUTDOWN_MILLIVOLTS
|
||||||
if (millis() > next_batt_chck) {
|
if (millis() > next_batt_chck) {
|
||||||
uint16_t milliVolts = getBattMilliVolts();
|
uint16_t milliVolts = getBattMilliVolts();
|
||||||
@@ -755,11 +781,11 @@ void UITask::toggleGPS() {
|
|||||||
if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
|
if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
|
||||||
if (strcmp(_sensors->getSettingValue(i), "1") == 0) {
|
if (strcmp(_sensors->getSettingValue(i), "1") == 0) {
|
||||||
_sensors->setSettingValue("gps", "0");
|
_sensors->setSettingValue("gps", "0");
|
||||||
soundBuzzer(UIEventType::ack);
|
notify(UIEventType::ack);
|
||||||
showAlert("GPS: Disabled", 800);
|
showAlert("GPS: Disabled", 800);
|
||||||
} else {
|
} else {
|
||||||
_sensors->setSettingValue("gps", "1");
|
_sensors->setSettingValue("gps", "1");
|
||||||
soundBuzzer(UIEventType::ack);
|
notify(UIEventType::ack);
|
||||||
showAlert("GPS: Enabled", 800);
|
showAlert("GPS: Enabled", 800);
|
||||||
}
|
}
|
||||||
_next_refresh = 0;
|
_next_refresh = 0;
|
||||||
@@ -774,7 +800,7 @@ void UITask::toggleBuzzer() {
|
|||||||
#ifdef PIN_BUZZER
|
#ifdef PIN_BUZZER
|
||||||
if (buzzer.isQuiet()) {
|
if (buzzer.isQuiet()) {
|
||||||
buzzer.quiet(false);
|
buzzer.quiet(false);
|
||||||
soundBuzzer(UIEventType::ack);
|
notify(UIEventType::ack);
|
||||||
showAlert("Buzzer: ON", 800);
|
showAlert("Buzzer: ON", 800);
|
||||||
} else {
|
} else {
|
||||||
buzzer.quiet(true);
|
buzzer.quiet(true);
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
#ifdef PIN_BUZZER
|
#ifdef PIN_BUZZER
|
||||||
#include <helpers/ui/buzzer.h>
|
#include <helpers/ui/buzzer.h>
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef PIN_VIBRATION
|
||||||
|
#include <helpers/ui/GenericVibration.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "../AbstractUITask.h"
|
#include "../AbstractUITask.h"
|
||||||
#include "../NodePrefs.h"
|
#include "../NodePrefs.h"
|
||||||
@@ -20,6 +23,9 @@ class UITask : public AbstractUITask {
|
|||||||
SensorManager* _sensors;
|
SensorManager* _sensors;
|
||||||
#ifdef PIN_BUZZER
|
#ifdef PIN_BUZZER
|
||||||
genericBuzzer buzzer;
|
genericBuzzer buzzer;
|
||||||
|
#endif
|
||||||
|
#ifdef PIN_VIBRATION
|
||||||
|
GenericVibration vibration;
|
||||||
#endif
|
#endif
|
||||||
unsigned long _next_refresh, _auto_off;
|
unsigned long _next_refresh, _auto_off;
|
||||||
NodePrefs* _node_prefs;
|
NodePrefs* _node_prefs;
|
||||||
@@ -71,7 +77,7 @@ public:
|
|||||||
// from AbstractUITask
|
// from AbstractUITask
|
||||||
void msgRead(int msgcount) override;
|
void msgRead(int msgcount) override;
|
||||||
void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override;
|
void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override;
|
||||||
void soundBuzzer(UIEventType bet = UIEventType::none) override;
|
void notify(UIEventType t = UIEventType::none) override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|
||||||
void shutdown(bool restart = false);
|
void shutdown(bool restart = false);
|
||||||
|
|||||||
@@ -88,9 +88,9 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
|||||||
ui_started_at = millis();
|
ui_started_at = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UITask::soundBuzzer(UIEventType bet) {
|
void UITask::notify(UIEventType t) {
|
||||||
#if defined(PIN_BUZZER)
|
#if defined(PIN_BUZZER)
|
||||||
switch(bet){
|
switch(t){
|
||||||
case UIEventType::contactMessage:
|
case UIEventType::contactMessage:
|
||||||
// gemini's pick
|
// gemini's pick
|
||||||
buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7");
|
buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7");
|
||||||
@@ -108,8 +108,8 @@ switch(bet){
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// Serial.print("DBG: Buzzzzzz -> ");
|
// Serial.print("DBG: Alert user -> ");
|
||||||
// Serial.println((int) bet);
|
// Serial.println((int) t);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UITask::msgRead(int msgcount) {
|
void UITask::msgRead(int msgcount) {
|
||||||
@@ -370,7 +370,7 @@ void UITask::handleButtonDoublePress() {
|
|||||||
MESH_DEBUG_PRINTLN("UITask: double press triggered, sending advert");
|
MESH_DEBUG_PRINTLN("UITask: double press triggered, sending advert");
|
||||||
// ADVERT
|
// ADVERT
|
||||||
#ifdef PIN_BUZZER
|
#ifdef PIN_BUZZER
|
||||||
soundBuzzer(UIEventType::ack);
|
notify(UIEventType::ack);
|
||||||
#endif
|
#endif
|
||||||
if (the_mesh.advert()) {
|
if (the_mesh.advert()) {
|
||||||
MESH_DEBUG_PRINTLN("Advert sent!");
|
MESH_DEBUG_PRINTLN("Advert sent!");
|
||||||
@@ -388,7 +388,7 @@ void UITask::handleButtonTriplePress() {
|
|||||||
#ifdef PIN_BUZZER
|
#ifdef PIN_BUZZER
|
||||||
if (buzzer.isQuiet()) {
|
if (buzzer.isQuiet()) {
|
||||||
buzzer.quiet(false);
|
buzzer.quiet(false);
|
||||||
soundBuzzer(UIEventType::ack);
|
notify(UIEventType::ack);
|
||||||
sprintf(_alert, "Buzzer: ON");
|
sprintf(_alert, "Buzzer: ON");
|
||||||
} else {
|
} else {
|
||||||
buzzer.quiet(true);
|
buzzer.quiet(true);
|
||||||
@@ -407,11 +407,11 @@ void UITask::handleButtonQuadruplePress() {
|
|||||||
if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
|
if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
|
||||||
if (strcmp(_sensors->getSettingValue(i), "1") == 0) {
|
if (strcmp(_sensors->getSettingValue(i), "1") == 0) {
|
||||||
_sensors->setSettingValue("gps", "0");
|
_sensors->setSettingValue("gps", "0");
|
||||||
soundBuzzer(UIEventType::ack);
|
notify(UIEventType::ack);
|
||||||
sprintf(_alert, "GPS: Disabled");
|
sprintf(_alert, "GPS: Disabled");
|
||||||
} else {
|
} else {
|
||||||
_sensors->setSettingValue("gps", "1");
|
_sensors->setSettingValue("gps", "1");
|
||||||
soundBuzzer(UIEventType::ack);
|
notify(UIEventType::ack);
|
||||||
sprintf(_alert, "GPS: Enabled");
|
sprintf(_alert, "GPS: Enabled");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public:
|
|||||||
// from AbstractUITask
|
// from AbstractUITask
|
||||||
void msgRead(int msgcount) override;
|
void msgRead(int msgcount) override;
|
||||||
void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override;
|
void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override;
|
||||||
void soundBuzzer(UIEventType bet = UIEventType::none) override;
|
void notify(UIEventType t = UIEventType::none) override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|
||||||
void shutdown(bool restart = false);
|
void shutdown(bool restart = false);
|
||||||
|
|||||||
@@ -43,28 +43,13 @@
|
|||||||
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
|
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
|
||||||
#define REQ_TYPE_KEEP_ALIVE 0x02
|
#define REQ_TYPE_KEEP_ALIVE 0x02
|
||||||
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
|
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
|
||||||
|
#define REQ_TYPE_GET_ACCESS_LIST 0x05
|
||||||
|
|
||||||
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
|
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
|
||||||
|
|
||||||
#define CLI_REPLY_DELAY_MILLIS 600
|
#define CLI_REPLY_DELAY_MILLIS 600
|
||||||
|
|
||||||
|
#define LAZY_CONTACTS_WRITE_DELAY 5000
|
||||||
ClientInfo *MyMesh::putClient(const mesh::Identity &id) {
|
|
||||||
uint32_t min_time = 0xFFFFFFFF;
|
|
||||||
ClientInfo *oldest = &known_clients[0];
|
|
||||||
for (int i = 0; i < MAX_CLIENTS; i++) {
|
|
||||||
if (known_clients[i].last_activity < min_time) {
|
|
||||||
oldest = &known_clients[i];
|
|
||||||
min_time = oldest->last_activity;
|
|
||||||
}
|
|
||||||
if (id.matches(known_clients[i].id)) return &known_clients[i]; // already known
|
|
||||||
}
|
|
||||||
|
|
||||||
oldest->id = id;
|
|
||||||
oldest->out_path_len = -1; // initially out_path is unknown
|
|
||||||
oldest->last_timestamp = 0;
|
|
||||||
return oldest;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float snr) {
|
void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float snr) {
|
||||||
#if MAX_NEIGHBOURS // check if neighbours enabled
|
#if MAX_NEIGHBOURS // check if neighbours enabled
|
||||||
@@ -93,15 +78,61 @@ void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float sn
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload,
|
uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) {
|
||||||
size_t payload_len) {
|
ClientInfo* client;
|
||||||
|
if (data[0] == 0) { // blank password, just check if sender is in ACL
|
||||||
|
client = acl.getClient(sender.pub_key, PUB_KEY_SIZE);
|
||||||
|
if (client == NULL) {
|
||||||
|
#if MESH_DEBUG
|
||||||
|
MESH_DEBUG_PRINTLN("Login, sender not in ACL");
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uint8_t perms;
|
||||||
|
if (strcmp((char *)data, _prefs.password) == 0) { // check for valid admin password
|
||||||
|
perms = PERM_ACL_ADMIN;
|
||||||
|
} else if (strcmp((char *)data, _prefs.guest_password) == 0) { // check guest password
|
||||||
|
perms = PERM_ACL_GUEST;
|
||||||
|
} else {
|
||||||
|
#if MESH_DEBUG
|
||||||
|
MESH_DEBUG_PRINTLN("Invalid password: %s", data);
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
client = acl.putClient(sender, 0); // add to contacts (if not already known)
|
||||||
|
if (sender_timestamp <= client->last_timestamp) {
|
||||||
|
MESH_DEBUG_PRINTLN("Possible login replay attack!");
|
||||||
|
return 0; // FATAL: client table is full -OR- replay attack
|
||||||
|
}
|
||||||
|
|
||||||
|
MESH_DEBUG_PRINTLN("Login success!");
|
||||||
|
client->last_timestamp = sender_timestamp;
|
||||||
|
client->last_activity = getRTCClock()->getCurrentTime();
|
||||||
|
client->permissions |= perms;
|
||||||
|
memcpy(client->shared_secret, secret, PUB_KEY_SIZE);
|
||||||
|
|
||||||
|
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||||
|
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
||||||
|
reply_data[4] = RESP_SERVER_LOGIN_OK;
|
||||||
|
reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16)
|
||||||
|
reply_data[6] = client->isAdmin() ? 1 : 0;
|
||||||
|
reply_data[7] = client->permissions;
|
||||||
|
getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness
|
||||||
|
|
||||||
|
return 12; // reply length
|
||||||
|
}
|
||||||
|
|
||||||
|
int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload, size_t payload_len) {
|
||||||
// uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
// uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||||
// memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
// memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
||||||
memcpy(reply_data, &sender_timestamp,
|
memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag')
|
||||||
4); // reflect sender_timestamp back in response packet (kind of like a 'tag')
|
|
||||||
|
|
||||||
switch (payload[0]) {
|
if (payload[0] == REQ_TYPE_GET_STATUS) { // guests can also access this now
|
||||||
case REQ_TYPE_GET_STATUS: { // guests can also access this now
|
|
||||||
RepeaterStats stats;
|
RepeaterStats stats;
|
||||||
stats.batt_milli_volts = board.getBattMilliVolts();
|
stats.batt_milli_volts = board.getBattMilliVolts();
|
||||||
stats.curr_tx_queue_len = _mgr->getOutboundCount(0xFFFFFFFF);
|
stats.curr_tx_queue_len = _mgr->getOutboundCount(0xFFFFFFFF);
|
||||||
@@ -125,18 +156,31 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
|||||||
|
|
||||||
return 4 + sizeof(stats); // reply_len
|
return 4 + sizeof(stats); // reply_len
|
||||||
}
|
}
|
||||||
case REQ_TYPE_GET_TELEMETRY_DATA: {
|
if (payload[0] == REQ_TYPE_GET_TELEMETRY_DATA) {
|
||||||
uint8_t perm_mask = ~(payload[1]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions
|
uint8_t perm_mask = ~(payload[1]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions
|
||||||
|
|
||||||
telemetry.reset();
|
telemetry.reset();
|
||||||
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
||||||
// query other sensors -- target specific
|
// query other sensors -- target specific
|
||||||
sensors.querySensors((sender->is_admin ? 0xFF : 0x00) & perm_mask, telemetry);
|
sensors.querySensors((sender->isAdmin() ? 0xFF : 0x00) & perm_mask, telemetry);
|
||||||
|
|
||||||
uint8_t tlen = telemetry.getSize();
|
uint8_t tlen = telemetry.getSize();
|
||||||
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
|
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
|
||||||
return 4 + tlen; // reply_len
|
return 4 + tlen; // reply_len
|
||||||
}
|
}
|
||||||
|
if (payload[0] == REQ_TYPE_GET_ACCESS_LIST && sender->isAdmin()) {
|
||||||
|
uint8_t res1 = payload[1]; // reserved for future (extra query params)
|
||||||
|
uint8_t res2 = payload[2];
|
||||||
|
if (res1 == 0 && res2 == 0) {
|
||||||
|
uint8_t ofs = 4;
|
||||||
|
for (int i = 0; i < acl.getNumClients() && ofs + 7 <= sizeof(reply_data) - 4; i++) {
|
||||||
|
auto c = acl.getClientByIdx(i);
|
||||||
|
if (c->permissions == 0) continue; // skip deleted entries
|
||||||
|
memcpy(&reply_data[ofs], c->id.pub_key, 6); ofs += 6; // just 6-byte pub_key prefix
|
||||||
|
reply_data[ofs++] = c->permissions;
|
||||||
|
}
|
||||||
|
return ofs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0; // unknown command
|
return 0; // unknown command
|
||||||
}
|
}
|
||||||
@@ -261,65 +305,26 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
|||||||
uint32_t timestamp;
|
uint32_t timestamp;
|
||||||
memcpy(×tamp, data, 4);
|
memcpy(×tamp, data, 4);
|
||||||
|
|
||||||
bool is_admin;
|
uint8_t reply_len = handleLoginReq(sender, secret, timestamp, &data[4]);
|
||||||
data[len] = 0; // ensure null terminator
|
|
||||||
if (strcmp((char *)&data[4], _prefs.password) == 0) { // check for valid password
|
|
||||||
is_admin = true;
|
|
||||||
} else if (strcmp((char *)&data[4], _prefs.guest_password) == 0) { // check guest password
|
|
||||||
is_admin = false;
|
|
||||||
} else {
|
|
||||||
#if MESH_DEBUG
|
|
||||||
MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]);
|
|
||||||
#endif
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto client = putClient(sender); // add to known clients (if not already known)
|
if (reply_len == 0) return; // invalid request
|
||||||
if (timestamp <= client->last_timestamp) {
|
|
||||||
MESH_DEBUG_PRINTLN("Possible login replay attack!");
|
|
||||||
return; // FATAL: client table is full -OR- replay attack
|
|
||||||
}
|
|
||||||
|
|
||||||
MESH_DEBUG_PRINTLN("Login success!");
|
|
||||||
client->last_timestamp = timestamp;
|
|
||||||
client->last_activity = getRTCClock()->getCurrentTime();
|
|
||||||
client->is_admin = is_admin;
|
|
||||||
memcpy(client->secret, secret, PUB_KEY_SIZE);
|
|
||||||
|
|
||||||
uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
|
||||||
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
|
||||||
#if 0
|
|
||||||
memcpy(&reply_data[4], "OK", 2); // legacy response
|
|
||||||
#else
|
|
||||||
reply_data[4] = RESP_SERVER_LOGIN_OK;
|
|
||||||
reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16)
|
|
||||||
reply_data[6] = is_admin ? 1 : 0;
|
|
||||||
reply_data[7] = 0; // FUTURE: reserved
|
|
||||||
getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (packet->isRouteFlood()) {
|
if (packet->isRouteFlood()) {
|
||||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
||||||
mesh::Packet *path = createPathReturn(sender, client->secret, packet->path, packet->path_len,
|
mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len,
|
||||||
PAYLOAD_TYPE_RESPONSE, reply_data, 12);
|
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
|
||||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
|
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
|
||||||
} else {
|
} else {
|
||||||
mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->secret, reply_data, 12);
|
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len);
|
||||||
if (reply) {
|
if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY);
|
||||||
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
|
|
||||||
sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY);
|
|
||||||
} else {
|
|
||||||
sendFlood(reply, SERVER_RESPONSE_DELAY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int MyMesh::searchPeersByHash(const uint8_t *hash) {
|
int MyMesh::searchPeersByHash(const uint8_t *hash) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
for (int i = 0; i < MAX_CLIENTS; i++) {
|
for (int i = 0; i < acl.getNumClients(); i++) {
|
||||||
if (known_clients[i].id.isHashMatch(hash)) {
|
if (acl.getClientByIdx(i)->id.isHashMatch(hash)) {
|
||||||
matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods)
|
matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,9 +333,9 @@ int MyMesh::searchPeersByHash(const uint8_t *hash) {
|
|||||||
|
|
||||||
void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) {
|
void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) {
|
||||||
int i = matching_peer_indexes[peer_idx];
|
int i = matching_peer_indexes[peer_idx];
|
||||||
if (i >= 0 && i < MAX_CLIENTS) {
|
if (i >= 0 && i < acl.getNumClients()) {
|
||||||
// lookup pre-calculated shared_secret
|
// lookup pre-calculated shared_secret
|
||||||
memcpy(dest_secret, known_clients[i].secret, PUB_KEY_SIZE);
|
memcpy(dest_secret, acl.getClientByIdx(i)->shared_secret, PUB_KEY_SIZE);
|
||||||
} else {
|
} else {
|
||||||
MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i);
|
MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i);
|
||||||
}
|
}
|
||||||
@@ -352,12 +357,12 @@ void MyMesh::onAdvertRecv(mesh::Packet *packet, const mesh::Identity &id, uint32
|
|||||||
void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, const uint8_t *secret,
|
void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, const uint8_t *secret,
|
||||||
uint8_t *data, size_t len) {
|
uint8_t *data, size_t len) {
|
||||||
int i = matching_peer_indexes[sender_idx];
|
int i = matching_peer_indexes[sender_idx];
|
||||||
if (i < 0 ||
|
if (i < 0 || i >= acl.getNumClients()) { // get from our known_clients table (sender SHOULD already be known in this context)
|
||||||
i >= MAX_CLIENTS) { // get from our known_clients table (sender SHOULD already be known in this context)
|
|
||||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: invalid peer idx: %d", i);
|
MESH_DEBUG_PRINTLN("onPeerDataRecv: invalid peer idx: %d", i);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto client = &known_clients[i];
|
ClientInfo* client = acl.getClientByIdx(i);
|
||||||
|
|
||||||
if (type == PAYLOAD_TYPE_REQ) { // request (from a Known admin client!)
|
if (type == PAYLOAD_TYPE_REQ) { // request (from a Known admin client!)
|
||||||
uint32_t timestamp;
|
uint32_t timestamp;
|
||||||
memcpy(×tamp, data, 4);
|
memcpy(×tamp, data, 4);
|
||||||
@@ -388,7 +393,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
|||||||
} else {
|
} else {
|
||||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected");
|
MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected");
|
||||||
}
|
}
|
||||||
} else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && client->is_admin) { // a CLI command
|
} else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && client->isAdmin()) { // a CLI command
|
||||||
uint32_t sender_timestamp;
|
uint32_t sender_timestamp;
|
||||||
memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong)
|
memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong)
|
||||||
uint flags = (data[4] >> 2); // message attempt number, and other flags
|
uint flags = (data[4] >> 2); // message attempt number, and other flags
|
||||||
@@ -457,11 +462,12 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t
|
|||||||
// TODO: prevent replay attacks
|
// TODO: prevent replay attacks
|
||||||
int i = matching_peer_indexes[sender_idx];
|
int i = matching_peer_indexes[sender_idx];
|
||||||
|
|
||||||
if (i >= 0 &&
|
if (i >= 0 && i < acl.getNumClients()) { // get from our known_clients table (sender SHOULD already be known in this context)
|
||||||
i < MAX_CLIENTS) { // get from our known_clients table (sender SHOULD already be known in this context)
|
|
||||||
MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t)path_len);
|
MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t)path_len);
|
||||||
auto client = &known_clients[i];
|
auto client = acl.getClientByIdx(i);
|
||||||
|
|
||||||
memcpy(client->out_path, path, client->out_path_len = path_len); // store a copy of path, for sendDirect()
|
memcpy(client->out_path, path, client->out_path_len = path_len); // store a copy of path, for sendDirect()
|
||||||
|
client->last_activity = getRTCClock()->getCurrentTime();
|
||||||
} else {
|
} else {
|
||||||
MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i);
|
MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i);
|
||||||
}
|
}
|
||||||
@@ -480,8 +486,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
|||||||
, bridge(_mgr, &rtc)
|
, bridge(_mgr, &rtc)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
memset(known_clients, 0, sizeof(known_clients));
|
|
||||||
next_local_advert = next_flood_advert = 0;
|
next_local_advert = next_flood_advert = 0;
|
||||||
|
dirty_contacts_expiry = 0;
|
||||||
set_radio_at = revert_radio_at = 0;
|
set_radio_at = revert_radio_at = 0;
|
||||||
_logging = false;
|
_logging = false;
|
||||||
|
|
||||||
@@ -515,6 +521,8 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
|||||||
// load persisted prefs
|
// load persisted prefs
|
||||||
_cli.loadPrefs(_fs);
|
_cli.loadPrefs(_fs);
|
||||||
|
|
||||||
|
acl.load(_fs);
|
||||||
|
|
||||||
#ifdef WITH_BRIDGE
|
#ifdef WITH_BRIDGE
|
||||||
bridge.begin();
|
bridge.begin();
|
||||||
#endif
|
#endif
|
||||||
@@ -664,7 +672,43 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
|||||||
command += 3;
|
command += 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
|
// handle ACL related commands
|
||||||
|
if (memcmp(command, "setperm ", 8) == 0) { // format: setperm {pubkey-hex} {permissions-int8}
|
||||||
|
char* hex = &command[8];
|
||||||
|
char* sp = strchr(hex, ' '); // look for separator char
|
||||||
|
if (sp == NULL) {
|
||||||
|
strcpy(reply, "Err - bad params");
|
||||||
|
} else {
|
||||||
|
*sp++ = 0; // replace space with null terminator
|
||||||
|
|
||||||
|
uint8_t pubkey[PUB_KEY_SIZE];
|
||||||
|
int hex_len = min(sp - hex, PUB_KEY_SIZE*2);
|
||||||
|
if (mesh::Utils::fromHex(pubkey, hex_len / 2, hex)) {
|
||||||
|
uint8_t perms = atoi(sp);
|
||||||
|
if (acl.applyPermissions(self_id, pubkey, hex_len / 2, perms)) {
|
||||||
|
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // trigger acl.save()
|
||||||
|
strcpy(reply, "OK");
|
||||||
|
} else {
|
||||||
|
strcpy(reply, "Err - invalid params");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strcpy(reply, "Err - bad pubkey");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (sender_timestamp == 0 && strcmp(command, "get acl") == 0) {
|
||||||
|
Serial.println("ACL:");
|
||||||
|
for (int i = 0; i < acl.getNumClients(); i++) {
|
||||||
|
auto c = acl.getClientByIdx(i);
|
||||||
|
if (c->permissions == 0) continue; // skip deleted (or guest) entries
|
||||||
|
|
||||||
|
Serial.printf("%02X ", c->permissions);
|
||||||
|
mesh::Utils::printHex(Serial, c->id.pub_key, PUB_KEY_SIZE);
|
||||||
|
Serial.printf("\n");
|
||||||
|
}
|
||||||
|
reply[0] = 0;
|
||||||
|
} else{
|
||||||
|
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyMesh::loop() {
|
void MyMesh::loop() {
|
||||||
@@ -698,4 +742,10 @@ void MyMesh::loop() {
|
|||||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||||
MESH_DEBUG_PRINTLN("Radio params restored");
|
MESH_DEBUG_PRINTLN("Radio params restored");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// is pending dirty contacts write needed?
|
||||||
|
if (dirty_contacts_expiry && millisHasNowPassed(dirty_contacts_expiry)) {
|
||||||
|
acl.save(_fs);
|
||||||
|
dirty_contacts_expiry = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include <helpers/IdentityStore.h>
|
#include <helpers/IdentityStore.h>
|
||||||
#include <helpers/AdvertDataHelpers.h>
|
#include <helpers/AdvertDataHelpers.h>
|
||||||
#include <helpers/TxtDataHelpers.h>
|
#include <helpers/TxtDataHelpers.h>
|
||||||
|
#include <helpers/ClientACL.h>
|
||||||
#include <RTClib.h>
|
#include <RTClib.h>
|
||||||
#include <target.h>
|
#include <target.h>
|
||||||
|
|
||||||
@@ -52,15 +53,6 @@ struct RepeaterStats {
|
|||||||
uint32_t total_rx_air_time_secs;
|
uint32_t total_rx_air_time_secs;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ClientInfo {
|
|
||||||
mesh::Identity id;
|
|
||||||
uint32_t last_timestamp, last_activity;
|
|
||||||
uint8_t secret[PUB_KEY_SIZE];
|
|
||||||
bool is_admin;
|
|
||||||
int8_t out_path_len;
|
|
||||||
uint8_t out_path[MAX_PATH_SIZE];
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifndef MAX_CLIENTS
|
#ifndef MAX_CLIENTS
|
||||||
#define MAX_CLIENTS 32
|
#define MAX_CLIENTS 32
|
||||||
#endif
|
#endif
|
||||||
@@ -91,7 +83,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
|||||||
NodePrefs _prefs;
|
NodePrefs _prefs;
|
||||||
CommonCLI _cli;
|
CommonCLI _cli;
|
||||||
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
||||||
ClientInfo known_clients[MAX_CLIENTS];
|
ClientACL acl;
|
||||||
|
unsigned long dirty_contacts_expiry;
|
||||||
#if MAX_NEIGHBOURS
|
#if MAX_NEIGHBOURS
|
||||||
NeighbourInfo neighbours[MAX_NEIGHBOURS];
|
NeighbourInfo neighbours[MAX_NEIGHBOURS];
|
||||||
#endif
|
#endif
|
||||||
@@ -108,8 +101,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
|||||||
ESPNowBridge bridge;
|
ESPNowBridge bridge;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ClientInfo* putClient(const mesh::Identity& id);
|
|
||||||
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr);
|
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr);
|
||||||
|
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data);
|
||||||
int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len);
|
int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len);
|
||||||
mesh::Packet* createSelfAdvert();
|
mesh::Packet* createSelfAdvert();
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,12 @@
|
|||||||
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
|
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
|
||||||
#define REQ_TYPE_KEEP_ALIVE 0x02
|
#define REQ_TYPE_KEEP_ALIVE 0x02
|
||||||
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
|
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
|
||||||
|
#define REQ_TYPE_GET_ACCESS_LIST 0x05
|
||||||
|
|
||||||
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
|
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
|
||||||
|
|
||||||
|
#define LAZY_CONTACTS_WRITE_DELAY 5000
|
||||||
|
|
||||||
struct ServerStats {
|
struct ServerStats {
|
||||||
uint16_t batt_milli_volts;
|
uint16_t batt_milli_volts;
|
||||||
uint16_t curr_tx_queue_len;
|
uint16_t curr_tx_queue_len;
|
||||||
@@ -35,38 +38,6 @@ struct ServerStats {
|
|||||||
uint16_t n_posted, n_post_push;
|
uint16_t n_posted, n_post_push;
|
||||||
};
|
};
|
||||||
|
|
||||||
ClientInfo *MyMesh::putClient(const mesh::Identity &id) {
|
|
||||||
for (int i = 0; i < num_clients; i++) {
|
|
||||||
if (id.matches(known_clients[i].id)) return &known_clients[i]; // already known
|
|
||||||
}
|
|
||||||
ClientInfo *newClient;
|
|
||||||
if (num_clients < MAX_CLIENTS) {
|
|
||||||
newClient = &known_clients[num_clients++];
|
|
||||||
} else { // table is currently full
|
|
||||||
// evict least active client
|
|
||||||
uint32_t oldest_timestamp = 0xFFFFFFFF;
|
|
||||||
newClient = &known_clients[0];
|
|
||||||
for (int i = 0; i < num_clients; i++) {
|
|
||||||
auto c = &known_clients[i];
|
|
||||||
if (c->last_activity < oldest_timestamp) {
|
|
||||||
oldest_timestamp = c->last_activity;
|
|
||||||
newClient = c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newClient->id = id;
|
|
||||||
newClient->out_path_len = -1; // initially out_path is unknown
|
|
||||||
newClient->last_timestamp = 0;
|
|
||||||
return newClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MyMesh::evict(ClientInfo *client) {
|
|
||||||
client->last_activity = 0; // this slot will now be re-used (will be oldest)
|
|
||||||
memset(client->id.pub_key, 0, sizeof(client->id.pub_key));
|
|
||||||
memset(client->secret, 0, sizeof(client->secret));
|
|
||||||
client->pending_ack = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MyMesh::addPost(ClientInfo *client, const char *postData) {
|
void MyMesh::addPost(ClientInfo *client, const char *postData) {
|
||||||
// TODO: suggested postData format: <title>/<descrption>
|
// TODO: suggested postData format: <title>/<descrption>
|
||||||
posts[next_post_idx].author = client->id; // add to cyclic queue
|
posts[next_post_idx].author = client->id; // add to cyclic queue
|
||||||
@@ -97,22 +68,22 @@ void MyMesh::pushPostToClient(ClientInfo *client, PostInfo &post) {
|
|||||||
len += text_len;
|
len += text_len;
|
||||||
|
|
||||||
// calc expected ACK reply
|
// calc expected ACK reply
|
||||||
mesh::Utils::sha256((uint8_t *)&client->pending_ack, 4, reply_data, len, client->id.pub_key, PUB_KEY_SIZE);
|
mesh::Utils::sha256((uint8_t *)&client->extra.room.pending_ack, 4, reply_data, len, client->id.pub_key, PUB_KEY_SIZE);
|
||||||
client->push_post_timestamp = post.post_timestamp;
|
client->extra.room.push_post_timestamp = post.post_timestamp;
|
||||||
|
|
||||||
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->secret, reply_data, len);
|
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->shared_secret, reply_data, len);
|
||||||
if (reply) {
|
if (reply) {
|
||||||
if (client->out_path_len < 0) {
|
if (client->out_path_len < 0) {
|
||||||
sendFlood(reply);
|
sendFlood(reply);
|
||||||
client->ack_timeout = futureMillis(PUSH_ACK_TIMEOUT_FLOOD);
|
client->extra.room.ack_timeout = futureMillis(PUSH_ACK_TIMEOUT_FLOOD);
|
||||||
} else {
|
} else {
|
||||||
sendDirect(reply, client->out_path, client->out_path_len);
|
sendDirect(reply, client->out_path, client->out_path_len);
|
||||||
client->ack_timeout =
|
client->extra.room.ack_timeout =
|
||||||
futureMillis(PUSH_TIMEOUT_BASE + PUSH_ACK_TIMEOUT_FACTOR * (client->out_path_len + 1));
|
futureMillis(PUSH_TIMEOUT_BASE + PUSH_ACK_TIMEOUT_FACTOR * (client->out_path_len + 1));
|
||||||
}
|
}
|
||||||
_num_post_pushes++; // stats
|
_num_post_pushes++; // stats
|
||||||
} else {
|
} else {
|
||||||
client->pending_ack = 0;
|
client->extra.room.pending_ack = 0;
|
||||||
MESH_DEBUG_PRINTLN("Unable to push post to client");
|
MESH_DEBUG_PRINTLN("Unable to push post to client");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,7 +91,7 @@ void MyMesh::pushPostToClient(ClientInfo *client, PostInfo &post) {
|
|||||||
uint8_t MyMesh::getUnsyncedCount(ClientInfo *client) {
|
uint8_t MyMesh::getUnsyncedCount(ClientInfo *client) {
|
||||||
uint8_t count = 0;
|
uint8_t count = 0;
|
||||||
for (int k = 0; k < MAX_UNSYNCED_POSTS; k++) {
|
for (int k = 0; k < MAX_UNSYNCED_POSTS; k++) {
|
||||||
if (posts[k].post_timestamp > client->sync_since // is new post for this Client?
|
if (posts[k].post_timestamp > client->extra.room.sync_since // is new post for this Client?
|
||||||
&& !posts[k].author.matches(client->id)) { // don't push posts to the author
|
&& !posts[k].author.matches(client->id)) { // don't push posts to the author
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
@@ -129,12 +100,12 @@ uint8_t MyMesh::getUnsyncedCount(ClientInfo *client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool MyMesh::processAck(const uint8_t *data) {
|
bool MyMesh::processAck(const uint8_t *data) {
|
||||||
for (int i = 0; i < num_clients; i++) {
|
for (int i = 0; i < acl.getNumClients(); i++) {
|
||||||
auto client = &known_clients[i];
|
auto client = acl.getClientByIdx(i);
|
||||||
if (client->pending_ack && memcmp(data, &client->pending_ack, 4) == 0) { // got an ACK from Client!
|
if (client->extra.room.pending_ack && memcmp(data, &client->extra.room.pending_ack, 4) == 0) { // got an ACK from Client!
|
||||||
client->pending_ack = 0; // clear this, so next push can happen
|
client->extra.room.pending_ack = 0; // clear this, so next push can happen
|
||||||
client->push_failures = 0;
|
client->extra.room.push_failures = 0;
|
||||||
client->sync_since = client->push_post_timestamp; // advance Client's SINCE timestamp, to sync next post
|
client->extra.room.sync_since = client->extra.room.push_post_timestamp; // advance Client's SINCE timestamp, to sync next post
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,8 +139,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
|||||||
// memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
// memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
||||||
memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag')
|
memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag')
|
||||||
|
|
||||||
switch (payload[0]) {
|
if (payload[0] == REQ_TYPE_GET_STATUS) {
|
||||||
case REQ_TYPE_GET_STATUS: {
|
|
||||||
ServerStats stats;
|
ServerStats stats;
|
||||||
stats.batt_milli_volts = board.getBattMilliVolts();
|
stats.batt_milli_volts = board.getBattMilliVolts();
|
||||||
stats.curr_tx_queue_len = _mgr->getOutboundCount(0xFFFFFFFF);
|
stats.curr_tx_queue_len = _mgr->getOutboundCount(0xFFFFFFFF);
|
||||||
@@ -193,19 +163,31 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
|||||||
memcpy(&reply_data[4], &stats, sizeof(stats));
|
memcpy(&reply_data[4], &stats, sizeof(stats));
|
||||||
return 4 + sizeof(stats);
|
return 4 + sizeof(stats);
|
||||||
}
|
}
|
||||||
|
if (payload[0] == REQ_TYPE_GET_TELEMETRY_DATA) {
|
||||||
case REQ_TYPE_GET_TELEMETRY_DATA: {
|
|
||||||
uint8_t perm_mask = ~(payload[1]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions
|
uint8_t perm_mask = ~(payload[1]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions
|
||||||
|
|
||||||
telemetry.reset();
|
telemetry.reset();
|
||||||
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
||||||
// query other sensors -- target specific
|
// query other sensors -- target specific
|
||||||
sensors.querySensors((sender->permission == RoomPermission::ADMIN ? 0xFF : 0x00) & perm_mask, telemetry);
|
sensors.querySensors((sender->isAdmin() ? 0xFF : 0x00) & perm_mask, telemetry);
|
||||||
|
|
||||||
uint8_t tlen = telemetry.getSize();
|
uint8_t tlen = telemetry.getSize();
|
||||||
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
|
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
|
||||||
return 4 + tlen; // reply_len
|
return 4 + tlen; // reply_len
|
||||||
}
|
}
|
||||||
|
if (payload[0] == REQ_TYPE_GET_ACCESS_LIST && sender->isAdmin()) {
|
||||||
|
uint8_t res1 = payload[1]; // reserved for future (extra query params)
|
||||||
|
uint8_t res2 = payload[2];
|
||||||
|
if (res1 == 0 && res2 == 0) {
|
||||||
|
uint8_t ofs = 4;
|
||||||
|
for (int i = 0; i < acl.getNumClients() && ofs + 7 <= sizeof(reply_data) - 4; i++) {
|
||||||
|
auto c = acl.getClientByIdx(i);
|
||||||
|
if (!c->isAdmin()) continue; // skip non-Admin entries
|
||||||
|
memcpy(&reply_data[ofs], c->id.pub_key, 6); ofs += 6; // just 6-byte pub_key prefix
|
||||||
|
reply_data[ofs++] = c->permissions;
|
||||||
|
}
|
||||||
|
return ofs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0; // unknown command
|
return 0; // unknown command
|
||||||
}
|
}
|
||||||
@@ -305,56 +287,71 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
|||||||
memcpy(&sender_timestamp, data, 4);
|
memcpy(&sender_timestamp, data, 4);
|
||||||
memcpy(&sender_sync_since, &data[4], 4); // sender's "sync messags SINCE x" timestamp
|
memcpy(&sender_sync_since, &data[4], 4); // sender's "sync messags SINCE x" timestamp
|
||||||
|
|
||||||
RoomPermission perm;
|
|
||||||
data[len] = 0; // ensure null terminator
|
data[len] = 0; // ensure null terminator
|
||||||
if (strcmp((char *)&data[8], _prefs.password) == 0) { // check for valid admin password
|
|
||||||
perm = RoomPermission::ADMIN;
|
ClientInfo* client;
|
||||||
} else {
|
if (data[8] == 0 && !_prefs.allow_read_only) { // blank password, just check if sender is in ACL
|
||||||
if (strcmp((char *)&data[8], _prefs.guest_password) == 0) { // check the room/public password
|
client = acl.getClient(sender.pub_key, PUB_KEY_SIZE);
|
||||||
perm = RoomPermission::GUEST;
|
if (client == NULL) {
|
||||||
} else if (_prefs.allow_read_only) {
|
#if MESH_DEBUG
|
||||||
perm = RoomPermission::READ_ONLY;
|
MESH_DEBUG_PRINTLN("Login, sender not in ACL");
|
||||||
} else {
|
#endif
|
||||||
MESH_DEBUG_PRINTLN("Incorrect room password");
|
return;
|
||||||
return; // no response. Client will timeout
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
uint8_t perm;
|
||||||
|
if (strcmp((char *)&data[8], _prefs.password) == 0) { // check for valid admin password
|
||||||
|
perm = PERM_ACL_ADMIN;
|
||||||
|
} else {
|
||||||
|
if (strcmp((char *)&data[8], _prefs.guest_password) == 0) { // check the room/public password
|
||||||
|
perm = PERM_ACL_READ_WRITE;
|
||||||
|
} else if (_prefs.allow_read_only) {
|
||||||
|
perm = PERM_ACL_GUEST;
|
||||||
|
} else {
|
||||||
|
MESH_DEBUG_PRINTLN("Incorrect room password");
|
||||||
|
return; // no response. Client will timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client = acl.putClient(sender, 0); // add to known clients (if not already known)
|
||||||
|
if (sender_timestamp <= client->last_timestamp) {
|
||||||
|
MESH_DEBUG_PRINTLN("possible replay attack!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MESH_DEBUG_PRINTLN("Login success!");
|
||||||
|
client->last_timestamp = sender_timestamp;
|
||||||
|
client->extra.room.sync_since = sender_sync_since;
|
||||||
|
client->extra.room.pending_ack = 0;
|
||||||
|
client->extra.room.push_failures = 0;
|
||||||
|
|
||||||
|
client->last_activity = getRTCClock()->getCurrentTime();
|
||||||
|
client->permissions |= perm;
|
||||||
|
memcpy(client->shared_secret, secret, PUB_KEY_SIZE);
|
||||||
|
|
||||||
|
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto client = putClient(sender); // add to known clients (if not already known)
|
uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||||
if (sender_timestamp <= client->last_timestamp) {
|
|
||||||
MESH_DEBUG_PRINTLN("possible replay attack!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MESH_DEBUG_PRINTLN("Login success!");
|
|
||||||
client->permission = perm;
|
|
||||||
client->last_timestamp = sender_timestamp;
|
|
||||||
client->sync_since = sender_sync_since;
|
|
||||||
client->pending_ack = 0;
|
|
||||||
client->push_failures = 0;
|
|
||||||
memcpy(client->secret, secret, PUB_KEY_SIZE);
|
|
||||||
|
|
||||||
uint32_t now = getRTCClock()->getCurrentTime();
|
|
||||||
client->last_activity = now;
|
|
||||||
|
|
||||||
now = getRTCClock()->getCurrentTimeUnique();
|
|
||||||
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
||||||
// TODO: maybe reply with count of messages waiting to be synced for THIS client?
|
// TODO: maybe reply with count of messages waiting to be synced for THIS client?
|
||||||
reply_data[4] = RESP_SERVER_LOGIN_OK;
|
reply_data[4] = RESP_SERVER_LOGIN_OK;
|
||||||
reply_data[5] = (CLIENT_KEEP_ALIVE_SECS >> 4); // NEW: recommended keep-alive interval (secs / 16)
|
reply_data[5] = (CLIENT_KEEP_ALIVE_SECS >> 4); // NEW: recommended keep-alive interval (secs / 16)
|
||||||
reply_data[6] = (perm == RoomPermission::ADMIN ? 1 : (perm == RoomPermission::GUEST ? 0 : 2));
|
reply_data[6] = (client->isAdmin() ? 1 : (client->permissions == 0 ? 2 : 0));
|
||||||
reply_data[7] = getUnsyncedCount(client); // NEW
|
// LEGACY: reply_data[7] = getUnsyncedCount(client);
|
||||||
memcpy(&reply_data[8], "OK", 2); // REVISIT: not really needed
|
reply_data[7] = client->permissions; // NEW
|
||||||
|
getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness
|
||||||
|
// LEGACY: memcpy(&reply_data[8], "OK", 2);
|
||||||
|
|
||||||
next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS); // delay next push, give RESPONSE packet time to arrive first
|
next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS); // delay next push, give RESPONSE packet time to arrive first
|
||||||
|
|
||||||
if (packet->isRouteFlood()) {
|
if (packet->isRouteFlood()) {
|
||||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
||||||
mesh::Packet *path = createPathReturn(sender, client->secret, packet->path, packet->path_len,
|
mesh::Packet *path = createPathReturn(sender, client->shared_secret, packet->path, packet->path_len,
|
||||||
PAYLOAD_TYPE_RESPONSE, reply_data, 8 + 2);
|
PAYLOAD_TYPE_RESPONSE, reply_data, 12);
|
||||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
|
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
|
||||||
} else {
|
} else {
|
||||||
mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->secret, reply_data, 8 + 2);
|
mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->shared_secret, reply_data, 12);
|
||||||
if (reply) {
|
if (reply) {
|
||||||
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
|
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
|
||||||
sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY);
|
sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY);
|
||||||
@@ -368,8 +365,8 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
|||||||
|
|
||||||
int MyMesh::searchPeersByHash(const uint8_t *hash) {
|
int MyMesh::searchPeersByHash(const uint8_t *hash) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
for (int i = 0; i < num_clients; i++) {
|
for (int i = 0; i < acl.getNumClients(); i++) {
|
||||||
if (known_clients[i].id.isHashMatch(hash)) {
|
if (acl.getClientByIdx(i)->id.isHashMatch(hash)) {
|
||||||
matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods)
|
matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -378,9 +375,9 @@ int MyMesh::searchPeersByHash(const uint8_t *hash) {
|
|||||||
|
|
||||||
void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) {
|
void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) {
|
||||||
int i = matching_peer_indexes[peer_idx];
|
int i = matching_peer_indexes[peer_idx];
|
||||||
if (i >= 0 && i < num_clients) {
|
if (i >= 0 && i < acl.getNumClients()) {
|
||||||
// lookup pre-calculated shared_secret
|
// lookup pre-calculated shared_secret
|
||||||
memcpy(dest_secret, known_clients[i].secret, PUB_KEY_SIZE);
|
memcpy(dest_secret, acl.getClientByIdx(i)->shared_secret, PUB_KEY_SIZE);
|
||||||
} else {
|
} else {
|
||||||
MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i);
|
MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i);
|
||||||
}
|
}
|
||||||
@@ -389,11 +386,11 @@ void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) {
|
|||||||
void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, const uint8_t *secret,
|
void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, const uint8_t *secret,
|
||||||
uint8_t *data, size_t len) {
|
uint8_t *data, size_t len) {
|
||||||
int i = matching_peer_indexes[sender_idx];
|
int i = matching_peer_indexes[sender_idx];
|
||||||
if (i < 0 || i >= num_clients) { // get from our known_clients table (sender SHOULD already be known in this context)
|
if (i < 0 || i >= acl.getNumClients()) { // get from our known_clients table (sender SHOULD already be known in this context)
|
||||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: invalid peer idx: %d", i);
|
MESH_DEBUG_PRINTLN("onPeerDataRecv: invalid peer idx: %d", i);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto client = &known_clients[i];
|
auto client = acl.getClientByIdx(i);
|
||||||
if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { // a CLI command or new Post
|
if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { // a CLI command or new Post
|
||||||
uint32_t sender_timestamp;
|
uint32_t sender_timestamp;
|
||||||
memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong)
|
memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong)
|
||||||
@@ -407,7 +404,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
|||||||
|
|
||||||
uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||||
client->last_activity = now;
|
client->last_activity = now;
|
||||||
client->push_failures = 0; // reset so push can resume (if prev failed)
|
client->extra.room.push_failures = 0; // reset so push can resume (if prev failed)
|
||||||
|
|
||||||
// len can be > original length, but 'text' will be padded with zeroes
|
// 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
|
data[len] = 0; // need to make a C string again, with null terminator
|
||||||
@@ -420,7 +417,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
|||||||
uint8_t temp[166];
|
uint8_t temp[166];
|
||||||
bool send_ack;
|
bool send_ack;
|
||||||
if (flags == TXT_TYPE_CLI_DATA) {
|
if (flags == TXT_TYPE_CLI_DATA) {
|
||||||
if (client->permission == RoomPermission::ADMIN) {
|
if (client->isAdmin()) {
|
||||||
if (is_retry) {
|
if (is_retry) {
|
||||||
temp[5] = 0; // no reply
|
temp[5] = 0; // no reply
|
||||||
} else {
|
} else {
|
||||||
@@ -433,7 +430,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
|||||||
send_ack = false; // and no ACK... user shoudn't be sending these
|
send_ack = false; // and no ACK... user shoudn't be sending these
|
||||||
}
|
}
|
||||||
} else { // TXT_TYPE_PLAIN
|
} else { // TXT_TYPE_PLAIN
|
||||||
if (client->permission == RoomPermission::READ_ONLY) {
|
if ((client->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) {
|
||||||
temp[5] = 0; // no reply
|
temp[5] = 0; // no reply
|
||||||
send_ack = false; // no ACK
|
send_ack = false; // no ACK
|
||||||
} else {
|
} else {
|
||||||
@@ -501,7 +498,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
|||||||
|
|
||||||
uint32_t now = getRTCClock()->getCurrentTime();
|
uint32_t now = getRTCClock()->getCurrentTime();
|
||||||
client->last_activity = now; // <-- THIS will keep client connection alive
|
client->last_activity = now; // <-- THIS will keep client connection alive
|
||||||
client->push_failures = 0; // reset so push can resume (if prev failed)
|
client->extra.room.push_failures = 0; // reset so push can resume (if prev failed)
|
||||||
|
|
||||||
if (data[4] == REQ_TYPE_KEEP_ALIVE && packet->isRouteDirect()) { // request type
|
if (data[4] == REQ_TYPE_KEEP_ALIVE && packet->isRouteDirect()) { // request type
|
||||||
uint32_t forceSince = 0;
|
uint32_t forceSince = 0;
|
||||||
@@ -511,10 +508,10 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
|||||||
memcpy(&data[5], &forceSince, 4); // make sure there are zeroes in payload (for ack_hash calc below)
|
memcpy(&data[5], &forceSince, 4); // make sure there are zeroes in payload (for ack_hash calc below)
|
||||||
}
|
}
|
||||||
if (forceSince > 0) {
|
if (forceSince > 0) {
|
||||||
client->sync_since = forceSince; // force-update the 'sync since'
|
client->extra.room.sync_since = forceSince; // force-update the 'sync since'
|
||||||
}
|
}
|
||||||
|
|
||||||
client->pending_ack = 0;
|
client->extra.room.pending_ack = 0;
|
||||||
|
|
||||||
// TODO: Throttle KEEP_ALIVE requests!
|
// TODO: Throttle KEEP_ALIVE requests!
|
||||||
// if client sends too quickly, evict()
|
// if client sends too quickly, evict()
|
||||||
@@ -559,10 +556,11 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t
|
|||||||
// TODO: prevent replay attacks
|
// TODO: prevent replay attacks
|
||||||
int i = matching_peer_indexes[sender_idx];
|
int i = matching_peer_indexes[sender_idx];
|
||||||
|
|
||||||
if (i >= 0 && i < num_clients) { // get from our known_clients table (sender SHOULD already be known in this context)
|
if (i >= 0 && i < acl.getNumClients()) { // get from our known_clients table (sender SHOULD already be known in this context)
|
||||||
MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t)path_len);
|
MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t)path_len);
|
||||||
auto client = &known_clients[i];
|
auto client = acl.getClientByIdx(i);
|
||||||
memcpy(client->out_path, path, client->out_path_len = path_len); // store a copy of path, for sendDirect()
|
memcpy(client->out_path, path, client->out_path_len = path_len); // store a copy of path, for sendDirect()
|
||||||
|
client->last_activity = getRTCClock()->getCurrentTime();
|
||||||
} else {
|
} else {
|
||||||
MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i);
|
MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i);
|
||||||
}
|
}
|
||||||
@@ -587,6 +585,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
|||||||
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
||||||
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) {
|
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) {
|
||||||
next_local_advert = next_flood_advert = 0;
|
next_local_advert = next_flood_advert = 0;
|
||||||
|
dirty_contacts_expiry = 0;
|
||||||
_logging = false;
|
_logging = false;
|
||||||
set_radio_at = revert_radio_at = 0;
|
set_radio_at = revert_radio_at = 0;
|
||||||
|
|
||||||
@@ -613,7 +612,6 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
|||||||
StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password));
|
StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
num_clients = 0;
|
|
||||||
next_post_idx = 0;
|
next_post_idx = 0;
|
||||||
next_client_idx = 0;
|
next_client_idx = 0;
|
||||||
next_push = 0;
|
next_push = 0;
|
||||||
@@ -627,6 +625,8 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
|||||||
// load persisted prefs
|
// load persisted prefs
|
||||||
_cli.loadPrefs(_fs);
|
_cli.loadPrefs(_fs);
|
||||||
|
|
||||||
|
acl.load(_fs);
|
||||||
|
|
||||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||||
radio_set_tx_power(_prefs.tx_power_dbm);
|
radio_set_tx_power(_prefs.tx_power_dbm);
|
||||||
|
|
||||||
@@ -731,33 +731,73 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
|||||||
command += 3;
|
command += 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
|
// handle ACL related commands
|
||||||
|
if (memcmp(command, "setperm ", 8) == 0) { // format: setperm {pubkey-hex} {permissions-int8}
|
||||||
|
char* hex = &command[8];
|
||||||
|
char* sp = strchr(hex, ' '); // look for separator char
|
||||||
|
if (sp == NULL) {
|
||||||
|
strcpy(reply, "Err - bad params");
|
||||||
|
} else {
|
||||||
|
*sp++ = 0; // replace space with null terminator
|
||||||
|
|
||||||
|
uint8_t pubkey[PUB_KEY_SIZE];
|
||||||
|
int hex_len = min(sp - hex, PUB_KEY_SIZE*2);
|
||||||
|
if (mesh::Utils::fromHex(pubkey, hex_len / 2, hex)) {
|
||||||
|
uint8_t perms = atoi(sp);
|
||||||
|
if (acl.applyPermissions(self_id, pubkey, hex_len / 2, perms)) {
|
||||||
|
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // trigger acl.save()
|
||||||
|
strcpy(reply, "OK");
|
||||||
|
} else {
|
||||||
|
strcpy(reply, "Err - invalid params");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strcpy(reply, "Err - bad pubkey");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (sender_timestamp == 0 && strcmp(command, "get acl") == 0) {
|
||||||
|
Serial.println("ACL:");
|
||||||
|
for (int i = 0; i < acl.getNumClients(); i++) {
|
||||||
|
auto c = acl.getClientByIdx(i);
|
||||||
|
if (c->permissions == 0) continue; // skip deleted (or guest) entries
|
||||||
|
|
||||||
|
Serial.printf("%02X ", c->permissions);
|
||||||
|
mesh::Utils::printHex(Serial, c->id.pub_key, PUB_KEY_SIZE);
|
||||||
|
Serial.printf("\n");
|
||||||
|
}
|
||||||
|
reply[0] = 0;
|
||||||
|
} else{
|
||||||
|
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MyMesh::saveFilter(ClientInfo* client) {
|
||||||
|
return client->isAdmin(); // only save Admins
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyMesh::loop() {
|
void MyMesh::loop() {
|
||||||
mesh::Mesh::loop();
|
mesh::Mesh::loop();
|
||||||
|
|
||||||
if (millisHasNowPassed(next_push) && num_clients > 0) {
|
if (millisHasNowPassed(next_push) && acl.getNumClients() > 0) {
|
||||||
// check for ACK timeouts
|
// check for ACK timeouts
|
||||||
for (int i = 0; i < num_clients; i++) {
|
for (int i = 0; i < acl.getNumClients(); i++) {
|
||||||
auto c = &known_clients[i];
|
auto c = acl.getClientByIdx(i);
|
||||||
if (c->pending_ack && millisHasNowPassed(c->ack_timeout)) {
|
if (c->extra.room.pending_ack && millisHasNowPassed(c->extra.room.ack_timeout)) {
|
||||||
c->push_failures++;
|
c->extra.room.push_failures++;
|
||||||
c->pending_ack = 0; // reset (TODO: keep prev expected_ack's in a list, incase they arrive LATER, after we retry)
|
c->extra.room.pending_ack = 0; // reset (TODO: keep prev expected_ack's in a list, incase they arrive LATER, after we retry)
|
||||||
MESH_DEBUG_PRINTLN("pending ACK timed out: push_failures: %d", (uint32_t)c->push_failures);
|
MESH_DEBUG_PRINTLN("pending ACK timed out: push_failures: %d", (uint32_t)c->push_failures);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check next Round-Robin client, and sync next new post
|
// check next Round-Robin client, and sync next new post
|
||||||
auto client = &known_clients[next_client_idx];
|
auto client = acl.getClientByIdx(next_client_idx);
|
||||||
bool did_push = false;
|
bool did_push = false;
|
||||||
if (client->pending_ack == 0 && client->last_activity != 0 &&
|
if (client->extra.room.pending_ack == 0 && client->last_activity != 0 &&
|
||||||
client->push_failures < 3) { // not already waiting for ACK, AND not evicted, AND retries not max
|
client->extra.room.push_failures < 3) { // not already waiting for ACK, AND not evicted, AND retries not max
|
||||||
MESH_DEBUG_PRINTLN("loop - checking for client %02X", (uint32_t)client->id.pub_key[0]);
|
MESH_DEBUG_PRINTLN("loop - checking for client %02X", (uint32_t)client->id.pub_key[0]);
|
||||||
uint32_t now = getRTCClock()->getCurrentTime();
|
uint32_t now = getRTCClock()->getCurrentTime();
|
||||||
for (int k = 0, idx = next_post_idx; k < MAX_UNSYNCED_POSTS; k++) {
|
for (int k = 0, idx = next_post_idx; k < MAX_UNSYNCED_POSTS; k++) {
|
||||||
auto p = &posts[idx];
|
auto p = &posts[idx];
|
||||||
if (now >= p->post_timestamp + POST_SYNC_DELAY_SECS &&
|
if (now >= p->post_timestamp + POST_SYNC_DELAY_SECS &&
|
||||||
p->post_timestamp > client->sync_since // is new post for this Client?
|
p->post_timestamp > client->extra.room.sync_since // is new post for this Client?
|
||||||
&& !p->author.matches(client->id)) { // don't push posts to the author
|
&& !p->author.matches(client->id)) { // don't push posts to the author
|
||||||
// push this post to Client, then wait for ACK
|
// push this post to Client, then wait for ACK
|
||||||
pushPostToClient(client, *p);
|
pushPostToClient(client, *p);
|
||||||
@@ -770,7 +810,7 @@ void MyMesh::loop() {
|
|||||||
} else {
|
} else {
|
||||||
MESH_DEBUG_PRINTLN("loop - skipping busy (or evicted) client %02X", (uint32_t)client->id.pub_key[0]);
|
MESH_DEBUG_PRINTLN("loop - skipping busy (or evicted) client %02X", (uint32_t)client->id.pub_key[0]);
|
||||||
}
|
}
|
||||||
next_client_idx = (next_client_idx + 1) % num_clients; // round robin polling for each client
|
next_client_idx = (next_client_idx + 1) % acl.getNumClients(); // round robin polling for each client
|
||||||
|
|
||||||
if (did_push) {
|
if (did_push) {
|
||||||
next_push = futureMillis(SYNC_PUSH_INTERVAL);
|
next_push = futureMillis(SYNC_PUSH_INTERVAL);
|
||||||
@@ -805,5 +845,11 @@ void MyMesh::loop() {
|
|||||||
MESH_DEBUG_PRINTLN("Radio params restored");
|
MESH_DEBUG_PRINTLN("Radio params restored");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// is pending dirty contacts write needed?
|
||||||
|
if (dirty_contacts_expiry && millisHasNowPassed(dirty_contacts_expiry)) {
|
||||||
|
acl.save(_fs, MyMesh::saveFilter);
|
||||||
|
dirty_contacts_expiry = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: periodically check for OLD/inactive entries in known_clients[], and evict
|
// TODO: periodically check for OLD/inactive entries in known_clients[], and evict
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include <helpers/AdvertDataHelpers.h>
|
#include <helpers/AdvertDataHelpers.h>
|
||||||
#include <helpers/TxtDataHelpers.h>
|
#include <helpers/TxtDataHelpers.h>
|
||||||
#include <helpers/CommonCLI.h>
|
#include <helpers/CommonCLI.h>
|
||||||
|
#include <helpers/ClientACL.h>
|
||||||
#include <RTClib.h>
|
#include <RTClib.h>
|
||||||
#include <target.h>
|
#include <target.h>
|
||||||
|
|
||||||
@@ -61,10 +62,6 @@
|
|||||||
#define ADMIN_PASSWORD "password"
|
#define ADMIN_PASSWORD "password"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef MAX_CLIENTS
|
|
||||||
#define MAX_CLIENTS 32
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef MAX_UNSYNCED_POSTS
|
#ifndef MAX_UNSYNCED_POSTS
|
||||||
#define MAX_UNSYNCED_POSTS 32
|
#define MAX_UNSYNCED_POSTS 32
|
||||||
#endif
|
#endif
|
||||||
@@ -81,27 +78,6 @@
|
|||||||
|
|
||||||
#define PACKET_LOG_FILE "/packet_log"
|
#define PACKET_LOG_FILE "/packet_log"
|
||||||
|
|
||||||
enum RoomPermission {
|
|
||||||
ADMIN,
|
|
||||||
GUEST,
|
|
||||||
READ_ONLY
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ClientInfo {
|
|
||||||
mesh::Identity id;
|
|
||||||
uint32_t last_timestamp; // by THEIR clock
|
|
||||||
uint32_t last_activity; // by OUR clock
|
|
||||||
uint32_t sync_since; // sync messages SINCE this timestamp (by OUR clock)
|
|
||||||
uint32_t pending_ack;
|
|
||||||
uint32_t push_post_timestamp;
|
|
||||||
unsigned long ack_timeout;
|
|
||||||
RoomPermission permission;
|
|
||||||
uint8_t push_failures;
|
|
||||||
uint8_t secret[PUB_KEY_SIZE];
|
|
||||||
int out_path_len;
|
|
||||||
uint8_t out_path[MAX_PATH_SIZE];
|
|
||||||
};
|
|
||||||
|
|
||||||
#define MAX_POST_TEXT_LEN (160-9)
|
#define MAX_POST_TEXT_LEN (160-9)
|
||||||
|
|
||||||
struct PostInfo {
|
struct PostInfo {
|
||||||
@@ -116,9 +92,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
|||||||
bool _logging;
|
bool _logging;
|
||||||
NodePrefs _prefs;
|
NodePrefs _prefs;
|
||||||
CommonCLI _cli;
|
CommonCLI _cli;
|
||||||
|
ClientACL acl;
|
||||||
|
unsigned long dirty_contacts_expiry;
|
||||||
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
||||||
int num_clients;
|
|
||||||
ClientInfo known_clients[MAX_CLIENTS];
|
|
||||||
unsigned long next_push;
|
unsigned long next_push;
|
||||||
uint16_t _num_posted, _num_post_pushes;
|
uint16_t _num_posted, _num_post_pushes;
|
||||||
int next_client_idx; // for round-robin polling
|
int next_client_idx; // for round-robin polling
|
||||||
@@ -132,8 +108,6 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
|||||||
uint8_t pending_cr;
|
uint8_t pending_cr;
|
||||||
int matching_peer_indexes[MAX_CLIENTS];
|
int matching_peer_indexes[MAX_CLIENTS];
|
||||||
|
|
||||||
ClientInfo* putClient(const mesh::Identity& id);
|
|
||||||
void evict(ClientInfo* client);
|
|
||||||
void addPost(ClientInfo* client, const char* postData);
|
void addPost(ClientInfo* client, const char* postData);
|
||||||
void pushPostToClient(ClientInfo* client, PostInfo& post);
|
void pushPostToClient(ClientInfo* client, PostInfo& post);
|
||||||
uint8_t getUnsyncedCount(ClientInfo* client);
|
uint8_t getUnsyncedCount(ClientInfo* client);
|
||||||
@@ -213,6 +187,8 @@ public:
|
|||||||
|
|
||||||
mesh::LocalIdentity& getSelfId() override { return self_id; }
|
mesh::LocalIdentity& getSelfId() override { return self_id; }
|
||||||
|
|
||||||
|
static bool saveFilter(ClientInfo* client);
|
||||||
|
|
||||||
void saveIdentity(const mesh::LocalIdentity& new_id) override;
|
void saveIdentity(const mesh::LocalIdentity& new_id) override;
|
||||||
void clearStats() override;
|
void clearStats() override;
|
||||||
void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
|
void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
|
||||||
|
|||||||
@@ -217,18 +217,18 @@ protected:
|
|||||||
saveContacts();
|
saveContacts();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool processAck(const uint8_t *data) override {
|
ContactInfo* processAck(const uint8_t *data) override {
|
||||||
if (memcmp(data, &expected_ack_crc, 4) == 0) { // got an ACK from recipient
|
if (memcmp(data, &expected_ack_crc, 4) == 0) { // got an ACK from recipient
|
||||||
Serial.printf(" Got ACK! (round trip: %d millis)\n", _ms->getMillis() - last_msg_sent);
|
Serial.printf(" Got ACK! (round trip: %d millis)\n", _ms->getMillis() - last_msg_sent);
|
||||||
// NOTE: the same ACK can be received multiple times!
|
// NOTE: the same ACK can be received multiple times!
|
||||||
expected_ack_crc = 0; // reset our expected hash, now that we have received ACK
|
expected_ack_crc = 0; // reset our expected hash, now that we have received ACK
|
||||||
return true;
|
return NULL; // TODO: really should return ContactInfo pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
//uint32_t crc;
|
//uint32_t crc;
|
||||||
//memcpy(&crc, data, 4);
|
//memcpy(&crc, data, 4);
|
||||||
//MESH_DEBUG_PRINTLN("unknown ACK received: %08X (expected: %08X)", crc, expected_ack_crc);
|
//MESH_DEBUG_PRINTLN("unknown ACK received: %08X (expected: %08X)", crc, expected_ack_crc);
|
||||||
return false;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onMessageRecv(const ContactInfo& from, mesh::Packet* pkt, uint32_t sender_timestamp, const char *text) override {
|
void onMessageRecv(const ContactInfo& from, mesh::Packet* pkt, uint32_t sender_timestamp, const char *text) override {
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ build_src_filter = ${arduino_base.build_src_filter}
|
|||||||
+<helpers/stm32>
|
+<helpers/stm32>
|
||||||
lib_deps = ${arduino_base.lib_deps}
|
lib_deps = ${arduino_base.lib_deps}
|
||||||
file://arch/stm32/Adafruit_LittleFS_stm32
|
file://arch/stm32/Adafruit_LittleFS_stm32
|
||||||
|
adafruit/Adafruit BusIO @ 1.17.2
|
||||||
|
|
||||||
[sensor_base]
|
[sensor_base]
|
||||||
build_flags =
|
build_flags =
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
|||||||
data[len] = 0; // need to make a C string again, with null terminator
|
data[len] = 0; // need to make a C string again, with null terminator
|
||||||
|
|
||||||
if (flags == TXT_TYPE_PLAIN) {
|
if (flags == TXT_TYPE_PLAIN) {
|
||||||
|
from.lastmod = getRTCClock()->getCurrentTime(); // update last heard time
|
||||||
onMessageRecv(from, packet, timestamp, (const char *) &data[5]); // let UI know
|
onMessageRecv(from, packet, timestamp, (const char *) &data[5]); // let UI know
|
||||||
|
|
||||||
uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it
|
uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it
|
||||||
@@ -184,6 +185,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
|||||||
if (timestamp > from.sync_since) { // make sure 'sync_since' is up-to-date
|
if (timestamp > from.sync_since) { // make sure 'sync_since' is up-to-date
|
||||||
from.sync_since = timestamp;
|
from.sync_since = timestamp;
|
||||||
}
|
}
|
||||||
|
from.lastmod = getRTCClock()->getCurrentTime(); // update last heard time
|
||||||
onSignedMessageRecv(from, packet, timestamp, &data[5], (const char *) &data[9]); // let UI know
|
onSignedMessageRecv(from, packet, timestamp, &data[5], (const char *) &data[9]); // let UI know
|
||||||
|
|
||||||
uint32_t ack_hash; // calc truncated hash of the message timestamp + text + OUR pub_key, to prove to sender that we got it
|
uint32_t ack_hash; // calc truncated hash of the message timestamp + text + OUR pub_key, to prove to sender that we got it
|
||||||
@@ -223,6 +225,10 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
|||||||
}
|
}
|
||||||
} else if (type == PAYLOAD_TYPE_RESPONSE && len > 0) {
|
} else if (type == PAYLOAD_TYPE_RESPONSE && len > 0) {
|
||||||
onContactResponse(from, data, len);
|
onContactResponse(from, data, len);
|
||||||
|
if (packet->isRouteFlood() && from.out_path_len >= 0) {
|
||||||
|
// we have direct path, but other node is still sending flood response, so maybe they didn't receive reciprocal path properly(?)
|
||||||
|
handleReturnPathRetry(from, packet->path, packet->path_len);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +254,7 @@ bool BaseChatMesh::onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_
|
|||||||
|
|
||||||
if (extra_type == PAYLOAD_TYPE_ACK && extra_len >= 4) {
|
if (extra_type == PAYLOAD_TYPE_ACK && extra_len >= 4) {
|
||||||
// also got an encoded ACK!
|
// also got an encoded ACK!
|
||||||
if (processAck(extra)) {
|
if (processAck(extra) != NULL) {
|
||||||
txt_send_timeout = 0; // matched one we're waiting for, cancel timeout timer
|
txt_send_timeout = 0; // matched one we're waiting for, cancel timeout timer
|
||||||
}
|
}
|
||||||
} else if (extra_type == PAYLOAD_TYPE_RESPONSE && extra_len > 0) {
|
} else if (extra_type == PAYLOAD_TYPE_RESPONSE && extra_len > 0) {
|
||||||
@@ -258,12 +264,25 @@ bool BaseChatMesh::onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) {
|
void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) {
|
||||||
if (processAck((uint8_t *)&ack_crc)) {
|
ContactInfo* from;
|
||||||
|
if ((from = processAck((uint8_t *)&ack_crc)) != NULL) {
|
||||||
txt_send_timeout = 0; // matched one we're waiting for, cancel timeout timer
|
txt_send_timeout = 0; // matched one we're waiting for, cancel timeout timer
|
||||||
packet->markDoNotRetransmit(); // ACK was for this node, so don't retransmit
|
packet->markDoNotRetransmit(); // ACK was for this node, so don't retransmit
|
||||||
|
|
||||||
|
if (packet->isRouteFlood() && from->out_path_len >= 0) {
|
||||||
|
// we have direct path, but other node is still sending flood, so maybe they didn't receive reciprocal path properly(?)
|
||||||
|
handleReturnPathRetry(*from, packet->path, packet->path_len);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BaseChatMesh::handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len) {
|
||||||
|
// NOTE: simplest impl is just to re-send a reciprocal return path to sender (DIRECTLY)
|
||||||
|
// override this method in various firmwares, if there's a better strategy
|
||||||
|
mesh::Packet* rpath = createPathReturn(contact.id, contact.shared_secret, path, path_len, 0, NULL, 0);
|
||||||
|
if (rpath) sendDirect(rpath, contact.out_path, contact.out_path_len, 3000); // 3 second delay
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef MAX_GROUP_CHANNELS
|
#ifdef MAX_GROUP_CHANNELS
|
||||||
int BaseChatMesh::searchChannelsByHash(const uint8_t* hash, mesh::GroupChannel dest[], int max_matches) {
|
int BaseChatMesh::searchChannelsByHash(const uint8_t* hash, mesh::GroupChannel dest[], int max_matches) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
@@ -550,7 +569,7 @@ void BaseChatMesh::markConnectionActive(const ContactInfo& contact) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BaseChatMesh::checkConnectionsAck(const uint8_t* data) {
|
ContactInfo* BaseChatMesh::checkConnectionsAck(const uint8_t* data) {
|
||||||
for (int i = 0; i < MAX_CONNECTIONS; i++) {
|
for (int i = 0; i < MAX_CONNECTIONS; i++) {
|
||||||
if (connections[i].keep_alive_millis > 0 && memcmp(&connections[i].expected_ack, data, 4) == 0) {
|
if (connections[i].keep_alive_millis > 0 && memcmp(&connections[i].expected_ack, data, 4) == 0) {
|
||||||
// yes, got an ack for our keep_alive request!
|
// yes, got an ack for our keep_alive request!
|
||||||
@@ -559,10 +578,12 @@ bool BaseChatMesh::checkConnectionsAck(const uint8_t* data) {
|
|||||||
|
|
||||||
// re-schedule next KEEP_ALIVE, now that we have heard from server
|
// re-schedule next KEEP_ALIVE, now that we have heard from server
|
||||||
connections[i].next_ping = futureMillis(connections[i].keep_alive_millis);
|
connections[i].next_ping = futureMillis(connections[i].keep_alive_millis);
|
||||||
return true; // yes, a match
|
|
||||||
|
auto id = &connections[i].server_id;
|
||||||
|
return lookupContactByPubKey(id->pub_key, PUB_KEY_SIZE); // yes, a match
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false; /// no match
|
return NULL; /// no match
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseChatMesh::checkConnections() {
|
void BaseChatMesh::checkConnections() {
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ protected:
|
|||||||
// 'UI' concepts, for sub-classes to implement
|
// 'UI' concepts, for sub-classes to implement
|
||||||
virtual bool isAutoAddEnabled() const { return true; }
|
virtual bool isAutoAddEnabled() const { return true; }
|
||||||
virtual void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) = 0;
|
virtual void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) = 0;
|
||||||
virtual bool processAck(const uint8_t *data) = 0;
|
virtual ContactInfo* processAck(const uint8_t *data) = 0;
|
||||||
virtual void onContactPathUpdated(const ContactInfo& contact) = 0;
|
virtual void onContactPathUpdated(const ContactInfo& contact) = 0;
|
||||||
virtual bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len);
|
virtual bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len);
|
||||||
virtual void onMessageRecv(const ContactInfo& contact, mesh::Packet* pkt, uint32_t sender_timestamp, const char *text) = 0;
|
virtual void onMessageRecv(const ContactInfo& contact, mesh::Packet* pkt, uint32_t sender_timestamp, const char *text) = 0;
|
||||||
@@ -105,6 +105,7 @@ protected:
|
|||||||
virtual void onChannelMessageRecv(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t timestamp, const char *text) = 0;
|
virtual void onChannelMessageRecv(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t timestamp, const char *text) = 0;
|
||||||
virtual uint8_t onContactRequest(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t* data, uint8_t len, uint8_t* reply) = 0;
|
virtual uint8_t onContactRequest(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t* data, uint8_t len, uint8_t* reply) = 0;
|
||||||
virtual void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) = 0;
|
virtual void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) = 0;
|
||||||
|
virtual void handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len);
|
||||||
|
|
||||||
// storage concepts, for sub-classes to override/implement
|
// storage concepts, for sub-classes to override/implement
|
||||||
virtual int getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { return 0; } // not implemented
|
virtual int getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { return 0; } // not implemented
|
||||||
@@ -127,7 +128,7 @@ protected:
|
|||||||
void stopConnection(const uint8_t* pub_key);
|
void stopConnection(const uint8_t* pub_key);
|
||||||
bool hasConnectionTo(const uint8_t* pub_key);
|
bool hasConnectionTo(const uint8_t* pub_key);
|
||||||
void markConnectionActive(const ContactInfo& contact);
|
void markConnectionActive(const ContactInfo& contact);
|
||||||
bool checkConnectionsAck(const uint8_t* data);
|
ContactInfo* checkConnectionsAck(const uint8_t* data);
|
||||||
void checkConnections();
|
void checkConnections();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -24,16 +24,17 @@ void ClientACL::load(FILESYSTEM* _fs) {
|
|||||||
while (!full) {
|
while (!full) {
|
||||||
ClientInfo c;
|
ClientInfo c;
|
||||||
uint8_t pub_key[32];
|
uint8_t pub_key[32];
|
||||||
uint8_t unused[6];
|
uint8_t unused[2];
|
||||||
|
|
||||||
|
memset(&c, 0, sizeof(c));
|
||||||
|
|
||||||
bool success = (file.read(pub_key, 32) == 32);
|
bool success = (file.read(pub_key, 32) == 32);
|
||||||
success = success && (file.read((uint8_t *) &c.permissions, 1) == 1);
|
success = success && (file.read((uint8_t *) &c.permissions, 1) == 1);
|
||||||
success = success && (file.read(unused, 6) == 6);
|
success = success && (file.read((uint8_t *) &c.extra.room.sync_since, 4) == 4);
|
||||||
|
success = success && (file.read(unused, 2) == 2);
|
||||||
success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1);
|
success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1);
|
||||||
success = success && (file.read(c.out_path, 64) == 64);
|
success = success && (file.read(c.out_path, 64) == 64);
|
||||||
success = success && (file.read(c.shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE);
|
success = success && (file.read(c.shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE);
|
||||||
c.last_timestamp = 0; // transient
|
|
||||||
c.last_activity = 0;
|
|
||||||
|
|
||||||
if (!success) break; // EOF
|
if (!success) break; // EOF
|
||||||
|
|
||||||
@@ -49,19 +50,20 @@ void ClientACL::load(FILESYSTEM* _fs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientACL::save(FILESYSTEM* _fs) {
|
void ClientACL::save(FILESYSTEM* _fs, bool (*filter)(ClientInfo*)) {
|
||||||
File file = openWrite(_fs, "/s_contacts");
|
File file = openWrite(_fs, "/s_contacts");
|
||||||
if (file) {
|
if (file) {
|
||||||
uint8_t unused[6];
|
uint8_t unused[2];
|
||||||
memset(unused, 0, sizeof(unused));
|
memset(unused, 0, sizeof(unused));
|
||||||
|
|
||||||
for (int i = 0; i < num_clients; i++) {
|
for (int i = 0; i < num_clients; i++) {
|
||||||
auto c = &clients[i];
|
auto c = &clients[i];
|
||||||
if (c->permissions == 0) continue; // skip deleted entries
|
if (c->permissions == 0 || (filter && !filter(c))) continue; // skip deleted entries, or by filter function
|
||||||
|
|
||||||
bool success = (file.write(c->id.pub_key, 32) == 32);
|
bool success = (file.write(c->id.pub_key, 32) == 32);
|
||||||
success = success && (file.write((uint8_t *) &c->permissions, 1) == 1);
|
success = success && (file.write((uint8_t *) &c->permissions, 1) == 1);
|
||||||
success = success && (file.write(unused, 6) == 6);
|
success = success && (file.write((uint8_t *) &c->extra.room.sync_since, 4) == 4);
|
||||||
|
success = success && (file.write(unused, 2) == 2);
|
||||||
success = success && (file.write((uint8_t *)&c->out_path_len, 1) == 1);
|
success = success && (file.write((uint8_t *)&c->out_path_len, 1) == 1);
|
||||||
success = success && (file.write(c->out_path, 64) == 64);
|
success = success && (file.write(c->out_path, 64) == 64);
|
||||||
success = success && (file.write(c->shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE);
|
success = success && (file.write(c->shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE);
|
||||||
|
|||||||
@@ -18,6 +18,15 @@ struct ClientInfo {
|
|||||||
uint8_t shared_secret[PUB_KEY_SIZE];
|
uint8_t shared_secret[PUB_KEY_SIZE];
|
||||||
uint32_t last_timestamp; // by THEIR clock (transient)
|
uint32_t last_timestamp; // by THEIR clock (transient)
|
||||||
uint32_t last_activity; // by OUR clock (transient)
|
uint32_t last_activity; // by OUR clock (transient)
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
uint32_t sync_since; // sync messages SINCE this timestamp (by OUR clock)
|
||||||
|
uint32_t pending_ack;
|
||||||
|
uint32_t push_post_timestamp;
|
||||||
|
unsigned long ack_timeout;
|
||||||
|
uint8_t push_failures;
|
||||||
|
} room;
|
||||||
|
} extra;
|
||||||
|
|
||||||
bool isAdmin() const { return (permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_ADMIN; }
|
bool isAdmin() const { return (permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_ADMIN; }
|
||||||
};
|
};
|
||||||
@@ -36,7 +45,7 @@ public:
|
|||||||
num_clients = 0;
|
num_clients = 0;
|
||||||
}
|
}
|
||||||
void load(FILESYSTEM* _fs);
|
void load(FILESYSTEM* _fs);
|
||||||
void save(FILESYSTEM* _fs);
|
void save(FILESYSTEM* _fs, bool (*filter)(ClientInfo*)=NULL);
|
||||||
|
|
||||||
ClientInfo* getClient(const uint8_t* pubkey, int key_len);
|
ClientInfo* getClient(const uint8_t* pubkey, int key_len);
|
||||||
ClientInfo* putClient(const mesh::Identity& id, uint8_t init_perms);
|
ClientInfo* putClient(const mesh::Identity& id, uint8_t init_perms);
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ bool SerialBLEInterface::onSecurityRequest() {
|
|||||||
void SerialBLEInterface::onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) {
|
void SerialBLEInterface::onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) {
|
||||||
if (cmpl.success) {
|
if (cmpl.success) {
|
||||||
BLE_DEBUG_PRINTLN(" - SecurityCallback - Authentication Success");
|
BLE_DEBUG_PRINTLN(" - SecurityCallback - Authentication Success");
|
||||||
//deviceConnected = true;
|
deviceConnected = true;
|
||||||
} else {
|
} else {
|
||||||
BLE_DEBUG_PRINTLN(" - SecurityCallback - Authentication Failure*");
|
BLE_DEBUG_PRINTLN(" - SecurityCallback - Authentication Failure*");
|
||||||
|
|
||||||
@@ -88,8 +88,6 @@ void SerialBLEInterface::onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t
|
|||||||
|
|
||||||
void SerialBLEInterface::onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) {
|
void SerialBLEInterface::onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) {
|
||||||
BLE_DEBUG_PRINTLN("onMtuChanged(), mtu=%d", pServer->getPeerMTU(param->mtu.conn_id));
|
BLE_DEBUG_PRINTLN("onMtuChanged(), mtu=%d", pServer->getPeerMTU(param->mtu.conn_id));
|
||||||
|
|
||||||
deviceConnected = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SerialBLEInterface::onDisconnect(BLEServer* pServer) {
|
void SerialBLEInterface::onDisconnect(BLEServer* pServer) {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ void SerialBLEInterface::startAdv() {
|
|||||||
* https://developer.apple.com/library/content/qa/qa1931/_index.html
|
* https://developer.apple.com/library/content/qa/qa1931/_index.html
|
||||||
*/
|
*/
|
||||||
Bluefruit.Advertising.restartOnDisconnect(false); // don't restart automatically as we handle it in onDisconnect
|
Bluefruit.Advertising.restartOnDisconnect(false); // don't restart automatically as we handle it in onDisconnect
|
||||||
Bluefruit.Advertising.setInterval(32, 1600);
|
Bluefruit.Advertising.setInterval(32, 244);
|
||||||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
class DisplayDriver {
|
class DisplayDriver {
|
||||||
int _w, _h;
|
int _w, _h;
|
||||||
@@ -31,5 +32,60 @@ public:
|
|||||||
setCursor(mid_x - w/2, y);
|
setCursor(mid_x - w/2, y);
|
||||||
print(str);
|
print(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convert UTF-8 characters to displayable block characters for compatibility
|
||||||
|
virtual void translateUTF8ToBlocks(char* dest, const char* src, size_t dest_size) {
|
||||||
|
size_t j = 0;
|
||||||
|
for (size_t i = 0; src[i] != 0 && j < dest_size - 1; i++) {
|
||||||
|
unsigned char c = (unsigned char)src[i];
|
||||||
|
if (c >= 32 && c <= 126) {
|
||||||
|
dest[j++] = c; // ASCII printable
|
||||||
|
} else if (c >= 0x80) {
|
||||||
|
dest[j++] = '\xDB'; // CP437 full block █
|
||||||
|
while (src[i+1] && (src[i+1] & 0xC0) == 0x80)
|
||||||
|
i++; // skip UTF-8 continuation bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dest[j] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw text with ellipsis if it exceeds max_width
|
||||||
|
virtual void drawTextEllipsized(int x, int y, int max_width, const char* str) {
|
||||||
|
char temp_str[256]; // reasonable buffer size
|
||||||
|
size_t len = strlen(str);
|
||||||
|
if (len >= sizeof(temp_str)) len = sizeof(temp_str) - 1;
|
||||||
|
memcpy(temp_str, str, len);
|
||||||
|
temp_str[len] = 0;
|
||||||
|
|
||||||
|
if (getTextWidth(temp_str) <= max_width) {
|
||||||
|
setCursor(x, y);
|
||||||
|
print(temp_str);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for variable-width fonts (GxEPD), add space after ellipsis
|
||||||
|
// for fixed-width fonts (OLED), keep tight spacing to save precious characters
|
||||||
|
const char* ellipsis;
|
||||||
|
// use a simple heuristic: if 'i' and 'l' have different widths, it's variable-width
|
||||||
|
int i_width = getTextWidth("i");
|
||||||
|
int l_width = getTextWidth("l");
|
||||||
|
if (i_width != l_width) {
|
||||||
|
ellipsis = "... "; // variable-width fonts: add space
|
||||||
|
} else {
|
||||||
|
ellipsis = "..."; // fixed-width fonts: no space
|
||||||
|
}
|
||||||
|
|
||||||
|
int ellipsis_width = getTextWidth(ellipsis);
|
||||||
|
int str_len = strlen(temp_str);
|
||||||
|
|
||||||
|
while (str_len > 0 && getTextWidth(temp_str) > max_width - ellipsis_width) {
|
||||||
|
temp_str[--str_len] = 0;
|
||||||
|
}
|
||||||
|
strcat(temp_str, ellipsis);
|
||||||
|
|
||||||
|
setCursor(x, y);
|
||||||
|
print(temp_str);
|
||||||
|
}
|
||||||
|
|
||||||
virtual void endFrame() = 0;
|
virtual void endFrame() = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
38
src/helpers/ui/GenericVibration.cpp
Normal file
38
src/helpers/ui/GenericVibration.cpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#ifdef PIN_VIBRATION
|
||||||
|
#include "GenericVibration.h"
|
||||||
|
|
||||||
|
void GenericVibration::begin() {
|
||||||
|
pinMode(PIN_VIBRATION, OUTPUT);
|
||||||
|
digitalWrite(PIN_VIBRATION, LOW);
|
||||||
|
duration = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericVibration::trigger() {
|
||||||
|
duration = millis();
|
||||||
|
digitalWrite(PIN_VIBRATION, HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericVibration::loop() {
|
||||||
|
if (isVibrating()) {
|
||||||
|
if ((millis() / 1000) % 2 == 0) {
|
||||||
|
digitalWrite(PIN_VIBRATION, LOW);
|
||||||
|
} else {
|
||||||
|
digitalWrite(PIN_VIBRATION, HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (millis() - duration > VIBRATION_TIMEOUT) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GenericVibration::isVibrating() {
|
||||||
|
return duration > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericVibration::stop() {
|
||||||
|
duration = 0;
|
||||||
|
digitalWrite(PIN_VIBRATION, LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ifdef PIN_VIBRATION
|
||||||
33
src/helpers/ui/GenericVibration.h
Normal file
33
src/helpers/ui/GenericVibration.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef PIN_VIBRATION
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Vibration motor control class
|
||||||
|
*
|
||||||
|
* Provides vibration feedback for events like new messages and new contacts
|
||||||
|
* Features:
|
||||||
|
* - 1-second vibration pulse
|
||||||
|
* - 5-second nag timeout (cooldown between vibrations)
|
||||||
|
* - Non-blocking operation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef VIBRATION_TIMEOUT
|
||||||
|
#define VIBRATION_TIMEOUT 5000 // 5 seconds default
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class GenericVibration {
|
||||||
|
public:
|
||||||
|
void begin(); // set up vibration pin
|
||||||
|
void trigger(); // trigger vibration if cooldown has passed
|
||||||
|
void loop(); // non-blocking timer handling
|
||||||
|
bool isVibrating(); // returns true if currently vibrating
|
||||||
|
void stop(); // stop vibration immediately
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned long duration;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ifdef PIN_VIBRATION
|
||||||
136
variants/ebyte_eora_s3/platformio.ini
Normal file
136
variants/ebyte_eora_s3/platformio.ini
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
[Ebyte_EoRa-S3]
|
||||||
|
extends = esp32_base
|
||||||
|
board = ebyte_eora-s3
|
||||||
|
build_flags =
|
||||||
|
${esp32_base.build_flags}
|
||||||
|
-I variants/ebyte_eora_s3
|
||||||
|
-D EBYTE_EORA_S3
|
||||||
|
-D P_LORA_DIO_1=33
|
||||||
|
-D P_LORA_NSS=7
|
||||||
|
-D P_LORA_RESET=8 ; RADIOLIB_NC
|
||||||
|
-D P_LORA_BUSY=34
|
||||||
|
-D P_LORA_SCLK=5
|
||||||
|
-D P_LORA_MISO=3
|
||||||
|
-D P_LORA_MOSI=6
|
||||||
|
-D P_LORA_TX_LED=37
|
||||||
|
-D PIN_VBAT_READ=1
|
||||||
|
-D PIN_USER_BTN=0
|
||||||
|
-D PIN_BOARD_SDA=18
|
||||||
|
-D PIN_BOARD_SCL=17
|
||||||
|
|
||||||
|
; SD_DAT0/MISO - GPIO2
|
||||||
|
; SD_DAT1 - GPIO4
|
||||||
|
; SD_CMD/MOSI - GPIO11
|
||||||
|
; SD_DAT2 - GPIO112
|
||||||
|
; SD_DAT3/CS - GPIO113
|
||||||
|
; SD_CLK - GPIO114
|
||||||
|
-D PIN_BOARD_SDA=18
|
||||||
|
-D PIN_BOARD_SCL=17
|
||||||
|
|
||||||
|
-D SX126X_DIO2_AS_RF_SWITCH=true
|
||||||
|
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||||
|
-D SX126X_CURRENT_LIMIT=140
|
||||||
|
-D RADIO_CLASS=CustomSX1262
|
||||||
|
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||||
|
-D LORA_TX_POWER=22
|
||||||
|
-D SX126X_RX_BOOSTED_GAIN=1
|
||||||
|
build_src_filter = ${esp32_base.build_src_filter}
|
||||||
|
+<../variants/ebyte_eora_s3>
|
||||||
|
lib_deps =
|
||||||
|
${esp32_base.lib_deps}
|
||||||
|
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||||
|
|
||||||
|
; === EByte EORA_S3 with SX1262 environments ===
|
||||||
|
[env:Ebyte_EoRa-S3_Repeater]
|
||||||
|
extends = Ebyte_EoRa-S3
|
||||||
|
build_flags =
|
||||||
|
${Ebyte_EoRa-S3.build_flags}
|
||||||
|
-D DISPLAY_CLASS=SSD1306Display
|
||||||
|
-D ADVERT_NAME='"EORA_S3-1262 Repeater"'
|
||||||
|
-D ADVERT_LAT=0.0
|
||||||
|
-D ADVERT_LON=0.0
|
||||||
|
-D ADMIN_PASSWORD='"password"'
|
||||||
|
-D MAX_NEIGHBOURS=8
|
||||||
|
; -D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${Ebyte_EoRa-S3.build_src_filter}
|
||||||
|
+<helpers/ui/SSD1306Display.cpp>
|
||||||
|
+<../examples/simple_repeater>
|
||||||
|
lib_deps =
|
||||||
|
${Ebyte_EoRa-S3.lib_deps}
|
||||||
|
${esp32_ota.lib_deps}
|
||||||
|
|
||||||
|
[env:Ebyte_EoRa-S3_terminal_chat]
|
||||||
|
extends = Ebyte_EoRa-S3
|
||||||
|
build_flags =
|
||||||
|
${Ebyte_EoRa-S3.build_flags}
|
||||||
|
-D MAX_CONTACTS=300
|
||||||
|
-D MAX_GROUP_CHANNELS=1
|
||||||
|
; -D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${Ebyte_EoRa-S3.build_src_filter}
|
||||||
|
+<../examples/simple_secure_chat/main.cpp>
|
||||||
|
lib_deps =
|
||||||
|
${Ebyte_EoRa-S3.lib_deps}
|
||||||
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
|
||||||
|
[env:Ebyte_EoRa-S3_room_server]
|
||||||
|
extends = Ebyte_EoRa-S3
|
||||||
|
build_flags =
|
||||||
|
${Ebyte_EoRa-S3.build_flags}
|
||||||
|
-D DISPLAY_CLASS=SSD1306Display
|
||||||
|
-D ADVERT_NAME='"EORA_S3-1262 Room"'
|
||||||
|
-D ADVERT_LAT=0.0
|
||||||
|
-D ADVERT_LON=0.0
|
||||||
|
-D ADMIN_PASSWORD='"password"'
|
||||||
|
-D ROOM_PASSWORD='"hello"'
|
||||||
|
; -D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${Ebyte_EoRa-S3.build_src_filter}
|
||||||
|
+<helpers/ui/SSD1306Display.cpp>
|
||||||
|
+<../examples/simple_room_server>
|
||||||
|
lib_deps =
|
||||||
|
${Ebyte_EoRa-S3.lib_deps}
|
||||||
|
${esp32_ota.lib_deps}
|
||||||
|
|
||||||
|
[env:Ebyte_EoRa-S3_companion_radio_usb]
|
||||||
|
extends = Ebyte_EoRa-S3
|
||||||
|
build_flags =
|
||||||
|
${Ebyte_EoRa-S3.build_flags}
|
||||||
|
-I examples/companion_radio/ui-new
|
||||||
|
-D DISPLAY_CLASS=SSD1306Display
|
||||||
|
-D MAX_CONTACTS=300
|
||||||
|
-D MAX_GROUP_CHANNELS=8
|
||||||
|
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||||
|
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${Ebyte_EoRa-S3.build_src_filter}
|
||||||
|
+<helpers/ui/SSD1306Display.cpp>
|
||||||
|
+<helpers/ui/MomentaryButton.cpp>
|
||||||
|
+<../examples/companion_radio/*.cpp>
|
||||||
|
+<../examples/companion_radio/ui-new/*.cpp>
|
||||||
|
lib_deps =
|
||||||
|
${Ebyte_EoRa-S3.lib_deps}
|
||||||
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
|
||||||
|
[env:Ebyte_EoRa-S3_companion_radio_ble]
|
||||||
|
extends = Ebyte_EoRa-S3
|
||||||
|
build_flags =
|
||||||
|
${Ebyte_EoRa-S3.build_flags}
|
||||||
|
-I examples/companion_radio/ui-new
|
||||||
|
-D DISPLAY_CLASS=SSD1306Display
|
||||||
|
-D MAX_CONTACTS=300
|
||||||
|
-D MAX_GROUP_CHANNELS=8
|
||||||
|
-D BLE_PIN_CODE=123456
|
||||||
|
-D BLE_DEBUG_LOGGING=1
|
||||||
|
-D OFFLINE_QUEUE_SIZE=256
|
||||||
|
; -D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${Ebyte_EoRa-S3.build_src_filter}
|
||||||
|
+<helpers/esp32/*.cpp>
|
||||||
|
+<helpers/ui/SSD1306Display.cpp>
|
||||||
|
+<helpers/ui/MomentaryButton.cpp>
|
||||||
|
+<../examples/companion_radio/*.cpp>
|
||||||
|
+<../examples/companion_radio/ui-new/*.cpp>
|
||||||
|
lib_deps =
|
||||||
|
${Ebyte_EoRa-S3.lib_deps}
|
||||||
|
densaugeo/base64 @ ~1.4.0
|
||||||
85
variants/ebyte_eora_s3/target.cpp
Normal file
85
variants/ebyte_eora_s3/target.cpp
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include "target.h"
|
||||||
|
|
||||||
|
ESP32Board board;
|
||||||
|
|
||||||
|
#if defined(P_LORA_SCLK)
|
||||||
|
static SPIClass spi;
|
||||||
|
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
|
||||||
|
#else
|
||||||
|
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
WRAPPER_CLASS radio_driver(radio, board);
|
||||||
|
|
||||||
|
ESP32RTCClock fallback_clock;
|
||||||
|
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||||
|
SensorManager sensors;
|
||||||
|
|
||||||
|
#ifdef DISPLAY_CLASS
|
||||||
|
DISPLAY_CLASS display;
|
||||||
|
MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef LORA_CR
|
||||||
|
#define LORA_CR 5
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool radio_init() {
|
||||||
|
fallback_clock.begin();
|
||||||
|
rtc_clock.begin(Wire);
|
||||||
|
|
||||||
|
#ifdef SX126X_DIO3_TCXO_VOLTAGE
|
||||||
|
float tcxo = SX126X_DIO3_TCXO_VOLTAGE;
|
||||||
|
#else
|
||||||
|
float tcxo = 1.6f;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(P_LORA_SCLK)
|
||||||
|
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
|
||||||
|
#endif
|
||||||
|
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, tcxo);
|
||||||
|
if (status != RADIOLIB_ERR_NONE) {
|
||||||
|
Serial.print("ERROR: radio init failed: ");
|
||||||
|
Serial.println(status);
|
||||||
|
return false; // fail
|
||||||
|
}
|
||||||
|
|
||||||
|
radio.setCRC(1);
|
||||||
|
|
||||||
|
#if defined(SX126X_RXEN) && defined(SX126X_TXEN)
|
||||||
|
radio.setRfSwitchPins(SX126X_RXEN, SX126X_TXEN);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef SX126X_CURRENT_LIMIT
|
||||||
|
radio.setCurrentLimit(SX126X_CURRENT_LIMIT);
|
||||||
|
#endif
|
||||||
|
#ifdef SX126X_DIO2_AS_RF_SWITCH
|
||||||
|
radio.setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH);
|
||||||
|
#endif
|
||||||
|
#ifdef SX126X_RX_BOOSTED_GAIN
|
||||||
|
radio.setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return true; // success
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t radio_get_rng_seed() {
|
||||||
|
return radio.random(0x7FFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||||
|
radio.setFrequency(freq);
|
||||||
|
radio.setSpreadingFactor(sf);
|
||||||
|
radio.setBandwidth(bw);
|
||||||
|
radio.setCodingRate(cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void radio_set_tx_power(uint8_t dbm) {
|
||||||
|
radio.setOutputPower(dbm);
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh::LocalIdentity radio_new_identity() {
|
||||||
|
RadioNoiseListener rng(radio);
|
||||||
|
return mesh::LocalIdentity(&rng); // create new random identity
|
||||||
|
}
|
||||||
29
variants/ebyte_eora_s3/target.h
Normal file
29
variants/ebyte_eora_s3/target.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define RADIOLIB_STATIC_ONLY 1
|
||||||
|
#include <RadioLib.h>
|
||||||
|
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||||
|
#include <helpers/ESP32Board.h>
|
||||||
|
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||||
|
#include <helpers/AutoDiscoverRTCClock.h>
|
||||||
|
#include <helpers/SensorManager.h>
|
||||||
|
#ifdef DISPLAY_CLASS
|
||||||
|
#include <helpers/ui/SSD1306Display.h>
|
||||||
|
#include <helpers/ui/MomentaryButton.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern ESP32Board board;
|
||||||
|
extern WRAPPER_CLASS radio_driver;
|
||||||
|
extern AutoDiscoverRTCClock rtc_clock;
|
||||||
|
extern SensorManager sensors;
|
||||||
|
|
||||||
|
#ifdef DISPLAY_CLASS
|
||||||
|
extern DISPLAY_CLASS display;
|
||||||
|
extern MomentaryButton user_btn;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool radio_init();
|
||||||
|
uint32_t radio_get_rng_seed();
|
||||||
|
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||||
|
void radio_set_tx_power(uint8_t dbm);
|
||||||
|
mesh::LocalIdentity radio_new_identity();
|
||||||
@@ -28,14 +28,12 @@ build_flags = ${nrf52_base.build_flags}
|
|||||||
-D PIN_BOARD_SCL=PIN_WIRE_SCL
|
-D PIN_BOARD_SCL=PIN_WIRE_SCL
|
||||||
build_src_filter = ${nrf52_base.build_src_filter}
|
build_src_filter = ${nrf52_base.build_src_filter}
|
||||||
+<../variants/rak_wismesh_tag>
|
+<../variants/rak_wismesh_tag>
|
||||||
+<helpers/ui/buzzer.cpp>
|
|
||||||
+<helpers/ui/MomentaryButton.cpp>
|
+<helpers/ui/MomentaryButton.cpp>
|
||||||
+<helpers/ui/NullDisplayDriver.cpp>
|
+<helpers/ui/NullDisplayDriver.cpp>
|
||||||
+<helpers/sensors>
|
+<helpers/sensors>
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${nrf52_base.lib_deps}
|
${nrf52_base.lib_deps}
|
||||||
${sensor_base.lib_deps}
|
${sensor_base.lib_deps}
|
||||||
end2endzone/NonBlockingRTTTL@^1.3.0
|
|
||||||
|
|
||||||
[env:RAK_WisMesh_Tag_Repeater]
|
[env:RAK_WisMesh_Tag_Repeater]
|
||||||
extends = rak_wismesh_tag
|
extends = rak_wismesh_tag
|
||||||
@@ -77,11 +75,13 @@ build_flags =
|
|||||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||||
build_src_filter = ${rak_wismesh_tag.build_src_filter}
|
build_src_filter = ${rak_wismesh_tag.build_src_filter}
|
||||||
|
+<helpers/ui/buzzer.cpp>
|
||||||
+<../examples/companion_radio/*.cpp>
|
+<../examples/companion_radio/*.cpp>
|
||||||
+<../examples/companion_radio/ui-orig/*.cpp>
|
+<../examples/companion_radio/ui-orig/*.cpp>
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${rak_wismesh_tag.lib_deps}
|
${rak_wismesh_tag.lib_deps}
|
||||||
densaugeo/base64 @ ~1.4.0
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
end2endzone/NonBlockingRTTTL@^1.3.0
|
||||||
|
|
||||||
[env:RAK_WisMesh_Tag_companion_radio_ble]
|
[env:RAK_WisMesh_Tag_companion_radio_ble]
|
||||||
extends = rak_wismesh_tag
|
extends = rak_wismesh_tag
|
||||||
@@ -98,12 +98,14 @@ build_flags =
|
|||||||
; -D MESH_PACKET_LOGGING=1
|
; -D MESH_PACKET_LOGGING=1
|
||||||
-D MESH_DEBUG=1
|
-D MESH_DEBUG=1
|
||||||
build_src_filter = ${rak_wismesh_tag.build_src_filter}
|
build_src_filter = ${rak_wismesh_tag.build_src_filter}
|
||||||
|
+<helpers/ui/buzzer.cpp>
|
||||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||||
+<../examples/companion_radio/*.cpp>
|
+<../examples/companion_radio/*.cpp>
|
||||||
+<../examples/companion_radio/ui-orig/*.cpp>
|
+<../examples/companion_radio/ui-orig/*.cpp>
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${rak4631.lib_deps}
|
${rak4631.lib_deps}
|
||||||
densaugeo/base64 @ ~1.4.0
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
end2endzone/NonBlockingRTTTL@^1.3.0
|
||||||
|
|
||||||
[env:RAK_WisMesh_Tag_sensor]
|
[env:RAK_WisMesh_Tag_sensor]
|
||||||
extends = rak4631
|
extends = rak4631
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ extends = nrf52_base
|
|||||||
board = seeed_sensecap_solar
|
board = seeed_sensecap_solar
|
||||||
board_build.ldscript = boards/nrf52840_s140_v7.ld
|
board_build.ldscript = boards/nrf52840_s140_v7.ld
|
||||||
build_flags = ${nrf52_base.build_flags}
|
build_flags = ${nrf52_base.build_flags}
|
||||||
|
${sensor_base.build_flags}
|
||||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include
|
-I lib/nrf52/s140_nrf52_7.3.0_API/include
|
||||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52
|
-I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52
|
||||||
-I variants/sensecap_solar
|
-I variants/sensecap_solar
|
||||||
-I src/helpers/nrf52
|
-I src/helpers/nrf52
|
||||||
|
-UENV_INCLUDE_GPS
|
||||||
-D NRF52_PLATFORM=1
|
-D NRF52_PLATFORM=1
|
||||||
-D RADIO_CLASS=CustomSX1262
|
-D RADIO_CLASS=CustomSX1262
|
||||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||||
@@ -22,13 +24,6 @@ build_flags = ${nrf52_base.build_flags}
|
|||||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||||
-D SX126X_CURRENT_LIMIT=140
|
-D SX126X_CURRENT_LIMIT=140
|
||||||
-D SX126X_RX_BOOSTED_GAIN=1
|
-D SX126X_RX_BOOSTED_GAIN=1
|
||||||
-D ENV_INCLUDE_AHTX0=1
|
|
||||||
-D ENV_INCLUDE_BME280=1
|
|
||||||
-D ENV_INCLUDE_BMP280=1
|
|
||||||
-D ENV_INCLUDE_SHTC3=1
|
|
||||||
-D ENV_INCLUDE_LPS22HB=1
|
|
||||||
-D ENV_INCLUDE_INA3221=1
|
|
||||||
-D ENV_INCLUDE_INA219=1
|
|
||||||
build_src_filter = ${nrf52_base.build_src_filter}
|
build_src_filter = ${nrf52_base.build_src_filter}
|
||||||
+<helpers/*.cpp>
|
+<helpers/*.cpp>
|
||||||
+<helpers/sensors>
|
+<helpers/sensors>
|
||||||
@@ -37,13 +32,7 @@ debug_tool = jlink
|
|||||||
upload_protocol = nrfutil
|
upload_protocol = nrfutil
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${nrf52_base.lib_deps}
|
${nrf52_base.lib_deps}
|
||||||
adafruit/Adafruit INA3221 Library @ ^1.0.1
|
${sensor_base.lib_deps}
|
||||||
adafruit/Adafruit INA219 @ ^1.2.3
|
|
||||||
adafruit/Adafruit AHTX0 @ ^2.0.5
|
|
||||||
adafruit/Adafruit BME280 Library @ ^2.3.0
|
|
||||||
adafruit/Adafruit BMP280 Library @ ^2.6.8
|
|
||||||
adafruit/Adafruit SHTC3 Library @ ^1.0.1
|
|
||||||
arduino-libraries/Arduino_LPS22HB @ ^1.0.2
|
|
||||||
|
|
||||||
[env:SenseCap_Solar_repeater]
|
[env:SenseCap_Solar_repeater]
|
||||||
extends = SenseCap_Solar
|
extends = SenseCap_Solar
|
||||||
|
|||||||
44
variants/tiny_relay/platformio.ini
Normal file
44
variants/tiny_relay/platformio.ini
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
[Tiny_Relay]
|
||||||
|
extends = stm32_base
|
||||||
|
board = tiny_relay
|
||||||
|
board_upload.maximum_size = 229376 ; 32kb for FS
|
||||||
|
build_flags = ${stm32_base.build_flags}
|
||||||
|
-D RADIO_CLASS=CustomSTM32WLx
|
||||||
|
-D WRAPPER_CLASS=CustomSTM32WLxWrapper
|
||||||
|
-D SPI_INTERFACES_COUNT=0
|
||||||
|
-D RX_BOOSTED_GAIN=true
|
||||||
|
; -D STM32WL_TCXO_VOLTAGE=1.6 ; defaults to 0 if undef
|
||||||
|
; -D LORA_TX_POWER=14 ; Defaults to 22 for HP, 14 is for LP version
|
||||||
|
-D LORA_TX_POWER=22 ; Enable 22dBm transmission
|
||||||
|
-D MAX_LORA_TX_POWER=22 ; Allow setting up to 22dBm in companion radio
|
||||||
|
-I variants/tiny_relay
|
||||||
|
build_src_filter = ${stm32_base.build_src_filter}
|
||||||
|
+<../variants/tiny_relay>
|
||||||
|
|
||||||
|
[env:Tiny_Relay-repeater]
|
||||||
|
extends = Tiny_Relay
|
||||||
|
build_flags = ${Tiny_Relay.build_flags}
|
||||||
|
-D ADVERT_NAME='"tiny_relay Repeater"'
|
||||||
|
-D ADMIN_PASSWORD='"password"'
|
||||||
|
build_src_filter = ${Tiny_Relay.build_src_filter}
|
||||||
|
+<../examples/simple_repeater/main.cpp>
|
||||||
|
|
||||||
|
[env:Tiny_Relay-sensor]
|
||||||
|
extends = Tiny_Relay
|
||||||
|
build_flags = ${Tiny_Relay.build_flags}
|
||||||
|
-D ADVERT_NAME='"tiny_relay Sensor"'
|
||||||
|
-D ADMIN_PASSWORD='"password"'
|
||||||
|
build_src_filter = ${Tiny_Relay.build_src_filter}
|
||||||
|
+<../examples/simple_sensor>
|
||||||
|
|
||||||
|
[env:Tiny_Relay_companion_radio_usb]
|
||||||
|
extends = Tiny_Relay
|
||||||
|
build_flags = ${Tiny_Relay.build_flags}
|
||||||
|
; -D FORMAT_FS=true
|
||||||
|
-D MAX_CONTACTS=100
|
||||||
|
-D MAX_GROUP_CHANNELS=8
|
||||||
|
-D MAX_LORA_TX_POWER=22
|
||||||
|
build_src_filter = ${Tiny_Relay.build_src_filter}
|
||||||
|
+<../examples/companion_radio/*.cpp>
|
||||||
|
lib_deps = ${Tiny_Relay.lib_deps}
|
||||||
|
densaugeo/base64 @ ~1.4.0
|
||||||
82
variants/tiny_relay/target.cpp
Normal file
82
variants/tiny_relay/target.cpp
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#include "target.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <helpers/ArduinoHelpers.h>
|
||||||
|
|
||||||
|
TinyRelayBoard board;
|
||||||
|
|
||||||
|
RADIO_CLASS radio = new STM32WLx_Module();
|
||||||
|
|
||||||
|
WRAPPER_CLASS radio_driver(radio, board);
|
||||||
|
|
||||||
|
static const uint32_t rfswitch_pins[] = {LORAWAN_RFSWITCH_PINS, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
|
||||||
|
static const Module::RfSwitchMode_t rfswitch_table[] = {
|
||||||
|
{STM32WLx::MODE_IDLE, {LOW, LOW}},
|
||||||
|
{STM32WLx::MODE_RX, {HIGH, LOW}},
|
||||||
|
{STM32WLx::MODE_TX_LP, {LOW, HIGH}},
|
||||||
|
{STM32WLx::MODE_TX_HP, {LOW, HIGH}},
|
||||||
|
END_OF_MODE_TABLE,
|
||||||
|
};
|
||||||
|
|
||||||
|
VolatileRTCClock rtc_clock;
|
||||||
|
SensorManager sensors;
|
||||||
|
|
||||||
|
#ifndef LORA_CR
|
||||||
|
#define LORA_CR 5
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef STM32WL_TCXO_VOLTAGE
|
||||||
|
// TCXO set to 0 for RAK3172
|
||||||
|
#define STM32WL_TCXO_VOLTAGE 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef LORA_TX_POWER
|
||||||
|
#define LORA_TX_POWER 22
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool radio_init()
|
||||||
|
{
|
||||||
|
// rtc_clock.begin(Wire);
|
||||||
|
|
||||||
|
radio.setRfSwitchTable(rfswitch_pins, rfswitch_table);
|
||||||
|
|
||||||
|
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16,
|
||||||
|
STM32WL_TCXO_VOLTAGE, 0);
|
||||||
|
|
||||||
|
if (status != RADIOLIB_ERR_NONE) {
|
||||||
|
Serial.print("ERROR: radio init failed: ");
|
||||||
|
Serial.println(status);
|
||||||
|
return false; // fail
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef RX_BOOSTED_GAIN
|
||||||
|
radio.setRxBoostedGainMode(RX_BOOSTED_GAIN);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
radio.setCRC(1);
|
||||||
|
|
||||||
|
return true; // success
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t radio_get_rng_seed()
|
||||||
|
{
|
||||||
|
return radio.random(0x7FFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr)
|
||||||
|
{
|
||||||
|
radio.setFrequency(freq);
|
||||||
|
radio.setSpreadingFactor(sf);
|
||||||
|
radio.setBandwidth(bw);
|
||||||
|
radio.setCodingRate(cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void radio_set_tx_power(uint8_t dbm)
|
||||||
|
{
|
||||||
|
radio.setOutputPower(dbm);
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh::LocalIdentity radio_new_identity()
|
||||||
|
{
|
||||||
|
RadioNoiseListener rng(radio);
|
||||||
|
return mesh::LocalIdentity(&rng); // create new random identity
|
||||||
|
}
|
||||||
59
variants/tiny_relay/target.h
Normal file
59
variants/tiny_relay/target.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define RADIOLIB_STATIC_ONLY 1
|
||||||
|
#include <RadioLib.h>
|
||||||
|
#include <helpers/ArduinoHelpers.h>
|
||||||
|
#include <helpers/SensorManager.h>
|
||||||
|
#include <helpers/radiolib/CustomSTM32WLxWrapper.h>
|
||||||
|
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||||
|
#include <helpers/stm32/STM32Board.h>
|
||||||
|
|
||||||
|
#define PIN_VBAT_READ A0
|
||||||
|
#define ADC_MULTIPLIER (5 * 1.73 * 1000)
|
||||||
|
|
||||||
|
class TinyRelayBoard : public STM32Board
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void begin() override
|
||||||
|
{
|
||||||
|
STM32Board::begin();
|
||||||
|
pinMode(PA0, OUTPUT);
|
||||||
|
pinMode(PA1, OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *getManufacturerName() const override { return "Tiny Relay"; }
|
||||||
|
|
||||||
|
uint16_t getBattMilliVolts() override
|
||||||
|
{
|
||||||
|
analogReadResolution(12);
|
||||||
|
uint32_t raw = 0;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
raw += analogRead(PIN_VBAT_READ);
|
||||||
|
}
|
||||||
|
return ((double)raw) * ADC_MULTIPLIER / 8 / 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGpio(uint32_t values) override
|
||||||
|
{
|
||||||
|
// set led values
|
||||||
|
digitalWrite(PA0, values & 1);
|
||||||
|
digitalWrite(PA1, (values & 2) >> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getGpio() override
|
||||||
|
{
|
||||||
|
// get led value
|
||||||
|
return (digitalRead(PA1) << 1) | digitalRead(PA0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
extern TinyRelayBoard board;
|
||||||
|
extern WRAPPER_CLASS radio_driver;
|
||||||
|
extern VolatileRTCClock rtc_clock;
|
||||||
|
extern SensorManager sensors;
|
||||||
|
|
||||||
|
bool radio_init();
|
||||||
|
uint32_t radio_get_rng_seed();
|
||||||
|
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||||
|
void radio_set_tx_power(uint8_t dbm);
|
||||||
|
mesh::LocalIdentity radio_new_identity();
|
||||||
5
variants/tiny_relay/variant.h
Normal file
5
variants/tiny_relay/variant.h
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <variant_RAK3172_MODULE.h>
|
||||||
|
|
||||||
|
#undef RNG
|
||||||
@@ -1,21 +1,19 @@
|
|||||||
#ifdef XIAO_NRF52
|
#ifdef XIAO_NRF52
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "XiaoNrf52Board.h"
|
|
||||||
|
|
||||||
#include <bluefruit.h>
|
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
|
#include <bluefruit.h>
|
||||||
|
|
||||||
|
#include "XiaoNrf52Board.h"
|
||||||
|
|
||||||
static BLEDfu bledfu;
|
static BLEDfu bledfu;
|
||||||
|
|
||||||
static void connect_callback(uint16_t conn_handle)
|
static void connect_callback(uint16_t conn_handle) {
|
||||||
{
|
|
||||||
(void)conn_handle;
|
(void)conn_handle;
|
||||||
MESH_DEBUG_PRINTLN("BLE client connected");
|
MESH_DEBUG_PRINTLN("BLE client connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason)
|
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
|
||||||
{
|
|
||||||
(void)conn_handle;
|
(void)conn_handle;
|
||||||
(void)reason;
|
(void)reason;
|
||||||
|
|
||||||
@@ -41,12 +39,12 @@ void XiaoNrf52Board::begin() {
|
|||||||
digitalWrite(P_LORA_TX_LED, HIGH);
|
digitalWrite(P_LORA_TX_LED, HIGH);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// pinMode(SX126X_POWER_EN, OUTPUT);
|
// pinMode(SX126X_POWER_EN, OUTPUT);
|
||||||
// digitalWrite(SX126X_POWER_EN, HIGH);
|
// digitalWrite(SX126X_POWER_EN, HIGH);
|
||||||
delay(10); // give sx1262 some time to power up
|
delay(10); // give sx1262 some time to power up
|
||||||
}
|
}
|
||||||
|
|
||||||
bool XiaoNrf52Board::startOTAUpdate(const char* id, char reply[]) {
|
bool XiaoNrf52Board::startOTAUpdate(const char *id, char reply[]) {
|
||||||
// Config the peripheral connection with maximum bandwidth
|
// Config the peripheral connection with maximum bandwidth
|
||||||
// more SRAM required by SoftDevice
|
// more SRAM required by SoftDevice
|
||||||
// Note: All config***() function must be called before begin()
|
// Note: All config***() function must be called before begin()
|
||||||
@@ -86,10 +84,8 @@ bool XiaoNrf52Board::startOTAUpdate(const char* id, char reply[]) {
|
|||||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||||
|
|
||||||
strcpy(reply, "OK - started");
|
strcpy(reply, "OK - started");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -5,20 +5,6 @@
|
|||||||
|
|
||||||
#ifdef XIAO_NRF52
|
#ifdef XIAO_NRF52
|
||||||
|
|
||||||
// redefine lora pins if using the S3 variant of SX1262 board
|
|
||||||
#ifdef SX1262_XIAO_S3_VARIANT
|
|
||||||
#undef P_LORA_DIO_1
|
|
||||||
#undef P_LORA_BUSY
|
|
||||||
#undef P_LORA_RESET
|
|
||||||
#undef P_LORA_NSS
|
|
||||||
#undef SX126X_RXEN
|
|
||||||
#define P_LORA_DIO_1 D0
|
|
||||||
#define P_LORA_BUSY D1
|
|
||||||
#define P_LORA_RESET D2
|
|
||||||
#define P_LORA_NSS D3
|
|
||||||
#define SX126X_RXEN D4
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class XiaoNrf52Board : public mesh::MainBoard {
|
class XiaoNrf52Board : public mesh::MainBoard {
|
||||||
protected:
|
protected:
|
||||||
uint8_t startup_reason;
|
uint8_t startup_reason;
|
||||||
|
|||||||
@@ -1,37 +1,19 @@
|
|||||||
[nrf52840_xiao]
|
[Xiao_nrf52]
|
||||||
extends = nrf52_base
|
extends = nrf52_base
|
||||||
platform_packages =
|
|
||||||
toolchain-gccarmnoneeabi@~1.100301.0
|
|
||||||
framework-arduinoadafruitnrf52
|
|
||||||
board = seeed-xiao-afruitnrf52-nrf52840
|
board = seeed-xiao-afruitnrf52-nrf52840
|
||||||
board_build.ldscript = boards/nrf52840_s140_v7.ld
|
board_build.ldscript = boards/nrf52840_s140_v7.ld
|
||||||
build_flags = ${nrf52_base.build_flags}
|
build_flags = ${nrf52_base.build_flags}
|
||||||
-D NRF52_PLATFORM -D XIAO_NRF52
|
${sensor_base.build_flags}
|
||||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include
|
-I lib/nrf52/s140_nrf52_7.3.0_API/include
|
||||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52
|
-I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52
|
||||||
lib_ignore =
|
|
||||||
BluetoothOTA
|
|
||||||
lvgl
|
|
||||||
lib5b4
|
|
||||||
lib_deps =
|
|
||||||
${nrf52_base.lib_deps}
|
|
||||||
rweather/Crypto @ ^0.4.0
|
|
||||||
adafruit/Adafruit INA3221 Library @ ^1.0.1
|
|
||||||
adafruit/Adafruit INA219 @ ^1.2.3
|
|
||||||
adafruit/Adafruit AHTX0 @ ^2.0.5
|
|
||||||
adafruit/Adafruit BME280 Library @ ^2.3.0
|
|
||||||
|
|
||||||
|
|
||||||
[Xiao_nrf52]
|
|
||||||
extends = nrf52840_xiao
|
|
||||||
;board_build.ldscript = boards/nrf52840_s140_v7.ld
|
|
||||||
build_flags = ${nrf52840_xiao.build_flags}
|
|
||||||
-D P_LORA_TX_LED=11
|
|
||||||
-I variants/xiao_nrf52
|
-I variants/xiao_nrf52
|
||||||
-I src/helpers/nrf52
|
-UENV_INCLUDE_GPS
|
||||||
|
-D NRF52_PLATFORM
|
||||||
|
-D XIAO_NRF52
|
||||||
-D RADIO_CLASS=CustomSX1262
|
-D RADIO_CLASS=CustomSX1262
|
||||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||||
-D LORA_TX_POWER=22
|
-D LORA_TX_POWER=22
|
||||||
|
-D P_LORA_TX_LED=11
|
||||||
-D P_LORA_DIO_1=D1
|
-D P_LORA_DIO_1=D1
|
||||||
-D P_LORA_RESET=D2
|
-D P_LORA_RESET=D2
|
||||||
-D P_LORA_BUSY=D3
|
-D P_LORA_BUSY=D3
|
||||||
@@ -42,18 +24,16 @@ build_flags = ${nrf52840_xiao.build_flags}
|
|||||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||||
-D SX126X_CURRENT_LIMIT=140
|
-D SX126X_CURRENT_LIMIT=140
|
||||||
-D SX126X_RX_BOOSTED_GAIN=1
|
-D SX126X_RX_BOOSTED_GAIN=1
|
||||||
-D PIN_WIRE_SCL=6
|
-D PIN_WIRE_SCL=D6
|
||||||
-D PIN_WIRE_SDA=7
|
-D PIN_WIRE_SDA=D7
|
||||||
-D ENV_INCLUDE_AHTX0=1
|
build_src_filter = ${nrf52_base.build_src_filter}
|
||||||
-D ENV_INCLUDE_BME280=1
|
|
||||||
-D ENV_INCLUDE_INA3221=1
|
|
||||||
-D ENV_INCLUDE_INA219=1
|
|
||||||
build_src_filter = ${nrf52840_xiao.build_src_filter}
|
|
||||||
+<helpers/*.cpp>
|
+<helpers/*.cpp>
|
||||||
+<helpers/sensors>
|
+<helpers/sensors>
|
||||||
+<../variants/xiao_nrf52>
|
+<../variants/xiao_nrf52>
|
||||||
debug_tool = jlink
|
debug_tool = jlink
|
||||||
upload_protocol = nrfutil
|
upload_protocol = nrfutil
|
||||||
|
lib_deps = ${nrf52_base.lib_deps}
|
||||||
|
${sensor_base.lib_deps}
|
||||||
|
|
||||||
[env:Xiao_nrf52_companion_radio_ble]
|
[env:Xiao_nrf52_companion_radio_ble]
|
||||||
extends = Xiao_nrf52
|
extends = Xiao_nrf52
|
||||||
@@ -94,12 +74,6 @@ lib_deps =
|
|||||||
${Xiao_nrf52.lib_deps}
|
${Xiao_nrf52.lib_deps}
|
||||||
densaugeo/base64 @ ~1.4.0
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
|
||||||
[env:Xiao_nrf52_alt_pinout_companion_radio_ble]
|
|
||||||
extends = env:Xiao_nrf52_companion_radio_ble
|
|
||||||
build_flags =
|
|
||||||
${env:Xiao_nrf52_companion_radio_ble.build_flags}
|
|
||||||
-D SX1262_XIAO_S3_VARIANT
|
|
||||||
|
|
||||||
[env:Xiao_nrf52_repeater]
|
[env:Xiao_nrf52_repeater]
|
||||||
extends = Xiao_nrf52
|
extends = Xiao_nrf52
|
||||||
build_flags =
|
build_flags =
|
||||||
@@ -114,12 +88,6 @@ build_flags =
|
|||||||
build_src_filter = ${Xiao_nrf52.build_src_filter}
|
build_src_filter = ${Xiao_nrf52.build_src_filter}
|
||||||
+<../examples/simple_repeater/*.cpp>
|
+<../examples/simple_repeater/*.cpp>
|
||||||
|
|
||||||
[env:Xiao_nrf52_alt_pinout_repeater]
|
|
||||||
extends = env:Xiao_nrf52_repeater
|
|
||||||
build_flags =
|
|
||||||
${env:Xiao_nrf52_repeater.build_flags}
|
|
||||||
-D SX1262_XIAO_S3_VARIANT
|
|
||||||
|
|
||||||
[env:Xiao_nrf52_room_server]
|
[env:Xiao_nrf52_room_server]
|
||||||
extends = Xiao_nrf52
|
extends = Xiao_nrf52
|
||||||
build_flags =
|
build_flags =
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ WRAPPER_CLASS radio_driver(radio, board);
|
|||||||
|
|
||||||
VolatileRTCClock fallback_clock;
|
VolatileRTCClock fallback_clock;
|
||||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||||
|
|
||||||
EnvironmentSensorManager sensors;
|
EnvironmentSensorManager sensors;
|
||||||
|
|
||||||
bool radio_init() {
|
bool radio_init() {
|
||||||
rtc_clock.begin(Wire);
|
rtc_clock.begin(Wire);
|
||||||
|
|
||||||
return radio.std_init(&SPI);
|
return radio.std_init(&SPI);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t radio_get_rng_seed() {
|
uint32_t radio_get_rng_seed() {
|
||||||
@@ -35,5 +36,5 @@ void radio_set_tx_power(uint8_t dbm) {
|
|||||||
|
|
||||||
mesh::LocalIdentity radio_new_identity() {
|
mesh::LocalIdentity radio_new_identity() {
|
||||||
RadioNoiseListener rng(radio);
|
RadioNoiseListener rng(radio);
|
||||||
return mesh::LocalIdentity(&rng); // create new random identity
|
return mesh::LocalIdentity(&rng); // create new random identity
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,86 +1,85 @@
|
|||||||
#include "variant.h"
|
#include "variant.h"
|
||||||
|
|
||||||
|
#include "nrf.h"
|
||||||
#include "wiring_constants.h"
|
#include "wiring_constants.h"
|
||||||
#include "wiring_digital.h"
|
#include "wiring_digital.h"
|
||||||
#include "nrf.h"
|
|
||||||
|
|
||||||
const uint32_t g_ADigitalPinMap[] =
|
const uint32_t g_ADigitalPinMap[] = {
|
||||||
{
|
// D0 .. D10
|
||||||
// D0 .. D10
|
2, // D0 is P0.02 (A0)
|
||||||
2, // D0 is P0.02 (A0)
|
3, // D1 is P0.03 (A1)
|
||||||
3, // D1 is P0.03 (A1)
|
28, // D2 is P0.28 (A2)
|
||||||
28, // D2 is P0.28 (A2)
|
29, // D3 is P0.29 (A3)
|
||||||
29, // D3 is P0.29 (A3)
|
4, // D4 is P0.04 (A4,SDA)
|
||||||
4, // D4 is P0.04 (A4,SDA)
|
5, // D5 is P0.05 (A5,SCL)
|
||||||
5, // D5 is P0.05 (A5,SCL)
|
43, // D6 is P1.11 (TX)
|
||||||
43, // D6 is P1.11 (TX)
|
44, // D7 is P1.12 (RX)
|
||||||
44, // D7 is P1.12 (RX)
|
45, // D8 is P1.13 (SCK)
|
||||||
45, // D8 is P1.13 (SCK)
|
46, // D9 is P1.14 (MISO)
|
||||||
46, // D9 is P1.14 (MISO)
|
47, // D10 is P1.15 (MOSI)
|
||||||
47, // D10 is P1.15 (MOSI)
|
|
||||||
|
|
||||||
// LEDs
|
// LEDs
|
||||||
26, // D11 is P0.26 (LED RED)
|
26, // D11 is P0.26 (LED RED)
|
||||||
6, // D12 is P0.06 (LED BLUE)
|
6, // D12 is P0.06 (LED BLUE)
|
||||||
30, // D13 is P0.30 (LED GREEN)
|
30, // D13 is P0.30 (LED GREEN)
|
||||||
14, // D14 is P0.14 (READ_BAT)
|
14, // D14 is P0.14 (READ_BAT)
|
||||||
|
|
||||||
// LSM6DS3TR
|
// LSM6DS3TR
|
||||||
40, // D15 is P1.08 (6D_PWR)
|
40, // D15 is P1.08 (6D_PWR)
|
||||||
27, // D16 is P0.27 (6D_I2C_SCL)
|
27, // D16 is P0.27 (6D_I2C_SCL)
|
||||||
7, // D17 is P0.07 (6D_I2C_SDA)
|
7, // D17 is P0.07 (6D_I2C_SDA)
|
||||||
11, // D18 is P0.11 (6D_INT1)
|
11, // D18 is P0.11 (6D_INT1)
|
||||||
|
|
||||||
// MIC
|
// MIC
|
||||||
42, // D19 is P1.10 (MIC_PWR)
|
42, // D19 is P1.10 (MIC_PWR)
|
||||||
32, // D20 is P1.00 (PDM_CLK)
|
32, // D20 is P1.00 (PDM_CLK)
|
||||||
16, // D21 is P0.16 (PDM_DATA)
|
16, // D21 is P0.16 (PDM_DATA)
|
||||||
|
|
||||||
// BQ25100
|
// BQ25100
|
||||||
13, // D22 is P0.13 (HICHG)
|
13, // D22 is P0.13 (HICHG)
|
||||||
17, // D23 is P0.17 (~CHG)
|
17, // D23 is P0.17 (~CHG)
|
||||||
|
|
||||||
//
|
//
|
||||||
21, // D24 is P0.21 (QSPI_SCK)
|
21, // D24 is P0.21 (QSPI_SCK)
|
||||||
25, // D25 is P0.25 (QSPI_CSN)
|
25, // D25 is P0.25 (QSPI_CSN)
|
||||||
20, // D26 is P0.20 (QSPI_SIO_0 DI)
|
20, // D26 is P0.20 (QSPI_SIO_0 DI)
|
||||||
24, // D27 is P0.24 (QSPI_SIO_1 DO)
|
24, // D27 is P0.24 (QSPI_SIO_1 DO)
|
||||||
22, // D28 is P0.22 (QSPI_SIO_2 WP)
|
22, // D28 is P0.22 (QSPI_SIO_2 WP)
|
||||||
23, // D29 is P0.23 (QSPI_SIO_3 HOLD)
|
23, // D29 is P0.23 (QSPI_SIO_3 HOLD)
|
||||||
|
|
||||||
// NFC
|
// NFC
|
||||||
9, // D30 is P0.09 (NFC1)
|
9, // D30 is P0.09 (NFC1)
|
||||||
10, // D31 is P0.10 (NFC2)
|
10, // D31 is P0.10 (NFC2)
|
||||||
|
|
||||||
// VBAT
|
// VBAT
|
||||||
31, // D32 is P0.31 (VBAT)
|
31, // D32 is P0.31 (VBAT)
|
||||||
};
|
};
|
||||||
|
|
||||||
void initVariant()
|
void initVariant() {
|
||||||
{
|
// Disable reading of the BAT voltage.
|
||||||
// Disable reading of the BAT voltage.
|
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
|
||||||
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
|
pinMode(VBAT_ENABLE, OUTPUT);
|
||||||
pinMode(VBAT_ENABLE, OUTPUT);
|
// digitalWrite(VBAT_ENABLE, HIGH);
|
||||||
//digitalWrite(VBAT_ENABLE, HIGH);
|
// This was taken from Seeed github butis not coherent with the doc,
|
||||||
// This was taken from Seeed github butis not coherent with the doc,
|
// VBAT_ENABLE should be kept to LOW to protect P0.14, (1500/500)*(4.2-3.3)+3.3 = 3.9V > 3.6V
|
||||||
// VBAT_ENABLE should be kept to LOW to protect P0.14, (1500/500)*(4.2-3.3)+3.3 = 3.9V > 3.6V
|
// This induces a 3mA current in the resistors :( but it's better than burning the nrf
|
||||||
// This induces a 3mA current in the resistors :( but it's better than burning the nrf
|
digitalWrite(VBAT_ENABLE, LOW);
|
||||||
digitalWrite(VBAT_ENABLE, LOW);
|
|
||||||
|
|
||||||
// Low charging current (50mA)
|
// Low charging current (50mA)
|
||||||
// https://wiki.seeedstudio.com/XIAO_BLE#battery-charging-current
|
// https://wiki.seeedstudio.com/XIAO_BLE#battery-charging-current
|
||||||
//pinMode(PIN_CHARGING_CURRENT, INPUT);
|
// pinMode(PIN_CHARGING_CURRENT, INPUT);
|
||||||
|
|
||||||
// High charging current (100mA)
|
// High charging current (100mA)
|
||||||
pinMode(PIN_CHARGING_CURRENT, OUTPUT);
|
pinMode(PIN_CHARGING_CURRENT, OUTPUT);
|
||||||
digitalWrite(PIN_CHARGING_CURRENT, LOW);
|
digitalWrite(PIN_CHARGING_CURRENT, LOW);
|
||||||
|
|
||||||
pinMode(PIN_QSPI_CS, OUTPUT);
|
pinMode(PIN_QSPI_CS, OUTPUT);
|
||||||
digitalWrite(PIN_QSPI_CS, HIGH);
|
digitalWrite(PIN_QSPI_CS, HIGH);
|
||||||
|
|
||||||
pinMode(LED_RED, OUTPUT);
|
pinMode(LED_RED, OUTPUT);
|
||||||
digitalWrite(LED_RED, HIGH);
|
digitalWrite(LED_RED, HIGH);
|
||||||
pinMode(LED_GREEN, OUTPUT);
|
pinMode(LED_GREEN, OUTPUT);
|
||||||
digitalWrite(LED_GREEN, HIGH);
|
digitalWrite(LED_GREEN, HIGH);
|
||||||
pinMode(LED_BLUE, OUTPUT);
|
pinMode(LED_BLUE, OUTPUT);
|
||||||
digitalWrite(LED_BLUE, HIGH);
|
digitalWrite(LED_BLUE, HIGH);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,8 +113,8 @@ static const uint8_t A5 = PIN_A5;
|
|||||||
// #define PIN_WIRE_SDA (17) // 4 and 5 are used for the sx1262 !
|
// #define PIN_WIRE_SDA (17) // 4 and 5 are used for the sx1262 !
|
||||||
// #define PIN_WIRE_SCL (16) // use WIRE1_SDA
|
// #define PIN_WIRE_SCL (16) // use WIRE1_SDA
|
||||||
|
|
||||||
static const uint8_t SDA = PIN_WIRE_SDA;
|
// static const uint8_t SDA = PIN_WIRE_SDA;
|
||||||
static const uint8_t SCL = PIN_WIRE_SCL;
|
// static const uint8_t SCL = PIN_WIRE_SCL;
|
||||||
|
|
||||||
//#define PIN_WIRE1_SDA (17)
|
//#define PIN_WIRE1_SDA (17)
|
||||||
//#define PIN_WIRE1_SCL (16)
|
//#define PIN_WIRE1_SCL (16)
|
||||||
|
|||||||
Reference in New Issue
Block a user