From 21564ae4946b8fa01e7031b174a16371986e762e Mon Sep 17 00:00:00 2001 From: Jeremy O'Brien Date: Wed, 16 Apr 2025 12:07:51 -0400 Subject: [PATCH 001/154] add default.nix/.envrc for automagic platformio dev environment on NixOS --- .envrc | 1 + .gitignore | 1 + default.nix | 10 ++++++++++ 3 files changed, 12 insertions(+) create mode 100644 .envrc create mode 100644 default.nix diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..1d953f4b --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use nix diff --git a/.gitignore b/.gitignore index a66b3e93..9b9580a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.direnv .pio .vscode/.browse.c_cpp.db* .vscode/c_cpp_properties.json diff --git a/default.nix b/default.nix new file mode 100644 index 00000000..828c0ee8 --- /dev/null +++ b/default.nix @@ -0,0 +1,10 @@ +{ pkgs ? import {} }: +let +in + pkgs.mkShell { + buildInputs = [ + pkgs.platformio + # optional: needed as a programmer i.e. for esp32 + pkgs.avrdude + ]; +} From c34dd2a40c4a293ddbe6f93228ef12e4c1b81362 Mon Sep 17 00:00:00 2001 From: Jacob Quatier Date: Sun, 20 Apr 2025 17:40:58 -0700 Subject: [PATCH 002/154] UI: battery indicator, boot screen, radio settings --- examples/companion_radio/NodePrefs.h | 22 +++++++ examples/companion_radio/UITask.cpp | 85 ++++++++++++++++++++------ examples/companion_radio/UITask.h | 6 +- examples/companion_radio/main.cpp | 22 ++----- examples/simple_repeater/UITask.cpp | 55 ++++++++++++----- examples/simple_repeater/UITask.h | 5 +- examples/simple_repeater/main.cpp | 5 +- examples/simple_room_server/UITask.cpp | 55 ++++++++++++----- examples/simple_room_server/UITask.h | 5 +- examples/simple_room_server/main.cpp | 5 +- 10 files changed, 195 insertions(+), 70 deletions(-) create mode 100644 examples/companion_radio/NodePrefs.h diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h new file mode 100644 index 00000000..691eec42 --- /dev/null +++ b/examples/companion_radio/NodePrefs.h @@ -0,0 +1,22 @@ +#ifndef NODE_PREFS_H +#define NODE_PREFS_H + +#include // For uint8_t, uint32_t + +struct NodePrefs { // persisted to file + float airtime_factor; + char node_name[32]; + double node_lat, node_lon; + float freq; + uint8_t sf; + uint8_t cr; + uint8_t reserved1; + uint8_t manual_add_contacts; + float bw; + uint8_t tx_power_dbm; + uint8_t unused[3]; + float rx_delay_base; + uint32_t ble_pin; +}; + +#endif // NODE_PREFS_H \ No newline at end of file diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index 01770363..f605e6de 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -1,8 +1,10 @@ #include "UITask.h" #include #include +#include "NodePrefs.h" -#define AUTO_OFF_MILLIS 15000 // 15 seconds +#define AUTO_OFF_MILLIS 15000 // 15 seconds +#define BOOT_SCREEN_MILLIS 4000 // 4 seconds #ifndef USER_BTN_PRESSED #define USER_BTN_PRESSED LOW @@ -25,11 +27,11 @@ static const uint8_t meshcore_logo [] PROGMEM = { 0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8, }; -void UITask::begin(DisplayDriver* display, const char* node_name, const char* build_date, const char* firmware_version, uint32_t pin_code) { +void UITask::begin(DisplayDriver* display, NodePrefs* node_prefs, const char* build_date, const char* firmware_version, uint32_t pin_code) { _display = display; _auto_off = millis() + AUTO_OFF_MILLIS; clearMsgPreview(); - _node_name = node_name; + _node_prefs = node_prefs; _pin_code = pin_code; if (_display != NULL) { _display->turnOn(); @@ -81,16 +83,42 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i } } +void renderBatteryIndicator(DisplayDriver* _display, uint16_t batteryMilliVolts) { + // Convert millivolts to percentage + const int minMilliVolts = 3000; // Minimum voltage (e.g., 3.0V) + const int maxMilliVolts = 4200; // Maximum voltage (e.g., 4.2V) + int batteryPercentage = ((batteryMilliVolts - minMilliVolts) * 100) / (maxMilliVolts - minMilliVolts); + if (batteryPercentage < 0) batteryPercentage = 0; // Clamp to 0% + if (batteryPercentage > 100) batteryPercentage = 100; // Clamp to 100% + + // battery icon + int iconWidth = 24; + int iconHeight = 12; + int iconX = _display->width() - iconWidth - 5; // Position the icon near the top-right corner + int iconY = 0; + _display->setColor(DisplayDriver::GREEN); + + // battery outline + _display->drawRect(iconX, iconY, iconWidth, iconHeight); + + // battery "cap" + _display->fillRect(iconX + iconWidth, iconY + (iconHeight / 4), 3, iconHeight / 2); + + // fill the battery based on the percentage + int fillWidth = (batteryPercentage * (iconWidth - 2)) / 100; + _display->fillRect(iconX + 1, iconY + 1, fillWidth, iconHeight - 2); +} + void UITask::renderCurrScreen() { if (_display == NULL) return; // assert() ?? char tmp[80]; - if (_origin[0] && _msg[0]) { + if (_origin[0] && _msg[0]) { // message preview // render message preview _display->setCursor(0, 0); _display->setTextSize(1); _display->setColor(DisplayDriver::GREEN); - _display->print(_node_name); + _display->print(_node_prefs->node_name); _display->setCursor(0, 12); _display->setColor(DisplayDriver::YELLOW); @@ -104,23 +132,41 @@ void UITask::renderCurrScreen() { _display->setColor(DisplayDriver::ORANGE); sprintf(tmp, "%d", _msgcount); _display->print(tmp); - } else { - // render 'home' screen + } else if (millis() < BOOT_SCREEN_MILLIS) { // boot screen + // meshcore logo _display->setColor(DisplayDriver::BLUE); - _display->drawXbm(0, 0, meshcore_logo, 128, 13); - _display->setCursor(0, 20); - _display->setTextSize(1); + int logoWidth = 128; + _display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); + // version info _display->setColor(DisplayDriver::LIGHT); - _display->print(_node_name); - - _display->setCursor(0, 32); + _display->setTextSize(1); + int textWidth = strlen(_version_info) * 6; // Assuming each character is 6 pixels wide + _display->setCursor((_display->width() - textWidth) / 2, 22); _display->print(_version_info); + } else { // home screen + // node name + _display->setCursor(0, 0); + _display->setTextSize(1); + _display->setColor(DisplayDriver::GREEN); + _display->print(_node_prefs->node_name); - if (_connected) { - //_display->printf("freq : %03.2f sf %d\n", _prefs.freq, _prefs.sf); - //_display->printf("bw : %03.2f cr %d\n", _prefs.bw, _prefs.cr); - } else if (_pin_code != 0) { + // battery voltage + renderBatteryIndicator(_display, _board->getBattMilliVolts()); + + // freq / sf + _display->setCursor(0, 20); + _display->setColor(DisplayDriver::YELLOW); + sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf); + _display->print(tmp); + + // bw / cr + _display->setCursor(0, 30); + sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr); + _display->print(tmp); + + // BT pin + if (_pin_code != 0) { _display->setColor(DisplayDriver::RED); _display->setTextSize(2); _display->setCursor(0, 43); @@ -198,6 +244,11 @@ void UITask::loop() { userLedHandler(); if (_display != NULL && _display->isOn()) { + static bool _firstBoot = true; + if(_firstBoot && millis() >= BOOT_SCREEN_MILLIS) { + _need_refresh = true; + _firstBoot = false; + } if (millis() >= _next_refresh && _need_refresh) { _display->startFrame(); renderCurrScreen(); diff --git a/examples/companion_radio/UITask.h b/examples/companion_radio/UITask.h index 9050bc42..5edaa8e2 100644 --- a/examples/companion_radio/UITask.h +++ b/examples/companion_radio/UITask.h @@ -4,13 +4,15 @@ #include #include +#include "NodePrefs.h" + class UITask { DisplayDriver* _display; mesh::MainBoard* _board; unsigned long _next_refresh, _auto_off; bool _connected; uint32_t _pin_code; - const char* _node_name; + NodePrefs* _node_prefs; char _version_info[32]; char _origin[62]; char _msg[80]; @@ -27,7 +29,7 @@ public: _next_refresh = 0; _connected = false; } - void begin(DisplayDriver* display, const char* node_name, const char* build_date, const char* firmware_version, uint32_t pin_code); + void begin(DisplayDriver* display, NodePrefs* node_prefs, const char* build_date, const char* firmware_version, uint32_t pin_code); void setHasConnection(bool connected) { _connected = connected; } bool hasDisplay() const { return _display != NULL; } diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 7438dd89..52108d78 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -12,6 +12,7 @@ #include #include #include +#include "NodePrefs.h" #include #include @@ -181,22 +182,6 @@ static uint32_t _atoi(const char* sp) { #define MAX_SIGN_DATA_LEN (8*1024) // 8K -struct NodePrefs { // persisted to file - float airtime_factor; - char node_name[32]; - double node_lat, node_lon; - float freq; - uint8_t sf; - uint8_t cr; - uint8_t reserved1; - uint8_t manual_add_contacts; - float bw; - uint8_t tx_power_dbm; - uint8_t unused[3]; - float rx_delay_base; - uint32_t ble_pin; -}; - class MyMesh : public BaseChatMesh { FILESYSTEM* _fs; IdentityStore* _identity_store; @@ -837,6 +822,9 @@ public: } const char* getNodeName() { return _prefs.node_name; } + NodePrefs* getNodePrefs() { + return &_prefs; + } uint32_t getBLEPin() { return _active_ble_pin; } void startInterface(BaseSerialInterface& serial) { @@ -1529,7 +1517,7 @@ void setup() { #endif #ifdef HAS_UI - ui_task.begin(disp, the_mesh.getNodeName(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION, the_mesh.getBLEPin()); + ui_task.begin(disp, the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION, the_mesh.getBLEPin()); #endif } diff --git a/examples/simple_repeater/UITask.cpp b/examples/simple_repeater/UITask.cpp index 6fff675e..39ca1c81 100644 --- a/examples/simple_repeater/UITask.cpp +++ b/examples/simple_repeater/UITask.cpp @@ -1,7 +1,9 @@ #include "UITask.h" #include +#include -#define AUTO_OFF_MILLIS 20000 // 20 seconds +#define AUTO_OFF_MILLIS 20000 // 20 seconds +#define BOOT_SCREEN_MILLIS 4000 // 4 seconds // 'meshcore', 128x13px static const uint8_t meshcore_logo [] PROGMEM = { @@ -20,10 +22,10 @@ static const uint8_t meshcore_logo [] PROGMEM = { 0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8, }; -void UITask::begin(const char* node_name, const char* build_date, const char* firmware_version) { +void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version) { _prevBtnState = HIGH; _auto_off = millis() + AUTO_OFF_MILLIS; - _node_name = node_name; + _node_prefs = node_prefs; _display->turnOn(); // strip off dash and commit hash by changing dash to null terminator @@ -39,18 +41,43 @@ void UITask::begin(const char* node_name, const char* build_date, const char* fi } void UITask::renderCurrScreen() { - // render 'home' screen - _display->drawXbm(0, 0, meshcore_logo, 128, 13); - _display->setCursor(0, 20); - _display->setTextSize(1); - _display->print(_node_name); + char tmp[80]; + if (millis() < BOOT_SCREEN_MILLIS) { // boot screen + // meshcore logo + _display->setColor(DisplayDriver::BLUE); + int logoWidth = 128; + _display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); - _display->setCursor(0, 32); - _display->print(_version_info); - _display->setCursor(0, 43); - _display->print("< Repeater >"); - //_display->printf("freq : %03.2f sf %d\n", _prefs.freq, _prefs.sf); - //_display->printf("bw : %03.2f cr %d\n", _prefs.bw, _prefs.cr); + // version info + _display->setColor(DisplayDriver::LIGHT); + _display->setTextSize(1); + int versionWidth = strlen(_version_info) * 6; + _display->setCursor((_display->width() - versionWidth) / 2, 22); + _display->print(_version_info); + + // node type + const char* node_type = "< Repeater >"; + int nodeTypeWidth = strlen(node_type) * 6; + _display->setCursor((_display->width() - nodeTypeWidth) / 2, 35); + _display->print(node_type); + } else { // home screen + // node name + _display->setCursor(0, 0); + _display->setTextSize(1); + _display->setColor(DisplayDriver::GREEN); + _display->print(_node_prefs->node_name); + + // freq / sf + _display->setCursor(0, 20); + _display->setColor(DisplayDriver::YELLOW); + sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf); + _display->print(tmp); + + // bw / cr + _display->setCursor(0, 30); + sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr); + _display->print(tmp); + } } void UITask::loop() { diff --git a/examples/simple_repeater/UITask.h b/examples/simple_repeater/UITask.h index 13005957..a27259f1 100644 --- a/examples/simple_repeater/UITask.h +++ b/examples/simple_repeater/UITask.h @@ -1,18 +1,19 @@ #pragma once #include +#include class UITask { DisplayDriver* _display; unsigned long _next_read, _next_refresh, _auto_off; int _prevBtnState; - const char* _node_name; + NodePrefs* _node_prefs; char _version_info[32]; void renderCurrScreen(); public: UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; } - void begin(const char* node_name, const char* build_date, const char* firmware_version); + void begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version); void loop(); }; \ No newline at end of file diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 1d8cac6e..07ea25f2 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -516,6 +516,9 @@ public: const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; } const char* getRole() override { return FIRMWARE_ROLE; } const char* getNodeName() { return _prefs.node_name; } + NodePrefs* getNodePrefs() { + return &_prefs; + } void savePrefs() override { _cli.savePrefs(_fs); @@ -658,7 +661,7 @@ void setup() { the_mesh.begin(fs); #ifdef DISPLAY_CLASS - ui_task.begin(the_mesh.getNodeName(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); + ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); #endif // send out initial Advertisement to the mesh diff --git a/examples/simple_room_server/UITask.cpp b/examples/simple_room_server/UITask.cpp index eb7be78e..2f559111 100644 --- a/examples/simple_room_server/UITask.cpp +++ b/examples/simple_room_server/UITask.cpp @@ -1,7 +1,9 @@ #include "UITask.h" #include +#include -#define AUTO_OFF_MILLIS 20000 // 20 seconds +#define AUTO_OFF_MILLIS 20000 // 20 seconds +#define BOOT_SCREEN_MILLIS 4000 // 4 seconds // 'meshcore', 128x13px static const uint8_t meshcore_logo [] PROGMEM = { @@ -20,10 +22,10 @@ static const uint8_t meshcore_logo [] PROGMEM = { 0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8, }; -void UITask::begin(const char* node_name, const char* build_date, const char* firmware_version) { +void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version) { _prevBtnState = HIGH; _auto_off = millis() + AUTO_OFF_MILLIS; - _node_name = node_name; + _node_prefs = node_prefs; _display->turnOn(); // strip off dash and commit hash by changing dash to null terminator @@ -39,18 +41,43 @@ void UITask::begin(const char* node_name, const char* build_date, const char* fi } void UITask::renderCurrScreen() { - // render 'home' screen - _display->drawXbm(0, 0, meshcore_logo, 128, 13); - _display->setCursor(0, 20); - _display->setTextSize(1); - _display->print(_node_name); + char tmp[80]; + if (millis() < BOOT_SCREEN_MILLIS) { // boot screen + // meshcore logo + _display->setColor(DisplayDriver::BLUE); + int logoWidth = 128; + _display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); - _display->setCursor(0, 32); - _display->print(_version_info); - _display->setCursor(0, 43); - _display->print("< Room Server >"); - //_display->printf("freq : %03.2f sf %d\n", _prefs.freq, _prefs.sf); - //_display->printf("bw : %03.2f cr %d\n", _prefs.bw, _prefs.cr); + // version info + _display->setColor(DisplayDriver::LIGHT); + _display->setTextSize(1); + int versionWidth = strlen(_version_info) * 6; + _display->setCursor((_display->width() - versionWidth) / 2, 22); + _display->print(_version_info); + + // node type + const char* node_type = "< Room Server >"; + int nodeTypeWidth = strlen(node_type) * 6; + _display->setCursor((_display->width() - nodeTypeWidth) / 2, 35); + _display->print(node_type); + } else { // home screen + // node name + _display->setCursor(0, 0); + _display->setTextSize(1); + _display->setColor(DisplayDriver::GREEN); + _display->print(_node_prefs->node_name); + + // freq / sf + _display->setCursor(0, 20); + _display->setColor(DisplayDriver::YELLOW); + sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf); + _display->print(tmp); + + // bw / cr + _display->setCursor(0, 30); + sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr); + _display->print(tmp); + } } void UITask::loop() { diff --git a/examples/simple_room_server/UITask.h b/examples/simple_room_server/UITask.h index 13005957..a27259f1 100644 --- a/examples/simple_room_server/UITask.h +++ b/examples/simple_room_server/UITask.h @@ -1,18 +1,19 @@ #pragma once #include +#include class UITask { DisplayDriver* _display; unsigned long _next_read, _next_refresh, _auto_off; int _prevBtnState; - const char* _node_name; + NodePrefs* _node_prefs; char _version_info[32]; void renderCurrScreen(); public: UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; } - void begin(const char* node_name, const char* build_date, const char* firmware_version); + void begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version); void loop(); }; \ No newline at end of file diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 3afe76c8..2a20e88e 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -717,6 +717,9 @@ public: const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; } const char* getRole() override { return FIRMWARE_ROLE; } const char* getNodeName() { return _prefs.node_name; } + NodePrefs* getNodePrefs() { + return &_prefs; + } void savePrefs() override { _cli.savePrefs(_fs); @@ -899,7 +902,7 @@ void setup() { the_mesh.begin(fs); #ifdef DISPLAY_CLASS - ui_task.begin(the_mesh.getNodeName(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); + ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); #endif // send out initial Advertisement to the mesh From 7d7692a13be32b88b9506e940a58e779e8aef95f Mon Sep 17 00:00:00 2001 From: JQ Date: Sun, 20 Apr 2025 19:08:00 -0700 Subject: [PATCH 003/154] adding connected check --- examples/companion_radio/UITask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index f605e6de..a7b51d92 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -166,7 +166,7 @@ void UITask::renderCurrScreen() { _display->print(tmp); // BT pin - if (_pin_code != 0) { + if (!_connected && _pin_code != 0) { _display->setColor(DisplayDriver::RED); _display->setTextSize(2); _display->setCursor(0, 43); From 99246e6b6f15bbba9adf7ec705f8224c3160e15e Mon Sep 17 00:00:00 2001 From: AeroXuk Date: Mon, 21 Apr 2025 21:17:03 +0100 Subject: [PATCH 004/154] Added Pi PIcoW support in the following modes: - Companion Radio over USB Serial - Repeater - Room Server - Terminal Chat --- examples/companion_radio/main.cpp | 69 ++++++++++++++++++ examples/simple_repeater/main.cpp | 16 ++++- examples/simple_room_server/main.cpp | 14 ++++ examples/simple_secure_chat/main.cpp | 17 +++++ platformio.ini | 8 +++ src/helpers/CommonCLI.cpp | 6 ++ src/helpers/IdentityStore.cpp | 12 ++++ src/helpers/IdentityStore.h | 2 +- src/helpers/rp2040/PicoWBoard.cpp | 42 +++++++++++ src/helpers/rp2040/PicoWBoard.h | 64 +++++++++++++++++ variants/picow/platformio.ini | 103 +++++++++++++++++++++++++++ variants/picow/target.cpp | 73 +++++++++++++++++++ variants/picow/target.h | 18 +++++ 13 files changed, 442 insertions(+), 2 deletions(-) create mode 100644 src/helpers/rp2040/PicoWBoard.cpp create mode 100644 src/helpers/rp2040/PicoWBoard.h create mode 100644 variants/picow/platformio.ini create mode 100644 variants/picow/target.cpp create mode 100644 variants/picow/target.h diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 30e492ce..67c66487 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -3,6 +3,8 @@ #if defined(NRF52_PLATFORM) #include +#elif defined(RP2040_PLATFORM) + #include #elif defined(ESP32) #include #endif @@ -242,7 +244,11 @@ class MyMesh : public BaseChatMesh { void loadContacts() { if (_fs->exists("/contacts3")) { + #if defined(RP2040_PLATFORM) + File file = _fs->open("/contacts3", "r"); + #else File file = _fs->open("/contacts3"); + #endif if (file) { bool full = false; while (!full) { @@ -277,6 +283,8 @@ class MyMesh : public BaseChatMesh { #if defined(NRF52_PLATFORM) File file = _fs->open("/contacts3", FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } +#elif defined(RP2040_PLATFORM) + File file = _fs->open("/contacts3", "w+"); #else File file = _fs->open("/contacts3", "w", true); #endif @@ -307,7 +315,11 @@ class MyMesh : public BaseChatMesh { void loadChannels() { if (_fs->exists("/channels2")) { + #if defined(RP2040_PLATFORM) + File file = _fs->open("/channels2", "r"); + #else File file = _fs->open("/channels2"); + #endif if (file) { bool full = false; uint8_t channel_idx = 0; @@ -336,6 +348,8 @@ class MyMesh : public BaseChatMesh { #if defined(NRF52_PLATFORM) File file = _fs->open("/channels2", FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } + #elif defined(RP2040_PLATFORM) + File file = _fs->open("/channels2", "w+"); #else File file = _fs->open("/channels2", "w", true); #endif @@ -366,7 +380,11 @@ class MyMesh : public BaseChatMesh { sprintf(path, "/bl/%s", fname); if (_fs->exists(path)) { + #if defined(RP2040_PLATFORM) + File f = _fs->open(path, "r"); + #else File f = _fs->open(path); + #endif if (f) { int len = f.read(dest_buf, 255); // currently MAX 255 byte blob len supported!! f.close(); @@ -387,6 +405,8 @@ class MyMesh : public BaseChatMesh { #if defined(NRF52_PLATFORM) File f = _fs->open(path, FILE_O_WRITE); if (f) { f.seek(0); f.truncate(); } + #elif defined(RP2040_PLATFORM) + File f = _fs->open(path, "w+"); #else File f = _fs->open(path, "w", true); #endif @@ -733,7 +753,11 @@ public: } void loadPrefsInt(const char* filename) { +#if defined(RP2040_PLATFORM) + File file = _fs->open(filename, "r"); +#else File file = _fs->open(filename); +#endif if (file) { uint8_t pad[8]; @@ -831,6 +855,8 @@ public: #if defined(NRF52_PLATFORM) File file = _fs->open("/new_prefs", FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } +#elif defined(RP2040_PLATFORM) + File file = _fs->open("/new_prefs", "w+"); #else File file = _fs->open("/new_prefs", "w", true); #endif @@ -1416,6 +1442,24 @@ public: #include ArduinoSerialInterface serial_interface; #endif +#elif defined(RP2040_PLATFORM) + //#ifdef WIFI_SSID + // #include + // SerialWifiInterface serial_interface; + // #ifndef TCP_PORT + // #define TCP_PORT 5000 + // #endif + // #elif defined(BLE_PIN_CODE) + // #include + // SerialBLEInterface serial_interface; + #if defined(SERIAL_RX) + #include + ArduinoSerialInterface serial_interface; + HardwareSerial companion_serial(1); + #else + #include + ArduinoSerialInterface serial_interface; + #endif #elif defined(NRF52_PLATFORM) #ifdef BLE_PIN_CODE #include @@ -1475,6 +1519,31 @@ void setup() { serial_interface.begin(Serial); #endif the_mesh.startInterface(serial_interface); +#elif defined(RP2040_PLATFORM) + LittleFS.begin(); + the_mesh.begin(LittleFS, + #ifdef HAS_UI + disp != NULL + #else + false + #endif + ); + + //#ifdef WIFI_SSID + // WiFi.begin(WIFI_SSID, WIFI_PWD); + // serial_interface.begin(TCP_PORT); + // #elif defined(BLE_PIN_CODE) + // char dev_name[32+16]; + // sprintf(dev_name, "%s%s", BLE_NAME_PREFIX, the_mesh.getNodeName()); + // serial_interface.begin(dev_name, the_mesh.getBLEPin()); + #if defined(SERIAL_RX) + companion_serial.setPins(SERIAL_RX, SERIAL_TX); + companion_serial.begin(115200); + serial_interface.begin(companion_serial); + #else + serial_interface.begin(Serial); + #endif + the_mesh.startInterface(serial_interface); #elif defined(ESP32) SPIFFS.begin(true); the_mesh.begin(SPIFFS, diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 1d8cac6e..cc4f299c 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -3,6 +3,8 @@ #if defined(NRF52_PLATFORM) #include +#elif defined(RP2040_PLATFORM) + #include #elif defined(ESP32) #include #endif @@ -180,6 +182,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { File openAppend(const char* fname) { #if defined(NRF52_PLATFORM) return _fs->open(fname, FILE_O_WRITE); + #elif defined(RP2040_PLATFORM) + return _fs->open(fname, "a"); #else return _fs->open(fname, "a", true); #endif @@ -524,10 +528,12 @@ public: bool formatFileSystem() override { #if defined(NRF52_PLATFORM) return InternalFS.format(); +#elif defined(RP2040_PLATFORM) + return LittleFS.format(); #elif defined(ESP32) return SPIFFS.format(); #else - #error "need to implement file system erase" + #error "need to implement file system erase" return false; #endif } @@ -563,7 +569,11 @@ public: } void dumpLogFile() override { +#if defined(RP2040_PLATFORM) + File f = _fs->open(PACKET_LOG_FILE, "r"); +#else File f = _fs->open(PACKET_LOG_FILE); +#endif if (f) { while (f.available()) { int c = f.read(); @@ -637,6 +647,10 @@ void setup() { SPIFFS.begin(true); fs = &SPIFFS; IdentityStore store(SPIFFS, "/identity"); +#elif defined(RP2040_PLATFORM) + LittleFS.begin(); + fs = &LittleFS; + IdentityStore store(LittleFS, "/identity"); #else #error "need to define filesystem" #endif diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 235638f8..b304b209 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -3,6 +3,8 @@ #if defined(NRF52_PLATFORM) #include +#elif defined(RP2040_PLATFORM) + #include #elif defined(ESP32) #include #endif @@ -259,6 +261,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { File openAppend(const char* fname) { #if defined(NRF52_PLATFORM) return _fs->open(fname, FILE_O_WRITE); + #elif defined(RP2040_PLATFORM) + return _fs->open(fname, "a"); #else return _fs->open(fname, "a", true); #endif @@ -713,6 +717,8 @@ public: bool formatFileSystem() override { #if defined(NRF52_PLATFORM) return InternalFS.format(); + #elif defined(RP2040_PLATFORM) + return LittleFS.format(); #elif defined(ESP32) return SPIFFS.format(); #else @@ -752,7 +758,11 @@ public: } void dumpLogFile() override { + #if defined(RP2040_PLATFORM) + File f = _fs->open(PACKET_LOG_FILE, "r"); + #else File f = _fs->open(PACKET_LOG_FILE); + #endif if (f) { while (f.available()) { int c = f.read(); @@ -863,6 +873,10 @@ void setup() { InternalFS.begin(); fs = &InternalFS; IdentityStore store(InternalFS, ""); +#elif defined(RP2040_PLATFORM) + LittleFS.begin(); + fs = &LittleFS; + IdentityStore store(LittleFS, "/identity"); #elif defined(ESP32) SPIFFS.begin(true); fs = &SPIFFS; diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index f5cbc743..6dde003f 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -3,6 +3,8 @@ #if defined(NRF52_PLATFORM) #include +#elif defined(RP2040_PLATFORM) + #include #elif defined(ESP32) #include #endif @@ -88,7 +90,11 @@ class MyMesh : public BaseChatMesh, ContactVisitor { void loadContacts() { if (_fs->exists("/contacts")) { + #if defined(RP2040_PLATFORM) + File file = _fs->open("/contacts", "r"); + #else File file = _fs->open("/contacts"); + #endif if (file) { bool full = false; while (!full) { @@ -123,6 +129,8 @@ class MyMesh : public BaseChatMesh, ContactVisitor { #if defined(NRF52_PLATFORM) File file = _fs->open("/contacts", FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } +#elif defined(RP2040_PLATFORM) + File file = _fs->open("/contacts", "w+"); #else File file = _fs->open("/contacts", "w", true); #endif @@ -309,7 +317,11 @@ public: // load persisted prefs if (_fs->exists("/node_prefs")) { + #if defined(RP2040_PLATFORM) + File file = _fs->open("/node_prefs", "r"); + #else File file = _fs->open("/node_prefs"); + #endif if (file) { file.read((uint8_t *) &_prefs, sizeof(_prefs)); file.close(); @@ -324,6 +336,8 @@ public: #if defined(NRF52_PLATFORM) File file = _fs->open("/node_prefs", FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } +#elif defined(RP2040_PLATFORM) + File file = _fs->open("/node_prefs", "w+"); #else File file = _fs->open("/node_prefs", "w", true); #endif @@ -545,6 +559,9 @@ void setup() { #if defined(NRF52_PLATFORM) InternalFS.begin(); the_mesh.begin(InternalFS); +#elif defined(RP2040_PLATFORM) + LittleFS.begin(); + the_mesh.begin(LittleFS); #elif defined(ESP32) SPIFFS.begin(true); the_mesh.begin(SPIFFS); diff --git a/platformio.ini b/platformio.ini index 1652f1e3..8c4766a8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -47,6 +47,7 @@ lib_deps = file://arch/esp32/AsyncElegantOTA ; ----------------- NRF52 --------------------- + [nrf52_base] extends = arduino_base platform = nordicnrf52 @@ -60,3 +61,10 @@ lib_deps = ${nrf52_base.lib_deps} rweather/Crypto @ ^0.4.0 https://github.com/adafruit/Adafruit_nRF52_Arduino + +; ----------------- RP2040 --------------------- + +[rp2040_base] +extends = arduino_base +build_flags = ${arduino_base.build_flags} + -D RP2040_PLATFORM diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 2bafd5f9..f3077afb 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -24,7 +24,11 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) { } void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { +#if defined(RP2040_PLATFORM) + File file = fs->open(filename, "r"); +#else File file = fs->open(filename); +#endif if (file) { uint8_t pad[8]; @@ -72,6 +76,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { #if defined(NRF52_PLATFORM) File file = fs->open("/com_prefs", FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } +#elif defined(RP2040_PLATFORM) + File file = fs->open("/com_prefs", "w"); #else File file = fs->open("/com_prefs", "w", true); #endif diff --git a/src/helpers/IdentityStore.cpp b/src/helpers/IdentityStore.cpp index 3b44eb4a..1ecfd3cb 100644 --- a/src/helpers/IdentityStore.cpp +++ b/src/helpers/IdentityStore.cpp @@ -5,7 +5,11 @@ bool IdentityStore::load(const char *name, mesh::LocalIdentity& id) { char filename[40]; sprintf(filename, "%s/%s.id", _dir, name); if (_fs->exists(filename)) { +#if defined(RP2040_PLATFORM) + File file = _fs->open(filename, "r"); +#else File file = _fs->open(filename); +#endif if (file) { loaded = id.readFrom(file); file.close(); @@ -19,7 +23,11 @@ bool IdentityStore::load(const char *name, mesh::LocalIdentity& id, char display char filename[40]; sprintf(filename, "%s/%s.id", _dir, name); if (_fs->exists(filename)) { +#if defined(RP2040_PLATFORM) + File file = _fs->open(filename, "r"); +#else File file = _fs->open(filename); +#endif if (file) { loaded = id.readFrom(file); @@ -41,6 +49,8 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id) { #if defined(NRF52_PLATFORM) File file = _fs->open(filename, FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } +#elif defined(RP2040_PLATFORM) + File file = _fs->open(filename, "w+"); #else File file = _fs->open(filename, "w", true); #endif @@ -61,6 +71,8 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id, const #if defined(NRF52_PLATFORM) File file = _fs->open(filename, FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } +#elif defined(RP2040_PLATFORM) + File file = _fs->open(filename, "w+"); #else File file = _fs->open(filename, "w", true); #endif diff --git a/src/helpers/IdentityStore.h b/src/helpers/IdentityStore.h index 2a5363ea..7607fa5b 100644 --- a/src/helpers/IdentityStore.h +++ b/src/helpers/IdentityStore.h @@ -1,6 +1,6 @@ #pragma once -#if defined(ESP32) +#if defined(ESP32) || defined(RP2040_PLATFORM) #include #define FILESYSTEM fs::FS #elif defined(NRF52_PLATFORM) diff --git a/src/helpers/rp2040/PicoWBoard.cpp b/src/helpers/rp2040/PicoWBoard.cpp new file mode 100644 index 00000000..f345f96d --- /dev/null +++ b/src/helpers/rp2040/PicoWBoard.cpp @@ -0,0 +1,42 @@ +#include +#include "PicoWBoard.h" + +//#include +#include + +//static BLEDfu bledfu; + +static void connect_callback(uint16_t conn_handle) { + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { + (void)conn_handle; + (void)reason; + + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + +void PicoWBoard::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + pinMode(PIN_VBAT_READ, INPUT); +#ifdef PIN_USER_BTN + pinMode(PIN_USER_BTN, INPUT_PULLUP); +#endif + +#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) + Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL); +#endif + + Wire.begin(); + + //pinMode(SX126X_POWER_EN, OUTPUT); + //digitalWrite(SX126X_POWER_EN, HIGH); + delay(10); // give sx1262 some time to power up +} + +bool PicoWBoard::startOTAUpdate(const char* id, char reply[]) { + return false; +} diff --git a/src/helpers/rp2040/PicoWBoard.h b/src/helpers/rp2040/PicoWBoard.h new file mode 100644 index 00000000..b39778d0 --- /dev/null +++ b/src/helpers/rp2040/PicoWBoard.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + +// LoRa radio module pins for PicoW +#define P_LORA_DIO_1 20 +#define P_LORA_NSS 3 +#define P_LORA_RESET 15 +#define P_LORA_BUSY 2 +#define P_LORA_SCLK 10 +#define P_LORA_MISO 12 +#define P_LORA_MOSI 11 +//#define SX126X_POWER_EN ??? // Not Sure + +#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// built-ins +#define PIN_VBAT_READ 26 +#define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) // MT Uses 3.1 +#define PIN_LED_BUILTIN LED_BUILTIN + +class PicoWBoard : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin(); + uint8_t getStartupReason() const override { return startup_reason; } + + void onBeforeTransmit() override { + digitalWrite(LED_BUILTIN, HIGH); // turn TX LED on + } + + void onAfterTransmit() override { + digitalWrite(LED_BUILTIN, LOW); // turn TX LED off + } + + #define BATTERY_SAMPLES 8 + + uint16_t getBattMilliVolts() override { + analogReadResolution(12); + + uint32_t raw = 0; + for (int i = 0; i < BATTERY_SAMPLES; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / BATTERY_SAMPLES; + + return (ADC_MULTIPLIER * raw) / 4096; + } + + const char* getManufacturerName() const override { + return "Pico W"; + } + + void reboot() override { + //NVIC_SystemReset(); + rp2040.reboot(); + } + + bool startOTAUpdate(const char* id, char reply[]) override; +}; diff --git a/variants/picow/platformio.ini b/variants/picow/platformio.ini new file mode 100644 index 00000000..9070099d --- /dev/null +++ b/variants/picow/platformio.ini @@ -0,0 +1,103 @@ +[picow] +extends = rp2040_base +platform = https://github.com/maxgerhardt/platform-raspberrypi.git +board = rpipicow +board_build.core = earlephilhower +board_build.filesystem_size = 0.5m +build_flags = ${rp2040_base.build_flags} + -I variants/picow +; -D PICOW +; -D HW_SPI1_DEVICE + -D SX126X_CURRENT_LIMIT=130 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_RX_BOOSTED_GAIN=1 +build_src_filter = ${rp2040_base.build_src_filter} + + + +<../variants/picow> +lib_deps = ${rp2040_base.lib_deps} + +[env:PicoW_Repeater] +extends = picow +build_flags = ${picow.build_flags} + -D ADVERT_NAME='"PicoW Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${picow.build_src_filter} + +<../examples/simple_repeater> + +[env:PicoW_room_server] +extends = picow +build_flags = ${picow.build_flags} + -D ADVERT_NAME='"Test 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 = ${picow.build_src_filter} + +<../examples/simple_room_server> + +[env:PicoW_companion_radio_usb] +extends = picow +build_flags = ${picow.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 +; -D ENABLE_PRIVATE_KEY_IMPORT=1 +; -D ENABLE_PRIVATE_KEY_EXPORT=1 +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${picow.build_src_filter} + +<../examples/companion_radio> +lib_deps = ${picow.lib_deps} + densaugeo/base64 @ ~1.4.0 + +; [env:PicoW_companion_radio_ble] +; extends = picow +; build_flags = ${picow.build_flags} +; -D MAX_CONTACTS=100 +; -D MAX_GROUP_CHANNELS=8 +; -D BLE_PIN_CODE=123456 +; -D BLE_DEBUG_LOGGING=1 +; ; -D ENABLE_PRIVATE_KEY_IMPORT=1 +; ; -D ENABLE_PRIVATE_KEY_EXPORT=1 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${picow.build_src_filter} +; +<../examples/companion_radio> +; lib_deps = ${picow.lib_deps} +; densaugeo/base64 @ ~1.4.0 + +; [env:PicoW_companion_radio_wifi] +; extends = picow +; build_flags = ${picow.build_flags} +; -D MAX_CONTACTS=100 +; -D MAX_GROUP_CHANNELS=8 +; -D WIFI_DEBUG_LOGGING=1 +; -D WIFI_SSID='"myssid"' +; -D WIFI_PWD='"mypwd"' +; ; -D ENABLE_PRIVATE_KEY_IMPORT=1 +; ; -D ENABLE_PRIVATE_KEY_EXPORT=1 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${picow.build_src_filter} +; +<../examples/companion_radio> +; lib_deps = ${picow.lib_deps} +; densaugeo/base64 @ ~1.4.0 + +[env:PicoW_terminal_chat] +extends = picow +build_flags = ${picow.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=1 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 +build_src_filter = ${picow.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = ${picow.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/picow/target.cpp b/variants/picow/target.cpp new file mode 100644 index 00000000..81a3665b --- /dev/null +++ b/variants/picow/target.cpp @@ -0,0 +1,73 @@ +#include +#include "target.h" +#include + +PicoWBoard board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI1); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +#ifndef LORA_CR + #define LORA_CR 5 +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + +#ifdef SX126X_DIO3_TCXO_VOLTAGE + float tcxo = SX126X_DIO3_TCXO_VOLTAGE; +#else + float tcxo = 1.6f; +#endif + + SPI1.setMISO(P_LORA_MISO); + //SPI1.setCS(P_LORA_NSS); // Setting CS results in freeze + SPI1.setSCK(P_LORA_SCLK); + SPI1.setMOSI(P_LORA_MOSI); + + SPI1.begin(); + 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); + +#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 +} diff --git a/variants/picow/target.h b/variants/picow/target.h new file mode 100644 index 00000000..1881ed2a --- /dev/null +++ b/variants/picow/target.h @@ -0,0 +1,18 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include + +extern PicoWBoard board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; + +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(); From 4d9964ff98fc518769068cae03fddfbeb338d3a2 Mon Sep 17 00:00:00 2001 From: AeroXuk Date: Mon, 21 Apr 2025 21:49:41 +0100 Subject: [PATCH 005/154] Correct opens to use "w" filemode instead of "w+" filemode. --- examples/companion_radio/main.cpp | 8 ++++---- examples/simple_secure_chat/main.cpp | 4 ++-- src/helpers/IdentityStore.cpp | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 6ae1951e..bf6d7b53 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -291,7 +291,7 @@ class MyMesh : public BaseChatMesh { File file = _fs->open("/contacts3", FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) - File file = _fs->open("/contacts3", "w+"); + File file = _fs->open("/contacts3", "w"); #else File file = _fs->open("/contacts3", "w", true); #endif @@ -356,7 +356,7 @@ class MyMesh : public BaseChatMesh { File file = _fs->open("/channels2", FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) - File file = _fs->open("/channels2", "w+"); + File file = _fs->open("/channels2", "w"); #else File file = _fs->open("/channels2", "w", true); #endif @@ -413,7 +413,7 @@ class MyMesh : public BaseChatMesh { File f = _fs->open(path, FILE_O_WRITE); if (f) { f.seek(0); f.truncate(); } #elif defined(RP2040_PLATFORM) - File f = _fs->open(path, "w+"); + File f = _fs->open(path, "w"); #else File f = _fs->open(path, "w", true); #endif @@ -875,7 +875,7 @@ public: File file = _fs->open("/new_prefs", FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) - File file = _fs->open("/new_prefs", "w+"); + File file = _fs->open("/new_prefs", "w"); #else File file = _fs->open("/new_prefs", "w", true); #endif diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index 6dde003f..7a80cb81 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -130,7 +130,7 @@ class MyMesh : public BaseChatMesh, ContactVisitor { File file = _fs->open("/contacts", FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) - File file = _fs->open("/contacts", "w+"); + File file = _fs->open("/contacts", "w"); #else File file = _fs->open("/contacts", "w", true); #endif @@ -337,7 +337,7 @@ public: File file = _fs->open("/node_prefs", FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) - File file = _fs->open("/node_prefs", "w+"); + File file = _fs->open("/node_prefs", "w"); #else File file = _fs->open("/node_prefs", "w", true); #endif diff --git a/src/helpers/IdentityStore.cpp b/src/helpers/IdentityStore.cpp index 1ecfd3cb..d111edfb 100644 --- a/src/helpers/IdentityStore.cpp +++ b/src/helpers/IdentityStore.cpp @@ -50,7 +50,7 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id) { File file = _fs->open(filename, FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) - File file = _fs->open(filename, "w+"); + File file = _fs->open(filename, "w"); #else File file = _fs->open(filename, "w", true); #endif @@ -72,7 +72,7 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id, const File file = _fs->open(filename, FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) - File file = _fs->open(filename, "w+"); + File file = _fs->open(filename, "w"); #else File file = _fs->open(filename, "w", true); #endif From 26efe2fb1921b3ba3598102cd3cbe02c08d25fb4 Mon Sep 17 00:00:00 2001 From: AeroXuk Date: Mon, 21 Apr 2025 23:01:44 +0100 Subject: [PATCH 006/154] Hopefully the correct ADC_MULTIPLIER value. --- src/helpers/rp2040/PicoWBoard.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/rp2040/PicoWBoard.h b/src/helpers/rp2040/PicoWBoard.h index b39778d0..cdc9c9f7 100644 --- a/src/helpers/rp2040/PicoWBoard.h +++ b/src/helpers/rp2040/PicoWBoard.h @@ -18,7 +18,7 @@ // built-ins #define PIN_VBAT_READ 26 -#define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) // MT Uses 3.1 +#define ADC_MULTIPLIER (3.1 * 3.3 * 1000) // MT Uses 3.1 #define PIN_LED_BUILTIN LED_BUILTIN class PicoWBoard : public mesh::MainBoard { From a87b5231ccbd261ead8dd8159d232009d9d36d35 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 22 Apr 2025 15:26:04 +1000 Subject: [PATCH 007/154] * RP2040 IdentityStore begin(), to ensure mkdir() --- examples/companion_radio/main.cpp | 3 +++ examples/simple_repeater/main.cpp | 1 + examples/simple_room_server/main.cpp | 1 + examples/simple_secure_chat/main.cpp | 3 +++ src/helpers/IdentityStore.h | 2 +- 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index bf6d7b53..e2817045 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -817,6 +817,9 @@ public: #if defined(NRF52_PLATFORM) _identity_store = new IdentityStore(fs, ""); + #elif defined(RP2040_PLATFORM) + _identity_store = new IdentityStore(fs, "/identity"); + _identity_store->begin(); #else _identity_store = new IdentityStore(fs, "/identity"); #endif diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index b3049546..1e012b58 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -651,6 +651,7 @@ void setup() { LittleFS.begin(); fs = &LittleFS; IdentityStore store(LittleFS, "/identity"); + store.begin(); #else #error "need to define filesystem" #endif diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 13296335..a607f202 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -889,6 +889,7 @@ void setup() { LittleFS.begin(); fs = &LittleFS; IdentityStore store(LittleFS, "/identity"); + store.begin(); #elif defined(ESP32) SPIFFS.begin(true); fs = &SPIFFS; diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index 7a80cb81..30bb52bd 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -295,6 +295,9 @@ public: #if defined(NRF52_PLATFORM) IdentityStore store(fs, ""); + #elif defined(RP2040_PLATFORM) + IdentityStore store(fs, "/identity"); + store.begin(); #else IdentityStore store(fs, "/identity"); #endif diff --git a/src/helpers/IdentityStore.h b/src/helpers/IdentityStore.h index 7607fa5b..35b56d97 100644 --- a/src/helpers/IdentityStore.h +++ b/src/helpers/IdentityStore.h @@ -18,7 +18,7 @@ class IdentityStore { public: IdentityStore(FILESYSTEM& fs, const char* dir): _fs(&fs), _dir(dir) { } - void begin() { _fs->mkdir(_dir); } + void begin() { if (_dir && _dir[0] == '/') { _fs->mkdir(_dir); } } bool load(const char *name, mesh::LocalIdentity& id); bool load(const char *name, mesh::LocalIdentity& id, char display_name[], int max_name_sz); bool save(const char *name, const mesh::LocalIdentity& id); From 669597ea4f0b92acb471d76837134b44006f01ba Mon Sep 17 00:00:00 2001 From: recrof Date: Wed, 23 Apr 2025 10:43:56 +0200 Subject: [PATCH 008/154] bugfix: only include SSD1306Display during build --- variants/promicro/platformio.ini | 121 +++++++++++++++---------------- 1 file changed, 59 insertions(+), 62 deletions(-) diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index f9f74feb..b32dc9c0 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -17,84 +17,84 @@ build_flags = ${nrf52840_base.build_flags} build_src_filter = ${nrf52840_base.build_src_filter} + +<../variants/promicro> -lib_deps= - ${nrf52840_base.lib_deps} +lib_deps= ${nrf52840_base.lib_deps} adafruit/Adafruit SSD1306 @ ^2.5.13 [env:Faketec_Repeater] extends = Faketec -build_src_filter = ${Faketec.build_src_filter} +<../examples/simple_repeater> + +build_src_filter = ${Faketec.build_src_filter} + +<../examples/simple_repeater> + + build_flags = ${Faketec.build_flags} - -D ADVERT_NAME="\"Faketec Repeater\"" + -D ADVERT_NAME='"Faketec Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD="\"password\"" + -D ADMIN_PASSWORD='"password"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -lib_deps = - ${Faketec.lib_deps} +lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 [env:Faketec_room_server] extends = Faketec -build_src_filter = ${Faketec.build_src_filter} +<../examples/simple_room_server> + -build_flags = - ${Faketec.build_flags} - -D ADVERT_NAME="\"Test Room\"" +build_src_filter = ${Faketec.build_src_filter} + +<../examples/simple_room_server> + + +build_flags = ${Faketec.build_flags} + -D ADVERT_NAME='"Faketec Room"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD="\"password\"" - -D ROOM_PASSWORD="\"hello\"" + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -lib_deps = - ${Faketec.lib_deps} +lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 [env:Faketec_terminal_chat] extends = Faketec -build_flags = - ${Faketec.build_flags} +build_flags = ${Faketec.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Faketec.build_src_filter} +<../examples/simple_secure_chat/main.cpp> -lib_deps = - ${Faketec.lib_deps} +build_src_filter = ${Faketec.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = ${Faketec.lib_deps} densaugeo/base64 @ ~1.4.0 adafruit/RTClib @ ^2.1.3 [env:Faketec_companion_radio_usb] extends = Faketec -build_flags = - ${Faketec.build_flags} +build_flags = ${Faketec.build_flags} -D MAX_CONTACTS=100 -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 = ${Faketec.build_src_filter} +<../examples/companion_radio> +<../examples/companion_radio> + -lib_deps = - ${Faketec.lib_deps} +build_src_filter = ${Faketec.build_src_filter} + +<../examples/companion_radio> + + +lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 [env:Faketec_companion_radio_ble] extends = Faketec -build_flags = - ${Faketec.build_flags} +build_flags = ${Faketec.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D ENABLE_PRIVATE_KEY_EXPORT=1 -D ENABLE_PRIVATE_KEY_IMPORT=1 -; -D MESH_PACKET_LOGGING=1 + -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Faketec.build_src_filter} + +<../examples/companion_radio> + -lib_deps = - ${Faketec.lib_deps} +build_src_filter = ${Faketec.build_src_filter} + + + +<../examples/companion_radio> + + +lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 @@ -109,69 +109,65 @@ build_flags = ${nrf52840_base.build_flags} -D LORA_TX_POWER=22 -D SX126X_CURRENT_LIMIT=130 -D SX126X_RX_BOOSTED_GAIN=1 -build_src_filter = ${nrf52840_base.build_src_filter} +build_src_filter = + ${nrf52840_base.build_src_filter} + +<../variants/promicro> [env:ProMicroLLCC68_Repeater] extends = ProMicroLLCC68 -build_src_filter = ${ProMicroLLCC68.build_src_filter} +<../examples/simple_repeater/main.cpp> -build_flags = - ${ProMicroLLCC68.build_flags} - -D ADVERT_NAME="\"ProMicroLLCC68 Repeater\"" - -D ADMIN_PASSWORD="\"password\"" +build_src_filter = ${ProMicroLLCC68.build_src_filter} + +<../examples/simple_repeater/main.cpp> +build_flags = ${ProMicroLLCC68.build_flags} + -D ADVERT_NAME='"ProMicroLLCC68 Repeater"' + -D ADMIN_PASSWORD='"password"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -lib_deps = - ${ProMicroLLCC68.lib_deps} +lib_deps = ${ProMicroLLCC68.lib_deps} adafruit/RTClib @ ^2.1.3 [env:ProMicroLLCC68_room_server] extends = ProMicroLLCC68 -build_src_filter = ${ProMicroLLCC68.build_src_filter} +<../examples/simple_room_server/main.cpp> -build_flags = - ${ProMicroLLCC68.build_flags} - -D ADVERT_NAME="\"ProMicroLLCC68 Room\"" - -D ADMIN_PASSWORD="\"password\"" - -D ROOM_PASSWORD="\"hello\"" +build_src_filter = ${ProMicroLLCC68.build_src_filter} + +<../examples/simple_room_server/main.cpp> +build_flags = ${ProMicroLLCC68.build_flags} + -D ADVERT_NAME='"ProMicroLLCC68 Room"' + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -lib_deps = - ${ProMicroLLCC68.lib_deps} +lib_deps = ${ProMicroLLCC68.lib_deps} adafruit/RTClib @ ^2.1.3 [env:ProMicroLLCC68_terminal_chat] extends = ProMicroLLCC68 -build_flags = - ${ProMicroLLCC68.build_flags} +build_flags = ${ProMicroLLCC68.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${ProMicroLLCC68.build_src_filter} +<../examples/simple_secure_chat/main.cpp> -lib_deps = - ${ProMicroLLCC68.lib_deps} +build_src_filter = ${ProMicroLLCC68.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = ${ProMicroLLCC68.lib_deps} densaugeo/base64 @ ~1.4.0 adafruit/RTClib @ ^2.1.3 [env:ProMicroLLCC68_companion_radio_usb] extends = ProMicroLLCC68 -build_flags = - ${ProMicroLLCC68.build_flags} +build_flags = ${ProMicroLLCC68.build_flags} -D MAX_CONTACTS=100 -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 = ${ProMicroLLCC68.build_src_filter} +<../examples/companion_radio/main.cpp> -lib_deps = - ${ProMicroLLCC68.lib_deps} +build_src_filter = ${ProMicroLLCC68.build_src_filter} + +<../examples/companion_radio/main.cpp> +lib_deps = ${ProMicroLLCC68.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 [env:ProMicroLLCC68_companion_radio_ble] extends = ProMicroLLCC68 -build_flags = - ${ProMicroLLCC68.build_flags} +build_flags = ${ProMicroLLCC68.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -180,8 +176,9 @@ build_flags = -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${ProMicroLLCC68.build_src_filter} + +<../examples/companion_radio/main.cpp> -lib_deps = - ${ProMicroLLCC68.lib_deps} +build_src_filter = ${ProMicroLLCC68.build_src_filter} + + + +<../examples/companion_radio/main.cpp> +lib_deps = ${ProMicroLLCC68.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 From 8c992d5037f35e068ed06c775b0801fd0f8e5faf Mon Sep 17 00:00:00 2001 From: Florent Date: Wed, 23 Apr 2025 11:20:28 +0200 Subject: [PATCH 009/154] xiao_nrf52-missing_targets --- variants/xiao_nrf52/platformio.ini | 34 +++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index 0947d86e..422729ab 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -55,6 +55,25 @@ lib_deps = ${Xiao_nrf52.lib_deps} densaugeo/base64 @ ~1.4.0 +[env:Xiao_nrf52_companion_radio_usb] +extends = Xiao_nrf52 +build_flags = + ${Xiao_nrf52.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 +; -D BLE_PIN_CODE=123456 +; -D BLE_DEBUG_LOGGING=1 +; -D ENABLE_PRIVATE_KEY_IMPORT=1 +; -D ENABLE_PRIVATE_KEY_EXPORT=1 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 +build_src_filter = ${Xiao_nrf52.build_src_filter} + + + +<../examples/companion_radio/main.cpp> +lib_deps = + ${Xiao_nrf52.lib_deps} + densaugeo/base64 @ ~1.4.0 + [env:Xiao_nrf52_alt_pinout_companion_radio_ble] extends = env:Xiao_nrf52_companion_radio_ble build_flags = @@ -78,4 +97,17 @@ build_src_filter = ${Xiao_nrf52.build_src_filter} extends = env:Xiao_nrf52_repeater build_flags = ${env:Xiao_nrf52_repeater.build_flags} - -D SX1262_XIAO_S3_VARIANT \ No newline at end of file + -D SX1262_XIAO_S3_VARIANT + +[env:Xiao_nrf52_room_server] +extends = Xiao_nrf52 +build_flags = + ${Xiao_nrf52.build_flags} + -D ADVERT_NAME='"Xiao_nrf52 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Xiao_nrf52.build_src_filter} + +<../examples/simple_room_server/main.cpp> \ No newline at end of file From da1febdd88a1d46abce859d3f6710f1b87295572 Mon Sep 17 00:00:00 2001 From: "rusty.labs" <18264094+rusty-labs@users.noreply.github.com> Date: Wed, 23 Apr 2025 20:23:06 -0400 Subject: [PATCH 010/154] Support for TBeam SX1262 board --- src/helpers/TBeamBoardSX1262.h | 79 +++++++++++++++++++++ variants/lilygo_tbeam_SX1262/platformio.ini | 68 ++++++++++++++++++ variants/lilygo_tbeam_SX1262/target.cpp | 65 +++++++++++++++++ variants/lilygo_tbeam_SX1262/target.h | 18 +++++ 4 files changed, 230 insertions(+) create mode 100644 src/helpers/TBeamBoardSX1262.h create mode 100644 variants/lilygo_tbeam_SX1262/platformio.ini create mode 100644 variants/lilygo_tbeam_SX1262/target.cpp create mode 100644 variants/lilygo_tbeam_SX1262/target.h diff --git a/src/helpers/TBeamBoardSX1262.h b/src/helpers/TBeamBoardSX1262.h new file mode 100644 index 00000000..f47df28e --- /dev/null +++ b/src/helpers/TBeamBoardSX1262.h @@ -0,0 +1,79 @@ +#pragma once + + +#include +#include +#include "XPowersLib.h" + +#define XPOWERS_CHIP_AXP192 + +// LoRa radio module pins for TBeam +#define P_LORA_DIO_1 33 // SX1262 IRQ pin +#define P_LORA_NSS 18 +#define P_LORA_RESET 23 +#define P_LORA_BUSY 32 // SX1262 Busy pin +#define P_LORA_SCLK 5 +#define P_LORA_MISO 19 +#define P_LORA_MOSI 27 + +#include "ESP32Board.h" + +#include + +class TBeamBoardSX1262 : public ESP32Board { + XPowersAXP192 power; + +public: + void begin() { + ESP32Board::begin(); + + power.setLDO2Voltage(3300); + power.enableLDO2(); + + power.enableBattVoltageMeasure(); + + pinMode(38, INPUT_PULLUP); + + esp_reset_reason_t reason = esp_reset_reason(); + if (reason == ESP_RST_DEEPSLEEP) { + long wakeup_source = esp_sleep_get_ext1_wakeup_status(); + if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) + startup_reason = BD_STARTUP_RX_PACKET; + } + + rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); + rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); + } + } + + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + + // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep + rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); + rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); + + rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); + + if (pin_wake_btn < 0) { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + } else { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + } + + if (secs > 0) { + esp_sleep_enable_timer_wakeup(secs * 1000000); + } + + // Finally set ESP32 into sleep + esp_deep_sleep_start(); // CPU halts here and never returns! + } + + uint16_t getBattMilliVolts() override { + return power.getBattVoltage(); + } + + const char* getManufacturerName() const override { + return "LilyGo T-Beam SX1262"; + } +}; diff --git a/variants/lilygo_tbeam_SX1262/platformio.ini b/variants/lilygo_tbeam_SX1262/platformio.ini new file mode 100644 index 00000000..83096428 --- /dev/null +++ b/variants/lilygo_tbeam_SX1262/platformio.ini @@ -0,0 +1,68 @@ +[LilyGo_TBeam_SX1262] +extends = esp32_base +board = ttgo-t-beam +build_flags = + ${esp32_base.build_flags} + -I variants/lilygo_tbeam_SX1262 + -D LILYGO_TBEAM_SX1262 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D P_LORA_TX_LED=4 + -D PIN_BOARD_SDA=21 + -D PIN_BOARD_SCL=22 + -D PIN_USER_BTN=38 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/lilygo_tbeam_SX1262> +board_build.partitions = min_spiffs.csv ; get around 4mb flash limit +lib_deps = + ${esp32_base.lib_deps} + lewisxhe/XPowersLib@^0.2.7 + adafruit/Adafruit SSD1306 @ ^2.5.13 + +[env:Tbeam_SX1262_companion_radio_ble] +extends = LilyGo_TBeam_SX1262 +board_build.upload.maximum_ram_size=2000000 +build_flags = + ${LilyGo_TBeam_SX1262.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +; -D RADIOLIB_DEBUG_BASIC=1 +; -D ENABLE_PRIVATE_KEY_IMPORT=1 +; -D ENABLE_PRIVATE_KEY_EXPORT=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} + + + + + +<../examples/companion_radio> +lib_deps = + ${LilyGo_TBeam_SX1262.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Tbeam_SX1262_repeater] +extends = LilyGo_TBeam_SX1262 +build_flags = + ${LilyGo_TBeam_SX1262.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Tbeam SX1262 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${LilyGo_TBeam_SX1262.lib_deps} + ${esp32_ota.lib_deps} \ No newline at end of file diff --git a/variants/lilygo_tbeam_SX1262/target.cpp b/variants/lilygo_tbeam_SX1262/target.cpp new file mode 100644 index 00000000..65f64734 --- /dev/null +++ b/variants/lilygo_tbeam_SX1262/target.cpp @@ -0,0 +1,65 @@ +#include +#include "target.h" + +TBeamBoardSX1262 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); + +#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); + + 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 +} diff --git a/variants/lilygo_tbeam_SX1262/target.h b/variants/lilygo_tbeam_SX1262/target.h new file mode 100644 index 00000000..16234e3c --- /dev/null +++ b/variants/lilygo_tbeam_SX1262/target.h @@ -0,0 +1,18 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include + +extern TBeamBoardSX1262 board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; + +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(); From 00f0bb74712115b3211ec3e41ed3d516378e54d3 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 24 Apr 2025 10:59:01 +1000 Subject: [PATCH 011/154] * ESPNOW: now using hardware RNG for radio_new_identity() --- src/helpers/esp32/ESPNOWRadio.cpp | 2 +- src/helpers/esp32/ESPNOWRadio.h | 2 +- variants/generic_espnow/platformio.ini | 2 +- variants/generic_espnow/target.cpp | 14 ++++++++++++-- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/helpers/esp32/ESPNOWRadio.cpp b/src/helpers/esp32/ESPNOWRadio.cpp index 7f8f4f71..68f6494a 100644 --- a/src/helpers/esp32/ESPNOWRadio.cpp +++ b/src/helpers/esp32/ESPNOWRadio.cpp @@ -22,7 +22,7 @@ static void OnDataRecv(const uint8_t *mac, const uint8_t *data, int len) { last_rx_len = len; } -void ESPNOWRadio::begin() { +void ESPNOWRadio::init() { // Set device as a Wi-Fi Station WiFi.mode(WIFI_STA); // Long Range mode diff --git a/src/helpers/esp32/ESPNOWRadio.h b/src/helpers/esp32/ESPNOWRadio.h index 7e628344..ab645f12 100644 --- a/src/helpers/esp32/ESPNOWRadio.h +++ b/src/helpers/esp32/ESPNOWRadio.h @@ -9,7 +9,7 @@ protected: public: ESPNOWRadio() { n_recv = n_sent = 0; } - void begin() override; + void init(); int recvRaw(uint8_t* bytes, int sz) override; uint32_t getEstAirtimeFor(int len_bytes) override; void startSendRaw(const uint8_t* bytes, int len) override; diff --git a/variants/generic_espnow/platformio.ini b/variants/generic_espnow/platformio.ini index 802902be..50eede9c 100644 --- a/variants/generic_espnow/platformio.ini +++ b/variants/generic_espnow/platformio.ini @@ -19,7 +19,7 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${esp32_base.build_src_filter} - + + + +<../variants/generic_espnow> [env:Generic_ESPNOW_terminal_chat] diff --git a/variants/generic_espnow/target.cpp b/variants/generic_espnow/target.cpp index a92b88ac..743a7aeb 100644 --- a/variants/generic_espnow/target.cpp +++ b/variants/generic_espnow/target.cpp @@ -11,7 +11,8 @@ ESP32RTCClock rtc_clock; bool radio_init() { rtc_clock.begin(); - // NOTE: radio_driver.begin() is called by Dispatcher::begin(), so not needed here + radio_driver.init(); + return true; // success } @@ -27,7 +28,16 @@ void radio_set_tx_power(uint8_t dbm) { radio_driver.setTxPower(dbm); } +// NOTE: as we are using the WiFi radio, the ESP_IDF will have enabled hardware RNG: +// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html +class ESP_RNG : public mesh::RNG { +public: + void random(uint8_t* dest, size_t sz) override { + esp_fill_random(dest, sz); + } +}; + mesh::LocalIdentity radio_new_identity() { - StdRNG rng; // TODO: need stronger True-RNG here + ESP_RNG rng; return mesh::LocalIdentity(&rng); // create new random identity } From e1092118d9eb2c773c4825c6e12630f97df9a71b Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 24 Apr 2025 12:16:55 +1000 Subject: [PATCH 012/154] * ESPNOW: packet rx/tx counters --- src/helpers/esp32/ESPNOWRadio.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/helpers/esp32/ESPNOWRadio.cpp b/src/helpers/esp32/ESPNOWRadio.cpp index 68f6494a..9cdc0234 100644 --- a/src/helpers/esp32/ESPNOWRadio.cpp +++ b/src/helpers/esp32/ESPNOWRadio.cpp @@ -72,6 +72,7 @@ void ESPNOWRadio::startSendRaw(const uint8_t* bytes, int len) { is_send_complete = false; esp_err_t result = esp_now_send(broadcastAddress, bytes, len); if (result == ESP_OK) { + n_sent++; ESPNOW_DEBUG_PRINTLN("Send success"); } else { last_send_result = result; @@ -94,6 +95,7 @@ int ESPNOWRadio::recvRaw(uint8_t* bytes, int sz) { if (last_rx_len > 0) { memcpy(bytes, rx_buf, last_rx_len); last_rx_len = 0; + n_recv++; } return len; } From 36b981c9eb84c9a53b6f091ce9215c4087f10b88 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 24 Apr 2025 13:50:18 +1000 Subject: [PATCH 013/154] * new targets: Generic_E22_*_repeater --- variants/generic-e22/platformio.ini | 66 ++++++++++++++++++++++++ variants/generic-e22/target.cpp | 79 +++++++++++++++++++++++++++++ variants/generic-e22/target.h | 19 +++++++ variants/generic-e22/variant.h | 44 ++++++++++++++++ 4 files changed, 208 insertions(+) create mode 100644 variants/generic-e22/platformio.ini create mode 100644 variants/generic-e22/target.cpp create mode 100644 variants/generic-e22/target.h create mode 100644 variants/generic-e22/variant.h diff --git a/variants/generic-e22/platformio.ini b/variants/generic-e22/platformio.ini new file mode 100644 index 00000000..6f2830b1 --- /dev/null +++ b/variants/generic-e22/platformio.ini @@ -0,0 +1,66 @@ +[Generic_E22] +extends = esp32_base +board = esp32doit-devkit-v1 +build_flags = + ${esp32_base.build_flags} + -I variants/generic-e22 + -D GENERIC_E22 + -D P_LORA_TX_LED=2 + -D PIN_VBAT_READ=35 + -D P_LORA_DIO_1=33 + -D P_LORA_NSS=18 + -D P_LORA_RESET=RADIOLIB_NC + -D P_LORA_BUSY=32 + -D P_LORA_SCLK=5 + -D P_LORA_MOSI=27 + -D P_LORA_MISO=19 + -D SX126X_TXEN=13 + -D SX126X_RXEN=14 + -D PIN_BOARD_SDA=21 + -D PIN_BOARD_SCL=22 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=130.0f ; for best TX power! +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/generic-e22> +lib_deps = + ${esp32_base.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 + +[env:Generic_E22_sx1262_repeater] +extends = Generic_E22 +build_src_filter = ${Generic_E22.build_src_filter} + +<../examples/simple_repeater/main.cpp> +build_flags = + ${Generic_E22.build_flags} + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D ADVERT_NAME='"E22 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${Generic_E22.lib_deps} + ${esp32_ota.lib_deps} + +[env:Generic_E22_sx1268_repeater] +extends = Generic_E22 +build_src_filter = ${Generic_E22.build_src_filter} + +<../examples/simple_repeater/main.cpp> +build_flags = + ${Generic_E22.build_flags} + -D RADIO_CLASS=CustomSX1268 + -D WRAPPER_CLASS=CustomSX1268Wrapper + -D LORA_TX_POWER=22 + -D ADVERT_NAME='"E22 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${Generic_E22.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/generic-e22/target.cpp b/variants/generic-e22/target.cpp new file mode 100644 index 00000000..f3028faa --- /dev/null +++ b/variants/generic-e22/target.cpp @@ -0,0 +1,79 @@ +#include +#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); + +#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 +} diff --git a/variants/generic-e22/target.h b/variants/generic-e22/target.h new file mode 100644 index 00000000..77ae8664 --- /dev/null +++ b/variants/generic-e22/target.h @@ -0,0 +1,19 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include + +extern ESP32Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; + +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(); diff --git a/variants/generic-e22/variant.h b/variants/generic-e22/variant.h new file mode 100644 index 00000000..6c850af2 --- /dev/null +++ b/variants/generic-e22/variant.h @@ -0,0 +1,44 @@ +// For OLED LCD +#define I2C_SDA 21 +#define I2C_SCL 22 + +// For GPS, 'undef's not needed +#define GPS_TX_PIN 15 +#define GPS_RX_PIN 12 +#define PIN_GPS_EN 4 +#define GPS_POWER_TOGGLE // Moved definition from platformio.ini to here + +#define BUTTON_PIN 39 // The middle button GPIO on the T-Beam +#define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define ADC_MULTIPLIER 1.85 // (R1 = 470k, R2 = 680k) +#define EXT_PWR_DETECT 4 // Pin to detect connected external power source for LILYGO® TTGO T-Energy T18 and other DIY boards +#define EXT_NOTIFY_OUT 12 // Overridden default pin to use for Ext Notify Module (#975). +#define LED_PIN 2 // add status LED (compatible with core-pcb and DIY targets) + +// Radio +#define USE_SX1262 // E22-900M30S uses SX1262 +#define USE_SX1268 // E22-400M30S uses SX1268 +#define SX126X_MAX_POWER 22 // Outputting 22dBm from SX1262 results in ~30dBm E22-900M30S output (module only uses last stage of the YP2233W PA) +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 // E22 series TCXO reference voltage is 1.8V + +#define SX126X_CS 18 // EBYTE module's NSS pin +#define SX126X_SCK 5 // EBYTE module's SCK pin +#define SX126X_MOSI 27 // EBYTE module's MOSI pin +#define SX126X_MISO 19 // EBYTE module's MISO pin +#define SX126X_RESET 23 // EBYTE module's NRST pin +#define SX126X_BUSY 32 // EBYTE module's BUSY pin +#define SX126X_DIO1 33 // EBYTE module's DIO1 pin + +// The E22's TXEN pin is connected to MCU pin, E22's RXEN pin is connected to MCU pin (allows for ramping up PA before transmission +// Don't define DIO2_AS_RF_SWITCH because we only use DIO2 or an MCU pin mutually exclusively to connect to E22's TXEN (to prevent +// a short if they are both connected at the same time and there's a slight non-neglibible delay and/or voltage difference between +// DIO2 and TXEN). +#define SX126X_TXEN 13 // Schematic connects EBYTE module's TXEN pin to MCU +#define SX126X_RXEN 14 // Schematic connects EBYTE module's RXEN pin to MCU + +#define LORA_CS SX126X_CS // Compatibility with variant file configuration structure +#define LORA_SCK SX126X_SCK // Compatibility with variant file configuration structure +#define LORA_MOSI SX126X_MOSI // Compatibility with variant file configuration structure +#define LORA_MISO SX126X_MISO // Compatibility with variant file configuration structure +#define LORA_DIO1 SX126X_DIO1 // Compatibility with variant file configuration structure From 0fc4d244eabb0456aa75d134b3bd07f79a5aee2b Mon Sep 17 00:00:00 2001 From: Jeremy O'Brien Date: Thu, 24 Apr 2025 12:18:22 -0400 Subject: [PATCH 014/154] Add .direnv/ to .gitignore. This is the directory that holds the nix-generated development environment --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9b9580a3..7e7cc694 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ .vscode/launch.json .vscode/ipch out/ +.direnv/ From 2a7e105c59c4d8aaa9e08ae9ecfebad01ad904d7 Mon Sep 17 00:00:00 2001 From: Florent Date: Thu, 24 Apr 2025 22:37:06 +0200 Subject: [PATCH 015/154] some tests --- src/helpers/ui/OLEDDisplay.cpp | 1176 +++++++++++++++++++++++++ src/helpers/ui/OLEDDisplay.h | 395 +++++++++ src/helpers/ui/OLEDDisplayFonts.cpp | 1273 +++++++++++++++++++++++++++ src/helpers/ui/OLEDDisplayFonts.h | 13 + src/helpers/ui/ST7789Display.cpp | 62 +- src/helpers/ui/ST7789Display.h | 8 +- src/helpers/ui/ST7789Spi.h | 430 +++++++++ variants/t114/platformio.ini | 25 +- 8 files changed, 3336 insertions(+), 46 deletions(-) create mode 100644 src/helpers/ui/OLEDDisplay.cpp create mode 100644 src/helpers/ui/OLEDDisplay.h create mode 100644 src/helpers/ui/OLEDDisplayFonts.cpp create mode 100644 src/helpers/ui/OLEDDisplayFonts.h create mode 100644 src/helpers/ui/ST7789Spi.h diff --git a/src/helpers/ui/OLEDDisplay.cpp b/src/helpers/ui/OLEDDisplay.cpp new file mode 100644 index 00000000..dca2ad1b --- /dev/null +++ b/src/helpers/ui/OLEDDisplay.cpp @@ -0,0 +1,1176 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + + /* + * TODO Helmut + * - test/finish dislplay.printf() on mbed-os + * - Finish _putc with drawLogBuffer when running display + */ + +#include "OLEDDisplay.h" + +OLEDDisplay::OLEDDisplay() { + + displayWidth = 128; + displayHeight = 64; + displayBufferSize = displayWidth * displayHeight / 8; + color = WHITE; + geometry = GEOMETRY_128_64; + textAlignment = TEXT_ALIGN_LEFT; + fontData = ArialMT_Plain_10; + fontTableLookupFunction = DefaultFontTableLookup; + buffer = NULL; +#ifdef OLEDDISPLAY_DOUBLE_BUFFER + buffer_back = NULL; +#endif +} + +OLEDDisplay::~OLEDDisplay() { + end(); +} + +bool OLEDDisplay::allocateBuffer() { + + logBufferSize = 0; + logBufferFilled = 0; + logBufferLine = 0; + logBufferMaxLines = 0; + logBuffer = NULL; + + if (!connect()) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Can't establish connection to display\n"); + return false; + } + + if(this->buffer==NULL) { + this->buffer = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + BufferOffset); + this->buffer += BufferOffset; + + if(!this->buffer) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create display\n"); + return false; + } + } + + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + if(this->buffer_back==NULL) { + this->buffer_back = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + BufferOffset); + this->buffer_back += BufferOffset; + + if(!this->buffer_back) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create back buffer\n"); + free(this->buffer - BufferOffset); + return false; + } + } + #endif + + return true; +} + +bool OLEDDisplay::init() { + + BufferOffset = getBufferOffset(); + + if(!allocateBuffer()) { + return false; + } + + sendInitCommands(); + resetDisplay(); + + return true; +} + +void OLEDDisplay::end() { + if (this->buffer) { free(this->buffer - BufferOffset); this->buffer = NULL; } + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + if (this->buffer_back) { free(this->buffer_back - BufferOffset); this->buffer_back = NULL; } + #endif + if (this->logBuffer != NULL) { free(this->logBuffer); this->logBuffer = NULL; } +} + +void OLEDDisplay::resetDisplay(void) { + clear(); + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + memset(buffer_back, 1, displayBufferSize); + #endif + display(); +} + +void OLEDDisplay::setColor(OLEDDISPLAY_COLOR color) { + this->color = color; +} + +OLEDDISPLAY_COLOR OLEDDisplay::getColor() { + return this->color; +} + +void OLEDDisplay::setPixel(int16_t x, int16_t y) { + if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) { + switch (color) { + case WHITE: buffer[x + (y / 8) * this->width()] |= (1 << (y & 7)); break; + case BLACK: buffer[x + (y / 8) * this->width()] &= ~(1 << (y & 7)); break; + case INVERSE: buffer[x + (y / 8) * this->width()] ^= (1 << (y & 7)); break; + } + } +} + +void OLEDDisplay::setPixelColor(int16_t x, int16_t y, OLEDDISPLAY_COLOR color) { + if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) { + switch (color) { + case WHITE: buffer[x + (y / 8) * this->width()] |= (1 << (y & 7)); break; + case BLACK: buffer[x + (y / 8) * this->width()] &= ~(1 << (y & 7)); break; + case INVERSE: buffer[x + (y / 8) * this->width()] ^= (1 << (y & 7)); break; + } + } +} + +void OLEDDisplay::clearPixel(int16_t x, int16_t y) { + if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) { + switch (color) { + case BLACK: buffer[x + (y >> 3) * this->width()] |= (1 << (y & 7)); break; + case WHITE: buffer[x + (y >> 3) * this->width()] &= ~(1 << (y & 7)); break; + case INVERSE: buffer[x + (y >> 3) * this->width()] ^= (1 << (y & 7)); break; + } + } +} + + +// Bresenham's algorithm - thx wikipedia and Adafruit_GFX +void OLEDDisplay::drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1) { + int16_t steep = abs(y1 - y0) > abs(x1 - x0); + if (steep) { + _swap_int16_t(x0, y0); + _swap_int16_t(x1, y1); + } + + if (x0 > x1) { + _swap_int16_t(x0, x1); + _swap_int16_t(y0, y1); + } + + int16_t dx, dy; + dx = x1 - x0; + dy = abs(y1 - y0); + + int16_t err = dx / 2; + int16_t ystep; + + if (y0 < y1) { + ystep = 1; + } else { + ystep = -1; + } + + for (; x0<=x1; x0++) { + if (steep) { + setPixel(y0, x0); + } else { + setPixel(x0, y0); + } + err -= dy; + if (err < 0) { + y0 += ystep; + err += dx; + } + } +} + +void OLEDDisplay::drawRect(int16_t x, int16_t y, int16_t width, int16_t height) { + drawHorizontalLine(x, y, width); + drawVerticalLine(x, y, height); + drawVerticalLine(x + width - 1, y, height); + drawHorizontalLine(x, y + height - 1, width); +} + +void OLEDDisplay::fillRect(int16_t xMove, int16_t yMove, int16_t width, int16_t height) { + for (int16_t x = xMove; x < xMove + width; x++) { + drawVerticalLine(x, yMove, height); + } +} + +void OLEDDisplay::drawCircle(int16_t x0, int16_t y0, int16_t radius) { + int16_t x = 0, y = radius; + int16_t dp = 1 - radius; + do { + if (dp < 0) + dp = dp + (x++) * 2 + 3; + else + dp = dp + (x++) * 2 - (y--) * 2 + 5; + + setPixel(x0 + x, y0 + y); //For the 8 octants + setPixel(x0 - x, y0 + y); + setPixel(x0 + x, y0 - y); + setPixel(x0 - x, y0 - y); + setPixel(x0 + y, y0 + x); + setPixel(x0 - y, y0 + x); + setPixel(x0 + y, y0 - x); + setPixel(x0 - y, y0 - x); + + } while (x < y); + + setPixel(x0 + radius, y0); + setPixel(x0, y0 + radius); + setPixel(x0 - radius, y0); + setPixel(x0, y0 - radius); +} + +void OLEDDisplay::drawCircleQuads(int16_t x0, int16_t y0, int16_t radius, uint8_t quads) { + int16_t x = 0, y = radius; + int16_t dp = 1 - radius; + while (x < y) { + if (dp < 0) + dp = dp + (x++) * 2 + 3; + else + dp = dp + (x++) * 2 - (y--) * 2 + 5; + if (quads & 0x1) { + setPixel(x0 + x, y0 - y); + setPixel(x0 + y, y0 - x); + } + if (quads & 0x2) { + setPixel(x0 - y, y0 - x); + setPixel(x0 - x, y0 - y); + } + if (quads & 0x4) { + setPixel(x0 - y, y0 + x); + setPixel(x0 - x, y0 + y); + } + if (quads & 0x8) { + setPixel(x0 + x, y0 + y); + setPixel(x0 + y, y0 + x); + } + } + if (quads & 0x1 && quads & 0x8) { + setPixel(x0 + radius, y0); + } + if (quads & 0x4 && quads & 0x8) { + setPixel(x0, y0 + radius); + } + if (quads & 0x2 && quads & 0x4) { + setPixel(x0 - radius, y0); + } + if (quads & 0x1 && quads & 0x2) { + setPixel(x0, y0 - radius); + } +} + + +void OLEDDisplay::fillCircle(int16_t x0, int16_t y0, int16_t radius) { + int16_t x = 0, y = radius; + int16_t dp = 1 - radius; + do { + if (dp < 0) + dp = dp + (x++) * 2 + 3; + else + dp = dp + (x++) * 2 - (y--) * 2 + 5; + + drawHorizontalLine(x0 - x, y0 - y, 2*x); + drawHorizontalLine(x0 - x, y0 + y, 2*x); + drawHorizontalLine(x0 - y, y0 - x, 2*y); + drawHorizontalLine(x0 - y, y0 + x, 2*y); + + + } while (x < y); + drawHorizontalLine(x0 - radius, y0, 2 * radius); + +} + +void OLEDDisplay::drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, + int16_t x2, int16_t y2) { + drawLine(x0, y0, x1, y1); + drawLine(x1, y1, x2, y2); + drawLine(x2, y2, x0, y0); +} + +void OLEDDisplay::fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, + int16_t x2, int16_t y2) { + int16_t a, b, y, last; + + if (y0 > y1) { + _swap_int16_t(y0, y1); + _swap_int16_t(x0, x1); + } + if (y1 > y2) { + _swap_int16_t(y2, y1); + _swap_int16_t(x2, x1); + } + if (y0 > y1) { + _swap_int16_t(y0, y1); + _swap_int16_t(x0, x1); + } + + if (y0 == y2) { + a = b = x0; + if (x1 < a) { + a = x1; + } else if (x1 > b) { + b = x1; + } + if (x2 < a) { + a = x2; + } else if (x2 > b) { + b = x2; + } + drawHorizontalLine(a, y0, b - a + 1); + return; + } + + int16_t + dx01 = x1 - x0, + dy01 = y1 - y0, + dx02 = x2 - x0, + dy02 = y2 - y0, + dx12 = x2 - x1, + dy12 = y2 - y1; + int32_t + sa = 0, + sb = 0; + + if (y1 == y2) { + last = y1; // Include y1 scanline + } else { + last = y1 - 1; // Skip it + } + + for (y = y0; y <= last; y++) { + a = x0 + sa / dy01; + b = x0 + sb / dy02; + sa += dx01; + sb += dx02; + + if (a > b) { + _swap_int16_t(a, b); + } + drawHorizontalLine(a, y, b - a + 1); + } + + sa = dx12 * (y - y1); + sb = dx02 * (y - y0); + for (; y <= y2; y++) { + a = x1 + sa / dy12; + b = x0 + sb / dy02; + sa += dx12; + sb += dx02; + + if (a > b) { + _swap_int16_t(a, b); + } + drawHorizontalLine(a, y, b - a + 1); + } +} + +void OLEDDisplay::drawHorizontalLine(int16_t x, int16_t y, int16_t length) { + if (y < 0 || y >= this->height()) { return; } + + if (x < 0) { + length += x; + x = 0; + } + + if ( (x + length) > this->width()) { + length = (this->width() - x); + } + + if (length <= 0) { return; } + + uint8_t * bufferPtr = buffer; + bufferPtr += (y >> 3) * this->width(); + bufferPtr += x; + + uint8_t drawBit = 1 << (y & 7); + + switch (color) { + case WHITE: while (length--) { + *bufferPtr++ |= drawBit; + }; break; + case BLACK: drawBit = ~drawBit; while (length--) { + *bufferPtr++ &= drawBit; + }; break; + case INVERSE: while (length--) { + *bufferPtr++ ^= drawBit; + }; break; + } +} + +void OLEDDisplay::drawVerticalLine(int16_t x, int16_t y, int16_t length) { + if (x < 0 || x >= this->width()) return; + + if (y < 0) { + length += y; + y = 0; + } + + if ( (y + length) > this->height()) { + length = (this->height() - y); + } + + if (length <= 0) return; + + + uint8_t yOffset = y & 7; + uint8_t drawBit; + uint8_t *bufferPtr = buffer; + + bufferPtr += (y >> 3) * this->width(); + bufferPtr += x; + + if (yOffset) { + yOffset = 8 - yOffset; + drawBit = ~(0xFF >> (yOffset)); + + if (length < yOffset) { + drawBit &= (0xFF >> (yOffset - length)); + } + + switch (color) { + case WHITE: *bufferPtr |= drawBit; break; + case BLACK: *bufferPtr &= ~drawBit; break; + case INVERSE: *bufferPtr ^= drawBit; break; + } + + if (length < yOffset) return; + + length -= yOffset; + bufferPtr += this->width(); + } + + if (length >= 8) { + switch (color) { + case WHITE: + case BLACK: + drawBit = (color == WHITE) ? 0xFF : 0x00; + do { + *bufferPtr = drawBit; + bufferPtr += this->width(); + length -= 8; + } while (length >= 8); + break; + case INVERSE: + do { + *bufferPtr = ~(*bufferPtr); + bufferPtr += this->width(); + length -= 8; + } while (length >= 8); + break; + } + } + + if (length > 0) { + drawBit = (1 << (length & 7)) - 1; + switch (color) { + case WHITE: *bufferPtr |= drawBit; break; + case BLACK: *bufferPtr &= ~drawBit; break; + case INVERSE: *bufferPtr ^= drawBit; break; + } + } +} + +void OLEDDisplay::drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress) { + uint16_t radius = height / 2; + uint16_t xRadius = x + radius; + uint16_t yRadius = y + radius; + uint16_t doubleRadius = 2 * radius; + uint16_t innerRadius = radius - 2; + + setColor(WHITE); + drawCircleQuads(xRadius, yRadius, radius, 0b00000110); + drawHorizontalLine(xRadius, y, width - doubleRadius + 1); + drawHorizontalLine(xRadius, y + height, width - doubleRadius + 1); + drawCircleQuads(x + width - radius, yRadius, radius, 0b00001001); + + uint16_t maxProgressWidth = (width - doubleRadius + 1) * progress / 100; + + fillCircle(xRadius, yRadius, innerRadius); + fillRect(xRadius + 1, y + 2, maxProgressWidth, height - 3); + fillCircle(xRadius + maxProgressWidth, yRadius, innerRadius); +} + +void OLEDDisplay::drawFastImage(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *image) { + drawInternal(xMove, yMove, width, height, image, 0, 0); +} + +void OLEDDisplay::drawXbm(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *xbm) { + int16_t widthInXbm = (width + 7) / 8; + uint8_t data = 0; + + for(int16_t y = 0; y < height; y++) { + for(int16_t x = 0; x < width; x++ ) { + if (x & 7) { + data >>= 1; // Move a bit + } else { // Read new data every 8 bit + data = pgm_read_byte(xbm + (x / 8) + y * widthInXbm); + } + // if there is a bit draw it + if (data & 0x01) { + setPixel(xMove + x, yMove + y); + } + } + } +} + +void OLEDDisplay::drawIco16x16(int16_t xMove, int16_t yMove, const uint8_t *ico, bool inverse) { + uint16_t data; + + for(int16_t y = 0; y < 16; y++) { + data = pgm_read_byte(ico + (y << 1)) + (pgm_read_byte(ico + (y << 1) + 1) << 8); + for(int16_t x = 0; x < 16; x++ ) { + if ((data & 0x01) ^ inverse) { + setPixelColor(xMove + x, yMove + y, WHITE); + } else { + setPixelColor(xMove + x, yMove + y, BLACK); + } + data >>= 1; // Move a bit + } + } +} + +uint16_t OLEDDisplay::drawStringInternal(int16_t xMove, int16_t yMove, const char* text, uint16_t textLength, uint16_t textWidth, bool utf8) { + uint8_t textHeight = pgm_read_byte(fontData + HEIGHT_POS); + uint8_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); + uint16_t sizeOfJumpTable = pgm_read_byte(fontData + CHAR_NUM_POS) * JUMPTABLE_BYTES; + + uint16_t cursorX = 0; + uint16_t cursorY = 0; + uint16_t charCount = 0; + + switch (textAlignment) { + case TEXT_ALIGN_CENTER_BOTH: + yMove -= textHeight >> 1; + // Fallthrough + case TEXT_ALIGN_CENTER: + xMove -= textWidth >> 1; // divide by 2 + break; + case TEXT_ALIGN_RIGHT: + xMove -= textWidth; + break; + case TEXT_ALIGN_LEFT: + break; + } + + // Don't draw anything if it is not on the screen. + if (xMove + textWidth < 0 || xMove >= this->width() ) {return 0;} + if (yMove + textHeight < 0 || yMove >= this->height()) {return 0;} + + for (uint16_t j = 0; j < textLength; j++) { + int16_t xPos = xMove + cursorX; + int16_t yPos = yMove + cursorY; + if (xPos > this->width()) + break; // no need to continue + charCount++; + + uint8_t code; + if (utf8) { + code = (this->fontTableLookupFunction)(text[j]); + if (code == 0) + continue; + } else + code = text[j]; + if (code >= firstChar) { + uint8_t charCode = code - firstChar; + + // 4 Bytes per char code + uint8_t msbJumpToChar = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES ); // MSB \ JumpAddress + uint8_t lsbJumpToChar = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_LSB); // LSB / + uint8_t charByteSize = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_SIZE); // Size + uint8_t currentCharWidth = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); // Width + + // Test if the char is drawable + if (!(msbJumpToChar == 255 && lsbJumpToChar == 255)) { + // Get the position of the char data + uint16_t charDataPosition = JUMPTABLE_START + sizeOfJumpTable + ((msbJumpToChar << 8) + lsbJumpToChar); + drawInternal(xPos, yPos, currentCharWidth, textHeight, fontData, charDataPosition, charByteSize); + } + + cursorX += currentCharWidth; + } + } + return charCount; +} + + +uint16_t OLEDDisplay::drawString(int16_t xMove, int16_t yMove, const String &strUser) { + uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS); + + // char* text must be freed! + char* text = strdup(strUser.c_str()); + if (!text) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][drawString] Can't allocate char array.\n"); + return 0; + } + + uint16_t yOffset = 0; + // If the string should be centered vertically too + // we need to now how heigh the string is. + if (textAlignment == TEXT_ALIGN_CENTER_BOTH) { + uint16_t lb = 0; + // Find number of linebreaks in text + for (uint16_t i=0;text[i] != 0; i++) { + lb += (text[i] == 10); + } + // Calculate center + yOffset = (lb * lineHeight) / 2; + } + + uint16_t charDrawn = 0; + uint16_t line = 0; + char* textPart = strtok(text,"\n"); + while (textPart != NULL) { + uint16_t length = strlen(textPart); + charDrawn += drawStringInternal(xMove, yMove - yOffset + (line++) * lineHeight, textPart, length, getStringWidth(textPart, length, true), true); + textPart = strtok(NULL, "\n"); + } + free(text); + return charDrawn; +} + +void OLEDDisplay::drawStringf( int16_t x, int16_t y, char* buffer, String format, ... ) +{ + va_list myargs; + va_start(myargs, format); + vsprintf(buffer, format.c_str(), myargs); + va_end(myargs); + drawString( x, y, buffer ); +} + +uint16_t OLEDDisplay::drawStringMaxWidth(int16_t xMove, int16_t yMove, uint16_t maxLineWidth, const String &strUser) { + uint16_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); + uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS); + + const char* text = strUser.c_str(); + + uint16_t length = strlen(text); + uint16_t lastDrawnPos = 0; + uint16_t lineNumber = 0; + uint16_t strWidth = 0; + + uint16_t preferredBreakpoint = 0; + uint16_t widthAtBreakpoint = 0; + uint16_t firstLineChars = 0; + uint16_t drawStringResult = 1; // later tested for 0 == error, so initialize to 1 + + for (uint16_t i = 0; i < length; i++) { + char c = (this->fontTableLookupFunction)(text[i]); + if (c == 0) + continue; + strWidth += pgm_read_byte(fontData + JUMPTABLE_START + (c - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); + + // Always break on newline + if (text[i] == '\n') { + drawStringResult = drawStringInternal(xMove, yMove + (lineNumber++) * lineHeight , &text[lastDrawnPos], i - lastDrawnPos, strWidth, true); + if (firstLineChars == 0) + firstLineChars = i; + + lastDrawnPos = i + 1; + strWidth = 0; + if (drawStringResult == 0) // we are past the display already? + break; + } + + // Always try to break on a space, dash or slash + if (text[i] == ' ' || text[i]== '-' || text[i] == '/') { + preferredBreakpoint = i + 1; + widthAtBreakpoint = strWidth; + } + + if (strWidth >= maxLineWidth) { + if (preferredBreakpoint == 0) { + preferredBreakpoint = i; + widthAtBreakpoint = strWidth; + } + drawStringResult = drawStringInternal(xMove, yMove + (lineNumber++) * lineHeight , &text[lastDrawnPos], preferredBreakpoint - lastDrawnPos, widthAtBreakpoint, true); + if (firstLineChars == 0) + firstLineChars = preferredBreakpoint; + lastDrawnPos = preferredBreakpoint; + // It is possible that we did not draw all letters to i so we need + // to account for the width of the chars from `i - preferredBreakpoint` + // by calculating the width we did not draw yet. + strWidth = strWidth - widthAtBreakpoint; + preferredBreakpoint = 0; + if (drawStringResult == 0) // we are past the display already? + break; + } + } + + // Draw last part if needed + if (drawStringResult != 0 && lastDrawnPos < length) { + drawStringResult = drawStringInternal(xMove, yMove + (lineNumber++) * lineHeight , &text[lastDrawnPos], length - lastDrawnPos, getStringWidth(&text[lastDrawnPos], length - lastDrawnPos, true), true); + } + + if (drawStringResult == 0 || (yMove + lineNumber * lineHeight) >= this->height()) // text did not fit on screen + return firstLineChars; + return 0; // everything was drawn +} + +uint16_t OLEDDisplay::getStringWidth(const char* text, uint16_t length, bool utf8) { + uint16_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); + + uint16_t stringWidth = 0; + uint16_t maxWidth = 0; + + for (uint16_t i = 0; i < length; i++) { + char c = text[i]; + if (utf8) { + c = (this->fontTableLookupFunction)(c); + if (c == 0) + continue; + } + stringWidth += pgm_read_byte(fontData + JUMPTABLE_START + (c - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); + if (c == 10) { + maxWidth = max(maxWidth, stringWidth); + stringWidth = 0; + } + } + + return max(maxWidth, stringWidth); +} + +uint16_t OLEDDisplay::getStringWidth(const String &strUser) { + uint16_t width = getStringWidth(strUser.c_str(), strUser.length()); + return width; +} + +void OLEDDisplay::setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment) { + this->textAlignment = textAlignment; +} + +void OLEDDisplay::setFont(const uint8_t *fontData) { + this->fontData = fontData; +} + +void OLEDDisplay::displayOn(void) { + sendCommand(DISPLAYON); +} + +void OLEDDisplay::displayOff(void) { + sendCommand(DISPLAYOFF); +} + +void OLEDDisplay::invertDisplay(void) { + sendCommand(INVERTDISPLAY); +} + +void OLEDDisplay::normalDisplay(void) { + sendCommand(NORMALDISPLAY); +} + +void OLEDDisplay::setContrast(uint8_t contrast, uint8_t precharge, uint8_t comdetect) { + sendCommand(SETPRECHARGE); //0xD9 + sendCommand(precharge); //0xF1 default, to lower the contrast, put 1-1F + sendCommand(SETCONTRAST); + sendCommand(contrast); // 0-255 + sendCommand(SETVCOMDETECT); //0xDB, (additionally needed to lower the contrast) + sendCommand(comdetect); //0x40 default, to lower the contrast, put 0 + sendCommand(DISPLAYALLON_RESUME); + sendCommand(NORMALDISPLAY); + sendCommand(DISPLAYON); +} + +void OLEDDisplay::setBrightness(uint8_t brightness) { + uint8_t contrast = brightness; + if (brightness < 128) { + // Magic values to get a smooth/ step-free transition + contrast = brightness * 1.171; + } else { + contrast = brightness * 1.171 - 43; + } + + uint8_t precharge = 241; + if (brightness == 0) { + precharge = 0; + } + uint8_t comdetect = brightness / 8; + + setContrast(contrast, precharge, comdetect); +} + +void OLEDDisplay::resetOrientation() { + sendCommand(SEGREMAP); + sendCommand(COMSCANINC); //Reset screen rotation or mirroring +} + +void OLEDDisplay::flipScreenVertically() { + sendCommand(SEGREMAP | 0x01); + sendCommand(COMSCANDEC); //Rotate screen 180 Deg +} + +void OLEDDisplay::mirrorScreen() { + sendCommand(SEGREMAP); + sendCommand(COMSCANDEC); //Mirror screen +} + +void OLEDDisplay::clear(void) { + memset(buffer, 0, displayBufferSize); +} + +void OLEDDisplay::drawLogBuffer(uint16_t xMove, uint16_t yMove) { + uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS); + // Always align left + setTextAlignment(TEXT_ALIGN_LEFT); + + // State values + uint16_t length = 0; + uint16_t line = 0; + uint16_t lastPos = 0; + + for (uint16_t i=0;ilogBufferFilled;i++){ + // Everytime we have a \n print + if (this->logBuffer[i] == 10) { + length++; + // Draw string on line `line` from lastPos to length + // Passing 0 as the lenght because we are in TEXT_ALIGN_LEFT + drawStringInternal(xMove, yMove + (line++) * lineHeight, &this->logBuffer[lastPos], length, 0, false); + // Remember last pos + lastPos = i; + // Reset length + length = 0; + } else { + // Count chars until next linebreak + length++; + } + } + // Draw the remaining string + if (length > 0) { + drawStringInternal(xMove, yMove + line * lineHeight, &this->logBuffer[lastPos], length, 0, false); + } +} + +uint16_t OLEDDisplay::getWidth(void) { + return displayWidth; +} + +uint16_t OLEDDisplay::getHeight(void) { + return displayHeight; +} + +bool OLEDDisplay::setLogBuffer(uint16_t lines, uint16_t chars){ + if (logBuffer != NULL) free(logBuffer); + uint16_t size = lines * chars; + if (size > 0) { + this->logBufferLine = 0; // Lines printed + this->logBufferFilled = 0; // Nothing stored yet + this->logBufferMaxLines = lines; // Lines max printable + this->logBufferSize = size; // Total number of characters the buffer can hold + this->logBuffer = (char *) malloc(size * sizeof(uint8_t)); + if(!this->logBuffer) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][setLogBuffer] Not enough memory to create log buffer\n"); + return false; + } + } + return true; +} + +size_t OLEDDisplay::write(uint8_t c) { + if (this->logBufferSize > 0) { + // Don't waste space on \r\n line endings, dropping \r + if (c == 13) return 1; + + // convert UTF-8 character to font table index + c = (this->fontTableLookupFunction)(c); + // drop unknown character + if (c == 0) return 1; + + bool maxLineNotReached = this->logBufferLine < this->logBufferMaxLines; + bool bufferNotFull = this->logBufferFilled < this->logBufferSize; + + // Can we write to the buffer? + if (bufferNotFull && maxLineNotReached) { + this->logBuffer[logBufferFilled] = c; + this->logBufferFilled++; + // Keep track of lines written + if (c == 10) this->logBufferLine++; + } else { + // Max line number is reached + if (!maxLineNotReached) this->logBufferLine--; + + // Find the end of the first line + uint16_t firstLineEnd = 0; + for (uint16_t i=0;ilogBufferFilled;i++) { + if (this->logBuffer[i] == 10){ + // Include last char too + firstLineEnd = i + 1; + break; + } + } + // If there was a line ending + if (firstLineEnd > 0) { + // Calculate the new logBufferFilled value + this->logBufferFilled = logBufferFilled - firstLineEnd; + // Now we move the lines infront of the buffer + memcpy(this->logBuffer, &this->logBuffer[firstLineEnd], logBufferFilled); + } else { + // Let's reuse the buffer if it was full + if (!bufferNotFull) { + this->logBufferFilled = 0; + }// else { + // Nothing to do here + //} + } + write(c); + } + } + // We are always writing all uint8_t to the buffer + return 1; +} + +size_t OLEDDisplay::write(const char* str) { + if (str == NULL) return 0; + size_t length = strlen(str); + for (size_t i = 0; i < length; i++) { + write(str[i]); + } + return length; +} + +#ifdef __MBED__ +int OLEDDisplay::_putc(int c) { + + if (!fontData) + return 1; + if (!logBufferSize) { + uint8_t textHeight = pgm_read_byte(fontData + HEIGHT_POS); + uint16_t lines = this->displayHeight / textHeight; + uint16_t chars = 2 * (this->displayWidth / textHeight); + + if (this->displayHeight % textHeight) + lines++; + if (this->displayWidth % textHeight) + chars++; + setLogBuffer(lines, chars); + } + + return this->write((uint8_t)c); +} +#endif + +// Private functions +void OLEDDisplay::setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width, uint16_t height) { + this->geometry = g; + + switch (g) { + case GEOMETRY_128_128: + this->displayWidth = 128; + this->displayHeight = 128; + break; + case GEOMETRY_128_64: + this->displayWidth = 128; + this->displayHeight = 64; + break; + case GEOMETRY_128_32: + this->displayWidth = 128; + this->displayHeight = 32; + break; + case GEOMETRY_64_48: + this->displayWidth = 64; + this->displayHeight = 48; + break; + case GEOMETRY_64_32: + this->displayWidth = 64; + this->displayHeight = 32; + break; + case GEOMETRY_RAWMODE: + this->displayWidth = width > 0 ? width : 128; + this->displayHeight = height > 0 ? height : 64; + break; + } + this->displayBufferSize = displayWidth * displayHeight / 8; +} + +void OLEDDisplay::sendInitCommands(void) { + if (geometry == GEOMETRY_RAWMODE) + return; + sendCommand(DISPLAYOFF); + sendCommand(SETDISPLAYCLOCKDIV); + sendCommand(0xF0); // Increase speed of the display max ~96Hz + sendCommand(SETMULTIPLEX); + sendCommand(this->height() - 1); + sendCommand(SETDISPLAYOFFSET); + sendCommand(0x00); + if(geometry == GEOMETRY_64_32) + sendCommand(0x00); + else + sendCommand(SETSTARTLINE); + sendCommand(CHARGEPUMP); + sendCommand(0x14); + sendCommand(MEMORYMODE); + sendCommand(0x00); + sendCommand(SEGREMAP); + sendCommand(COMSCANINC); + sendCommand(SETCOMPINS); + + if (geometry == GEOMETRY_128_128 || geometry == GEOMETRY_128_64 || geometry == GEOMETRY_64_48 || geometry == GEOMETRY_64_32) { + sendCommand(0x12); + } else if (geometry == GEOMETRY_128_32) { + sendCommand(0x02); + } + + sendCommand(SETCONTRAST); + + if (geometry == GEOMETRY_128_128 || geometry == GEOMETRY_128_64 || geometry == GEOMETRY_64_48 || geometry == GEOMETRY_64_32) { + sendCommand(0xCF); + } else if (geometry == GEOMETRY_128_32) { + sendCommand(0x8F); + } + + sendCommand(SETPRECHARGE); + sendCommand(0xF1); + sendCommand(SETVCOMDETECT); //0xDB, (additionally needed to lower the contrast) + sendCommand(0x40); //0x40 default, to lower the contrast, put 0 + sendCommand(DISPLAYALLON_RESUME); + sendCommand(NORMALDISPLAY); + sendCommand(0x2e); // stop scroll + sendCommand(DISPLAYON); +} + +void inline OLEDDisplay::drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *data, uint16_t offset, uint16_t bytesInData) { + if (width < 0 || height < 0) return; + if (yMove + height < 0 || yMove > this->height()) return; + if (xMove + width < 0 || xMove > this->width()) return; + + uint8_t rasterHeight = 1 + ((height - 1) >> 3); // fast ceil(height / 8.0) + int8_t yOffset = yMove & 7; + + bytesInData = bytesInData == 0 ? width * rasterHeight : bytesInData; + + int16_t initYMove = yMove; + int8_t initYOffset = yOffset; + + + for (uint16_t i = 0; i < bytesInData; i++) { + + // Reset if next horizontal drawing phase is started. + if ( i % rasterHeight == 0) { + yMove = initYMove; + yOffset = initYOffset; + } + + uint8_t currentByte = pgm_read_byte(data + offset + i); + + int16_t xPos = xMove + (i / rasterHeight); + int16_t yPos = ((yMove >> 3) + (i % rasterHeight)) * this->width(); + +// int16_t yScreenPos = yMove + yOffset; + int16_t dataPos = xPos + yPos; + + if (dataPos >= 0 && dataPos < displayBufferSize && + xPos >= 0 && xPos < this->width() ) { + + if (yOffset >= 0) { + switch (this->color) { + case WHITE: buffer[dataPos] |= currentByte << yOffset; break; + case BLACK: buffer[dataPos] &= ~(currentByte << yOffset); break; + case INVERSE: buffer[dataPos] ^= currentByte << yOffset; break; + } + + if (dataPos < (displayBufferSize - this->width())) { + switch (this->color) { + case WHITE: buffer[dataPos + this->width()] |= currentByte >> (8 - yOffset); break; + case BLACK: buffer[dataPos + this->width()] &= ~(currentByte >> (8 - yOffset)); break; + case INVERSE: buffer[dataPos + this->width()] ^= currentByte >> (8 - yOffset); break; + } + } + } else { + // Make new offset position + yOffset = -yOffset; + + switch (this->color) { + case WHITE: buffer[dataPos] |= currentByte >> yOffset; break; + case BLACK: buffer[dataPos] &= ~(currentByte >> yOffset); break; + case INVERSE: buffer[dataPos] ^= currentByte >> yOffset; break; + } + + // Prepare for next iteration by moving one block up + yMove -= 8; + + // and setting the new yOffset + yOffset = 8 - yOffset; + } +#ifndef __MBED__ + yield(); +#endif + } + } +} + +// You need to free the char! +char* OLEDDisplay::utf8ascii(const String &str) { + uint16_t k = 0; + uint16_t length = str.length() + 1; + + // Copy the string into a char array + char* s = (char*) malloc(length * sizeof(char)); + if(!s) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][utf8ascii] Can't allocate another char array. Drop support for UTF-8.\n"); + return (char*) str.c_str(); + } + str.toCharArray(s, length); + + length--; + + for (uint16_t i=0; i < length; i++) { + char c = (this->fontTableLookupFunction)(s[i]); + if (c!=0) { + s[k++]=c; + } + } + + s[k]=0; + + // This will leak 's' be sure to free it in the calling function. + return s; +} + +void OLEDDisplay::setFontTableLookupFunction(FontTableLookupFunction function) { + this->fontTableLookupFunction = function; +} + + +char DefaultFontTableLookup(const uint8_t ch) { + // UTF-8 to font table index converter + // Code form http://playground.arduino.cc/Main/Utf8ascii + static uint8_t LASTCHAR; + + if (ch < 128) { // Standard ASCII-set 0..0x7F handling + LASTCHAR = 0; + return ch; + } + + uint8_t last = LASTCHAR; // get last char + LASTCHAR = ch; + + switch (last) { // conversion depnding on first UTF8-character + case 0xC2: return (uint8_t) ch; + case 0xC3: return (uint8_t) (ch | 0xC0); + case 0x82: if (ch == 0xAC) return (uint8_t) 0x80; // special case Euro-symbol + } + + return (uint8_t) 0; // otherwise: return zero, if character has to be ignored +} diff --git a/src/helpers/ui/OLEDDisplay.h b/src/helpers/ui/OLEDDisplay.h new file mode 100644 index 00000000..23e68455 --- /dev/null +++ b/src/helpers/ui/OLEDDisplay.h @@ -0,0 +1,395 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + +#ifndef OLEDDISPLAY_h +#define OLEDDISPLAY_h + +#include + +#ifdef ARDUINO +#include +#elif __MBED__ +#define pgm_read_byte(addr) (*(const unsigned char *)(addr)) + +#include +#define delay(x) wait_ms(x) +#define yield() void() + +/* + * This is a little Arduino String emulation to keep the OLEDDisplay + * library code in common between Arduino and mbed-os + */ +class String { +public: + String(const char *s) { _str = s; }; + int length() { return strlen(_str); }; + const char *c_str() { return _str; }; + void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const { + memcpy(buf, _str + index, std::min(bufsize, strlen(_str))); + }; +private: + const char *_str; +}; + +#else +#error "Unkown operating system" +#endif + +#include "OLEDDisplayFonts.h" + +//#define DEBUG_OLEDDISPLAY(...) Serial.printf( __VA_ARGS__ ) +//#define DEBUG_OLEDDISPLAY(...) dprintf("%s", __VA_ARGS__ ) + +#ifndef DEBUG_OLEDDISPLAY +#define DEBUG_OLEDDISPLAY(...) +#endif + +// Use DOUBLE BUFFERING by default +#ifndef OLEDDISPLAY_REDUCE_MEMORY +#define OLEDDISPLAY_DOUBLE_BUFFER +#endif + +// Header Values +#define JUMPTABLE_BYTES 4 + +#define JUMPTABLE_LSB 1 +#define JUMPTABLE_SIZE 2 +#define JUMPTABLE_WIDTH 3 +#define JUMPTABLE_START 4 + +#define WIDTH_POS 0 +#define HEIGHT_POS 1 +#define FIRST_CHAR_POS 2 +#define CHAR_NUM_POS 3 + + +// Display commands +#define CHARGEPUMP 0x8D +#define COLUMNADDR 0x21 +#define COMSCANDEC 0xC8 +#define COMSCANINC 0xC0 +#define DISPLAYALLON 0xA5 +#define DISPLAYALLON_RESUME 0xA4 +#define DISPLAYOFF 0xAE +#define DISPLAYON 0xAF +#define EXTERNALVCC 0x1 +#define INVERTDISPLAY 0xA7 +#define MEMORYMODE 0x20 +#define NORMALDISPLAY 0xA6 +#define PAGEADDR 0x22 +#define SEGREMAP 0xA0 +#define SETCOMPINS 0xDA +#define SETCONTRAST 0x81 +#define SETDISPLAYCLOCKDIV 0xD5 +#define SETDISPLAYOFFSET 0xD3 +#define SETHIGHCOLUMN 0x10 +#define SETLOWCOLUMN 0x00 +#define SETMULTIPLEX 0xA8 +#define SETPRECHARGE 0xD9 +#define SETSEGMENTREMAP 0xA1 +#define SETSTARTLINE 0x40 +#define SETVCOMDETECT 0xDB +#define SWITCHCAPVCC 0x2 + +#ifndef _swap_int16_t +#define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; } +#endif + +enum OLEDDISPLAY_COLOR { + BLACK = 0, + WHITE = 1, + INVERSE = 2 +}; + +enum OLEDDISPLAY_TEXT_ALIGNMENT { + TEXT_ALIGN_LEFT = 0, + TEXT_ALIGN_RIGHT = 1, + TEXT_ALIGN_CENTER = 2, + TEXT_ALIGN_CENTER_BOTH = 3 +}; + + +enum OLEDDISPLAY_GEOMETRY { + GEOMETRY_128_64 = 0, + GEOMETRY_128_32 = 1, + GEOMETRY_64_48 = 2, + GEOMETRY_64_32 = 3, + GEOMETRY_RAWMODE = 4, + GEOMETRY_128_128 = 5 +}; + +enum HW_I2C { + I2C_ONE, + I2C_TWO +}; + +typedef char (*FontTableLookupFunction)(const uint8_t ch); +char DefaultFontTableLookup(const uint8_t ch); + + +#ifdef ARDUINO +class OLEDDisplay : public Print { +#elif __MBED__ +class OLEDDisplay : public Stream { +#else +#error "Unkown operating system" +#endif + + public: + OLEDDisplay(); + virtual ~OLEDDisplay(); + + uint16_t width(void) const { return displayWidth; }; + uint16_t height(void) const { return displayHeight; }; + + // Use this to resume after a deep sleep without resetting the display (what init() would do). + // Returns true if connection to the display was established and the buffer allocated, false otherwise. + bool allocateBuffer(); + + // Allocates the buffer and initializes the driver & display. Resets the display! + // Returns false if buffer allocation failed, true otherwise. + bool init(); + + // Free the memory used by the display + void end(); + + // Cycle through the initialization + void resetDisplay(void); + + /* Drawing functions */ + // Sets the color of all pixel operations + void setColor(OLEDDISPLAY_COLOR color); + + // Returns the current color. + OLEDDISPLAY_COLOR getColor(); + + // Draw a pixel at given position + void setPixel(int16_t x, int16_t y); + + // Draw a pixel at given position and color + void setPixelColor(int16_t x, int16_t y, OLEDDISPLAY_COLOR color); + + // Clear a pixel at given position FIXME: INVERSE is untested with this function + void clearPixel(int16_t x, int16_t y); + + // Draw a line from position 0 to position 1 + void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1); + + // Draw the border of a rectangle at the given location + void drawRect(int16_t x, int16_t y, int16_t width, int16_t height); + + // Fill the rectangle + void fillRect(int16_t x, int16_t y, int16_t width, int16_t height); + + // Draw the border of a circle + void drawCircle(int16_t x, int16_t y, int16_t radius); + + // Draw all Quadrants specified in the quads bit mask + void drawCircleQuads(int16_t x0, int16_t y0, int16_t radius, uint8_t quads); + + // Fill circle + void fillCircle(int16_t x, int16_t y, int16_t radius); + + // Draw an empty triangle i.e. only the outline + void drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2); + + // Draw a solid triangle i.e. filled + void fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2); + + // Draw a line horizontally + void drawHorizontalLine(int16_t x, int16_t y, int16_t length); + + // Draw a line vertically + void drawVerticalLine(int16_t x, int16_t y, int16_t length); + + // Draws a rounded progress bar with the outer dimensions given by width and height. Progress is + // a unsigned byte value between 0 and 100 + void drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress); + + // Draw a bitmap in the internal image format + void drawFastImage(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *image); + + // Draw a XBM + void drawXbm(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *xbm); + + // Draw icon 16x16 xbm format + void drawIco16x16(int16_t x, int16_t y, const uint8_t *ico, bool inverse = false); + + /* Text functions */ + + // Draws a string at the given location, returns how many chars have been written + uint16_t drawString(int16_t x, int16_t y, const String &text); + + // Draws a formatted string (like printf) at the given location + void drawStringf(int16_t x, int16_t y, char* buffer, String format, ... ); + + // Draws a String with a maximum width at the given location. + // If the given String is wider than the specified width + // The text will be wrapped to the next line at a space or dash + // returns 0 if everything fits on the screen or the numbers of characters in the + // first line if not + uint16_t drawStringMaxWidth(int16_t x, int16_t y, uint16_t maxLineWidth, const String &text); + + // Returns the width of the const char* with the current + // font settings + uint16_t getStringWidth(const char* text, uint16_t length, bool utf8 = false); + + // Convencience method for the const char version + uint16_t getStringWidth(const String &text); + + // Specifies relative to which anchor point + // the text is rendered. Available constants: + // TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER_BOTH + void setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment); + + // Sets the current font. Available default fonts + // ArialMT_Plain_10, ArialMT_Plain_16, ArialMT_Plain_24 + void setFont(const uint8_t *fontData); + + // Set the function that will convert utf-8 to font table index + void setFontTableLookupFunction(FontTableLookupFunction function); + + /* Display functions */ + + // Turn the display on + void displayOn(void); + + // Turn the display offs + void displayOff(void); + + // Inverted display mode + void invertDisplay(void); + + // Normal display mode + void normalDisplay(void); + + // Set display contrast + // really low brightness & contrast: contrast = 10, precharge = 5, comdetect = 0 + // normal brightness & contrast: contrast = 100 + void setContrast(uint8_t contrast, uint8_t precharge = 241, uint8_t comdetect = 64); + + // Convenience method to access + virtual void setBrightness(uint8_t); + + // Reset display rotation or mirroring + void resetOrientation(); + + // Turn the display upside down + void flipScreenVertically(); + + // Mirror the display (to be used in a mirror or as a projector) + void mirrorScreen(); + + // Write the buffer to the display memory + virtual void display(void) = 0; + + // Clear the local pixel buffer + void clear(void); + + // Log buffer implementation + + // This will define the lines and characters you can + // print to the screen. When you exeed the buffer size (lines * chars) + // the output may be truncated due to the size constraint. + bool setLogBuffer(uint16_t lines, uint16_t chars); + + // Draw the log buffer at position (x, y) + void drawLogBuffer(uint16_t x, uint16_t y); + + // Get screen geometry + uint16_t getWidth(void); + uint16_t getHeight(void); + + // Implement needed function to be compatible with Print class + size_t write(uint8_t c); + size_t write(const char* s); + + // Implement needed function to be compatible with Stream class +#ifdef __MBED__ + int _putc(int c); + int _getc() { return -1; }; +#endif + + + uint8_t *buffer; + + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + uint8_t *buffer_back; + #endif + + // Set the correct height, width and buffer for the geometry + void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width = 0, uint16_t height = 0); + + protected: + + OLEDDISPLAY_GEOMETRY geometry; + + uint16_t displayWidth; + uint16_t displayHeight; + uint16_t displayBufferSize; + + OLEDDISPLAY_TEXT_ALIGNMENT textAlignment; + OLEDDISPLAY_COLOR color; + + const uint8_t *fontData; + + // State values for logBuffer + uint16_t logBufferSize; + uint16_t logBufferFilled; + uint16_t logBufferLine; + uint16_t logBufferMaxLines; + char *logBuffer; + + + // the header size of the buffer used, e.g. for the SPI command header + int BufferOffset; + virtual int getBufferOffset(void) = 0; + + // Send a command to the display (low level function) + virtual void sendCommand(uint8_t com) {(void)com;}; + + // Connect to the display + virtual bool connect() { return false; }; + + // Send all the init commands + virtual void sendInitCommands(); + + // converts utf8 characters to extended ascii + char* utf8ascii(const String &s); + + void inline drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *data, uint16_t offset, uint16_t bytesInData) __attribute__((always_inline)); + + uint16_t drawStringInternal(int16_t xMove, int16_t yMove, const char* text, uint16_t textLength, uint16_t textWidth, bool utf8); + + FontTableLookupFunction fontTableLookupFunction; +}; + +#endif diff --git a/src/helpers/ui/OLEDDisplayFonts.cpp b/src/helpers/ui/OLEDDisplayFonts.cpp new file mode 100644 index 00000000..660eb6a3 --- /dev/null +++ b/src/helpers/ui/OLEDDisplayFonts.cpp @@ -0,0 +1,1273 @@ +#include "OLEDDisplayFonts.h" + +const uint8_t ArialMT_Plain_10[] PROGMEM = { + 0x0A, // Width: 10 + 0x0D, // Height: 13 + 0x20, // First Char: 32 + 0xE0, // Numbers of Chars: 224 + + // Jump Table: + 0xFF, 0xFF, 0x00, 0x03, // 32:65535 + 0x00, 0x00, 0x04, 0x03, // 33:0 + 0x00, 0x04, 0x05, 0x04, // 34:4 + 0x00, 0x09, 0x09, 0x06, // 35:9 + 0x00, 0x12, 0x0A, 0x06, // 36:18 + 0x00, 0x1C, 0x10, 0x09, // 37:28 + 0x00, 0x2C, 0x0E, 0x07, // 38:44 + 0x00, 0x3A, 0x01, 0x02, // 39:58 + 0x00, 0x3B, 0x06, 0x03, // 40:59 + 0x00, 0x41, 0x06, 0x03, // 41:65 + 0x00, 0x47, 0x05, 0x04, // 42:71 + 0x00, 0x4C, 0x09, 0x06, // 43:76 + 0x00, 0x55, 0x04, 0x03, // 44:85 + 0x00, 0x59, 0x03, 0x03, // 45:89 + 0x00, 0x5C, 0x04, 0x03, // 46:92 + 0x00, 0x60, 0x05, 0x03, // 47:96 + 0x00, 0x65, 0x0A, 0x06, // 48:101 + 0x00, 0x6F, 0x08, 0x06, // 49:111 + 0x00, 0x77, 0x0A, 0x06, // 50:119 + 0x00, 0x81, 0x0A, 0x06, // 51:129 + 0x00, 0x8B, 0x0B, 0x06, // 52:139 + 0x00, 0x96, 0x0A, 0x06, // 53:150 + 0x00, 0xA0, 0x0A, 0x06, // 54:160 + 0x00, 0xAA, 0x09, 0x06, // 55:170 + 0x00, 0xB3, 0x0A, 0x06, // 56:179 + 0x00, 0xBD, 0x0A, 0x06, // 57:189 + 0x00, 0xC7, 0x04, 0x03, // 58:199 + 0x00, 0xCB, 0x04, 0x03, // 59:203 + 0x00, 0xCF, 0x0A, 0x06, // 60:207 + 0x00, 0xD9, 0x09, 0x06, // 61:217 + 0x00, 0xE2, 0x09, 0x06, // 62:226 + 0x00, 0xEB, 0x0B, 0x06, // 63:235 + 0x00, 0xF6, 0x14, 0x0A, // 64:246 + 0x01, 0x0A, 0x0E, 0x07, // 65:266 + 0x01, 0x18, 0x0C, 0x07, // 66:280 + 0x01, 0x24, 0x0C, 0x07, // 67:292 + 0x01, 0x30, 0x0B, 0x07, // 68:304 + 0x01, 0x3B, 0x0C, 0x07, // 69:315 + 0x01, 0x47, 0x09, 0x06, // 70:327 + 0x01, 0x50, 0x0D, 0x08, // 71:336 + 0x01, 0x5D, 0x0C, 0x07, // 72:349 + 0x01, 0x69, 0x04, 0x03, // 73:361 + 0x01, 0x6D, 0x08, 0x05, // 74:365 + 0x01, 0x75, 0x0E, 0x07, // 75:373 + 0x01, 0x83, 0x0C, 0x06, // 76:387 + 0x01, 0x8F, 0x10, 0x08, // 77:399 + 0x01, 0x9F, 0x0C, 0x07, // 78:415 + 0x01, 0xAB, 0x0E, 0x08, // 79:427 + 0x01, 0xB9, 0x0B, 0x07, // 80:441 + 0x01, 0xC4, 0x0E, 0x08, // 81:452 + 0x01, 0xD2, 0x0C, 0x07, // 82:466 + 0x01, 0xDE, 0x0C, 0x07, // 83:478 + 0x01, 0xEA, 0x0B, 0x06, // 84:490 + 0x01, 0xF5, 0x0C, 0x07, // 85:501 + 0x02, 0x01, 0x0D, 0x07, // 86:513 + 0x02, 0x0E, 0x11, 0x09, // 87:526 + 0x02, 0x1F, 0x0E, 0x07, // 88:543 + 0x02, 0x2D, 0x0D, 0x07, // 89:557 + 0x02, 0x3A, 0x0C, 0x06, // 90:570 + 0x02, 0x46, 0x06, 0x03, // 91:582 + 0x02, 0x4C, 0x06, 0x03, // 92:588 + 0x02, 0x52, 0x04, 0x03, // 93:594 + 0x02, 0x56, 0x09, 0x05, // 94:598 + 0x02, 0x5F, 0x0C, 0x06, // 95:607 + 0x02, 0x6B, 0x03, 0x03, // 96:619 + 0x02, 0x6E, 0x0A, 0x06, // 97:622 + 0x02, 0x78, 0x0A, 0x06, // 98:632 + 0x02, 0x82, 0x0A, 0x05, // 99:642 + 0x02, 0x8C, 0x0A, 0x06, // 100:652 + 0x02, 0x96, 0x0A, 0x06, // 101:662 + 0x02, 0xA0, 0x05, 0x03, // 102:672 + 0x02, 0xA5, 0x0A, 0x06, // 103:677 + 0x02, 0xAF, 0x0A, 0x06, // 104:687 + 0x02, 0xB9, 0x04, 0x02, // 105:697 + 0x02, 0xBD, 0x04, 0x02, // 106:701 + 0x02, 0xC1, 0x08, 0x05, // 107:705 + 0x02, 0xC9, 0x04, 0x02, // 108:713 + 0x02, 0xCD, 0x10, 0x08, // 109:717 + 0x02, 0xDD, 0x0A, 0x06, // 110:733 + 0x02, 0xE7, 0x0A, 0x06, // 111:743 + 0x02, 0xF1, 0x0A, 0x06, // 112:753 + 0x02, 0xFB, 0x0A, 0x06, // 113:763 + 0x03, 0x05, 0x05, 0x03, // 114:773 + 0x03, 0x0A, 0x08, 0x05, // 115:778 + 0x03, 0x12, 0x06, 0x03, // 116:786 + 0x03, 0x18, 0x0A, 0x06, // 117:792 + 0x03, 0x22, 0x09, 0x05, // 118:802 + 0x03, 0x2B, 0x0E, 0x07, // 119:811 + 0x03, 0x39, 0x0A, 0x05, // 120:825 + 0x03, 0x43, 0x09, 0x05, // 121:835 + 0x03, 0x4C, 0x0A, 0x05, // 122:844 + 0x03, 0x56, 0x06, 0x03, // 123:854 + 0x03, 0x5C, 0x04, 0x03, // 124:860 + 0x03, 0x60, 0x05, 0x03, // 125:864 + 0x03, 0x65, 0x09, 0x06, // 126:869 + 0xFF, 0xFF, 0x00, 0x00, // 127:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 128:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 129:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 130:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 131:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 132:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 133:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 134:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 135:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 136:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 137:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 138:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 139:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 140:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 141:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 142:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 143:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 144:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 145:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 146:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 147:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 148:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 149:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 150:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 151:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 152:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 153:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 154:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 155:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 156:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 157:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 158:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 159:65535 + 0xFF, 0xFF, 0x00, 0x03, // 160:65535 + 0x03, 0x6E, 0x04, 0x03, // 161:878 + 0x03, 0x72, 0x0A, 0x06, // 162:882 + 0x03, 0x7C, 0x0C, 0x06, // 163:892 + 0x03, 0x88, 0x0A, 0x06, // 164:904 + 0x03, 0x92, 0x0A, 0x06, // 165:914 + 0x03, 0x9C, 0x04, 0x03, // 166:924 + 0x03, 0xA0, 0x0A, 0x06, // 167:928 + 0x03, 0xAA, 0x05, 0x03, // 168:938 + 0x03, 0xAF, 0x0D, 0x07, // 169:943 + 0x03, 0xBC, 0x07, 0x04, // 170:956 + 0x03, 0xC3, 0x0A, 0x06, // 171:963 + 0x03, 0xCD, 0x09, 0x06, // 172:973 + 0x03, 0xD6, 0x03, 0x03, // 173:982 + 0x03, 0xD9, 0x0D, 0x07, // 174:985 + 0x03, 0xE6, 0x0B, 0x06, // 175:998 + 0x03, 0xF1, 0x07, 0x04, // 176:1009 + 0x03, 0xF8, 0x0A, 0x05, // 177:1016 + 0x04, 0x02, 0x05, 0x03, // 178:1026 + 0x04, 0x07, 0x05, 0x03, // 179:1031 + 0x04, 0x0C, 0x05, 0x03, // 180:1036 + 0x04, 0x11, 0x0A, 0x06, // 181:1041 + 0x04, 0x1B, 0x09, 0x05, // 182:1051 + 0x04, 0x24, 0x03, 0x03, // 183:1060 + 0x04, 0x27, 0x06, 0x03, // 184:1063 + 0x04, 0x2D, 0x05, 0x03, // 185:1069 + 0x04, 0x32, 0x07, 0x04, // 186:1074 + 0x04, 0x39, 0x0A, 0x06, // 187:1081 + 0x04, 0x43, 0x10, 0x08, // 188:1091 + 0x04, 0x53, 0x10, 0x08, // 189:1107 + 0x04, 0x63, 0x10, 0x08, // 190:1123 + 0x04, 0x73, 0x0A, 0x06, // 191:1139 + 0x04, 0x7D, 0x0E, 0x07, // 192:1149 + 0x04, 0x8B, 0x0E, 0x07, // 193:1163 + 0x04, 0x99, 0x0E, 0x07, // 194:1177 + 0x04, 0xA7, 0x0E, 0x07, // 195:1191 + 0x04, 0xB5, 0x0E, 0x07, // 196:1205 + 0x04, 0xC3, 0x0E, 0x07, // 197:1219 + 0x04, 0xD1, 0x12, 0x0A, // 198:1233 + 0x04, 0xE3, 0x0C, 0x07, // 199:1251 + 0x04, 0xEF, 0x0C, 0x07, // 200:1263 + 0x04, 0xFB, 0x0C, 0x07, // 201:1275 + 0x05, 0x07, 0x0C, 0x07, // 202:1287 + 0x05, 0x13, 0x0C, 0x07, // 203:1299 + 0x05, 0x1F, 0x05, 0x03, // 204:1311 + 0x05, 0x24, 0x04, 0x03, // 205:1316 + 0x05, 0x28, 0x04, 0x03, // 206:1320 + 0x05, 0x2C, 0x05, 0x03, // 207:1324 + 0x05, 0x31, 0x0B, 0x07, // 208:1329 + 0x05, 0x3C, 0x0C, 0x07, // 209:1340 + 0x05, 0x48, 0x0E, 0x08, // 210:1352 + 0x05, 0x56, 0x0E, 0x08, // 211:1366 + 0x05, 0x64, 0x0E, 0x08, // 212:1380 + 0x05, 0x72, 0x0E, 0x08, // 213:1394 + 0x05, 0x80, 0x0E, 0x08, // 214:1408 + 0x05, 0x8E, 0x0A, 0x06, // 215:1422 + 0x05, 0x98, 0x0D, 0x08, // 216:1432 + 0x05, 0xA5, 0x0C, 0x07, // 217:1445 + 0x05, 0xB1, 0x0C, 0x07, // 218:1457 + 0x05, 0xBD, 0x0C, 0x07, // 219:1469 + 0x05, 0xC9, 0x0C, 0x07, // 220:1481 + 0x05, 0xD5, 0x0D, 0x07, // 221:1493 + 0x05, 0xE2, 0x0B, 0x07, // 222:1506 + 0x05, 0xED, 0x0C, 0x06, // 223:1517 + 0x05, 0xF9, 0x0A, 0x06, // 224:1529 + 0x06, 0x03, 0x0A, 0x06, // 225:1539 + 0x06, 0x0D, 0x0A, 0x06, // 226:1549 + 0x06, 0x17, 0x0A, 0x06, // 227:1559 + 0x06, 0x21, 0x0A, 0x06, // 228:1569 + 0x06, 0x2B, 0x0A, 0x06, // 229:1579 + 0x06, 0x35, 0x10, 0x09, // 230:1589 + 0x06, 0x45, 0x0A, 0x05, // 231:1605 + 0x06, 0x4F, 0x0A, 0x06, // 232:1615 + 0x06, 0x59, 0x0A, 0x06, // 233:1625 + 0x06, 0x63, 0x0A, 0x06, // 234:1635 + 0x06, 0x6D, 0x0A, 0x06, // 235:1645 + 0x06, 0x77, 0x05, 0x03, // 236:1655 + 0x06, 0x7C, 0x04, 0x03, // 237:1660 + 0x06, 0x80, 0x05, 0x03, // 238:1664 + 0x06, 0x85, 0x05, 0x03, // 239:1669 + 0x06, 0x8A, 0x0A, 0x06, // 240:1674 + 0x06, 0x94, 0x0A, 0x06, // 241:1684 + 0x06, 0x9E, 0x0A, 0x06, // 242:1694 + 0x06, 0xA8, 0x0A, 0x06, // 243:1704 + 0x06, 0xB2, 0x0A, 0x06, // 244:1714 + 0x06, 0xBC, 0x0A, 0x06, // 245:1724 + 0x06, 0xC6, 0x0A, 0x06, // 246:1734 + 0x06, 0xD0, 0x09, 0x05, // 247:1744 + 0x06, 0xD9, 0x0A, 0x06, // 248:1753 + 0x06, 0xE3, 0x0A, 0x06, // 249:1763 + 0x06, 0xED, 0x0A, 0x06, // 250:1773 + 0x06, 0xF7, 0x0A, 0x06, // 251:1783 + 0x07, 0x01, 0x0A, 0x06, // 252:1793 + 0x07, 0x0B, 0x09, 0x05, // 253:1803 + 0x07, 0x14, 0x0A, 0x06, // 254:1812 + 0x07, 0x1E, 0x09, 0x05, // 255:1822 + + // Font Data: + 0x00,0x00,0xF8,0x02, // 33 + 0x38,0x00,0x00,0x00,0x38, // 34 + 0xA0,0x03,0xE0,0x00,0xB8,0x03,0xE0,0x00,0xB8, // 35 + 0x30,0x01,0x28,0x02,0xF8,0x07,0x48,0x02,0x90,0x01, // 36 + 0x00,0x00,0x30,0x00,0x48,0x00,0x30,0x03,0xC0,0x00,0xB0,0x01,0x48,0x02,0x80,0x01, // 37 + 0x80,0x01,0x50,0x02,0x68,0x02,0xA8,0x02,0x18,0x01,0x80,0x03,0x80,0x02, // 38 + 0x38, // 39 + 0xE0,0x03,0x10,0x04,0x08,0x08, // 40 + 0x08,0x08,0x10,0x04,0xE0,0x03, // 41 + 0x28,0x00,0x18,0x00,0x28, // 42 + 0x40,0x00,0x40,0x00,0xF0,0x01,0x40,0x00,0x40, // 43 + 0x00,0x00,0x00,0x06, // 44 + 0x80,0x00,0x80, // 45 + 0x00,0x00,0x00,0x02, // 46 + 0x00,0x03,0xE0,0x00,0x18, // 47 + 0xF0,0x01,0x08,0x02,0x08,0x02,0x08,0x02,0xF0,0x01, // 48 + 0x00,0x00,0x20,0x00,0x10,0x00,0xF8,0x03, // 49 + 0x10,0x02,0x08,0x03,0x88,0x02,0x48,0x02,0x30,0x02, // 50 + 0x10,0x01,0x08,0x02,0x48,0x02,0x48,0x02,0xB0,0x01, // 51 + 0xC0,0x00,0xA0,0x00,0x90,0x00,0x88,0x00,0xF8,0x03,0x80, // 52 + 0x60,0x01,0x38,0x02,0x28,0x02,0x28,0x02,0xC8,0x01, // 53 + 0xF0,0x01,0x28,0x02,0x28,0x02,0x28,0x02,0xD0,0x01, // 54 + 0x08,0x00,0x08,0x03,0xC8,0x00,0x38,0x00,0x08, // 55 + 0xB0,0x01,0x48,0x02,0x48,0x02,0x48,0x02,0xB0,0x01, // 56 + 0x70,0x01,0x88,0x02,0x88,0x02,0x88,0x02,0xF0,0x01, // 57 + 0x00,0x00,0x20,0x02, // 58 + 0x00,0x00,0x20,0x06, // 59 + 0x00,0x00,0x40,0x00,0xA0,0x00,0xA0,0x00,0x10,0x01, // 60 + 0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0, // 61 + 0x00,0x00,0x10,0x01,0xA0,0x00,0xA0,0x00,0x40, // 62 + 0x10,0x00,0x08,0x00,0x08,0x00,0xC8,0x02,0x48,0x00,0x30, // 63 + 0x00,0x00,0xC0,0x03,0x30,0x04,0xD0,0x09,0x28,0x0A,0x28,0x0A,0xC8,0x0B,0x68,0x0A,0x10,0x05,0xE0,0x04, // 64 + 0x00,0x02,0xC0,0x01,0xB0,0x00,0x88,0x00,0xB0,0x00,0xC0,0x01,0x00,0x02, // 65 + 0x00,0x00,0xF8,0x03,0x48,0x02,0x48,0x02,0x48,0x02,0xF0,0x01, // 66 + 0x00,0x00,0xF0,0x01,0x08,0x02,0x08,0x02,0x08,0x02,0x10,0x01, // 67 + 0x00,0x00,0xF8,0x03,0x08,0x02,0x08,0x02,0x10,0x01,0xE0, // 68 + 0x00,0x00,0xF8,0x03,0x48,0x02,0x48,0x02,0x48,0x02,0x48,0x02, // 69 + 0x00,0x00,0xF8,0x03,0x48,0x00,0x48,0x00,0x08, // 70 + 0x00,0x00,0xE0,0x00,0x10,0x01,0x08,0x02,0x48,0x02,0x50,0x01,0xC0, // 71 + 0x00,0x00,0xF8,0x03,0x40,0x00,0x40,0x00,0x40,0x00,0xF8,0x03, // 72 + 0x00,0x00,0xF8,0x03, // 73 + 0x00,0x03,0x00,0x02,0x00,0x02,0xF8,0x01, // 74 + 0x00,0x00,0xF8,0x03,0x80,0x00,0x60,0x00,0x90,0x00,0x08,0x01,0x00,0x02, // 75 + 0x00,0x00,0xF8,0x03,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02, // 76 + 0x00,0x00,0xF8,0x03,0x30,0x00,0xC0,0x01,0x00,0x02,0xC0,0x01,0x30,0x00,0xF8,0x03, // 77 + 0x00,0x00,0xF8,0x03,0x30,0x00,0x40,0x00,0x80,0x01,0xF8,0x03, // 78 + 0x00,0x00,0xF0,0x01,0x08,0x02,0x08,0x02,0x08,0x02,0x08,0x02,0xF0,0x01, // 79 + 0x00,0x00,0xF8,0x03,0x48,0x00,0x48,0x00,0x48,0x00,0x30, // 80 + 0x00,0x00,0xF0,0x01,0x08,0x02,0x08,0x02,0x08,0x03,0x08,0x03,0xF0,0x02, // 81 + 0x00,0x00,0xF8,0x03,0x48,0x00,0x48,0x00,0xC8,0x00,0x30,0x03, // 82 + 0x00,0x00,0x30,0x01,0x48,0x02,0x48,0x02,0x48,0x02,0x90,0x01, // 83 + 0x00,0x00,0x08,0x00,0x08,0x00,0xF8,0x03,0x08,0x00,0x08, // 84 + 0x00,0x00,0xF8,0x01,0x00,0x02,0x00,0x02,0x00,0x02,0xF8,0x01, // 85 + 0x08,0x00,0x70,0x00,0x80,0x01,0x00,0x02,0x80,0x01,0x70,0x00,0x08, // 86 + 0x18,0x00,0xE0,0x01,0x00,0x02,0xF0,0x01,0x08,0x00,0xF0,0x01,0x00,0x02,0xE0,0x01,0x18, // 87 + 0x00,0x02,0x08,0x01,0x90,0x00,0x60,0x00,0x90,0x00,0x08,0x01,0x00,0x02, // 88 + 0x08,0x00,0x10,0x00,0x20,0x00,0xC0,0x03,0x20,0x00,0x10,0x00,0x08, // 89 + 0x08,0x03,0x88,0x02,0xC8,0x02,0x68,0x02,0x38,0x02,0x18,0x02, // 90 + 0x00,0x00,0xF8,0x0F,0x08,0x08, // 91 + 0x18,0x00,0xE0,0x00,0x00,0x03, // 92 + 0x08,0x08,0xF8,0x0F, // 93 + 0x40,0x00,0x30,0x00,0x08,0x00,0x30,0x00,0x40, // 94 + 0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x08, // 95 + 0x08,0x00,0x10, // 96 + 0x00,0x00,0x00,0x03,0xA0,0x02,0xA0,0x02,0xE0,0x03, // 97 + 0x00,0x00,0xF8,0x03,0x20,0x02,0x20,0x02,0xC0,0x01, // 98 + 0x00,0x00,0xC0,0x01,0x20,0x02,0x20,0x02,0x40,0x01, // 99 + 0x00,0x00,0xC0,0x01,0x20,0x02,0x20,0x02,0xF8,0x03, // 100 + 0x00,0x00,0xC0,0x01,0xA0,0x02,0xA0,0x02,0xC0,0x02, // 101 + 0x20,0x00,0xF0,0x03,0x28, // 102 + 0x00,0x00,0xC0,0x05,0x20,0x0A,0x20,0x0A,0xE0,0x07, // 103 + 0x00,0x00,0xF8,0x03,0x20,0x00,0x20,0x00,0xC0,0x03, // 104 + 0x00,0x00,0xE8,0x03, // 105 + 0x00,0x08,0xE8,0x07, // 106 + 0xF8,0x03,0x80,0x00,0xC0,0x01,0x20,0x02, // 107 + 0x00,0x00,0xF8,0x03, // 108 + 0x00,0x00,0xE0,0x03,0x20,0x00,0x20,0x00,0xE0,0x03,0x20,0x00,0x20,0x00,0xC0,0x03, // 109 + 0x00,0x00,0xE0,0x03,0x20,0x00,0x20,0x00,0xC0,0x03, // 110 + 0x00,0x00,0xC0,0x01,0x20,0x02,0x20,0x02,0xC0,0x01, // 111 + 0x00,0x00,0xE0,0x0F,0x20,0x02,0x20,0x02,0xC0,0x01, // 112 + 0x00,0x00,0xC0,0x01,0x20,0x02,0x20,0x02,0xE0,0x0F, // 113 + 0x00,0x00,0xE0,0x03,0x20, // 114 + 0x40,0x02,0xA0,0x02,0xA0,0x02,0x20,0x01, // 115 + 0x20,0x00,0xF8,0x03,0x20,0x02, // 116 + 0x00,0x00,0xE0,0x01,0x00,0x02,0x00,0x02,0xE0,0x03, // 117 + 0x20,0x00,0xC0,0x01,0x00,0x02,0xC0,0x01,0x20, // 118 + 0xE0,0x01,0x00,0x02,0xC0,0x01,0x20,0x00,0xC0,0x01,0x00,0x02,0xE0,0x01, // 119 + 0x20,0x02,0x40,0x01,0x80,0x00,0x40,0x01,0x20,0x02, // 120 + 0x20,0x00,0xC0,0x09,0x00,0x06,0xC0,0x01,0x20, // 121 + 0x20,0x02,0x20,0x03,0xA0,0x02,0x60,0x02,0x20,0x02, // 122 + 0x80,0x00,0x78,0x0F,0x08,0x08, // 123 + 0x00,0x00,0xF8,0x0F, // 124 + 0x08,0x08,0x78,0x0F,0x80, // 125 + 0xC0,0x00,0x40,0x00,0xC0,0x00,0x80,0x00,0xC0, // 126 + 0x00,0x00,0xA0,0x0F, // 161 + 0x00,0x00,0xC0,0x01,0xA0,0x0F,0x78,0x02,0x40,0x01, // 162 + 0x40,0x02,0x70,0x03,0xC8,0x02,0x48,0x02,0x08,0x02,0x10,0x02, // 163 + 0x00,0x00,0xE0,0x01,0x20,0x01,0x20,0x01,0xE0,0x01, // 164 + 0x48,0x01,0x70,0x01,0xC0,0x03,0x70,0x01,0x48,0x01, // 165 + 0x00,0x00,0x38,0x0F, // 166 + 0xD0,0x04,0x28,0x09,0x48,0x09,0x48,0x0A,0x90,0x05, // 167 + 0x08,0x00,0x00,0x00,0x08, // 168 + 0xE0,0x00,0x10,0x01,0x48,0x02,0xA8,0x02,0xA8,0x02,0x10,0x01,0xE0, // 169 + 0x68,0x00,0x68,0x00,0x68,0x00,0x78, // 170 + 0x00,0x00,0x80,0x01,0x40,0x02,0x80,0x01,0x40,0x02, // 171 + 0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0xE0, // 172 + 0x80,0x00,0x80, // 173 + 0xE0,0x00,0x10,0x01,0xE8,0x02,0x68,0x02,0xC8,0x02,0x10,0x01,0xE0, // 174 + 0x02,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02, // 175 + 0x00,0x00,0x38,0x00,0x28,0x00,0x38, // 176 + 0x40,0x02,0x40,0x02,0xF0,0x03,0x40,0x02,0x40,0x02, // 177 + 0x48,0x00,0x68,0x00,0x58, // 178 + 0x48,0x00,0x58,0x00,0x68, // 179 + 0x00,0x00,0x10,0x00,0x08, // 180 + 0x00,0x00,0xE0,0x0F,0x00,0x02,0x00,0x02,0xE0,0x03, // 181 + 0x70,0x00,0xF8,0x0F,0x08,0x00,0xF8,0x0F,0x08, // 182 + 0x00,0x00,0x40, // 183 + 0x00,0x00,0x00,0x14,0x00,0x18, // 184 + 0x00,0x00,0x10,0x00,0x78, // 185 + 0x30,0x00,0x48,0x00,0x48,0x00,0x30, // 186 + 0x00,0x00,0x40,0x02,0x80,0x01,0x40,0x02,0x80,0x01, // 187 + 0x00,0x00,0x10,0x02,0x78,0x01,0xC0,0x00,0x20,0x01,0x90,0x01,0xC8,0x03,0x00,0x01, // 188 + 0x00,0x00,0x10,0x02,0x78,0x01,0x80,0x00,0x60,0x00,0x50,0x02,0x48,0x03,0xC0,0x02, // 189 + 0x48,0x00,0x58,0x00,0x68,0x03,0x80,0x00,0x60,0x01,0x90,0x01,0xC8,0x03,0x00,0x01, // 190 + 0x00,0x00,0x00,0x06,0x00,0x09,0xA0,0x09,0x00,0x04, // 191 + 0x00,0x02,0xC0,0x01,0xB0,0x00,0x89,0x00,0xB2,0x00,0xC0,0x01,0x00,0x02, // 192 + 0x00,0x02,0xC0,0x01,0xB0,0x00,0x8A,0x00,0xB1,0x00,0xC0,0x01,0x00,0x02, // 193 + 0x00,0x02,0xC0,0x01,0xB2,0x00,0x89,0x00,0xB2,0x00,0xC0,0x01,0x00,0x02, // 194 + 0x00,0x02,0xC2,0x01,0xB1,0x00,0x8A,0x00,0xB1,0x00,0xC0,0x01,0x00,0x02, // 195 + 0x00,0x02,0xC0,0x01,0xB2,0x00,0x88,0x00,0xB2,0x00,0xC0,0x01,0x00,0x02, // 196 + 0x00,0x02,0xC0,0x01,0xBE,0x00,0x8A,0x00,0xBE,0x00,0xC0,0x01,0x00,0x02, // 197 + 0x00,0x03,0xC0,0x00,0xE0,0x00,0x98,0x00,0x88,0x00,0xF8,0x03,0x48,0x02,0x48,0x02,0x48,0x02, // 198 + 0x00,0x00,0xF0,0x01,0x08,0x02,0x08,0x16,0x08,0x1A,0x10,0x01, // 199 + 0x00,0x00,0xF8,0x03,0x49,0x02,0x4A,0x02,0x48,0x02,0x48,0x02, // 200 + 0x00,0x00,0xF8,0x03,0x48,0x02,0x4A,0x02,0x49,0x02,0x48,0x02, // 201 + 0x00,0x00,0xFA,0x03,0x49,0x02,0x4A,0x02,0x48,0x02,0x48,0x02, // 202 + 0x00,0x00,0xF8,0x03,0x4A,0x02,0x48,0x02,0x4A,0x02,0x48,0x02, // 203 + 0x00,0x00,0xF9,0x03,0x02, // 204 + 0x02,0x00,0xF9,0x03, // 205 + 0x01,0x00,0xFA,0x03, // 206 + 0x02,0x00,0xF8,0x03,0x02, // 207 + 0x40,0x00,0xF8,0x03,0x48,0x02,0x48,0x02,0x10,0x01,0xE0, // 208 + 0x00,0x00,0xFA,0x03,0x31,0x00,0x42,0x00,0x81,0x01,0xF8,0x03, // 209 + 0x00,0x00,0xF0,0x01,0x08,0x02,0x09,0x02,0x0A,0x02,0x08,0x02,0xF0,0x01, // 210 + 0x00,0x00,0xF0,0x01,0x08,0x02,0x0A,0x02,0x09,0x02,0x08,0x02,0xF0,0x01, // 211 + 0x00,0x00,0xF0,0x01,0x08,0x02,0x0A,0x02,0x09,0x02,0x0A,0x02,0xF0,0x01, // 212 + 0x00,0x00,0xF0,0x01,0x0A,0x02,0x09,0x02,0x0A,0x02,0x09,0x02,0xF0,0x01, // 213 + 0x00,0x00,0xF0,0x01,0x0A,0x02,0x08,0x02,0x0A,0x02,0x08,0x02,0xF0,0x01, // 214 + 0x10,0x01,0xA0,0x00,0xE0,0x00,0xA0,0x00,0x10,0x01, // 215 + 0x00,0x00,0xF0,0x02,0x08,0x03,0xC8,0x02,0x28,0x02,0x18,0x03,0xE8, // 216 + 0x00,0x00,0xF8,0x01,0x01,0x02,0x02,0x02,0x00,0x02,0xF8,0x01, // 217 + 0x00,0x00,0xF8,0x01,0x02,0x02,0x01,0x02,0x00,0x02,0xF8,0x01, // 218 + 0x00,0x00,0xF8,0x01,0x02,0x02,0x01,0x02,0x02,0x02,0xF8,0x01, // 219 + 0x00,0x00,0xF8,0x01,0x02,0x02,0x00,0x02,0x02,0x02,0xF8,0x01, // 220 + 0x08,0x00,0x10,0x00,0x20,0x00,0xC2,0x03,0x21,0x00,0x10,0x00,0x08, // 221 + 0x00,0x00,0xF8,0x03,0x10,0x01,0x10,0x01,0x10,0x01,0xE0, // 222 + 0x00,0x00,0xF0,0x03,0x08,0x01,0x48,0x02,0xB0,0x02,0x80,0x01, // 223 + 0x00,0x00,0x00,0x03,0xA4,0x02,0xA8,0x02,0xE0,0x03, // 224 + 0x00,0x00,0x00,0x03,0xA8,0x02,0xA4,0x02,0xE0,0x03, // 225 + 0x00,0x00,0x00,0x03,0xA8,0x02,0xA4,0x02,0xE8,0x03, // 226 + 0x00,0x00,0x08,0x03,0xA4,0x02,0xA8,0x02,0xE4,0x03, // 227 + 0x00,0x00,0x00,0x03,0xA8,0x02,0xA0,0x02,0xE8,0x03, // 228 + 0x00,0x00,0x00,0x03,0xAE,0x02,0xAA,0x02,0xEE,0x03, // 229 + 0x00,0x00,0x40,0x03,0xA0,0x02,0xA0,0x02,0xC0,0x01,0xA0,0x02,0xA0,0x02,0xC0,0x02, // 230 + 0x00,0x00,0xC0,0x01,0x20,0x16,0x20,0x1A,0x40,0x01, // 231 + 0x00,0x00,0xC0,0x01,0xA4,0x02,0xA8,0x02,0xC0,0x02, // 232 + 0x00,0x00,0xC0,0x01,0xA8,0x02,0xA4,0x02,0xC0,0x02, // 233 + 0x00,0x00,0xC0,0x01,0xA8,0x02,0xA4,0x02,0xC8,0x02, // 234 + 0x00,0x00,0xC0,0x01,0xA8,0x02,0xA0,0x02,0xC8,0x02, // 235 + 0x00,0x00,0xE4,0x03,0x08, // 236 + 0x08,0x00,0xE4,0x03, // 237 + 0x08,0x00,0xE4,0x03,0x08, // 238 + 0x08,0x00,0xE0,0x03,0x08, // 239 + 0x00,0x00,0xC0,0x01,0x28,0x02,0x38,0x02,0xE0,0x01, // 240 + 0x00,0x00,0xE8,0x03,0x24,0x00,0x28,0x00,0xC4,0x03, // 241 + 0x00,0x00,0xC0,0x01,0x24,0x02,0x28,0x02,0xC0,0x01, // 242 + 0x00,0x00,0xC0,0x01,0x28,0x02,0x24,0x02,0xC0,0x01, // 243 + 0x00,0x00,0xC0,0x01,0x28,0x02,0x24,0x02,0xC8,0x01, // 244 + 0x00,0x00,0xC8,0x01,0x24,0x02,0x28,0x02,0xC4,0x01, // 245 + 0x00,0x00,0xC0,0x01,0x28,0x02,0x20,0x02,0xC8,0x01, // 246 + 0x40,0x00,0x40,0x00,0x50,0x01,0x40,0x00,0x40, // 247 + 0x00,0x00,0xC0,0x02,0xA0,0x03,0x60,0x02,0xA0,0x01, // 248 + 0x00,0x00,0xE0,0x01,0x04,0x02,0x08,0x02,0xE0,0x03, // 249 + 0x00,0x00,0xE0,0x01,0x08,0x02,0x04,0x02,0xE0,0x03, // 250 + 0x00,0x00,0xE8,0x01,0x04,0x02,0x08,0x02,0xE0,0x03, // 251 + 0x00,0x00,0xE0,0x01,0x08,0x02,0x00,0x02,0xE8,0x03, // 252 + 0x20,0x00,0xC0,0x09,0x08,0x06,0xC4,0x01,0x20, // 253 + 0x00,0x00,0xF8,0x0F,0x20,0x02,0x20,0x02,0xC0,0x01, // 254 + 0x20,0x00,0xC8,0x09,0x00,0x06,0xC8,0x01,0x20 // 255 +}; + +const uint8_t ArialMT_Plain_16[] PROGMEM = { + 0x10, // Width: 16 + 0x13, // Height: 19 + 0x20, // First Char: 32 + 0xE0, // Numbers of Chars: 224 + + // Jump Table: + 0xFF, 0xFF, 0x00, 0x04, // 32:65535 + 0x00, 0x00, 0x08, 0x04, // 33:0 + 0x00, 0x08, 0x0D, 0x06, // 34:8 + 0x00, 0x15, 0x1A, 0x09, // 35:21 + 0x00, 0x2F, 0x17, 0x09, // 36:47 + 0x00, 0x46, 0x26, 0x0E, // 37:70 + 0x00, 0x6C, 0x1D, 0x0B, // 38:108 + 0x00, 0x89, 0x04, 0x03, // 39:137 + 0x00, 0x8D, 0x0C, 0x05, // 40:141 + 0x00, 0x99, 0x0B, 0x05, // 41:153 + 0x00, 0xA4, 0x0D, 0x06, // 42:164 + 0x00, 0xB1, 0x17, 0x09, // 43:177 + 0x00, 0xC8, 0x09, 0x04, // 44:200 + 0x00, 0xD1, 0x0B, 0x05, // 45:209 + 0x00, 0xDC, 0x08, 0x04, // 46:220 + 0x00, 0xE4, 0x0A, 0x04, // 47:228 + 0x00, 0xEE, 0x17, 0x09, // 48:238 + 0x01, 0x05, 0x11, 0x09, // 49:261 + 0x01, 0x16, 0x17, 0x09, // 50:278 + 0x01, 0x2D, 0x17, 0x09, // 51:301 + 0x01, 0x44, 0x17, 0x09, // 52:324 + 0x01, 0x5B, 0x17, 0x09, // 53:347 + 0x01, 0x72, 0x17, 0x09, // 54:370 + 0x01, 0x89, 0x16, 0x09, // 55:393 + 0x01, 0x9F, 0x17, 0x09, // 56:415 + 0x01, 0xB6, 0x17, 0x09, // 57:438 + 0x01, 0xCD, 0x05, 0x04, // 58:461 + 0x01, 0xD2, 0x06, 0x04, // 59:466 + 0x01, 0xD8, 0x17, 0x09, // 60:472 + 0x01, 0xEF, 0x17, 0x09, // 61:495 + 0x02, 0x06, 0x17, 0x09, // 62:518 + 0x02, 0x1D, 0x16, 0x09, // 63:541 + 0x02, 0x33, 0x2F, 0x10, // 64:563 + 0x02, 0x62, 0x1D, 0x0B, // 65:610 + 0x02, 0x7F, 0x1D, 0x0B, // 66:639 + 0x02, 0x9C, 0x20, 0x0C, // 67:668 + 0x02, 0xBC, 0x20, 0x0C, // 68:700 + 0x02, 0xDC, 0x1D, 0x0B, // 69:732 + 0x02, 0xF9, 0x19, 0x0A, // 70:761 + 0x03, 0x12, 0x20, 0x0C, // 71:786 + 0x03, 0x32, 0x1D, 0x0C, // 72:818 + 0x03, 0x4F, 0x05, 0x04, // 73:847 + 0x03, 0x54, 0x14, 0x08, // 74:852 + 0x03, 0x68, 0x1D, 0x0B, // 75:872 + 0x03, 0x85, 0x17, 0x09, // 76:901 + 0x03, 0x9C, 0x23, 0x0D, // 77:924 + 0x03, 0xBF, 0x1D, 0x0C, // 78:959 + 0x03, 0xDC, 0x20, 0x0C, // 79:988 + 0x03, 0xFC, 0x1C, 0x0B, // 80:1020 + 0x04, 0x18, 0x20, 0x0C, // 81:1048 + 0x04, 0x38, 0x1D, 0x0C, // 82:1080 + 0x04, 0x55, 0x1D, 0x0B, // 83:1109 + 0x04, 0x72, 0x19, 0x0A, // 84:1138 + 0x04, 0x8B, 0x1D, 0x0C, // 85:1163 + 0x04, 0xA8, 0x1C, 0x0B, // 86:1192 + 0x04, 0xC4, 0x2B, 0x0F, // 87:1220 + 0x04, 0xEF, 0x20, 0x0B, // 88:1263 + 0x05, 0x0F, 0x19, 0x0B, // 89:1295 + 0x05, 0x28, 0x1A, 0x0A, // 90:1320 + 0x05, 0x42, 0x0C, 0x04, // 91:1346 + 0x05, 0x4E, 0x0B, 0x04, // 92:1358 + 0x05, 0x59, 0x09, 0x04, // 93:1369 + 0x05, 0x62, 0x14, 0x08, // 94:1378 + 0x05, 0x76, 0x1B, 0x09, // 95:1398 + 0x05, 0x91, 0x07, 0x05, // 96:1425 + 0x05, 0x98, 0x17, 0x09, // 97:1432 + 0x05, 0xAF, 0x17, 0x09, // 98:1455 + 0x05, 0xC6, 0x14, 0x08, // 99:1478 + 0x05, 0xDA, 0x17, 0x09, // 100:1498 + 0x05, 0xF1, 0x17, 0x09, // 101:1521 + 0x06, 0x08, 0x0A, 0x04, // 102:1544 + 0x06, 0x12, 0x17, 0x09, // 103:1554 + 0x06, 0x29, 0x14, 0x09, // 104:1577 + 0x06, 0x3D, 0x05, 0x04, // 105:1597 + 0x06, 0x42, 0x06, 0x04, // 106:1602 + 0x06, 0x48, 0x17, 0x08, // 107:1608 + 0x06, 0x5F, 0x05, 0x04, // 108:1631 + 0x06, 0x64, 0x23, 0x0D, // 109:1636 + 0x06, 0x87, 0x14, 0x09, // 110:1671 + 0x06, 0x9B, 0x17, 0x09, // 111:1691 + 0x06, 0xB2, 0x17, 0x09, // 112:1714 + 0x06, 0xC9, 0x18, 0x09, // 113:1737 + 0x06, 0xE1, 0x0D, 0x05, // 114:1761 + 0x06, 0xEE, 0x14, 0x08, // 115:1774 + 0x07, 0x02, 0x0B, 0x04, // 116:1794 + 0x07, 0x0D, 0x14, 0x09, // 117:1805 + 0x07, 0x21, 0x13, 0x08, // 118:1825 + 0x07, 0x34, 0x1F, 0x0C, // 119:1844 + 0x07, 0x53, 0x14, 0x08, // 120:1875 + 0x07, 0x67, 0x13, 0x08, // 121:1895 + 0x07, 0x7A, 0x14, 0x08, // 122:1914 + 0x07, 0x8E, 0x0F, 0x05, // 123:1934 + 0x07, 0x9D, 0x06, 0x04, // 124:1949 + 0x07, 0xA3, 0x0E, 0x05, // 125:1955 + 0x07, 0xB1, 0x17, 0x09, // 126:1969 + 0xFF, 0xFF, 0x00, 0x00, // 127:65535 + 0xFF, 0xFF, 0x00, 0x10, // 128:65535 + 0xFF, 0xFF, 0x00, 0x10, // 129:65535 + 0xFF, 0xFF, 0x00, 0x10, // 130:65535 + 0xFF, 0xFF, 0x00, 0x10, // 131:65535 + 0xFF, 0xFF, 0x00, 0x10, // 132:65535 + 0xFF, 0xFF, 0x00, 0x10, // 133:65535 + 0xFF, 0xFF, 0x00, 0x10, // 134:65535 + 0xFF, 0xFF, 0x00, 0x10, // 135:65535 + 0xFF, 0xFF, 0x00, 0x10, // 136:65535 + 0xFF, 0xFF, 0x00, 0x10, // 137:65535 + 0xFF, 0xFF, 0x00, 0x10, // 138:65535 + 0xFF, 0xFF, 0x00, 0x10, // 139:65535 + 0xFF, 0xFF, 0x00, 0x10, // 140:65535 + 0xFF, 0xFF, 0x00, 0x10, // 141:65535 + 0xFF, 0xFF, 0x00, 0x10, // 142:65535 + 0xFF, 0xFF, 0x00, 0x10, // 143:65535 + 0xFF, 0xFF, 0x00, 0x10, // 144:65535 + 0xFF, 0xFF, 0x00, 0x10, // 145:65535 + 0xFF, 0xFF, 0x00, 0x10, // 146:65535 + 0xFF, 0xFF, 0x00, 0x10, // 147:65535 + 0xFF, 0xFF, 0x00, 0x10, // 148:65535 + 0xFF, 0xFF, 0x00, 0x10, // 149:65535 + 0xFF, 0xFF, 0x00, 0x10, // 150:65535 + 0xFF, 0xFF, 0x00, 0x10, // 151:65535 + 0xFF, 0xFF, 0x00, 0x10, // 152:65535 + 0xFF, 0xFF, 0x00, 0x10, // 153:65535 + 0xFF, 0xFF, 0x00, 0x10, // 154:65535 + 0xFF, 0xFF, 0x00, 0x10, // 155:65535 + 0xFF, 0xFF, 0x00, 0x10, // 156:65535 + 0xFF, 0xFF, 0x00, 0x10, // 157:65535 + 0xFF, 0xFF, 0x00, 0x10, // 158:65535 + 0xFF, 0xFF, 0x00, 0x10, // 159:65535 + 0xFF, 0xFF, 0x00, 0x04, // 160:65535 + 0x07, 0xC8, 0x09, 0x05, // 161:1992 + 0x07, 0xD1, 0x17, 0x09, // 162:2001 + 0x07, 0xE8, 0x17, 0x09, // 163:2024 + 0x07, 0xFF, 0x14, 0x09, // 164:2047 + 0x08, 0x13, 0x1A, 0x09, // 165:2067 + 0x08, 0x2D, 0x06, 0x04, // 166:2093 + 0x08, 0x33, 0x17, 0x09, // 167:2099 + 0x08, 0x4A, 0x07, 0x05, // 168:2122 + 0x08, 0x51, 0x23, 0x0C, // 169:2129 + 0x08, 0x74, 0x0E, 0x06, // 170:2164 + 0x08, 0x82, 0x14, 0x09, // 171:2178 + 0x08, 0x96, 0x17, 0x09, // 172:2198 + 0x08, 0xAD, 0x0B, 0x05, // 173:2221 + 0x08, 0xB8, 0x23, 0x0C, // 174:2232 + 0x08, 0xDB, 0x19, 0x09, // 175:2267 + 0x08, 0xF4, 0x0D, 0x06, // 176:2292 + 0x09, 0x01, 0x17, 0x09, // 177:2305 + 0x09, 0x18, 0x0E, 0x05, // 178:2328 + 0x09, 0x26, 0x0D, 0x05, // 179:2342 + 0x09, 0x33, 0x0A, 0x05, // 180:2355 + 0x09, 0x3D, 0x17, 0x09, // 181:2365 + 0x09, 0x54, 0x19, 0x09, // 182:2388 + 0x09, 0x6D, 0x08, 0x05, // 183:2413 + 0x09, 0x75, 0x0C, 0x05, // 184:2421 + 0x09, 0x81, 0x0B, 0x05, // 185:2433 + 0x09, 0x8C, 0x0D, 0x06, // 186:2444 + 0x09, 0x99, 0x17, 0x09, // 187:2457 + 0x09, 0xB0, 0x26, 0x0D, // 188:2480 + 0x09, 0xD6, 0x26, 0x0D, // 189:2518 + 0x09, 0xFC, 0x26, 0x0D, // 190:2556 + 0x0A, 0x22, 0x1A, 0x0A, // 191:2594 + 0x0A, 0x3C, 0x1D, 0x0B, // 192:2620 + 0x0A, 0x59, 0x1D, 0x0B, // 193:2649 + 0x0A, 0x76, 0x1D, 0x0B, // 194:2678 + 0x0A, 0x93, 0x1D, 0x0B, // 195:2707 + 0x0A, 0xB0, 0x1D, 0x0B, // 196:2736 + 0x0A, 0xCD, 0x1D, 0x0B, // 197:2765 + 0x0A, 0xEA, 0x2C, 0x10, // 198:2794 + 0x0B, 0x16, 0x20, 0x0C, // 199:2838 + 0x0B, 0x36, 0x1D, 0x0B, // 200:2870 + 0x0B, 0x53, 0x1D, 0x0B, // 201:2899 + 0x0B, 0x70, 0x1D, 0x0B, // 202:2928 + 0x0B, 0x8D, 0x1D, 0x0B, // 203:2957 + 0x0B, 0xAA, 0x05, 0x04, // 204:2986 + 0x0B, 0xAF, 0x07, 0x04, // 205:2991 + 0x0B, 0xB6, 0x0A, 0x04, // 206:2998 + 0x0B, 0xC0, 0x07, 0x04, // 207:3008 + 0x0B, 0xC7, 0x20, 0x0C, // 208:3015 + 0x0B, 0xE7, 0x1D, 0x0C, // 209:3047 + 0x0C, 0x04, 0x20, 0x0C, // 210:3076 + 0x0C, 0x24, 0x20, 0x0C, // 211:3108 + 0x0C, 0x44, 0x20, 0x0C, // 212:3140 + 0x0C, 0x64, 0x20, 0x0C, // 213:3172 + 0x0C, 0x84, 0x20, 0x0C, // 214:3204 + 0x0C, 0xA4, 0x17, 0x09, // 215:3236 + 0x0C, 0xBB, 0x20, 0x0C, // 216:3259 + 0x0C, 0xDB, 0x1D, 0x0C, // 217:3291 + 0x0C, 0xF8, 0x1D, 0x0C, // 218:3320 + 0x0D, 0x15, 0x1D, 0x0C, // 219:3349 + 0x0D, 0x32, 0x1D, 0x0C, // 220:3378 + 0x0D, 0x4F, 0x19, 0x0B, // 221:3407 + 0x0D, 0x68, 0x1D, 0x0B, // 222:3432 + 0x0D, 0x85, 0x17, 0x0A, // 223:3461 + 0x0D, 0x9C, 0x17, 0x09, // 224:3484 + 0x0D, 0xB3, 0x17, 0x09, // 225:3507 + 0x0D, 0xCA, 0x17, 0x09, // 226:3530 + 0x0D, 0xE1, 0x17, 0x09, // 227:3553 + 0x0D, 0xF8, 0x17, 0x09, // 228:3576 + 0x0E, 0x0F, 0x17, 0x09, // 229:3599 + 0x0E, 0x26, 0x29, 0x0E, // 230:3622 + 0x0E, 0x4F, 0x14, 0x08, // 231:3663 + 0x0E, 0x63, 0x17, 0x09, // 232:3683 + 0x0E, 0x7A, 0x17, 0x09, // 233:3706 + 0x0E, 0x91, 0x17, 0x09, // 234:3729 + 0x0E, 0xA8, 0x17, 0x09, // 235:3752 + 0x0E, 0xBF, 0x05, 0x04, // 236:3775 + 0x0E, 0xC4, 0x07, 0x04, // 237:3780 + 0x0E, 0xCB, 0x0A, 0x04, // 238:3787 + 0x0E, 0xD5, 0x07, 0x04, // 239:3797 + 0x0E, 0xDC, 0x17, 0x09, // 240:3804 + 0x0E, 0xF3, 0x14, 0x09, // 241:3827 + 0x0F, 0x07, 0x17, 0x09, // 242:3847 + 0x0F, 0x1E, 0x17, 0x09, // 243:3870 + 0x0F, 0x35, 0x17, 0x09, // 244:3893 + 0x0F, 0x4C, 0x17, 0x09, // 245:3916 + 0x0F, 0x63, 0x17, 0x09, // 246:3939 + 0x0F, 0x7A, 0x17, 0x09, // 247:3962 + 0x0F, 0x91, 0x17, 0x0A, // 248:3985 + 0x0F, 0xA8, 0x14, 0x09, // 249:4008 + 0x0F, 0xBC, 0x14, 0x09, // 250:4028 + 0x0F, 0xD0, 0x14, 0x09, // 251:4048 + 0x0F, 0xE4, 0x14, 0x09, // 252:4068 + 0x0F, 0xF8, 0x13, 0x08, // 253:4088 + 0x10, 0x0B, 0x17, 0x09, // 254:4107 + 0x10, 0x22, 0x13, 0x08, // 255:4130 + + // Font Data: + 0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x5F, // 33 + 0x00,0x00,0x00,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78, // 34 + 0x80,0x08,0x00,0x80,0x78,0x00,0xC0,0x0F,0x00,0xB8,0x08,0x00,0x80,0x08,0x00,0x80,0x78,0x00,0xC0,0x0F,0x00,0xB8,0x08,0x00,0x80,0x08, // 35 + 0x00,0x00,0x00,0xE0,0x10,0x00,0x10,0x21,0x00,0x08,0x41,0x00,0xFC,0xFF,0x00,0x08,0x42,0x00,0x10,0x22,0x00,0x20,0x1C, // 36 + 0x00,0x00,0x00,0xF0,0x00,0x00,0x08,0x01,0x00,0x08,0x01,0x00,0x08,0x61,0x00,0xF0,0x18,0x00,0x00,0x06,0x00,0xC0,0x01,0x00,0x30,0x3C,0x00,0x08,0x42,0x00,0x00,0x42,0x00,0x00,0x42,0x00,0x00,0x3C, // 37 + 0x00,0x00,0x00,0x00,0x1C,0x00,0x70,0x22,0x00,0x88,0x41,0x00,0x08,0x43,0x00,0x88,0x44,0x00,0x70,0x28,0x00,0x00,0x10,0x00,0x00,0x28,0x00,0x00,0x44, // 38 + 0x00,0x00,0x00,0x78, // 39 + 0x00,0x00,0x00,0x80,0x3F,0x00,0x70,0xC0,0x01,0x08,0x00,0x02, // 40 + 0x00,0x00,0x00,0x08,0x00,0x02,0x70,0xC0,0x01,0x80,0x3F, // 41 + 0x10,0x00,0x00,0xD0,0x00,0x00,0x38,0x00,0x00,0xD0,0x00,0x00,0x10, // 42 + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0xC0,0x1F,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02, // 43 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x01, // 44 + 0x00,0x08,0x00,0x00,0x08,0x00,0x00,0x08,0x00,0x00,0x08, // 45 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40, // 46 + 0x00,0x60,0x00,0x00,0x1E,0x00,0xE0,0x01,0x00,0x18, // 47 + 0x00,0x00,0x00,0xE0,0x1F,0x00,0x10,0x20,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x10,0x20,0x00,0xE0,0x1F, // 48 + 0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x20,0x00,0x00,0x10,0x00,0x00,0xF8,0x7F, // 49 + 0x00,0x00,0x00,0x20,0x40,0x00,0x10,0x60,0x00,0x08,0x50,0x00,0x08,0x48,0x00,0x08,0x44,0x00,0x10,0x43,0x00,0xE0,0x40, // 50 + 0x00,0x00,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x88,0x41,0x00,0xF0,0x22,0x00,0x00,0x1C, // 51 + 0x00,0x0C,0x00,0x00,0x0A,0x00,0x00,0x09,0x00,0xC0,0x08,0x00,0x20,0x08,0x00,0x10,0x08,0x00,0xF8,0x7F,0x00,0x00,0x08, // 52 + 0x00,0x00,0x00,0xC0,0x11,0x00,0xB8,0x20,0x00,0x88,0x40,0x00,0x88,0x40,0x00,0x88,0x40,0x00,0x08,0x21,0x00,0x08,0x1E, // 53 + 0x00,0x00,0x00,0xE0,0x1F,0x00,0x10,0x21,0x00,0x88,0x40,0x00,0x88,0x40,0x00,0x88,0x40,0x00,0x10,0x21,0x00,0x20,0x1E, // 54 + 0x00,0x00,0x00,0x08,0x00,0x00,0x08,0x00,0x00,0x08,0x78,0x00,0x08,0x07,0x00,0xC8,0x00,0x00,0x28,0x00,0x00,0x18, // 55 + 0x00,0x00,0x00,0x60,0x1C,0x00,0x90,0x22,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x90,0x22,0x00,0x60,0x1C, // 56 + 0x00,0x00,0x00,0xE0,0x11,0x00,0x10,0x22,0x00,0x08,0x44,0x00,0x08,0x44,0x00,0x08,0x44,0x00,0x10,0x22,0x00,0xE0,0x1F, // 57 + 0x00,0x00,0x00,0x40,0x40, // 58 + 0x00,0x00,0x00,0x40,0xC0,0x01, // 59 + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x05,0x00,0x00,0x05,0x00,0x80,0x08,0x00,0x80,0x08,0x00,0x80,0x08,0x00,0x40,0x10, // 60 + 0x00,0x00,0x00,0x80,0x08,0x00,0x80,0x08,0x00,0x80,0x08,0x00,0x80,0x08,0x00,0x80,0x08,0x00,0x80,0x08,0x00,0x80,0x08, // 61 + 0x00,0x00,0x00,0x40,0x10,0x00,0x80,0x08,0x00,0x80,0x08,0x00,0x80,0x08,0x00,0x00,0x05,0x00,0x00,0x05,0x00,0x00,0x02, // 62 + 0x00,0x00,0x00,0x60,0x00,0x00,0x10,0x00,0x00,0x08,0x00,0x00,0x08,0x5C,0x00,0x08,0x02,0x00,0x10,0x01,0x00,0xE0, // 63 + 0x00,0x00,0x00,0x00,0x3F,0x00,0xC0,0x40,0x00,0x20,0x80,0x00,0x10,0x1E,0x01,0x10,0x21,0x01,0x88,0x40,0x02,0x48,0x40,0x02,0x48,0x40,0x02,0x48,0x20,0x02,0x88,0x7C,0x02,0xC8,0x43,0x02,0x10,0x40,0x02,0x10,0x20,0x01,0x60,0x10,0x01,0x80,0x8F, // 64 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x80,0x07,0x00,0x70,0x04,0x00,0x08,0x04,0x00,0x70,0x04,0x00,0x80,0x07,0x00,0x00,0x1C,0x00,0x00,0x60, // 65 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x90,0x22,0x00,0x60,0x1C, // 66 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x10,0x20,0x00,0x20,0x10, // 67 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 68 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x40, // 69 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08, // 70 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x42,0x00,0x08,0x42,0x00,0x10,0x22,0x00,0x20,0x12,0x00,0x00,0x0E, // 71 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0xF8,0x7F, // 72 + 0x00,0x00,0x00,0xF8,0x7F, // 73 + 0x00,0x00,0x00,0x00,0x38,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0xF8,0x3F, // 74 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x00,0x04,0x00,0x00,0x02,0x00,0x00,0x01,0x00,0x80,0x03,0x00,0x40,0x04,0x00,0x20,0x18,0x00,0x10,0x20,0x00,0x08,0x40, // 75 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40, // 76 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x30,0x00,0x00,0xC0,0x00,0x00,0x00,0x03,0x00,0x00,0x1C,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x00,0x03,0x00,0xC0,0x00,0x00,0x30,0x00,0x00,0xF8,0x7F, // 77 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x10,0x00,0x00,0x60,0x00,0x00,0x80,0x00,0x00,0x00,0x03,0x00,0x00,0x04,0x00,0x00,0x18,0x00,0x00,0x20,0x00,0xF8,0x7F, // 78 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 79 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x10,0x01,0x00,0xE0, // 80 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x50,0x00,0x08,0x50,0x00,0x10,0x20,0x00,0x20,0x70,0x00,0xC0,0x4F, // 81 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x06,0x00,0x08,0x1A,0x00,0x10,0x21,0x00,0xE0,0x40, // 82 + 0x00,0x00,0x00,0x60,0x10,0x00,0x90,0x20,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x42,0x00,0x08,0x42,0x00,0x10,0x22,0x00,0x20,0x1C, // 83 + 0x08,0x00,0x00,0x08,0x00,0x00,0x08,0x00,0x00,0x08,0x00,0x00,0xF8,0x7F,0x00,0x08,0x00,0x00,0x08,0x00,0x00,0x08,0x00,0x00,0x08, // 84 + 0x00,0x00,0x00,0xF8,0x1F,0x00,0x00,0x20,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x20,0x00,0xF8,0x1F, // 85 + 0x00,0x00,0x00,0x18,0x00,0x00,0xE0,0x00,0x00,0x00,0x07,0x00,0x00,0x18,0x00,0x00,0x60,0x00,0x00,0x18,0x00,0x00,0x07,0x00,0xE0,0x00,0x00,0x18, // 86 + 0x18,0x00,0x00,0xE0,0x01,0x00,0x00,0x1E,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x80,0x03,0x00,0x70,0x00,0x00,0x08,0x00,0x00,0x70,0x00,0x00,0x80,0x03,0x00,0x00,0x1C,0x00,0x00,0x60,0x00,0x00,0x1E,0x00,0xE0,0x01,0x00,0x18, // 87 + 0x00,0x40,0x00,0x08,0x20,0x00,0x10,0x10,0x00,0x60,0x0C,0x00,0x80,0x02,0x00,0x00,0x01,0x00,0x80,0x02,0x00,0x60,0x0C,0x00,0x10,0x10,0x00,0x08,0x20,0x00,0x00,0x40, // 88 + 0x08,0x00,0x00,0x30,0x00,0x00,0x40,0x00,0x00,0x80,0x01,0x00,0x00,0x7E,0x00,0x80,0x01,0x00,0x40,0x00,0x00,0x30,0x00,0x00,0x08, // 89 + 0x00,0x40,0x00,0x08,0x60,0x00,0x08,0x58,0x00,0x08,0x44,0x00,0x08,0x43,0x00,0x88,0x40,0x00,0x68,0x40,0x00,0x18,0x40,0x00,0x08,0x40, // 90 + 0x00,0x00,0x00,0xF8,0xFF,0x03,0x08,0x00,0x02,0x08,0x00,0x02, // 91 + 0x18,0x00,0x00,0xE0,0x01,0x00,0x00,0x1E,0x00,0x00,0x60, // 92 + 0x08,0x00,0x02,0x08,0x00,0x02,0xF8,0xFF,0x03, // 93 + 0x00,0x01,0x00,0xC0,0x00,0x00,0x30,0x00,0x00,0x08,0x00,0x00,0x30,0x00,0x00,0xC0,0x00,0x00,0x00,0x01, // 94 + 0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02, // 95 + 0x00,0x00,0x00,0x08,0x00,0x00,0x10, // 96 + 0x00,0x00,0x00,0x00,0x39,0x00,0x80,0x44,0x00,0x40,0x44,0x00,0x40,0x44,0x00,0x40,0x42,0x00,0x40,0x22,0x00,0x80,0x7F, // 97 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x80,0x20,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x80,0x20,0x00,0x00,0x1F, // 98 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x80,0x20, // 99 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x80,0x20,0x00,0xF8,0x7F, // 100 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x24,0x00,0x40,0x44,0x00,0x40,0x44,0x00,0x40,0x44,0x00,0x80,0x24,0x00,0x00,0x17, // 101 + 0x40,0x00,0x00,0xF0,0x7F,0x00,0x48,0x00,0x00,0x48, // 102 + 0x00,0x00,0x00,0x00,0x1F,0x01,0x80,0x20,0x02,0x40,0x40,0x02,0x40,0x40,0x02,0x40,0x40,0x02,0x80,0x20,0x01,0xC0,0xFF, // 103 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x80,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x80,0x7F, // 104 + 0x00,0x00,0x00,0xC8,0x7F, // 105 + 0x00,0x00,0x02,0xC8,0xFF,0x01, // 106 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x00,0x08,0x00,0x00,0x04,0x00,0x00,0x06,0x00,0x00,0x19,0x00,0x80,0x20,0x00,0x40,0x40, // 107 + 0x00,0x00,0x00,0xF8,0x7F, // 108 + 0x00,0x00,0x00,0xC0,0x7F,0x00,0x80,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x80,0x7F,0x00,0x80,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x80,0x7F, // 109 + 0x00,0x00,0x00,0xC0,0x7F,0x00,0x80,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x80,0x7F, // 110 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x80,0x20,0x00,0x00,0x1F, // 111 + 0x00,0x00,0x00,0xC0,0xFF,0x03,0x80,0x20,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x80,0x20,0x00,0x00,0x1F, // 112 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x80,0x20,0x00,0xC0,0xFF,0x03, // 113 + 0x00,0x00,0x00,0xC0,0x7F,0x00,0x80,0x00,0x00,0x40,0x00,0x00,0x40, // 114 + 0x00,0x00,0x00,0x80,0x23,0x00,0x40,0x44,0x00,0x40,0x44,0x00,0x40,0x44,0x00,0x40,0x44,0x00,0x80,0x38, // 115 + 0x40,0x00,0x00,0xF0,0x7F,0x00,0x40,0x40,0x00,0x40,0x40, // 116 + 0x00,0x00,0x00,0xC0,0x3F,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x20,0x00,0xC0,0x7F, // 117 + 0xC0,0x00,0x00,0x00,0x03,0x00,0x00,0x1C,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x00,0x03,0x00,0xC0, // 118 + 0xC0,0x00,0x00,0x00,0x1F,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x00,0x03,0x00,0xC0,0x00,0x00,0x00,0x03,0x00,0x00,0x1C,0x00,0x00,0x60,0x00,0x00,0x1F,0x00,0xC0, // 119 + 0x40,0x40,0x00,0x80,0x20,0x00,0x00,0x1B,0x00,0x00,0x04,0x00,0x00,0x1B,0x00,0x80,0x20,0x00,0x40,0x40, // 120 + 0xC0,0x01,0x00,0x00,0x06,0x02,0x00,0x38,0x02,0x00,0xE0,0x01,0x00,0x38,0x00,0x00,0x07,0x00,0xC0, // 121 + 0x40,0x40,0x00,0x40,0x60,0x00,0x40,0x58,0x00,0x40,0x44,0x00,0x40,0x43,0x00,0xC0,0x40,0x00,0x40,0x40, // 122 + 0x00,0x04,0x00,0x00,0x04,0x00,0xF0,0xFB,0x01,0x08,0x00,0x02,0x08,0x00,0x02, // 123 + 0x00,0x00,0x00,0xF8,0xFF,0x03, // 124 + 0x08,0x00,0x02,0x08,0x00,0x02,0xF0,0xFB,0x01,0x00,0x04,0x00,0x00,0x04, // 125 + 0x00,0x02,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x01, // 126 + 0x00,0x00,0x00,0x00,0x00,0x00,0x40,0xFF,0x03, // 161 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x03,0x40,0xF0,0x00,0x40,0x4E,0x00,0xC0,0x41,0x00,0xB8,0x20,0x00,0x00,0x11, // 162 + 0x00,0x41,0x00,0xE0,0x31,0x00,0x10,0x2F,0x00,0x08,0x21,0x00,0x08,0x21,0x00,0x08,0x40,0x00,0x10,0x40,0x00,0x20,0x20, // 163 + 0x00,0x00,0x00,0x40,0x0B,0x00,0x80,0x04,0x00,0x40,0x08,0x00,0x40,0x08,0x00,0x80,0x04,0x00,0x40,0x0B, // 164 + 0x08,0x0A,0x00,0x10,0x0A,0x00,0x60,0x0A,0x00,0x80,0x0B,0x00,0x00,0x7E,0x00,0x80,0x0B,0x00,0x60,0x0A,0x00,0x10,0x0A,0x00,0x08,0x0A, // 165 + 0x00,0x00,0x00,0xF8,0xF1,0x03, // 166 + 0x00,0x86,0x00,0x70,0x09,0x01,0xC8,0x10,0x02,0x88,0x10,0x02,0x08,0x21,0x02,0x08,0x61,0x02,0x30,0xD2,0x01,0x00,0x0C, // 167 + 0x08,0x00,0x00,0x00,0x00,0x00,0x08, // 168 + 0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0xC8,0x47,0x00,0x28,0x48,0x00,0x28,0x48,0x00,0x28,0x48,0x00,0x28,0x48,0x00,0x48,0x44,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 169 + 0xD0,0x00,0x00,0x48,0x01,0x00,0x28,0x01,0x00,0x28,0x01,0x00,0xF0,0x01, // 170 + 0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x1B,0x00,0x80,0x20,0x00,0x00,0x04,0x00,0x00,0x1B,0x00,0x80,0x20, // 171 + 0x00,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x0F, // 172 + 0x00,0x08,0x00,0x00,0x08,0x00,0x00,0x08,0x00,0x00,0x08, // 173 + 0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0xE8,0x4F,0x00,0x28,0x41,0x00,0x28,0x41,0x00,0x28,0x43,0x00,0x28,0x45,0x00,0xC8,0x48,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 174 + 0x04,0x00,0x00,0x04,0x00,0x00,0x04,0x00,0x00,0x04,0x00,0x00,0x04,0x00,0x00,0x04,0x00,0x00,0x04,0x00,0x00,0x04,0x00,0x00,0x04, // 175 + 0x00,0x00,0x00,0x30,0x00,0x00,0x48,0x00,0x00,0x48,0x00,0x00,0x30, // 176 + 0x00,0x00,0x00,0x00,0x41,0x00,0x00,0x41,0x00,0x00,0x41,0x00,0xE0,0x4F,0x00,0x00,0x41,0x00,0x00,0x41,0x00,0x00,0x41, // 177 + 0x10,0x01,0x00,0x88,0x01,0x00,0x48,0x01,0x00,0x48,0x01,0x00,0x30,0x01, // 178 + 0x90,0x00,0x00,0x08,0x01,0x00,0x08,0x01,0x00,0x28,0x01,0x00,0xD8, // 179 + 0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x08, // 180 + 0x00,0x00,0x00,0xC0,0xFF,0x03,0x00,0x20,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x20,0x00,0xC0,0x7F, // 181 + 0xF0,0x00,0x00,0xF8,0x00,0x00,0xF8,0x01,0x00,0xF8,0x01,0x00,0xF8,0xFF,0x03,0x08,0x00,0x00,0x08,0x00,0x00,0xF8,0xFF,0x03,0x08, // 182 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02, // 183 + 0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x80,0x02,0x00,0x00,0x03, // 184 + 0x00,0x00,0x00,0x10,0x00,0x00,0x08,0x00,0x00,0xF8,0x01, // 185 + 0xF0,0x00,0x00,0x08,0x01,0x00,0x08,0x01,0x00,0x08,0x01,0x00,0xF0, // 186 + 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x20,0x00,0x00,0x1B,0x00,0x00,0x04,0x00,0x80,0x20,0x00,0x00,0x1B,0x00,0x00,0x04, // 187 + 0x00,0x00,0x00,0x10,0x00,0x00,0x08,0x40,0x00,0xF8,0x21,0x00,0x00,0x10,0x00,0x00,0x0C,0x00,0x00,0x02,0x00,0x80,0x01,0x00,0x40,0x30,0x00,0x30,0x28,0x00,0x08,0x24,0x00,0x00,0x7E,0x00,0x00,0x20, // 188 + 0x00,0x00,0x00,0x10,0x00,0x00,0x08,0x40,0x00,0xF8,0x31,0x00,0x00,0x08,0x00,0x00,0x04,0x00,0x00,0x03,0x00,0x80,0x00,0x00,0x60,0x44,0x00,0x10,0x62,0x00,0x08,0x52,0x00,0x00,0x52,0x00,0x00,0x4C, // 189 + 0x90,0x00,0x00,0x08,0x01,0x00,0x08,0x41,0x00,0x28,0x21,0x00,0xD8,0x18,0x00,0x00,0x04,0x00,0x00,0x03,0x00,0x80,0x00,0x00,0x40,0x30,0x00,0x30,0x28,0x00,0x08,0x24,0x00,0x00,0x7E,0x00,0x00,0x20, // 190 + 0x00,0x00,0x00,0x00,0xE0,0x00,0x00,0x10,0x01,0x00,0x08,0x02,0x40,0x07,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x01,0x00,0xC0, // 191 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x80,0x07,0x00,0x71,0x04,0x00,0x0A,0x04,0x00,0x70,0x04,0x00,0x80,0x07,0x00,0x00,0x1C,0x00,0x00,0x60, // 192 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x80,0x07,0x00,0x70,0x04,0x00,0x0A,0x04,0x00,0x71,0x04,0x00,0x80,0x07,0x00,0x00,0x1C,0x00,0x00,0x60, // 193 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x80,0x07,0x00,0x72,0x04,0x00,0x09,0x04,0x00,0x71,0x04,0x00,0x82,0x07,0x00,0x00,0x1C,0x00,0x00,0x60, // 194 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x80,0x07,0x00,0x72,0x04,0x00,0x09,0x04,0x00,0x72,0x04,0x00,0x81,0x07,0x00,0x00,0x1C,0x00,0x00,0x60, // 195 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x80,0x07,0x00,0x72,0x04,0x00,0x08,0x04,0x00,0x72,0x04,0x00,0x80,0x07,0x00,0x00,0x1C,0x00,0x00,0x60, // 196 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x80,0x07,0x00,0x7E,0x04,0x00,0x0A,0x04,0x00,0x7E,0x04,0x00,0x80,0x07,0x00,0x00,0x1C,0x00,0x00,0x60, // 197 + 0x00,0x60,0x00,0x00,0x18,0x00,0x00,0x06,0x00,0x80,0x05,0x00,0x60,0x04,0x00,0x18,0x04,0x00,0x08,0x04,0x00,0x08,0x04,0x00,0xF8,0x7F,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41, // 198 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x08,0x40,0x00,0x08,0x40,0x02,0x08,0xC0,0x02,0x08,0x40,0x03,0x08,0x40,0x00,0x10,0x20,0x00,0x20,0x10, // 199 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x09,0x41,0x00,0x0A,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x40, // 200 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x0A,0x41,0x00,0x09,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x40, // 201 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x0A,0x41,0x00,0x09,0x41,0x00,0x09,0x41,0x00,0x0A,0x41,0x00,0x08,0x41,0x00,0x08,0x40, // 202 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x0A,0x41,0x00,0x08,0x41,0x00,0x0A,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x40, // 203 + 0x01,0x00,0x00,0xFA,0x7F, // 204 + 0x00,0x00,0x00,0xFA,0x7F,0x00,0x01, // 205 + 0x02,0x00,0x00,0xF9,0x7F,0x00,0x01,0x00,0x00,0x02, // 206 + 0x02,0x00,0x00,0xF8,0x7F,0x00,0x02, // 207 + 0x00,0x02,0x00,0xF8,0x7F,0x00,0x08,0x42,0x00,0x08,0x42,0x00,0x08,0x42,0x00,0x08,0x42,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 208 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x10,0x00,0x00,0x60,0x00,0x00,0x82,0x00,0x00,0x01,0x03,0x00,0x02,0x04,0x00,0x01,0x18,0x00,0x00,0x20,0x00,0xF8,0x7F, // 209 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x08,0x40,0x00,0x09,0x40,0x00,0x0A,0x40,0x00,0x08,0x40,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 210 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x08,0x40,0x00,0x0A,0x40,0x00,0x09,0x40,0x00,0x08,0x40,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 211 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x0A,0x40,0x00,0x09,0x40,0x00,0x09,0x40,0x00,0x0A,0x40,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 212 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x0A,0x40,0x00,0x09,0x40,0x00,0x0A,0x40,0x00,0x09,0x40,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 213 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x08,0x40,0x00,0x0A,0x40,0x00,0x08,0x40,0x00,0x0A,0x40,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 214 + 0x00,0x00,0x00,0x40,0x10,0x00,0x80,0x08,0x00,0x00,0x05,0x00,0x00,0x07,0x00,0x00,0x05,0x00,0x80,0x08,0x00,0x40,0x10, // 215 + 0x00,0x00,0x00,0xC0,0x4F,0x00,0x20,0x30,0x00,0x10,0x30,0x00,0x08,0x4C,0x00,0x08,0x42,0x00,0x08,0x41,0x00,0xC8,0x40,0x00,0x30,0x20,0x00,0x30,0x10,0x00,0xC8,0x0F, // 216 + 0x00,0x00,0x00,0xF8,0x1F,0x00,0x00,0x20,0x00,0x00,0x40,0x00,0x01,0x40,0x00,0x02,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x20,0x00,0xF8,0x1F, // 217 + 0x00,0x00,0x00,0xF8,0x1F,0x00,0x00,0x20,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x02,0x40,0x00,0x01,0x40,0x00,0x00,0x40,0x00,0x00,0x20,0x00,0xF8,0x1F, // 218 + 0x00,0x00,0x00,0xF8,0x1F,0x00,0x00,0x20,0x00,0x00,0x40,0x00,0x02,0x40,0x00,0x01,0x40,0x00,0x01,0x40,0x00,0x02,0x40,0x00,0x00,0x20,0x00,0xF8,0x1F, // 219 + 0x00,0x00,0x00,0xF8,0x1F,0x00,0x00,0x20,0x00,0x00,0x40,0x00,0x02,0x40,0x00,0x00,0x40,0x00,0x02,0x40,0x00,0x00,0x40,0x00,0x00,0x20,0x00,0xF8,0x1F, // 220 + 0x08,0x00,0x00,0x30,0x00,0x00,0x40,0x00,0x00,0x80,0x01,0x00,0x02,0x7E,0x00,0x81,0x01,0x00,0x40,0x00,0x00,0x30,0x00,0x00,0x08, // 221 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x20,0x10,0x00,0x20,0x10,0x00,0x20,0x10,0x00,0x20,0x10,0x00,0x20,0x10,0x00,0x20,0x10,0x00,0x40,0x08,0x00,0x80,0x07, // 222 + 0x00,0x00,0x00,0xE0,0x7F,0x00,0x10,0x00,0x00,0x08,0x20,0x00,0x88,0x43,0x00,0x70,0x42,0x00,0x00,0x44,0x00,0x00,0x38, // 223 + 0x00,0x00,0x00,0x00,0x39,0x00,0x80,0x44,0x00,0x40,0x44,0x00,0x48,0x44,0x00,0x50,0x42,0x00,0x40,0x22,0x00,0x80,0x7F, // 224 + 0x00,0x00,0x00,0x00,0x39,0x00,0x80,0x44,0x00,0x40,0x44,0x00,0x50,0x44,0x00,0x48,0x42,0x00,0x40,0x22,0x00,0x80,0x7F, // 225 + 0x00,0x00,0x00,0x00,0x39,0x00,0x80,0x44,0x00,0x50,0x44,0x00,0x48,0x44,0x00,0x48,0x42,0x00,0x50,0x22,0x00,0x80,0x7F, // 226 + 0x00,0x00,0x00,0x00,0x39,0x00,0x80,0x44,0x00,0x50,0x44,0x00,0x48,0x44,0x00,0x50,0x42,0x00,0x48,0x22,0x00,0x80,0x7F, // 227 + 0x00,0x00,0x00,0x00,0x39,0x00,0x80,0x44,0x00,0x50,0x44,0x00,0x40,0x44,0x00,0x50,0x42,0x00,0x40,0x22,0x00,0x80,0x7F, // 228 + 0x00,0x00,0x00,0x00,0x39,0x00,0x80,0x44,0x00,0x5C,0x44,0x00,0x54,0x44,0x00,0x5C,0x42,0x00,0x40,0x22,0x00,0x80,0x7F, // 229 + 0x00,0x00,0x00,0x00,0x39,0x00,0x80,0x44,0x00,0x40,0x44,0x00,0x40,0x44,0x00,0x40,0x42,0x00,0x40,0x22,0x00,0x80,0x3F,0x00,0x80,0x24,0x00,0x40,0x44,0x00,0x40,0x44,0x00,0x40,0x44,0x00,0x80,0x24,0x00,0x00,0x17, // 230 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x40,0x40,0x02,0x40,0xC0,0x02,0x40,0x40,0x03,0x80,0x20, // 231 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x24,0x00,0x48,0x44,0x00,0x50,0x44,0x00,0x40,0x44,0x00,0x80,0x24,0x00,0x00,0x17, // 232 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x24,0x00,0x40,0x44,0x00,0x50,0x44,0x00,0x48,0x44,0x00,0x80,0x24,0x00,0x00,0x17, // 233 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x24,0x00,0x50,0x44,0x00,0x48,0x44,0x00,0x48,0x44,0x00,0x90,0x24,0x00,0x00,0x17, // 234 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x24,0x00,0x50,0x44,0x00,0x40,0x44,0x00,0x50,0x44,0x00,0x80,0x24,0x00,0x00,0x17, // 235 + 0x08,0x00,0x00,0xD0,0x7F, // 236 + 0x00,0x00,0x00,0xD0,0x7F,0x00,0x08, // 237 + 0x10,0x00,0x00,0xC8,0x7F,0x00,0x08,0x00,0x00,0x10, // 238 + 0x10,0x00,0x00,0xC0,0x7F,0x00,0x10, // 239 + 0x00,0x00,0x00,0x00,0x1F,0x00,0xA0,0x20,0x00,0x68,0x40,0x00,0x58,0x40,0x00,0x70,0x40,0x00,0xE8,0x20,0x00,0x00,0x1F, // 240 + 0x00,0x00,0x00,0xC0,0x7F,0x00,0x90,0x00,0x00,0x48,0x00,0x00,0x50,0x00,0x00,0x48,0x00,0x00,0x80,0x7F, // 241 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x48,0x40,0x00,0x50,0x40,0x00,0x40,0x40,0x00,0x80,0x20,0x00,0x00,0x1F, // 242 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x40,0x40,0x00,0x50,0x40,0x00,0x48,0x40,0x00,0x80,0x20,0x00,0x00,0x1F, // 243 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x50,0x40,0x00,0x48,0x40,0x00,0x48,0x40,0x00,0x90,0x20,0x00,0x00,0x1F, // 244 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x50,0x40,0x00,0x48,0x40,0x00,0x50,0x40,0x00,0x88,0x20,0x00,0x00,0x1F, // 245 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x50,0x40,0x00,0x40,0x40,0x00,0x50,0x40,0x00,0x80,0x20,0x00,0x00,0x1F, // 246 + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x80,0x0A,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02, // 247 + 0x00,0x00,0x00,0x00,0x5F,0x00,0x80,0x30,0x00,0x40,0x48,0x00,0x40,0x44,0x00,0x40,0x42,0x00,0x80,0x21,0x00,0x40,0x1F, // 248 + 0x00,0x00,0x00,0xC0,0x3F,0x00,0x00,0x40,0x00,0x08,0x40,0x00,0x10,0x40,0x00,0x00,0x20,0x00,0xC0,0x7F, // 249 + 0x00,0x00,0x00,0xC0,0x3F,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x10,0x40,0x00,0x08,0x20,0x00,0xC0,0x7F, // 250 + 0x00,0x00,0x00,0xC0,0x3F,0x00,0x10,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x10,0x20,0x00,0xC0,0x7F, // 251 + 0x00,0x00,0x00,0xD0,0x3F,0x00,0x00,0x40,0x00,0x10,0x40,0x00,0x00,0x40,0x00,0x00,0x20,0x00,0xC0,0x7F, // 252 + 0xC0,0x01,0x00,0x00,0x06,0x02,0x00,0x38,0x02,0x10,0xE0,0x01,0x08,0x38,0x00,0x00,0x07,0x00,0xC0, // 253 + 0x00,0x00,0x00,0xF8,0xFF,0x03,0x80,0x20,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x80,0x20,0x00,0x00,0x1F, // 254 + 0xC0,0x01,0x00,0x00,0x06,0x02,0x10,0x38,0x02,0x00,0xE0,0x01,0x10,0x38,0x00,0x00,0x07,0x00,0xC0 // 255 +}; + +const uint8_t ArialMT_Plain_24[] PROGMEM = { + 0x18, // Width: 24 + 0x1C, // Height: 28 + 0x20, // First Char: 32 + 0xE0, // Numbers of Chars: 224 + + // Jump Table: + 0xFF, 0xFF, 0x00, 0x07, // 32:65535 + 0x00, 0x00, 0x13, 0x07, // 33:0 + 0x00, 0x13, 0x1A, 0x09, // 34:19 + 0x00, 0x2D, 0x33, 0x0D, // 35:45 + 0x00, 0x60, 0x2F, 0x0D, // 36:96 + 0x00, 0x8F, 0x4F, 0x15, // 37:143 + 0x00, 0xDE, 0x3B, 0x10, // 38:222 + 0x01, 0x19, 0x0A, 0x05, // 39:281 + 0x01, 0x23, 0x1C, 0x08, // 40:291 + 0x01, 0x3F, 0x1B, 0x08, // 41:319 + 0x01, 0x5A, 0x21, 0x09, // 42:346 + 0x01, 0x7B, 0x32, 0x0E, // 43:379 + 0x01, 0xAD, 0x10, 0x07, // 44:429 + 0x01, 0xBD, 0x1B, 0x08, // 45:445 + 0x01, 0xD8, 0x0F, 0x07, // 46:472 + 0x01, 0xE7, 0x19, 0x07, // 47:487 + 0x02, 0x00, 0x2F, 0x0D, // 48:512 + 0x02, 0x2F, 0x23, 0x0D, // 49:559 + 0x02, 0x52, 0x2F, 0x0D, // 50:594 + 0x02, 0x81, 0x2F, 0x0D, // 51:641 + 0x02, 0xB0, 0x2F, 0x0D, // 52:688 + 0x02, 0xDF, 0x2F, 0x0D, // 53:735 + 0x03, 0x0E, 0x2F, 0x0D, // 54:782 + 0x03, 0x3D, 0x2D, 0x0D, // 55:829 + 0x03, 0x6A, 0x2F, 0x0D, // 56:874 + 0x03, 0x99, 0x2F, 0x0D, // 57:921 + 0x03, 0xC8, 0x0F, 0x07, // 58:968 + 0x03, 0xD7, 0x10, 0x07, // 59:983 + 0x03, 0xE7, 0x2F, 0x0E, // 60:999 + 0x04, 0x16, 0x2F, 0x0E, // 61:1046 + 0x04, 0x45, 0x2E, 0x0E, // 62:1093 + 0x04, 0x73, 0x2E, 0x0D, // 63:1139 + 0x04, 0xA1, 0x5B, 0x18, // 64:1185 + 0x04, 0xFC, 0x3B, 0x10, // 65:1276 + 0x05, 0x37, 0x3B, 0x10, // 66:1335 + 0x05, 0x72, 0x3F, 0x11, // 67:1394 + 0x05, 0xB1, 0x3F, 0x11, // 68:1457 + 0x05, 0xF0, 0x3B, 0x10, // 69:1520 + 0x06, 0x2B, 0x35, 0x0F, // 70:1579 + 0x06, 0x60, 0x43, 0x13, // 71:1632 + 0x06, 0xA3, 0x3B, 0x11, // 72:1699 + 0x06, 0xDE, 0x0F, 0x07, // 73:1758 + 0x06, 0xED, 0x27, 0x0C, // 74:1773 + 0x07, 0x14, 0x3F, 0x10, // 75:1812 + 0x07, 0x53, 0x2F, 0x0D, // 76:1875 + 0x07, 0x82, 0x43, 0x14, // 77:1922 + 0x07, 0xC5, 0x3B, 0x11, // 78:1989 + 0x08, 0x00, 0x47, 0x13, // 79:2048 + 0x08, 0x47, 0x3A, 0x10, // 80:2119 + 0x08, 0x81, 0x47, 0x13, // 81:2177 + 0x08, 0xC8, 0x3F, 0x11, // 82:2248 + 0x09, 0x07, 0x3B, 0x10, // 83:2311 + 0x09, 0x42, 0x35, 0x0F, // 84:2370 + 0x09, 0x77, 0x3B, 0x11, // 85:2423 + 0x09, 0xB2, 0x39, 0x10, // 86:2482 + 0x09, 0xEB, 0x59, 0x17, // 87:2539 + 0x0A, 0x44, 0x3B, 0x10, // 88:2628 + 0x0A, 0x7F, 0x3D, 0x10, // 89:2687 + 0x0A, 0xBC, 0x37, 0x0F, // 90:2748 + 0x0A, 0xF3, 0x14, 0x07, // 91:2803 + 0x0B, 0x07, 0x1B, 0x07, // 92:2823 + 0x0B, 0x22, 0x18, 0x07, // 93:2850 + 0x0B, 0x3A, 0x2A, 0x0B, // 94:2874 + 0x0B, 0x64, 0x34, 0x0D, // 95:2916 + 0x0B, 0x98, 0x11, 0x08, // 96:2968 + 0x0B, 0xA9, 0x2F, 0x0D, // 97:2985 + 0x0B, 0xD8, 0x33, 0x0D, // 98:3032 + 0x0C, 0x0B, 0x2B, 0x0C, // 99:3083 + 0x0C, 0x36, 0x2F, 0x0D, // 100:3126 + 0x0C, 0x65, 0x2F, 0x0D, // 101:3173 + 0x0C, 0x94, 0x1A, 0x07, // 102:3220 + 0x0C, 0xAE, 0x2F, 0x0D, // 103:3246 + 0x0C, 0xDD, 0x2F, 0x0D, // 104:3293 + 0x0D, 0x0C, 0x0F, 0x05, // 105:3340 + 0x0D, 0x1B, 0x10, 0x05, // 106:3355 + 0x0D, 0x2B, 0x2F, 0x0C, // 107:3371 + 0x0D, 0x5A, 0x0F, 0x05, // 108:3418 + 0x0D, 0x69, 0x47, 0x14, // 109:3433 + 0x0D, 0xB0, 0x2F, 0x0D, // 110:3504 + 0x0D, 0xDF, 0x2F, 0x0D, // 111:3551 + 0x0E, 0x0E, 0x33, 0x0D, // 112:3598 + 0x0E, 0x41, 0x30, 0x0D, // 113:3649 + 0x0E, 0x71, 0x1E, 0x08, // 114:3697 + 0x0E, 0x8F, 0x2B, 0x0C, // 115:3727 + 0x0E, 0xBA, 0x1B, 0x07, // 116:3770 + 0x0E, 0xD5, 0x2F, 0x0D, // 117:3797 + 0x0F, 0x04, 0x2A, 0x0C, // 118:3844 + 0x0F, 0x2E, 0x42, 0x11, // 119:3886 + 0x0F, 0x70, 0x2B, 0x0C, // 120:3952 + 0x0F, 0x9B, 0x2A, 0x0C, // 121:3995 + 0x0F, 0xC5, 0x2B, 0x0C, // 122:4037 + 0x0F, 0xF0, 0x1C, 0x08, // 123:4080 + 0x10, 0x0C, 0x10, 0x06, // 124:4108 + 0x10, 0x1C, 0x1B, 0x08, // 125:4124 + 0x10, 0x37, 0x32, 0x0E, // 126:4151 + 0xFF, 0xFF, 0x00, 0x00, // 127:65535 + 0xFF, 0xFF, 0x00, 0x18, // 128:65535 + 0xFF, 0xFF, 0x00, 0x18, // 129:65535 + 0xFF, 0xFF, 0x00, 0x18, // 130:65535 + 0xFF, 0xFF, 0x00, 0x18, // 131:65535 + 0xFF, 0xFF, 0x00, 0x18, // 132:65535 + 0xFF, 0xFF, 0x00, 0x18, // 133:65535 + 0xFF, 0xFF, 0x00, 0x18, // 134:65535 + 0xFF, 0xFF, 0x00, 0x18, // 135:65535 + 0xFF, 0xFF, 0x00, 0x18, // 136:65535 + 0xFF, 0xFF, 0x00, 0x18, // 137:65535 + 0xFF, 0xFF, 0x00, 0x18, // 138:65535 + 0xFF, 0xFF, 0x00, 0x18, // 139:65535 + 0xFF, 0xFF, 0x00, 0x18, // 140:65535 + 0xFF, 0xFF, 0x00, 0x18, // 141:65535 + 0xFF, 0xFF, 0x00, 0x18, // 142:65535 + 0xFF, 0xFF, 0x00, 0x18, // 143:65535 + 0xFF, 0xFF, 0x00, 0x18, // 144:65535 + 0xFF, 0xFF, 0x00, 0x18, // 145:65535 + 0xFF, 0xFF, 0x00, 0x18, // 146:65535 + 0xFF, 0xFF, 0x00, 0x18, // 147:65535 + 0xFF, 0xFF, 0x00, 0x18, // 148:65535 + 0xFF, 0xFF, 0x00, 0x18, // 149:65535 + 0xFF, 0xFF, 0x00, 0x18, // 150:65535 + 0xFF, 0xFF, 0x00, 0x18, // 151:65535 + 0xFF, 0xFF, 0x00, 0x18, // 152:65535 + 0xFF, 0xFF, 0x00, 0x18, // 153:65535 + 0xFF, 0xFF, 0x00, 0x18, // 154:65535 + 0xFF, 0xFF, 0x00, 0x18, // 155:65535 + 0xFF, 0xFF, 0x00, 0x18, // 156:65535 + 0xFF, 0xFF, 0x00, 0x18, // 157:65535 + 0xFF, 0xFF, 0x00, 0x18, // 158:65535 + 0xFF, 0xFF, 0x00, 0x18, // 159:65535 + 0xFF, 0xFF, 0x00, 0x07, // 160:65535 + 0x10, 0x69, 0x14, 0x08, // 161:4201 + 0x10, 0x7D, 0x2B, 0x0D, // 162:4221 + 0x10, 0xA8, 0x2F, 0x0D, // 163:4264 + 0x10, 0xD7, 0x33, 0x0D, // 164:4311 + 0x11, 0x0A, 0x31, 0x0D, // 165:4362 + 0x11, 0x3B, 0x10, 0x06, // 166:4411 + 0x11, 0x4B, 0x2F, 0x0D, // 167:4427 + 0x11, 0x7A, 0x19, 0x08, // 168:4474 + 0x11, 0x93, 0x46, 0x12, // 169:4499 + 0x11, 0xD9, 0x1A, 0x09, // 170:4569 + 0x11, 0xF3, 0x27, 0x0D, // 171:4595 + 0x12, 0x1A, 0x2F, 0x0E, // 172:4634 + 0x12, 0x49, 0x1B, 0x08, // 173:4681 + 0x12, 0x64, 0x46, 0x12, // 174:4708 + 0x12, 0xAA, 0x31, 0x0D, // 175:4778 + 0x12, 0xDB, 0x1E, 0x0A, // 176:4827 + 0x12, 0xF9, 0x33, 0x0D, // 177:4857 + 0x13, 0x2C, 0x1A, 0x08, // 178:4908 + 0x13, 0x46, 0x1A, 0x08, // 179:4934 + 0x13, 0x60, 0x19, 0x08, // 180:4960 + 0x13, 0x79, 0x2F, 0x0E, // 181:4985 + 0x13, 0xA8, 0x31, 0x0D, // 182:5032 + 0x13, 0xD9, 0x12, 0x08, // 183:5081 + 0x13, 0xEB, 0x18, 0x08, // 184:5099 + 0x14, 0x03, 0x16, 0x08, // 185:5123 + 0x14, 0x19, 0x1E, 0x09, // 186:5145 + 0x14, 0x37, 0x2E, 0x0D, // 187:5175 + 0x14, 0x65, 0x4F, 0x14, // 188:5221 + 0x14, 0xB4, 0x4B, 0x14, // 189:5300 + 0x14, 0xFF, 0x4B, 0x14, // 190:5375 + 0x15, 0x4A, 0x33, 0x0F, // 191:5450 + 0x15, 0x7D, 0x3B, 0x10, // 192:5501 + 0x15, 0xB8, 0x3B, 0x10, // 193:5560 + 0x15, 0xF3, 0x3B, 0x10, // 194:5619 + 0x16, 0x2E, 0x3B, 0x10, // 195:5678 + 0x16, 0x69, 0x3B, 0x10, // 196:5737 + 0x16, 0xA4, 0x3B, 0x10, // 197:5796 + 0x16, 0xDF, 0x5B, 0x18, // 198:5855 + 0x17, 0x3A, 0x3F, 0x11, // 199:5946 + 0x17, 0x79, 0x3B, 0x10, // 200:6009 + 0x17, 0xB4, 0x3B, 0x10, // 201:6068 + 0x17, 0xEF, 0x3B, 0x10, // 202:6127 + 0x18, 0x2A, 0x3B, 0x10, // 203:6186 + 0x18, 0x65, 0x11, 0x07, // 204:6245 + 0x18, 0x76, 0x11, 0x07, // 205:6262 + 0x18, 0x87, 0x15, 0x07, // 206:6279 + 0x18, 0x9C, 0x15, 0x07, // 207:6300 + 0x18, 0xB1, 0x3F, 0x11, // 208:6321 + 0x18, 0xF0, 0x3B, 0x11, // 209:6384 + 0x19, 0x2B, 0x47, 0x13, // 210:6443 + 0x19, 0x72, 0x47, 0x13, // 211:6514 + 0x19, 0xB9, 0x47, 0x13, // 212:6585 + 0x1A, 0x00, 0x47, 0x13, // 213:6656 + 0x1A, 0x47, 0x47, 0x13, // 214:6727 + 0x1A, 0x8E, 0x2B, 0x0E, // 215:6798 + 0x1A, 0xB9, 0x47, 0x13, // 216:6841 + 0x1B, 0x00, 0x3B, 0x11, // 217:6912 + 0x1B, 0x3B, 0x3B, 0x11, // 218:6971 + 0x1B, 0x76, 0x3B, 0x11, // 219:7030 + 0x1B, 0xB1, 0x3B, 0x11, // 220:7089 + 0x1B, 0xEC, 0x3D, 0x10, // 221:7148 + 0x1C, 0x29, 0x3A, 0x10, // 222:7209 + 0x1C, 0x63, 0x37, 0x0F, // 223:7267 + 0x1C, 0x9A, 0x2F, 0x0D, // 224:7322 + 0x1C, 0xC9, 0x2F, 0x0D, // 225:7369 + 0x1C, 0xF8, 0x2F, 0x0D, // 226:7416 + 0x1D, 0x27, 0x2F, 0x0D, // 227:7463 + 0x1D, 0x56, 0x2F, 0x0D, // 228:7510 + 0x1D, 0x85, 0x2F, 0x0D, // 229:7557 + 0x1D, 0xB4, 0x53, 0x15, // 230:7604 + 0x1E, 0x07, 0x2B, 0x0C, // 231:7687 + 0x1E, 0x32, 0x2F, 0x0D, // 232:7730 + 0x1E, 0x61, 0x2F, 0x0D, // 233:7777 + 0x1E, 0x90, 0x2F, 0x0D, // 234:7824 + 0x1E, 0xBF, 0x2F, 0x0D, // 235:7871 + 0x1E, 0xEE, 0x11, 0x07, // 236:7918 + 0x1E, 0xFF, 0x11, 0x07, // 237:7935 + 0x1F, 0x10, 0x15, 0x07, // 238:7952 + 0x1F, 0x25, 0x15, 0x07, // 239:7973 + 0x1F, 0x3A, 0x2F, 0x0D, // 240:7994 + 0x1F, 0x69, 0x2F, 0x0D, // 241:8041 + 0x1F, 0x98, 0x2F, 0x0D, // 242:8088 + 0x1F, 0xC7, 0x2F, 0x0D, // 243:8135 + 0x1F, 0xF6, 0x2F, 0x0D, // 244:8182 + 0x20, 0x25, 0x2F, 0x0D, // 245:8229 + 0x20, 0x54, 0x2F, 0x0D, // 246:8276 + 0x20, 0x83, 0x32, 0x0D, // 247:8323 + 0x20, 0xB5, 0x33, 0x0F, // 248:8373 + 0x20, 0xE8, 0x2F, 0x0D, // 249:8424 + 0x21, 0x17, 0x2F, 0x0D, // 250:8471 + 0x21, 0x46, 0x2F, 0x0D, // 251:8518 + 0x21, 0x75, 0x2F, 0x0D, // 252:8565 + 0x21, 0xA4, 0x2A, 0x0C, // 253:8612 + 0x21, 0xCE, 0x2F, 0x0D, // 254:8654 + 0x21, 0xFD, 0x2A, 0x0C, // 255:8701 + + // Font Data: + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x33,0x00,0xE0,0xFF,0x33, // 33 + 0x00,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0xE0,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0xE0,0x07, // 34 + 0x00,0x0C,0x03,0x00,0x00,0x0C,0x33,0x00,0x00,0x0C,0x3F,0x00,0x00,0xFC,0x0F,0x00,0x80,0xFF,0x03,0x00,0xE0,0x0F,0x03,0x00,0x60,0x0C,0x33,0x00,0x00,0x0C,0x3F,0x00,0x00,0xFC,0x0F,0x00,0x80,0xFF,0x03,0x00,0xE0,0x0F,0x03,0x00,0x60,0x0C,0x03,0x00,0x00,0x0C,0x03, // 35 + 0x00,0x00,0x00,0x00,0x80,0x07,0x06,0x00,0xC0,0x0F,0x1E,0x00,0xC0,0x18,0x1C,0x00,0x60,0x18,0x38,0x00,0x60,0x30,0x30,0x00,0xF0,0xFF,0xFF,0x00,0x60,0x30,0x30,0x00,0x60,0x60,0x38,0x00,0xC0,0x60,0x18,0x00,0xC0,0xC1,0x1F,0x00,0x00,0x81,0x07, // 36 + 0x00,0x00,0x00,0x00,0x80,0x0F,0x00,0x00,0xC0,0x1F,0x00,0x00,0x60,0x30,0x00,0x00,0x20,0x20,0x00,0x00,0x20,0x20,0x20,0x00,0x60,0x30,0x38,0x00,0xC0,0x1F,0x1E,0x00,0x80,0x8F,0x0F,0x00,0x00,0xC0,0x03,0x00,0x00,0xF0,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x8F,0x0F,0x00,0xC0,0xC3,0x1F,0x00,0xE0,0x60,0x30,0x00,0x20,0x20,0x20,0x00,0x00,0x20,0x20,0x00,0x00,0x60,0x30,0x00,0x00,0xC0,0x1F,0x00,0x00,0x80,0x0F, // 37 + 0x00,0x00,0x00,0x00,0x00,0x80,0x07,0x00,0x00,0xC0,0x0F,0x00,0x80,0xE3,0x1C,0x00,0xC0,0x77,0x38,0x00,0xE0,0x3C,0x30,0x00,0x60,0x38,0x30,0x00,0x60,0x78,0x30,0x00,0xE0,0xEC,0x38,0x00,0xC0,0x8F,0x1B,0x00,0x80,0x03,0x1F,0x00,0x00,0x00,0x0F,0x00,0x00,0xC0,0x1F,0x00,0x00,0xC0,0x38,0x00,0x00,0x00,0x10, // 38 + 0x00,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0xE0,0x07, // 39 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x0F,0x00,0x00,0xFE,0x7F,0x00,0x80,0x0F,0xF0,0x01,0xC0,0x01,0x80,0x03,0x60,0x00,0x00,0x06,0x20,0x00,0x00,0x04, // 40 + 0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x04,0x60,0x00,0x00,0x06,0xC0,0x01,0x80,0x03,0x80,0x0F,0xF0,0x01,0x00,0xFE,0x7F,0x00,0x00,0xF0,0x0F, // 41 + 0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x80,0x04,0x00,0x00,0x80,0x0F,0x00,0x00,0xE0,0x03,0x00,0x00,0xE0,0x03,0x00,0x00,0x80,0x0F,0x00,0x00,0x80,0x04,0x00,0x00,0x80, // 42 + 0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0xFF,0x0F,0x00,0x00,0xFF,0x0F,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60, // 43 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x03,0x00,0x00,0xF0,0x01, // 44 + 0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01, // 45 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30, // 46 + 0x00,0x00,0x30,0x00,0x00,0x00,0x3E,0x00,0x00,0xE0,0x0F,0x00,0x00,0xFC,0x01,0x00,0x80,0x3F,0x00,0x00,0xE0,0x03,0x00,0x00,0x60, // 47 + 0x00,0x00,0x00,0x00,0x00,0xFE,0x03,0x00,0x80,0xFF,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xE0,0x00,0x38,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0xE0,0x00,0x38,0x00,0xC0,0x01,0x1C,0x00,0x80,0xFF,0x0F,0x00,0x00,0xFE,0x03, // 48 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x03,0x00,0x00,0x80,0x01,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F, // 49 + 0x00,0x00,0x00,0x00,0x00,0x03,0x30,0x00,0xC0,0x03,0x38,0x00,0xC0,0x00,0x3C,0x00,0x60,0x00,0x36,0x00,0x60,0x00,0x33,0x00,0x60,0x80,0x31,0x00,0x60,0xC0,0x30,0x00,0x60,0x60,0x30,0x00,0xC0,0x30,0x30,0x00,0xC0,0x1F,0x30,0x00,0x00,0x0F,0x30, // 50 + 0x00,0x00,0x00,0x00,0x00,0x01,0x06,0x00,0xC0,0x01,0x0E,0x00,0xC0,0x00,0x1C,0x00,0x60,0x00,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0xC0,0x38,0x30,0x00,0xC0,0x6F,0x18,0x00,0x80,0xC7,0x0F,0x00,0x00,0x80,0x07, // 51 + 0x00,0x00,0x00,0x00,0x00,0x80,0x03,0x00,0x00,0xC0,0x03,0x00,0x00,0xF0,0x03,0x00,0x00,0x3C,0x03,0x00,0x00,0x0E,0x03,0x00,0x80,0x07,0x03,0x00,0xC0,0x01,0x03,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x03, // 52 + 0x00,0x00,0x00,0x00,0x00,0x30,0x06,0x00,0x80,0x3F,0x0E,0x00,0xE0,0x1F,0x18,0x00,0x60,0x08,0x30,0x00,0x60,0x0C,0x30,0x00,0x60,0x0C,0x30,0x00,0x60,0x0C,0x30,0x00,0x60,0x0C,0x30,0x00,0x60,0x18,0x1C,0x00,0x60,0xF0,0x0F,0x00,0x00,0xE0,0x03, // 53 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x03,0x00,0x80,0xFF,0x0F,0x00,0xC0,0x63,0x1C,0x00,0xC0,0x30,0x38,0x00,0x60,0x18,0x30,0x00,0x60,0x18,0x30,0x00,0x60,0x18,0x30,0x00,0x60,0x18,0x30,0x00,0xE0,0x30,0x18,0x00,0xC0,0xF1,0x0F,0x00,0x80,0xC1,0x07, // 54 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x3C,0x00,0x60,0x80,0x3F,0x00,0x60,0xE0,0x03,0x00,0x60,0x78,0x00,0x00,0x60,0x0E,0x00,0x00,0x60,0x03,0x00,0x00,0xE0,0x01,0x00,0x00,0x60, // 55 + 0x00,0x00,0x00,0x00,0x00,0x80,0x07,0x00,0x80,0xC7,0x1F,0x00,0xC0,0x6F,0x18,0x00,0xE0,0x38,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0xE0,0x38,0x30,0x00,0xC0,0x6F,0x18,0x00,0x80,0xC7,0x1F,0x00,0x00,0x80,0x07, // 56 + 0x00,0x00,0x00,0x00,0x00,0x1F,0x0C,0x00,0x80,0x7F,0x1C,0x00,0xC0,0x61,0x38,0x00,0x60,0xC0,0x30,0x00,0x60,0xC0,0x30,0x00,0x60,0xC0,0x30,0x00,0x60,0xC0,0x30,0x00,0x60,0x60,0x18,0x00,0xC0,0x31,0x1E,0x00,0x80,0xFF,0x0F,0x00,0x00,0xFE,0x01, // 57 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30, // 58 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x30,0x03,0x00,0x06,0xF0,0x01, // 59 + 0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0xD8,0x00,0x00,0x00,0xD8,0x00,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x04,0x01,0x00,0x00,0x06,0x03,0x00,0x00,0x06,0x03,0x00,0x00,0x03,0x06, // 60 + 0x00,0x00,0x00,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01, // 61 + 0x00,0x00,0x00,0x00,0x00,0x03,0x06,0x00,0x00,0x06,0x03,0x00,0x00,0x06,0x03,0x00,0x00,0x04,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0xD8,0x00,0x00,0x00,0xD8,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x20, // 62 + 0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x80,0x03,0x00,0x00,0xC0,0x01,0x00,0x00,0xE0,0x00,0x00,0x00,0x60,0x80,0x33,0x00,0x60,0xC0,0x33,0x00,0x60,0xE0,0x00,0x00,0x60,0x30,0x00,0x00,0xC0,0x38,0x00,0x00,0xC0,0x1F,0x00,0x00,0x00,0x07, // 63 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x0F,0x00,0x00,0xF8,0x3F,0x00,0x00,0x1E,0xF0,0x00,0x00,0x07,0xC0,0x01,0x80,0xC3,0x87,0x01,0xC0,0xF1,0x9F,0x03,0xC0,0x38,0x18,0x03,0xC0,0x0C,0x30,0x03,0x60,0x0E,0x30,0x06,0x60,0x06,0x30,0x06,0x60,0x06,0x18,0x06,0x60,0x06,0x0C,0x06,0x60,0x0C,0x1E,0x06,0x60,0xF8,0x3F,0x06,0xE0,0xFE,0x31,0x06,0xC0,0x0E,0x30,0x06,0xC0,0x01,0x18,0x03,0x80,0x03,0x1C,0x03,0x00,0x07,0x8F,0x01,0x00,0xFE,0x87,0x01,0x00,0xF8,0xC1,0x00,0x00,0x00,0x40, // 64 + 0x00,0x00,0x30,0x00,0x00,0x00,0x3E,0x00,0x00,0x80,0x0F,0x00,0x00,0xF0,0x03,0x00,0x00,0xFE,0x01,0x00,0x80,0x8F,0x01,0x00,0xE0,0x83,0x01,0x00,0x60,0x80,0x01,0x00,0xE0,0x83,0x01,0x00,0x80,0x8F,0x01,0x00,0x00,0xFE,0x01,0x00,0x00,0xF0,0x03,0x00,0x00,0x80,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x30, // 65 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0xC0,0x78,0x30,0x00,0xC0,0xFF,0x18,0x00,0x80,0xC7,0x1F,0x00,0x00,0x80,0x07, // 66 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xC0,0x00,0x18,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0xC0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x03,0x0F,0x00,0x00,0x02,0x03, // 67 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0xE0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x03,0x0E,0x00,0x00,0xFF,0x07,0x00,0x00,0xFC,0x01, // 68 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x00,0x30, // 69 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60, // 70 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xC0,0x00,0x18,0x00,0xE0,0x00,0x18,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x60,0x30,0x00,0x60,0x60,0x30,0x00,0xE0,0x60,0x38,0x00,0xC0,0x60,0x18,0x00,0xC0,0x61,0x18,0x00,0x80,0xE3,0x0F,0x00,0x00,0xE2,0x0F, // 71 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F, // 72 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F, // 73 + 0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x38,0x00,0xE0,0xFF,0x1F,0x00,0xE0,0xFF,0x0F, // 74 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x00,0xE0,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x7C,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0xE7,0x01,0x00,0x80,0x83,0x07,0x00,0xC0,0x01,0x0F,0x00,0xE0,0x00,0x1E,0x00,0x60,0x00,0x38,0x00,0x20,0x00,0x30,0x00,0x00,0x00,0x20, // 75 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30, // 76 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0x01,0x00,0x00,0xC0,0x0F,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x3F,0x00,0x00,0xE0,0x07,0x00,0x00,0xFE,0x00,0x00,0xC0,0x0F,0x00,0x00,0xE0,0x01,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F, // 77 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0xC0,0x01,0x00,0x00,0x80,0x03,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0xE0,0x01,0x00,0x00,0x80,0x03,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x1C,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F, // 78 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xC0,0x00,0x18,0x00,0xE0,0x00,0x38,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0xE0,0x00,0x38,0x00,0xC0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x07,0x0F,0x00,0x00,0xFF,0x07,0x00,0x00,0xFC,0x01, // 79 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x60,0x00,0x00,0x60,0x60,0x00,0x00,0x60,0x60,0x00,0x00,0x60,0x60,0x00,0x00,0x60,0x60,0x00,0x00,0x60,0x60,0x00,0x00,0x60,0x60,0x00,0x00,0x60,0x60,0x00,0x00,0xC0,0x30,0x00,0x00,0xC0,0x3F,0x00,0x00,0x00,0x0F, // 80 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x0C,0x00,0xC0,0x00,0x18,0x00,0xE0,0x00,0x18,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x36,0x00,0x60,0x00,0x36,0x00,0xE0,0x00,0x3C,0x00,0xC0,0x00,0x1C,0x00,0xC0,0x01,0x1C,0x00,0x80,0x07,0x3F,0x00,0x00,0xFF,0x77,0x00,0x00,0xFC,0x61, // 81 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x70,0x00,0x00,0x60,0xF0,0x00,0x00,0x60,0xF0,0x03,0x00,0x60,0xB0,0x07,0x00,0xE0,0x18,0x1F,0x00,0xC0,0x1F,0x3C,0x00,0x80,0x0F,0x30,0x00,0x00,0x00,0x20, // 82 + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x07,0x0F,0x00,0xC0,0x1F,0x1C,0x00,0xC0,0x18,0x18,0x00,0x60,0x38,0x38,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x70,0x30,0x00,0xC0,0x60,0x18,0x00,0xC0,0xE1,0x18,0x00,0x80,0xC3,0x0F,0x00,0x00,0x83,0x07, // 83 + 0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60, // 84 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x03,0x00,0xE0,0xFF,0x0F,0x00,0x00,0x00,0x1C,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x1C,0x00,0xE0,0xFF,0x0F,0x00,0xE0,0xFF,0x03, // 85 + 0x20,0x00,0x00,0x00,0xE0,0x01,0x00,0x00,0xC0,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0xF8,0x01,0x00,0x00,0xC0,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x3E,0x00,0x00,0xC0,0x0F,0x00,0x00,0xF8,0x01,0x00,0x00,0x3E,0x00,0x00,0xC0,0x0F,0x00,0x00,0xE0,0x01,0x00,0x00,0x20, // 86 + 0x60,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0x80,0xFF,0x00,0x00,0x00,0xF8,0x0F,0x00,0x00,0x80,0x3F,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x3F,0x00,0x00,0xE0,0x0F,0x00,0x00,0xFC,0x01,0x00,0x80,0x1F,0x00,0x00,0xE0,0x03,0x00,0x00,0x60,0x00,0x00,0x00,0xE0,0x03,0x00,0x00,0x80,0x1F,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xE0,0x0F,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x30,0x00,0x00,0x80,0x3F,0x00,0x00,0xF8,0x0F,0x00,0x80,0xFF,0x00,0x00,0xE0,0x07,0x00,0x00,0x60, // 87 + 0x00,0x00,0x20,0x00,0x20,0x00,0x30,0x00,0x60,0x00,0x3C,0x00,0xE0,0x01,0x1E,0x00,0xC0,0x83,0x07,0x00,0x00,0xCF,0x03,0x00,0x00,0xFE,0x01,0x00,0x00,0x38,0x00,0x00,0x00,0xFE,0x01,0x00,0x00,0xCF,0x03,0x00,0xC0,0x03,0x07,0x00,0xE0,0x01,0x1E,0x00,0x60,0x00,0x3C,0x00,0x20,0x00,0x30,0x00,0x00,0x00,0x20, // 88 + 0x20,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0xC0,0x01,0x00,0x00,0x80,0x03,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0xF0,0x3F,0x00,0x00,0xF0,0x3F,0x00,0x00,0x3C,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x07,0x00,0x00,0xC0,0x03,0x00,0x00,0xE0,0x01,0x00,0x00,0x60,0x00,0x00,0x00,0x20, // 89 + 0x00,0x00,0x30,0x00,0x60,0x00,0x38,0x00,0x60,0x00,0x3C,0x00,0x60,0x00,0x37,0x00,0x60,0x80,0x33,0x00,0x60,0xC0,0x31,0x00,0x60,0xE0,0x30,0x00,0x60,0x38,0x30,0x00,0x60,0x1C,0x30,0x00,0x60,0x0E,0x30,0x00,0x60,0x07,0x30,0x00,0xE0,0x01,0x30,0x00,0xE0,0x00,0x30,0x00,0x60,0x00,0x30, // 90 + 0x00,0x00,0x00,0x00,0xE0,0xFF,0xFF,0x07,0xE0,0xFF,0xFF,0x07,0x60,0x00,0x00,0x06,0x60,0x00,0x00,0x06, // 91 + 0x60,0x00,0x00,0x00,0xE0,0x03,0x00,0x00,0x80,0x3F,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xE0,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x30, // 92 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x06,0x60,0x00,0x00,0x06,0xE0,0xFF,0xFF,0x07,0xE0,0xFF,0xFF,0x07, // 93 + 0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x1F,0x00,0x00,0xC0,0x07,0x00,0x00,0xE0,0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0xC0,0x07,0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x20, // 94 + 0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06, // 95 + 0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0x80, // 96 + 0x00,0x00,0x00,0x00,0x00,0x18,0x0E,0x00,0x00,0x1C,0x1F,0x00,0x00,0x8C,0x39,0x00,0x00,0x86,0x31,0x00,0x00,0x86,0x31,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x18,0x00,0x00,0xCE,0x0C,0x00,0x00,0xFC,0x1F,0x00,0x00,0xF8,0x3F,0x00,0x00,0x00,0x20, // 97 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x00,0x18,0x0C,0x00,0x00,0x0C,0x18,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x0E,0x38,0x00,0x00,0x1C,0x1C,0x00,0x00,0xF8,0x0F,0x00,0x00,0xE0,0x03, // 98 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x00,0x0E,0x38,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x0E,0x38,0x00,0x00,0x1C,0x1C,0x00,0x00,0x18,0x0C, // 99 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x03,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x00,0x0E,0x38,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x0C,0x18,0x00,0x00,0x18,0x0C,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F, // 100 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0xDC,0x1C,0x00,0x00,0xCE,0x38,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0xCE,0x38,0x00,0x00,0xDC,0x18,0x00,0x00,0xF8,0x0C,0x00,0x00,0xF0,0x04, // 101 + 0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0xC0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x06,0x00,0x00,0x60,0x06,0x00,0x00,0x60,0x06, // 102 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x83,0x01,0x00,0xF8,0x8F,0x03,0x00,0x1C,0x1C,0x07,0x00,0x0E,0x38,0x06,0x00,0x06,0x30,0x06,0x00,0x06,0x30,0x06,0x00,0x06,0x30,0x06,0x00,0x0C,0x18,0x07,0x00,0x18,0x8C,0x03,0x00,0xFE,0xFF,0x01,0x00,0xFE,0xFF, // 103 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x00,0x18,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0xFC,0x3F,0x00,0x00,0xF8,0x3F, // 104 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0xFE,0x3F,0x00,0x60,0xFE,0x3F, // 105 + 0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x60,0xFE,0xFF,0x07,0x60,0xFE,0xFF,0x03, // 106 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x00,0xC0,0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0xF0,0x01,0x00,0x00,0x98,0x07,0x00,0x00,0x0C,0x0E,0x00,0x00,0x06,0x3C,0x00,0x00,0x02,0x30,0x00,0x00,0x00,0x20, // 107 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F, // 108 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F,0x00,0x00,0x0C,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0xFC,0x3F,0x00,0x00,0xF8,0x3F,0x00,0x00,0x0C,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0xFC,0x3F,0x00,0x00,0xF8,0x3F, // 109 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F,0x00,0x00,0x18,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0xFC,0x3F,0x00,0x00,0xF8,0x3F, // 110 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x00,0x0E,0x38,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x0E,0x38,0x00,0x00,0x1C,0x1C,0x00,0x00,0xF8,0x0F,0x00,0x00,0xF0,0x07, // 111 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0xFF,0x07,0x00,0xFE,0xFF,0x07,0x00,0x18,0x0C,0x00,0x00,0x0C,0x18,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x0E,0x38,0x00,0x00,0x1C,0x1C,0x00,0x00,0xF8,0x0F,0x00,0x00,0xE0,0x03, // 112 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x03,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x00,0x0E,0x38,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x0C,0x18,0x00,0x00,0x18,0x0C,0x00,0x00,0xFE,0xFF,0x07,0x00,0xFE,0xFF,0x07, // 113 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F,0x00,0x00,0x0C,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06, // 114 + 0x00,0x00,0x00,0x00,0x00,0x38,0x0C,0x00,0x00,0x7C,0x1C,0x00,0x00,0xEE,0x38,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x31,0x00,0x00,0xC6,0x31,0x00,0x00,0x8E,0x39,0x00,0x00,0x9C,0x1F,0x00,0x00,0x18,0x0F, // 115 + 0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0xC0,0xFF,0x1F,0x00,0xE0,0xFF,0x3F,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30, // 116 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x0F,0x00,0x00,0xFE,0x1F,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x0C,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F, // 117 + 0x00,0x06,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0xC0,0x07,0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x1F,0x00,0x00,0xC0,0x07,0x00,0x00,0xF8,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x06, // 118 + 0x00,0x0E,0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0xF0,0x03,0x00,0x00,0x80,0x1F,0x00,0x00,0x00,0x38,0x00,0x00,0x80,0x1F,0x00,0x00,0xE0,0x03,0x00,0x00,0x7C,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x7C,0x00,0x00,0x00,0xE0,0x03,0x00,0x00,0x80,0x1F,0x00,0x00,0x00,0x38,0x00,0x00,0x80,0x1F,0x00,0x00,0xF0,0x03,0x00,0x00,0x7E,0x00,0x00,0x00,0x0E, // 119 + 0x00,0x02,0x20,0x00,0x00,0x06,0x30,0x00,0x00,0x1E,0x3C,0x00,0x00,0x38,0x0E,0x00,0x00,0xF0,0x07,0x00,0x00,0xC0,0x01,0x00,0x00,0xE0,0x07,0x00,0x00,0x38,0x0E,0x00,0x00,0x1C,0x3C,0x00,0x00,0x0E,0x30,0x00,0x00,0x02,0x20, // 120 + 0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x7E,0x00,0x06,0x00,0xF0,0x01,0x06,0x00,0x80,0x0F,0x07,0x00,0x00,0xFE,0x03,0x00,0x00,0xFC,0x00,0x00,0xC0,0x1F,0x00,0x00,0xF8,0x03,0x00,0x00,0x3E,0x00,0x00,0x00,0x06, // 121 + 0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x00,0x06,0x3C,0x00,0x00,0x06,0x3E,0x00,0x00,0x06,0x37,0x00,0x00,0xC6,0x33,0x00,0x00,0xE6,0x30,0x00,0x00,0x76,0x30,0x00,0x00,0x3E,0x30,0x00,0x00,0x1E,0x30,0x00,0x00,0x06,0x30, // 122 + 0x00,0x00,0x00,0x00,0x00,0x80,0x01,0x00,0x00,0xC0,0x03,0x00,0xC0,0x7F,0xFE,0x03,0xE0,0x3F,0xFC,0x07,0x60,0x00,0x00,0x06,0x60,0x00,0x00,0x06, // 123 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0xFF,0x0F,0xE0,0xFF,0xFF,0x0F, // 124 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x06,0x60,0x00,0x00,0x06,0xE0,0x3F,0xFC,0x07,0xC0,0x7F,0xFF,0x03,0x00,0xC0,0x03,0x00,0x00,0x80,0x01, // 125 + 0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0x60, // 126 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE6,0xFF,0x07,0x00,0xE6,0xFF,0x07, // 161 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x9C,0x07,0x00,0x0E,0x78,0x00,0x00,0x06,0x3F,0x00,0x00,0xF6,0x30,0x00,0x00,0x0E,0x30,0x00,0xE0,0x0D,0x1C,0x00,0x00,0x1C,0x0E,0x00,0x00,0x10,0x06, // 162 + 0x00,0x60,0x10,0x00,0x00,0x60,0x38,0x00,0x00,0x7F,0x1C,0x00,0xC0,0xFF,0x1F,0x00,0xE0,0xE0,0x19,0x00,0x60,0x60,0x18,0x00,0x60,0x60,0x18,0x00,0x60,0x60,0x30,0x00,0xE0,0x00,0x30,0x00,0xC0,0x01,0x30,0x00,0x80,0x01,0x38,0x00,0x00,0x00,0x10, // 163 + 0x00,0x00,0x00,0x00,0x00,0x02,0x04,0x00,0x00,0xF7,0x0E,0x00,0x00,0xFE,0x07,0x00,0x00,0x0C,0x03,0x00,0x00,0x06,0x06,0x00,0x00,0x06,0x06,0x00,0x00,0x06,0x06,0x00,0x00,0x06,0x06,0x00,0x00,0x0C,0x03,0x00,0x00,0xFE,0x07,0x00,0x00,0xF7,0x0E,0x00,0x00,0x02,0x04, // 164 + 0xE0,0x60,0x06,0x00,0xC0,0x61,0x06,0x00,0x80,0x67,0x06,0x00,0x00,0x7E,0x06,0x00,0x00,0x7C,0x06,0x00,0x00,0xF0,0x3F,0x00,0x00,0xF0,0x3F,0x00,0x00,0x7C,0x06,0x00,0x00,0x7E,0x06,0x00,0x80,0x67,0x06,0x00,0xC0,0x61,0x06,0x00,0xE0,0x60,0x06,0x00,0x20, // 165 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x7F,0xF8,0x0F,0xE0,0x7F,0xF8,0x0F, // 166 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x00,0x00,0x80,0xF3,0xC1,0x00,0xC0,0x1F,0xC3,0x03,0xE0,0x0C,0x07,0x03,0x60,0x1C,0x06,0x06,0x60,0x18,0x0C,0x06,0x60,0x30,0x1C,0x06,0xE0,0x70,0x38,0x07,0xC0,0xE1,0xF4,0x03,0x80,0xC1,0xE7,0x01,0x00,0x80,0x03, // 167 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60, // 168 + 0x00,0xF8,0x00,0x00,0x00,0xFE,0x03,0x00,0x00,0x07,0x07,0x00,0x80,0x01,0x0C,0x00,0xC0,0x79,0x1C,0x00,0xC0,0xFE,0x19,0x00,0x60,0x86,0x31,0x00,0x60,0x03,0x33,0x00,0x60,0x03,0x33,0x00,0x60,0x03,0x33,0x00,0x60,0x03,0x33,0x00,0x60,0x87,0x33,0x00,0xC0,0x86,0x19,0x00,0xC0,0x85,0x1C,0x00,0x80,0x01,0x0C,0x00,0x00,0x07,0x07,0x00,0x00,0xFE,0x03,0x00,0x00,0xF8, // 169 + 0x00,0x00,0x00,0x00,0xC0,0x1C,0x00,0x00,0xE0,0x3E,0x00,0x00,0x60,0x32,0x00,0x00,0x60,0x32,0x00,0x00,0xE0,0x3F,0x00,0x00,0xC0,0x3F, // 170 + 0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0xE0,0x03,0x00,0x00,0x78,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x00,0x84,0x10,0x00,0x00,0xE0,0x03,0x00,0x00,0x78,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x00,0x04,0x10, // 171 + 0x00,0x00,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFC,0x01, // 172 + 0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01, // 173 + 0x00,0xF8,0x00,0x00,0x00,0xFE,0x03,0x00,0x00,0x07,0x07,0x00,0x80,0x01,0x0C,0x00,0xC0,0x01,0x1C,0x00,0xC0,0xFE,0x1B,0x00,0x60,0xFE,0x33,0x00,0x60,0x66,0x30,0x00,0x60,0x66,0x30,0x00,0x60,0xE6,0x30,0x00,0x60,0xFE,0x31,0x00,0x60,0x3C,0x33,0x00,0xC0,0x00,0x1A,0x00,0xC0,0x01,0x1C,0x00,0x80,0x01,0x0C,0x00,0x00,0x07,0x07,0x00,0x00,0xFE,0x03,0x00,0x00,0xF8, // 174 + 0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C, // 175 + 0x00,0x00,0x00,0x00,0x80,0x03,0x00,0x00,0x40,0x04,0x00,0x00,0x20,0x08,0x00,0x00,0x20,0x08,0x00,0x00,0x20,0x08,0x00,0x00,0x40,0x04,0x00,0x00,0x80,0x03, // 176 + 0x00,0x00,0x00,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0xFF,0x3F,0x00,0x00,0xFF,0x3F,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30, // 177 + 0x40,0x20,0x00,0x00,0x60,0x30,0x00,0x00,0x20,0x38,0x00,0x00,0x20,0x2C,0x00,0x00,0x20,0x26,0x00,0x00,0xE0,0x23,0x00,0x00,0xC0,0x21, // 178 + 0x40,0x10,0x00,0x00,0x60,0x30,0x00,0x00,0x20,0x20,0x00,0x00,0x20,0x22,0x00,0x00,0x20,0x22,0x00,0x00,0xE0,0x3D,0x00,0x00,0xC0,0x1D, // 179 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x20, // 180 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0xFF,0x07,0x00,0xFE,0xFF,0x07,0x00,0x00,0x1C,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x1C,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F, // 181 + 0x00,0x0F,0x00,0x00,0xC0,0x3F,0x00,0x00,0xC0,0x3F,0x00,0x00,0xE0,0x7F,0x00,0x00,0xE0,0x7F,0x00,0x00,0xE0,0xFF,0xFF,0x07,0xE0,0xFF,0xFF,0x07,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0xE0,0xFF,0xFF,0x07,0xE0,0xFF,0xFF,0x07,0x60,0x00,0x00,0x00,0x60, // 182 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60, // 183 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x02,0x00,0x00,0xC0,0x02,0x00,0x00,0x80,0x03,0x00,0x00,0x00,0x01, // 184 + 0x00,0x00,0x00,0x00,0x80,0x01,0x00,0x00,0xC0,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0xE0,0x3F,0x00,0x00,0xE0,0x3F, // 185 + 0x00,0x00,0x00,0x00,0x80,0x0F,0x00,0x00,0xC0,0x1F,0x00,0x00,0xE0,0x38,0x00,0x00,0x60,0x30,0x00,0x00,0xE0,0x38,0x00,0x00,0xC0,0x1F,0x00,0x00,0x80,0x0F, // 186 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x10,0x00,0x00,0x1C,0x1C,0x00,0x00,0x78,0x0F,0x00,0x00,0xE0,0x03,0x00,0x00,0x84,0x10,0x00,0x00,0x1C,0x1C,0x00,0x00,0x78,0x0F,0x00,0x00,0xE0,0x03,0x00,0x00,0x80, // 187 + 0x00,0x00,0x00,0x00,0x80,0x01,0x00,0x00,0xC0,0x00,0x00,0x00,0xC0,0x00,0x20,0x00,0xE0,0x3F,0x38,0x00,0xE0,0x3F,0x1C,0x00,0x00,0x00,0x0E,0x00,0x00,0x80,0x03,0x00,0x00,0xC0,0x01,0x00,0x00,0xE0,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x1C,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x07,0x0C,0x00,0xC0,0x01,0x0E,0x00,0xE0,0x80,0x0B,0x00,0x60,0xC0,0x08,0x00,0x00,0xE0,0x3F,0x00,0x00,0xE0,0x3F,0x00,0x00,0x00,0x08, // 188 + 0x00,0x00,0x00,0x00,0x80,0x01,0x00,0x00,0xC0,0x00,0x00,0x00,0xC0,0x00,0x20,0x00,0xE0,0x3F,0x30,0x00,0xE0,0x3F,0x1C,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x07,0x00,0x00,0xC0,0x01,0x00,0x00,0xE0,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x1C,0x00,0x00,0x00,0x4E,0x20,0x00,0x00,0x67,0x30,0x00,0xC0,0x21,0x38,0x00,0xE0,0x20,0x2C,0x00,0x60,0x20,0x26,0x00,0x00,0xE0,0x27,0x00,0x00,0xC0,0x21, // 189 + 0x40,0x10,0x00,0x00,0x60,0x30,0x00,0x00,0x20,0x20,0x00,0x00,0x20,0x22,0x20,0x00,0x20,0x22,0x30,0x00,0xE0,0x3D,0x38,0x00,0xC0,0x1D,0x0E,0x00,0x00,0x00,0x07,0x00,0x00,0x80,0x03,0x00,0x00,0xE0,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x0E,0x0C,0x00,0x00,0x07,0x0E,0x00,0x80,0x83,0x0B,0x00,0xE0,0xC0,0x08,0x00,0x60,0xE0,0x3F,0x00,0x20,0xE0,0x3F,0x00,0x00,0x00,0x08, // 190 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0xF8,0x03,0x00,0x00,0x1E,0x03,0x00,0x00,0x07,0x07,0x00,0xE6,0x03,0x06,0x00,0xE6,0x01,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x07,0x00,0x00,0x80,0x03,0x00,0x00,0xC0,0x01,0x00,0x00,0xC0, // 191 + 0x00,0x00,0x30,0x00,0x00,0x00,0x3E,0x00,0x00,0x80,0x0F,0x00,0x00,0xF0,0x03,0x00,0x00,0xFE,0x01,0x00,0x82,0x8F,0x01,0x00,0xE6,0x83,0x01,0x00,0x6E,0x80,0x01,0x00,0xE8,0x83,0x01,0x00,0x80,0x8F,0x01,0x00,0x00,0xFE,0x01,0x00,0x00,0xF0,0x03,0x00,0x00,0x80,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x30, // 192 + 0x00,0x00,0x30,0x00,0x00,0x00,0x3E,0x00,0x00,0x80,0x0F,0x00,0x00,0xF0,0x03,0x00,0x00,0xFE,0x01,0x00,0x80,0x8F,0x01,0x00,0xE8,0x83,0x01,0x00,0x6E,0x80,0x01,0x00,0xE6,0x83,0x01,0x00,0x82,0x8F,0x01,0x00,0x00,0xFE,0x01,0x00,0x00,0xF0,0x03,0x00,0x00,0x80,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x30, // 193 + 0x00,0x00,0x30,0x00,0x00,0x00,0x3E,0x00,0x00,0x80,0x0F,0x00,0x00,0xF0,0x03,0x00,0x00,0xFE,0x01,0x00,0x88,0x8F,0x01,0x00,0xEC,0x83,0x01,0x00,0x66,0x80,0x01,0x00,0xE6,0x83,0x01,0x00,0x8C,0x8F,0x01,0x00,0x08,0xFE,0x01,0x00,0x00,0xF0,0x03,0x00,0x00,0x80,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x30, // 194 + 0x00,0x00,0x30,0x00,0x00,0x00,0x3E,0x00,0x00,0x80,0x0F,0x00,0x00,0xF0,0x03,0x00,0x0C,0xFE,0x01,0x00,0x8E,0x8F,0x01,0x00,0xE6,0x83,0x01,0x00,0x66,0x80,0x01,0x00,0xEC,0x83,0x01,0x00,0x8C,0x8F,0x01,0x00,0x0E,0xFE,0x01,0x00,0x06,0xF0,0x03,0x00,0x00,0x80,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x30, // 195 + 0x00,0x00,0x30,0x00,0x00,0x00,0x3E,0x00,0x00,0x80,0x0F,0x00,0x00,0xF0,0x03,0x00,0x00,0xFE,0x01,0x00,0x8C,0x8F,0x01,0x00,0xEC,0x83,0x01,0x00,0x60,0x80,0x01,0x00,0xE0,0x83,0x01,0x00,0x8C,0x8F,0x01,0x00,0x0C,0xFE,0x01,0x00,0x00,0xF0,0x03,0x00,0x00,0x80,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x30, // 196 + 0x00,0x00,0x30,0x00,0x00,0x00,0x3E,0x00,0x00,0x80,0x0F,0x00,0x00,0xF0,0x03,0x00,0x00,0xFE,0x01,0x00,0x9C,0x8F,0x01,0x00,0xE2,0x83,0x01,0x00,0x62,0x80,0x01,0x00,0xE2,0x83,0x01,0x00,0x9C,0x8F,0x01,0x00,0x00,0xFE,0x01,0x00,0x00,0xF0,0x03,0x00,0x00,0x80,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x30, // 197 + 0x00,0x00,0x30,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x0F,0x00,0x00,0xC0,0x03,0x00,0x00,0xF0,0x01,0x00,0x00,0xBC,0x01,0x00,0x00,0x8F,0x01,0x00,0xC0,0x83,0x01,0x00,0xE0,0x80,0x01,0x00,0x60,0x80,0x01,0x00,0x60,0x80,0x01,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x00,0x30, // 198 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xC0,0x00,0x18,0x00,0x60,0x00,0x30,0x02,0x60,0x00,0x30,0x02,0x60,0x00,0xF0,0x02,0x60,0x00,0xB0,0x03,0x60,0x00,0x30,0x01,0x60,0x00,0x30,0x00,0xC0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x03,0x0F,0x00,0x00,0x02,0x03, // 199 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x62,0x30,0x30,0x00,0x66,0x30,0x30,0x00,0x6E,0x30,0x30,0x00,0x68,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x00,0x30, // 200 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x68,0x30,0x30,0x00,0x6E,0x30,0x30,0x00,0x66,0x30,0x30,0x00,0x62,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x00,0x30, // 201 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x68,0x30,0x30,0x00,0x6C,0x30,0x30,0x00,0x66,0x30,0x30,0x00,0x66,0x30,0x30,0x00,0x6C,0x30,0x30,0x00,0x68,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x00,0x30, // 202 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x6C,0x30,0x30,0x00,0x6C,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x6C,0x30,0x30,0x00,0x6C,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x00,0x30, // 203 + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0xE6,0xFF,0x3F,0x00,0xEE,0xFF,0x3F,0x00,0x08, // 204 + 0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0xEE,0xFF,0x3F,0x00,0xE6,0xFF,0x3F,0x00,0x02, // 205 + 0x08,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0xE6,0xFF,0x3F,0x00,0xE6,0xFF,0x3F,0x00,0x0C,0x00,0x00,0x00,0x08, // 206 + 0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x0C,0x00,0x00,0x00,0x0C, // 207 + 0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0xE0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x03,0x0E,0x00,0x00,0xFF,0x07,0x00,0x00,0xFC,0x01, // 208 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0xC0,0x01,0x00,0x00,0x8C,0x03,0x00,0x00,0x0E,0x0E,0x00,0x00,0x06,0x3C,0x00,0x00,0x06,0x70,0x00,0x00,0x0C,0xE0,0x01,0x00,0x0C,0x80,0x03,0x00,0x0E,0x00,0x0F,0x00,0x06,0x00,0x1C,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F, // 209 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xC0,0x00,0x18,0x00,0xE0,0x00,0x38,0x00,0x62,0x00,0x30,0x00,0x66,0x00,0x30,0x00,0x6E,0x00,0x30,0x00,0x68,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0xE0,0x00,0x38,0x00,0xC0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x07,0x0F,0x00,0x00,0xFF,0x07,0x00,0x00,0xFC,0x01, // 210 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xC0,0x00,0x18,0x00,0xE0,0x00,0x38,0x00,0x60,0x00,0x30,0x00,0x68,0x00,0x30,0x00,0x6E,0x00,0x30,0x00,0x66,0x00,0x30,0x00,0x62,0x00,0x30,0x00,0xE0,0x00,0x38,0x00,0xC0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x07,0x0F,0x00,0x00,0xFF,0x07,0x00,0x00,0xFC,0x01, // 211 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xC0,0x00,0x18,0x00,0xE0,0x00,0x38,0x00,0x68,0x00,0x30,0x00,0x6C,0x00,0x30,0x00,0x66,0x00,0x30,0x00,0x66,0x00,0x30,0x00,0x6C,0x00,0x30,0x00,0xE8,0x00,0x38,0x00,0xC0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x07,0x0F,0x00,0x00,0xFF,0x07,0x00,0x00,0xFC,0x01, // 212 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xCC,0x00,0x18,0x00,0xEE,0x00,0x38,0x00,0x66,0x00,0x30,0x00,0x66,0x00,0x30,0x00,0x6C,0x00,0x30,0x00,0x6C,0x00,0x30,0x00,0x6E,0x00,0x30,0x00,0xE6,0x00,0x38,0x00,0xC0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x07,0x0F,0x00,0x00,0xFF,0x07,0x00,0x00,0xFC,0x01, // 213 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xC0,0x00,0x18,0x00,0xE0,0x00,0x38,0x00,0x6C,0x00,0x30,0x00,0x6C,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x6C,0x00,0x30,0x00,0xEC,0x00,0x38,0x00,0xC0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x07,0x0F,0x00,0x00,0xFF,0x07,0x00,0x00,0xFC,0x01, // 214 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x03,0x00,0x00,0x8E,0x03,0x00,0x00,0xDC,0x01,0x00,0x00,0xF8,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0xDC,0x01,0x00,0x00,0x8E,0x03,0x00,0x00,0x06,0x03, // 215 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x21,0x00,0x00,0xFF,0x77,0x00,0x80,0x07,0x3F,0x00,0xC0,0x01,0x1E,0x00,0xC0,0x00,0x1F,0x00,0xE0,0x80,0x3B,0x00,0x60,0xC0,0x31,0x00,0x60,0xE0,0x30,0x00,0x60,0x70,0x30,0x00,0x60,0x38,0x30,0x00,0x60,0x1C,0x30,0x00,0xE0,0x0E,0x38,0x00,0xC0,0x07,0x18,0x00,0xC0,0x03,0x1C,0x00,0xE0,0x07,0x0F,0x00,0x70,0xFF,0x07,0x00,0x20,0xFC,0x01, // 216 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x03,0x00,0xE0,0xFF,0x0F,0x00,0x00,0x00,0x1C,0x00,0x00,0x00,0x38,0x00,0x02,0x00,0x30,0x00,0x06,0x00,0x30,0x00,0x0E,0x00,0x30,0x00,0x08,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x1C,0x00,0xE0,0xFF,0x0F,0x00,0xE0,0xFF,0x03, // 217 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x03,0x00,0xE0,0xFF,0x0F,0x00,0x00,0x00,0x1C,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x30,0x00,0x08,0x00,0x30,0x00,0x0E,0x00,0x30,0x00,0x06,0x00,0x30,0x00,0x02,0x00,0x30,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x1C,0x00,0xE0,0xFF,0x0F,0x00,0xE0,0xFF,0x03, // 218 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x03,0x00,0xE0,0xFF,0x0F,0x00,0x00,0x00,0x1C,0x00,0x00,0x00,0x38,0x00,0x08,0x00,0x30,0x00,0x0C,0x00,0x30,0x00,0x06,0x00,0x30,0x00,0x06,0x00,0x30,0x00,0x0C,0x00,0x30,0x00,0x08,0x00,0x38,0x00,0x00,0x00,0x1C,0x00,0xE0,0xFF,0x0F,0x00,0xE0,0xFF,0x03, // 219 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x03,0x00,0xE0,0xFF,0x0F,0x00,0x00,0x00,0x1C,0x00,0x00,0x00,0x38,0x00,0x0C,0x00,0x30,0x00,0x0C,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x0C,0x00,0x30,0x00,0x0C,0x00,0x38,0x00,0x00,0x00,0x1C,0x00,0xE0,0xFF,0x0F,0x00,0xE0,0xFF,0x03, // 220 + 0x20,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0xC0,0x01,0x00,0x00,0x80,0x03,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x3C,0x00,0x00,0x08,0xF0,0x3F,0x00,0x0E,0xF0,0x3F,0x00,0x06,0x3C,0x00,0x00,0x02,0x1E,0x00,0x00,0x00,0x07,0x00,0x00,0xC0,0x03,0x00,0x00,0xE0,0x01,0x00,0x00,0x60,0x00,0x00,0x00,0x20, // 221 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x00,0x03,0x06,0x00,0x00,0x03,0x06,0x00,0x00,0x03,0x06,0x00,0x00,0x03,0x06,0x00,0x00,0x03,0x06,0x00,0x00,0x03,0x06,0x00,0x00,0x03,0x06,0x00,0x00,0x03,0x07,0x00,0x00,0x86,0x03,0x00,0x00,0xFE,0x01,0x00,0x00,0xF8, // 222 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xFF,0x3F,0x00,0xC0,0xFF,0x3F,0x00,0xC0,0x00,0x00,0x00,0x60,0x00,0x08,0x00,0x60,0x00,0x1C,0x00,0x60,0x00,0x38,0x00,0xE0,0x78,0x30,0x00,0xC0,0x7F,0x30,0x00,0x80,0xC7,0x30,0x00,0x00,0x80,0x39,0x00,0x00,0x80,0x1F,0x00,0x00,0x00,0x0F, // 223 + 0x00,0x00,0x00,0x00,0x00,0x18,0x0E,0x00,0x00,0x1C,0x1F,0x00,0x00,0x8C,0x39,0x00,0x20,0x86,0x31,0x00,0x60,0x86,0x31,0x00,0xE0,0xC6,0x30,0x00,0x80,0xC6,0x18,0x00,0x00,0xCE,0x0C,0x00,0x00,0xFC,0x1F,0x00,0x00,0xF8,0x3F,0x00,0x00,0x00,0x20, // 224 + 0x00,0x00,0x00,0x00,0x00,0x18,0x0E,0x00,0x00,0x1C,0x1F,0x00,0x00,0x8C,0x39,0x00,0x00,0x86,0x31,0x00,0x80,0x86,0x31,0x00,0xE0,0xC6,0x30,0x00,0x60,0xC6,0x18,0x00,0x20,0xCE,0x0C,0x00,0x00,0xFC,0x1F,0x00,0x00,0xF8,0x3F,0x00,0x00,0x00,0x20, // 225 + 0x00,0x00,0x00,0x00,0x00,0x18,0x0E,0x00,0x00,0x1C,0x1F,0x00,0x80,0x8C,0x39,0x00,0xC0,0x86,0x31,0x00,0x60,0x86,0x31,0x00,0x60,0xC6,0x30,0x00,0xC0,0xC6,0x18,0x00,0x80,0xCE,0x0C,0x00,0x00,0xFC,0x1F,0x00,0x00,0xF8,0x3F,0x00,0x00,0x00,0x20, // 226 + 0x00,0x00,0x00,0x00,0x00,0x18,0x0E,0x00,0xC0,0x1C,0x1F,0x00,0xE0,0x8C,0x39,0x00,0x60,0x86,0x31,0x00,0x60,0x86,0x31,0x00,0xC0,0xC6,0x30,0x00,0xC0,0xC6,0x18,0x00,0xE0,0xCE,0x0C,0x00,0x60,0xFC,0x1F,0x00,0x00,0xF8,0x3F,0x00,0x00,0x00,0x20, // 227 + 0x00,0x00,0x00,0x00,0x00,0x18,0x0E,0x00,0x00,0x1C,0x1F,0x00,0xC0,0x8C,0x39,0x00,0xC0,0x86,0x31,0x00,0x00,0x86,0x31,0x00,0x00,0xC6,0x30,0x00,0xC0,0xC6,0x18,0x00,0xC0,0xCE,0x0C,0x00,0x00,0xFC,0x1F,0x00,0x00,0xF8,0x3F,0x00,0x00,0x00,0x20, // 228 + 0x00,0x00,0x00,0x00,0x00,0x18,0x0E,0x00,0x00,0x1C,0x1F,0x00,0x00,0x8C,0x39,0x00,0x70,0x86,0x31,0x00,0x88,0x86,0x31,0x00,0x88,0xC6,0x30,0x00,0x88,0xC6,0x18,0x00,0x70,0xCE,0x0C,0x00,0x00,0xFC,0x1F,0x00,0x00,0xF8,0x3F,0x00,0x00,0x00,0x20, // 229 + 0x00,0x00,0x00,0x00,0x00,0x10,0x0F,0x00,0x00,0x9C,0x1F,0x00,0x00,0xCC,0x39,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0x66,0x18,0x00,0x00,0x6E,0x1C,0x00,0x00,0xFC,0x0F,0x00,0x00,0xFC,0x1F,0x00,0x00,0xCC,0x1C,0x00,0x00,0xCE,0x38,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0xCC,0x18,0x00,0x00,0xF8,0x0C,0x00,0x00,0xE0,0x04, // 230 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x00,0x0E,0x38,0x02,0x00,0x06,0x30,0x02,0x00,0x06,0xF0,0x02,0x00,0x06,0xB0,0x03,0x00,0x0E,0x38,0x01,0x00,0x1C,0x1C,0x00,0x00,0x18,0x0C, // 231 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0xDC,0x1C,0x00,0x20,0xCE,0x38,0x00,0x60,0xC6,0x30,0x00,0xE0,0xC6,0x30,0x00,0x80,0xC6,0x30,0x00,0x00,0xCE,0x38,0x00,0x00,0xDC,0x18,0x00,0x00,0xF8,0x0C,0x00,0x00,0xF0,0x04, // 232 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0xDC,0x1C,0x00,0x00,0xCE,0x38,0x00,0x80,0xC6,0x30,0x00,0xE0,0xC6,0x30,0x00,0x60,0xC6,0x30,0x00,0x20,0xCE,0x38,0x00,0x00,0xDC,0x18,0x00,0x00,0xF8,0x0C,0x00,0x00,0xF0,0x04, // 233 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0xDC,0x1C,0x00,0x80,0xCE,0x38,0x00,0xC0,0xC6,0x30,0x00,0x60,0xC6,0x30,0x00,0x60,0xC6,0x30,0x00,0xC0,0xCE,0x38,0x00,0x80,0xDC,0x18,0x00,0x00,0xF8,0x0C,0x00,0x00,0xF0,0x04, // 234 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0xDC,0x1C,0x00,0xC0,0xCE,0x38,0x00,0xC0,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0xC0,0xCE,0x38,0x00,0xC0,0xDC,0x18,0x00,0x00,0xF8,0x0C,0x00,0x00,0xF0,0x04, // 235 + 0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x60,0xFE,0x3F,0x00,0xE0,0xFE,0x3F,0x00,0x80, // 236 + 0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0xE0,0xFE,0x3F,0x00,0x60,0xFE,0x3F,0x00,0x20, // 237 + 0x80,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0x60,0xFE,0x3F,0x00,0x60,0xFE,0x3F,0x00,0xC0,0x00,0x00,0x00,0x80, // 238 + 0xC0,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F,0x00,0xC0,0x00,0x00,0x00,0xC0, // 239 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1D,0x1C,0x00,0xA0,0x0F,0x38,0x00,0xA0,0x06,0x30,0x00,0xE0,0x06,0x30,0x00,0xC0,0x06,0x30,0x00,0xC0,0x0F,0x38,0x00,0x20,0x1F,0x1C,0x00,0x00,0xFC,0x0F,0x00,0x00,0xE0,0x07, // 240 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x3F,0x00,0xC0,0xFE,0x3F,0x00,0xE0,0x18,0x00,0x00,0x60,0x0C,0x00,0x00,0x60,0x06,0x00,0x00,0xC0,0x06,0x00,0x00,0xC0,0x06,0x00,0x00,0xE0,0x0E,0x00,0x00,0x60,0xFC,0x3F,0x00,0x00,0xF8,0x3F, // 241 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x20,0x0E,0x38,0x00,0x60,0x06,0x30,0x00,0xE0,0x06,0x30,0x00,0x80,0x06,0x30,0x00,0x00,0x0E,0x38,0x00,0x00,0x1C,0x1C,0x00,0x00,0xF8,0x0F,0x00,0x00,0xF0,0x07, // 242 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x00,0x0E,0x38,0x00,0x80,0x06,0x30,0x00,0xE0,0x06,0x30,0x00,0x60,0x06,0x30,0x00,0x20,0x0E,0x38,0x00,0x00,0x1C,0x1C,0x00,0x00,0xF8,0x0F,0x00,0x00,0xF0,0x07, // 243 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x80,0x0E,0x38,0x00,0xC0,0x06,0x30,0x00,0x60,0x06,0x30,0x00,0x60,0x06,0x30,0x00,0xC0,0x0E,0x38,0x00,0x80,0x1C,0x1C,0x00,0x00,0xF8,0x0F,0x00,0x00,0xF0,0x07, // 244 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0xF8,0x0F,0x00,0xC0,0x1C,0x1C,0x00,0xE0,0x0E,0x38,0x00,0x60,0x06,0x30,0x00,0x60,0x06,0x30,0x00,0xC0,0x06,0x30,0x00,0xC0,0x0E,0x38,0x00,0xE0,0x1C,0x1C,0x00,0x60,0xF8,0x0F,0x00,0x00,0xF0,0x07, // 245 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x1C,0x00,0xC0,0x0E,0x38,0x00,0xC0,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0xC0,0x0E,0x38,0x00,0xC0,0x1C,0x1C,0x00,0x00,0xF8,0x0F,0x00,0x00,0xF0,0x07, // 246 + 0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0xB6,0x01,0x00,0x00,0xB6,0x01,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30, // 247 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0x67,0x00,0x00,0xF8,0x7F,0x00,0x00,0x1C,0x1C,0x00,0x00,0x0E,0x3F,0x00,0x00,0x86,0x33,0x00,0x00,0xE6,0x31,0x00,0x00,0x76,0x30,0x00,0x00,0x3E,0x38,0x00,0x00,0x1C,0x1C,0x00,0x00,0xFF,0x0F,0x00,0x00,0xF3,0x07, // 248 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x0F,0x00,0x00,0xFE,0x1F,0x00,0x20,0x00,0x38,0x00,0x60,0x00,0x30,0x00,0xE0,0x00,0x30,0x00,0x80,0x00,0x30,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x0C,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F, // 249 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x0F,0x00,0x00,0xFE,0x1F,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x30,0x00,0x80,0x00,0x30,0x00,0xE0,0x00,0x30,0x00,0x60,0x00,0x18,0x00,0x20,0x00,0x0C,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F, // 250 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x0F,0x00,0x00,0xFE,0x1F,0x00,0x80,0x00,0x38,0x00,0xC0,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0xC0,0x00,0x18,0x00,0x80,0x00,0x0C,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F, // 251 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x0F,0x00,0x00,0xFE,0x1F,0x00,0xC0,0x00,0x38,0x00,0xC0,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0xC0,0x00,0x18,0x00,0xC0,0x00,0x0C,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F, // 252 + 0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x7E,0x00,0x06,0x00,0xF0,0x01,0x06,0x00,0x80,0x0F,0x07,0x80,0x00,0xFE,0x03,0xE0,0x00,0xFC,0x00,0x60,0xC0,0x1F,0x00,0x20,0xF8,0x03,0x00,0x00,0x3E,0x00,0x00,0x00,0x06, // 253 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0xFF,0x07,0xE0,0xFF,0xFF,0x07,0x00,0x1C,0x18,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x0E,0x38,0x00,0x00,0x1C,0x1C,0x00,0x00,0xF8,0x0F,0x00,0x00,0xF0,0x03, // 254 + 0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x7E,0x00,0x06,0xC0,0xF0,0x01,0x06,0xC0,0x80,0x0F,0x07,0x00,0x00,0xFE,0x03,0x00,0x00,0xFC,0x00,0xC0,0xC0,0x1F,0x00,0xC0,0xF8,0x03,0x00,0x00,0x3E,0x00,0x00,0x00,0x06 // 255 +}; diff --git a/src/helpers/ui/OLEDDisplayFonts.h b/src/helpers/ui/OLEDDisplayFonts.h new file mode 100644 index 00000000..266e4078 --- /dev/null +++ b/src/helpers/ui/OLEDDisplayFonts.h @@ -0,0 +1,13 @@ +#ifndef OLEDDISPLAYFONTS_h +#define OLEDDISPLAYFONTS_h + +#ifdef ARDUINO +#include +#elif __MBED__ +#define PROGMEM +#endif + +extern const uint8_t ArialMT_Plain_10[] PROGMEM; +extern const uint8_t ArialMT_Plain_16[] PROGMEM; +extern const uint8_t ArialMT_Plain_24[] PROGMEM; +#endif diff --git a/src/helpers/ui/ST7789Display.cpp b/src/helpers/ui/ST7789Display.cpp index 8f04f872..71f88ff1 100644 --- a/src/helpers/ui/ST7789Display.cpp +++ b/src/helpers/ui/ST7789Display.cpp @@ -2,15 +2,6 @@ #include "ST7789Display.h" -bool ST7789Display::i2c_probe(TwoWire& wire, uint8_t addr) { - return true; -/* - wire.beginTransmission(addr); - uint8_t error = wire.endTransmission(); - return (error == 0); -*/ -} - bool ST7789Display::begin() { if(!_isOn) { pinMode(PIN_TFT_VDD_CTL, OUTPUT); @@ -19,13 +10,8 @@ bool ST7789Display::begin() { digitalWrite(PIN_TFT_LEDA_CTL, LOW); digitalWrite(PIN_TFT_RST, HIGH); - display.init(135, 240); - display.setRotation(2); - display.setSPISpeed(40000000); - display.fillScreen(ST77XX_BLACK); - display.setTextColor(ST77XX_WHITE); - display.setTextSize(2); - display.cp437(true); // Use full 256 char 'Code Page 437' font + display.init(); + display.displayOn(); _isOn = true; } @@ -46,18 +32,33 @@ void ST7789Display::turnOff() { } void ST7789Display::clear() { - display.fillScreen(ST77XX_BLACK); + // display.fillScreen(ST77XX_BLACK); + display.clear(); } void ST7789Display::startFrame(Color bkg) { - display.fillScreen(0x00); - display.setTextColor(ST77XX_WHITE); - display.setTextSize(2); - display.cp437(true); // Use full 256 char 'Code Page 437' font + display.clear(); + // display.fillScreen(0x00); + // display.setTextColor(ST77XX_WHITE); + // display.setTextSize(2); + // display.cp437(true); // Use full 256 char 'Code Page 437' font } void ST7789Display::setTextSize(int sz) { - display.setTextSize(sz); +// display.setTextSize(sz); + switch(sz) { + case 1 : + display.setFont(ArialMT_Plain_10); + break; + case 2 : + display.setFont(ArialMT_Plain_16); + break; + case 3 : + display.setFont(ArialMT_Plain_24); + break; + default: + display.setFont(ArialMT_Plain_10); + } } void ST7789Display::setColor(Color c) { @@ -87,31 +88,34 @@ void ST7789Display::setColor(Color c) { _color = ST77XX_WHITE; break; } - display.setTextColor(_color); + display.setRGB(_color); + //display.setColor((OLEDDISPLAY_COLOR) 4); } void ST7789Display::setCursor(int x, int y) { - display.setCursor(x, y); + //display.setCursor(x, y); + _x = x; + _y = y; } void ST7789Display::print(const char* str) { - display.print(str); + display.drawString(_x, _y, str); } void ST7789Display::fillRect(int x, int y, int w, int h) { - display.fillRect(x, y, w, h, _color); + display.fillRect(x, y, w, h); } void ST7789Display::drawRect(int x, int y, int w, int h) { - display.drawRect(x, y, w, h, _color); + display.drawRect(x, y, w, h); } void ST7789Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { - display.drawBitmap(x, y, bits, w, h, _color); + display.drawXbm(x, y, w, h, bits); } void ST7789Display::endFrame() { - // display.display(); + display.display(); } #endif \ No newline at end of file diff --git a/src/helpers/ui/ST7789Display.h b/src/helpers/ui/ST7789Display.h index af319ef0..769957f1 100644 --- a/src/helpers/ui/ST7789Display.h +++ b/src/helpers/ui/ST7789Display.h @@ -4,16 +4,18 @@ #include #include #include -#include +#include class ST7789Display : public DisplayDriver { - Adafruit_ST7789 display; + ST7789Spi display; bool _isOn; uint16_t _color; + int _x=0, _y=0; bool i2c_probe(TwoWire& wire, uint8_t addr); public: - ST7789Display() : DisplayDriver(135, 240), display(&SPI1, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST) { _isOn = false; } + ST7789Display() : DisplayDriver(135, 240), display(&SPI1, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 240, 135) {_isOn = false;} + // ST7789Display() : DisplayDriver(135, 240), display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST) { _isOn = false; } bool begin(); diff --git a/src/helpers/ui/ST7789Spi.h b/src/helpers/ui/ST7789Spi.h new file mode 100644 index 00000000..a43fa1f5 --- /dev/null +++ b/src/helpers/ui/ST7789Spi.h @@ -0,0 +1,430 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * Copyright (c) 2024 by Heltec AutoMation + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + +#ifndef ST7789Spi_h +#define ST7789Spi_h + +#include "OLEDDisplay.h" +#include + + +#define ST_CMD_DELAY 0x80 // special signifier for command lists + +#define ST77XX_NOP 0x00 +#define ST77XX_SWRESET 0x01 +#define ST77XX_RDDID 0x04 +#define ST77XX_RDDST 0x09 + +#define ST77XX_SLPIN 0x10 +#define ST77XX_SLPOUT 0x11 +#define ST77XX_PTLON 0x12 +#define ST77XX_NORON 0x13 + +#define ST77XX_INVOFF 0x20 +#define ST77XX_INVON 0x21 +#define ST77XX_DISPOFF 0x28 +#define ST77XX_DISPON 0x29 +#define ST77XX_CASET 0x2A +#define ST77XX_RASET 0x2B +#define ST77XX_RAMWR 0x2C +#define ST77XX_RAMRD 0x2E + +#define ST77XX_PTLAR 0x30 +#define ST77XX_TEOFF 0x34 +#define ST77XX_TEON 0x35 +#define ST77XX_MADCTL 0x36 +#define ST77XX_COLMOD 0x3A + +#define ST77XX_MADCTL_MY 0x80 +#define ST77XX_MADCTL_MX 0x40 +#define ST77XX_MADCTL_MV 0x20 +#define ST77XX_MADCTL_ML 0x10 +#define ST77XX_MADCTL_RGB 0x00 + +#define ST77XX_RDID1 0xDA +#define ST77XX_RDID2 0xDB +#define ST77XX_RDID3 0xDC +#define ST77XX_RDID4 0xDD + +// Some ready-made 16-bit ('565') color settings: +#define ST77XX_BLACK 0x0000 +#define ST77XX_WHITE 0xFFFF +#define ST77XX_RED 0xF800 +#define ST77XX_GREEN 0x07E0 +#define ST77XX_BLUE 0x001F +#define ST77XX_CYAN 0x07FF +#define ST77XX_MAGENTA 0xF81F +#define ST77XX_YELLOW 0xFFE0 +#define ST77XX_ORANGE 0xFC00 + +#define LED_A_ON LOW + +#ifdef ESP_PLATFORM +#undef LED_A_ON +#define LED_A_ON HIGH +#define rtos_free free +#define rtos_malloc malloc +//SPIClass SPI1(HSPI); +#endif +class ST7789Spi : public OLEDDisplay { + private: + uint8_t _rst; + uint8_t _dc; + uint8_t _cs; + uint8_t _ledA; + int _miso; + int _mosi; + int _clk; + SPIClass * _spi; + SPISettings _spiSettings; + uint16_t _RGB=0xFFFF; + uint8_t _buffheight; + public: + /* pass _cs as -1 to indicate "do not use CS pin", for cases where it is hard wired low */ + ST7789Spi(SPIClass *spiClass,uint8_t _rst, uint8_t _dc, uint8_t _cs, OLEDDISPLAY_GEOMETRY g = GEOMETRY_RAWMODE,uint16_t width=240,uint16_t height=135,int mosi=-1,int miso=-1,int clk=-1) { + this->_spi = spiClass; + this->_rst = _rst; + this->_dc = _dc; + this->_cs = _cs; + this->_mosi=mosi; + this->_miso=miso; + this->_clk=clk; + //this->_ledA = _ledA; + _spiSettings = SPISettings(40000000, MSBFIRST, SPI_MODE0); + setGeometry(g,width,height); + } + + bool connect(){ + this->_buffheight=displayHeight / 8; + this->_buffheight+=displayHeight % 8 ? 1:0; + pinMode(_cs, OUTPUT); + pinMode(_dc, OUTPUT); + //pinMode(_ledA, OUTPUT); + if (_cs != (uint8_t) -1) { + pinMode(_cs, OUTPUT); + } + pinMode(_rst, OUTPUT); + +#ifdef ESP_PLATFORM + _spi->begin(_clk,_miso,_mosi,-1); +#else + _spi->begin(); +#endif + _spi->setClockDivider (SPI_CLOCK_DIV2); + + // Pulse Reset low for 10ms + digitalWrite(_rst, HIGH); + delay(1); + digitalWrite(_rst, LOW); + delay(10); + digitalWrite(_rst, HIGH); + _spi->begin (); + //digitalWrite(_ledA, LED_A_ON); + return true; + } + + void display(void) { + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + + uint16_t minBoundY = UINT16_MAX; + uint16_t maxBoundY = 0; + + uint16_t minBoundX = UINT16_MAX; + uint16_t maxBoundX = 0; + + uint16_t x, y; + + // Calculate the Y bounding box of changes + // and copy buffer[pos] to buffer_back[pos]; + for (y = 0; y < _buffheight; y++) { + for (x = 0; x < displayWidth; x++) { + //Serial.printf("x %d y %d\r\n",x,y); + uint16_t pos = x + y * displayWidth; + if (buffer[pos] != buffer_back[pos]) { + minBoundY = min(minBoundY, y); + maxBoundY = max(maxBoundY, y); + minBoundX = min(minBoundX, x); + maxBoundX = max(maxBoundX, x); + } + buffer_back[pos] = buffer[pos]; + } + yield(); + } + + // If the minBoundY wasn't updated + // we can savely assume that buffer_back[pos] == buffer[pos] + // holdes true for all values of pos + if (minBoundY == UINT16_MAX) return; + + set_CS(LOW); + _spi->beginTransaction(_spiSettings); + + for (y = minBoundY; y <= maxBoundY; y++) + { + for(int temp = 0; temp<8;temp++) + { + //setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); + setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); + //setAddrWindow(y*8+temp,minBoundX,1,maxBoundX-minBoundX+1); + uint32_t const pixbufcount = maxBoundX-minBoundX+1; + uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount); + for (x = minBoundX; x <= maxBoundX; x++) + { + pixbuf[x-minBoundX] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0; + } +#ifdef ESP_PLATFORM + _spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount); +#else + _spi->transfer(pixbuf, NULL, 2 * pixbufcount); +#endif + rtos_free(pixbuf); + } + } + _spi->endTransaction(); + set_CS(HIGH); + + #else + set_CS(LOW); + _spi->beginTransaction(_spiSettings); + uint8_t x, y; + for (y = 0; y < _buffheight; y++) + { + for(int temp = 0; temp<8;temp++) + { + //setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); + //setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); + setAddrWindow(y*8+temp,0,1,displayWidth); + uint32_t const pixbufcount = displayWidth; + uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount); + for (x = 0; x < displayWidth; x++) + { + pixbuf[x] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0; + } +#ifdef ESP_PLATFORM + _spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount); +#else + _spi->transfer(pixbuf, NULL, 2 * pixbufcount); +#endif + rtos_free(pixbuf); + } + } + _spi->endTransaction(); + set_CS(HIGH); + + #endif + } + + virtual void resetOrientation() { + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX; + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + } + + virtual void flipScreenVertically() { + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MY; + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + } + + virtual void mirrorScreen() { + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX|ST77XX_MADCTL_MY; + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + } + + void setRGB(uint16_t c) + { + + this->_RGB=0x00|c>>8|c<<8&0xFF00; + } + + void displayOn(void) { + //sendCommand(DISPLAYON); + } + + void displayOff(void) { + //sendCommand(DISPLAYOFF); + } + +//#define ST77XX_MADCTL_MY 0x80 +//#define ST77XX_MADCTL_MX 0x40 +//#define ST77XX_MADCTL_MV 0x20 +//#define ST77XX_MADCTL_ML 0x10 + protected: + // Send all the init commands + virtual void sendInitCommands() + { + sendCommand(ST77XX_SWRESET); // 1: Software reset, no args, w/delay + delay(150); + + sendCommand(ST77XX_SLPOUT); // 2: Out of sleep mode, no args, w/delay + delay(10); + + sendCommand(ST77XX_COLMOD); // 3: Set color mode, 16-bit color + WriteData(0x55); + delay(10); + + sendCommand(ST77XX_MADCTL); // 4: Mem access ctrl (directions), Row/col addr, bottom-top refresh + WriteData(0x08); + + sendCommand(ST77XX_CASET); // 5: Column addr set, + WriteData(0x00); + WriteData(0x00); // XSTART = 0 + WriteData(0x00); + WriteData(240); // XEND = 240 + + sendCommand(ST77XX_RASET); // 6: Row addr set, + WriteData(0x00); + WriteData(0x00); // YSTART = 0 + WriteData(320>>8); + WriteData(320&0xFF); // YSTART = 320 + + sendCommand(ST77XX_SLPOUT); // 7: hack + delay(10); + + sendCommand(ST77XX_NORON); // 8: Normal display on, no args, w/delay + delay(10); + + sendCommand(ST77XX_DISPON); // 9: Main screen turn on, no args, delay + delay(10); + + sendCommand(ST77XX_INVON); // 10: invert + delay(10); + + //uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MX; + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX; + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + setRGB(ST77XX_GREEN); + } + + + private: + + void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { + x += (320-displayWidth)/2; + y += (240-displayHeight)/2; + uint32_t xa = ((uint32_t)x << 16) | (x + w - 1); + uint32_t ya = ((uint32_t)y << 16) | (y + h - 1); + + writeCommand(ST77XX_CASET); // Column addr set + SPI_WRITE32(xa); + + writeCommand(ST77XX_RASET); // Row addr set + SPI_WRITE32(ya); + + writeCommand(ST77XX_RAMWR); // write to RAM + } + int getBufferOffset(void) { + return 0; + } + inline void set_CS(bool level) { + if (_cs != (uint8_t) -1) { + digitalWrite(_cs, level); + } + }; + inline void sendCommand(uint8_t com) __attribute__((always_inline)){ + set_CS(HIGH); + digitalWrite(_dc, LOW); + set_CS(LOW); + _spi->beginTransaction(_spiSettings); + _spi->transfer(com); + _spi->endTransaction(); + set_CS(HIGH); + digitalWrite(_dc, HIGH); + } + + inline void WriteData(uint8_t data) __attribute__((always_inline)){ + digitalWrite(_cs, LOW); + _spi->beginTransaction(_spiSettings); + _spi->transfer(data); + _spi->endTransaction(); + digitalWrite(_cs, HIGH); + } + void SPI_WRITE32(uint32_t l) + { + _spi->transfer(l >> 24); + _spi->transfer(l >> 16); + _spi->transfer(l >> 8); + _spi->transfer(l); + } + void writeCommand(uint8_t cmd) { + digitalWrite(_dc, LOW); + _spi->transfer(cmd); + digitalWrite(_dc, HIGH); + } + +// Private functions + void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width, uint16_t height) { + this->geometry = g; + + switch (g) { + case GEOMETRY_128_128: + this->displayWidth = 128; + this->displayHeight = 128; + break; + case GEOMETRY_128_64: + this->displayWidth = 128; + this->displayHeight = 64; + break; + case GEOMETRY_128_32: + this->displayWidth = 128; + this->displayHeight = 32; + break; + case GEOMETRY_64_48: + this->displayWidth = 64; + this->displayHeight = 48; + break; + case GEOMETRY_64_32: + this->displayWidth = 64; + this->displayHeight = 32; + break; + case GEOMETRY_RAWMODE: + this->displayWidth = width > 0 ? width : 128; + this->displayHeight = height > 0 ? height : 64; + break; + } + uint8_t tmp=displayHeight % 8; + uint8_t _buffheight=displayHeight / 8; + + if(tmp!=0) + _buffheight++; + this->displayBufferSize = displayWidth * _buffheight ; + } + + + +}; + +#endif diff --git a/variants/t114/platformio.ini b/variants/t114/platformio.ini index 2abdfc69..232dfb0b 100644 --- a/variants/t114/platformio.ini +++ b/variants/t114/platformio.ini @@ -60,6 +60,9 @@ build_flags = extends = Heltec_t114 build_flags = ${Heltec_t114.build_flags} + -I src/helpers/ui + -D ST7789 + -D DISPLAY_CLASS=ST7789Display -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -69,23 +72,17 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_t114.build_src_filter} - + + + + + +<../examples/companion_radio/main.cpp> -lib_deps = - ${Heltec_t114.lib_deps} - densaugeo/base64 @ ~1.4.0 - -[env:Heltec_t114_companion_radio_ble_screen] -extends = env:Heltec_t114_companion_radio_ble -build_flags = ${env:Heltec_t114_companion_radio_ble.build_flags} - -I src/helpers/ui - -D ST7789 - -D DISPLAY_CLASS=ST7789Display -build_src_filter = ${env:Heltec_t114_companion_radio_ble.build_src_filter} +<../examples/companion_radio/UITask.cpp> + -lib_deps = ${env:Heltec_t114_companion_radio_ble.lib_deps} - adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + + + + +lib_deps = + adafruit/Adafruit GFX Library @ ^1.12.1 + ${Heltec_t114.lib_deps} + densaugeo/base64 @ ~1.4.0 [env:Heltec_t114_companion_radio_usb] extends = Heltec_t114 From f51ab11cf1aa8eca26bfee0e8aa3b5334a4e8f88 Mon Sep 17 00:00:00 2001 From: Alessandro Genova Date: Thu, 24 Apr 2025 21:54:37 -0400 Subject: [PATCH 016/154] companion_radio: greatly reduce the status LED usage --- examples/companion_radio/UITask.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index 7448f303..cc9ed6ed 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -4,6 +4,12 @@ #define AUTO_OFF_MILLIS 15000 // 15 seconds +#ifdef PIN_STATUS_LED +#define LED_ON_MILLIS 20 +#define LED_ON_MSG_MILLIS 200 +#define LED_CYCLE_MILLIS 4000 +#endif + #ifndef USER_BTN_PRESSED #define USER_BTN_PRESSED LOW #endif @@ -135,22 +141,21 @@ void UITask::userLedHandler() { #ifdef PIN_STATUS_LED static int state = 0; static int next_change = 0; + static int last_increment = 0; + int cur_time = millis(); if (cur_time > next_change) { if (state == 0) { - state = 1; // led on, short = unread msg + state = 1; if (_msgcount > 0) { - next_change = cur_time + 500; + last_increment = LED_ON_MSG_MILLIS; } else { - next_change = cur_time + 2000; + last_increment = LED_ON_MILLIS; } + next_change = cur_time + last_increment; } else { state = 0; - if (_board->getBattMilliVolts() > 3800) { - next_change = cur_time + 2000; - } else { - next_change = cur_time + 4000; // 4s blank if bat level low - } + next_change = cur_time + LED_CYCLE_MILLIS - last_increment; } digitalWrite(PIN_STATUS_LED, state); } From c942aa06f930a28bcdea16d20f11744a4901cc71 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 26 Apr 2025 11:05:13 +1000 Subject: [PATCH 017/154] * Packet::readFrom() payload_len guard --- src/Packet.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Packet.cpp b/src/Packet.cpp index cb31638c..2d54ca45 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -52,6 +52,7 @@ bool Packet::readFrom(const uint8_t src[], uint8_t len) { memcpy(path, &src[i], path_len); i += path_len; if (i >= len) return false; // bad encoding payload_len = len - i; + if (payload_len > sizeof(payload)) return false; // bad encoding memcpy(payload, &src[i], payload_len); //i += payload_len; return true; // success } From 8f5a2ac83251cec9f3bd525862690e335766dfb0 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sat, 26 Apr 2025 17:54:59 +1200 Subject: [PATCH 018/154] remove pin mode setup from uitask --- examples/companion_radio/UITask.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index 7448f303..ea6eb435 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -43,10 +43,6 @@ void UITask::begin(DisplayDriver* display, const char* node_name, const char* bu *dash = 0; } - #ifdef PIN_USER_BTN - pinMode(PIN_USER_BTN, INPUT); - #endif - // v1.2.3 (1 Jan 2025) sprintf(_version_info, "%s (%s)", version, build_date); } From 4f2aaa47d3a38fe8002fba191d8980a2af62fa65 Mon Sep 17 00:00:00 2001 From: recrof Date: Sun, 27 Apr 2025 10:24:38 +0200 Subject: [PATCH 019/154] detect if we have nrf52 by probing for *.zip and *.hex output files --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 build.sh diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 index 10cabe7a..095a1633 --- a/build.sh +++ b/build.sh @@ -58,7 +58,7 @@ build_firmware() { fi # build .uf2 for nrf52 boards - if [[ $1 == *"RAK_4631"* || $1 == *"t1000e"* || $1 == *"t114"* || $1 == *"T-Echo"* || $1 == *"Faketec"* || $1 == *"ProMicro"* ]]; then + if [[ -f .pio/build/$1/firmware.zip && -f .pio/build/$1/firmware.hex ]]; then python bin/uf2conv/uf2conv.py .pio/build/$1/firmware.hex -c -o .pio/build/$1/firmware.uf2 -f 0xADA52840 fi From 2cdb3b501c4c90cc84e7c691e7b6654bee9ff051 Mon Sep 17 00:00:00 2001 From: Florent de Lamotte Date: Mon, 28 Apr 2025 11:08:20 +0200 Subject: [PATCH 020/154] add display to companion_radio_ble target --- variants/xiao_s3_wio/platformio.ini | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index ca043f1f..5073efd8 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -79,17 +79,20 @@ build_flags = -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 + -D DISPLAY_CLASS=SSD1306Display ; -D BLE_DEBUG_LOGGING=1 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Xiao_S3_WIO.build_src_filter} + + + - +<../examples/companion_radio/main.cpp> + +<../examples/companion_radio> lib_deps = ${Xiao_S3_WIO.lib_deps} densaugeo/base64 @ ~1.4.0 + adafruit/Adafruit SSD1306 @ ^2.5.13 [env:Xiao_S3_WIO_companion_radio_serial] extends = Xiao_S3_WIO @@ -108,24 +111,4 @@ lib_deps = ${Xiao_S3_WIO.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Xiao_S3_WIO_expansion_companion_radio_ble] -extends = Xiao_S3_WIO -build_flags = - ${Xiao_S3_WIO.build_flags} - -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=8 - -D BLE_PIN_CODE=123456 - -D DISPLAY_CLASS=SSD1306Display -; -D BLE_DEBUG_LOGGING=1 -; -D ENABLE_PRIVATE_KEY_IMPORT=1 -; -D ENABLE_PRIVATE_KEY_EXPORT=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${Xiao_S3_WIO.build_src_filter} - + - + - +<../examples/companion_radio> -lib_deps = - ${Xiao_S3_WIO.lib_deps} - densaugeo/base64 @ ~1.4.0 - adafruit/Adafruit SSD1306 @ ^2.5.13 + From 7eebd81cd04316250195a0ac06e1df0b3f23a74f Mon Sep 17 00:00:00 2001 From: Florent de Lamotte Date: Mon, 28 Apr 2025 16:17:29 +0200 Subject: [PATCH 021/154] use Stream abstract interface for serial port in ArduinoSerialInterface --- src/helpers/ArduinoSerialInterface.h | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/helpers/ArduinoSerialInterface.h b/src/helpers/ArduinoSerialInterface.h index e84f6d37..c4086353 100644 --- a/src/helpers/ArduinoSerialInterface.h +++ b/src/helpers/ArduinoSerialInterface.h @@ -8,30 +8,18 @@ class ArduinoSerialInterface : public BaseSerialInterface { uint8_t _state; uint16_t _frame_len; uint16_t rx_len; -#ifdef LILYGO_T3S3 - HWCDC* _serial; -#elif defined(NRF52_PLATFORM) - Adafruit_USBD_CDC* _serial; -#else - HardwareSerial* _serial; -#endif + Stream* _serial; uint8_t rx_buf[MAX_FRAME_SIZE]; public: ArduinoSerialInterface() { _isEnabled = false; _state = 0; } -#ifdef LILYGO_T3S3 - void begin(HWCDC& serial) { _serial = &serial; } -#elif defined(NRF52_PLATFORM) - void begin(Adafruit_USBD_CDC& serial) { - _serial = &serial; + void begin(Stream& serial) { + _serial = &serial; #ifdef RAK_4631 pinMode(WB_IO2, OUTPUT); - #endif + #endif } -#else - void begin(HardwareSerial& serial) { _serial = &serial; } -#endif // BaseSerialInterface methods void enable() override; From 154b5e401452f571862459e5e054e8b1a35b0b99 Mon Sep 17 00:00:00 2001 From: recrof Date: Tue, 29 Apr 2025 17:32:08 +0200 Subject: [PATCH 022/154] New Board: Elecrow ThinkNode M1 --- boards/t-echo.json | 36 +++---- boards/thinknode_m1.json | 71 +++++++++++++ src/helpers/nrf52/ThinkNodeM1Board.cpp | 90 +++++++++++++++++ src/helpers/nrf52/ThinkNodeM1Board.h | 49 +++++++++ src/helpers/ui/GxEPDDisplay.cpp | 6 +- variants/thinknode_m1/platformio.ini | 86 ++++++++++++++++ variants/thinknode_m1/target.cpp | 69 +++++++++++++ variants/thinknode_m1/target.h | 18 ++++ variants/thinknode_m1/variant.cpp | 34 +++++++ variants/thinknode_m1/variant.h | 133 +++++++++++++++++++++++++ 10 files changed, 573 insertions(+), 19 deletions(-) create mode 100644 boards/thinknode_m1.json create mode 100644 src/helpers/nrf52/ThinkNodeM1Board.cpp create mode 100644 src/helpers/nrf52/ThinkNodeM1Board.h create mode 100644 variants/thinknode_m1/platformio.ini create mode 100644 variants/thinknode_m1/target.cpp create mode 100644 variants/thinknode_m1/target.h create mode 100644 variants/thinknode_m1/variant.cpp create mode 100644 variants/thinknode_m1/variant.h diff --git a/boards/t-echo.json b/boards/t-echo.json index 6bfa64df..5c2703a3 100644 --- a/boards/t-echo.json +++ b/boards/t-echo.json @@ -1,32 +1,32 @@ { "build": { "arduino": { - "ldscript": "nrf52840_s140_v6.ld" + "ldscript": "nrf52840_s140_v6.ld" }, "core": "nRF5", "cpu": "cortex-m4", "extra_flags": "-DARDUINO_NRF52840_PCA10056 -DNRF52840_XXAA", "f_cpu": "64000000L", "hwids": [ - [ - "0x239A", - "0x8029" - ] + [ + "0x239A", + "0x8029" + ] ], "usb_product": "NRF52 DK", "mcu": "nrf52840", "variant": "pca10056", "bsp": { - "name": "adafruit" + "name": "adafruit" }, "softdevice": { - "sd_flags": "-DS140", - "sd_name": "s140", - "sd_version": "6.1.1", - "sd_fwid": "0x00B6" + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" }, "bootloader": { - "settings_addr": "0xFF000" + "settings_addr": "0xFF000" } }, "connectivity": [ @@ -35,7 +35,7 @@ "debug": { "jlink_device": "nRF52840_xxAA", "onboard_tools": [ - "jlink" + "jlink" ], "svd_path": "nrf52840.svd" }, @@ -50,13 +50,13 @@ "speed": 115200, "protocol": "jlink", "protocols": [ - "jlink", - "nrfjprog", - "stlink", - "cmsis-dap", - "blackmagic" + "jlink", + "nrfjprog", + "stlink", + "cmsis-dap", + "blackmagic" ] }, "url": "https://os.mbed.com/platforms/Nordic-nRF52840-DK/", "vendor": "Nordic" - } \ No newline at end of file +} \ No newline at end of file diff --git a/boards/thinknode_m1.json b/boards/thinknode_m1.json new file mode 100644 index 00000000..0f313063 --- /dev/null +++ b/boards/thinknode_m1.json @@ -0,0 +1,71 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_TTGO_EINK -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x4405" + ], + [ + "0x239A", + "0x0029" + ], + [ + "0x239A", + "0x002A" + ] + ], + "usb_product": "elecrow_eink", + "mcu": "nrf52840", + "variant": "ELECROW-ThinkNode-M1", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": [ + "bluetooth" + ], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": [ + "jlink" + ], + "svd_path": "nrf52840.svd" + }, + "frameworks": [ + "arduino" + ], + "name": "elecrow eink", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink" + ] + }, + "url": "https://github.com/Elecrow-RD", + "vendor": "ELECROW" +} \ No newline at end of file diff --git a/src/helpers/nrf52/ThinkNodeM1Board.cpp b/src/helpers/nrf52/ThinkNodeM1Board.cpp new file mode 100644 index 00000000..ef1cf111 --- /dev/null +++ b/src/helpers/nrf52/ThinkNodeM1Board.cpp @@ -0,0 +1,90 @@ +#include +#include "ThinkNodeM1Board.h" + +#ifdef THINKNODE_M1 + +#include +#include + +static BLEDfu bledfu; + +static void connect_callback(uint16_t conn_handle) { + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { + (void)conn_handle; + (void)reason; + + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + +void ThinkNodeM1Board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + + Wire.begin(); + + pinMode(SX126X_POWER_EN, OUTPUT); + digitalWrite(SX126X_POWER_EN, HIGH); + delay(10); // give sx1262 some time to power up +} + +uint16_t ThinkNodeM1Board::getBattMilliVolts() { + int adcvalue = 0; + + analogReference(AR_INTERNAL_3_0); + analogReadResolution(12); + delay(10); + + // ADC range is 0..3000mV and resolution is 12-bit (0..4095) + adcvalue = analogRead(PIN_VBAT_READ); + // Convert the raw value to compensated mv, taking the resistor- + // divider into account (providing the actual LIPO voltage) + return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB); +} + +bool ThinkNodeM1Board::startOTAUpdate(const char* id, char reply[]) { + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 + Bluefruit.setTxPower(4); + // Set the BLE device name + Bluefruit.setName("THINKNODE_M1_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // To be consistent OTA DFU should be added first if it exists + bledfu.begin(); + + // Set up and start advertising + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addName(); + + /* Start Advertising + - Enable auto advertising if disconnected + - Interval: fast mode = 20 ms, slow mode = 152.5 ms + - Timeout for fast mode is 30 seconds + - Start(timeout) with timeout = 0 will advertise forever (until connected) + + For recommended advertising interval + https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + + strcpy(reply, "OK - started"); + return true; +} +#endif diff --git a/src/helpers/nrf52/ThinkNodeM1Board.h b/src/helpers/nrf52/ThinkNodeM1Board.h new file mode 100644 index 00000000..cc87c96d --- /dev/null +++ b/src/helpers/nrf52/ThinkNodeM1Board.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +// LoRa radio module pins for Elecrow ThinkNode M1 +#define P_LORA_DIO_1 20 +#define P_LORA_NSS 24 +#define P_LORA_RESET 25 +#define P_LORA_BUSY 17 +#define P_LORA_SCLK 19 +#define P_LORA_MISO 23 +#define P_LORA_MOSI 22 +#define SX126X_POWER_EN 37 + +#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// built-ins +#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 + +#define VBAT_DIVIDER (0.5F) // 150K + 150K voltage divider on VBAT +#define VBAT_DIVIDER_COMP (2.0F) // Compensation factor for the VBAT divider + +#define PIN_VBAT_READ (4) +#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) + +class ThinkNodeM1Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + + void begin(); + uint16_t getBattMilliVolts() override; + bool startOTAUpdate(const char* id, char reply[]) override; + + uint8_t getStartupReason() const override { + return startup_reason; + } + + const char* getManufacturerName() const override { + return "Elecrow ThinkNode-M1"; + } + + void reboot() override { + NVIC_SystemReset(); + } +}; diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index e7b70b49..80fffbca 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -1,11 +1,15 @@ #include "GxEPDDisplay.h" +#ifndef DISPLAY_ROTATION + #define DISPLAY_ROTATION 3 +#endif + bool GxEPDDisplay::begin() { display.epd2.selectSPI(SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0)); SPI1.begin(); display.init(115200, true, 2, false); - display.setRotation(3); + display.setRotation(DISPLAY_ROTATION); #ifdef TECHO_ZOOM display.setFont(&FreeMono9pt7b); #endif diff --git a/variants/thinknode_m1/platformio.ini b/variants/thinknode_m1/platformio.ini new file mode 100644 index 00000000..1cdd66a1 --- /dev/null +++ b/variants/thinknode_m1/platformio.ini @@ -0,0 +1,86 @@ +[nrf52840_thinknode_m1] +extends = nrf52_base +platform_packages = framework-arduinoadafruitnrf52 +build_flags = ${nrf52_base.build_flags} + -I src/helpers/nrf52 + -I lib/nrf52/s140_nrf52_6.1.1_API/include + -I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52 +lib_deps = + ${nrf52_base.lib_deps} + rweather/Crypto @ ^0.4.0 + +[ThinkNode_M1] +extends = nrf52840_thinknode_m1 +board = thinknode_m1 +board_build.ldscript = boards/nrf52840_s140_v6.ld +build_flags = ${nrf52840_thinknode_m1.build_flags} + -I variants/thinknode_m1 + -D THINKNODE_M1=1 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_CURRENT_LIMIT=130 + -D SX126X_RX_BOOSTED_GAIN=1 + +build_src_filter = ${nrf52840_thinknode_m1.build_src_filter} + + + + + +<../variants/thinknode_m1> +debug_tool = jlink +upload_protocol = nrfutil + +[env:ThinkNode_M1_repeater] +extends = ThinkNode_M1 +build_src_filter = ${ThinkNode_M1.build_src_filter} +<../examples/simple_repeater/main.cpp> +build_flags = + ${ThinkNode_M1.build_flags} + -D ADVERT_NAME='"ThinkNode Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D DISPLAY_CLASS=GxEPDDisplay + -D DISPLAY_ROTATION=1 + -D HAS_GxEPD +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + +[env:ThinkNode_M1_room_server] +extends = ThinkNode_M1 +build_src_filter = ${ThinkNode_M1.build_src_filter} +<../examples/simple_room_server/main.cpp> +build_flags = + ${ThinkNode_M1.build_flags} + -D ADVERT_NAME='"ThinkNode Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D DISPLAY_CLASS=GxEPDDisplay + -D DISPLAY_ROTATION=2 + -D HAS_GxEPD +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + +[env:ThinkNode_M1_companion_radio_ble] +extends = ThinkNode_M1 +build_flags = + ${ThinkNode_M1.build_flags} + -I src/helpers/ui + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D DISPLAY_CLASS=GxEPDDisplay + -D DISPLAY_ROTATION=4 + -D HAS_GxEPD +; -D ENABLE_PRIVATE_KEY_IMPORT=1 +; -D ENABLE_PRIVATE_KEY_EXPORT=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M1.build_src_filter} + + + + + + + +<../examples/companion_radio> +lib_deps = + ${ThinkNode_M1.lib_deps} + densaugeo/base64 @ ~1.4.0 + zinggjm/GxEPD2 @ 1.6.2 diff --git a/variants/thinknode_m1/target.cpp b/variants/thinknode_m1/target.cpp new file mode 100644 index 00000000..8ee9fd96 --- /dev/null +++ b/variants/thinknode_m1/target.cpp @@ -0,0 +1,69 @@ +#include +#include "target.h" +#include + +ThinkNodeM1Board board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +#ifndef LORA_CR + #define LORA_CR 5 +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + +#ifdef SX126X_DIO3_TCXO_VOLTAGE + float tcxo = SX126X_DIO3_TCXO_VOLTAGE; +#else + float tcxo = 1.6f; +#endif + + SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); + SPI.begin(); + 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); + +#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 +} diff --git a/variants/thinknode_m1/target.h b/variants/thinknode_m1/target.h new file mode 100644 index 00000000..042c5fa1 --- /dev/null +++ b/variants/thinknode_m1/target.h @@ -0,0 +1,18 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include + +extern ThinkNodeM1Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; + +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(); diff --git a/variants/thinknode_m1/variant.cpp b/variants/thinknode_m1/variant.cpp new file mode 100644 index 00000000..155aa42d --- /dev/null +++ b/variants/thinknode_m1/variant.cpp @@ -0,0 +1,34 @@ +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const int MISO = PIN_SPI1_MISO; +const int MOSI = PIN_SPI1_MOSI; +const int SCK = PIN_SPI1_SCK; + +const uint32_t g_ADigitalPinMap[] = { + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 +}; + +void initVariant() { + pinMode(PIN_PWR_EN, OUTPUT); + digitalWrite(PIN_PWR_EN, HIGH); + + pinMode(PIN_BUTTON1, INPUT_PULLUP); + pinMode(PIN_BUTTON2, INPUT_PULLUP); + + pinMode(LED_RED, OUTPUT); + pinMode(LED_GREEN, OUTPUT); + pinMode(LED_BLUE, OUTPUT); + digitalWrite(LED_BLUE, HIGH); + + pinMode(PIN_TXCO, OUTPUT); + digitalWrite(PIN_TXCO, HIGH); + + // shutdown gps + pinMode(PIN_GPS_STANDBY, OUTPUT); + digitalWrite(PIN_GPS_STANDBY, LOW); +} diff --git a/variants/thinknode_m1/variant.h b/variants/thinknode_m1/variant.h new file mode 100644 index 00000000..3f35ace0 --- /dev/null +++ b/variants/thinknode_m1/variant.h @@ -0,0 +1,133 @@ +/* + * variant.h + * Copyright (C) 2023 Seeed K.K. + * MIT License + */ + +#pragma once + +#include "WVariant.h" + +//////////////////////////////////////////////////////////////////////////////// +// Low frequency clock source + +#define USE_LFXO // 32.768 kHz crystal oscillator +#define VARIANT_MCK (64000000ul) + +#define WIRE_INTERFACES_COUNT (1) +#define PIN_TXCO (21) +//////////////////////////////////////////////////////////////////////////////// +// Power + +#define PIN_PWR_EN (12) + +#define BATTERY_PIN (4) +#define ADC_MULTIPLIER (4.90F) + +#define ADC_RESOLUTION (14) +#define BATTERY_SENSE_RES (12) + +#define AREF_VOLTAGE (3.0) + +//////////////////////////////////////////////////////////////////////////////// +// Number of pins + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +//////////////////////////////////////////////////////////////////////////////// +// UART pin definition + +#define PIN_SERIAL1_RX (41) // GPS TX +#define PIN_SERIAL1_TX (40) // GPS RX +//////////////////////////////////////////////////////////////////////////////// +// I2C pin definition + +#define PIN_WIRE_SDA (26) // P0.26 +#define PIN_WIRE_SCL (27) // P0.27 + +//////////////////////////////////////////////////////////////////////////////// +// SPI pin definition + +#define SPI_INTERFACES_COUNT (2) + +#define PIN_SPI_MISO (23) +#define PIN_SPI_MOSI (22) +#define PIN_SPI_SCK (19) +#define PIN_SPI_NSS (24) + +//////////////////////////////////////////////////////////////////////////////// +// Builtin LEDs + +#define LED_RED (38) +#define LED_GREEN (36) +#define LED_BLUE (14) + +#define PIN_STATUS_LED LED_GREEN +#define LED_BUILTIN LED_GREEN +#define PIN_LED LED_BUILTIN +#define LED_PIN LED_BUILTIN +#define LED_STATE_ON LOW + +#define PIN_NEOPIXEL (14) +#define NEOPIXEL_NUM (2) + +//////////////////////////////////////////////////////////////////////////////// +// Builtin buttons + +#define PIN_BUTTON1 (42) +#define BUTTON_PIN PIN_BUTTON1 +#define PIN_USER_BTN BUTTON_PIN + +#define PIN_BUTTON2 (11) +#define BUTTON_PIN2 PIN_BUTTON2 + +#define EXTERNAL_FLASH_DEVICES MX25R1635F +#define EXTERNAL_FLASH_USE_QSPI + +//////////////////////////////////////////////////////////////////////////////// +// Lora + +#define USE_SX1262 +#define LORA_CS (24) +#define SX126X_DIO1 (20) +#define SX126X_BUSY (17) +#define SX126X_RESET (25) +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +//////////////////////////////////////////////////////////////////////////////// +// SPI1 + +#define PIN_SPI1_MISO (38) +#define PIN_SPI1_MOSI (29) +#define PIN_SPI1_SCK (31) + +// GxEPD2 needs that for a panel that is not even used ! +extern const int MISO; +extern const int MOSI; +extern const int SCK; + +//////////////////////////////////////////////////////////////////////////////// +// Display + +#define DISP_MISO (38) +#define DISP_MOSI (29) +#define DISP_SCLK (31) +#define DISP_CS (30) +#define DISP_DC (28) +#define DISP_RST (2) +#define DISP_BUSY (3) +#define DISP_BACKLIGHT (43) + +//////////////////////////////////////////////////////////////////////////////// +// GPS + +#define PIN_GPS_RX (41) +#define PIN_GPS_TX (40) +#define PIN_GPS_WAKEUP (34) +#define PIN_GPS_RESET (37) +#define PIN_GPS_PPS (36) +#define PIN_GPS_STANDBY (34) From e6325db72bb2b3f78d0bb7b6e1ad406595fba99f Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 30 Apr 2025 18:01:30 +1000 Subject: [PATCH 023/154] * repeater: new CLI command 'neighbors' --- examples/simple_repeater/main.cpp | 77 ++++++++++++++++++++++++++++++- variants/xiao_c3/platformio.ini | 1 + 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 1e012b58..fca40b20 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -105,6 +105,13 @@ struct ClientInfo { #define MAX_CLIENTS 4 +struct NeighbourInfo { + mesh::Identity id; + uint32_t advert_timestamp; + uint32_t heard_timestamp; + int8_t snr; // multiplied by 4, user should divide to get float value +}; + // NOTE: need to space the ACK and the reply text apart (in CLI) #define CLI_REPLY_DELAY_MILLIS 1500 @@ -116,6 +123,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { CommonCLI _cli; uint8_t reply_data[MAX_PACKET_PAYLOAD]; ClientInfo known_clients[MAX_CLIENTS]; +#if MAX_NEIGHBOURS + NeighbourInfo neighbours[MAX_NEIGHBOURS]; +#endif ClientInfo* putClient(const mesh::Identity& id) { uint32_t min_time = 0xFFFFFFFF; @@ -135,6 +145,33 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { return oldest; } + void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr) { + #if MAX_NEIGHBOURS // check if neighbours enabled + // find existing neighbour, else use least recently updated + uint32_t oldest_timestamp = 0xFFFFFFFF; + NeighbourInfo* neighbour = &neighbours[0]; + for (int i = 0; i < MAX_NEIGHBOURS; i++) { + // if neighbour already known, we should update it + if (id.matches(neighbours[i].id)) { + neighbour = &neighbours[i]; + break; + } + + // otherwise we should update the least recently updated neighbour + if (neighbours[i].heard_timestamp < oldest_timestamp) { + neighbour = &neighbours[i]; + oldest_timestamp = neighbour->heard_timestamp; + } + } + + // update neighbour info + neighbour->id = id; + neighbour->advert_timestamp = timestamp; + neighbour->heard_timestamp = getRTCClock()->getCurrentTime(); + neighbour->snr = (int8_t) (snr * 4); + #endif + } + int handleRequest(ClientInfo* sender, uint8_t* payload, size_t payload_len) { uint32_t now = getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp @@ -361,6 +398,15 @@ protected: } } + void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) { + mesh::Mesh::onAdvertRecv(packet, id, timestamp, app_data, app_data_len); // chain to super impl + + // if this a zero hop advert, add it to neighbours + if (packet->path_len == 0) { + putNeighbour(id, timestamp, packet->getSNR()); + } + } + void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override { int i = matching_peer_indexes[sender_idx]; if (i < 0 || i >= MAX_CLIENTS) { // get from our known_clients table (sender SHOULD already be known in this context) @@ -427,12 +473,35 @@ protected: } uint8_t temp[166]; + const char *command = (const char *) &data[5]; + char *reply = (char *) &temp[5]; if (is_retry) { temp[0] = 0; + #if MAX_NEIGHBOURS + } else if (memcmp(command, "neighbors", 9) == 0) { + char *dp = reply; + + for (int i = 0; i < MAX_NEIGHBOURS && dp - reply < 130; i++) { + NeighbourInfo* neighbour = &neighbours[i]; + if (neighbour->heard_timestamp == 0) continue; // skip empty slots + + // add new line if not first item + if (i > 0) *dp++ = '\n'; + + char hex[10]; + // get 4 bytes of neighbour id as hex + mesh::Utils::toHex(hex, neighbour->id.pub_key, 4); + + // add next neighbour + sprintf(dp, "%s:%d:%d", hex, neighbour->advert_timestamp, neighbour->snr); + while (*dp) dp++; // find end of string + } + *dp = 0; // null terminator + #endif } else { - _cli.handleCommand(sender_timestamp, (const char *) &data[5], (char *) &temp[5]); + _cli.handleCommand(sender_timestamp, command, reply); } - int text_len = strlen((char *) &temp[5]); + int text_len = strlen(reply); if (text_len > 0) { uint32_t timestamp = getRTCClock()->getCurrentTimeUnique(); if (timestamp == sender_timestamp) { @@ -482,6 +551,10 @@ public: next_local_advert = next_flood_advert = 0; _logging = false; + #if MAX_NEIGHBOURS + memset(neighbours, 0, sizeof(neighbours)); + #endif + // defaults memset(&_prefs, 0, sizeof(_prefs)); _prefs.airtime_factor = 1.0; // one half diff --git a/variants/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index e790e74e..7e89b377 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -33,6 +33,7 @@ build_flags = -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 lib_deps = From f26159960846f509c08e494ac66a4b827b422030 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 30 Apr 2025 18:10:58 +1000 Subject: [PATCH 024/154] * bug fix for CLI retry attempts (should be ignored) --- examples/simple_repeater/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index fca40b20..332ba3a7 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -476,7 +476,7 @@ protected: const char *command = (const char *) &data[5]; char *reply = (char *) &temp[5]; if (is_retry) { - temp[0] = 0; + *reply = 0; #if MAX_NEIGHBOURS } else if (memcmp(command, "neighbors", 9) == 0) { char *dp = reply; From 056bcf83d9b486b7cffb4f4afa32cf2b680cb8b0 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 30 Apr 2025 18:43:48 +1000 Subject: [PATCH 025/154] * Repeater: neighbour table now only of other repeaters --- examples/simple_repeater/main.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 332ba3a7..201d4fc9 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -403,7 +403,10 @@ protected: // if this a zero hop advert, add it to neighbours if (packet->path_len == 0) { - putNeighbour(id, timestamp, packet->getSNR()); + AdvertDataParser parser(app_data, app_data_len); + if (parser.isValid() && parser.getType() == ADV_TYPE_REPEATER) { // just keep neigbouring Repeaters + putNeighbour(id, timestamp, packet->getSNR()); + } } } @@ -481,7 +484,7 @@ protected: } else if (memcmp(command, "neighbors", 9) == 0) { char *dp = reply; - for (int i = 0; i < MAX_NEIGHBOURS && dp - reply < 130; i++) { + for (int i = 0; i < MAX_NEIGHBOURS && dp - reply < 136; i++) { NeighbourInfo* neighbour = &neighbours[i]; if (neighbour->heard_timestamp == 0) continue; // skip empty slots From 1c67d1cb42165e57a287d9d19f331d54dd1cacea Mon Sep 17 00:00:00 2001 From: Florent de Lamotte Date: Wed, 30 Apr 2025 11:09:43 +0200 Subject: [PATCH 026/154] change screen rotation and fix bitmap --- src/helpers/ui/ST7789Display.cpp | 26 +++++++++----------------- src/helpers/ui/ST7789Spi.h | 31 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/helpers/ui/ST7789Display.cpp b/src/helpers/ui/ST7789Display.cpp index 71f88ff1..fcb36c3b 100644 --- a/src/helpers/ui/ST7789Display.cpp +++ b/src/helpers/ui/ST7789Display.cpp @@ -2,6 +2,10 @@ #include "ST7789Display.h" +#ifndef X_OFFSET +#define X_OFFSET 16 +#endif + bool ST7789Display::begin() { if(!_isOn) { pinMode(PIN_TFT_VDD_CTL, OUTPUT); @@ -11,6 +15,7 @@ bool ST7789Display::begin() { digitalWrite(PIN_TFT_RST, HIGH); display.init(); + display.landscapeScreen(); display.displayOn(); _isOn = true; @@ -26,34 +31,23 @@ void ST7789Display::turnOff() { digitalWrite(PIN_TFT_VDD_CTL, HIGH); digitalWrite(PIN_TFT_LEDA_CTL, HIGH); digitalWrite(PIN_TFT_RST, LOW); - // digitalWrite(PIN_TFT_VDD_CTL, LOW); - // digitalWrite(PIN_TFT_LEDA_CTL, LOW); _isOn = false; } void ST7789Display::clear() { - // display.fillScreen(ST77XX_BLACK); display.clear(); } void ST7789Display::startFrame(Color bkg) { display.clear(); - // display.fillScreen(0x00); - // display.setTextColor(ST77XX_WHITE); - // display.setTextSize(2); - // display.cp437(true); // Use full 256 char 'Code Page 437' font } void ST7789Display::setTextSize(int sz) { -// display.setTextSize(sz); switch(sz) { case 1 : display.setFont(ArialMT_Plain_10); break; case 2 : - display.setFont(ArialMT_Plain_16); - break; - case 3 : display.setFont(ArialMT_Plain_24); break; default: @@ -89,12 +83,10 @@ void ST7789Display::setColor(Color c) { break; } display.setRGB(_color); - //display.setColor((OLEDDISPLAY_COLOR) 4); } void ST7789Display::setCursor(int x, int y) { - //display.setCursor(x, y); - _x = x; + _x = x + X_OFFSET; _y = y; } @@ -103,15 +95,15 @@ void ST7789Display::print(const char* str) { } void ST7789Display::fillRect(int x, int y, int w, int h) { - display.fillRect(x, y, w, h); + display.fillRect(x + X_OFFSET, y, w, h); } void ST7789Display::drawRect(int x, int y, int w, int h) { - display.drawRect(x, y, w, h); + display.drawRect(x + X_OFFSET, y, w, h); } void ST7789Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { - display.drawXbm(x, y, w, h, bits); + display.drawBitmap(x+X_OFFSET, y, w, h, bits); } void ST7789Display::endFrame() { diff --git a/src/helpers/ui/ST7789Spi.h b/src/helpers/ui/ST7789Spi.h index a43fa1f5..ec32f3b0 100644 --- a/src/helpers/ui/ST7789Spi.h +++ b/src/helpers/ui/ST7789Spi.h @@ -262,6 +262,17 @@ class ST7789Spi : public OLEDDisplay { delay(10); } + virtual void landscapeScreen() { + + + uint8_t madctl = ST77XX_MADCTL_RGB; + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + + } + + void setRGB(uint16_t c) { @@ -276,6 +287,26 @@ class ST7789Spi : public OLEDDisplay { //sendCommand(DISPLAYOFF); } + void drawBitmap(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *xbm) { + int16_t widthInXbm = (width + 7) / 8; + uint8_t data = 0; + + for(int16_t y = 0; y < height; y++) { + for(int16_t x = 0; x < width; x++ ) { + if (x & 7) { + data <<= 1; // Move a bit + } else { // Read new data every 8 bit + data = pgm_read_byte(xbm + (x / 8) + y * widthInXbm); + } + // if there is a bit draw it + if (data & 0x80) { + setPixel(xMove + x, yMove + y); + } + } + } + } + + //#define ST77XX_MADCTL_MY 0x80 //#define ST77XX_MADCTL_MX 0x40 //#define ST77XX_MADCTL_MV 0x20 From 05254bd67bacba92a7d1d5eeee041b8c7d3415d7 Mon Sep 17 00:00:00 2001 From: Florent de Lamotte Date: Wed, 30 Apr 2025 11:26:04 +0200 Subject: [PATCH 027/154] t114 display : some fixes --- examples/companion_radio/UITask.cpp | 5 +++++ src/helpers/ui/ST7789Display.cpp | 3 ++- variants/t114/variant.cpp | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index 479c684f..04209f2a 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -106,6 +106,7 @@ void UITask::renderCurrScreen() { _display->setColor(DisplayDriver::ORANGE); sprintf(tmp, "%d", _msgcount); _display->print(tmp); + _display->setColor(DisplayDriver::YELLOW); // last color will be kept on T114 } else { // render 'home' screen _display->setColor(DisplayDriver::BLUE); @@ -120,6 +121,7 @@ void UITask::renderCurrScreen() { _display->print(_version_info); if (_connected) { + _display->setColor(DisplayDriver::BLUE); //_display->printf("freq : %03.2f sf %d\n", _prefs.freq, _prefs.sf); //_display->printf("bw : %03.2f cr %d\n", _prefs.bw, _prefs.cr); } else if (_pin_code != 0) { @@ -128,6 +130,9 @@ void UITask::renderCurrScreen() { _display->setCursor(0, 43); sprintf(tmp, "Pin:%d", _pin_code); _display->print(tmp); + _display->setColor(DisplayDriver::GREEN); + } else { + _display->setColor(DisplayDriver::LIGHT); } } _need_refresh = false; diff --git a/src/helpers/ui/ST7789Display.cpp b/src/helpers/ui/ST7789Display.cpp index fcb36c3b..9b7184ac 100644 --- a/src/helpers/ui/ST7789Display.cpp +++ b/src/helpers/ui/ST7789Display.cpp @@ -17,7 +17,8 @@ bool ST7789Display::begin() { display.init(); display.landscapeScreen(); display.displayOn(); - + setCursor(0,0); + _isOn = true; } return true; diff --git a/variants/t114/variant.cpp b/variants/t114/variant.cpp index 4d07d1ae..2bca56a1 100644 --- a/variants/t114/variant.cpp +++ b/variants/t114/variant.cpp @@ -11,4 +11,5 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { + pinMode(PIN_USER_BTN, INPUT); } From 8a8e89f2820046c0c276dce93174544899639edf Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 30 Apr 2025 21:41:09 +1000 Subject: [PATCH 028/154] * refactor: "neighbors" command --- examples/simple_repeater/main.cpp | 44 +++++++++++++++------------- examples/simple_room_server/main.cpp | 4 +++ src/helpers/CommonCLI.cpp | 2 ++ src/helpers/CommonCLI.h | 1 + 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 201d4fc9..1e5a092e 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -480,27 +480,6 @@ protected: char *reply = (char *) &temp[5]; if (is_retry) { *reply = 0; - #if MAX_NEIGHBOURS - } else if (memcmp(command, "neighbors", 9) == 0) { - char *dp = reply; - - for (int i = 0; i < MAX_NEIGHBOURS && dp - reply < 136; i++) { - NeighbourInfo* neighbour = &neighbours[i]; - if (neighbour->heard_timestamp == 0) continue; // skip empty slots - - // add new line if not first item - if (i > 0) *dp++ = '\n'; - - char hex[10]; - // get 4 bytes of neighbour id as hex - mesh::Utils::toHex(hex, neighbour->id.pub_key, 4); - - // add next neighbour - sprintf(dp, "%s:%d:%d", hex, neighbour->advert_timestamp, neighbour->snr); - while (*dp) dp++; // find end of string - } - *dp = 0; // null terminator - #endif } else { _cli.handleCommand(sender_timestamp, command, reply); } @@ -664,6 +643,29 @@ public: radio_set_tx_power(power_dbm); } + void formatNeighborsReply(char *reply) override { + char *dp = reply; + +#if MAX_NEIGHBOURS + for (int i = 0; i < MAX_NEIGHBOURS && dp - reply < 134; i++) { + NeighbourInfo* neighbour = &neighbours[i]; + if (neighbour->heard_timestamp == 0) continue; // skip empty slots + + // add new line if not first item + if (i > 0) *dp++ = '\n'; + + char hex[10]; + // get 4 bytes of neighbour id as hex + mesh::Utils::toHex(hex, neighbour->id.pub_key, 4); + + // add next neighbour + sprintf(dp, "%s:%d:%d", hex, neighbour->advert_timestamp, neighbour->snr); + while (*dp) dp++; // find end of string + } +#endif + *dp = 0; // null terminator + } + void loop() { mesh::Mesh::loop(); diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index a607f202..d80b0d4b 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -789,6 +789,10 @@ public: radio_set_tx_power(power_dbm); } + void formatNeighborsReply(char *reply) override { + strcpy(reply, "not supported"); + } + void loop() { mesh::Mesh::loop(); diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index f3077afb..acf12574 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -161,6 +161,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else { strcpy(reply, "(ERR: clock cannot go backwards)"); } + } else if (memcmp(command, "neighbors", 9) == 0) { + _callbacks->formatNeighborsReply(reply); } else if (memcmp(command, "password ", 9) == 0) { // change admin password StrHelper::strncpy(_prefs->password, &command[9], sizeof(_prefs->password)); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 50e5f8d6..27fd1c08 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -40,6 +40,7 @@ public: virtual void eraseLogFile() = 0; virtual void dumpLogFile() = 0; virtual void setTxPower(uint8_t power_dbm) = 0; + virtual void formatNeighborsReply(char *reply) = 0; }; class CommonCLI { From 2818749a09cc4e39cba665f181b5fd4779cffd76 Mon Sep 17 00:00:00 2001 From: JQ Date: Thu, 1 May 2025 18:29:25 -0700 Subject: [PATCH 029/154] revert file --- docs/faq.md | 346 +++++++++------------------------------------------- 1 file changed, 58 insertions(+), 288 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 411de350..632f369c 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,75 +1,13 @@ -**MeshCore-FAQ** +# MeshCore-FAQ A list of frequently-asked questions and answers for MeshCore The current version of this MeshCore FAQ is at https://github.com/ripplebiz/MeshCore/blob/main/docs/faq.md. This MeshCore FAQ is also mirrored at https://github.com/LitBomb/MeshCore-FAQ and might have newer updates if pull requests on Scott's MeshCore repo are not approved yet. -author: https://github.com/LitBomb +author: https://github.com/LitBomb --- -- [1. Introduction](#1-introduction) - - [1.1. Q: What is MeshCore?](#11-q-what-is-meshcore) - - [1.2. Q: What do you need to start using MeshCore?](#12-q-what-do-you-need-to-start-using-meshcore) - - [1.2.1. Hardware](#121-hardware) - - [1.2.2. Firmware](#122-firmware) - - [1.2.3. Companion Radio Firmware](#123-companion-radio-firmware) - - [1.2.4. Repeater](#124-repeater) - - [1.2.5. Room Server](#125-room-server) -- [2. Initial Setup](#2-initial-setup) - - [2.1. Q: How many devices do I need to start using meshcore?](#21-q-how-many-devices-do-i-need-to-start-using-meshcore) - - [2.2. Q: Does MeshCore cost any money?](#22-q-does-meshcore-cost-any-money) - - [2.3. Q: What frequencies are supported by MeshCore?](#23-q-what-frequencies-are-supported-by-meshcore) - - [2.4. Q: What is an "advert" in MeshCore?](#24-q-what-is-an-advert-in-meshcore) - - [2.5. Q: Is there a hop limit?](#25-q-is-there-a-hop-limit) -- [3. Server Administration](#3-server-administration) - - [3.1. Q: How do you configure a repeater or a room server?](#31-q-how-do-you-configure-a-repeater-or-a-room-server) - - [3.2. Q: Do I need to set the location for a repeater?](#32-q-do-i-need-to-set-the-location-for-a-repeater) - - [3.3. Q: What is the password to administer a repeater or a room server?](#33-q-what-is-the-password-to-administer-a-repeater-or-a-room-server) - - [3.4. Q: What is the password to join a room server?](#34-q-what-is-the-password-to-join-a-room-server) -- [4. T-Deck Related](#4-t-deck-related) - - [4.1. Q: What are the steps to get a T-Deck into DFU (Device Firmware Update) mode?](#41-q-what-are-the-steps-to-get-a-t-deck-into-dfu-device-firmware-update-mode) - - [4.2. Q: Why is my T-Deck Plus not getting any satellite lock?](#42-q-why-is-my-t-deck-plus-not-getting-any-satellite-lock) - - [4.3. Q: Why is my OG (non-Plus) T-Deck not getting any satellite lock?](#43-q-why-is-my-og-non-plus-t-deck-not-getting-any-satellite-lock) - - [4.4. Q: What size of SD card does the T-Deck support?](#44-q-what-size-of-sd-card-does-the-t-deck-support) - - [4.5. Q: How do I get maps on T-Deck?](#45-q-how-do-i-get-maps-on-t-deck) - - [4.6. Q: Where do the map tiles go?](#46-q-where-do-the-map-tiles-go) - - [4.7. Q: How to unlock deeper map zoom and server management features on T-Deck?](#47-q-how-to-unlock-deeper-map-zoom-and-server-management-features-on-t-deck) - - [4.8. Q: How to decipher the diagnostics screen on T-Deck?](#48-q-how-to-decipher-the-diagnostics-screen-on-t-deck) - - [4.9. Q: The T-Deck sound is too loud?](#49-q-the-t-deck-sound-is-too-loud) - - [4.10. Q: Can you customize the sound?](#410-q-can-you-customize-the-sound) - - [4.11. Q: What is the 'Import from Clipboard' feature on the t-deck and is there a way to manually add nodes without having to receive adverts?](#411-q-what-is-the-import-from-clipboard-feature-on-the-t-deck-and-is-there-a-way-to-manually-add-nodes-without-having-to-receive-adverts) -- [5. General](#5-general) - - [5.1. Q: What are BW, SF, and CR?](#51-q-what-are-bw-sf-and-cr) - - [5.2. Q: Do MeshCore clients repeat?](#52-q-do-meshcore-clients-repeat) - - [5.3. Q: What happens when a node learns a route via a mobile repeater, and that repeater is gone?](#53-q-what-happens-when-a-node-learns-a-route-via-a-mobile-repeater-and-that-repeater-is-gone) - - [5.4. Q: How does a node discovery a path to its destination and then use it to send messages in the future, instead of flooding every message it sends like Meshtastic?](#54-q-how-does-a-node-discovery-a-path-to-its-destination-and-then-use-it-to-send-messages-in-the-future-instead-of-flooding-every-message-it-sends-like-meshtastic) - - [5.5. Q: Do public channels always flood? Do private channels always flood?](#55-q-do-public-channels-always-flood-do-private-channels-always-flood) - - [5.6. Q: what is the public key for the default public channel?](#56-q-what-is-the-public-key-for-the-default-public-channel) - - [5.7. Q: Is MeshCore open source?](#57-q-is-meshcore-open-source) - - [5.8. Q: How can I support MeshCore?](#58-q-how-can-i-support-meshcore) - - [5.9. Q: How do I build MeshCore firmware from source?](#59-q-how-do-i-build-meshcore-firmware-from-source) - - [5.10. Q: Are there other MeshCore related open source projects?](#510-q-are-there-other-meshcore-related-open-source-projects) - - [5.11. Q: Does MeshCore support ATAK](#511-q-does-meshcore-support-atak) - - [5.12. Q: How do I add a node to the MeshCore Map](#512-q-how-do-i-add-a-node-to-the-meshcore-map) - - [5.13. Q: Can I use a Raspberry Pi to update a MeshCore radio?](#513-q-can-i-use-a-raspberry-pi-to-update-a-meshcore-radio) - - [5.14. Q: Are there are projects built around MeshCore?](#514-q-are-there-are-projects-built-around-meshcore) - - [5.14.1. meshcoremqtt](#5141-meshcoremqtt) - - [5.14.2. MeshCore for Home Assistant](#5142-meshcore-for-home-assistant) - - [5.14.3. Python MeshCore](#5143-python-meshcore) - - [5.14.4. meshcore-cli](#5144-meshcore-cli) - - [5.14.5. meshcore.js](#5145-meshcorejs) -- [6. Troubleshooting](#6-troubleshooting) - - [6.1. Q: My client says another client or a repeater or a room server was last seen many, many days ago.](#61-q-my-client-says-another-client-or-a-repeater-or-a-room-server-was-last-seen-many-many-days-ago) - - [6.2. Q: A repeater or a client or a room server I expect to see on my discover list (on T-Deck) or contact list (on a smart device client) are not listed.](#62-q-a-repeater-or-a-client-or-a-room-server-i-expect-to-see-on-my-discover-list-on-t-deck-or-contact-list-on-a-smart-device-client-are-not-listed) - - [6.3. Q: How to connect to a repeater via BLE (bluetooth)?](#63-q-how-to-connect-to-a-repeater-via-ble-bluetooth) - - [6.4. Q: I can't connect via bluetooth, what is the bluetooth pairing code?](#64-q-i-cant-connect-via-bluetooth-what-is-the-bluetooth-pairing-code) - - [6.5. Q: My Heltec V3 keeps disconnecting from my smartphone. It can't hold a solid Bluetooth connection.](#65-q-my-heltec-v3-keeps-disconnecting-from-my-smartphone--it-cant-hold-a-solid-bluetooth-connection) -- [7. Other Questions:](#7-other-questions) - - [7.1. Q: How to Update repeater and room server firmware over the air?](#71-q-how-to--update-repeater-and-room-server-firmware-over-the-air) - -## 1. Introduction - -### 1.1. Q: What is MeshCore? +## Q: What is MeshCore? **A:** MeshCore is free and open source * MeshCore is the routing and firmware etc, available on GitHub under MIT license @@ -84,7 +22,7 @@ These features are completely optional and aren't needed for the core messaging Anyone is able to build anything they like on top of MeshCore without paying anything. -### 1.2. Q: What do you need to start using MeshCore? +## Q: What do you need to start using MeshCore? **A:** Everything you need for MeshCore is available at: Main web site: [https://meshcore.co.uk/](https://meshcore.co.uk/) Firmware Flasher: https://flasher.meshcore.co.uk/ @@ -96,15 +34,15 @@ Anyone is able to build anything they like on top of MeshCore without paying any You need LoRa hardware devices to run MeshCore firmware as clients or server (repeater and room server). -#### 1.2.1. Hardware +### Hardware To use MeshCore without using a phone as the client interface, you can run MeshCore on a T-Deck or T-Deck Plus. It is a complete off-grid secure communication solution. MeshCore is also available on a variety of 868MHz and 915MHz LoRa devices. For example, RAK4631 devices (19003, 19007, 19026), Heltec V3, Xiao S3 WIO, Xiao C3, Heltec T114, Station G2, Seeed Studio T1000-E. More devices will be supported later. -#### 1.2.2. Firmware +### Firmware MeshCore has four firmware types that are not available on other LoRa systems. MeshCore has the following: -#### 1.2.3. Companion Radio Firmware +#### Companion Radio Firmware Companion radios are for connecting to the Android app or web app as a messenger client. There are two different companion radio firmware versions: 1. **BLE Companion** @@ -116,12 +54,12 @@ Companion radios are for connecting to the Android app or web app as a messenger -#### 1.2.4. Repeater +#### Repeater Repeaters are used to extend the range of a MeshCore network. Repeater firmware runs on the same devices that run client firmware. A repeater's job is to forward MeshCore packets to the destination device. It does **not** forward or retransmit every packet it receives, unlike other LoRa mesh systems. A repeater can be remotely administered using a T-Deck running the MeshCore firwmware with remote admistration features unlocked, or from a BLE Companion client connected to a smartphone running the MeshCore app. -#### 1.2.5. Room Server +#### Room Server A room server is a simple BBS server for sharing posts. T-Deck devices running MeshCore firmware or a BLE Companion client connected to a smartphone running the MeshCore app can connect to a room server. room servers store message history on them, and push the stored messages to users. Room servers allow roaming users to come back later and retrieve message history. Contrast to channels, messages are either received when it's sent, or not received and missed if the a room user is out of range. You can think of room servers like email servers where you can come back later and get your emails from your mail server @@ -136,9 +74,9 @@ A room server can also take on the repeater role. To enable repeater role on a --- -## 2. Initial Setup +## Initial Setup -### 2.1. Q: How many devices do I need to start using meshcore? +### Q: How many devices do I need to start using meshcore? **A:** If you have one supported device, flash the BLE Companion firmware and use your device as a client. You can connect to the device using the Android client via bluetooth (iOS client will be available later). You can start communiating with other MeshCore users near you. If you have two supported devices, and there are not many MeshCore users near you, flash both of them to BLE Companion firmware so you can use your devices to communiate with your near-by friends and family. @@ -153,7 +91,7 @@ The repeater and room server CLI reference is here: https://github.com/ripplebiz If you have more supported devices, you can use your additional deivces with the room server firmware. -### 2.2. Q: Does MeshCore cost any money? +### Q: Does MeshCore cost any money? **A:** All radio firmware versions (e.g. for Heltec V3, RAK, T-1000E, etc) are free and open source developed by Scott at Ripple Radios. @@ -162,23 +100,19 @@ The native Android and iOS client uses the freemium model and is developed by Li The T-Deck firmware is free to download and most features are available without cost. To support the firmware developer, you can pay for a registration key to unlock your T-Deck for deeper map zoom and remote server administration over RF using the T-Deck. You do not need to pay for the registration to use your T-Deck for direct messaging and connecting to repeaters and room servers. -### 2.3. Q: What frequencies are supported by MeshCore? +### Q: What frequencies are supported by MeshCore? **A:** It supports the 868MHz range in the UK/EU and the 915MHz range in New Zealand, Australia, and the USA. Countries and regions in these two frequency ranges are also supported. The firmware and client allow users to set their preferred frequency. - Australia and New Zealand are on **915.8MHz** - UK and EU are on **869.525MHz** - Canada and USA are on **910.525MHz** - For other regions and countries, please check your local LoRa frequency -In UK and EU, 867.5MHz is not allowed to use 250kHz bandwidth and it only allows 2.5% duty cycle for clients. 869.525Mhz allows an airtime of 10%, 250KHz bandwidth, and a higher EIRP, therefore MeshCore nodes can send more often and with more power. That is why this frequency is chosen for UK and EU. This is also why Meshtastic also uses this frequency. - -[Source]([https://](https://discord.com/channels/826570251612323860/1330643963501351004/1356540643853209641)) - the rest of the radio settings are the same for all frequencies: - Spread Factor (SF): 10 - Coding Rate (CR): 5 - Bandwidth (BW): 250.00 -### 2.4. Q: What is an "advert" in MeshCore? +### Q: What is an "advert" in MeshCore? **A:** Advert means to advertise yourself on the network. In Reticulum terms it would be to announce. In Meshtastic terms it would be the node sending it's node info. @@ -191,7 +125,7 @@ MeshCore clients only advertise themselves when the user initiates it. A repeate `set advert.interval {minutes}` -### 2.5. Q: Is there a hop limit? +### Q: Is there a hop limit? **A:** Internally the firmware has maximum limit of 64 hops. In real world settings it will be difficult to get close to the limit due to the environments and timing as packets travel further and further. We want to hear how far your MeshCore conversations go. @@ -199,43 +133,30 @@ MeshCore clients only advertise themselves when the user initiates it. A repeate --- -## 3. Server Administration +## Server Administration -### 3.1. Q: How do you configure a repeater or a room server? - -**A:** - When MeshCore is flashed onto a LoRa device is for the first time, it is necessary to set the server device's frequency to make it utilize the frequency that is legal in your country or region. - -Repeater or room server can be administered with one of the options below: - -- After a repeater or room server firmware is flashed on to a LoRa device, go to and use the web user interface to connect to the LoRa device via USB serial. From there you can set the name of the server, its frequency and other related settings, location, passwords etc. - -![image](https://github.com/user-attachments/assets/bec28ff3-a7d6-4a1e-8602-cb6b290dd150) - - +### Q: How do you configure a repeater or a room server? +**A:** One of these servers can be administered with one of the options below: - Connect the server device using a USB cable to a computer running Chrome on https://flasher.meshcore.co.uk/, then use the `console` feature to connect to the device + - this is necessary to set the server device's frequency if it doesn't match the frequency for your local region or country +- MeshCore smart device clients have the ability to remotely administer servers. +- A T-Deck running unlocked/registered MeshCore firmware. Remote server administration is enabled through registering your T-Deck with Ripple Radios. It is one of the ways to support MeshCore development. You can register your T-Deck at: + -- Use a MeshCore smartphone clients to remotely administer servers via LoRa. - -- A T-Deck running unlocked/registered MeshCore firmware. Remote server administration is enabled through registering your T-Deck with Ripple Radios. It is one of the ways to support MeshCore development. You can register your T-Deck at: - - - - - -### 3.2. Q: Do I need to set the location for a repeater? +### Q: Do I need to set the location for a repeater? **A:** With location set for a repeater, it can show up on a MeshCore map in the future. Set location with the following commands: `set lat set long ` You can get the latitude and longitude from Google Maps by right-clicking the location you are at on the map. -### 3.3. Q: What is the password to administer a repeater or a room server? +### Q: What is the password to administer a repeater or a room server? **A:** The default admin password to a repeater and room server is `password`. Use the following command to change the admin password: `password {new-password}` -### 3.4. Q: What is the password to join a room server? +### Q: What is the password to join a room server? **A:** The default guest password to a room server is `hello`. Use the following command to change the guest password: `set guest.password {guest-password}` @@ -243,9 +164,9 @@ You can get the latitude and longitude from Google Maps by right-clicking the lo --- -## 4. T-Deck Related +## T-Deck Related -### 4.1. Q: What are the steps to get a T-Deck into DFU (Device Firmware Update) mode? +### Q: What are the steps to get a T-Deck into DFU (Device Firmware Update) mode? **A:** 1. Device off 2. Connect USB cable to device @@ -256,20 +177,16 @@ You can get the latitude and longitude from Google Maps by right-clicking the lo 7. T-Deck in DFU mode now 8. At this point you can begin flashing using -### 4.2. Q: Why is my T-Deck Plus not getting any satellite lock? +### Q: Why is my T-Deck Plus not getting any satellite lock? **A:** For T-Deck Plus, the GPS baud rate should be set to **38400**. Also, a number of T-Deck Plus devices were found to have the GPS module installed upside down, with the GPS antenna facing down instead of up. If your T-Deck Plus still doesn't get any satellite lock after setting the baud rate to 38400, you might need to open up the device to check the GPS orientation. -GPS on T-Deck is always enabled. You can skip the "GPS clock sync" and the T-Deck will continue to try to get a GPS lock. You can go to the `GPS Info` screen, you should see the `Sentences:` coutner increasing if the baud rate is correct. - -[Source]([https://](https://discord.com/channels/826570251612323860/1330643963501351004/1356609240302616689)) - -### 4.3. Q: Why is my OG (non-Plus) T-Deck not getting any satellite lock? +### Q: Why is my OG (non-Plus) T-Deck not getting any satellite lock? **A:** The OG (non-Plus) T-Deck doesn't come with a GPS. If you added a GPS to your OG T-Deck, please refer to the manual of your GPS to see what baud rate it requires. Alternatively, you can try to set the baud rate from 9600, 19200, etc., and up to 115200 to see which one works. -### 4.4. Q: What size of SD card does the T-Deck support? +### Q: What size of SD card does the T-Deck support? **A:** Users have had no issues using 16GB or 32GB SD cards. Format the SD card to **FAT32**. -### 4.5. Q: How do I get maps on T-Deck? +### Q: How do I get maps on T-Deck? **A:** You need map tiles. You can get pre-downloaded map tiles here (a good way to support development): - (Europe) - (US) @@ -283,46 +200,28 @@ There is also a modified script that adds additional error handling and parallel UK map tiles are available separately from Andy Kirby on his discord server: -### 4.6. Q: Where do the map tiles go? +### Q: Where do the map tiles go? Once you have the tiles downloaded, copy the `\tiles` folder to the root of your T-Deck's SD card. -### 4.7. Q: How to unlock deeper map zoom and server management features on T-Deck? +### Q: How to unlock deeper map zoom and server management features on T-Deck? **A:** You can download, install, and use the T-Deck firmware for free, but it has some features (map zoom, server administration) that are enabled if you purchase an unlock code for \$10 per T-Deck device. Unlock page: -### 4.8. Q: How to decipher the diagnostics screen on T-Deck? -**A: ** Space is tight on T-Deck's screen so the information is a bit cryptic. Format is : -`{hops} l:{packet-length}({payload-len}) t:{packet-type} snr:{n} rssi:{n}` - -See here for packet-type: [https://github.com/ripplebiz/MeshCore/blob/main/src/Packet.h#L19](https://github.com/ripplebiz/MeshCore/blob/main/src/Packet.h#L19 "https://github.com/ripplebiz/MeshCore/blob/main/src/Packet.h#L19") - - - #define PAYLOAD_TYPE_REQ 0x00 // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob) - #define PAYLOAD_TYPE_RESPONSE 0x01 // response to REQ or ANON_REQ (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob) - #define PAYLOAD_TYPE_TXT_MSG 0x02 // a plain text message (prefixed with dest/src hashes, MAC) (enc data: timestamp, text) - #define PAYLOAD_TYPE_ACK 0x03 // a simple ack #define PAYLOAD_TYPE_ADVERT 0x04 // a node advertising its Identity - #define PAYLOAD_TYPE_GRP_TXT 0x05 // an (unverified) group text message (prefixed with channel hash, MAC) (enc data: timestamp, "name: msg") - #define PAYLOAD_TYPE_GRP_DATA 0x06 // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: timestamp, blob) - #define PAYLOAD_TYPE_ANON_REQ 0x07 // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...) - #define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra) - -[Source](https://discord.com/channels/1343693475589263471/1343693475589263474/1350611321040932966) - -### 4.9. Q: The T-Deck sound is too loud? -### 4.10. Q: Can you customize the sound? +### Q: The T-Deck sound is too loud? +### Q: Can you customize the sound? **A:** You can customise the sounds on the T-Deck, just by placing `.mp3` files onto the `root` dir of the SD card. `startup.mp3`, `alert.mp3` and `new-advert.mp3` -### 4.11. Q: What is the 'Import from Clipboard' feature on the t-deck and is there a way to manually add nodes without having to receive adverts? +### Q: What is the 'Import from Clipboard' feature on the t-deck and is there a way to manually add nodes without having to receive adverts? **A:** 'Import from Clipboard' is for importing a contact via a file named 'clipboard.txt' on the SD card. The opposite, is in the Identity screen, the 'Card to Clipboard' menu, which writes to 'clipboard.txt' so you can share yourself (call these 'biz cards', that start with "meshcore://...") --- -## 5. General +## General -### 5.1. Q: What are BW, SF, and CR? +### Q: What are BW, SF, and CR? **A:** @@ -338,201 +237,74 @@ Lowering the spreading factor makes it more difficult for the gateway to receive So it's balancing act between speed of the transmission and resistance to noise. things network is mainly focused on LoRaWAN, but the LoRa low-level stuff still checks out for any LoRa project -### 5.2. Q: Do MeshCore clients repeat? -**A:** No, MeshCore clients do not repeat. This is the core of MeshCore's messaging-first design. This is to avoid devices flooding the air ware and create endless collisions so messages sent aren't received. -In MeshCore, only repeaters and room server with '`set repeat on` repeat. - -### 5.3. Q: What happens when a node learns a route via a mobile repeater, and that repeater is gone? +### Q: What happens when a node learns a route via a mobile repeater, and that repeater is gone? **A:** If you used to reach a node through a repeater and the repeater is no longer reachable, the client will send the message using the existing (but now broken) known path, the message will fail after 3 retries, and the app will reset the path and send the message as flood on the last retry by default. This can be turned off in settings. If the destination is reachable directly or through another repeater, the new path will be used going forward. Or you can set the path manually if you know a specific repeater to use to reach that destination. In the case if users are moving around frequently, and the paths are breaking, they just see the phone client retries and revert to flood to attempt to reestablish a path. -### 5.4. Q: How does a node discovery a path to its destination and then use it to send messages in the future, instead of flooding every message it sends like Meshtastic? -Routes are stored in sender's contact list. When you send a message the first time, the message first gets to your destination by flood routing, When your destination node gets the message, it sends back to the sender a delivery report with all repeaters that the original message went through. This delivery report is flood-routed back to you the sender and is a basis for future direct path. when you send the next message, the path will get embedded into the packet and be evaluated by repeaters. if the hop and address of the repeater matches, it will retransmit the message, otherwise it will not retransmit, hence minimizing utilization. - -[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1351279141630119996) - -### 5.5. Q: Do public channels always flood? Do private channels always flood? - -**A:** Yes, group channels are A to B, so there is no defined path. They have to flood. Repeaters can however deny flood traffic up to some hop limit, with the `set flood.max` CLI command. Admistrators of repeaters get to set the rules of their repeaters. - -[Source](https://discord.com/channels/1343693475589263471/1343693475589263474/1350023009527664672) - - -### 5.6. Q: what is the public key for the default public channel? -**A:** The smartphone app key is in hex: -` 8b3387e9c5cdea6ac9e5edbaa115cd72` - -T-Deck uses the same key but in base64 -`izOH6cXN6mrJ5e26oRXNcg==` -The third character is the capital letter 'O', not zero `0` -[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1354194409213792388) - -### 5.7. Q: Is MeshCore open source? +### Q: Is MeshCore open source? **A:** Most of the firmware is freely available. Everything is open source except the T-Deck firmware and Liam's native mobile apps. - Firmware repo: -### 5.8. Q: How can I support MeshCore? +### Q: How can I support MeshCore? **A:** Provide your honest feedback on GitHub and on AndyKirby's Discord server . Spread the word of MeshCore to your friends and communities; help them get started with MeshCore. Support Scott's MeshCore development at . Support Liam Cottle's smartphone client development by unlocking the server administration wait gate with in-app purchase Support Rastislav Vysoky (recrof)'s flasher web site and the map web site development through [PayPal](https://www.paypal.com/donate/?business=DREHF5HM265ES&no_recurring=0&item_name=If+you+enjoy+my+work%2C+you+can+support+me+here%3A¤cy_code=EUR) or [Revolut](https://revolut.me/recrof) -### 5.9. Q: How do I build MeshCore firmware from source? +### Q: How do I build MeshCore firmware from source? **A:** See instructions here: -https://discord.com/channels/826570251612323860/1330643963501351004/1341826372120608769 - -Build instructions for MeshCore: - -For Windows, first install WSL and Python+pip via: https://plainenglish.io/blog/setting-up-python-on-windows-subsystem-for-linux-wsl-26510f1b2d80 - -(Linux, Windows+WSL) In the terminal/shell: -``` -sudo apt update -sudo apt install libpython3-dev -sudo apt install python3-venv -``` -Mac: python3 should be already installed. - -Then it should be the same for all platforms: -``` -python3 -m venv meshcore -cd meshcore && source bin/activate -pip install -U platformio -git clone https://github.com/ripplebiz/MeshCore.git -cd MeshCore -``` -open platformio.ini and in `[arduino_base]` edit the `LORA_FREQ=867.5` -save, then run: -``` -pio run -e RAK_4631_Repeater -``` -then you'll find `firmware.zip` in `.pio/build/RAK_4631_Repeater` + Andy also has a video on how to build using VS Code: *How to build and flash Meshcore repeater firmware | Heltec V3* *(Link referenced in the Discord post)* -### 5.10. Q: Are there other MeshCore related open source projects? +### Q: Are there other MeshCore related open source projects? **A:** [Liam Cottle](https://liamcottle.net)'s MeshCore web client and MeshCore Javascript libary are open source under MIT license. Web client: https://github.com/liamcottle/meshcore-web Javascript: https://github.com/liamcottle/meshcore.js -### 5.11. Q: Does MeshCore support ATAK +### Q: Does MeshCore support ATAK **A:** ATAK is not currently on MeshCore's roadmap. -Meshcore would not be best suited to ATAK because MeshCore: -clients do not repeat and therefore you would need a network of repeaters in place -will not have a stable path where all clients are constantly moving between repeaters - -MeshCore clients would need to reset path constantly and flood traffic across the network which could lead to lots of collisions with something as chatty as ATAK. - -This could change in the future if MeshCore develops a client firmware that repeats. -[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1354780032140054659) - -### 5.12. Q: How do I add a node to the [MeshCore Map]([url](https://meshcore.co.uk/map.html)) +### Q: How do I add a node to the [MeshCore Map]([url](https://meshcore.co.uk/map.html)) **A:** From the smartphone app, connect to a BLE Companion radio - To add the BLE Companion radio your smartphone is connected to to the map, tap the `advert` icon, then tap `Advert (To Clipboard)`. - To add a Repeater or Room Server to the map, tap the 3 dots next to the Repeater or Room Server you want to add to the map, then tap `Share (To Clipboard)`. - Go to the [MeshCore Map web site]([url](https://meshcore.co.uk/map.html)), tap the plus sign on the lower right corner and paste in the meshcore://... blob, then tap `Add Node` - -### 5.13. Q: Can I use a Raspberry Pi to update a MeshCore radio? -** A:** Yes. -You will need to install picocom on the pi. -`sudo apt install picocom` - -Then run the following commands to setup the repeater. -``` -picocom -b 115200 /dev/ttyUSB0 --imap lfcrlf -set name your_repeater_name -time epoch_time -password your_unique_password -set advert.interval 240 -advert -``` -Note: If using a RAK the path will most likely be /dev/ttyACM0 - -Epoch time comes from https://www.epochconverter.com/ - -You can also flash the repeater using esptool. You will need to install esptool with the following command... - -`pip install esptool --break-system-packages` - -Then to flash the firmware to Heltec, obtain the .bin file from https://flasher.meshcore.co.uk/ (download all firmware link) - -For Heltec: -`esptool.py -p /dev/ttyUSB0 --chip esp32-s3 write_flash 0x00000 firmware.bin` - -If flashing a visual studio code build bin file, flash with the following offset: -`esptool.py -p /dev/ttyUSB0 --chip esp32-s3 write_flash 0x10000 firmware.bin` - -For Pi -Download the zip from the online flasher website and use the following command: - -Note: Requires adafruit-nrfutil command which can be installed as follows. -`pip install adafruit-nrfutil --break-system-packages` - -``` -adafruit-nrfutil --verbose dfu serial --package t1000_e_bootloader-0.9.1-5-g488711a_s140_7.3.0.zip -p /dev/ttyACM0 -b 115200 --singlebank --touch 1200 -``` - -[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1342120825251299388) - -### 5.14. Q: Are there are projects built around MeshCore? - -**A:** Yes. See the following: - -#### 5.14.1. meshcoremqtt -A python based script to send meshore debug and packet capture data to MQTT for analysis -https://github.com/Andrew-a-g/meshcoretomqtt - -#### 5.14.2. MeshCore for Home Assistant -A custom Home Assistant integration for MeshCore mesh radio nodes. It allows you to monitor and control MeshCore nodes via USB, BLE, or TCP connections. -https://github.com/awolden/meshcore-ha - -#### 5.14.3. Python MeshCore -Bindings to access your MeshCore companion radio nodes in python. -https://github.com/fdlamotte/meshcore_py - -#### 5.14.4. meshcore-cli -CLI interface to MeshCore companion radio over BLE, TCP, or serial. Uses Pyton MeshCore above. - https://github.com/fdlamotte/meshcore-cli - -#### 5.14.5. meshcore.js -A Javascript library for interacting with a MeshCore device running the companion radio firmware -https://github.com/liamcottle/meshcore.js - + --- -## 6. Troubleshooting +## Troubleshooting -### 6.1. Q: My client says another client or a repeater or a room server was last seen many, many days ago. -### 6.2. Q: A repeater or a client or a room server I expect to see on my discover list (on T-Deck) or contact list (on a smart device client) are not listed. +### Q: My client says another client or a repeater or a room server was last seen many, many days ago. +### Q: A repeater or a client or a room server I expect to see on my discover list (on T-Deck) or contact list (on a smart device client) are not listed. **A:** - If your client is a T-Deck, it may not have its time set (no GPS installed, no GPS lock, or wrong GPS baud rate). - If you are using the Android or iOS client, the other client, repeater, or room server may have the wrong time. You can get the epoch time on and use it to set your T-Deck clock. For a repeater and room server, the admin can use a T-Deck to remotely set their clock (clock sync), or use the `time` command in the USB serial console with the server device connected. -### 6.3. Q: How to connect to a repeater via BLE (bluetooth)? +### Q: How to connect to a repeater via BLE (bluetooth)? **A:** You can't connect to a device running repeater firmware via bluetooth. Devices running the BLE companion firmware you can connect to it via bluetooth using the android app -### 6.4. Q: I can't connect via bluetooth, what is the bluetooth pairing code? +### Q: I can't connect via bluetooth, what is the bluetooth pairing code? **A:** the default bluetooth pairing code is `123456` -### 6.5. Q: My Heltec V3 keeps disconnecting from my smartphone. It can't hold a solid Bluetooth connection. +### Q: My Heltec V3 keeps disconnecting from my smartphone. It can't hold a solid Bluetooth connection. **A:** Heltec V3 has a very small coil antenna on its PCB for WiFi and Bluetooth connectivty. It has a very short range, only a few feet. It is possible to remove the coil antenna and replace it with a 31mm wire. The BT range is much improved with the modification. --- -## 7. Other Questions: -### 7.1. Q: How to Update repeater and room server firmware over the air? +## Other Questions: +### Q: How to Update repeater and room server firmware over the air? **A:** Only nRF-based RAK4631 and Heltec T114 OTA firmware update are verified using nRF smartphone app. Lilygo T-Echo doesn't work currently. You can update repeater and room server firmware with a bluetooth connection between your smartphone and your LoRa radio using the nRF app. @@ -541,8 +313,7 @@ You can update repeater and room server firmware with a bluetooth connection bet 2. On the phone client, log on to the repeater as administrator (default password is `password`) to issue the `start ota`command to the repeater or room server to get the device into OTA DFU mode ![image](https://github.com/user-attachments/assets/889bb81b-7214-4a1c-955a-396b5a05d8ad) - -1. `start ota` can be initiated from USB serial console on the web flasher page or a T-Deck + 1. `start ota` can be initiated from USB serial console on the web flasher page or a T-Deck 4. On the smartphone, download and run the nRF app and scan for Bluetooth devices 5. Connect to the repeater/room server node you want to update 1. nRF app is available on both Android and iOS @@ -551,8 +322,7 @@ You can update repeater and room server firmware with a bluetooth connection bet **iOS continues here:** 5. Once connected successfully, a `DFU` icon ![Pasted image 20250309173039](https://github.com/user-attachments/assets/af7a9f78-8739-4946-b734-02bade9c8e71) - appears in the top right corner of the app - ![Pasted image 20250309171919](https://github.com/user-attachments/assets/08007ec8-4924-49c1-989f-ca2611e78793) + appears in the top right corner of the app![Pasted image 20250309171919](https://github.com/user-attachments/assets/08007ec8-4924-49c1-989f-ca2611e78793) 6. Scroll down to change the `PRN(s)` number: @@ -602,4 +372,4 @@ You can update repeater and room server firmware with a bluetooth connection bet --- - + \ No newline at end of file From c0870960d68391ee89458258f9845eb1c02c69bd Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 2 May 2025 13:24:06 +1000 Subject: [PATCH 030/154] * repeater CLI: 'neighbors' command now responds with "-none-" if no neighbors --- examples/simple_repeater/main.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 5fdb919a..c019f246 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -666,6 +666,9 @@ public: while (*dp) dp++; // find end of string } #endif + if (dp == reply) { // no neighbours, need empty response + strcpy(dp, "-none-"); dp += 6; + } *dp = 0; // null terminator } From e1c3dfca92fa234dd3e24cf168202681592a2a66 Mon Sep 17 00:00:00 2001 From: Florent Date: Fri, 2 May 2025 08:27:26 +0200 Subject: [PATCH 031/154] xiao-nrf : move pindef in pio.ini --- src/helpers/nrf52/XiaoNrf52Board.h | 16 ++++------------ variants/xiao_nrf52/platformio.ini | 14 ++++++++------ variants/xiao_nrf52/variant.h | 10 +++++----- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/helpers/nrf52/XiaoNrf52Board.h b/src/helpers/nrf52/XiaoNrf52Board.h index 386001a2..e6c8e7f7 100644 --- a/src/helpers/nrf52/XiaoNrf52Board.h +++ b/src/helpers/nrf52/XiaoNrf52Board.h @@ -7,25 +7,17 @@ // LoRa radio module pins for Seeed Xiao-nrf52 #ifdef SX1262_XIAO_S3_VARIANT + #undef P_LORA_DIO_1 + #undef P_LORA_BUSY + #undef P_LORA_RESET + #undef P_LORA_NSS #define P_LORA_DIO_1 D0 #define P_LORA_BUSY D1 #define P_LORA_RESET D2 #define P_LORA_NSS D3 - #define LORA_TX_BOOST_PIN D4 -#else - #define P_LORA_DIO_1 D1 - #define P_LORA_BUSY D3 - #define P_LORA_RESET D2 - #define P_LORA_NSS D4 - #define LORA_TX_BOOST_PIN D5 #endif -#define P_LORA_SCLK PIN_SPI_SCK -#define P_LORA_MISO PIN_SPI_MISO -#define P_LORA_MOSI PIN_SPI_MOSI //#define SX126X_POWER_EN 37 -#define SX126X_DIO2_AS_RF_SWITCH true -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 class XiaoNrf52Board : public mesh::MainBoard { diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index 422729ab..6fa29512 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -27,6 +27,12 @@ build_flags = ${nrf52840_xiao.build_flags} -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 + -D P_LORA_DIO_1=D1 + -D P_LORA_RESET=D2 + -D P_LORA_BUSY=D3 + -D P_LORA_NSS=D4 + -D SX126X_DIO2_AS_RF_SWITCH=1 + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=130 -D SX126X_RX_BOOSTED_GAIN=1 build_src_filter = ${nrf52840_xiao.build_src_filter} @@ -45,7 +51,6 @@ build_flags = -D BLE_PIN_CODE=123456 ; -D BLE_DEBUG_LOGGING=1 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 -; -D ENABLE_PRIVATE_KEY_EXPORT=1 -D MESH_PACKET_LOGGING=1 -D MESH_DEBUG=1 build_src_filter = ${Xiao_nrf52.build_src_filter} @@ -61,12 +66,9 @@ build_flags = ${Xiao_nrf52.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -; -D BLE_PIN_CODE=123456 -; -D BLE_DEBUG_LOGGING=1 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 -; -D ENABLE_PRIVATE_KEY_EXPORT=1 - -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 build_src_filter = ${Xiao_nrf52.build_src_filter} + +<../examples/companion_radio/main.cpp> diff --git a/variants/xiao_nrf52/variant.h b/variants/xiao_nrf52/variant.h index 5d68d270..0d0950af 100644 --- a/variants/xiao_nrf52/variant.h +++ b/variants/xiao_nrf52/variant.h @@ -98,15 +98,15 @@ static const uint8_t A5 = PIN_A5; #define PIN_SPI_MOSI (10) #define PIN_SPI_SCK (8) -static const uint8_t SS = D3; // NSS for sx ? -static const uint8_t MOSI = PIN_SPI_MOSI; -static const uint8_t MISO = PIN_SPI_MISO; -static const uint8_t SCK = PIN_SPI_SCK ; - #define PIN_SPI1_MISO (25) #define PIN_SPI1_MOSI (26) #define PIN_SPI1_SCK (29) +// Lora SPI is on SPI0 +#define P_LORA_SCLK PIN_SPI_SCK +#define P_LORA_MISO PIN_SPI_MISO +#define P_LORA_MOSI PIN_SPI_MOSI + // Wire Interfaces #define WIRE_INTERFACES_COUNT (1) From 99774f10ac6d42f041c436c1ad946c8fe598d9de Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 3 May 2025 13:14:03 +1000 Subject: [PATCH 032/154] * new: SensorManager * BasChatMesh: new onContactRequest(), for PAYLOAD_TYPE_REQ handling. * companion, repeater and room_server: now with basic 'plumbing' to handle REQ_TYPE_GET_TELEMETRY_DATA (0x03). * dependency: added CayenneLPP to libdeps * all target.* modules now with a stub 'sensors' object. --- examples/companion_radio/main.cpp | 27 ++++- examples/simple_repeater/main.cpp | 34 ++++-- examples/simple_room_server/main.cpp | 110 +++++++++++------- examples/simple_secure_chat/main.cpp | 4 + platformio.ini | 1 + src/helpers/BaseChatMesh.cpp | 21 ++++ src/helpers/BaseChatMesh.h | 1 + src/helpers/SensorManager.h | 14 +++ variants/generic-e22/target.cpp | 1 + variants/generic-e22/target.h | 2 + variants/generic_espnow/target.cpp | 1 + variants/generic_espnow/target.h | 2 + variants/heltec_v2/target.cpp | 1 + variants/heltec_v2/target.h | 2 + variants/heltec_v3/target.cpp | 1 + variants/heltec_v3/target.h | 2 + variants/lilygo_t3s3/target.cpp | 1 + variants/lilygo_t3s3/target.h | 2 + variants/lilygo_tbeam/target.cpp | 1 + variants/lilygo_tbeam/target.h | 2 + variants/lilygo_tbeam_SX1262/target.cpp | 1 + variants/lilygo_tbeam_SX1262/target.h | 2 + .../lilygo_tbeam_supreme_SX1262/target.cpp | 2 +- variants/lilygo_tbeam_supreme_SX1262/target.h | 2 + variants/lilygo_tlora_v2_1/target.cpp | 1 + variants/lilygo_tlora_v2_1/target.h | 2 + variants/picow/target.cpp | 1 + variants/picow/target.h | 2 + variants/promicro/target.cpp | 1 + variants/promicro/target.h | 2 + variants/rak4631/target.cpp | 1 + variants/rak4631/target.h | 2 + variants/station_g2/target.cpp | 1 + variants/station_g2/target.h | 2 + variants/t1000-e/target.cpp | 17 +++ variants/t1000-e/target.h | 11 ++ variants/t114/target.cpp | 1 + variants/t114/target.h | 2 + variants/techo/target.cpp | 1 + variants/techo/target.h | 2 + variants/xiao_c3/target.cpp | 1 + variants/xiao_c3/target.h | 2 + variants/xiao_nrf52/target.cpp | 1 + variants/xiao_nrf52/target.h | 2 + variants/xiao_s3_wio/target.cpp | 1 + variants/xiao_s3_wio/target.h | 2 + 46 files changed, 243 insertions(+), 52 deletions(-) create mode 100644 src/helpers/SensorManager.h diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 3e692b53..a36c754f 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -184,6 +184,10 @@ static uint32_t _atoi(const char* sp) { /* -------------------------------------------------------------------------------------- */ +#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS +#define REQ_TYPE_KEEP_ALIVE 0x02 +#define REQ_TYPE_GET_TELEMETRY_DATA 0x03 + #define MAX_SIGN_DATA_LEN (8*1024) // 8K class MyMesh : public BaseChatMesh { @@ -203,6 +207,7 @@ class MyMesh : public BaseChatMesh { uint32_t sign_data_len; uint8_t cmd_frame[MAX_FRAME_SIZE+1]; uint8_t out_frame[MAX_FRAME_SIZE+1]; + CayenneLPP telemetry; struct Frame { uint8_t len; @@ -644,6 +649,22 @@ protected: #endif } + uint8_t onContactRequest(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t* data, uint8_t len, uint8_t* reply) override { + if (data[0] == REQ_TYPE_GET_TELEMETRY_DATA) { + telemetry.reset(); + telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); + // query other sensors -- target specific + sensors.querySensors(contact.flags, telemetry); + + memcpy(reply, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag') + + uint8_t tlen = telemetry.getSize(); + memcpy(&reply[4], telemetry.getBuffer(), tlen); + return 4 + tlen; + } + return 0; // unknown + } + void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) override { uint32_t sender_timestamp; memcpy(&sender_timestamp, data, 4); @@ -734,7 +755,8 @@ protected: public: MyMesh(mesh::Radio& radio, mesh::RNG& rng, mesh::RTCClock& rtc, SimpleMeshTables& tables) - : BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables), _serial(NULL) + : BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables), _serial(NULL), + telemetry(MAX_PACKET_PAYLOAD - 4) { _iter_started = false; offline_queue_len = 0; @@ -1592,6 +1614,8 @@ void setup() { #error "need to define filesystem" #endif + sensors.begin(); + #ifdef HAS_UI ui_task.begin(disp, the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION, the_mesh.getBLEPin()); #endif @@ -1599,4 +1623,5 @@ void setup() { void loop() { the_mesh.loop(); + sensors.loop(); } diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index c019f246..3926cfec 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -74,7 +74,9 @@ /* ------------------------------ Code -------------------------------- */ -#define CMD_GET_STATUS 0x01 +#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS +#define REQ_TYPE_KEEP_ALIVE 0x02 +#define REQ_TYPE_GET_TELEMETRY_DATA 0x03 #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ @@ -126,6 +128,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #if MAX_NEIGHBOURS NeighbourInfo neighbours[MAX_NEIGHBOURS]; #endif + CayenneLPP telemetry; ClientInfo* putClient(const mesh::Identity& id) { uint32_t min_time = 0xFFFFFFFF; @@ -172,12 +175,13 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #endif } - int handleRequest(ClientInfo* sender, uint8_t* payload, size_t payload_len) { - uint32_t now = getRTCClock()->getCurrentTimeUnique(); - memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp + int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len) { + // uint32_t now = getRTCClock()->getCurrentTimeUnique(); + // 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') switch (payload[0]) { - case CMD_GET_STATUS: { // guests can also access this now + case REQ_TYPE_GET_STATUS: { // guests can also access this now RepeaterStats stats; stats.batt_milli_volts = board.getBattMilliVolts(); stats.curr_tx_queue_len = _mgr->getOutboundCount(); @@ -200,9 +204,18 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { return 4 + sizeof(stats); // reply_len } + case REQ_TYPE_GET_TELEMETRY_DATA: { + telemetry.reset(); + telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); + // query other sensors -- target specific + sensors.querySensors(sender->is_admin ? 0xFF : 0x00, telemetry); + + uint8_t tlen = telemetry.getSize(); + memcpy(&reply_data[4], telemetry.getBuffer(), tlen); + return 4 + tlen; // reply_len + } } - // unknown command - return 0; // reply_len + return 0; // unknown command } mesh::Packet* createSelfAdvert() { @@ -422,7 +435,7 @@ protected: memcpy(×tamp, data, 4); if (timestamp > client->last_timestamp) { // prevent replay attacks - int reply_len = handleRequest(client, &data[4], len - 4); + int reply_len = handleRequest(client, timestamp, &data[4], len - 4); if (reply_len == 0) return; // invalid command client->last_timestamp = timestamp; @@ -527,7 +540,7 @@ protected: public: MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, this, &_prefs, this) + _cli(board, this, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { memset(known_clients, 0, sizeof(known_clients)); next_local_advert = next_flood_advert = 0; @@ -754,6 +767,8 @@ void setup() { command[0] = 0; + sensors.begin(); + the_mesh.begin(fs); #ifdef DISPLAY_CLASS @@ -790,4 +805,5 @@ void loop() { } the_mesh.loop(); + sensors.loop(); } diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index a6eafc5d..eabcf98e 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -121,8 +121,9 @@ struct PostInfo { #define CLIENT_KEEP_ALIVE_SECS 128 -#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS -#define REQ_TYPE_KEEP_ALIVE 0x02 +#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS +#define REQ_TYPE_KEEP_ALIVE 0x02 +#define REQ_TYPE_GET_TELEMETRY_DATA 0x03 #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ @@ -157,6 +158,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { int next_client_idx; // for round-robin polling int next_post_idx; PostInfo posts[MAX_UNSYNCED_POSTS]; // cyclic queue + CayenneLPP telemetry; ClientInfo* putClient(const mesh::Identity& id) { for (int i = 0; i < num_clients; i++) { @@ -279,6 +281,51 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #endif } + int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len) { + // uint32_t now = getRTCClock()->getCurrentTimeUnique(); + // 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') + + switch (payload[0]) { + case REQ_TYPE_GET_STATUS: { + ServerStats stats; + stats.batt_milli_volts = board.getBattMilliVolts(); + stats.curr_tx_queue_len = _mgr->getOutboundCount(); + stats.curr_free_queue_len = _mgr->getFreeCount(); + stats.last_rssi = (int16_t) radio_driver.getLastRSSI(); + stats.n_packets_recv = radio_driver.getPacketsRecv(); + stats.n_packets_sent = radio_driver.getPacketsSent(); + stats.total_air_time_secs = getTotalAirTime() / 1000; + stats.total_up_time_secs = _ms->getMillis() / 1000; + stats.n_sent_flood = getNumSentFlood(); + stats.n_sent_direct = getNumSentDirect(); + stats.n_recv_flood = getNumRecvFlood(); + stats.n_recv_direct = getNumRecvDirect(); + stats.n_full_events = getNumFullEvents(); + stats.last_snr = (int16_t)(radio_driver.getLastSNR() * 4); + stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups(); + stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups(); + stats.n_posted = _num_posted; + stats.n_post_push = _num_post_pushes; + + memcpy(&reply_data[4], &stats, sizeof(stats)); + return 4 + sizeof(stats); + } + + case REQ_TYPE_GET_TELEMETRY_DATA: { + telemetry.reset(); + telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); + // query other sensors -- target specific + sensors.querySensors(sender->permission == RoomPermission::ADMIN ? 0xFF : 0x00, telemetry); + + uint8_t tlen = telemetry.getSize(); + memcpy(&reply_data[4], telemetry.getBuffer(), tlen); + return 4 + tlen; // reply_len + } + } + return 0; // unknown command + } + protected: float getAirtimeBudgetFactor() const override { return _prefs.airtime_factor; @@ -591,44 +638,22 @@ protected: sendDirect(reply, client->out_path, client->out_path_len); } } - } else if (data[4] == REQ_TYPE_GET_STATUS) { - ServerStats stats; - stats.batt_milli_volts = board.getBattMilliVolts(); - stats.curr_tx_queue_len = _mgr->getOutboundCount(); - stats.curr_free_queue_len = _mgr->getFreeCount(); - stats.last_rssi = (int16_t) radio_driver.getLastRSSI(); - stats.n_packets_recv = radio_driver.getPacketsRecv(); - stats.n_packets_sent = radio_driver.getPacketsSent(); - stats.total_air_time_secs = getTotalAirTime() / 1000; - stats.total_up_time_secs = _ms->getMillis() / 1000; - stats.n_sent_flood = getNumSentFlood(); - stats.n_sent_direct = getNumSentDirect(); - stats.n_recv_flood = getNumRecvFlood(); - stats.n_recv_direct = getNumRecvDirect(); - stats.n_full_events = getNumFullEvents(); - stats.last_snr = (int16_t)(radio_driver.getLastSNR() * 4); - stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups(); - stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups(); - stats.n_posted = _num_posted; - stats.n_post_push = _num_post_pushes; - - now = getRTCClock()->getCurrentTimeUnique(); - memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp - memcpy(&reply_data[4], &stats, sizeof(stats)); - uint8_t reply_len = 4 + sizeof(stats); - - if (packet->isRouteFlood()) { - // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response - mesh::Packet* path = createPathReturn(client->id, secret, packet->path, packet->path_len, - PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); - if (path) sendFlood(path); - } else { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); - if (reply) { - if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT - sendDirect(reply, client->out_path, client->out_path_len); - } else { - sendFlood(reply); + } else { + int reply_len = handleRequest(client, sender_timestamp, &data[4], len - 4); + if (reply_len > 0) { // valid command + if (packet->isRouteFlood()) { + // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response + mesh::Packet* path = createPathReturn(client->id, secret, packet->path, packet->path_len, + PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); + if (path) sendFlood(path); + } else { + mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); + if (reply) { + if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT + sendDirect(reply, client->out_path, client->out_path_len); + } else { + sendFlood(reply); + } } } } @@ -667,7 +692,7 @@ protected: public: MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, this, &_prefs, this) + _cli(board, this, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { next_local_advert = next_flood_advert = 0; _logging = false; @@ -918,6 +943,8 @@ void setup() { command[0] = 0; + sensors.begin(); + the_mesh.begin(fs); #ifdef DISPLAY_CLASS @@ -954,4 +981,5 @@ void loop() { } the_mesh.loop(); + sensors.loop(); } diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index 30bb52bd..1d0d847f 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -254,6 +254,10 @@ protected: Serial.printf(" %s\n", text); } + uint8_t onContactRequest(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t* data, uint8_t len, uint8_t* reply) override { + return 0; // unknown + } + void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) override { // not supported } diff --git a/platformio.ini b/platformio.ini index 8c4766a8..42a19256 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,6 +22,7 @@ lib_deps = rweather/Crypto @ ^0.4.0 adafruit/RTClib @ ^2.1.3 melopero/Melopero RV3028 @ ^1.1.0 + electroniccats/CayenneLPP @ 1.4.0 build_flags = -w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1 -D LORA_FREQ=869.525 -D LORA_BW=250 diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 97aefff4..673c0058 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -178,6 +178,27 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender } else { MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported message type: %u", (uint32_t) flags); } + } else if (type == PAYLOAD_TYPE_REQ && len > 4) { + uint32_t sender_timestamp; + memcpy(&sender_timestamp, data, 4); + uint8_t reply_len = onContactRequest(from, sender_timestamp, &data[4], len - 4, temp_buf); + if (reply_len > 0) { + if (packet->isRouteFlood()) { + // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response + mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, + PAYLOAD_TYPE_RESPONSE, temp_buf, reply_len); + if (path) sendFlood(path); + } else { + mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len); + if (reply) { + if (from.out_path_len >= 0) { // we have an out_path, so send DIRECT + sendDirect(reply, from.out_path, from.out_path_len); + } else { + sendFlood(reply); + } + } + } + } } else if (type == PAYLOAD_TYPE_RESPONSE && len > 0) { onContactResponse(from, data, len); } diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index ed6e1c3a..d91a0a86 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -114,6 +114,7 @@ protected: virtual uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const = 0; virtual void onSendTimeout() = 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 void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) = 0; // storage concepts, for sub-classes to override/implement diff --git a/src/helpers/SensorManager.h b/src/helpers/SensorManager.h new file mode 100644 index 00000000..84d6c7b0 --- /dev/null +++ b/src/helpers/SensorManager.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#define TELEM_PERM_LOCATION 0x02 + +#define TELEM_CHANNEL_SELF 1 // LPP data channel for 'self' device + +class SensorManager { +public: + virtual bool begin() { return false; } + virtual bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { return false; } + virtual void loop() { } +}; diff --git a/variants/generic-e22/target.cpp b/variants/generic-e22/target.cpp index f3028faa..d9b70c3a 100644 --- a/variants/generic-e22/target.cpp +++ b/variants/generic-e22/target.cpp @@ -14,6 +14,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 diff --git a/variants/generic-e22/target.h b/variants/generic-e22/target.h index 77ae8664..e5c116e1 100644 --- a/variants/generic-e22/target.h +++ b/variants/generic-e22/target.h @@ -7,10 +7,12 @@ #include #include #include +#include extern ESP32Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/generic_espnow/target.cpp b/variants/generic_espnow/target.cpp index 743a7aeb..6b5d4e44 100644 --- a/variants/generic_espnow/target.cpp +++ b/variants/generic_espnow/target.cpp @@ -7,6 +7,7 @@ ESP32Board board; ESPNOWRadio radio_driver; ESP32RTCClock rtc_clock; +SensorManager sensors; bool radio_init() { rtc_clock.begin(); diff --git a/variants/generic_espnow/target.h b/variants/generic_espnow/target.h index 16eeaa2e..99b6f577 100644 --- a/variants/generic_espnow/target.h +++ b/variants/generic_espnow/target.h @@ -2,10 +2,12 @@ #include #include +#include extern ESP32Board board; extern ESPNOWRadio radio_driver; extern ESP32RTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/heltec_v2/target.cpp b/variants/heltec_v2/target.cpp index 284fa600..efb86c67 100644 --- a/variants/heltec_v2/target.cpp +++ b/variants/heltec_v2/target.cpp @@ -14,6 +14,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 diff --git a/variants/heltec_v2/target.h b/variants/heltec_v2/target.h index bfdb6132..92aec7f2 100644 --- a/variants/heltec_v2/target.h +++ b/variants/heltec_v2/target.h @@ -6,10 +6,12 @@ #include #include #include +#include extern HeltecV2Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/heltec_v3/target.cpp b/variants/heltec_v3/target.cpp index 768cd84c..27e0ebf9 100644 --- a/variants/heltec_v3/target.cpp +++ b/variants/heltec_v3/target.cpp @@ -14,6 +14,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 diff --git a/variants/heltec_v3/target.h b/variants/heltec_v3/target.h index 04fec40c..b6ab2577 100644 --- a/variants/heltec_v3/target.h +++ b/variants/heltec_v3/target.h @@ -6,10 +6,12 @@ #include #include #include +#include extern HeltecV3Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/lilygo_t3s3/target.cpp b/variants/lilygo_t3s3/target.cpp index 8e8b8e75..4073518f 100644 --- a/variants/lilygo_t3s3/target.cpp +++ b/variants/lilygo_t3s3/target.cpp @@ -14,6 +14,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 diff --git a/variants/lilygo_t3s3/target.h b/variants/lilygo_t3s3/target.h index 8eed467f..eef923ab 100644 --- a/variants/lilygo_t3s3/target.h +++ b/variants/lilygo_t3s3/target.h @@ -6,10 +6,12 @@ #include #include #include +#include extern ESP32Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/lilygo_tbeam/target.cpp b/variants/lilygo_tbeam/target.cpp index 24e4a342..101ab09c 100644 --- a/variants/lilygo_tbeam/target.cpp +++ b/variants/lilygo_tbeam/target.cpp @@ -14,6 +14,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 diff --git a/variants/lilygo_tbeam/target.h b/variants/lilygo_tbeam/target.h index 1fd68a84..2c1897ef 100644 --- a/variants/lilygo_tbeam/target.h +++ b/variants/lilygo_tbeam/target.h @@ -6,10 +6,12 @@ #include #include #include +#include extern TBeamBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/lilygo_tbeam_SX1262/target.cpp b/variants/lilygo_tbeam_SX1262/target.cpp index 65f64734..4dcc3cf1 100644 --- a/variants/lilygo_tbeam_SX1262/target.cpp +++ b/variants/lilygo_tbeam_SX1262/target.cpp @@ -14,6 +14,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 diff --git a/variants/lilygo_tbeam_SX1262/target.h b/variants/lilygo_tbeam_SX1262/target.h index 16234e3c..bcc80ef1 100644 --- a/variants/lilygo_tbeam_SX1262/target.h +++ b/variants/lilygo_tbeam_SX1262/target.h @@ -6,10 +6,12 @@ #include #include #include +#include extern TBeamBoardSX1262 board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.cpp b/variants/lilygo_tbeam_supreme_SX1262/target.cpp index c7ab2984..bbdd604e 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.cpp +++ b/variants/lilygo_tbeam_supreme_SX1262/target.cpp @@ -23,7 +23,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); - +SensorManager sensors; static void setPMUIntFlag(){ pmuIntFlag = true; diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.h b/variants/lilygo_tbeam_supreme_SX1262/target.h index 2ee3cb01..f14b2142 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.h +++ b/variants/lilygo_tbeam_supreme_SX1262/target.h @@ -5,10 +5,12 @@ #include #include #include +#include extern TBeamS3SupremeBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/lilygo_tlora_v2_1/target.cpp b/variants/lilygo_tlora_v2_1/target.cpp index 740e0561..76d12382 100644 --- a/variants/lilygo_tlora_v2_1/target.cpp +++ b/variants/lilygo_tlora_v2_1/target.cpp @@ -10,6 +10,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 diff --git a/variants/lilygo_tlora_v2_1/target.h b/variants/lilygo_tlora_v2_1/target.h index 5c0910cb..9e73bc37 100644 --- a/variants/lilygo_tlora_v2_1/target.h +++ b/variants/lilygo_tlora_v2_1/target.h @@ -6,10 +6,12 @@ #include #include #include +#include extern LilyGoTLoraBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/picow/target.cpp b/variants/picow/target.cpp index 81a3665b..22d2d854 100644 --- a/variants/picow/target.cpp +++ b/variants/picow/target.cpp @@ -10,6 +10,7 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 diff --git a/variants/picow/target.h b/variants/picow/target.h index 1881ed2a..a89b5ba8 100644 --- a/variants/picow/target.h +++ b/variants/picow/target.h @@ -6,10 +6,12 @@ #include #include #include +#include extern PicoWBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index a3ea1879..212e3b25 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -10,6 +10,7 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 diff --git a/variants/promicro/target.h b/variants/promicro/target.h index 79d0862f..b2c4f9d2 100644 --- a/variants/promicro/target.h +++ b/variants/promicro/target.h @@ -7,10 +7,12 @@ #include #include #include +#include extern PromicroBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/rak4631/target.cpp b/variants/rak4631/target.cpp index 17e76532..aaa3e8f1 100644 --- a/variants/rak4631/target.cpp +++ b/variants/rak4631/target.cpp @@ -10,6 +10,7 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 diff --git a/variants/rak4631/target.h b/variants/rak4631/target.h index cd32c12d..91f1d719 100644 --- a/variants/rak4631/target.h +++ b/variants/rak4631/target.h @@ -6,10 +6,12 @@ #include #include #include +#include extern RAK4631Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/station_g2/target.cpp b/variants/station_g2/target.cpp index 988ac4fc..e279b139 100644 --- a/variants/station_g2/target.cpp +++ b/variants/station_g2/target.cpp @@ -14,6 +14,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 diff --git a/variants/station_g2/target.h b/variants/station_g2/target.h index 96fd5095..e44c2ebe 100644 --- a/variants/station_g2/target.h +++ b/variants/station_g2/target.h @@ -6,10 +6,12 @@ #include #include #include +#include extern StationG2Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index cf51cc91..45c48fb9 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -8,6 +8,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock rtc_clock; +T1000SensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 @@ -84,3 +85,19 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } + +bool T1000SensorManager::begin() { + // TODO: init GPS + return true; +} + +bool T1000SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { + if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission? + telemetry.addGPS(TELEM_CHANNEL_SELF, _lat, _lon, _alt); + } + return true; +} + +void T1000SensorManager::loop() { + // TODO: poll GPS serial, set _lat, _lon, _alt +} diff --git a/variants/t1000-e/target.h b/variants/t1000-e/target.h index 83bd3eae..a47d05b2 100644 --- a/variants/t1000-e/target.h +++ b/variants/t1000-e/target.h @@ -6,10 +6,21 @@ #include #include #include +#include + +class T1000SensorManager : public SensorManager { + float _lat, _lon, _alt; +public: + T1000SensorManager(): _lat(0), _lon(0), _alt(0) { } + bool begin() override; + bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; + void loop() override; +}; extern T1000eBoard board; extern WRAPPER_CLASS radio_driver; extern VolatileRTCClock rtc_clock; +extern T1000SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/t114/target.cpp b/variants/t114/target.cpp index 07a60688..31215e82 100644 --- a/variants/t114/target.cpp +++ b/variants/t114/target.cpp @@ -10,6 +10,7 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 diff --git a/variants/t114/target.h b/variants/t114/target.h index 8d80c0ec..b1c05b83 100644 --- a/variants/t114/target.h +++ b/variants/t114/target.h @@ -6,10 +6,12 @@ #include #include #include +#include extern T114Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/techo/target.cpp b/variants/techo/target.cpp index ecffc37a..0aabfd47 100644 --- a/variants/techo/target.cpp +++ b/variants/techo/target.cpp @@ -10,6 +10,7 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 diff --git a/variants/techo/target.h b/variants/techo/target.h index 4a66f24e..0ec9ab04 100644 --- a/variants/techo/target.h +++ b/variants/techo/target.h @@ -6,10 +6,12 @@ #include #include #include +#include extern TechoBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/xiao_c3/target.cpp b/variants/xiao_c3/target.cpp index bab602cc..0ed1609d 100644 --- a/variants/xiao_c3/target.cpp +++ b/variants/xiao_c3/target.cpp @@ -14,6 +14,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 diff --git a/variants/xiao_c3/target.h b/variants/xiao_c3/target.h index c0a1eb32..0aea87dd 100644 --- a/variants/xiao_c3/target.h +++ b/variants/xiao_c3/target.h @@ -7,10 +7,12 @@ #include #include #include +#include extern XiaoC3Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/xiao_nrf52/target.cpp b/variants/xiao_nrf52/target.cpp index 0707aa39..1d23e76a 100644 --- a/variants/xiao_nrf52/target.cpp +++ b/variants/xiao_nrf52/target.cpp @@ -9,6 +9,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock rtc_clock; +SensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 diff --git a/variants/xiao_nrf52/target.h b/variants/xiao_nrf52/target.h index 85f1ce1c..868664b5 100644 --- a/variants/xiao_nrf52/target.h +++ b/variants/xiao_nrf52/target.h @@ -6,10 +6,12 @@ #include #include #include +#include extern XiaoNrf52Board board; extern WRAPPER_CLASS radio_driver; extern VolatileRTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/xiao_s3_wio/target.cpp b/variants/xiao_s3_wio/target.cpp index ddb700b7..517b9ef6 100644 --- a/variants/xiao_s3_wio/target.cpp +++ b/variants/xiao_s3_wio/target.cpp @@ -14,6 +14,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 diff --git a/variants/xiao_s3_wio/target.h b/variants/xiao_s3_wio/target.h index 8eed467f..eef923ab 100644 --- a/variants/xiao_s3_wio/target.h +++ b/variants/xiao_s3_wio/target.h @@ -6,10 +6,12 @@ #include #include #include +#include extern ESP32Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); From 26f01e0605a1242d94ef4aeb02ee5d0f41ff5f6c Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 3 May 2025 20:08:44 +1000 Subject: [PATCH 033/154] * companion: new CMD_SEND_TELEMETRY_REQ, PUSH_CODE_TELEMETRY_RESPONSE --- examples/companion_radio/main.cpp | 53 ++++++++++++++++++++++++------- src/helpers/BaseChatMesh.cpp | 8 ++--- src/helpers/BaseChatMesh.h | 2 +- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index a36c754f..1ca5c650 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -139,6 +139,7 @@ static uint32_t _atoi(const char* sp) { #define CMD_SEND_TRACE_PATH 36 #define CMD_SET_DEVICE_PIN 37 #define CMD_SET_OTHER_PARAMS 38 +#define CMD_SEND_TELEMETRY_REQ 39 #define RESP_CODE_OK 0 #define RESP_CODE_ERR 1 @@ -174,6 +175,7 @@ static uint32_t _atoi(const char* sp) { #define PUSH_CODE_LOG_RX_DATA 0x88 #define PUSH_CODE_TRACE_DATA 0x89 #define PUSH_CODE_NEW_ADVERT 0x8A +#define PUSH_CODE_TELEMETRY_RESPONSE 0x8B #define ERR_CODE_UNSUPPORTED_CMD 1 #define ERR_CODE_NOT_FOUND 2 @@ -196,6 +198,7 @@ class MyMesh : public BaseChatMesh { NodePrefs _prefs; uint32_t pending_login; uint32_t pending_status; + uint32_t pending_telemetry; BaseSerialInterface* _serial; ContactsIterator _iter; uint32_t _iter_filter_since; @@ -666,8 +669,8 @@ protected: } void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) override { - uint32_t sender_timestamp; - memcpy(&sender_timestamp, data, 4); + uint32_t tag; + memcpy(&tag, data, 4); if (pending_login && memcmp(&pending_login, contact.id.pub_key, 4) == 0) { // check for login response // yes, is response to pending sendLogin() @@ -690,8 +693,7 @@ protected: } memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix _serial->writeFrame(out_frame, i); - } else if (len > 4 && pending_status && memcmp(&pending_status, contact.id.pub_key, 4) == 0) { // check for status response - // yes, is response to pending sendStatusRequest() + } else if (len > 4 && tag == pending_status) { // check for status response pending_status = 0; int i = 0; @@ -700,6 +702,15 @@ protected: memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix memcpy(&out_frame[i], &data[4], len - 4); i += (len - 4); _serial->writeFrame(out_frame, i); + } else if (len > 4 && tag == pending_telemetry) { // check for telemetry response + pending_telemetry = 0; + + int i = 0; + out_frame[i++] = PUSH_CODE_TELEMETRY_RESPONSE; + out_frame[i++] = 0; // reserved + memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix + memcpy(&out_frame[i], &data[4], len - 4); i += (len - 4); + _serial->writeFrame(out_frame, i); } } @@ -762,7 +773,7 @@ public: offline_queue_len = 0; app_target_ver = 0; _identity_store = NULL; - pending_login = pending_status = 0; + pending_login = pending_status = pending_telemetry = 0; next_ack_idx = 0; sign_data = NULL; @@ -1300,7 +1311,7 @@ public: if (result == MSG_SEND_FAILED) { writeErrFrame(ERR_CODE_TABLE_FULL); } else { - pending_status = 0; + pending_telemetry = pending_status = 0; memcpy(&pending_login, recipient->id.pub_key, 4); // match this to onContactResponse() out_frame[0] = RESP_CODE_SENT; out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0; @@ -1315,16 +1326,36 @@ public: uint8_t* pub_key = &cmd_frame[1]; ContactInfo* recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE); if (recipient) { - uint32_t est_timeout; - int result = sendStatusRequest(*recipient, est_timeout); + uint32_t tag, est_timeout; + int result = sendRequest(*recipient, REQ_TYPE_GET_STATUS, tag, est_timeout); if (result == MSG_SEND_FAILED) { writeErrFrame(ERR_CODE_TABLE_FULL); } else { - pending_login = 0; - memcpy(&pending_status, recipient->id.pub_key, 4); // match this to onContactResponse() + pending_telemetry = pending_login = 0; + pending_status = tag; // match this in onContactResponse() out_frame[0] = RESP_CODE_SENT; out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0; - memcpy(&out_frame[2], &pending_status, 4); + memcpy(&out_frame[2], &tag, 4); + memcpy(&out_frame[6], &est_timeout, 4); + _serial->writeFrame(out_frame, 10); + } + } else { + writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found + } + } else if (cmd_frame[0] == CMD_SEND_TELEMETRY_REQ && len >= 4+PUB_KEY_SIZE) { + uint8_t* pub_key = &cmd_frame[4]; + ContactInfo* recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE); + if (recipient) { + uint32_t tag, est_timeout; + int result = sendRequest(*recipient, REQ_TYPE_GET_TELEMETRY_DATA, tag, est_timeout); + if (result == MSG_SEND_FAILED) { + writeErrFrame(ERR_CODE_TABLE_FULL); + } else { + pending_status = pending_login = 0; + pending_telemetry = tag; // match this in onContactResponse() + out_frame[0] = RESP_CODE_SENT; + out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0; + memcpy(&out_frame[2], &tag, 4); memcpy(&out_frame[6], &est_timeout, 4); _serial->writeFrame(out_frame, 10); } diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 673c0058..b6be2582 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -414,11 +414,11 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, return MSG_SEND_FAILED; } -int BaseChatMesh::sendStatusRequest(const ContactInfo& recipient, uint32_t& est_timeout) { +int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout) { uint8_t temp[13]; - uint32_t now = getRTCClock()->getCurrentTimeUnique(); - memcpy(temp, &now, 4); // mostly an extra blob to help make packet_hash unique - temp[4] = REQ_TYPE_GET_STATUS; + tag = getRTCClock()->getCurrentTimeUnique(); + memcpy(temp, &tag, 4); // mostly an extra blob to help make packet_hash unique + temp[4] = req_type; memset(&temp[5], 0, 4); // reserved (possibly for 'since' param) getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index d91a0a86..2222bf4e 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -147,7 +147,7 @@ public: int sendCommandData(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& est_timeout); bool sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& channel, const char* sender_name, const char* text, int text_len); int sendLogin(const ContactInfo& recipient, const char* password, uint32_t& est_timeout); - int sendStatusRequest(const ContactInfo& recipient, uint32_t& est_timeout); + int sendRequest(const ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout); bool shareContactZeroHop(const ContactInfo& contact); uint8_t exportContact(const ContactInfo& contact, uint8_t dest_buf[]); bool importContact(const uint8_t src_buf[], uint8_t len); From b407f923e0836e5a8b4784e148b4a3923ccea48d Mon Sep 17 00:00:00 2001 From: recrof Date: Sat, 3 May 2025 15:42:10 +0200 Subject: [PATCH 034/154] initial support for Elecrow ThinkNode M1 --- variants/thinknode_m1/platformio.ini | 19 +++++++++---------- variants/thinknode_m1/target.cpp | 1 + variants/thinknode_m1/target.h | 2 ++ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/variants/thinknode_m1/platformio.ini b/variants/thinknode_m1/platformio.ini index 1cdd66a1..00f7d768 100644 --- a/variants/thinknode_m1/platformio.ini +++ b/variants/thinknode_m1/platformio.ini @@ -21,7 +21,6 @@ build_flags = ${nrf52840_thinknode_m1.build_flags} -D LORA_TX_POWER=22 -D SX126X_CURRENT_LIMIT=130 -D SX126X_RX_BOOSTED_GAIN=1 - build_src_filter = ${nrf52840_thinknode_m1.build_src_filter} + + @@ -31,33 +30,33 @@ upload_protocol = nrfutil [env:ThinkNode_M1_repeater] extends = ThinkNode_M1 -build_src_filter = ${ThinkNode_M1.build_src_filter} +<../examples/simple_repeater/main.cpp> build_flags = ${ThinkNode_M1.build_flags} -D ADVERT_NAME='"ThinkNode Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D DISPLAY_CLASS=GxEPDDisplay - -D DISPLAY_ROTATION=1 - -D HAS_GxEPD ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M1.build_src_filter} + +<../examples/simple_repeater/main.cpp> +lib_deps = + ${ThinkNode_M1.lib_deps} [env:ThinkNode_M1_room_server] extends = ThinkNode_M1 -build_src_filter = ${ThinkNode_M1.build_src_filter} +<../examples/simple_room_server/main.cpp> build_flags = ${ThinkNode_M1.build_flags} -D ADVERT_NAME='"ThinkNode Room"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D DISPLAY_CLASS=GxEPDDisplay - -D DISPLAY_ROTATION=2 - -D HAS_GxEPD ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M1.build_src_filter} + +<../examples/simple_room_server/main.cpp> +lib_deps = + ${ThinkNode_M1.lib_deps} [env:ThinkNode_M1_companion_radio_ble] extends = ThinkNode_M1 @@ -68,8 +67,8 @@ build_flags = -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 - -D DISPLAY_CLASS=GxEPDDisplay -D DISPLAY_ROTATION=4 + -D DISPLAY_CLASS=GxEPDDisplay -D HAS_GxEPD ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 diff --git a/variants/thinknode_m1/target.cpp b/variants/thinknode_m1/target.cpp index 8ee9fd96..c31749f6 100644 --- a/variants/thinknode_m1/target.cpp +++ b/variants/thinknode_m1/target.cpp @@ -10,6 +10,7 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 diff --git a/variants/thinknode_m1/target.h b/variants/thinknode_m1/target.h index 042c5fa1..73e8a134 100644 --- a/variants/thinknode_m1/target.h +++ b/variants/thinknode_m1/target.h @@ -6,10 +6,12 @@ #include #include #include +#include extern ThinkNodeM1Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); From 933e7ba8476086e2e142c1df3d8d8e475b660837 Mon Sep 17 00:00:00 2001 From: Florent Date: Sat, 3 May 2025 17:00:53 +0200 Subject: [PATCH 035/154] t1000e quick and dirty integration of gps into telemetry framework --- variants/t1000-e/LocationProvider.h | 17 +++++ variants/t1000-e/MicroNMEALocationProvider.h | 80 ++++++++++++++++++++ variants/t1000-e/platformio.ini | 1 + variants/t1000-e/target.cpp | 77 ++++++++++++++++++- variants/t1000-e/target.h | 11 ++- 5 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 variants/t1000-e/LocationProvider.h create mode 100644 variants/t1000-e/MicroNMEALocationProvider.h diff --git a/variants/t1000-e/LocationProvider.h b/variants/t1000-e/LocationProvider.h new file mode 100644 index 00000000..b5ac5812 --- /dev/null +++ b/variants/t1000-e/LocationProvider.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Mesh.h" + + +class LocationProvider { + +public: + virtual long getLatitude() = 0; + virtual long getLongitude() = 0; + virtual bool isValid() = 0; + virtual long getTimestamp() = 0; + virtual void reset(); + virtual void begin(); + virtual void stop(); + virtual void loop(); +}; diff --git a/variants/t1000-e/MicroNMEALocationProvider.h b/variants/t1000-e/MicroNMEALocationProvider.h new file mode 100644 index 00000000..da9f74f6 --- /dev/null +++ b/variants/t1000-e/MicroNMEALocationProvider.h @@ -0,0 +1,80 @@ +#pragma once + +#include "LocationProvider.h" +#include +#include + +#ifndef GPS_EN +#define GPS_EN (-1) +#endif + +#ifndef GPS_RESET +#define GPS_RESET (-1) +#endif + +#ifndef GPS_RESET_FORCE +#define GPS_RESET_FORCE LOW +#endif + +class MicroNMEALocationProvider : public LocationProvider { + char _nmeaBuffer[100]; + MicroNMEA nmea; + Stream* _gps_serial; + int _pin_reset; + int _pin_en; + +public : + MicroNMEALocationProvider(Stream& ser, int pin_reset = GPS_RESET, int pin_en = GPS_EN) : + _gps_serial(&ser), nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _pin_reset(pin_reset), _pin_en(pin_en) { + if (_pin_reset != -1) { + pinMode(_pin_reset, OUTPUT); + digitalWrite(_pin_reset, GPS_RESET_FORCE); + } + if (_pin_en != -1) { + pinMode(_pin_en, OUTPUT); + digitalWrite(_pin_en, LOW); + } + } + + void begin() override { + if (_pin_reset != -1) { + digitalWrite(_pin_reset, !GPS_RESET_FORCE); + } + if (_pin_en != -1) { + digitalWrite(_pin_en, HIGH); + } + } + + void reset() override { + if (_pin_reset != -1) { + digitalWrite(_pin_reset, GPS_RESET_FORCE); + delay(100); + digitalWrite(_pin_reset, !GPS_RESET_FORCE); + } + } + + void stop() override { + if (_pin_en != -1) { + digitalWrite(_pin_en, LOW); + } + } + + long getLatitude() override { return nmea.getLatitude(); } + long getLongitude() override { return nmea.getLongitude(); } + bool isValid() override { return nmea.isValid(); } + + long getTimestamp() override { + DateTime dt(nmea.getYear(), nmea.getMonth(),nmea.getDay(),nmea.getHour(),nmea.getMinute(),nmea.getSecond()); + return dt.unixtime(); + } + + void loop() override { + while (_gps_serial->available()) { + char c = _gps_serial->read(); + #ifdef GPS_NMEA_DEBUG + Serial.print(c); + #endif + nmea.process(c); + } + } +}; \ No newline at end of file diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index e5c0d11c..aac367fe 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -51,3 +51,4 @@ build_src_filter = ${t1000-e.build_src_filter} +<../examples/companion_radio/*.cpp> lib_deps = ${t1000-e.lib_deps} densaugeo/base64 @ ~1.4.0 + stevemarple/MicroNMEA @ ^2.0.6 diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index 45c48fb9..770227f1 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -8,7 +8,8 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock rtc_clock; -T1000SensorManager sensors; +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +T1000SensorManager sensors = T1000SensorManager(nmea); #ifndef LORA_CR #define LORA_CR 5 @@ -86,8 +87,70 @@ mesh::LocalIdentity radio_new_identity() { return mesh::LocalIdentity(&rng); // create new random identity } +void T1000SensorManager::start_gps() { + gps_active = true; + //_nmea->begin(); + // this init sequence should be better + // comes from seeed examples and deals with all gps pins + pinMode(GPS_EN, OUTPUT); + digitalWrite(GPS_EN, HIGH); + delay(10); + pinMode(GPS_VRTC_EN, OUTPUT); + digitalWrite(GPS_VRTC_EN, HIGH); + delay(10); + + pinMode(GPS_RESET, OUTPUT); + digitalWrite(GPS_RESET, HIGH); + delay(10); + digitalWrite(GPS_RESET, LOW); + + pinMode(GPS_SLEEP_INT, OUTPUT); + digitalWrite(GPS_SLEEP_INT, HIGH); + pinMode(GPS_RTC_INT, OUTPUT); + digitalWrite(GPS_RTC_INT, LOW); + pinMode(GPS_RESETB, INPUT_PULLUP); +} + +void T1000SensorManager::sleep_gps() { + gps_active = false; + digitalWrite(GPS_VRTC_EN, HIGH); + digitalWrite(GPS_EN, LOW); + digitalWrite(GPS_RESET, HIGH); + digitalWrite(GPS_SLEEP_INT, HIGH); + digitalWrite(GPS_RTC_INT, LOW); + pinMode(GPS_RESETB, OUTPUT); + digitalWrite(GPS_RESETB, LOW); + //_nmea->stop(); +} + +void T1000SensorManager::stop_gps() { + gps_active = false; + digitalWrite(GPS_VRTC_EN, LOW); + digitalWrite(GPS_EN, LOW); + digitalWrite(GPS_RESET, HIGH); + digitalWrite(GPS_SLEEP_INT, HIGH); + digitalWrite(GPS_RTC_INT, LOW); + pinMode(GPS_RESETB, OUTPUT); + digitalWrite(GPS_RESETB, LOW); + //_nmea->stop(); +} + + bool T1000SensorManager::begin() { // TODO: init GPS + Serial1.begin(115200); + + // make sure gps pin are off + digitalWrite(GPS_VRTC_EN, LOW); + digitalWrite(GPS_RESET, LOW); + digitalWrite(GPS_SLEEP_INT, LOW); + digitalWrite(GPS_RTC_INT, LOW); + pinMode(GPS_RESETB, OUTPUT); + digitalWrite(GPS_RESETB, LOW); + + start_gps(); + + return true; } @@ -100,4 +163,16 @@ bool T1000SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& void T1000SensorManager::loop() { // TODO: poll GPS serial, set _lat, _lon, _alt + static long next_gps_update = 0; + + _nmea->loop(); + + if (millis() > next_gps_update) { + if (_nmea->isValid()) { + _lat = ((double)_nmea->getLatitude())/1000000.; + _lon = ((double)_nmea->getLongitude())/1000000.; + //Serial.printf("lat %f lon %f\r\n", _lat, _lon); + } + next_gps_update = millis() + 5000; + } } diff --git a/variants/t1000-e/target.h b/variants/t1000-e/target.h index a47d05b2..a8cbcb84 100644 --- a/variants/t1000-e/target.h +++ b/variants/t1000-e/target.h @@ -1,5 +1,7 @@ #pragma once +#include "MicroNMEALocationProvider.h" + #define RADIOLIB_STATIC_ONLY 1 #include #include @@ -8,13 +10,18 @@ #include #include -class T1000SensorManager : public SensorManager { +class T1000SensorManager: public SensorManager { float _lat, _lon, _alt; + bool gps_active = false; + LocationProvider * _nmea; public: - T1000SensorManager(): _lat(0), _lon(0), _alt(0) { } + T1000SensorManager(LocationProvider &nmea): _nmea(&nmea), _lat(0), _lon(0), _alt(0) { } bool begin() override; bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; void loop() override; + void start_gps(); + void sleep_gps(); + void stop_gps(); }; extern T1000eBoard board; From e442e94e3d947868974c622d6008cde5a54064fc Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 5 May 2025 00:15:35 +1000 Subject: [PATCH 036/154] * SensorManager: now can influence advert lat/lon, new custom name:value pairs for custom settings (eg, gps on/off) * companion: new CMD_GET_CUSTOM_VARS, CMD_SET_CUSTOM_VAR * T1000e: now supports "gps" custom setting (value "0" or "1") --- examples/companion_radio/NodePrefs.h | 1 - examples/companion_radio/main.cpp | 47 ++++++++++++++++++++++------ src/helpers/SensorManager.h | 7 +++++ variants/t1000-e/target.cpp | 37 ++++++++++++++++------ variants/t1000-e/target.h | 16 ++++++---- 5 files changed, 82 insertions(+), 26 deletions(-) diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 691eec42..37a170b5 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -6,7 +6,6 @@ struct NodePrefs { // persisted to file float airtime_factor; char node_name[32]; - double node_lat, node_lon; float freq; uint8_t sf; uint8_t cr; diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 1ca5c650..7ab94619 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -140,6 +140,8 @@ static uint32_t _atoi(const char* sp) { #define CMD_SET_DEVICE_PIN 37 #define CMD_SET_OTHER_PARAMS 38 #define CMD_SEND_TELEMETRY_REQ 39 +#define CMD_GET_CUSTOM_VARS 40 +#define CMD_SET_CUSTOM_VAR 41 #define RESP_CODE_OK 0 #define RESP_CODE_ERR 1 @@ -162,6 +164,7 @@ static uint32_t _atoi(const char* sp) { #define RESP_CODE_CHANNEL_INFO 18 // a reply to CMD_GET_CHANNEL #define RESP_CODE_SIGN_START 19 #define RESP_CODE_SIGNATURE 20 +#define RESP_CODE_CUSTOM_VARS 21 // these are _pushed_ to client app at any time #define PUSH_CODE_ADVERT 0x80 @@ -801,8 +804,8 @@ public: file.read((uint8_t *) &_prefs.airtime_factor, sizeof(float)); // 0 file.read((uint8_t *) _prefs.node_name, sizeof(_prefs.node_name)); // 4 file.read(pad, 4); // 36 - file.read((uint8_t *) &_prefs.node_lat, sizeof(_prefs.node_lat)); // 40 - file.read((uint8_t *) &_prefs.node_lon, sizeof(_prefs.node_lon)); // 48 + file.read((uint8_t *) &sensors.node_lat, sizeof(sensors.node_lat)); // 40 + file.read((uint8_t *) &sensors.node_lon, sizeof(sensors.node_lon)); // 48 file.read((uint8_t *) &_prefs.freq, sizeof(_prefs.freq)); // 56 file.read((uint8_t *) &_prefs.sf, sizeof(_prefs.sf)); // 60 file.read((uint8_t *) &_prefs.cr, sizeof(_prefs.cr)); // 61 @@ -910,8 +913,8 @@ public: file.write((uint8_t *) &_prefs.airtime_factor, sizeof(float)); // 0 file.write((uint8_t *) _prefs.node_name, sizeof(_prefs.node_name)); // 4 file.write(pad, 4); // 36 - file.write((uint8_t *) &_prefs.node_lat, sizeof(_prefs.node_lat)); // 40 - file.write((uint8_t *) &_prefs.node_lon, sizeof(_prefs.node_lon)); // 48 + file.write((uint8_t *) &sensors.node_lat, sizeof(sensors.node_lat)); // 40 + file.write((uint8_t *) &sensors.node_lon, sizeof(sensors.node_lon)); // 48 file.write((uint8_t *) &_prefs.freq, sizeof(_prefs.freq)); // 56 file.write((uint8_t *) &_prefs.sf, sizeof(_prefs.sf)); // 60 file.write((uint8_t *) &_prefs.cr, sizeof(_prefs.cr)); // 61 @@ -958,8 +961,8 @@ public: memcpy(&out_frame[i], self_id.pub_key, PUB_KEY_SIZE); i += PUB_KEY_SIZE; int32_t lat, lon; - lat = (_prefs.node_lat * 1000000.0); - lon = (_prefs.node_lon * 1000000.0); + lat = (sensors.node_lat * 1000000.0); + lon = (sensors.node_lon * 1000000.0); memcpy(&out_frame[i], &lat, 4); i += 4; memcpy(&out_frame[i], &lon, 4); i += 4; out_frame[i++] = 0; // reserved @@ -1072,8 +1075,8 @@ public: memcpy(&alt, &cmd_frame[9], 4); // for FUTURE support } if (lat <= 90*1E6 && lat >= -90*1E6 && lon <= 180*1E6 && lon >= -180*1E6) { - _prefs.node_lat = ((double)lat) / 1000000.0; - _prefs.node_lon = ((double)lon) / 1000000.0; + sensors.node_lat = ((double)lat) / 1000000.0; + sensors.node_lon = ((double)lon) / 1000000.0; savePrefs(); writeOKFrame(); } else { @@ -1096,7 +1099,7 @@ public: writeErrFrame(ERR_CODE_ILLEGAL_ARG); } } else if (cmd_frame[0] == CMD_SEND_SELF_ADVERT) { - auto pkt = createSelfAdvert(_prefs.node_name, _prefs.node_lat, _prefs.node_lon); + auto pkt = createSelfAdvert(_prefs.node_name, sensors.node_lat, sensors.node_lon); if (pkt) { if (len >= 2 && cmd_frame[1] == 1) { // optional param (1 = flood, 0 = zero hop) sendFlood(pkt); @@ -1170,7 +1173,7 @@ public: } else if (cmd_frame[0] == CMD_EXPORT_CONTACT) { if (len < 1 + PUB_KEY_SIZE) { // export SELF - auto pkt = createSelfAdvert(_prefs.node_name, _prefs.node_lat, _prefs.node_lon); + auto pkt = createSelfAdvert(_prefs.node_name, sensors.node_lat, sensors.node_lon); if (pkt) { pkt->header |= ROUTE_TYPE_FLOOD; // would normally be sent in this mode @@ -1456,6 +1459,30 @@ public: memcpy(&_prefs.ble_pin, &cmd_frame[1], 4); savePrefs(); writeOKFrame(); + } else if (cmd_frame[0] == CMD_GET_CUSTOM_VARS) { + out_frame[0] = RESP_CODE_CUSTOM_VARS; + char* dp = (char *) &out_frame[1]; + for (int i = 0; i < sensors.getNumSettings() && dp - (char *) &out_frame[1] < 140; i++) { + if (i > 0) { *dp++ = ','; } + strcpy(dp, sensors.getSettingName(i)); dp = strchr(dp, 0); + *dp++ = ':'; + strcpy(dp, sensors.getSettingValue(i)); dp = strchr(dp, 0); + } + _serial->writeFrame(out_frame, dp - (char *)out_frame); + } else if (cmd_frame[0] == CMD_SET_CUSTOM_VAR && len >= 4) { + char* sp = (char *) &cmd_frame[1]; + char* np = strchr(sp, ':'); // look for separator char + if (np) { + *np++ = 0; // modify 'cmd_frame', replace ':' with null + bool success = sensors.setSettingValue(sp, np); + if (success) { + writeOKFrame(); + } else { + writeErrFrame(ERR_CODE_ILLEGAL_ARG); + } + } else { + writeErrFrame(ERR_CODE_ILLEGAL_ARG); + } } else { writeErrFrame(ERR_CODE_UNSUPPORTED_CMD); MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]); diff --git a/src/helpers/SensorManager.h b/src/helpers/SensorManager.h index 84d6c7b0..eb58fa78 100644 --- a/src/helpers/SensorManager.h +++ b/src/helpers/SensorManager.h @@ -8,7 +8,14 @@ class SensorManager { public: + double node_lat, node_lon; // modify these, if you want to affect Advert location + + SensorManager() { node_lat = 0; node_lon = 0; } virtual bool begin() { return false; } virtual bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { return false; } virtual void loop() { } + virtual int getNumSettings() const { return 0; } + virtual const char* getSettingName(int i) const { return NULL; } + virtual const char* getSettingValue(int i) const { return NULL; } + virtual bool setSettingValue(const char* name, const char* value) { return false; } }; diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index 770227f1..6a058545 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -137,7 +137,7 @@ void T1000SensorManager::stop_gps() { bool T1000SensorManager::begin() { - // TODO: init GPS + // init GPS Serial1.begin(115200); // make sure gps pin are off @@ -148,31 +148,50 @@ bool T1000SensorManager::begin() { pinMode(GPS_RESETB, OUTPUT); digitalWrite(GPS_RESETB, LOW); - start_gps(); - - return true; } bool T1000SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission? - telemetry.addGPS(TELEM_CHANNEL_SELF, _lat, _lon, _alt); + telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, 0.0f); } return true; } void T1000SensorManager::loop() { - // TODO: poll GPS serial, set _lat, _lon, _alt static long next_gps_update = 0; _nmea->loop(); if (millis() > next_gps_update) { if (_nmea->isValid()) { - _lat = ((double)_nmea->getLatitude())/1000000.; - _lon = ((double)_nmea->getLongitude())/1000000.; + node_lat = ((double)_nmea->getLatitude())/1000000.; + node_lon = ((double)_nmea->getLongitude())/1000000.; //Serial.printf("lat %f lon %f\r\n", _lat, _lon); } - next_gps_update = millis() + 5000; + next_gps_update = millis() + 1000; } } + +int T1000SensorManager::getNumSettings() const { return 1; } // just one supported: "gps" (power switch) + +const char* T1000SensorManager::getSettingName(int i) const { + return i == 0 ? "gps" : NULL; +} +const char* T1000SensorManager::getSettingValue(int i) const { + if (i == 0) { + return gps_active ? "1" : "0"; + } + return NULL; +} +bool T1000SensorManager::setSettingValue(const char* name, const char* value) { + if (strcmp(name, "gps") == 0) { + if (strcmp(value, "0") == 0) { + stop_gps(); // or should this be sleep_gps() ?? + } else { + start_gps(); + } + return true; + } + return false; // not supported +} diff --git a/variants/t1000-e/target.h b/variants/t1000-e/target.h index a8cbcb84..4fc4c94e 100644 --- a/variants/t1000-e/target.h +++ b/variants/t1000-e/target.h @@ -11,17 +11,21 @@ #include class T1000SensorManager: public SensorManager { - float _lat, _lon, _alt; bool gps_active = false; LocationProvider * _nmea; -public: - T1000SensorManager(LocationProvider &nmea): _nmea(&nmea), _lat(0), _lon(0), _alt(0) { } - bool begin() override; - bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; - void loop() override; + void start_gps(); void sleep_gps(); void stop_gps(); +public: + T1000SensorManager(LocationProvider &nmea): _nmea(&nmea) { } + bool begin() override; + bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; + void loop() override; + int getNumSettings() const override; + const char* getSettingName(int i) const override; + const char* getSettingValue(int i) const override; + bool setSettingValue(const char* name, const char* value) override; }; extern T1000eBoard board; From 0bccf29f64ad744fe3de23478d7f8d032c74e3f3 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Mon, 5 May 2025 11:21:31 +1200 Subject: [PATCH 037/154] use hex of first 4 bytes of identity public key as default node name --- examples/companion_radio/main.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 7ab94619..efd6ba63 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -847,6 +847,13 @@ public: loadMainIdentity(); + // use hex of first 4 bytes of identity public key as default node name + if(strcmp(_prefs.node_name, "NONAME") == 0){ + char pub_key_hex[10]; + mesh::Utils::toHex(pub_key_hex, self_id.pub_key, 4); + strcpy(_prefs.node_name, pub_key_hex); + } + // load persisted prefs if (_fs->exists("/new_prefs")) { loadPrefsInt("/new_prefs"); // new filename From 8f32ee61ce3f9ab60b8334f90ca2466479be9f54 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Mon, 5 May 2025 11:34:02 +1200 Subject: [PATCH 038/154] no need for prefs check before prefs are loaded --- examples/companion_radio/main.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index efd6ba63..569a7f30 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -848,11 +848,9 @@ public: loadMainIdentity(); // use hex of first 4 bytes of identity public key as default node name - if(strcmp(_prefs.node_name, "NONAME") == 0){ - char pub_key_hex[10]; - mesh::Utils::toHex(pub_key_hex, self_id.pub_key, 4); - strcpy(_prefs.node_name, pub_key_hex); - } + char pub_key_hex[10]; + mesh::Utils::toHex(pub_key_hex, self_id.pub_key, 4); + strcpy(_prefs.node_name, pub_key_hex); // load persisted prefs if (_fs->exists("/new_prefs")) { From 678f36a57b1bb5adc943bea12a77bdf5cce1e745 Mon Sep 17 00:00:00 2001 From: JQ Date: Sun, 4 May 2025 18:17:18 -0700 Subject: [PATCH 039/154] Implement getTextWidth for display classes - Added getTextWidth method to DisplayDriver interface - Implemented getTextWidth in all display classes - Updated examples to use getTextWidth directly --- examples/companion_radio/UITask.cpp | 2 +- examples/simple_repeater/UITask.cpp | 6 +++--- examples/simple_room_server/UITask.cpp | 8 ++++---- src/helpers/ui/DisplayDriver.h | 1 + src/helpers/ui/GxEPDDisplay.cpp | 7 +++++++ src/helpers/ui/GxEPDDisplay.h | 1 + src/helpers/ui/SSD1306Display.cpp | 7 +++++++ src/helpers/ui/SSD1306Display.h | 1 + src/helpers/ui/ST7789Display.cpp | 5 +++++ src/helpers/ui/ST7789Display.h | 2 ++ 10 files changed, 32 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index bf78927d..e0c2ec51 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -144,7 +144,7 @@ void UITask::renderCurrScreen() { // version info _display->setColor(DisplayDriver::LIGHT); _display->setTextSize(1); - int textWidth = strlen(_version_info) * 5; // Assuming each character is 5 pixels wide + uint16_t textWidth = _display->getTextWidth(_version_info); _display->setCursor((_display->width() - textWidth) / 2, 22); _display->print(_version_info); } else { // home screen diff --git a/examples/simple_repeater/UITask.cpp b/examples/simple_repeater/UITask.cpp index 39ca1c81..d096d14b 100644 --- a/examples/simple_repeater/UITask.cpp +++ b/examples/simple_repeater/UITask.cpp @@ -51,14 +51,14 @@ void UITask::renderCurrScreen() { // version info _display->setColor(DisplayDriver::LIGHT); _display->setTextSize(1); - int versionWidth = strlen(_version_info) * 6; + uint16_t versionWidth = _display->getTextWidth(_version_info); _display->setCursor((_display->width() - versionWidth) / 2, 22); _display->print(_version_info); // node type const char* node_type = "< Repeater >"; - int nodeTypeWidth = strlen(node_type) * 6; - _display->setCursor((_display->width() - nodeTypeWidth) / 2, 35); + uint16_t typeWidth = _display->getTextWidth(node_type); + _display->setCursor((_display->width() - typeWidth) / 2, 35); _display->print(node_type); } else { // home screen // node name diff --git a/examples/simple_room_server/UITask.cpp b/examples/simple_room_server/UITask.cpp index 2f559111..d096d14b 100644 --- a/examples/simple_room_server/UITask.cpp +++ b/examples/simple_room_server/UITask.cpp @@ -51,14 +51,14 @@ void UITask::renderCurrScreen() { // version info _display->setColor(DisplayDriver::LIGHT); _display->setTextSize(1); - int versionWidth = strlen(_version_info) * 6; + uint16_t versionWidth = _display->getTextWidth(_version_info); _display->setCursor((_display->width() - versionWidth) / 2, 22); _display->print(_version_info); // node type - const char* node_type = "< Room Server >"; - int nodeTypeWidth = strlen(node_type) * 6; - _display->setCursor((_display->width() - nodeTypeWidth) / 2, 35); + const char* node_type = "< Repeater >"; + uint16_t typeWidth = _display->getTextWidth(node_type); + _display->setCursor((_display->width() - typeWidth) / 2, 35); _display->print(node_type); } else { // home screen // node name diff --git a/src/helpers/ui/DisplayDriver.h b/src/helpers/ui/DisplayDriver.h index 57aed85c..2d8b69c1 100644 --- a/src/helpers/ui/DisplayDriver.h +++ b/src/helpers/ui/DisplayDriver.h @@ -24,5 +24,6 @@ public: virtual void fillRect(int x, int y, int w, int h) = 0; virtual void drawRect(int x, int y, int w, int h) = 0; virtual void drawXbm(int x, int y, const uint8_t* bits, int w, int h) = 0; + virtual uint16_t getTextWidth(const char* str) = 0; virtual void endFrame() = 0; }; diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index e7b70b49..47f6ae27 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -94,6 +94,13 @@ void GxEPDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { display.drawBitmap(x*1.5, (y*1.5) + 10, bits, w, h, GxEPD_BLACK); } +uint16_t GxEPDDisplay::getTextWidth(const char* str) { + int16_t x1, y1; + uint16_t w, h; + display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); + return w; +} + void GxEPDDisplay::endFrame() { display.display(true); } diff --git a/src/helpers/ui/GxEPDDisplay.h b/src/helpers/ui/GxEPDDisplay.h index ecadc50c..d772d131 100644 --- a/src/helpers/ui/GxEPDDisplay.h +++ b/src/helpers/ui/GxEPDDisplay.h @@ -47,5 +47,6 @@ public: void fillRect(int x, int y, int w, int h) override; void drawRect(int x, int y, int w, int h) override; void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override; + uint16_t getTextWidth(const char* str) override; void endFrame() override; }; diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp index 55516378..8d977db0 100644 --- a/src/helpers/ui/SSD1306Display.cpp +++ b/src/helpers/ui/SSD1306Display.cpp @@ -62,6 +62,13 @@ void SSD1306Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { display.drawBitmap(x, y, bits, w, h, SSD1306_WHITE); } +uint16_t SSD1306Display::getTextWidth(const char* str) { + int16_t x1, y1; + uint16_t w, h; + display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); + return w; +} + void SSD1306Display::endFrame() { display.display(); } diff --git a/src/helpers/ui/SSD1306Display.h b/src/helpers/ui/SSD1306Display.h index cd0e2a0a..1a3a9602 100644 --- a/src/helpers/ui/SSD1306Display.h +++ b/src/helpers/ui/SSD1306Display.h @@ -36,5 +36,6 @@ public: void fillRect(int x, int y, int w, int h) override; void drawRect(int x, int y, int w, int h) override; void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override; + uint16_t getTextWidth(const char* str) override; void endFrame() override; }; diff --git a/src/helpers/ui/ST7789Display.cpp b/src/helpers/ui/ST7789Display.cpp index 9b7184ac..f996f702 100644 --- a/src/helpers/ui/ST7789Display.cpp +++ b/src/helpers/ui/ST7789Display.cpp @@ -44,6 +44,7 @@ void ST7789Display::startFrame(Color bkg) { } void ST7789Display::setTextSize(int sz) { + _textSize = sz; // Store the text size switch(sz) { case 1 : display.setFont(ArialMT_Plain_10); @@ -107,6 +108,10 @@ void ST7789Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { display.drawBitmap(x+X_OFFSET, y, w, h, bits); } +uint16_t ST7789Display::getTextWidth(const char* str) { + return display.getStringWidth(str); +} + void ST7789Display::endFrame() { display.display(); } diff --git a/src/helpers/ui/ST7789Display.h b/src/helpers/ui/ST7789Display.h index 769957f1..1188f2b0 100644 --- a/src/helpers/ui/ST7789Display.h +++ b/src/helpers/ui/ST7789Display.h @@ -11,6 +11,7 @@ class ST7789Display : public DisplayDriver { bool _isOn; uint16_t _color; int _x=0, _y=0; + int _textSize=1; // Track the current text size bool i2c_probe(TwoWire& wire, uint8_t addr); public: @@ -31,5 +32,6 @@ public: void fillRect(int x, int y, int w, int h) override; void drawRect(int x, int y, int w, int h) override; void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override; + uint16_t getTextWidth(const char* str) override; void endFrame() override; }; From 9d967388f79189ba90579bbb8bde9479f27f27f2 Mon Sep 17 00:00:00 2001 From: JQ Date: Sun, 4 May 2025 18:20:53 -0700 Subject: [PATCH 040/154] cleanup --- src/helpers/ui/ST7789Display.cpp | 1 - src/helpers/ui/ST7789Display.h | 1 - 2 files changed, 2 deletions(-) diff --git a/src/helpers/ui/ST7789Display.cpp b/src/helpers/ui/ST7789Display.cpp index f996f702..fef7dc56 100644 --- a/src/helpers/ui/ST7789Display.cpp +++ b/src/helpers/ui/ST7789Display.cpp @@ -44,7 +44,6 @@ void ST7789Display::startFrame(Color bkg) { } void ST7789Display::setTextSize(int sz) { - _textSize = sz; // Store the text size switch(sz) { case 1 : display.setFont(ArialMT_Plain_10); diff --git a/src/helpers/ui/ST7789Display.h b/src/helpers/ui/ST7789Display.h index 1188f2b0..da0db818 100644 --- a/src/helpers/ui/ST7789Display.h +++ b/src/helpers/ui/ST7789Display.h @@ -11,7 +11,6 @@ class ST7789Display : public DisplayDriver { bool _isOn; uint16_t _color; int _x=0, _y=0; - int _textSize=1; // Track the current text size bool i2c_probe(TwoWire& wire, uint8_t addr); public: From cb80ceee4712cf5a65c72b9bfd0645db36fd1d19 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 5 May 2025 11:21:55 +1000 Subject: [PATCH 041/154] * companion: protocol ver bump to 5 * companion: new prefs: telemetry_mode_base, telemetry_mode_loc * companion: CMD_SET_OTHER_PARAMS, now optionally can set telemetry_modes --- examples/companion_radio/NodePrefs.h | 7 +++- examples/companion_radio/main.cpp | 49 +++++++++++++++++++++------- src/helpers/SensorManager.h | 1 + 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 37a170b5..2f209c54 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -3,6 +3,10 @@ #include // For uint8_t, uint32_t +#define TELEM_MODE_DENY 0 +#define TELEM_MODE_ALLOW_FLAGS 1 // use contact.flags +#define TELEM_MODE_ALLOW_ALL 2 + struct NodePrefs { // persisted to file float airtime_factor; char node_name[32]; @@ -13,7 +17,8 @@ struct NodePrefs { // persisted to file uint8_t manual_add_contacts; float bw; uint8_t tx_power_dbm; - uint8_t unused[3]; + uint8_t telemetry_mode_base; + uint8_t telemetry_mode_loc; float rx_delay_base; uint32_t ble_pin; }; diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 7ab94619..44ef8b79 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -91,7 +91,7 @@ static uint32_t _atoi(const char* sp) { /*------------ Frame Protocol --------------*/ -#define FIRMWARE_VER_CODE 4 +#define FIRMWARE_VER_CODE 5 #ifndef FIRMWARE_BUILD_DATE #define FIRMWARE_BUILD_DATE "21 Apr 2025" @@ -657,16 +657,33 @@ protected: uint8_t onContactRequest(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t* data, uint8_t len, uint8_t* reply) override { if (data[0] == REQ_TYPE_GET_TELEMETRY_DATA) { - telemetry.reset(); - telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); - // query other sensors -- target specific - sensors.querySensors(contact.flags, telemetry); + uint8_t permissions = 0; + uint8_t cp = contact.flags >> 1; // LSB used as 'favourite' bit (so only use upper bits) - memcpy(reply, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag') + if (_prefs.telemetry_mode_base == TELEM_MODE_ALLOW_ALL) { + permissions = TELEM_PERM_BASE; + } else if (_prefs.telemetry_mode_base == TELEM_MODE_ALLOW_FLAGS) { + permissions = cp & TELEM_PERM_BASE; + } - uint8_t tlen = telemetry.getSize(); - memcpy(&reply[4], telemetry.getBuffer(), tlen); - return 4 + tlen; + if (_prefs.telemetry_mode_loc == TELEM_MODE_ALLOW_ALL) { + permissions |= TELEM_PERM_LOCATION; + } else if (_prefs.telemetry_mode_loc == TELEM_MODE_ALLOW_FLAGS) { + permissions |= cp & TELEM_PERM_LOCATION; + } + + if (permissions & TELEM_PERM_BASE) { // only respond if base permission bit is set + telemetry.reset(); + telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); + // query other sensors -- target specific + sensors.querySensors(permissions, telemetry); + + memcpy(reply, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag') + + uint8_t tlen = telemetry.getSize(); + memcpy(&reply[4], telemetry.getBuffer(), tlen); + return 4 + tlen; + } } return 0; // unknown } @@ -813,7 +830,9 @@ public: file.read((uint8_t *) &_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63 file.read((uint8_t *) &_prefs.bw, sizeof(_prefs.bw)); // 64 file.read((uint8_t *) &_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 - file.read((uint8_t *) _prefs.unused, sizeof(_prefs.unused)); // 69 + file.read((uint8_t *) &_prefs.telemetry_mode_base, sizeof(_prefs.telemetry_mode_base)); // 69 + file.read((uint8_t *) &_prefs.telemetry_mode_loc, sizeof(_prefs.telemetry_mode_loc)); // 70 + file.read(pad, 1); // 71 file.read((uint8_t *) &_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 file.read(pad, 4); // 76 file.read((uint8_t *) &_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 @@ -922,7 +941,9 @@ public: file.write((uint8_t *) &_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63 file.write((uint8_t *) &_prefs.bw, sizeof(_prefs.bw)); // 64 file.write((uint8_t *) &_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 - file.write((uint8_t *) _prefs.unused, sizeof(_prefs.unused)); // 69 + file.write((uint8_t *) &_prefs.telemetry_mode_base, sizeof(_prefs.telemetry_mode_base)); // 69 + file.write((uint8_t *) &_prefs.telemetry_mode_loc, sizeof(_prefs.telemetry_mode_loc)); // 70 + file.write(pad, 1); // 71 file.write((uint8_t *) &_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 file.write(pad, 4); // 76 file.write((uint8_t *) &_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 @@ -967,7 +988,7 @@ public: memcpy(&out_frame[i], &lon, 4); i += 4; out_frame[i++] = 0; // reserved out_frame[i++] = 0; // reserved - out_frame[i++] = 0; // reserved + out_frame[i++] = (_prefs.telemetry_mode_loc << 2) | (_prefs.telemetry_mode_base); // v5+ out_frame[i++] = _prefs.manual_add_contacts; uint32_t freq = _prefs.freq * 1000; @@ -1256,6 +1277,10 @@ public: writeOKFrame(); } else if (cmd_frame[0] == CMD_SET_OTHER_PARAMS) { _prefs.manual_add_contacts = cmd_frame[1]; + if (len >= 3) { + _prefs.telemetry_mode_base = cmd_frame[2] & 0x03; // v5+ + _prefs.telemetry_mode_loc = (cmd_frame[2] >> 2) & 0x03; + } savePrefs(); writeOKFrame(); } else if (cmd_frame[0] == CMD_REBOOT && memcmp(&cmd_frame[1], "reboot", 6) == 0) { diff --git a/src/helpers/SensorManager.h b/src/helpers/SensorManager.h index eb58fa78..a5d53939 100644 --- a/src/helpers/SensorManager.h +++ b/src/helpers/SensorManager.h @@ -2,6 +2,7 @@ #include +#define TELEM_PERM_BASE 0x01 // 'base' permission includes battery #define TELEM_PERM_LOCATION 0x02 #define TELEM_CHANNEL_SELF 1 // LPP data channel for 'self' device From bcb64d8a4c7ea77e372208d326974a3390ee07f2 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 5 May 2025 11:49:17 +1000 Subject: [PATCH 042/154] * companion: fix for _GET_STATUS response --- examples/companion_radio/main.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 44ef8b79..92b8e3ea 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -713,7 +713,10 @@ protected: } memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix _serial->writeFrame(out_frame, i); - } else if (len > 4 && tag == pending_status) { // check for status response + } else if (len > 4 && // check for status response + pending_status && memcmp(&pending_status, contact.id.pub_key, 4) == 0 // legacy matching scheme + // FUTURE: tag == pending_status + ) { pending_status = 0; int i = 0; @@ -1360,7 +1363,8 @@ public: writeErrFrame(ERR_CODE_TABLE_FULL); } else { pending_telemetry = pending_login = 0; - pending_status = tag; // match this in onContactResponse() + // FUTURE: pending_status = tag; // match this in onContactResponse() + memcpy(&pending_status, recipient->id.pub_key, 4); // legacy matching scheme out_frame[0] = RESP_CODE_SENT; out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0; memcpy(&out_frame[2], &tag, 4); From af606343a7bc65d8099e16df28a31173b3580a68 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 5 May 2025 13:11:43 +1000 Subject: [PATCH 043/154] * FIX: UI should show "< Room Server >" --- examples/simple_room_server/UITask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_room_server/UITask.cpp b/examples/simple_room_server/UITask.cpp index d096d14b..46311c5e 100644 --- a/examples/simple_room_server/UITask.cpp +++ b/examples/simple_room_server/UITask.cpp @@ -56,7 +56,7 @@ void UITask::renderCurrScreen() { _display->print(_version_info); // node type - const char* node_type = "< Repeater >"; + const char* node_type = "< Room Server >"; uint16_t typeWidth = _display->getTextWidth(node_type); _display->setCursor((_display->width() - typeWidth) / 2, 35); _display->print(node_type); From 136f3d100067e77bee37f1218c589639fffbdc88 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 5 May 2025 13:37:48 +1000 Subject: [PATCH 044/154] * GxEPDDIsplay: driver now applying SCALE_X, SCALE_Y --- src/helpers/ui/GxEPDDisplay.cpp | 40 +++++++++++---------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index 589723d2..57a96074 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -5,6 +5,14 @@ #define DISPLAY_ROTATION 3 #endif +#ifdef TECHO_ZOOM + #define SCALE_X (1.5625f * 1.5f) // 200 / 128 (with 1.5 scale) + #define SCALE_Y (2.9687f * 1.5f) // 190 / 64 (with 1.5 scale) +#else + #define SCALE_X 1.5625f // 200 / 128 + #define SCALE_Y 2.9687f // 190 / 64 +#endif + bool GxEPDDisplay::begin() { display.epd2.selectSPI(SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0)); SPI1.begin(); @@ -57,11 +65,7 @@ void GxEPDDisplay::setColor(Color c) { } void GxEPDDisplay::setCursor(int x, int y) { -#ifdef TECHO_ZOOM - x = x + (x >> 1); - y = y + (y >> 1); -#endif - display.setCursor(x, (y+10)); + display.setCursor(x*SCALE_X, (y+10)*SCALE_Y); } void GxEPDDisplay::print(const char* str) { @@ -69,40 +73,22 @@ void GxEPDDisplay::print(const char* str) { } void GxEPDDisplay::fillRect(int x, int y, int w, int h) { -#ifdef TECHO_ZOOM - x = x + (x >> 1); - y = y + (y >> 1); - w = w + (w >> 1); - h = h + (h >> 1); -#endif - display.fillRect(x, y, w, h, GxEPD_BLACK); + display.fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, GxEPD_BLACK); } void GxEPDDisplay::drawRect(int x, int y, int w, int h) { -#ifdef TECHO_ZOOM - x = x + (x >> 1); - y = y + (y >> 1); - w = w + (w >> 1); - h = h + (h >> 1); -#endif - display.drawRect(x, y, w, h, GxEPD_BLACK); + display.drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, GxEPD_BLACK); } void GxEPDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { -#ifdef TECHO_ZOOM - x = x + (x >> 1); - y = y + (y >> 1); - w = w + (w >> 1); - h = h + (h >> 1); -#endif - display.drawBitmap(x*1.5, (y*1.5) + 10, bits, w, h, GxEPD_BLACK); + display.drawBitmap(x*SCALE_X, (y*SCALE_Y) + 10, bits, w, h, GxEPD_BLACK); } uint16_t GxEPDDisplay::getTextWidth(const char* str) { int16_t x1, y1; uint16_t w, h; display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); - return w; + return w / SCALE_X; } void GxEPDDisplay::endFrame() { From 67d709b3aa711b55f97efa1abca4a55ec55cd923 Mon Sep 17 00:00:00 2001 From: JQ Date: Sun, 4 May 2025 21:51:58 -0700 Subject: [PATCH 045/154] T114 Landscape --- src/helpers/ui/ST7789Display.cpp | 14 +++++++---- src/helpers/ui/ST7789Display.h | 2 +- src/helpers/ui/ST7789Spi.h | 40 ++++++++++++++++++-------------- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/helpers/ui/ST7789Display.cpp b/src/helpers/ui/ST7789Display.cpp index fef7dc56..cc522152 100644 --- a/src/helpers/ui/ST7789Display.cpp +++ b/src/helpers/ui/ST7789Display.cpp @@ -3,7 +3,11 @@ #include "ST7789Display.h" #ifndef X_OFFSET -#define X_OFFSET 16 +#define X_OFFSET 0 // No offset needed for landscape +#endif + +#ifndef Y_OFFSET +#define Y_OFFSET 1 // Vertical offset to prevent top row cutoff #endif bool ST7789Display::begin() { @@ -88,7 +92,7 @@ void ST7789Display::setColor(Color c) { void ST7789Display::setCursor(int x, int y) { _x = x + X_OFFSET; - _y = y; + _y = y + Y_OFFSET; } void ST7789Display::print(const char* str) { @@ -96,15 +100,15 @@ void ST7789Display::print(const char* str) { } void ST7789Display::fillRect(int x, int y, int w, int h) { - display.fillRect(x + X_OFFSET, y, w, h); + display.fillRect(x + X_OFFSET, y + Y_OFFSET, w, h); } void ST7789Display::drawRect(int x, int y, int w, int h) { - display.drawRect(x + X_OFFSET, y, w, h); + display.drawRect(x + X_OFFSET, y + Y_OFFSET, w, h); } void ST7789Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { - display.drawBitmap(x+X_OFFSET, y, w, h, bits); + display.drawBitmap(x + X_OFFSET, y + Y_OFFSET, w, h, bits); } uint16_t ST7789Display::getTextWidth(const char* str) { diff --git a/src/helpers/ui/ST7789Display.h b/src/helpers/ui/ST7789Display.h index da0db818..338399f4 100644 --- a/src/helpers/ui/ST7789Display.h +++ b/src/helpers/ui/ST7789Display.h @@ -14,7 +14,7 @@ class ST7789Display : public DisplayDriver { bool i2c_probe(TwoWire& wire, uint8_t addr); public: - ST7789Display() : DisplayDriver(135, 240), display(&SPI1, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 240, 135) {_isOn = false;} + ST7789Display() : DisplayDriver(240, 135), display(&SPI1, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 240, 135) {_isOn = false;} // ST7789Display() : DisplayDriver(135, 240), display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST) { _isOn = false; } bool begin(); diff --git a/src/helpers/ui/ST7789Spi.h b/src/helpers/ui/ST7789Spi.h index ec32f3b0..93bfa87c 100644 --- a/src/helpers/ui/ST7789Spi.h +++ b/src/helpers/ui/ST7789Spi.h @@ -242,7 +242,7 @@ class ST7789Spi : public OLEDDisplay { } virtual void resetOrientation() { - uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX; + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV; sendCommand(ST77XX_MADCTL); WriteData(madctl); delay(10); @@ -263,13 +263,13 @@ class ST7789Spi : public OLEDDisplay { } virtual void landscapeScreen() { - - - uint8_t madctl = ST77XX_MADCTL_RGB; + // For landscape mode rotated 180 degrees with correct text direction + // MV swaps rows/columns for landscape orientation + // Adding MX (instead of MY) flips X axis and rotates 180 degrees + uint8_t madctl = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MV | ST77XX_MADCTL_MX; sendCommand(ST77XX_MADCTL); WriteData(madctl); delay(10); - } @@ -325,20 +325,25 @@ class ST7789Spi : public OLEDDisplay { WriteData(0x55); delay(10); - sendCommand(ST77XX_MADCTL); // 4: Mem access ctrl (directions), Row/col addr, bottom-top refresh - WriteData(0x08); + // Initialize with landscape orientation rotated 180 degrees + uint8_t madctl = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MV | ST77XX_MADCTL_MX; + sendCommand(ST77XX_MADCTL); // 4: Mem access ctrl (directions) + WriteData(madctl); + delay(10); - sendCommand(ST77XX_CASET); // 5: Column addr set, + // Set column address range for landscape orientation (240 pixels wide) + sendCommand(ST77XX_CASET); // 5: Column addr set WriteData(0x00); WriteData(0x00); // XSTART = 0 WriteData(0x00); WriteData(240); // XEND = 240 - sendCommand(ST77XX_RASET); // 6: Row addr set, + // Set row address range for landscape orientation (135 pixels tall) + sendCommand(ST77XX_RASET); // 6: Row addr set WriteData(0x00); WriteData(0x00); // YSTART = 0 - WriteData(320>>8); - WriteData(320&0xFF); // YSTART = 320 + WriteData(0x00); + WriteData(135); // YEND = 135 sendCommand(ST77XX_SLPOUT); // 7: hack delay(10); @@ -352,11 +357,8 @@ class ST7789Spi : public OLEDDisplay { sendCommand(ST77XX_INVON); // 10: invert delay(10); - //uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MX; - uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX; - sendCommand(ST77XX_MADCTL); - WriteData(madctl); - delay(10); + // Use landscape mode instead of portrait + landscapeScreen(); setRGB(ST77XX_GREEN); } @@ -364,8 +366,10 @@ class ST7789Spi : public OLEDDisplay { private: void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { - x += (320-displayWidth)/2; - y += (240-displayHeight)/2; + // For landscape orientation (240x135) + x += (320-displayWidth)/2; // Center horizontally in 320 pixels + y += (240-displayHeight)/2; // Center vertically in 240 pixels + uint32_t xa = ((uint32_t)x << 16) | (x + w - 1); uint32_t ya = ((uint32_t)y << 16) | (y + h - 1); From d3a88e9206954ca536b893c647ba177f27d6dfc0 Mon Sep 17 00:00:00 2001 From: JQ Date: Sun, 4 May 2025 21:54:47 -0700 Subject: [PATCH 046/154] T114 Landscape --- src/helpers/ui/ST7789Spi.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/helpers/ui/ST7789Spi.h b/src/helpers/ui/ST7789Spi.h index 93bfa87c..09af6457 100644 --- a/src/helpers/ui/ST7789Spi.h +++ b/src/helpers/ui/ST7789Spi.h @@ -366,9 +366,8 @@ class ST7789Spi : public OLEDDisplay { private: void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { - // For landscape orientation (240x135) - x += (320-displayWidth)/2; // Center horizontally in 320 pixels - y += (240-displayHeight)/2; // Center vertically in 240 pixels + x += (320-displayWidth)/2; + y += (240-displayHeight)/2; uint32_t xa = ((uint32_t)x << 16) | (x + w - 1); uint32_t ya = ((uint32_t)y << 16) | (y + h - 1); From 791da53c7b6799e37d729c015f0687aeefff4953 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 5 May 2025 15:54:31 +1000 Subject: [PATCH 047/154] * ST7789Display: now with SCALE_X,SCALE_Y * fix for GxEPDDisplay --- src/helpers/ui/GxEPDDisplay.h | 3 +-- src/helpers/ui/ST7789Display.cpp | 19 +++++++++++-------- src/helpers/ui/ST7789Display.h | 3 +-- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/helpers/ui/GxEPDDisplay.h b/src/helpers/ui/GxEPDDisplay.h index d772d131..88aecaae 100644 --- a/src/helpers/ui/GxEPDDisplay.h +++ b/src/helpers/ui/GxEPDDisplay.h @@ -29,8 +29,7 @@ class GxEPDDisplay : public DisplayDriver { public: // there is a margin in y... - GxEPDDisplay() : DisplayDriver(200, 200-10), display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)) { - + GxEPDDisplay() : DisplayDriver(128, 64), display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)) { } bool begin(); diff --git a/src/helpers/ui/ST7789Display.cpp b/src/helpers/ui/ST7789Display.cpp index cc522152..d1a4aed3 100644 --- a/src/helpers/ui/ST7789Display.cpp +++ b/src/helpers/ui/ST7789Display.cpp @@ -10,6 +10,9 @@ #define Y_OFFSET 1 // Vertical offset to prevent top row cutoff #endif +#define SCALE_X 1.875f // 240 / 128 +#define SCALE_Y 2.109375f // 135 / 64 + bool ST7789Display::begin() { if(!_isOn) { pinMode(PIN_TFT_VDD_CTL, OUTPUT); @@ -50,13 +53,13 @@ void ST7789Display::startFrame(Color bkg) { void ST7789Display::setTextSize(int sz) { switch(sz) { case 1 : - display.setFont(ArialMT_Plain_10); + display.setFont(ArialMT_Plain_16); break; case 2 : display.setFont(ArialMT_Plain_24); break; default: - display.setFont(ArialMT_Plain_10); + display.setFont(ArialMT_Plain_16); } } @@ -91,8 +94,8 @@ void ST7789Display::setColor(Color c) { } void ST7789Display::setCursor(int x, int y) { - _x = x + X_OFFSET; - _y = y + Y_OFFSET; + _x = x*SCALE_X + X_OFFSET; + _y = y*SCALE_Y + Y_OFFSET; } void ST7789Display::print(const char* str) { @@ -100,19 +103,19 @@ void ST7789Display::print(const char* str) { } void ST7789Display::fillRect(int x, int y, int w, int h) { - display.fillRect(x + X_OFFSET, y + Y_OFFSET, w, h); + display.fillRect(x*SCALE_X + X_OFFSET, y*SCALE_Y + Y_OFFSET, w*SCALE_X, h*SCALE_Y); } void ST7789Display::drawRect(int x, int y, int w, int h) { - display.drawRect(x + X_OFFSET, y + Y_OFFSET, w, h); + display.drawRect(x*SCALE_X + X_OFFSET, y*SCALE_Y + Y_OFFSET, w*SCALE_X, h*SCALE_Y); } void ST7789Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { - display.drawBitmap(x + X_OFFSET, y + Y_OFFSET, w, h, bits); + display.drawBitmap(x*SCALE_X + X_OFFSET, y*SCALE_Y + Y_OFFSET, w, h, bits); } uint16_t ST7789Display::getTextWidth(const char* str) { - return display.getStringWidth(str); + return display.getStringWidth(str) / SCALE_X; } void ST7789Display::endFrame() { diff --git a/src/helpers/ui/ST7789Display.h b/src/helpers/ui/ST7789Display.h index 338399f4..b267a2cb 100644 --- a/src/helpers/ui/ST7789Display.h +++ b/src/helpers/ui/ST7789Display.h @@ -14,9 +14,8 @@ class ST7789Display : public DisplayDriver { bool i2c_probe(TwoWire& wire, uint8_t addr); public: - ST7789Display() : DisplayDriver(240, 135), display(&SPI1, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 240, 135) {_isOn = false;} + ST7789Display() : DisplayDriver(128, 64), display(&SPI1, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 240, 135) {_isOn = false;} -// ST7789Display() : DisplayDriver(135, 240), display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST) { _isOn = false; } bool begin(); bool isOn() override { return _isOn; } From 5780b50a48ab25650fd1507c0ffdce4c880e5b5d Mon Sep 17 00:00:00 2001 From: recrof Date: Mon, 5 May 2025 08:30:12 +0200 Subject: [PATCH 048/154] echo, m1: correct display scalling; all nrf52 boards jsons: added debug.openocd_target --- boards/heltec_t114.json | 3 ++- boards/promicro_nrf52840.json | 5 +++-- boards/t-echo.json | 3 ++- boards/thinknode_m1.json | 3 ++- boards/tracker-t1000-e.json | 3 ++- src/helpers/CustomLR1121.h | 17 +++++++++++++++++ src/helpers/CustomLR1121Wrapper.h | 30 ++++++++++++++++++++++++++++++ src/helpers/ui/GxEPDDisplay.cpp | 4 ++-- src/helpers/ui/GxEPDDisplay.h | 8 ++++---- 9 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 src/helpers/CustomLR1121.h create mode 100644 src/helpers/CustomLR1121Wrapper.h diff --git a/boards/heltec_t114.json b/boards/heltec_t114.json index 6df7266d..86f72e73 100644 --- a/boards/heltec_t114.json +++ b/boards/heltec_t114.json @@ -34,7 +34,8 @@ ], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52.cfg" }, "frameworks": [ "arduino" diff --git a/boards/promicro_nrf52840.json b/boards/promicro_nrf52840.json index 346f0f26..a428ffde 100644 --- a/boards/promicro_nrf52840.json +++ b/boards/promicro_nrf52840.json @@ -8,7 +8,7 @@ "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", "f_cpu": "64000000L", "hwids": [ - [ + [ "0x239A", "0x00B3" ], @@ -51,7 +51,8 @@ ], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52.cfg" }, "frameworks": [ "arduino", diff --git a/boards/t-echo.json b/boards/t-echo.json index 5c2703a3..8deea1bc 100644 --- a/boards/t-echo.json +++ b/boards/t-echo.json @@ -37,7 +37,8 @@ "onboard_tools": [ "jlink" ], - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52.cfg" }, "frameworks": [ "arduino" diff --git a/boards/thinknode_m1.json b/boards/thinknode_m1.json index 0f313063..9f486285 100644 --- a/boards/thinknode_m1.json +++ b/boards/thinknode_m1.json @@ -45,7 +45,8 @@ "onboard_tools": [ "jlink" ], - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52.cfg" }, "frameworks": [ "arduino" diff --git a/boards/tracker-t1000-e.json b/boards/tracker-t1000-e.json index 2be716e2..fc740ac5 100644 --- a/boards/tracker-t1000-e.json +++ b/boards/tracker-t1000-e.json @@ -32,7 +32,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52.cfg" }, "frameworks": ["arduino"], "name": "Seeed T1000-E", diff --git a/src/helpers/CustomLR1121.h b/src/helpers/CustomLR1121.h new file mode 100644 index 00000000..fa8aa871 --- /dev/null +++ b/src/helpers/CustomLR1121.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#define LR1121_IRQ_HAS_PREAMBLE 0b0000000100 // 4 4 valid LoRa header received +#define LR1121_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received + +class CustomLR1121 : public LR1121 { + public: + CustomLR1121(Module *mod) : LR1121(mod) { } + + bool isReceiving() { + uint16_t irq = getIrqStatus(); + bool hasPreamble = ((irq & LR1121_IRQ_HEADER_VALID) && (irq & LR1121_IRQ_HAS_PREAMBLE)); + return hasPreamble; + } +}; \ No newline at end of file diff --git a/src/helpers/CustomLR1121Wrapper.h b/src/helpers/CustomLR1121Wrapper.h new file mode 100644 index 00000000..359aecd4 --- /dev/null +++ b/src/helpers/CustomLR1121Wrapper.h @@ -0,0 +1,30 @@ +#pragma once + +#include "CustomLR1121.h" +#include "RadioLibWrappers.h" + +class CustomLR1121Wrapper : public RadioLibWrapper { +public: + CustomLR1121Wrapper(CustomLR1121& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } + bool isReceiving() override { + if (((CustomLR1121 *)_radio)->isReceiving()) return true; + + idle(); // put lr1121 into standby + // do some basic CAD (blocks for ~12780 micros (on SF 10)!) + bool activity = (((CustomLR1121 *)_radio)->scanChannel() == RADIOLIB_LORA_DETECTED); + if (activity) { + startRecv(); + } else { + idle(); + } + return activity; + } + + void onSendFinished() override { + RadioLibWrapper::onSendFinished(); + _radio->setPreambleLength(8); // overcomes weird issues with small and big pkts + } + + float getLastRSSI() const override { return ((CustomLR1121 *)_radio)->getRSSI(); } + float getLastSNR() const override { return ((CustomLR1121 *)_radio)->getSNR(); } +}; diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index 57a96074..7faaf2c3 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -7,10 +7,10 @@ #ifdef TECHO_ZOOM #define SCALE_X (1.5625f * 1.5f) // 200 / 128 (with 1.5 scale) - #define SCALE_Y (2.9687f * 1.5f) // 190 / 64 (with 1.5 scale) + #define SCALE_Y (1.5625f * 1.5f) // 200 / 128 (with 1.5 scale) #else #define SCALE_X 1.5625f // 200 / 128 - #define SCALE_Y 2.9687f // 190 / 64 + #define SCALE_Y 1.5625f // 200 / 128 #endif bool GxEPDDisplay::begin() { diff --git a/src/helpers/ui/GxEPDDisplay.h b/src/helpers/ui/GxEPDDisplay.h index 88aecaae..01c900ac 100644 --- a/src/helpers/ui/GxEPDDisplay.h +++ b/src/helpers/ui/GxEPDDisplay.h @@ -14,11 +14,11 @@ #define GxEPD2_DISPLAY_CLASS GxEPD2_BW #define GxEPD2_DRIVER_CLASS GxEPD2_150_BN // DEPG0150BN 200x200, SSD1681, (FPC8101), TTGO T5 V2.4.1 -#include // 1.54" b/w +#include // 1.54" b/w #include "DisplayDriver.h" -//GxEPD2_BW display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)); // DEPG0150BN 200x200, SSD1681, TTGO T5 V2.4.1 +//GxEPD2_BW display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)); // DEPG0150BN 200x200, SSD1681, TTGO T5 V2.4.1 class GxEPDDisplay : public DisplayDriver { @@ -29,12 +29,12 @@ class GxEPDDisplay : public DisplayDriver { public: // there is a margin in y... - GxEPDDisplay() : DisplayDriver(128, 64), display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)) { + GxEPDDisplay() : DisplayDriver(128, 128), display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)) { } bool begin(); - bool isOn() override {return _isOn;}; + bool isOn() override {return _isOn;}; void turnOn() override; void turnOff() override; void clear() override; From 310e6c64d41a0555c3eb4f9d3509f112650f4924 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Mon, 5 May 2025 08:34:24 +0200 Subject: [PATCH 049/154] Delete src/helpers/CustomLR1121.h --- src/helpers/CustomLR1121.h | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 src/helpers/CustomLR1121.h diff --git a/src/helpers/CustomLR1121.h b/src/helpers/CustomLR1121.h deleted file mode 100644 index fa8aa871..00000000 --- a/src/helpers/CustomLR1121.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - -#define LR1121_IRQ_HAS_PREAMBLE 0b0000000100 // 4 4 valid LoRa header received -#define LR1121_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received - -class CustomLR1121 : public LR1121 { - public: - CustomLR1121(Module *mod) : LR1121(mod) { } - - bool isReceiving() { - uint16_t irq = getIrqStatus(); - bool hasPreamble = ((irq & LR1121_IRQ_HEADER_VALID) && (irq & LR1121_IRQ_HAS_PREAMBLE)); - return hasPreamble; - } -}; \ No newline at end of file From 81863a599553e2c75b5efe3711ca2a9c246612e7 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Mon, 5 May 2025 08:34:41 +0200 Subject: [PATCH 050/154] Delete src/helpers/CustomLR1121Wrapper.h --- src/helpers/CustomLR1121Wrapper.h | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 src/helpers/CustomLR1121Wrapper.h diff --git a/src/helpers/CustomLR1121Wrapper.h b/src/helpers/CustomLR1121Wrapper.h deleted file mode 100644 index 359aecd4..00000000 --- a/src/helpers/CustomLR1121Wrapper.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "CustomLR1121.h" -#include "RadioLibWrappers.h" - -class CustomLR1121Wrapper : public RadioLibWrapper { -public: - CustomLR1121Wrapper(CustomLR1121& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } - bool isReceiving() override { - if (((CustomLR1121 *)_radio)->isReceiving()) return true; - - idle(); // put lr1121 into standby - // do some basic CAD (blocks for ~12780 micros (on SF 10)!) - bool activity = (((CustomLR1121 *)_radio)->scanChannel() == RADIOLIB_LORA_DETECTED); - if (activity) { - startRecv(); - } else { - idle(); - } - return activity; - } - - void onSendFinished() override { - RadioLibWrapper::onSendFinished(); - _radio->setPreambleLength(8); // overcomes weird issues with small and big pkts - } - - float getLastRSSI() const override { return ((CustomLR1121 *)_radio)->getRSSI(); } - float getLastSNR() const override { return ((CustomLR1121 *)_radio)->getSNR(); } -}; From a39c000f5d993d9fc2a5c334569d6496ce83a4b8 Mon Sep 17 00:00:00 2001 From: Florent de Lamotte Date: Mon, 5 May 2025 16:35:32 +0200 Subject: [PATCH 051/154] fix for set_custom_var --- examples/companion_radio/main.cpp | 1 + variants/t1000-e/platformio.ini | 6 +++--- variants/t1000-e/target.cpp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 92b8e3ea..23985c78 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -1499,6 +1499,7 @@ public: } _serial->writeFrame(out_frame, dp - (char *)out_frame); } else if (cmd_frame[0] == CMD_SET_CUSTOM_VAR && len >= 4) { + cmd_frame[len] = 0; char* sp = (char *) &cmd_frame[1]; char* np = strchr(sp, ':'); // look for separator char if (np) { diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index aac367fe..fd70503b 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -40,9 +40,9 @@ build_flags = ${t1000-e.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 - -D BLE_DEBUG_LOGGING=1 - -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 -D RX_BOOSTED_GAIN=true -D RF_SWITCH_TABLE -D HAS_UI diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index 6a058545..67c571fe 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -187,7 +187,7 @@ const char* T1000SensorManager::getSettingValue(int i) const { bool T1000SensorManager::setSettingValue(const char* name, const char* value) { if (strcmp(name, "gps") == 0) { if (strcmp(value, "0") == 0) { - stop_gps(); // or should this be sleep_gps() ?? + sleep_gps(); // sleep for faster fix ! } else { start_gps(); } From eaea26267ba491993004cf419c164e88e1e6668a Mon Sep 17 00:00:00 2001 From: recrof Date: Mon, 5 May 2025 22:48:21 +0200 Subject: [PATCH 052/154] disable debug flags that were not commented out after debugging --- variants/heltec_v3/platformio.ini | 4 ++-- variants/lilygo_tbeam/platformio.ini | 8 ++++---- variants/picow/platformio.ini | 4 ++-- variants/promicro/platformio.ini | 2 +- variants/rak4631/platformio.ini | 4 ++-- variants/station_g2/platformio.ini | 2 +- variants/xiao_nrf52/platformio.ini | 8 ++++---- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 49fa7434..f51b8c8f 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -31,7 +31,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MESH_PACKET_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} + @@ -142,7 +142,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MESH_PACKET_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} +<../examples/simple_repeater> diff --git a/variants/lilygo_tbeam/platformio.ini b/variants/lilygo_tbeam/platformio.ini index 129d2d32..95b9d372 100644 --- a/variants/lilygo_tbeam/platformio.ini +++ b/variants/lilygo_tbeam/platformio.ini @@ -30,9 +30,9 @@ build_flags = -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 - -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 - -D RADIOLIB_DEBUG_BASIC=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +; -D RADIOLIB_DEBUG_BASIC=1 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 ; -D MESH_PACKET_LOGGING=1 @@ -54,7 +54,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MESH_PACKET_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TBeam.build_src_filter} + diff --git a/variants/picow/platformio.ini b/variants/picow/platformio.ini index 9070099d..45d26b0f 100644 --- a/variants/picow/platformio.ini +++ b/variants/picow/platformio.ini @@ -95,8 +95,8 @@ extends = picow build_flags = ${picow.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=1 - -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 build_src_filter = ${picow.build_src_filter} +<../examples/simple_secure_chat/main.cpp> lib_deps = ${picow.lib_deps} diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index b32dc9c0..e1af5bd3 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -88,7 +88,7 @@ build_flags = ${Faketec.build_flags} -D BLE_DEBUG_LOGGING=1 -D ENABLE_PRIVATE_KEY_EXPORT=1 -D ENABLE_PRIVATE_KEY_IMPORT=1 - -D MESH_PACKET_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Faketec.build_src_filter} + diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 3f40447f..86d3ba4d 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -98,8 +98,8 @@ build_flags = ${rak4631.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=1 - -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 build_src_filter = ${rak4631.build_src_filter} +<../examples/simple_secure_chat/main.cpp> lib_deps = diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index 49e97b4b..deb80a75 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -30,7 +30,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MESH_PACKET_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Station_G2.build_src_filter} +<../examples/simple_repeater> diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index 6fa29512..43d13daf 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -51,8 +51,8 @@ build_flags = -D BLE_PIN_CODE=123456 ; -D BLE_DEBUG_LOGGING=1 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 - -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 build_src_filter = ${Xiao_nrf52.build_src_filter} + +<../examples/companion_radio/main.cpp> @@ -78,7 +78,7 @@ lib_deps = [env:Xiao_nrf52_alt_pinout_companion_radio_ble] extends = env:Xiao_nrf52_companion_radio_ble -build_flags = +build_flags = ${env:Xiao_nrf52_companion_radio_ble.build_flags} -D SX1262_XIAO_S3_VARIANT @@ -97,7 +97,7 @@ build_src_filter = ${Xiao_nrf52.build_src_filter} [env:Xiao_nrf52_alt_pinout_repeater] extends = env:Xiao_nrf52_repeater -build_flags = +build_flags = ${env:Xiao_nrf52_repeater.build_flags} -D SX1262_XIAO_S3_VARIANT From f855523481fd1064cdf28ce2a6999861a556c465 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 6 May 2025 11:51:51 +1000 Subject: [PATCH 053/154] * refactor: removed mesh::Mesh dependency from CommonCLI --- examples/simple_repeater/main.cpp | 4 +++- examples/simple_room_server/main.cpp | 4 +++- src/helpers/CommonCLI.cpp | 2 +- src/helpers/CommonCLI.h | 9 +++++---- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 3926cfec..c926a786 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -540,7 +540,7 @@ protected: public: MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, this, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) + _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { memset(known_clients, 0, sizeof(known_clients)); next_local_advert = next_flood_advert = 0; @@ -685,6 +685,8 @@ public: *dp = 0; // null terminator } + const uint8_t* getSelfIdPubKey() { return self_id.pub_key; } + void loop() { mesh::Mesh::loop(); diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index eabcf98e..dddef501 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -692,7 +692,7 @@ protected: public: MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, this, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) + _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { next_local_advert = next_flood_advert = 0; _logging = false; @@ -821,6 +821,8 @@ public: strcpy(reply, "not supported"); } + const uint8_t* getSelfIdPubKey() { return self_id.pub_key; } + void loop() { mesh::Mesh::loop(); diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index acf12574..7c0377e6 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -208,7 +208,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "> %s", StrHelper::ftoa(_prefs->freq)); } else if (memcmp(config, "public.key", 10) == 0) { strcpy(reply, "> "); - mesh::Utils::toHex(&reply[2], _mesh->self_id.pub_key, PUB_KEY_SIZE); + mesh::Utils::toHex(&reply[2], _callbacks->getSelfIdPubKey(), PUB_KEY_SIZE); } else if (memcmp(config, "role", 4) == 0) { sprintf(reply, "> %s", _callbacks->getRole()); } else { diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 27fd1c08..8bd3d150 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -41,16 +41,17 @@ public: virtual void dumpLogFile() = 0; virtual void setTxPower(uint8_t power_dbm) = 0; virtual void formatNeighborsReply(char *reply) = 0; + virtual const uint8_t* getSelfIdPubKey() = 0; }; class CommonCLI { - mesh::Mesh* _mesh; + mesh::RTCClock* _rtc; NodePrefs* _prefs; CommonCLICallbacks* _callbacks; mesh::MainBoard* _board; char tmp[80]; - mesh::RTCClock* getRTCClock() { return _mesh->getRTCClock(); } + mesh::RTCClock* getRTCClock() { return _rtc; } void savePrefs() { _callbacks->savePrefs(); } void checkAdvertInterval(); @@ -58,8 +59,8 @@ class CommonCLI { void loadPrefsInt(FILESYSTEM* _fs, const char* filename); public: - CommonCLI(mesh::MainBoard& board, mesh::Mesh* mesh, NodePrefs* prefs, CommonCLICallbacks* callbacks) - : _board(&board), _mesh(mesh), _prefs(prefs), _callbacks(callbacks) { } + CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, NodePrefs* prefs, CommonCLICallbacks* callbacks) + : _board(&board), _rtc(&rtc), _prefs(prefs), _callbacks(callbacks) { } void loadPrefs(FILESYSTEM* _fs); void savePrefs(FILESYSTEM* _fs); From 941d2d5c13fec7abe2c32e68673a103d909d77ad Mon Sep 17 00:00:00 2001 From: JQ Date: Tue, 6 May 2025 20:47:14 -0700 Subject: [PATCH 054/154] fixing scaling of bitmaps for 7789 display --- src/helpers/ui/ST7789Display.cpp | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/helpers/ui/ST7789Display.cpp b/src/helpers/ui/ST7789Display.cpp index d1a4aed3..f0ae8557 100644 --- a/src/helpers/ui/ST7789Display.cpp +++ b/src/helpers/ui/ST7789Display.cpp @@ -111,7 +111,35 @@ void ST7789Display::drawRect(int x, int y, int w, int h) { } void ST7789Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { - display.drawBitmap(x*SCALE_X + X_OFFSET, y*SCALE_Y + Y_OFFSET, w, h, bits); + // Calculate the base position in display coordinates + uint16_t startX = x * SCALE_X + X_OFFSET; + uint16_t startY = y * SCALE_Y + Y_OFFSET; + + // Width in bytes for bitmap processing + uint16_t widthInBytes = (w + 7) / 8; + + // Process the bitmap row by row + for (uint16_t by = 0; by < h; by++) { + // Scan across the row bit by bit + for (uint16_t bx = 0; bx < w; bx++) { + // Get the current bit + uint16_t byteOffset = (by * widthInBytes) + (bx / 8); + uint8_t bitMask = 0x80 >> (bx & 7); + bool bitSet = pgm_read_byte(bits + byteOffset) & bitMask; + + // If the bit is set, draw pixels using setPixel with scaling + if (bitSet) { + // Apply scaling - draw multiple pixels for each original bitmap pixel + for (uint16_t scaleY = 0; scaleY <= (uint16_t)SCALE_Y; scaleY++) { + for (uint16_t scaleX = 0; scaleX <= (uint16_t)SCALE_X; scaleX++) { + uint16_t pixelX = startX + (uint16_t)(bx * SCALE_X) + scaleX; + uint16_t pixelY = startY + (uint16_t)(by * SCALE_Y) + scaleY; + display.setPixel(pixelX, pixelY); + } + } + } + } + } } uint16_t ST7789Display::getTextWidth(const char* str) { From d04eda9f16932b7c973a6ffa324686755b2f59ec Mon Sep 17 00:00:00 2001 From: liamcottle Date: Wed, 7 May 2025 20:26:15 +1200 Subject: [PATCH 055/154] enable neighbours feature for all repeater variants --- variants/generic-e22/platformio.ini | 2 ++ variants/generic_espnow/platformio.ini | 1 + variants/heltec_v2/platformio.ini | 1 + variants/heltec_v3/platformio.ini | 2 ++ variants/lilygo_t3s3/platformio.ini | 1 + variants/lilygo_tbeam/platformio.ini | 1 + variants/lilygo_tbeam_SX1262/platformio.ini | 1 + variants/lilygo_tbeam_supreme_SX1262/platformio.ini | 1 + variants/lilygo_tlora_v2_1/platformio.ini | 1 + variants/picow/platformio.ini | 1 + variants/promicro/platformio.ini | 2 ++ variants/rak4631/platformio.ini | 1 + variants/station_g2/platformio.ini | 1 + variants/t114/platformio.ini | 1 + variants/techo/platformio.ini | 1 + variants/thinknode_m1/platformio.ini | 1 + variants/xiao_c3/platformio.ini | 1 + variants/xiao_nrf52/platformio.ini | 1 + variants/xiao_s3_wio/platformio.ini | 1 + 19 files changed, 22 insertions(+) diff --git a/variants/generic-e22/platformio.ini b/variants/generic-e22/platformio.ini index 6f2830b1..4aa195d9 100644 --- a/variants/generic-e22/platformio.ini +++ b/variants/generic-e22/platformio.ini @@ -40,6 +40,7 @@ build_flags = -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 lib_deps = @@ -59,6 +60,7 @@ build_flags = -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 lib_deps = diff --git a/variants/generic_espnow/platformio.ini b/variants/generic_espnow/platformio.ini index 50eede9c..8a033a62 100644 --- a/variants/generic_espnow/platformio.ini +++ b/variants/generic_espnow/platformio.ini @@ -42,6 +42,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 build_src_filter = ${Generic_ESPNOW.build_src_filter} +<../examples/simple_repeater/main.cpp> lib_deps = diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index bb1a8038..e2a6e09a 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/heltec_v2/platformio.ini @@ -28,6 +28,7 @@ build_flags = -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 = ${Heltec_lora32_v2.build_src_filter} diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index f51b8c8f..8452fbf5 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -31,6 +31,7 @@ build_flags = -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 = ${Heltec_lora32_v3.build_src_filter} @@ -142,6 +143,7 @@ build_flags = -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 = ${Heltec_lora32_v3.build_src_filter} diff --git a/variants/lilygo_t3s3/platformio.ini b/variants/lilygo_t3s3/platformio.ini index c857d51d..4e1c22ce 100644 --- a/variants/lilygo_t3s3/platformio.ini +++ b/variants/lilygo_t3s3/platformio.ini @@ -42,6 +42,7 @@ build_flags = -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 = ${LilyGo_T3S3_sx1262.build_src_filter} diff --git a/variants/lilygo_tbeam/platformio.ini b/variants/lilygo_tbeam/platformio.ini index 95b9d372..15c9919d 100644 --- a/variants/lilygo_tbeam/platformio.ini +++ b/variants/lilygo_tbeam/platformio.ini @@ -54,6 +54,7 @@ build_flags = -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 = ${LilyGo_TBeam.build_src_filter} diff --git a/variants/lilygo_tbeam_SX1262/platformio.ini b/variants/lilygo_tbeam_SX1262/platformio.ini index 83096428..c16548e9 100644 --- a/variants/lilygo_tbeam_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_SX1262/platformio.ini @@ -58,6 +58,7 @@ build_flags = -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 = ${LilyGo_TBeam_SX1262.build_src_filter} diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index 57cc68db..e26b0c08 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -27,6 +27,7 @@ build_flags = -D ADVERT_LAT=0 -D ADVERT_LON=0 -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index 078efc9c..bfd3c60d 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -42,6 +42,7 @@ build_flags = -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 ; -D CORE_DEBUG_LEVEL=3 diff --git a/variants/picow/platformio.ini b/variants/picow/platformio.ini index 45d26b0f..ec27e6ee 100644 --- a/variants/picow/platformio.ini +++ b/variants/picow/platformio.ini @@ -25,6 +25,7 @@ build_flags = ${picow.build_flags} -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 = ${picow.build_src_filter} diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index e1af5bd3..b2b7465c 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -31,6 +31,7 @@ build_flags = -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 lib_deps = ${Faketec.lib_deps} @@ -121,6 +122,7 @@ build_src_filter = ${ProMicroLLCC68.build_src_filter} build_flags = ${ProMicroLLCC68.build_flags} -D ADVERT_NAME='"ProMicroLLCC68 Repeater"' -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = ${ProMicroLLCC68.lib_deps} diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 86d3ba4d..85f59bfa 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -31,6 +31,7 @@ build_flags = -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 = ${rak4631.build_src_filter} diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index deb80a75..b81f25e4 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -30,6 +30,7 @@ build_flags = -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 = ${Station_G2.build_src_filter} diff --git a/variants/t114/platformio.ini b/variants/t114/platformio.ini index 232dfb0b..0f1047d6 100644 --- a/variants/t114/platformio.ini +++ b/variants/t114/platformio.ini @@ -39,6 +39,7 @@ build_flags = -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 diff --git a/variants/techo/platformio.ini b/variants/techo/platformio.ini index f3c2f973..b2a9af07 100644 --- a/variants/techo/platformio.ini +++ b/variants/techo/platformio.ini @@ -37,6 +37,7 @@ build_flags = -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 diff --git a/variants/thinknode_m1/platformio.ini b/variants/thinknode_m1/platformio.ini index 00f7d768..aefafe85 100644 --- a/variants/thinknode_m1/platformio.ini +++ b/variants/thinknode_m1/platformio.ini @@ -36,6 +36,7 @@ build_flags = -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 = ${ThinkNode_M1.build_src_filter} diff --git a/variants/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index 7e89b377..7af43b0e 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -53,6 +53,7 @@ build_flags = -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 lib_deps = diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index 43d13daf..ce515b24 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -90,6 +90,7 @@ build_flags = -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 = ${Xiao_nrf52.build_src_filter} diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 5073efd8..bc28c285 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -35,6 +35,7 @@ build_flags = -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 lib_deps = From f18a3b78ad2e21546cc806df0d5be4bfc65e9faa Mon Sep 17 00:00:00 2001 From: liamcottle Date: Wed, 7 May 2025 20:53:59 +1200 Subject: [PATCH 056/154] ble pin must be zero or a valid 6 digit pin --- examples/companion_radio/main.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 34e10343..a3eb1b6d 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -1490,9 +1490,20 @@ public: writeErrFrame(ERR_CODE_TABLE_FULL); } } else if (cmd_frame[0] == CMD_SET_DEVICE_PIN && len >= 5) { - memcpy(&_prefs.ble_pin, &cmd_frame[1], 4); - savePrefs(); - writeOKFrame(); + + // get pin from command frame + uint32_t pin; + memcpy(&pin, &cmd_frame[1], 4); + + // ensure pin is zero, or a valid 6 digit pin + if(pin == 0 || (pin >= 100000 && pin <= 999999)){ + _prefs.ble_pin = pin; + savePrefs(); + writeOKFrame(); + } else { + writeErrFrame(ERR_CODE_ILLEGAL_ARG); + } + } else if (cmd_frame[0] == CMD_GET_CUSTOM_VARS) { out_frame[0] = RESP_CODE_CUSTOM_VARS; char* dp = (char *) &out_frame[1]; From e076e797e68ff9099f1ed22b02c3194f899e04ef Mon Sep 17 00:00:00 2001 From: seagull9000 Date: Wed, 7 May 2025 21:40:27 +1200 Subject: [PATCH 057/154] Heltec Wireless Tracker support --- examples/companion_radio/main.cpp | 4 ++- src/helpers/HeltecV3Board.h | 7 +++-- variants/heltec_v3/platformio.ini | 43 ++++++++++++++++++++++++++++--- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 88652697..d43f47bb 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -59,7 +59,9 @@ #ifdef DISPLAY_CLASS #include "UITask.h" - #ifdef ST7789 + #ifdef ST7735 + #include + #elif ST7789 #include #elif defined(HAS_GxEPD) #include diff --git a/src/helpers/HeltecV3Board.h b/src/helpers/HeltecV3Board.h index 605bfe21..fd6352ec 100644 --- a/src/helpers/HeltecV3Board.h +++ b/src/helpers/HeltecV3Board.h @@ -3,6 +3,7 @@ #include // LoRa radio module pins for Heltec V3 +// Also for Heltec Wireless Tracker #define P_LORA_DIO_1 14 #define P_LORA_NSS 8 #define P_LORA_RESET RADIOLIB_NC @@ -13,10 +14,12 @@ // built-ins #define PIN_VBAT_READ 1 -#define PIN_ADC_CTRL 37 +#ifndef PIN_ADC_CTRL // set in platformio.ini for Heltec Wireless Tracker (2) + #define PIN_ADC_CTRL 37 +#endif #define PIN_ADC_CTRL_ACTIVE LOW #define PIN_ADC_CTRL_INACTIVE HIGH -#define PIN_LED_BUILTIN 35 +//#define PIN_LED_BUILTIN 35 #define PIN_VEXT_EN 36 #include "ESP32Board.h" diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 49fa7434..4644f53a 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -8,9 +8,9 @@ build_flags = -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 - -D P_LORA_TX_LED=35 - -D PIN_BOARD_SDA=17 - -D PIN_BOARD_SCL=18 + -D P_LORA_TX_LED=35 ; Heltec WT redefines this + -D PIN_BOARD_SDA=17 ; Heltec WT redefines this + -D PIN_BOARD_SCL=18 ; Heltec WT redefines this -D PIN_USER_BTN=0 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 @@ -185,3 +185,40 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter} lib_deps = ${Heltec_lora32_v3.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:Heltec_Wireless_Tracker_companion_radio_ble] +extends = Heltec_lora32_v3 +build_flags = + ${Heltec_lora32_v3.build_flags} + -I src/helpers/ui + ; -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for debugging + -D ST7735 + -D PIN_ADC_CTRL=2 ; redefines the V3 pin + -D DISPLAY_ROTATION=1 + -D DISPLAY_CLASS=ST7735Display + -D USE_PIN_TFT=1 + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 ; HWT will use display for pin +; -D BLE_DEBUG_LOGGING=1 + -D P_LORA_TX_LED=18 + -D PIN_TFT_SDA=42 ; SDIN + -D PIN_TFT_SCL=41 ; SCLK + -D PIN_TFT_DC=40 ; RS (register select) + -D PIN_TFT_RST=39 ; RES + -D PIN_TFT_CS=38 + -D PIN_TFT_VDD_CTL=3 ; Vext is connected to VDD which is also connected to LEDA + -D PIN_TFT_LEDA_CTL=21 ; LEDK (switches on/off via mosfet to create the ground) +; -D ENABLE_PRIVATE_KEY_IMPORT=1 +; -D ENABLE_PRIVATE_KEY_EXPORT=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + + +<../examples/companion_radio> + + +lib_deps = + ${Heltec_lora32_v3.lib_deps} + densaugeo/base64 @ ~1.4.0 + adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + \ No newline at end of file From c2ef0a3f0b7fa2dad3f5a413565aed05cc183481 Mon Sep 17 00:00:00 2001 From: seagull9000 Date: Wed, 7 May 2025 21:42:29 +1200 Subject: [PATCH 058/154] Heltec Wireless Tracker support --- src/helpers/ui/ST7735Display.cpp | 125 +++++++++++++++++++++++++++++++ src/helpers/ui/ST7735Display.h | 36 +++++++++ 2 files changed, 161 insertions(+) create mode 100644 src/helpers/ui/ST7735Display.cpp create mode 100644 src/helpers/ui/ST7735Display.h diff --git a/src/helpers/ui/ST7735Display.cpp b/src/helpers/ui/ST7735Display.cpp new file mode 100644 index 00000000..45fd2a39 --- /dev/null +++ b/src/helpers/ui/ST7735Display.cpp @@ -0,0 +1,125 @@ +#ifdef ST7735 + +#include "ST7735Display.h" + +#ifndef DISPLAY_ROTATION + #define DISPLAY_ROTATION 2 +#endif + + +bool ST7735Display::i2c_probe(TwoWire& wire, uint8_t addr) { + return true; +/* + wire.beginTransmission(addr); + uint8_t error = wire.endTransmission(); + return (error == 0); +*/ +} + +bool ST7735Display::begin() { + if(!_isOn) { + pinMode(PIN_TFT_VDD_CTL, OUTPUT); + pinMode(PIN_TFT_LEDA_CTL, OUTPUT); + digitalWrite(PIN_TFT_VDD_CTL, HIGH); + digitalWrite(PIN_TFT_LEDA_CTL, HIGH); + digitalWrite(PIN_TFT_RST, HIGH); + + display.initR(INITR_MINI160x80_PLUGIN); + display.setRotation(DISPLAY_ROTATION); + display.setSPISpeed(40000000); + display.fillScreen(ST77XX_BLACK); + display.setTextColor(ST77XX_WHITE); + display.setTextSize(2); + display.cp437(true); // Use full 256 char 'Code Page 437' font + + _isOn = true; + } + return true; +} + +void ST7735Display::turnOn() { + + ST7735Display::begin(); + +} + +void ST7735Display::turnOff() { + digitalWrite(PIN_TFT_VDD_CTL, HIGH); + digitalWrite(PIN_TFT_LEDA_CTL, HIGH); + digitalWrite(PIN_TFT_RST, LOW); + digitalWrite(PIN_TFT_VDD_CTL, LOW); + digitalWrite(PIN_TFT_LEDA_CTL, LOW); + _isOn = false; +} + +void ST7735Display::clear() { + //Serial.println("DBG: display.Clear"); + display.fillScreen(ST77XX_BLACK); +} + +void ST7735Display::startFrame(Color bkg) { + display.fillScreen(0x00); + display.setTextColor(ST77XX_WHITE); + display.setTextSize(1); // This one affects size of Please wait... message + display.cp437(true); // Use full 256 char 'Code Page 437' font +} + +void ST7735Display::setTextSize(int sz) { + display.setTextSize(sz); +} + +void ST7735Display::setColor(Color c) { + switch (c) { + case DisplayDriver::DARK : + _color = ST77XX_BLACK; + break; + case DisplayDriver::LIGHT : + _color = ST77XX_WHITE; + break; + case DisplayDriver::RED : + _color = ST77XX_RED; + break; + case DisplayDriver::GREEN : + _color = ST77XX_GREEN; + break; + case DisplayDriver::BLUE : + _color = ST77XX_BLUE; + break; + case DisplayDriver::YELLOW : + _color = ST77XX_YELLOW; + break; + case DisplayDriver::ORANGE : + _color = ST77XX_ORANGE; + break; + default: + _color = ST77XX_WHITE; + break; + } + display.setTextColor(_color); +} + +void ST7735Display::setCursor(int x, int y) { + display.setCursor(x, y); +} + +void ST7735Display::print(const char* str) { + display.print(str); +} + +void ST7735Display::fillRect(int x, int y, int w, int h) { + display.fillRect(x, y, w, h, _color); +} + +void ST7735Display::drawRect(int x, int y, int w, int h) { + display.drawRect(x, y, w, h, _color); +} + +void ST7735Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { + display.drawBitmap(x, y, bits, w, h, _color); +} + +void ST7735Display::endFrame() { + // display.display(); +} + +#endif \ No newline at end of file diff --git a/src/helpers/ui/ST7735Display.h b/src/helpers/ui/ST7735Display.h new file mode 100644 index 00000000..d456b477 --- /dev/null +++ b/src/helpers/ui/ST7735Display.h @@ -0,0 +1,36 @@ +#pragma once + +#include "DisplayDriver.h" +#include +#include +#include +#include + +class ST7735Display : public DisplayDriver { + Adafruit_ST7735 display; + bool _isOn; + uint16_t _color; + + bool i2c_probe(TwoWire& wire, uint8_t addr); +public: +#ifdef USE_PIN_TFT + ST7735Display() : DisplayDriver(80, 160), display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST) { _isOn = false; } +#else + ST7735Display() : DisplayDriver(80, 160), display(&SPI1, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST) { _isOn = false; } +#endif + bool begin(); + + bool isOn() override { return _isOn; } + void turnOn() override; + void turnOff() override; + void clear() override; + void startFrame(Color bkg = DARK) override; + void setTextSize(int sz) override; + void setColor(Color c) override; + void setCursor(int x, int y) override; + void print(const char* str) override; + void fillRect(int x, int y, int w, int h) override; + void drawRect(int x, int y, int w, int h) override; + void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override; + void endFrame() override; +}; From 94db70d511291d9ef0474fda2d3d3c50fbe697c9 Mon Sep 17 00:00:00 2001 From: JQ Date: Wed, 7 May 2025 18:14:56 -0700 Subject: [PATCH 059/154] new implementation --- src/helpers/ui/ST7789Display.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/helpers/ui/ST7789Display.cpp b/src/helpers/ui/ST7789Display.cpp index f0ae8557..0deebe3f 100644 --- a/src/helpers/ui/ST7789Display.cpp +++ b/src/helpers/ui/ST7789Display.cpp @@ -120,23 +120,27 @@ void ST7789Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { // Process the bitmap row by row for (uint16_t by = 0; by < h; by++) { + // Calculate the target y-coordinates for this logical row + int y1 = startY + (int)(by * SCALE_Y); + int y2 = startY + (int)((by + 1) * SCALE_Y); + int block_h = y2 - y1; + // Scan across the row bit by bit for (uint16_t bx = 0; bx < w; bx++) { + // Calculate the target x-coordinates for this logical column + int x1 = startX + (int)(bx * SCALE_X); + int x2 = startX + (int)((bx + 1) * SCALE_X); + int block_w = x2 - x1; + // Get the current bit uint16_t byteOffset = (by * widthInBytes) + (bx / 8); uint8_t bitMask = 0x80 >> (bx & 7); bool bitSet = pgm_read_byte(bits + byteOffset) & bitMask; - // If the bit is set, draw pixels using setPixel with scaling + // If the bit is set, draw a block of pixels if (bitSet) { - // Apply scaling - draw multiple pixels for each original bitmap pixel - for (uint16_t scaleY = 0; scaleY <= (uint16_t)SCALE_Y; scaleY++) { - for (uint16_t scaleX = 0; scaleX <= (uint16_t)SCALE_X; scaleX++) { - uint16_t pixelX = startX + (uint16_t)(bx * SCALE_X) + scaleX; - uint16_t pixelY = startY + (uint16_t)(by * SCALE_Y) + scaleY; - display.setPixel(pixelX, pixelY); - } - } + // Draw the block as a filled rectangle + display.fillRect(x1, y1, block_w, block_h); } } } From 7a7f43692104baf03a923a8ae6bfb686bafecd57 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 8 May 2025 12:42:28 +1000 Subject: [PATCH 060/154] * Heltec Wireless Tracker fixes: getTextWidth() missing, PIN_BOARD_SDA/SCL --- src/helpers/ui/ST7735Display.cpp | 7 +++++++ src/helpers/ui/ST7735Display.h | 1 + variants/heltec_v3/platformio.ini | 2 ++ 3 files changed, 10 insertions(+) diff --git a/src/helpers/ui/ST7735Display.cpp b/src/helpers/ui/ST7735Display.cpp index 45fd2a39..15d41407 100644 --- a/src/helpers/ui/ST7735Display.cpp +++ b/src/helpers/ui/ST7735Display.cpp @@ -118,6 +118,13 @@ void ST7735Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { display.drawBitmap(x, y, bits, w, h, _color); } +uint16_t ST7735Display::getTextWidth(const char* str) { + int16_t x1, y1; + uint16_t w, h; + display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); + return w; +} + void ST7735Display::endFrame() { // display.display(); } diff --git a/src/helpers/ui/ST7735Display.h b/src/helpers/ui/ST7735Display.h index d456b477..efde213d 100644 --- a/src/helpers/ui/ST7735Display.h +++ b/src/helpers/ui/ST7735Display.h @@ -32,5 +32,6 @@ public: void fillRect(int x, int y, int w, int h) override; void drawRect(int x, int y, int w, int h) override; void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override; + uint16_t getTextWidth(const char* str) override; void endFrame() override; }; diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 82645b4a..47a23754 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -196,6 +196,8 @@ build_flags = ; -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for debugging -D ST7735 -D PIN_ADC_CTRL=2 ; redefines the V3 pin + -D PIN_BOARD_SDA=45 + -D PIN_BOARD_SCL=46 -D DISPLAY_ROTATION=1 -D DISPLAY_CLASS=ST7735Display -D USE_PIN_TFT=1 From 60b7897665c9dc692b339ede282f2062de8cf1c4 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 8 May 2025 12:48:34 +1000 Subject: [PATCH 061/154] * ST7735Display: now applies SCALE_X, SCALE_Y --- src/helpers/ui/ST7735Display.cpp | 12 +++++++----- src/helpers/ui/ST7735Display.h | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/helpers/ui/ST7735Display.cpp b/src/helpers/ui/ST7735Display.cpp index 15d41407..bb3c6473 100644 --- a/src/helpers/ui/ST7735Display.cpp +++ b/src/helpers/ui/ST7735Display.cpp @@ -6,6 +6,8 @@ #define DISPLAY_ROTATION 2 #endif +#define SCALE_X 1.25f // 160 / 128 +#define SCALE_Y 1.25f // 80 / 64 bool ST7735Display::i2c_probe(TwoWire& wire, uint8_t addr) { return true; @@ -99,7 +101,7 @@ void ST7735Display::setColor(Color c) { } void ST7735Display::setCursor(int x, int y) { - display.setCursor(x, y); + display.setCursor(x*SCALE_X, y*SCALE_Y); } void ST7735Display::print(const char* str) { @@ -107,22 +109,22 @@ void ST7735Display::print(const char* str) { } void ST7735Display::fillRect(int x, int y, int w, int h) { - display.fillRect(x, y, w, h, _color); + display.fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _color); } void ST7735Display::drawRect(int x, int y, int w, int h) { - display.drawRect(x, y, w, h, _color); + display.drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _color); } void ST7735Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { - display.drawBitmap(x, y, bits, w, h, _color); + display.drawBitmap(x*SCALE_X, y*SCALE_Y, bits, w, h, _color); } uint16_t ST7735Display::getTextWidth(const char* str) { int16_t x1, y1; uint16_t w, h; display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); - return w; + return w / SCALE_X; } void ST7735Display::endFrame() { diff --git a/src/helpers/ui/ST7735Display.h b/src/helpers/ui/ST7735Display.h index efde213d..8e9bd04c 100644 --- a/src/helpers/ui/ST7735Display.h +++ b/src/helpers/ui/ST7735Display.h @@ -14,9 +14,9 @@ class ST7735Display : public DisplayDriver { bool i2c_probe(TwoWire& wire, uint8_t addr); public: #ifdef USE_PIN_TFT - ST7735Display() : DisplayDriver(80, 160), display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST) { _isOn = false; } + ST7735Display() : DisplayDriver(128, 64), display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST) { _isOn = false; } #else - ST7735Display() : DisplayDriver(80, 160), display(&SPI1, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST) { _isOn = false; } + ST7735Display() : DisplayDriver(128, 64), display(&SPI1, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST) { _isOn = false; } #endif bool begin(); From 98f178510480a40a98f544348633ba429b06c483 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 8 May 2025 13:23:53 +1000 Subject: [PATCH 062/154] * refactor: LocationProvider classes moved to src/helpers/sensors * refactor: Heltec_Wireless_Tracker* env moved to new variants/heltec_tracker dir --- .../helpers/sensors}/LocationProvider.h | 0 .../sensors}/MicroNMEALocationProvider.h | 0 variants/heltec_tracker/platformio.ini | 58 ++++++++++++++ variants/heltec_tracker/target.cpp | 76 +++++++++++++++++++ variants/heltec_tracker/target.h | 20 +++++ variants/heltec_v3/platformio.ini | 45 +---------- variants/t1000-e/target.cpp | 1 + variants/t1000-e/target.h | 3 +- 8 files changed, 159 insertions(+), 44 deletions(-) rename {variants/t1000-e => src/helpers/sensors}/LocationProvider.h (100%) rename {variants/t1000-e => src/helpers/sensors}/MicroNMEALocationProvider.h (100%) create mode 100644 variants/heltec_tracker/platformio.ini create mode 100644 variants/heltec_tracker/target.cpp create mode 100644 variants/heltec_tracker/target.h diff --git a/variants/t1000-e/LocationProvider.h b/src/helpers/sensors/LocationProvider.h similarity index 100% rename from variants/t1000-e/LocationProvider.h rename to src/helpers/sensors/LocationProvider.h diff --git a/variants/t1000-e/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h similarity index 100% rename from variants/t1000-e/MicroNMEALocationProvider.h rename to src/helpers/sensors/MicroNMEALocationProvider.h diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini new file mode 100644 index 00000000..7b37b810 --- /dev/null +++ b/variants/heltec_tracker/platformio.ini @@ -0,0 +1,58 @@ +[Heltec_tracker_base] +extends = esp32_base +board = esp32-s3-devkitc-1 +build_flags = + ${esp32_base.build_flags} + -I variants/heltec_tracker + -D HELTEC_LORA_V3 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D P_LORA_TX_LED=18 + -D PIN_BOARD_SDA=45 + -D PIN_BOARD_SCL=46 + -D PIN_USER_BTN=0 + -D PIN_ADC_CTRL=2 + -D PIN_TFT_SDA=42 ; SDIN + -D PIN_TFT_SCL=41 ; SCLK + -D PIN_TFT_DC=40 ; RS (register select) + -D PIN_TFT_RST=39 ; RES + -D PIN_TFT_CS=38 + -D USE_PIN_TFT=1 + -D PIN_TFT_VDD_CTL=3 ; Vext is connected to VDD which is also connected to LEDA + -D PIN_TFT_LEDA_CTL=21 ; LEDK (switches on/off via mosfet to create the ground) + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=130.0f ; for best TX power! + -D SX126X_RX_BOOSTED_GAIN=1 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/heltec_tracker> +lib_deps = + ${esp32_base.lib_deps} + +[env:Heltec_Wireless_Tracker_companion_radio_ble] +extends = Heltec_tracker_base +build_flags = + ${Heltec_tracker_base.build_flags} + -I src/helpers/ui + ; -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for debugging + -D ST7735 + -D DISPLAY_ROTATION=1 + -D DISPLAY_CLASS=ST7735Display + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 ; HWT will use display for pin +; -D BLE_DEBUG_LOGGING=1 +; -D ENABLE_PRIVATE_KEY_IMPORT=1 +; -D ENABLE_PRIVATE_KEY_EXPORT=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_base.build_src_filter} + + + +<../examples/companion_radio> + + +lib_deps = + ${Heltec_tracker_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + \ No newline at end of file diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp new file mode 100644 index 00000000..27e0ebf9 --- /dev/null +++ b/variants/heltec_tracker/target.cpp @@ -0,0 +1,76 @@ +#include +#include "target.h" + +HeltecV3Board 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; + +#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); + +#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 +} diff --git a/variants/heltec_tracker/target.h b/variants/heltec_tracker/target.h new file mode 100644 index 00000000..b6ab2577 --- /dev/null +++ b/variants/heltec_tracker/target.h @@ -0,0 +1,20 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include + +extern HeltecV3Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock 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(); diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 47a23754..8452fbf5 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -8,9 +8,9 @@ build_flags = -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 - -D P_LORA_TX_LED=35 ; Heltec WT redefines this - -D PIN_BOARD_SDA=17 ; Heltec WT redefines this - -D PIN_BOARD_SCL=18 ; Heltec WT redefines this + -D P_LORA_TX_LED=35 + -D PIN_BOARD_SDA=17 + -D PIN_BOARD_SCL=18 -D PIN_USER_BTN=0 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 @@ -187,42 +187,3 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter} lib_deps = ${Heltec_lora32_v3.lib_deps} densaugeo/base64 @ ~1.4.0 - -[env:Heltec_Wireless_Tracker_companion_radio_ble] -extends = Heltec_lora32_v3 -build_flags = - ${Heltec_lora32_v3.build_flags} - -I src/helpers/ui - ; -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for debugging - -D ST7735 - -D PIN_ADC_CTRL=2 ; redefines the V3 pin - -D PIN_BOARD_SDA=45 - -D PIN_BOARD_SCL=46 - -D DISPLAY_ROTATION=1 - -D DISPLAY_CLASS=ST7735Display - -D USE_PIN_TFT=1 - -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=8 - -D BLE_PIN_CODE=123456 ; HWT will use display for pin -; -D BLE_DEBUG_LOGGING=1 - -D P_LORA_TX_LED=18 - -D PIN_TFT_SDA=42 ; SDIN - -D PIN_TFT_SCL=41 ; SCLK - -D PIN_TFT_DC=40 ; RS (register select) - -D PIN_TFT_RST=39 ; RES - -D PIN_TFT_CS=38 - -D PIN_TFT_VDD_CTL=3 ; Vext is connected to VDD which is also connected to LEDA - -D PIN_TFT_LEDA_CTL=21 ; LEDK (switches on/off via mosfet to create the ground) -; -D ENABLE_PRIVATE_KEY_IMPORT=1 -; -D ENABLE_PRIVATE_KEY_EXPORT=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v3.build_src_filter} - + - +<../examples/companion_radio> - + -lib_deps = - ${Heltec_lora32_v3.lib_deps} - densaugeo/base64 @ ~1.4.0 - adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 - \ No newline at end of file diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index 67c571fe..f4bb75f6 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -1,5 +1,6 @@ #include #include "target.h" +#include T1000eBoard board; diff --git a/variants/t1000-e/target.h b/variants/t1000-e/target.h index 4fc4c94e..8e9fd9c7 100644 --- a/variants/t1000-e/target.h +++ b/variants/t1000-e/target.h @@ -1,7 +1,5 @@ #pragma once -#include "MicroNMEALocationProvider.h" - #define RADIOLIB_STATIC_ONLY 1 #include #include @@ -9,6 +7,7 @@ #include #include #include +#include class T1000SensorManager: public SensorManager { bool gps_active = false; From 997261a68e49bbab95f7082bab728421afaee2cb Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 8 May 2025 13:55:09 +1000 Subject: [PATCH 063/154] * Heltec tracker: added GPS to custom HWTSensorManager --- variants/heltec_tracker/platformio.ini | 3 ++ variants/heltec_tracker/target.cpp | 65 +++++++++++++++++++++++++- variants/heltec_tracker/target.h | 20 +++++++- 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 7b37b810..fdf3089c 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -21,6 +21,8 @@ build_flags = -D USE_PIN_TFT=1 -D PIN_TFT_VDD_CTL=3 ; Vext is connected to VDD which is also connected to LEDA -D PIN_TFT_LEDA_CTL=21 ; LEDK (switches on/off via mosfet to create the ground) + -D PIN_GPS_RX=33 + -D PIN_GPS_TX=34 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=130.0f ; for best TX power! @@ -54,5 +56,6 @@ build_src_filter = ${Heltec_tracker_base.build_src_filter} lib_deps = ${Heltec_tracker_base.lib_deps} densaugeo/base64 @ ~1.4.0 + stevemarple/MicroNMEA @ ^2.0.6 adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 \ No newline at end of file diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp index 27e0ebf9..792ce45b 100644 --- a/variants/heltec_tracker/target.cpp +++ b/variants/heltec_tracker/target.cpp @@ -1,5 +1,6 @@ #include #include "target.h" +#include HeltecV3Board board; @@ -14,7 +15,8 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +HWTSensorManager sensors = HWTSensorManager(nmea); #ifndef LORA_CR #define LORA_CR 5 @@ -74,3 +76,64 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } + +void HWTSensorManager::start_gps() { + gps_active = true; + // init GPS + Serial1.begin(115200, SERIAL_8N1, PIN_GPS_RX, PIN_GPS_TX); + Serial1.println("$CFGSYS,h35155*68"); +} + +void HWTSensorManager::stop_gps() { + gps_active = false; + Serial1.end(); +} + +bool HWTSensorManager::begin() { + return true; +} + +bool HWTSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { + if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission? + telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, 0.0f); + } + return true; +} + +void HWTSensorManager::loop() { + static long next_gps_update = 0; + + _location->loop(); + + if (millis() > next_gps_update) { + if (_location->isValid()) { + node_lat = ((double)_location->getLatitude())/1000000.; + node_lon = ((double)_location->getLongitude())/1000000.; + //Serial.printf("lat %f lon %f\r\n", _lat, _lon); + } + next_gps_update = millis() + 1000; + } +} + +int HWTSensorManager::getNumSettings() const { return 1; } // just one supported: "gps" (power switch) + +const char* HWTSensorManager::getSettingName(int i) const { + return i == 0 ? "gps" : NULL; +} +const char* HWTSensorManager::getSettingValue(int i) const { + if (i == 0) { + return gps_active ? "1" : "0"; + } + return NULL; +} +bool HWTSensorManager::setSettingValue(const char* name, const char* value) { + if (strcmp(name, "gps") == 0) { + if (strcmp(value, "0") == 0) { + stop_gps(); + } else { + start_gps(); + } + return true; + } + return false; // not supported +} diff --git a/variants/heltec_tracker/target.h b/variants/heltec_tracker/target.h index b6ab2577..422e6f14 100644 --- a/variants/heltec_tracker/target.h +++ b/variants/heltec_tracker/target.h @@ -7,11 +7,29 @@ #include #include #include +#include + +class HWTSensorManager : public SensorManager { + bool gps_active = false; + LocationProvider * _location; + + void start_gps(); + void stop_gps(); +public: + HWTSensorManager(LocationProvider &location): _location(&location) { } + bool begin() override; + bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; + void loop() override; + int getNumSettings() const override; + const char* getSettingName(int i) const override; + const char* getSettingValue(int i) const override; + bool setSettingValue(const char* name, const char* value) override; +}; extern HeltecV3Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern HWTSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); From 810fc8b8f0fada995f84ad399f4f63ca53c48706 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 8 May 2025 15:50:53 +1000 Subject: [PATCH 064/154] * Heltec tracker: new 'periph_power' shared pin (between Display & GPS) --- examples/companion_radio/main.cpp | 10 +++++++-- src/helpers/HeltecV3Board.h | 9 +++++--- src/helpers/RefCountedDigitalPin.h | 29 ++++++++++++++++++++++++++ src/helpers/ui/ST7735Display.cpp | 22 +++++++++---------- src/helpers/ui/ST7735Display.h | 16 ++++++++++++-- variants/heltec_tracker/platformio.ini | 2 +- variants/heltec_tracker/target.cpp | 24 ++++++++++++++------- variants/heltec_v3/platformio.ini | 1 + 8 files changed, 86 insertions(+), 27 deletions(-) create mode 100644 src/helpers/RefCountedDigitalPin.h diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 7a5921cf..83afe4fb 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -60,7 +60,7 @@ #define PUBLIC_GROUP_PSK "izOH6cXN6mrJ5e26oRXNcg==" -#ifdef DISPLAY_CLASS +#ifdef DISPLAY_CLASS // TODO: refactor this -- move to variants/*/target #include "UITask.h" #ifdef ST7735 #include @@ -71,7 +71,13 @@ #else #include #endif - static DISPLAY_CLASS display; + + #if defined(HELTEC_LORA_V3) && defined(ST7735) + static DISPLAY_CLASS display(&board.periph_power); // peripheral power pin is shared + #else + static DISPLAY_CLASS display; + #endif + #define HAS_UI #endif diff --git a/src/helpers/HeltecV3Board.h b/src/helpers/HeltecV3Board.h index fd6352ec..988e66d9 100644 --- a/src/helpers/HeltecV3Board.h +++ b/src/helpers/HeltecV3Board.h @@ -1,6 +1,7 @@ #pragma once #include +#include // LoRa radio module pins for Heltec V3 // Also for Heltec Wireless Tracker @@ -20,7 +21,6 @@ #define PIN_ADC_CTRL_ACTIVE LOW #define PIN_ADC_CTRL_INACTIVE HIGH //#define PIN_LED_BUILTIN 35 -#define PIN_VEXT_EN 36 #include "ESP32Board.h" @@ -31,6 +31,10 @@ private: bool adc_active_state; public: + RefCountedDigitalPin periph_power; + + HeltecV3Board() : periph_power(PIN_VEXT_EN) { } + void begin() { ESP32Board::begin(); @@ -41,8 +45,7 @@ public: pinMode(PIN_ADC_CTRL, OUTPUT); digitalWrite(PIN_ADC_CTRL, !adc_active_state); // Initially inactive - pinMode(PIN_VEXT_EN, OUTPUT); - digitalWrite(PIN_VEXT_EN, LOW); // for V3.2 boards + periph_power.begin(); esp_reset_reason_t reason = esp_reset_reason(); if (reason == ESP_RST_DEEPSLEEP) { diff --git a/src/helpers/RefCountedDigitalPin.h b/src/helpers/RefCountedDigitalPin.h new file mode 100644 index 00000000..14b67fb1 --- /dev/null +++ b/src/helpers/RefCountedDigitalPin.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +class RefCountedDigitalPin { + uint8_t _pin; + int8_t _claims = 0; + +public: + RefCountedDigitalPin(uint8_t pin): _pin(pin) { } + + void begin() { + pinMode(_pin, OUTPUT); + digitalWrite(_pin, LOW); // initial state + } + + void claim() { + _claims++; + if (_claims > 0) { + digitalWrite(_pin, HIGH); + } + } + void release() { + _claims--; + if (_claims == 0) { + digitalWrite(_pin, LOW); + } + } +}; diff --git a/src/helpers/ui/ST7735Display.cpp b/src/helpers/ui/ST7735Display.cpp index bb3c6473..91c40ea5 100644 --- a/src/helpers/ui/ST7735Display.cpp +++ b/src/helpers/ui/ST7735Display.cpp @@ -19,10 +19,10 @@ bool ST7735Display::i2c_probe(TwoWire& wire, uint8_t addr) { } bool ST7735Display::begin() { - if(!_isOn) { - pinMode(PIN_TFT_VDD_CTL, OUTPUT); + if (!_isOn) { + if (_peripher_power) _peripher_power->claim(); + pinMode(PIN_TFT_LEDA_CTL, OUTPUT); - digitalWrite(PIN_TFT_VDD_CTL, HIGH); digitalWrite(PIN_TFT_LEDA_CTL, HIGH); digitalWrite(PIN_TFT_RST, HIGH); @@ -40,18 +40,18 @@ bool ST7735Display::begin() { } void ST7735Display::turnOn() { - ST7735Display::begin(); - } void ST7735Display::turnOff() { - digitalWrite(PIN_TFT_VDD_CTL, HIGH); - digitalWrite(PIN_TFT_LEDA_CTL, HIGH); - digitalWrite(PIN_TFT_RST, LOW); - digitalWrite(PIN_TFT_VDD_CTL, LOW); - digitalWrite(PIN_TFT_LEDA_CTL, LOW); - _isOn = false; + if (_isOn) { + digitalWrite(PIN_TFT_LEDA_CTL, HIGH); + digitalWrite(PIN_TFT_RST, LOW); + digitalWrite(PIN_TFT_LEDA_CTL, LOW); + _isOn = false; + + if (_peripher_power) _peripher_power->release(); + } } void ST7735Display::clear() { diff --git a/src/helpers/ui/ST7735Display.h b/src/helpers/ui/ST7735Display.h index 8e9bd04c..94797503 100644 --- a/src/helpers/ui/ST7735Display.h +++ b/src/helpers/ui/ST7735Display.h @@ -5,18 +5,30 @@ #include #include #include +#include class ST7735Display : public DisplayDriver { Adafruit_ST7735 display; bool _isOn; uint16_t _color; + RefCountedDigitalPin* _peripher_power; bool i2c_probe(TwoWire& wire, uint8_t addr); public: #ifdef USE_PIN_TFT - ST7735Display() : DisplayDriver(128, 64), display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST) { _isOn = false; } + ST7735Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), + display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST), + _peripher_power(peripher_power) + { + _isOn = false; + } #else - ST7735Display() : DisplayDriver(128, 64), display(&SPI1, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST) { _isOn = false; } + ST7735Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), + display(&SPI1, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST), + _peripher_power(peripher_power) + { + _isOn = false; + } #endif bool begin(); diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index fdf3089c..679fe163 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -19,7 +19,7 @@ build_flags = -D PIN_TFT_RST=39 ; RES -D PIN_TFT_CS=38 -D USE_PIN_TFT=1 - -D PIN_TFT_VDD_CTL=3 ; Vext is connected to VDD which is also connected to LEDA + -D PIN_VEXT_EN=3 ; Vext is connected to VDD which is also connected to OLED & GPS -D PIN_TFT_LEDA_CTL=21 ; LEDK (switches on/off via mosfet to create the ground) -D PIN_GPS_RX=33 -D PIN_GPS_TX=34 diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp index 792ce45b..c82b70ad 100644 --- a/variants/heltec_tracker/target.cpp +++ b/variants/heltec_tracker/target.cpp @@ -1,5 +1,6 @@ #include #include "target.h" + #include HeltecV3Board board; @@ -78,18 +79,25 @@ mesh::LocalIdentity radio_new_identity() { } void HWTSensorManager::start_gps() { - gps_active = true; - // init GPS - Serial1.begin(115200, SERIAL_8N1, PIN_GPS_RX, PIN_GPS_TX); - Serial1.println("$CFGSYS,h35155*68"); + if (!gps_active) { + board.periph_power.claim(); + + gps_active = true; + Serial1.println("$CFGSYS,h35155*68"); + } } void HWTSensorManager::stop_gps() { - gps_active = false; - Serial1.end(); + if (gps_active) { + gps_active = false; + + board.periph_power.release(); + } } bool HWTSensorManager::begin() { + // init GPS port + Serial1.begin(115200, SERIAL_8N1, PIN_GPS_RX, PIN_GPS_TX); return true; } @@ -106,10 +114,10 @@ void HWTSensorManager::loop() { _location->loop(); if (millis() > next_gps_update) { - if (_location->isValid()) { + if (gps_active && _location->isValid()) { node_lat = ((double)_location->getLatitude())/1000000.; node_lon = ((double)_location->getLongitude())/1000000.; - //Serial.printf("lat %f lon %f\r\n", _lat, _lon); + MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); } next_gps_update = millis() + 1000; } diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 8452fbf5..0dbf0917 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -12,6 +12,7 @@ build_flags = -D PIN_BOARD_SDA=17 -D PIN_BOARD_SCL=18 -D PIN_USER_BTN=0 + -D PIN_VEXT_EN=36 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=130.0f ; for best TX power! From d8952f37100ac2b2ac3197fca639cc0fb83e1ab0 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 9 May 2025 16:17:36 +1000 Subject: [PATCH 065/154] * ESP32Board: can now download entire log file via OTA webserver (URL: /log) --- src/helpers/ESP32Board.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/helpers/ESP32Board.cpp b/src/helpers/ESP32Board.cpp index 57838d32..dd1f1801 100644 --- a/src/helpers/ESP32Board.cpp +++ b/src/helpers/ESP32Board.cpp @@ -8,6 +8,8 @@ #include #include +#include + bool ESP32Board::startOTAUpdate(const char* id, char reply[]) { WiFi.softAP("MeshCore-OTA", NULL); @@ -24,6 +26,9 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[]) { server->on("/", HTTP_GET, [](AsyncWebServerRequest *request) { request->send(200, "text/html", home_buf); }); + server->on("/log", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(SPIFFS, "/packet_log", "text/plain"); + }); AsyncElegantOTA.setID(id_buf); AsyncElegantOTA.begin(server); // Start ElegantOTA From d072e7b57560f1549b94b99c0f0acd46fe620b50 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 9 May 2025 18:12:42 +1000 Subject: [PATCH 066/154] * ver bump to v1.6.0 --- examples/companion_radio/main.cpp | 4 ++-- examples/simple_repeater/main.cpp | 4 ++-- examples/simple_room_server/main.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 83afe4fb..4cebc42a 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -102,11 +102,11 @@ static uint32_t _atoi(const char* sp) { #define FIRMWARE_VER_CODE 5 #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "21 Apr 2025" + #define FIRMWARE_BUILD_DATE "9 May 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.5.1" + #define FIRMWARE_VERSION "v1.6.0" #endif #define CMD_APP_START 1 diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index c926a786..9694300e 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -22,11 +22,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "21 Apr 2025" + #define FIRMWARE_BUILD_DATE "9 May 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.5.1" + #define FIRMWARE_VERSION "v1.6.0" #endif #ifndef LORA_FREQ diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index dddef501..a5d78e3c 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -22,11 +22,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "21 Apr 2025" + #define FIRMWARE_BUILD_DATE "9 May 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.5.1" + #define FIRMWARE_VERSION "v1.6.0" #endif #ifndef LORA_FREQ From 445179f53a3afa4cf8198ea2f156a784712e122a Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Fri, 9 May 2025 16:22:31 +0200 Subject: [PATCH 067/154] tbeam supreme companion: raise channels to 8 --- variants/lilygo_tbeam_supreme_SX1262/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index e26b0c08..9ccd2e2d 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -58,7 +58,7 @@ extends = T_Beam_S3_Supreme_SX1262 build_flags = ${T_Beam_S3_Supreme_SX1262.build_flags} -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=1 + -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 @@ -70,4 +70,4 @@ build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} +<../examples/companion_radio> lib_deps = ${T_Beam_S3_Supreme_SX1262.lib_deps} - densaugeo/base64 @ ~1.4.0 \ No newline at end of file + densaugeo/base64 @ ~1.4.0 From ae5052fec77de20af245512718c7e8a4ca11fef0 Mon Sep 17 00:00:00 2001 From: JQ Date: Fri, 9 May 2025 20:30:11 -0700 Subject: [PATCH 068/154] t114 gps support --- variants/t114/platformio.ini | 4 ++- variants/t114/target.cpp | 70 +++++++++++++++++++++++++++++++++++- variants/t114/target.h | 20 ++++++++++- variants/t114/variant.h | 10 ++++-- 4 files changed, 99 insertions(+), 5 deletions(-) diff --git a/variants/t114/platformio.ini b/variants/t114/platformio.ini index 0f1047d6..66536586 100644 --- a/variants/t114/platformio.ini +++ b/variants/t114/platformio.ini @@ -71,7 +71,8 @@ build_flags = ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 ; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 + -D MESH_DEBUG=1 +; -D GPS_NMEA_DEBUG=1 build_src_filter = ${Heltec_t114.build_src_filter} + + @@ -84,6 +85,7 @@ lib_deps = adafruit/Adafruit GFX Library @ ^1.12.1 ${Heltec_t114.lib_deps} densaugeo/base64 @ ~1.4.0 + stevemarple/MicroNMEA @ ^2.0.6 [env:Heltec_t114_companion_radio_usb] extends = Heltec_t114 diff --git a/variants/t114/target.cpp b/variants/t114/target.cpp index 31215e82..f59227f4 100644 --- a/variants/t114/target.cpp +++ b/variants/t114/target.cpp @@ -1,6 +1,7 @@ #include #include "target.h" #include +#include T114Board board; @@ -10,7 +11,8 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +T114SensorManager sensors = T114SensorManager(nmea); #ifndef LORA_CR #define LORA_CR 5 @@ -68,3 +70,69 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } + +void T114SensorManager::start_gps() { + if (!gps_active) { + gps_active = true; + _location->begin(); + } +} + +void T114SensorManager::stop_gps() { + if (gps_active) { + gps_active = false; + _location->stop(); + } +} + +bool T114SensorManager::begin() { + Serial1.begin(9600); + return true; +} + +bool T114SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { + if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission? + telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, 0.0f); + } + return true; +} + +void T114SensorManager::loop() { + static long next_gps_update = 0; + + _location->loop(); + + if (millis() > next_gps_update) { + if (_location->isValid()) { + node_lat = ((double)_location->getLatitude())/1000000.; + node_lon = ((double)_location->getLongitude())/1000000.; + MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); + } + next_gps_update = millis() + 1000; + } +} + +int T114SensorManager::getNumSettings() const { return 1; } // just one supported: "gps" (power switch) + +const char* T114SensorManager::getSettingName(int i) const { + return i == 0 ? "gps" : NULL; +} + +const char* T114SensorManager::getSettingValue(int i) const { + if (i == 0) { + return gps_active ? "1" : "0"; + } + return NULL; +} + +bool T114SensorManager::setSettingValue(const char* name, const char* value) { + if (strcmp(name, "gps") == 0) { + if (strcmp(value, "0") == 0) { + stop_gps(); + } else { + start_gps(); + } + return true; + } + return false; // not supported +} diff --git a/variants/t114/target.h b/variants/t114/target.h index b1c05b83..308fb7b9 100644 --- a/variants/t114/target.h +++ b/variants/t114/target.h @@ -7,11 +7,29 @@ #include #include #include +#include + +class T114SensorManager : public SensorManager { + bool gps_active = false; + LocationProvider* _location; + + void start_gps(); + void stop_gps(); +public: + T114SensorManager(LocationProvider &location): _location(&location) { } + bool begin() override; + bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; + void loop() override; + int getNumSettings() const override; + const char* getSettingName(int i) const override; + const char* getSettingValue(int i) const override; + bool setSettingValue(const char* name, const char* value) override; +}; extern T114Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern T114SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/t114/variant.h b/variants/t114/variant.h index 56fc536d..51b6558a 100644 --- a/variants/t114/variant.h +++ b/variants/t114/variant.h @@ -41,8 +41,8 @@ //////////////////////////////////////////////////////////////////////////////// // UART pin definition -#define PIN_SERIAL1_RX (39) -#define PIN_SERIAL1_TX (37) +#define PIN_SERIAL1_RX (37) +#define PIN_SERIAL1_TX (39) #define PIN_SERIAL2_RX (9) #define PIN_SERIAL2_TX (10) @@ -112,6 +112,12 @@ #define PIN_BUZZER (46) +//////////////////////////////////////////////////////////////////////////////// +// GPS + +#define GPS_EN (21) +#define GPS_RESET (38) + //////////////////////////////////////////////////////////////////////////////// // TFT #define PIN_TFT_SCL (40) From b92e2abe753850e57a5b800ead8acd0458549996 Mon Sep 17 00:00:00 2001 From: JQ Date: Fri, 9 May 2025 20:31:28 -0700 Subject: [PATCH 069/154] remove debug --- variants/t114/platformio.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/variants/t114/platformio.ini b/variants/t114/platformio.ini index 66536586..a896adce 100644 --- a/variants/t114/platformio.ini +++ b/variants/t114/platformio.ini @@ -71,8 +71,7 @@ build_flags = ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 ; -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 -; -D GPS_NMEA_DEBUG=1 +; -D MESH_DEBUG=1 build_src_filter = ${Heltec_t114.build_src_filter} + + From bce5dc97967b6ab3eccf11accad95824fd0b35a5 Mon Sep 17 00:00:00 2001 From: Jacob Quatier Date: Sat, 10 May 2025 20:47:13 -0700 Subject: [PATCH 070/154] Disable LED flashing during BLE advertising --- variants/t114/variant.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/t114/variant.h b/variants/t114/variant.h index 56fc536d..74b1fdca 100644 --- a/variants/t114/variant.h +++ b/variants/t114/variant.h @@ -69,7 +69,7 @@ #define LED_BUILTIN (35) #define PIN_LED LED_BUILTIN #define LED_RED LED_BUILTIN -#define LED_BLUE LED_BUILTIN +#define LED_BLUE (-1) // No blue led, prevents Bluefruit flashing the green LED during advertising #define LED_PIN LED_BUILTIN #define LED_STATE_ON HIGH From 35e1901d0e6a4220a50a15f096e17b94c9ac806f Mon Sep 17 00:00:00 2001 From: Florent Date: Sun, 11 May 2025 09:26:32 +0200 Subject: [PATCH 071/154] wio-e5 : initial port --- arch/stm32/Adafruit_LittleFS_stm32/README.md | 1 + .../library.properties | 10 + .../src/Adafruit_LittleFS.cpp | 273 ++ .../src/Adafruit_LittleFS.h | 102 + .../src/Adafruit_LittleFS_File.cpp | 420 +++ .../src/Adafruit_LittleFS_File.h | 108 + .../src/littlefs/LICENSE.md | 24 + .../src/littlefs/README.md | 177 ++ .../src/littlefs/lfs.c | 2585 +++++++++++++++++ .../src/littlefs/lfs.h | 501 ++++ .../src/littlefs/lfs_util.c | 31 + .../src/littlefs/lfs_util.h | 197 ++ arch/stm32/build_hex.py | 10 + examples/companion_radio/main.cpp | 17 +- examples/simple_repeater/main.cpp | 8 +- platformio.ini | 15 + src/helpers/CommonCLI.cpp | 2 +- src/helpers/CustomSTM32WLx.h | 17 + src/helpers/CustomSTM32WLxWrapper.h | 30 + src/helpers/IdentityStore.cpp | 4 +- src/helpers/IdentityStore.h | 2 +- src/helpers/stm32/InternalFileSystem.cpp | 139 + src/helpers/stm32/InternalFileSystem.h | 48 + src/helpers/stm32/STM32Board.h | 29 + variants/wio-e5/platformio.ini | 31 + variants/wio-e5/target.cpp | 69 + variants/wio-e5/target.h | 20 + variants/wio-e5/variant.h | 10 + 28 files changed, 4865 insertions(+), 15 deletions(-) create mode 100644 arch/stm32/Adafruit_LittleFS_stm32/README.md create mode 100644 arch/stm32/Adafruit_LittleFS_stm32/library.properties create mode 100644 arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS.cpp create mode 100644 arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS.h create mode 100644 arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS_File.cpp create mode 100644 arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS_File.h create mode 100644 arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/LICENSE.md create mode 100644 arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/README.md create mode 100644 arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/lfs.c create mode 100644 arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/lfs.h create mode 100644 arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/lfs_util.c create mode 100644 arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/lfs_util.h create mode 100644 arch/stm32/build_hex.py create mode 100644 src/helpers/CustomSTM32WLx.h create mode 100644 src/helpers/CustomSTM32WLxWrapper.h create mode 100644 src/helpers/stm32/InternalFileSystem.cpp create mode 100644 src/helpers/stm32/InternalFileSystem.h create mode 100644 src/helpers/stm32/STM32Board.h create mode 100644 variants/wio-e5/platformio.ini create mode 100644 variants/wio-e5/target.cpp create mode 100644 variants/wio-e5/target.h create mode 100644 variants/wio-e5/variant.h diff --git a/arch/stm32/Adafruit_LittleFS_stm32/README.md b/arch/stm32/Adafruit_LittleFS_stm32/README.md new file mode 100644 index 00000000..27ab29e8 --- /dev/null +++ b/arch/stm32/Adafruit_LittleFS_stm32/README.md @@ -0,0 +1 @@ +This is LittleFS from Adafruit, stripped from things that makes it not compile with stm32 (refs to TinyUSB and free_rtos, mostly) \ No newline at end of file diff --git a/arch/stm32/Adafruit_LittleFS_stm32/library.properties b/arch/stm32/Adafruit_LittleFS_stm32/library.properties new file mode 100644 index 00000000..750ed4d8 --- /dev/null +++ b/arch/stm32/Adafruit_LittleFS_stm32/library.properties @@ -0,0 +1,10 @@ +name=Adafruit Little File System Libraries +version=0.11.0 +author=Adafruit +maintainer=Adafruit +sentence=Arduino library for ARM Little File System +paragraph=Arduino library for ARM Little File System +category=Data Storage +url=https://github.com/adafruit/Adafruit_nRF52_Arduino +architectures=* +includes=Adafruit_LittleFS.h diff --git a/arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS.cpp b/arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS.cpp new file mode 100644 index 00000000..0c9c97b5 --- /dev/null +++ b/arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS.cpp @@ -0,0 +1,273 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include "Adafruit_LittleFS.h" + +//#include // for Serial + +using namespace Adafruit_LittleFS_Namespace; + +#define memclr(buffer, size) memset(buffer, 0, size) +#define varclr(_var) memclr(_var, sizeof(*(_var))) + +//--------------------------------------------------------------------+ +// Implementation +//--------------------------------------------------------------------+ + +Adafruit_LittleFS::Adafruit_LittleFS (void) + : Adafruit_LittleFS(NULL) +{ + +} + +Adafruit_LittleFS::Adafruit_LittleFS (struct lfs_config* cfg) +{ + varclr(&_lfs); + _lfs_cfg = cfg; + _mounted = false; +// _mutex = xSemaphoreCreateMutexStatic(&this->_MutexStorageSpace); +} + +Adafruit_LittleFS::~Adafruit_LittleFS () +{ + +} + +// Initialize and mount the file system +// Return true if mounted successfully else probably corrupted. +// User should format the disk and try again +bool Adafruit_LittleFS::begin (struct lfs_config * cfg) +{ + _lockFS(); + + bool ret; + // not a loop, just an quick way to short-circuit on error + do { + if (_mounted) { ret = true; break; } + if (cfg) { _lfs_cfg = cfg; } + if (nullptr == _lfs_cfg) { ret = false; break; } + // actually attempt to mount, and log error if one occurs + int err = lfs_mount(&_lfs, _lfs_cfg); + PRINT_LFS_ERR(err); + _mounted = (err == LFS_ERR_OK); + ret = _mounted; + } while(0); + + _unlockFS(); + return ret; +} + +// Tear down and unmount file system +void Adafruit_LittleFS::end(void) +{ + _lockFS(); + + if (_mounted) + { + _mounted = false; + int err = lfs_unmount(&_lfs); + PRINT_LFS_ERR(err); + (void)err; + } + + _unlockFS(); +} + +bool Adafruit_LittleFS::format (void) +{ + _lockFS(); + + int err = LFS_ERR_OK; + bool attemptMount = _mounted; + // not a loop, just an quick way to short-circuit on error + do + { + // if already mounted: umount first -> format -> remount + if (_mounted) + { + _mounted = false; + err = lfs_unmount(&_lfs); + if ( LFS_ERR_OK != err) { PRINT_LFS_ERR(err); break; } + } + err = lfs_format(&_lfs, _lfs_cfg); + if ( LFS_ERR_OK != err ) { PRINT_LFS_ERR(err); break; } + + if (attemptMount) + { + err = lfs_mount(&_lfs, _lfs_cfg); + if ( LFS_ERR_OK != err ) { PRINT_LFS_ERR(err); break; } + _mounted = true; + } + // success! + } while(0); + + _unlockFS(); + return LFS_ERR_OK == err; +} + +// Open a file or folder +Adafruit_LittleFS_Namespace::File Adafruit_LittleFS::open (char const *filepath, uint8_t mode) +{ + // No lock is required here ... the File() object will synchronize with the mutex provided + return Adafruit_LittleFS_Namespace::File(filepath, mode, *this); +} + +// Check if file or folder exists +bool Adafruit_LittleFS::exists (char const *filepath) +{ + struct lfs_info info; + _lockFS(); + + bool ret = (0 == lfs_stat(&_lfs, filepath, &info)); + + _unlockFS(); + return ret; +} + + +// Create a directory, create intermediate parent if needed +bool Adafruit_LittleFS::mkdir (char const *filepath) +{ + bool ret = true; + const char* slash = filepath; + if ( slash[0] == '/' ) slash++; // skip root '/' + + _lockFS(); + + // make intermediate parent directory(ies) + while ( NULL != (slash = strchr(slash, '/')) ) + { + char parent[slash - filepath + 1] = { 0 }; + memcpy(parent, filepath, slash - filepath); + + int rc = lfs_mkdir(&_lfs, parent); + if ( rc != LFS_ERR_OK && rc != LFS_ERR_EXIST ) + { + PRINT_LFS_ERR(rc); + ret = false; + break; + } + slash++; + } + // make the final requested directory + if (ret) + { + int rc = lfs_mkdir(&_lfs, filepath); + if ( rc != LFS_ERR_OK && rc != LFS_ERR_EXIST ) + { + PRINT_LFS_ERR(rc); + ret = false; + } + } + + _unlockFS(); + return ret; +} + +// Remove a file +bool Adafruit_LittleFS::remove (char const *filepath) +{ + _lockFS(); + + int err = lfs_remove(&_lfs, filepath); + PRINT_LFS_ERR(err); + + _unlockFS(); + return LFS_ERR_OK == err; +} + +// Rename a file +bool Adafruit_LittleFS::rename (char const *oldfilepath, char const *newfilepath) +{ + _lockFS(); + + int err = lfs_rename(&_lfs, oldfilepath, newfilepath); + PRINT_LFS_ERR(err); + + _unlockFS(); + return LFS_ERR_OK == err; +} + +// Remove a folder +bool Adafruit_LittleFS::rmdir (char const *filepath) +{ + _lockFS(); + + int err = lfs_remove(&_lfs, filepath); + PRINT_LFS_ERR(err); + + _unlockFS(); + return LFS_ERR_OK == err; +} + +// Remove a folder recursively +bool Adafruit_LittleFS::rmdir_r (char const *filepath) +{ + /* adafruit: lfs is modified to remove non-empty folder, + According to below issue, comment these 2 line won't corrupt filesystem + at least when using LFS v1. If moving to LFS v2, see tracked issue + to see if issues (such as the orphans in threaded linked list) are resolved. + https://github.com/ARMmbed/littlefs/issues/43 + */ + _lockFS(); + + int err = lfs_remove(&_lfs, filepath); + PRINT_LFS_ERR(err); + + _unlockFS(); + return LFS_ERR_OK == err; +} + +//------------- Debug -------------// +#if CFG_DEBUG + +const char* dbg_strerr_lfs (int32_t err) +{ + switch ( err ) + { + case LFS_ERR_OK : return "LFS_ERR_OK"; + case LFS_ERR_IO : return "LFS_ERR_IO"; + case LFS_ERR_CORRUPT : return "LFS_ERR_CORRUPT"; + case LFS_ERR_NOENT : return "LFS_ERR_NOENT"; + case LFS_ERR_EXIST : return "LFS_ERR_EXIST"; + case LFS_ERR_NOTDIR : return "LFS_ERR_NOTDIR"; + case LFS_ERR_ISDIR : return "LFS_ERR_ISDIR"; + case LFS_ERR_NOTEMPTY : return "LFS_ERR_NOTEMPTY"; + case LFS_ERR_BADF : return "LFS_ERR_BADF"; + case LFS_ERR_INVAL : return "LFS_ERR_INVAL"; + case LFS_ERR_NOSPC : return "LFS_ERR_NOSPC"; + case LFS_ERR_NOMEM : return "LFS_ERR_NOMEM"; + + default: + static char errcode[10]; + sprintf(errcode, "%ld", err); + return errcode; + } + + return NULL; +} + +#endif diff --git a/arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS.h b/arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS.h new file mode 100644 index 00000000..c183de1b --- /dev/null +++ b/arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS.h @@ -0,0 +1,102 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef ADAFRUIT_LITTLEFS_H_ +#define ADAFRUIT_LITTLEFS_H_ + +#include + +// Internal Flash uses ARM Little FileSystem +// https://github.com/ARMmbed/littlefs +#include "littlefs/lfs.h" +#include "Adafruit_LittleFS_File.h" +//#include "rtos.h" // tied to FreeRTOS for serialization + +class Adafruit_LittleFS +{ + public: + Adafruit_LittleFS (void); + Adafruit_LittleFS (struct lfs_config* cfg); + virtual ~Adafruit_LittleFS (); + + bool begin(struct lfs_config * cfg = NULL); + void end(void); + + // Open the specified file/directory with the supplied mode (e.g. read or + // write, etc). Returns a File object for interacting with the file. + // Note that currently only one file can be open at a time. + Adafruit_LittleFS_Namespace::File open (char const *filename, uint8_t mode = Adafruit_LittleFS_Namespace::FILE_O_READ); + + // Methods to determine if the requested file path exists. + bool exists (char const *filepath); + + // Create the requested directory hierarchy--if intermediate directories + // do not exist they will be created. + bool mkdir (char const *filepath); + + // Delete the file. + bool remove (char const *filepath); + + // Rename the file. + bool rename (char const *oldfilepath, char const *newfilepath); + + // Delete a folder (must be empty) + bool rmdir (char const *filepath); + + // Delete a folder (recursively) + bool rmdir_r (char const *filepath); + + // format file system + bool format (void); + + /*------------------------------------------------------------------*/ + /* INTERNAL USAGE ONLY + * Although declare as public, it is meant to be invoked by internal + * code. User should not call these directly + *------------------------------------------------------------------*/ + lfs_t* _getFS (void) { return &_lfs; } + void _lockFS (void) { }//xSemaphoreTake(_mutex, portMAX_DELAY); } + void _unlockFS(void) { }//xSemaphoreGive(_mutex); } + + protected: + bool _mounted; + struct lfs_config* _lfs_cfg; + lfs_t _lfs; +// SemaphoreHandle_t _mutex; + + private: +// StaticSemaphore_t _MutexStorageSpace; +}; + +#if !CFG_DEBUG + #define VERIFY_LFS(...) _GET_3RD_ARG(__VA_ARGS__, VERIFY_ERR_2ARGS, VERIFY_ERR_1ARGS)(__VA_ARGS__, NULL) + #define PRINT_LFS_ERR(_err) +#else + #define VERIFY_LFS(...) _GET_3RD_ARG(__VA_ARGS__, VERIFY_ERR_2ARGS, VERIFY_ERR_1ARGS)(__VA_ARGS__, dbg_strerr_lfs) + #define PRINT_LFS_ERR(_err) do { if (_err) { VERIFY_MESS((long int)_err, dbg_strerr_lfs); } } while(0) // LFS_ERR are of type int, VERIFY_MESS expects long_int + + const char* dbg_strerr_lfs (int32_t err); +#endif + +#endif /* ADAFRUIT_LITTLEFS_H_ */ diff --git a/arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS_File.cpp b/arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS_File.cpp new file mode 100644 index 00000000..41ba3570 --- /dev/null +++ b/arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS_File.cpp @@ -0,0 +1,420 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include "Adafruit_LittleFS.h" +#include "littlefs/lfs.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ + +using namespace Adafruit_LittleFS_Namespace; + +File::File (Adafruit_LittleFS &fs) +{ + _fs = &fs; + _is_dir = false; + _name[0] = 0; + _name[LFS_NAME_MAX] = 0; + _dir_path = NULL; + + _dir = NULL; + _file = NULL; +} + +File::File (char const *filename, uint8_t mode, Adafruit_LittleFS &fs) + : File(fs) +{ + // public constructor calls public API open(), which will obtain the mutex + this->open(filename, mode); +} + +bool File::_open_file (char const *filepath, uint8_t mode) +{ + int flags = (mode == FILE_O_READ) ? LFS_O_RDONLY : + (mode == FILE_O_WRITE) ? (LFS_O_RDWR | LFS_O_CREAT) : 0; + + if ( flags ) + { + _file = (lfs_file_t*) malloc(sizeof(lfs_file_t)); + if (!_file) return false; + + int rc = lfs_file_open(_fs->_getFS(), _file, filepath, flags); + + if ( rc ) + { + // failed to open + PRINT_LFS_ERR(rc); + // free memory + free(_file); + _file = NULL; + return false; + } + + // move to end of file + if ( mode == FILE_O_WRITE ) lfs_file_seek(_fs->_getFS(), _file, 0, LFS_SEEK_END); + + _is_dir = false; + } + + return true; +} + +bool File::_open_dir (char const *filepath) +{ + _dir = (lfs_dir_t*) malloc(sizeof(lfs_dir_t)); + if (!_dir) return false; + + int rc = lfs_dir_open(_fs->_getFS(), _dir, filepath); + + if ( rc ) + { + // failed to open + PRINT_LFS_ERR(rc); + // free memory + free(_dir); + _dir = NULL; + return false; + } + + _is_dir = true; + + _dir_path = (char*) malloc(strlen(filepath) + 1); + strcpy(_dir_path, filepath); + + return true; +} + +bool File::open (char const *filepath, uint8_t mode) +{ + bool ret = false; + _fs->_lockFS(); + + ret = this->_open(filepath, mode); + + _fs->_unlockFS(); + return ret; +} + +bool File::_open (char const *filepath, uint8_t mode) +{ + bool ret = false; + + // close if currently opened + if ( this->isOpen() ) _close(); + + struct lfs_info info; + int rc = lfs_stat(_fs->_getFS(), filepath, &info); + + if ( LFS_ERR_OK == rc ) + { + // file existed, open file or directory accordingly + ret = (info.type == LFS_TYPE_REG) ? _open_file(filepath, mode) : _open_dir(filepath); + } + else if ( LFS_ERR_NOENT == rc ) + { + // file not existed, only proceed with FILE_O_WRITE mode + if ( mode == FILE_O_WRITE ) ret = _open_file(filepath, mode); + } + else + { + PRINT_LFS_ERR(rc); + } + + // save bare file name + if (ret) + { + char const* splash = strrchr(filepath, '/'); + strncpy(_name, splash ? (splash + 1) : filepath, LFS_NAME_MAX); + } + return ret; +} + +size_t File::write (uint8_t ch) +{ + return write(&ch, 1); +} + +size_t File::write (uint8_t const *buf, size_t size) +{ + lfs_ssize_t wrcount = 0; + _fs->_lockFS(); + + if (!this->_is_dir) + { + wrcount = lfs_file_write(_fs->_getFS(), _file, buf, size); + if (wrcount < 0) + { + wrcount = 0; + } + } + + _fs->_unlockFS(); + return wrcount; +} + +int File::read (void) +{ + // this thin wrapper relies on called function to synchronize + int ret = -1; + uint8_t ch; + if (read(&ch, 1) > 0) + { + ret = static_cast(ch); + } + return ret; +} + +int File::read (void *buf, uint16_t nbyte) +{ + int ret = 0; + _fs->_lockFS(); + + if (!this->_is_dir) + { + ret = lfs_file_read(_fs->_getFS(), _file, buf, nbyte); + } + + _fs->_unlockFS(); + return ret; +} + +int File::peek (void) +{ + int ret = -1; + _fs->_lockFS(); + + if (!this->_is_dir) + { + uint32_t pos = lfs_file_tell(_fs->_getFS(), _file); + uint8_t ch = 0; + if (lfs_file_read(_fs->_getFS(), _file, &ch, 1) > 0) + { + ret = static_cast(ch); + } + (void) lfs_file_seek(_fs->_getFS(), _file, pos, LFS_SEEK_SET); + } + + _fs->_unlockFS(); + return ret; +} + +int File::available (void) +{ + int ret = 0; + _fs->_lockFS(); + + if (!this->_is_dir) + { + uint32_t size = lfs_file_size(_fs->_getFS(), _file); + uint32_t pos = lfs_file_tell(_fs->_getFS(), _file); + ret = size - pos; + } + + _fs->_unlockFS(); + return ret; +} + +bool File::seek (uint32_t pos) +{ + bool ret = false; + _fs->_lockFS(); + + if (!this->_is_dir) + { + ret = lfs_file_seek(_fs->_getFS(), _file, pos, LFS_SEEK_SET) >= 0; + } + + _fs->_unlockFS(); + return ret; +} + +uint32_t File::position (void) +{ + uint32_t ret = 0; + _fs->_lockFS(); + + if (!this->_is_dir) + { + ret = lfs_file_tell(_fs->_getFS(), _file); + } + + _fs->_unlockFS(); + return ret; +} + +uint32_t File::size (void) +{ + uint32_t ret = 0; + _fs->_lockFS(); + + if (!this->_is_dir) + { + ret = lfs_file_size(_fs->_getFS(), _file); + } + + _fs->_unlockFS(); + return ret; +} + +bool File::truncate (uint32_t pos) +{ + int32_t ret=LFS_ERR_ISDIR; + _fs->_lockFS(); + if (!this->_is_dir) + { + ret = lfs_file_truncate(_fs->_getFS(), _file, pos); + } + _fs->_unlockFS(); + return ( ret == 0 ); +} + +bool File::truncate (void) +{ + int32_t ret=LFS_ERR_ISDIR; + uint32_t pos; + _fs->_lockFS(); + if (!this->_is_dir) + { + pos = lfs_file_tell(_fs->_getFS(), _file); + ret = lfs_file_truncate(_fs->_getFS(), _file, pos); + } + _fs->_unlockFS(); + return ( ret == 0 ); +} + +void File::flush (void) +{ + _fs->_lockFS(); + + if (!this->_is_dir) + { + lfs_file_sync(_fs->_getFS(), _file); + } + + _fs->_unlockFS(); + return; +} + +void File::close (void) +{ + _fs->_lockFS(); + this->_close(); + _fs->_unlockFS(); +} + +void File::_close(void) +{ + if ( this->isOpen() ) + { + if ( this->_is_dir ) + { + lfs_dir_close(_fs->_getFS(), _dir); + free(_dir); + _dir = NULL; + + if ( this->_dir_path ) free(_dir_path); + _dir_path = NULL; + } + else + { + lfs_file_close(this->_fs->_getFS(), _file); + free(_file); + _file = NULL; + } + } +} + +File::operator bool (void) +{ + return isOpen(); +} + +bool File::isOpen(void) +{ + return (_file != NULL) || (_dir != NULL); +} + +// WARNING -- although marked as `const`, the values pointed +// to may change. For example, if the same File +// object has `open()` called with a different +// file or directory name, this same pointer will +// suddenly (unexpectedly?) have different values. +char const* File::name (void) +{ + return this->_name; +} + +bool File::isDirectory (void) +{ + return this->_is_dir; +} + +File File::openNextFile (uint8_t mode) +{ + _fs->_lockFS(); + + File ret(*_fs); + if (this->_is_dir) + { + struct lfs_info info; + int rc; + + // lfs_dir_read returns 0 when reaching end of directory, 1 if found an entry + // Skip the "." and ".." entries ... + do + { + rc = lfs_dir_read(_fs->_getFS(), _dir, &info); + } while ( rc == 1 && (!strcmp(".", info.name) || !strcmp("..", info.name)) ); + + if ( rc == 1 ) + { + // string cat name with current folder + char filepath[strlen(_dir_path) + 1 + strlen(info.name) + 1]; // potential for significant stack usage + strcpy(filepath, _dir_path); + if ( !(_dir_path[0] == '/' && _dir_path[1] == 0) ) strcat(filepath, "/"); // only add '/' if cwd is not root + strcat(filepath, info.name); + + (void)ret._open(filepath, mode); // return value is ignored ... caller is expected to check isOpened() + } + else if ( rc < 0 ) + { + PRINT_LFS_ERR(rc); + } + } + _fs->_unlockFS(); + return ret; +} + +void File::rewindDirectory (void) +{ + _fs->_lockFS(); + if (this->_is_dir) + { + lfs_dir_rewind(_fs->_getFS(), _dir); + } + _fs->_unlockFS(); +} + diff --git a/arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS_File.h b/arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS_File.h new file mode 100644 index 00000000..13c7058a --- /dev/null +++ b/arch/stm32/Adafruit_LittleFS_stm32/src/Adafruit_LittleFS_File.h @@ -0,0 +1,108 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef ADAFRUIT_LITTLEFS_FILE_H_ +#define ADAFRUIT_LITTLEFS_FILE_H_ + +// Forward declaration +class Adafruit_LittleFS; + +namespace Adafruit_LittleFS_Namespace +{ + +// avoid conflict with other FileSystem FILE_READ/FILE_WRITE +enum +{ + FILE_O_READ = 0, + FILE_O_WRITE = 1, +}; + +class File : public Stream +{ + public: + File (Adafruit_LittleFS &fs); + File (char const *filename, uint8_t mode, Adafruit_LittleFS &fs); + + public: + + bool open (char const *filename, uint8_t mode); + + //------------- Stream API -------------// + virtual size_t write (uint8_t ch); + virtual size_t write (uint8_t const *buf, size_t size); + size_t write(const char *str) { + if (str == NULL) return 0; + return write((const uint8_t *)str, strlen(str)); + } + size_t write(const char *buffer, size_t size) { + return write((const uint8_t *)buffer, size); + } + + virtual int read (void); + int read (void *buf, uint16_t nbyte); + + virtual int peek (void); + virtual int available (void); + virtual void flush (void); + + bool seek (uint32_t pos); + uint32_t position (void); + uint32_t size (void); + + bool truncate (uint32_t pos); + bool truncate (void); + + void close (void); + + operator bool (void); + + bool isOpen(void); + char const* name (void); + + bool isDirectory (void); + File openNextFile (uint8_t mode = FILE_O_READ); + void rewindDirectory (void); + + private: + Adafruit_LittleFS* _fs; + + bool _is_dir; + + union { + lfs_file_t* _file; + lfs_dir_t* _dir; + }; + + char* _dir_path; + char _name[LFS_NAME_MAX+1]; + + bool _open(char const *filepath, uint8_t mode); + bool _open_file(char const *filepath, uint8_t mode); + bool _open_dir (char const *filepath); + void _close(void); +}; + +} + +#endif /* ADAFRUIT_LITTLEFS_FILE_H_ */ diff --git a/arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/LICENSE.md b/arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/LICENSE.md new file mode 100644 index 00000000..ed69bea4 --- /dev/null +++ b/arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/LICENSE.md @@ -0,0 +1,24 @@ +Copyright (c) 2017, Arm Limited. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. +- Neither the name of ARM nor the names of its contributors may be used to + endorse or promote products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/README.md b/arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/README.md new file mode 100644 index 00000000..623ba0ae --- /dev/null +++ b/arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/README.md @@ -0,0 +1,177 @@ +## The little filesystem + +A little fail-safe filesystem designed for embedded systems. + +``` + | | | .---._____ + .-----. | | +--|o |---| littlefs | +--| |---| | + '-----' '----------' + | | | +``` + +**Bounded RAM/ROM** - The littlefs is designed to work with a limited amount +of memory. Recursion is avoided and dynamic memory is limited to configurable +buffers that can be provided statically. + +**Power-loss resilient** - The littlefs is designed for systems that may have +random power failures. The littlefs has strong copy-on-write guarantees and +storage on disk is always kept in a valid state. + +**Wear leveling** - Since the most common form of embedded storage is erodible +flash memories, littlefs provides a form of dynamic wear leveling for systems +that can not fit a full flash translation layer. + +## Example + +Here's a simple example that updates a file named `boot_count` every time +main runs. The program can be interrupted at any time without losing track +of how many times it has been booted and without corrupting the filesystem: + +``` c +#include "lfs.h" + +// variables used by the filesystem +lfs_t lfs; +lfs_file_t file; + +// configuration of the filesystem is provided by this struct +const struct lfs_config cfg = { + // block device operations + .read = user_provided_block_device_read, + .prog = user_provided_block_device_prog, + .erase = user_provided_block_device_erase, + .sync = user_provided_block_device_sync, + + // block device configuration + .read_size = 16, + .prog_size = 16, + .block_size = 4096, + .block_count = 128, + .lookahead = 128, +}; + +// entry point +int main(void) { + // mount the filesystem + int err = lfs_mount(&lfs, &cfg); + + // reformat if we can't mount the filesystem + // this should only happen on the first boot + if (err) { + lfs_format(&lfs, &cfg); + lfs_mount(&lfs, &cfg); + } + + // read current count + uint32_t boot_count = 0; + lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT); + lfs_file_read(&lfs, &file, &boot_count, sizeof(boot_count)); + + // update boot count + boot_count += 1; + lfs_file_rewind(&lfs, &file); + lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count)); + + // remember the storage is not updated until the file is closed successfully + lfs_file_close(&lfs, &file); + + // release any resources we were using + lfs_unmount(&lfs); + + // print the boot count + printf("boot_count: %d\n", boot_count); +} +``` + +## Usage + +Detailed documentation (or at least as much detail as is currently available) +can be found in the comments in [lfs.h](lfs.h). + +As you may have noticed, littlefs takes in a configuration structure that +defines how the filesystem operates. The configuration struct provides the +filesystem with the block device operations and dimensions, tweakable +parameters that tradeoff memory usage for performance, and optional +static buffers if the user wants to avoid dynamic memory. + +The state of the littlefs is stored in the `lfs_t` type which is left up +to the user to allocate, allowing multiple filesystems to be in use +simultaneously. With the `lfs_t` and configuration struct, a user can +format a block device or mount the filesystem. + +Once mounted, the littlefs provides a full set of POSIX-like file and +directory functions, with the deviation that the allocation of filesystem +structures must be provided by the user. + +All POSIX operations, such as remove and rename, are atomic, even in event +of power-loss. Additionally, no file updates are actually committed to the +filesystem until sync or close is called on the file. + +## Other notes + +All littlefs have the potential to return a negative error code. The errors +can be either one of those found in the `enum lfs_error` in [lfs.h](lfs.h), +or an error returned by the user's block device operations. + +In the configuration struct, the `prog` and `erase` function provided by the +user may return a `LFS_ERR_CORRUPT` error if the implementation already can +detect corrupt blocks. However, the wear leveling does not depend on the return +code of these functions, instead all data is read back and checked for +integrity. + +If your storage caches writes, make sure that the provided `sync` function +flushes all the data to memory and ensures that the next read fetches the data +from memory, otherwise data integrity can not be guaranteed. If the `write` +function does not perform caching, and therefore each `read` or `write` call +hits the memory, the `sync` function can simply return 0. + +## Reference material + +[DESIGN.md](DESIGN.md) - DESIGN.md contains a fully detailed dive into how +littlefs actually works. I would encourage you to read it since the +solutions and tradeoffs at work here are quite interesting. + +[SPEC.md](SPEC.md) - SPEC.md contains the on-disk specification of littlefs +with all the nitty-gritty details. Can be useful for developing tooling. + +## Testing + +The littlefs comes with a test suite designed to run on a PC using the +[emulated block device](emubd/lfs_emubd.h) found in the emubd directory. +The tests assume a Linux environment and can be started with make: + +``` bash +make test +``` + +## License + +The littlefs is provided under the [BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html) +license. See [LICENSE.md](LICENSE.md) for more information. Contributions to +this project are accepted under the same license. + +Individual files contain the following tag instead of the full license text. + + SPDX-License-Identifier: BSD-3-Clause + +This enables machine processing of license information based on the SPDX +License Identifiers that are here available: http://spdx.org/licenses/ + +## Related projects + +[Mbed OS](https://github.com/ARMmbed/mbed-os/tree/master/features/filesystem/littlefs) - +The easiest way to get started with littlefs is to jump into [Mbed](https://os.mbed.com/), +which already has block device drivers for most forms of embedded storage. The +littlefs is available in Mbed OS as the [LittleFileSystem](https://os.mbed.com/docs/latest/reference/littlefilesystem.html) +class. + +[littlefs-fuse](https://github.com/geky/littlefs-fuse) - A [FUSE](https://github.com/libfuse/libfuse) +wrapper for littlefs. The project allows you to mount littlefs directly on a +Linux machine. Can be useful for debugging littlefs if you have an SD card +handy. + +[littlefs-js](https://github.com/geky/littlefs-js) - A javascript wrapper for +littlefs. I'm not sure why you would want this, but it is handy for demos. +You can see it in action [here](http://littlefs.geky.net/demo.html). diff --git a/arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/lfs.c b/arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/lfs.c new file mode 100644 index 00000000..f9d9ce34 --- /dev/null +++ b/arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/lfs.c @@ -0,0 +1,2585 @@ +/* + * The little filesystem + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "lfs.h" +#include "lfs_util.h" + +#include + + +/// Caching block device operations /// +static int lfs_cache_read(lfs_t *lfs, lfs_cache_t *rcache, + const lfs_cache_t *pcache, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + uint8_t *data = buffer; + LFS_ASSERT(block < lfs->cfg->block_count); + + while (size > 0) { + if (pcache && block == pcache->block && off >= pcache->off && + off < pcache->off + lfs->cfg->prog_size) { + // is already in pcache? + lfs_size_t diff = lfs_min(size, + lfs->cfg->prog_size - (off-pcache->off)); + memcpy(data, &pcache->buffer[off-pcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + if (block == rcache->block && off >= rcache->off && + off < rcache->off + lfs->cfg->read_size) { + // is already in rcache? + lfs_size_t diff = lfs_min(size, + lfs->cfg->read_size - (off-rcache->off)); + memcpy(data, &rcache->buffer[off-rcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + if (off % lfs->cfg->read_size == 0 && size >= lfs->cfg->read_size) { + // bypass cache? + lfs_size_t diff = size - (size % lfs->cfg->read_size); + int err = lfs->cfg->read(lfs->cfg, block, off, data, diff); + if (err) { + return err; + } + + data += diff; + off += diff; + size -= diff; + continue; + } + + // load to cache, first condition can no longer fail + rcache->block = block; + rcache->off = off - (off % lfs->cfg->read_size); + int err = lfs->cfg->read(lfs->cfg, rcache->block, + rcache->off, rcache->buffer, lfs->cfg->read_size); + if (err) { + return err; + } + } + + return 0; +} + +static int lfs_cache_cmp(lfs_t *lfs, lfs_cache_t *rcache, + const lfs_cache_t *pcache, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + + for (lfs_off_t i = 0; i < size; i++) { + uint8_t c; + int err = lfs_cache_read(lfs, rcache, pcache, + block, off+i, &c, 1); + if (err) { + return err; + } + + if (c != data[i]) { + return false; + } + } + + return true; +} + +static int lfs_cache_crc(lfs_t *lfs, lfs_cache_t *rcache, + const lfs_cache_t *pcache, lfs_block_t block, + lfs_off_t off, lfs_size_t size, uint32_t *crc) { + for (lfs_off_t i = 0; i < size; i++) { + uint8_t c; + int err = lfs_cache_read(lfs, rcache, pcache, + block, off+i, &c, 1); + if (err) { + return err; + } + + lfs_crc(crc, &c, 1); + } + + return 0; +} + +static inline void lfs_cache_drop(lfs_t *lfs, lfs_cache_t *rcache) { + // do not zero, cheaper if cache is readonly or only going to be + // written with identical data (during relocates) + (void)lfs; + rcache->block = 0xffffffff; +} + +static inline void lfs_cache_zero(lfs_t *lfs, lfs_cache_t *pcache) { + // zero to avoid information leak + memset(pcache->buffer, 0xff, lfs->cfg->prog_size); + pcache->block = 0xffffffff; +} + +static int lfs_cache_flush(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache) { + if (pcache->block != 0xffffffff) { + int err = lfs->cfg->prog(lfs->cfg, pcache->block, + pcache->off, pcache->buffer, lfs->cfg->prog_size); + if (err) { + return err; + } + + if (rcache) { + int res = lfs_cache_cmp(lfs, rcache, NULL, pcache->block, + pcache->off, pcache->buffer, lfs->cfg->prog_size); + if (res < 0) { + return res; + } + + if (!res) { + return LFS_ERR_CORRUPT; + } + } + + lfs_cache_zero(lfs, pcache); + } + + return 0; +} + +static int lfs_cache_prog(lfs_t *lfs, lfs_cache_t *pcache, + lfs_cache_t *rcache, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + LFS_ASSERT(block < lfs->cfg->block_count); + + while (size > 0) { + if (block == pcache->block && off >= pcache->off && + off < pcache->off + lfs->cfg->prog_size) { + // is already in pcache? + lfs_size_t diff = lfs_min(size, + lfs->cfg->prog_size - (off-pcache->off)); + memcpy(&pcache->buffer[off-pcache->off], data, diff); + + data += diff; + off += diff; + size -= diff; + + if (off % lfs->cfg->prog_size == 0) { + // eagerly flush out pcache if we fill up + int err = lfs_cache_flush(lfs, pcache, rcache); + if (err) { + return err; + } + } + + continue; + } + + // pcache must have been flushed, either by programming and + // entire block or manually flushing the pcache + LFS_ASSERT(pcache->block == 0xffffffff); + + if (off % lfs->cfg->prog_size == 0 && + size >= lfs->cfg->prog_size) { + // bypass pcache? + lfs_size_t diff = size - (size % lfs->cfg->prog_size); + int err = lfs->cfg->prog(lfs->cfg, block, off, data, diff); + if (err) { + return err; + } + + if (rcache) { + int res = lfs_cache_cmp(lfs, rcache, NULL, + block, off, data, diff); + if (res < 0) { + return res; + } + + if (!res) { + return LFS_ERR_CORRUPT; + } + } + + data += diff; + off += diff; + size -= diff; + continue; + } + + // prepare pcache, first condition can no longer fail + pcache->block = block; + pcache->off = off - (off % lfs->cfg->prog_size); + } + + return 0; +} + + +/// General lfs block device operations /// +static int lfs_bd_read(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + // if we ever do more than writes to alternating pairs, + // this may need to consider pcache + return lfs_cache_read(lfs, &lfs->rcache, NULL, + block, off, buffer, size); +} + +static int lfs_bd_prog(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + return lfs_cache_prog(lfs, &lfs->pcache, NULL, + block, off, buffer, size); +} + +static int lfs_bd_cmp(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + return lfs_cache_cmp(lfs, &lfs->rcache, NULL, block, off, buffer, size); +} + +static int lfs_bd_crc(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, lfs_size_t size, uint32_t *crc) { + return lfs_cache_crc(lfs, &lfs->rcache, NULL, block, off, size, crc); +} + +static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) { + return lfs->cfg->erase(lfs->cfg, block); +} + +static int lfs_bd_sync(lfs_t *lfs) { + lfs_cache_drop(lfs, &lfs->rcache); + + int err = lfs_cache_flush(lfs, &lfs->pcache, NULL); + if (err) { + return err; + } + + return lfs->cfg->sync(lfs->cfg); +} + + +/// Internal operations predeclared here /// +int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); +static int lfs_pred(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *pdir); +static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2], + lfs_dir_t *parent, lfs_entry_t *entry); +static int lfs_moved(lfs_t *lfs, const void *e); +static int lfs_relocate(lfs_t *lfs, + const lfs_block_t oldpair[2], const lfs_block_t newpair[2]); +int lfs_deorphan(lfs_t *lfs); + + +/// Block allocator /// +static int lfs_alloc_lookahead(void *p, lfs_block_t block) { + lfs_t *lfs = p; + + lfs_block_t off = ((block - lfs->free.off) + + lfs->cfg->block_count) % lfs->cfg->block_count; + + if (off < lfs->free.size) { + lfs->free.buffer[off / 32] |= 1U << (off % 32); + } + + return 0; +} + +static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { + while (true) { + while (lfs->free.i != lfs->free.size) { + lfs_block_t off = lfs->free.i; + lfs->free.i += 1; + lfs->free.ack -= 1; + + if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) { + // found a free block + *block = (lfs->free.off + off) % lfs->cfg->block_count; + + // eagerly find next off so an alloc ack can + // discredit old lookahead blocks + while (lfs->free.i != lfs->free.size && + (lfs->free.buffer[lfs->free.i / 32] + & (1U << (lfs->free.i % 32)))) { + lfs->free.i += 1; + lfs->free.ack -= 1; + } + + return 0; + } + } + + // check if we have looked at all blocks since last ack + if (lfs->free.ack == 0) { + LFS_WARN("No more free space %" PRIu32, + lfs->free.i + lfs->free.off); + return LFS_ERR_NOSPC; + } + + lfs->free.off = (lfs->free.off + lfs->free.size) + % lfs->cfg->block_count; + lfs->free.size = lfs_min(lfs->cfg->lookahead, lfs->free.ack); + lfs->free.i = 0; + + // find mask of free blocks from tree + memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8); + int err = lfs_traverse(lfs, lfs_alloc_lookahead, lfs); + if (err) { + return err; + } + } +} + +static void lfs_alloc_ack(lfs_t *lfs) { + lfs->free.ack = lfs->cfg->block_count; +} + + +/// Endian swapping functions /// +static void lfs_dir_fromle32(struct lfs_disk_dir *d) { + d->rev = lfs_fromle32(d->rev); + d->size = lfs_fromle32(d->size); + d->tail[0] = lfs_fromle32(d->tail[0]); + d->tail[1] = lfs_fromle32(d->tail[1]); +} + +static void lfs_dir_tole32(struct lfs_disk_dir *d) { + d->rev = lfs_tole32(d->rev); + d->size = lfs_tole32(d->size); + d->tail[0] = lfs_tole32(d->tail[0]); + d->tail[1] = lfs_tole32(d->tail[1]); +} + +static void lfs_entry_fromle32(struct lfs_disk_entry *d) { + d->u.dir[0] = lfs_fromle32(d->u.dir[0]); + d->u.dir[1] = lfs_fromle32(d->u.dir[1]); +} + +static void lfs_entry_tole32(struct lfs_disk_entry *d) { + d->u.dir[0] = lfs_tole32(d->u.dir[0]); + d->u.dir[1] = lfs_tole32(d->u.dir[1]); +} + +static void lfs_superblock_fromle32(struct lfs_disk_superblock *d) { + d->root[0] = lfs_fromle32(d->root[0]); + d->root[1] = lfs_fromle32(d->root[1]); + d->block_size = lfs_fromle32(d->block_size); + d->block_count = lfs_fromle32(d->block_count); + d->version = lfs_fromle32(d->version); +} + +static void lfs_superblock_tole32(struct lfs_disk_superblock *d) { + d->root[0] = lfs_tole32(d->root[0]); + d->root[1] = lfs_tole32(d->root[1]); + d->block_size = lfs_tole32(d->block_size); + d->block_count = lfs_tole32(d->block_count); + d->version = lfs_tole32(d->version); +} + + +/// Metadata pair and directory operations /// +static inline void lfs_pairswap(lfs_block_t pair[2]) { + lfs_block_t t = pair[0]; + pair[0] = pair[1]; + pair[1] = t; +} + +static inline bool lfs_pairisnull(const lfs_block_t pair[2]) { + return pair[0] == 0xffffffff || pair[1] == 0xffffffff; +} + +static inline int lfs_paircmp( + const lfs_block_t paira[2], + const lfs_block_t pairb[2]) { + return !(paira[0] == pairb[0] || paira[1] == pairb[1] || + paira[0] == pairb[1] || paira[1] == pairb[0]); +} + +static inline bool lfs_pairsync( + const lfs_block_t paira[2], + const lfs_block_t pairb[2]) { + return (paira[0] == pairb[0] && paira[1] == pairb[1]) || + (paira[0] == pairb[1] && paira[1] == pairb[0]); +} + +static inline lfs_size_t lfs_entry_size(const lfs_entry_t *entry) { + return 4 + entry->d.elen + entry->d.alen + entry->d.nlen; +} + +static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir) { + // allocate pair of dir blocks + for (int i = 0; i < 2; i++) { + int err = lfs_alloc(lfs, &dir->pair[i]); + if (err) { + return err; + } + } + + // rather than clobbering one of the blocks we just pretend + // the revision may be valid + int err = lfs_bd_read(lfs, dir->pair[0], 0, &dir->d.rev, 4); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + if (err != LFS_ERR_CORRUPT) { + dir->d.rev = lfs_fromle32(dir->d.rev); + } + + // set defaults + dir->d.rev += 1; + dir->d.size = sizeof(dir->d)+4; + dir->d.tail[0] = 0xffffffff; + dir->d.tail[1] = 0xffffffff; + dir->off = sizeof(dir->d); + + // don't write out yet, let caller take care of that + return 0; +} + +static int lfs_dir_fetch(lfs_t *lfs, + lfs_dir_t *dir, const lfs_block_t pair[2]) { + // copy out pair, otherwise may be aliasing dir + const lfs_block_t tpair[2] = {pair[0], pair[1]}; + bool valid = false; + + // check both blocks for the most recent revision + for (int i = 0; i < 2; i++) { + struct lfs_disk_dir test; + int err = lfs_bd_read(lfs, tpair[i], 0, &test, sizeof(test)); + lfs_dir_fromle32(&test); + if (err) { + if (err == LFS_ERR_CORRUPT) { + continue; + } + return err; + } + + if (valid && lfs_scmp(test.rev, dir->d.rev) < 0) { + continue; + } + + if ((0x7fffffff & test.size) < sizeof(test)+4 || + (0x7fffffff & test.size) > lfs->cfg->block_size) { + continue; + } + + uint32_t crc = 0xffffffff; + lfs_dir_tole32(&test); + lfs_crc(&crc, &test, sizeof(test)); + lfs_dir_fromle32(&test); + err = lfs_bd_crc(lfs, tpair[i], sizeof(test), + (0x7fffffff & test.size) - sizeof(test), &crc); + if (err) { + if (err == LFS_ERR_CORRUPT) { + continue; + } + return err; + } + + if (crc != 0) { + continue; + } + + valid = true; + + // setup dir in case it's valid + dir->pair[0] = tpair[(i+0) % 2]; + dir->pair[1] = tpair[(i+1) % 2]; + dir->off = sizeof(dir->d); + dir->d = test; + } + + if (!valid) { + LFS_ERROR("Corrupted dir pair at %" PRIu32 " %" PRIu32 , + tpair[0], tpair[1]); + return LFS_ERR_CORRUPT; + } + + return 0; +} + +struct lfs_region { + lfs_off_t oldoff; + lfs_size_t oldlen; + const void *newdata; + lfs_size_t newlen; +}; + +static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir, + const struct lfs_region *regions, int count) { + // increment revision count + dir->d.rev += 1; + + // keep pairs in order such that pair[0] is most recent + lfs_pairswap(dir->pair); + for (int i = 0; i < count; i++) { + dir->d.size += regions[i].newlen - regions[i].oldlen; + } + + const lfs_block_t oldpair[2] = {dir->pair[0], dir->pair[1]}; + bool relocated = false; + + while (true) { + if (true) { + int err = lfs_bd_erase(lfs, dir->pair[0]); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + uint32_t crc = 0xffffffff; + lfs_dir_tole32(&dir->d); + lfs_crc(&crc, &dir->d, sizeof(dir->d)); + err = lfs_bd_prog(lfs, dir->pair[0], 0, &dir->d, sizeof(dir->d)); + lfs_dir_fromle32(&dir->d); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + int i = 0; + lfs_off_t oldoff = sizeof(dir->d); + lfs_off_t newoff = sizeof(dir->d); + while (newoff < (0x7fffffff & dir->d.size)-4) { + if (i < count && regions[i].oldoff == oldoff) { + lfs_crc(&crc, regions[i].newdata, regions[i].newlen); + err = lfs_bd_prog(lfs, dir->pair[0], + newoff, regions[i].newdata, regions[i].newlen); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + oldoff += regions[i].oldlen; + newoff += regions[i].newlen; + i += 1; + } else { + uint8_t data; + err = lfs_bd_read(lfs, oldpair[1], oldoff, &data, 1); + if (err) { + return err; + } + + lfs_crc(&crc, &data, 1); + err = lfs_bd_prog(lfs, dir->pair[0], newoff, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + oldoff += 1; + newoff += 1; + } + } + + crc = lfs_tole32(crc); + err = lfs_bd_prog(lfs, dir->pair[0], newoff, &crc, 4); + crc = lfs_fromle32(crc); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + err = lfs_bd_sync(lfs); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // successful commit, check checksum to make sure + uint32_t ncrc = 0xffffffff; + err = lfs_bd_crc(lfs, dir->pair[0], 0, + (0x7fffffff & dir->d.size)-4, &ncrc); + if (err) { + return err; + } + + if (ncrc != crc) { + goto relocate; + } + } + + break; +relocate: + //commit was corrupted + LFS_DEBUG("Bad block at %" PRIu32, dir->pair[0]); + + // drop caches and prepare to relocate block + relocated = true; + lfs_cache_drop(lfs, &lfs->pcache); + + // can't relocate superblock, filesystem is now frozen + if (lfs_paircmp(oldpair, (const lfs_block_t[2]){0, 1}) == 0) { + LFS_WARN("Superblock %" PRIu32 " has become unwritable", + oldpair[0]); + return LFS_ERR_CORRUPT; + } + + // relocate half of pair + int err = lfs_alloc(lfs, &dir->pair[0]); + if (err) { + return err; + } + } + + if (relocated) { + // update references if we relocated + LFS_DEBUG("Relocating %" PRIu32 " %" PRIu32 " to %" PRIu32 " %" PRIu32, + oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]); + int err = lfs_relocate(lfs, oldpair, dir->pair); + if (err) { + return err; + } + } + + // shift over any directories that are affected + for (lfs_dir_t *d = lfs->dirs; d; d = d->next) { + if (lfs_paircmp(d->pair, dir->pair) == 0) { + d->pair[0] = dir->pair[0]; + d->pair[1] = dir->pair[1]; + } + } + + return 0; +} + +static int lfs_dir_update(lfs_t *lfs, lfs_dir_t *dir, + lfs_entry_t *entry, const void *data) { + lfs_entry_tole32(&entry->d); + int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){ + {entry->off, sizeof(entry->d), &entry->d, sizeof(entry->d)}, + {entry->off+sizeof(entry->d), entry->d.nlen, data, entry->d.nlen} + }, data ? 2 : 1); + lfs_entry_fromle32(&entry->d); + return err; +} + +static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir, + lfs_entry_t *entry, const void *data) { + // check if we fit, if top bit is set we do not and move on + while (true) { + if (dir->d.size + lfs_entry_size(entry) <= lfs->cfg->block_size) { + entry->off = dir->d.size - 4; + + lfs_entry_tole32(&entry->d); + int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){ + {entry->off, 0, &entry->d, sizeof(entry->d)}, + {entry->off, 0, data, entry->d.nlen} + }, 2); + lfs_entry_fromle32(&entry->d); + return err; + } + + // we need to allocate a new dir block + if (!(0x80000000 & dir->d.size)) { + lfs_dir_t olddir = *dir; + int err = lfs_dir_alloc(lfs, dir); + if (err) { + return err; + } + + dir->d.tail[0] = olddir.d.tail[0]; + dir->d.tail[1] = olddir.d.tail[1]; + entry->off = dir->d.size - 4; + lfs_entry_tole32(&entry->d); + err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){ + {entry->off, 0, &entry->d, sizeof(entry->d)}, + {entry->off, 0, data, entry->d.nlen} + }, 2); + lfs_entry_fromle32(&entry->d); + if (err) { + return err; + } + + olddir.d.size |= 0x80000000; + olddir.d.tail[0] = dir->pair[0]; + olddir.d.tail[1] = dir->pair[1]; + return lfs_dir_commit(lfs, &olddir, NULL, 0); + } + + int err = lfs_dir_fetch(lfs, dir, dir->d.tail); + if (err) { + return err; + } + } +} + +static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { + // check if we should just drop the directory block + if ((dir->d.size & 0x7fffffff) == sizeof(dir->d)+4 + + lfs_entry_size(entry)) { + lfs_dir_t pdir; + int res = lfs_pred(lfs, dir->pair, &pdir); + if (res < 0) { + return res; + } + + if (pdir.d.size & 0x80000000) { + pdir.d.size &= dir->d.size | 0x7fffffff; + pdir.d.tail[0] = dir->d.tail[0]; + pdir.d.tail[1] = dir->d.tail[1]; + return lfs_dir_commit(lfs, &pdir, NULL, 0); + } + } + + // shift out the entry + int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){ + {entry->off, lfs_entry_size(entry), NULL, 0}, + }, 1); + if (err) { + return err; + } + + // shift over any files/directories that are affected + for (lfs_file_t *f = lfs->files; f; f = f->next) { + if (lfs_paircmp(f->pair, dir->pair) == 0) { + if (f->poff == entry->off) { + f->pair[0] = 0xffffffff; + f->pair[1] = 0xffffffff; + } else if (f->poff > entry->off) { + f->poff -= lfs_entry_size(entry); + } + } + } + + for (lfs_dir_t *d = lfs->dirs; d; d = d->next) { + if (lfs_paircmp(d->pair, dir->pair) == 0) { + if (d->off > entry->off) { + d->off -= lfs_entry_size(entry); + d->pos -= lfs_entry_size(entry); + } + } + } + + return 0; +} + +static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { + while (dir->off + sizeof(entry->d) > (0x7fffffff & dir->d.size)-4) { + if (!(0x80000000 & dir->d.size)) { + entry->off = dir->off; + return LFS_ERR_NOENT; + } + + int err = lfs_dir_fetch(lfs, dir, dir->d.tail); + if (err) { + return err; + } + + dir->off = sizeof(dir->d); + dir->pos += sizeof(dir->d) + 4; + } + + int err = lfs_bd_read(lfs, dir->pair[0], dir->off, + &entry->d, sizeof(entry->d)); + lfs_entry_fromle32(&entry->d); + if (err) { + return err; + } + + entry->off = dir->off; + dir->off += lfs_entry_size(entry); + dir->pos += lfs_entry_size(entry); + return 0; +} + +static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, + lfs_entry_t *entry, const char **path) { + const char *pathname = *path; + size_t pathlen; + entry->d.type = LFS_TYPE_DIR; + entry->d.elen = sizeof(entry->d) - 4; + entry->d.alen = 0; + entry->d.nlen = 0; + entry->d.u.dir[0] = lfs->root[0]; + entry->d.u.dir[1] = lfs->root[1]; + + while (true) { +nextname: + // skip slashes + pathname += strspn(pathname, "/"); + pathlen = strcspn(pathname, "/"); + + // skip '.' and root '..' + if ((pathlen == 1 && memcmp(pathname, ".", 1) == 0) || + (pathlen == 2 && memcmp(pathname, "..", 2) == 0)) { + pathname += pathlen; + goto nextname; + } + + // skip if matched by '..' in name + const char *suffix = pathname + pathlen; + size_t sufflen; + int depth = 1; + while (true) { + suffix += strspn(suffix, "/"); + sufflen = strcspn(suffix, "/"); + if (sufflen == 0) { + break; + } + + if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) { + depth -= 1; + if (depth == 0) { + pathname = suffix + sufflen; + goto nextname; + } + } else { + depth += 1; + } + + suffix += sufflen; + } + + // found path + if (pathname[0] == '\0') { + return 0; + } + + // update what we've found + *path = pathname; + + // continue on if we hit a directory + if (entry->d.type != LFS_TYPE_DIR) { + return LFS_ERR_NOTDIR; + } + + int err = lfs_dir_fetch(lfs, dir, entry->d.u.dir); + if (err) { + return err; + } + + // find entry matching name + while (true) { + err = lfs_dir_next(lfs, dir, entry); + if (err) { + return err; + } + + if (((0x7f & entry->d.type) != LFS_TYPE_REG && + (0x7f & entry->d.type) != LFS_TYPE_DIR) || + entry->d.nlen != pathlen) { + continue; + } + + int res = lfs_bd_cmp(lfs, dir->pair[0], + entry->off + 4+entry->d.elen+entry->d.alen, + pathname, pathlen); + if (res < 0) { + return res; + } + + // found match + if (res) { + break; + } + } + + // check that entry has not been moved + if (!lfs->moving && entry->d.type & 0x80) { + int moved = lfs_moved(lfs, &entry->d.u); + if (moved < 0 || moved) { + return (moved < 0) ? moved : LFS_ERR_NOENT; + } + + entry->d.type &= ~0x80; + } + + // to next name + pathname += pathlen; + } +} + + +/// Top level directory operations /// +int lfs_mkdir(lfs_t *lfs, const char *path) { + // deorphan if we haven't yet, needed at most once after poweron + if (!lfs->deorphaned) { + int err = lfs_deorphan(lfs); + if (err) { + return err; + } + } + + // fetch parent directory + lfs_dir_t cwd; + lfs_entry_t entry; + int err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err != LFS_ERR_NOENT || strchr(path, '/') != NULL) { + return err ? err : LFS_ERR_EXIST; + } + + // build up new directory + lfs_alloc_ack(lfs); + + lfs_dir_t dir; + err = lfs_dir_alloc(lfs, &dir); + if (err) { + return err; + } + dir.d.tail[0] = cwd.d.tail[0]; + dir.d.tail[1] = cwd.d.tail[1]; + + err = lfs_dir_commit(lfs, &dir, NULL, 0); + if (err) { + return err; + } + + entry.d.type = LFS_TYPE_DIR; + entry.d.elen = sizeof(entry.d) - 4; + entry.d.alen = 0; + entry.d.nlen = strlen(path); + entry.d.u.dir[0] = dir.pair[0]; + entry.d.u.dir[1] = dir.pair[1]; + + cwd.d.tail[0] = dir.pair[0]; + cwd.d.tail[1] = dir.pair[1]; + + err = lfs_dir_append(lfs, &cwd, &entry, path); + if (err) { + return err; + } + + lfs_alloc_ack(lfs); + return 0; +} + +int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { + dir->pair[0] = lfs->root[0]; + dir->pair[1] = lfs->root[1]; + + lfs_entry_t entry; + int err = lfs_dir_find(lfs, dir, &entry, &path); + if (err) { + return err; + } else if (entry.d.type != LFS_TYPE_DIR) { + return LFS_ERR_NOTDIR; + } + + err = lfs_dir_fetch(lfs, dir, entry.d.u.dir); + if (err) { + return err; + } + + // setup head dir + // special offset for '.' and '..' + dir->head[0] = dir->pair[0]; + dir->head[1] = dir->pair[1]; + dir->pos = sizeof(dir->d) - 2; + dir->off = sizeof(dir->d); + + // add to list of directories + dir->next = lfs->dirs; + lfs->dirs = dir; + + return 0; +} + +int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir) { + // remove from list of directories + for (lfs_dir_t **p = &lfs->dirs; *p; p = &(*p)->next) { + if (*p == dir) { + *p = dir->next; + break; + } + } + + return 0; +} + +int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { + memset(info, 0, sizeof(*info)); + + // special offset for '.' and '..' + if (dir->pos == sizeof(dir->d) - 2) { + info->type = LFS_TYPE_DIR; + strcpy(info->name, "."); + dir->pos += 1; + return 1; + } else if (dir->pos == sizeof(dir->d) - 1) { + info->type = LFS_TYPE_DIR; + strcpy(info->name, ".."); + dir->pos += 1; + return 1; + } + + lfs_entry_t entry; + while (true) { + int err = lfs_dir_next(lfs, dir, &entry); + if (err) { + return (err == LFS_ERR_NOENT) ? 0 : err; + } + + if ((0x7f & entry.d.type) != LFS_TYPE_REG && + (0x7f & entry.d.type) != LFS_TYPE_DIR) { + continue; + } + + // check that entry has not been moved + if (entry.d.type & 0x80) { + int moved = lfs_moved(lfs, &entry.d.u); + if (moved < 0) { + return moved; + } + + if (moved) { + continue; + } + + entry.d.type &= ~0x80; + } + + break; + } + + info->type = entry.d.type; + if (info->type == LFS_TYPE_REG) { + info->size = entry.d.u.file.size; + } + + int err = lfs_bd_read(lfs, dir->pair[0], + entry.off + 4+entry.d.elen+entry.d.alen, + info->name, entry.d.nlen); + if (err) { + return err; + } + + return 1; +} + +int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) { + // simply walk from head dir + int err = lfs_dir_rewind(lfs, dir); + if (err) { + return err; + } + dir->pos = off; + + while (off > (0x7fffffff & dir->d.size)) { + off -= 0x7fffffff & dir->d.size; + if (!(0x80000000 & dir->d.size)) { + return LFS_ERR_INVAL; + } + + err = lfs_dir_fetch(lfs, dir, dir->d.tail); + if (err) { + return err; + } + } + + dir->off = off; + return 0; +} + +lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) { + (void)lfs; + return dir->pos; +} + +int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) { + // reload the head dir + int err = lfs_dir_fetch(lfs, dir, dir->head); + if (err) { + return err; + } + + dir->pair[0] = dir->head[0]; + dir->pair[1] = dir->head[1]; + dir->pos = sizeof(dir->d) - 2; + dir->off = sizeof(dir->d); + return 0; +} + + +/// File index list operations /// +static int lfs_ctz_index(lfs_t *lfs, lfs_off_t *off) { + lfs_off_t size = *off; + lfs_off_t b = lfs->cfg->block_size - 2*4; + lfs_off_t i = size / b; + if (i == 0) { + return 0; + } + + i = (size - 4*(lfs_popc(i-1)+2)) / b; + *off = size - b*i - 4*lfs_popc(i); + return i; +} + +static int lfs_ctz_find(lfs_t *lfs, + lfs_cache_t *rcache, const lfs_cache_t *pcache, + lfs_block_t head, lfs_size_t size, + lfs_size_t pos, lfs_block_t *block, lfs_off_t *off) { + if (size == 0) { + *block = 0xffffffff; + *off = 0; + return 0; + } + + lfs_off_t current = lfs_ctz_index(lfs, &(lfs_off_t){size-1}); + lfs_off_t target = lfs_ctz_index(lfs, &pos); + + while (current > target) { + lfs_size_t skip = lfs_min( + lfs_npw2(current-target+1) - 1, + lfs_ctz(current)); + + int err = lfs_cache_read(lfs, rcache, pcache, head, 4*skip, &head, 4); + head = lfs_fromle32(head); + if (err) { + return err; + } + + LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count); + current -= 1 << skip; + } + + *block = head; + *off = pos; + return 0; +} + +static int lfs_ctz_extend(lfs_t *lfs, + lfs_cache_t *rcache, lfs_cache_t *pcache, + lfs_block_t head, lfs_size_t size, + lfs_block_t *block, lfs_off_t *off) { + while (true) { + // go ahead and grab a block + lfs_block_t nblock; + int err = lfs_alloc(lfs, &nblock); + if (err) { + return err; + } + LFS_ASSERT(nblock >= 2 && nblock <= lfs->cfg->block_count); + + if (true) { + err = lfs_bd_erase(lfs, nblock); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + if (size == 0) { + *block = nblock; + *off = 0; + return 0; + } + + size -= 1; + lfs_off_t index = lfs_ctz_index(lfs, &size); + size += 1; + + // just copy out the last block if it is incomplete + if (size != lfs->cfg->block_size) { + for (lfs_off_t i = 0; i < size; i++) { + uint8_t data; + err = lfs_cache_read(lfs, rcache, NULL, + head, i, &data, 1); + if (err) { + return err; + } + + err = lfs_cache_prog(lfs, pcache, rcache, + nblock, i, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + *block = nblock; + *off = size; + return 0; + } + + // append block + index += 1; + lfs_size_t skips = lfs_ctz(index) + 1; + + for (lfs_off_t i = 0; i < skips; i++) { + head = lfs_tole32(head); + err = lfs_cache_prog(lfs, pcache, rcache, + nblock, 4*i, &head, 4); + head = lfs_fromle32(head); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + if (i != skips-1) { + err = lfs_cache_read(lfs, rcache, NULL, + head, 4*i, &head, 4); + head = lfs_fromle32(head); + if (err) { + return err; + } + } + + LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count); + } + + *block = nblock; + *off = 4*skips; + return 0; + } + +relocate: + LFS_DEBUG("Bad block at %" PRIu32, nblock); + + // just clear cache and try a new block + lfs_cache_drop(lfs, &lfs->pcache); + } +} + +static int lfs_ctz_traverse(lfs_t *lfs, + lfs_cache_t *rcache, const lfs_cache_t *pcache, + lfs_block_t head, lfs_size_t size, + int (*cb)(void*, lfs_block_t), void *data) { + if (size == 0) { + return 0; + } + + lfs_off_t index = lfs_ctz_index(lfs, &(lfs_off_t){size-1}); + + while (true) { + int err = cb(data, head); + if (err) { + return err; + } + + if (index == 0) { + return 0; + } + + lfs_block_t heads[2]; + int count = 2 - (index & 1); + err = lfs_cache_read(lfs, rcache, pcache, head, 0, &heads, count*4); + heads[0] = lfs_fromle32(heads[0]); + heads[1] = lfs_fromle32(heads[1]); + if (err) { + return err; + } + + for (int i = 0; i < count-1; i++) { + err = cb(data, heads[i]); + if (err) { + return err; + } + } + + head = heads[count-1]; + index -= count; + } +} + + +/// Top level file operations /// +int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags, + const struct lfs_file_config *cfg) { + // deorphan if we haven't yet, needed at most once after poweron + if ((flags & 3) != LFS_O_RDONLY && !lfs->deorphaned) { + int err = lfs_deorphan(lfs); + if (err) { + return err; + } + } + + // allocate entry for file if it doesn't exist + lfs_dir_t cwd; + lfs_entry_t entry; + int err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err && (err != LFS_ERR_NOENT || strchr(path, '/') != NULL)) { + return err; + } + + if (err == LFS_ERR_NOENT) { + if (!(flags & LFS_O_CREAT)) { + return LFS_ERR_NOENT; + } + + // create entry to remember name + entry.d.type = LFS_TYPE_REG; + entry.d.elen = sizeof(entry.d) - 4; + entry.d.alen = 0; + entry.d.nlen = strlen(path); + entry.d.u.file.head = 0xffffffff; + entry.d.u.file.size = 0; + err = lfs_dir_append(lfs, &cwd, &entry, path); + if (err) { + return err; + } + } else if (entry.d.type == LFS_TYPE_DIR) { + return LFS_ERR_ISDIR; + } else if (flags & LFS_O_EXCL) { + return LFS_ERR_EXIST; + } + + // setup file struct + file->cfg = cfg; + file->pair[0] = cwd.pair[0]; + file->pair[1] = cwd.pair[1]; + file->poff = entry.off; + file->head = entry.d.u.file.head; + file->size = entry.d.u.file.size; + file->flags = flags; + file->pos = 0; + + if (flags & LFS_O_TRUNC) { + if (file->size != 0) { + file->flags |= LFS_F_DIRTY; + } + file->head = 0xffffffff; + file->size = 0; + } + + // allocate buffer if needed + file->cache.block = 0xffffffff; + if (file->cfg && file->cfg->buffer) { + file->cache.buffer = file->cfg->buffer; + } else if (lfs->cfg->file_buffer) { + if (lfs->files) { + // already in use + return LFS_ERR_NOMEM; + } + file->cache.buffer = lfs->cfg->file_buffer; + } else if ((file->flags & 3) == LFS_O_RDONLY) { + file->cache.buffer = lfs_malloc(lfs->cfg->read_size); + if (!file->cache.buffer) { + return LFS_ERR_NOMEM; + } + } else { + file->cache.buffer = lfs_malloc(lfs->cfg->prog_size); + if (!file->cache.buffer) { + return LFS_ERR_NOMEM; + } + } + + // zero to avoid information leak + lfs_cache_drop(lfs, &file->cache); + if ((file->flags & 3) != LFS_O_RDONLY) { + lfs_cache_zero(lfs, &file->cache); + } + + // add to list of files + file->next = lfs->files; + lfs->files = file; + + return 0; +} + +int lfs_file_open(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags) { + return lfs_file_opencfg(lfs, file, path, flags, NULL); +} + +int lfs_file_close(lfs_t *lfs, lfs_file_t *file) { + int err = lfs_file_sync(lfs, file); + + // remove from list of files + for (lfs_file_t **p = &lfs->files; *p; p = &(*p)->next) { + if (*p == file) { + *p = file->next; + break; + } + } + + // clean up memory + if (!(file->cfg && file->cfg->buffer) && !lfs->cfg->file_buffer) { + lfs_free(file->cache.buffer); + } + + return err; +} + +static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) { +relocate: + LFS_DEBUG("Bad block at %" PRIu32, file->block); + + // just relocate what exists into new block + lfs_block_t nblock; + int err = lfs_alloc(lfs, &nblock); + if (err) { + return err; + } + + err = lfs_bd_erase(lfs, nblock); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // either read from dirty cache or disk + for (lfs_off_t i = 0; i < file->off; i++) { + uint8_t data; + err = lfs_cache_read(lfs, &lfs->rcache, &file->cache, + file->block, i, &data, 1); + if (err) { + return err; + } + + err = lfs_cache_prog(lfs, &lfs->pcache, &lfs->rcache, + nblock, i, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + // copy over new state of file + memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->prog_size); + file->cache.block = lfs->pcache.block; + file->cache.off = lfs->pcache.off; + lfs_cache_zero(lfs, &lfs->pcache); + + file->block = nblock; + return 0; +} + +static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { + if (file->flags & LFS_F_READING) { + // just drop read cache + lfs_cache_drop(lfs, &file->cache); + file->flags &= ~LFS_F_READING; + } + + if (file->flags & LFS_F_WRITING) { + lfs_off_t pos = file->pos; + + // copy over anything after current branch + lfs_file_t orig = { + .head = file->head, + .size = file->size, + .flags = LFS_O_RDONLY, + .pos = file->pos, + .cache = lfs->rcache, + }; + lfs_cache_drop(lfs, &lfs->rcache); + + while (file->pos < file->size) { + // copy over a byte at a time, leave it up to caching + // to make this efficient + uint8_t data; + lfs_ssize_t res = lfs_file_read(lfs, &orig, &data, 1); + if (res < 0) { + return res; + } + + res = lfs_file_write(lfs, file, &data, 1); + if (res < 0) { + return res; + } + + // keep our reference to the rcache in sync + if (lfs->rcache.block != 0xffffffff) { + lfs_cache_drop(lfs, &orig.cache); + lfs_cache_drop(lfs, &lfs->rcache); + } + } + + // write out what we have + while (true) { + int err = lfs_cache_flush(lfs, &file->cache, &lfs->rcache); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + break; +relocate: + err = lfs_file_relocate(lfs, file); + if (err) { + return err; + } + } + + // actual file updates + file->head = file->block; + file->size = file->pos; + file->flags &= ~LFS_F_WRITING; + file->flags |= LFS_F_DIRTY; + + file->pos = pos; + } + + return 0; +} + +int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + + if ((file->flags & LFS_F_DIRTY) && + !(file->flags & LFS_F_ERRED) && + !lfs_pairisnull(file->pair)) { + // update dir entry + lfs_dir_t cwd; + err = lfs_dir_fetch(lfs, &cwd, file->pair); + if (err) { + return err; + } + + lfs_entry_t entry = {.off = file->poff}; + err = lfs_bd_read(lfs, cwd.pair[0], entry.off, + &entry.d, sizeof(entry.d)); + lfs_entry_fromle32(&entry.d); + if (err) { + return err; + } + + LFS_ASSERT(entry.d.type == LFS_TYPE_REG); + entry.d.u.file.head = file->head; + entry.d.u.file.size = file->size; + + err = lfs_dir_update(lfs, &cwd, &entry, NULL); + if (err) { + return err; + } + + file->flags &= ~LFS_F_DIRTY; + } + + return 0; +} + +lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size) { + uint8_t *data = buffer; + lfs_size_t nsize = size; + + if ((file->flags & 3) == LFS_O_WRONLY) { + return LFS_ERR_BADF; + } + + if (file->flags & LFS_F_WRITING) { + // flush out any writes + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + } + + if (file->pos >= file->size) { + // eof if past end + return 0; + } + + size = lfs_min(size, file->size - file->pos); + nsize = size; + + while (nsize > 0) { + // check if we need a new block + if (!(file->flags & LFS_F_READING) || + file->off == lfs->cfg->block_size) { + int err = lfs_ctz_find(lfs, &file->cache, NULL, + file->head, file->size, + file->pos, &file->block, &file->off); + if (err) { + return err; + } + + file->flags |= LFS_F_READING; + } + + // read as much as we can in current block + lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); + int err = lfs_cache_read(lfs, &file->cache, NULL, + file->block, file->off, data, diff); + if (err) { + return err; + } + + file->pos += diff; + file->off += diff; + data += diff; + nsize -= diff; + } + + return size; +} + +lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + lfs_size_t nsize = size; + + if ((file->flags & 3) == LFS_O_RDONLY) { + return LFS_ERR_BADF; + } + + if (file->flags & LFS_F_READING) { + // drop any reads + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + } + + if ((file->flags & LFS_O_APPEND) && file->pos < file->size) { + file->pos = file->size; + } + + if (file->pos + size > LFS_FILE_MAX) { + // larger than file limit? + return LFS_ERR_FBIG; + } + + if (!(file->flags & LFS_F_WRITING) && file->pos > file->size) { + // fill with zeros + lfs_off_t pos = file->pos; + file->pos = file->size; + + while (file->pos < pos) { + lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1); + if (res < 0) { + return res; + } + } + } + + while (nsize > 0) { + // check if we need a new block + if (!(file->flags & LFS_F_WRITING) || + file->off == lfs->cfg->block_size) { + if (!(file->flags & LFS_F_WRITING) && file->pos > 0) { + // find out which block we're extending from + int err = lfs_ctz_find(lfs, &file->cache, NULL, + file->head, file->size, + file->pos-1, &file->block, &file->off); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + + // mark cache as dirty since we may have read data into it + lfs_cache_zero(lfs, &file->cache); + } + + // extend file with new blocks + lfs_alloc_ack(lfs); + int err = lfs_ctz_extend(lfs, &lfs->rcache, &file->cache, + file->block, file->pos, + &file->block, &file->off); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + + file->flags |= LFS_F_WRITING; + } + + // program as much as we can in current block + lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); + while (true) { + int err = lfs_cache_prog(lfs, &file->cache, &lfs->rcache, + file->block, file->off, data, diff); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + file->flags |= LFS_F_ERRED; + return err; + } + + break; +relocate: + err = lfs_file_relocate(lfs, file); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + } + + file->pos += diff; + file->off += diff; + data += diff; + nsize -= diff; + + lfs_alloc_ack(lfs); + } + + file->flags &= ~LFS_F_ERRED; + return size; +} + +lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence) { + // write out everything beforehand, may be noop if rdonly + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + + // find new pos + lfs_soff_t npos = file->pos; + if (whence == LFS_SEEK_SET) { + npos = off; + } else if (whence == LFS_SEEK_CUR) { + npos = file->pos + off; + } else if (whence == LFS_SEEK_END) { + npos = file->size + off; + } + + if (npos < 0 || npos > LFS_FILE_MAX) { + // file position out of range + return LFS_ERR_INVAL; + } + + // update pos + file->pos = npos; + return npos; +} + +int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { + if ((file->flags & 3) == LFS_O_RDONLY) { + return LFS_ERR_BADF; + } + + lfs_off_t oldsize = lfs_file_size(lfs, file); + if (size < oldsize) { + // need to flush since directly changing metadata + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + + // lookup new head in ctz skip list + err = lfs_ctz_find(lfs, &file->cache, NULL, + file->head, file->size, + size, &file->head, &(lfs_off_t){0}); + if (err) { + return err; + } + + file->size = size; + file->flags |= LFS_F_DIRTY; + } else if (size > oldsize) { + lfs_off_t pos = file->pos; + + // flush+seek if not already at end + if (file->pos != oldsize) { + int err = lfs_file_seek(lfs, file, 0, LFS_SEEK_END); + if (err < 0) { + return err; + } + } + + // fill with zeros + while (file->pos < size) { + lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1); + if (res < 0) { + return res; + } + } + + // restore pos + int err = lfs_file_seek(lfs, file, pos, LFS_SEEK_SET); + if (err < 0) { + return err; + } + } + + return 0; +} + +lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) { + (void)lfs; + return file->pos; +} + +int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) { + lfs_soff_t res = lfs_file_seek(lfs, file, 0, LFS_SEEK_SET); + if (res < 0) { + return res; + } + + return 0; +} + +lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) { + (void)lfs; + if (file->flags & LFS_F_WRITING) { + return lfs_max(file->pos, file->size); + } else { + return file->size; + } +} + + +/// General fs operations /// +int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { + lfs_dir_t cwd; + lfs_entry_t entry; + int err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err) { + return err; + } + + memset(info, 0, sizeof(*info)); + info->type = entry.d.type; + if (info->type == LFS_TYPE_REG) { + info->size = entry.d.u.file.size; + } + + if (lfs_paircmp(entry.d.u.dir, lfs->root) == 0) { + strcpy(info->name, "/"); + } else { + err = lfs_bd_read(lfs, cwd.pair[0], + entry.off + 4+entry.d.elen+entry.d.alen, + info->name, entry.d.nlen); + if (err) { + return err; + } + } + + return 0; +} + +int lfs_remove(lfs_t *lfs, const char *path) { + // deorphan if we haven't yet, needed at most once after poweron + if (!lfs->deorphaned) { + int err = lfs_deorphan(lfs); + if (err) { + return err; + } + } + + lfs_dir_t cwd; + lfs_entry_t entry; + int err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err) { + return err; + } + + lfs_dir_t dir; + if (entry.d.type == LFS_TYPE_DIR) { + // must be empty before removal, checking size + // without masking top bit checks for any case where + // dir is not empty + err = lfs_dir_fetch(lfs, &dir, entry.d.u.dir); + if (err) { + return err; + } /* else if (dir.d.size != sizeof(dir.d)+4) { + return LFS_ERR_NOTEMPTY; + } adafruit: allow to remove non-empty folder, + According to below issue, comment these 2 line won't corrupt filesystem + https://github.com/ARMmbed/littlefs/issues/43 */ + } + + // remove the entry + err = lfs_dir_remove(lfs, &cwd, &entry); + if (err) { + return err; + } + + // if we were a directory, find pred, replace tail + if (entry.d.type == LFS_TYPE_DIR) { + int res = lfs_pred(lfs, dir.pair, &cwd); + if (res < 0) { + return res; + } + + LFS_ASSERT(res); // must have pred + cwd.d.tail[0] = dir.d.tail[0]; + cwd.d.tail[1] = dir.d.tail[1]; + + err = lfs_dir_commit(lfs, &cwd, NULL, 0); + if (err) { + return err; + } + } + + return 0; +} + +int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { + // deorphan if we haven't yet, needed at most once after poweron + if (!lfs->deorphaned) { + int err = lfs_deorphan(lfs); + if (err) { + return err; + } + } + + // find old entry + lfs_dir_t oldcwd; + lfs_entry_t oldentry; + int err = lfs_dir_find(lfs, &oldcwd, &oldentry, &(const char *){oldpath}); + if (err) { + return err; + } + + // mark as moving + oldentry.d.type |= 0x80; + err = lfs_dir_update(lfs, &oldcwd, &oldentry, NULL); + if (err) { + return err; + } + + // allocate new entry + lfs_dir_t newcwd; + lfs_entry_t preventry; + err = lfs_dir_find(lfs, &newcwd, &preventry, &newpath); + if (err && (err != LFS_ERR_NOENT || strchr(newpath, '/') != NULL)) { + return err; + } + + // must have same type + bool prevexists = (err != LFS_ERR_NOENT); + if (prevexists && preventry.d.type != (0x7f & oldentry.d.type)) { + return LFS_ERR_ISDIR; + } + + lfs_dir_t dir; + if (prevexists && preventry.d.type == LFS_TYPE_DIR) { + // must be empty before removal, checking size + // without masking top bit checks for any case where + // dir is not empty + err = lfs_dir_fetch(lfs, &dir, preventry.d.u.dir); + if (err) { + return err; + } else if (dir.d.size != sizeof(dir.d)+4) { + return LFS_ERR_NOTEMPTY; + } + } + + // move to new location + lfs_entry_t newentry = preventry; + newentry.d = oldentry.d; + newentry.d.type &= ~0x80; + newentry.d.nlen = strlen(newpath); + + if (prevexists) { + err = lfs_dir_update(lfs, &newcwd, &newentry, newpath); + if (err) { + return err; + } + } else { + err = lfs_dir_append(lfs, &newcwd, &newentry, newpath); + if (err) { + return err; + } + } + + // fetch old pair again in case dir block changed + lfs->moving = true; + err = lfs_dir_find(lfs, &oldcwd, &oldentry, &oldpath); + if (err) { + return err; + } + lfs->moving = false; + + // remove old entry + err = lfs_dir_remove(lfs, &oldcwd, &oldentry); + if (err) { + return err; + } + + // if we were a directory, find pred, replace tail + if (prevexists && preventry.d.type == LFS_TYPE_DIR) { + int res = lfs_pred(lfs, dir.pair, &newcwd); + if (res < 0) { + return res; + } + + LFS_ASSERT(res); // must have pred + newcwd.d.tail[0] = dir.d.tail[0]; + newcwd.d.tail[1] = dir.d.tail[1]; + + err = lfs_dir_commit(lfs, &newcwd, NULL, 0); + if (err) { + return err; + } + } + + return 0; +} + + +/// Filesystem operations /// +static void lfs_deinit(lfs_t *lfs) { + // free allocated memory + if (!lfs->cfg->read_buffer) { + lfs_free(lfs->rcache.buffer); + } + + if (!lfs->cfg->prog_buffer) { + lfs_free(lfs->pcache.buffer); + } + + if (!lfs->cfg->lookahead_buffer) { + lfs_free(lfs->free.buffer); + } +} + +static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { + lfs->cfg = cfg; + + // setup read cache + if (lfs->cfg->read_buffer) { + lfs->rcache.buffer = lfs->cfg->read_buffer; + } else { + lfs->rcache.buffer = lfs_malloc(lfs->cfg->read_size); + if (!lfs->rcache.buffer) { + goto cleanup; + } + } + + // setup program cache + if (lfs->cfg->prog_buffer) { + lfs->pcache.buffer = lfs->cfg->prog_buffer; + } else { + lfs->pcache.buffer = lfs_malloc(lfs->cfg->prog_size); + if (!lfs->pcache.buffer) { + goto cleanup; + } + } + + // zero to avoid information leaks + lfs_cache_zero(lfs, &lfs->pcache); + lfs_cache_drop(lfs, &lfs->rcache); + + // setup lookahead, round down to nearest 32-bits + LFS_ASSERT(lfs->cfg->lookahead % 32 == 0); + LFS_ASSERT(lfs->cfg->lookahead > 0); + if (lfs->cfg->lookahead_buffer) { + lfs->free.buffer = lfs->cfg->lookahead_buffer; + } else { + lfs->free.buffer = lfs_malloc(lfs->cfg->lookahead/8); + if (!lfs->free.buffer) { + goto cleanup; + } + } + + // check that program and read sizes are multiples of the block size + LFS_ASSERT(lfs->cfg->prog_size % lfs->cfg->read_size == 0); + LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->prog_size == 0); + + // check that the block size is large enough to fit ctz pointers + LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4)) + <= lfs->cfg->block_size); + + // setup default state + lfs->root[0] = 0xffffffff; + lfs->root[1] = 0xffffffff; + lfs->files = NULL; + lfs->dirs = NULL; + lfs->deorphaned = false; + lfs->moving = false; + + return 0; + +cleanup: + lfs_deinit(lfs); + return LFS_ERR_NOMEM; +} + +int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { + int err = 0; + if (true) { + err = lfs_init(lfs, cfg); + if (err) { + return err; + } + + // create free lookahead + memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8); + lfs->free.off = 0; + lfs->free.size = lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count); + lfs->free.i = 0; + lfs_alloc_ack(lfs); + + // create superblock dir + lfs_dir_t superdir; + err = lfs_dir_alloc(lfs, &superdir); + if (err) { + goto cleanup; + } + + // write root directory + lfs_dir_t root; + err = lfs_dir_alloc(lfs, &root); + if (err) { + goto cleanup; + } + + err = lfs_dir_commit(lfs, &root, NULL, 0); + if (err) { + goto cleanup; + } + + lfs->root[0] = root.pair[0]; + lfs->root[1] = root.pair[1]; + + // write superblocks + lfs_superblock_t superblock = { + .off = sizeof(superdir.d), + .d.type = LFS_TYPE_SUPERBLOCK, + .d.elen = sizeof(superblock.d) - sizeof(superblock.d.magic) - 4, + .d.nlen = sizeof(superblock.d.magic), + .d.version = LFS_DISK_VERSION, + .d.magic = {"littlefs"}, + .d.block_size = lfs->cfg->block_size, + .d.block_count = lfs->cfg->block_count, + .d.root = {lfs->root[0], lfs->root[1]}, + }; + superdir.d.tail[0] = root.pair[0]; + superdir.d.tail[1] = root.pair[1]; + superdir.d.size = sizeof(superdir.d) + sizeof(superblock.d) + 4; + + // write both pairs to be safe + lfs_superblock_tole32(&superblock.d); + bool valid = false; + for (int i = 0; i < 2; i++) { + err = lfs_dir_commit(lfs, &superdir, (struct lfs_region[]){ + {sizeof(superdir.d), sizeof(superblock.d), + &superblock.d, sizeof(superblock.d)} + }, 1); + if (err && err != LFS_ERR_CORRUPT) { + goto cleanup; + } + + valid = valid || !err; + } + + if (!valid) { + err = LFS_ERR_CORRUPT; + goto cleanup; + } + + // sanity check that fetch works + err = lfs_dir_fetch(lfs, &superdir, (const lfs_block_t[2]){0, 1}); + if (err) { + goto cleanup; + } + + lfs_alloc_ack(lfs); + } + +cleanup: + lfs_deinit(lfs); + return err; +} + +int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { + int err = 0; + if (true) { + err = lfs_init(lfs, cfg); + if (err) { + return err; + } + + // setup free lookahead + lfs->free.off = 0; + lfs->free.size = 0; + lfs->free.i = 0; + lfs_alloc_ack(lfs); + + // load superblock + lfs_dir_t dir; + lfs_superblock_t superblock; + err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1}); + if (err && err != LFS_ERR_CORRUPT) { + goto cleanup; + } + + if (!err) { + err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d), + &superblock.d, sizeof(superblock.d)); + lfs_superblock_fromle32(&superblock.d); + if (err) { + goto cleanup; + } + + lfs->root[0] = superblock.d.root[0]; + lfs->root[1] = superblock.d.root[1]; + } + + if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) { + LFS_ERROR("Invalid superblock at %d %d", 0, 1); + err = LFS_ERR_CORRUPT; + goto cleanup; + } + + uint16_t major_version = (0xffff & (superblock.d.version >> 16)); + uint16_t minor_version = (0xffff & (superblock.d.version >> 0)); + if ((major_version != LFS_DISK_VERSION_MAJOR || + minor_version > LFS_DISK_VERSION_MINOR)) { + LFS_ERROR("Invalid version %d.%d", major_version, minor_version); + err = LFS_ERR_INVAL; + goto cleanup; + } + + return 0; + } + +cleanup: + + lfs_deinit(lfs); + return err; +} + +int lfs_unmount(lfs_t *lfs) { + lfs_deinit(lfs); + return 0; +} + + +/// Littlefs specific operations /// +int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { + if (lfs_pairisnull(lfs->root)) { + return 0; + } + + // iterate over metadata pairs + lfs_dir_t dir; + lfs_entry_t entry; + lfs_block_t cwd[2] = {0, 1}; + + while (true) { + for (int i = 0; i < 2; i++) { + int err = cb(data, cwd[i]); + if (err) { + return err; + } + } + + int err = lfs_dir_fetch(lfs, &dir, cwd); + if (err) { + return err; + } + + // iterate over contents + while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size)-4) { + err = lfs_bd_read(lfs, dir.pair[0], dir.off, + &entry.d, sizeof(entry.d)); + lfs_entry_fromle32(&entry.d); + if (err) { + return err; + } + + dir.off += lfs_entry_size(&entry); + if ((0x70 & entry.d.type) == (0x70 & LFS_TYPE_REG)) { + err = lfs_ctz_traverse(lfs, &lfs->rcache, NULL, + entry.d.u.file.head, entry.d.u.file.size, cb, data); + if (err) { + return err; + } + } + } + + cwd[0] = dir.d.tail[0]; + cwd[1] = dir.d.tail[1]; + + if (lfs_pairisnull(cwd)) { + break; + } + } + + // iterate over any open files + for (lfs_file_t *f = lfs->files; f; f = f->next) { + if (f->flags & LFS_F_DIRTY) { + int err = lfs_ctz_traverse(lfs, &lfs->rcache, &f->cache, + f->head, f->size, cb, data); + if (err) { + return err; + } + } + + if (f->flags & LFS_F_WRITING) { + int err = lfs_ctz_traverse(lfs, &lfs->rcache, &f->cache, + f->block, f->pos, cb, data); + if (err) { + return err; + } + } + } + + return 0; +} + +static int lfs_pred(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *pdir) { + if (lfs_pairisnull(lfs->root)) { + return 0; + } + + // iterate over all directory directory entries + int err = lfs_dir_fetch(lfs, pdir, (const lfs_block_t[2]){0, 1}); + if (err) { + return err; + } + + while (!lfs_pairisnull(pdir->d.tail)) { + if (lfs_paircmp(pdir->d.tail, dir) == 0) { + return true; + } + + err = lfs_dir_fetch(lfs, pdir, pdir->d.tail); + if (err) { + return err; + } + } + + return false; +} + +static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2], + lfs_dir_t *parent, lfs_entry_t *entry) { + if (lfs_pairisnull(lfs->root)) { + return 0; + } + + parent->d.tail[0] = 0; + parent->d.tail[1] = 1; + + // iterate over all directory directory entries + while (!lfs_pairisnull(parent->d.tail)) { + int err = lfs_dir_fetch(lfs, parent, parent->d.tail); + if (err) { + return err; + } + + while (true) { + err = lfs_dir_next(lfs, parent, entry); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err == LFS_ERR_NOENT) { + break; + } + + if (((0x70 & entry->d.type) == (0x70 & LFS_TYPE_DIR)) && + lfs_paircmp(entry->d.u.dir, dir) == 0) { + return true; + } + } + } + + return false; +} + +static int lfs_moved(lfs_t *lfs, const void *e) { + if (lfs_pairisnull(lfs->root)) { + return 0; + } + + // skip superblock + lfs_dir_t cwd; + int err = lfs_dir_fetch(lfs, &cwd, (const lfs_block_t[2]){0, 1}); + if (err) { + return err; + } + + // iterate over all directory directory entries + lfs_entry_t entry; + while (!lfs_pairisnull(cwd.d.tail)) { + err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail); + if (err) { + return err; + } + + while (true) { + err = lfs_dir_next(lfs, &cwd, &entry); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err == LFS_ERR_NOENT) { + break; + } + + if (!(0x80 & entry.d.type) && + memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) { + return true; + } + } + } + + return false; +} + +static int lfs_relocate(lfs_t *lfs, + const lfs_block_t oldpair[2], const lfs_block_t newpair[2]) { + // find parent + lfs_dir_t parent; + lfs_entry_t entry; + int res = lfs_parent(lfs, oldpair, &parent, &entry); + if (res < 0) { + return res; + } + + if (res) { + // update disk, this creates a desync + entry.d.u.dir[0] = newpair[0]; + entry.d.u.dir[1] = newpair[1]; + + int err = lfs_dir_update(lfs, &parent, &entry, NULL); + if (err) { + return err; + } + + // update internal root + if (lfs_paircmp(oldpair, lfs->root) == 0) { + LFS_DEBUG("Relocating root %" PRIu32 " %" PRIu32, + newpair[0], newpair[1]); + lfs->root[0] = newpair[0]; + lfs->root[1] = newpair[1]; + } + + // clean up bad block, which should now be a desync + return lfs_deorphan(lfs); + } + + // find pred + res = lfs_pred(lfs, oldpair, &parent); + if (res < 0) { + return res; + } + + if (res) { + // just replace bad pair, no desync can occur + parent.d.tail[0] = newpair[0]; + parent.d.tail[1] = newpair[1]; + + return lfs_dir_commit(lfs, &parent, NULL, 0); + } + + // couldn't find dir, must be new + return 0; +} + +int lfs_deorphan(lfs_t *lfs) { + lfs->deorphaned = true; + + if (lfs_pairisnull(lfs->root)) { + return 0; + } + + lfs_dir_t pdir = {.d.size = 0x80000000}; + lfs_dir_t cwd = {.d.tail[0] = 0, .d.tail[1] = 1}; + + // iterate over all directory directory entries + for (lfs_size_t i = 0; i < lfs->cfg->block_count; i++) { + if (lfs_pairisnull(cwd.d.tail)) { + return 0; + } + + int err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail); + if (err) { + return err; + } + + // check head blocks for orphans + if (!(0x80000000 & pdir.d.size)) { + // check if we have a parent + lfs_dir_t parent; + lfs_entry_t entry; + int res = lfs_parent(lfs, pdir.d.tail, &parent, &entry); + if (res < 0) { + return res; + } + + if (!res) { + // we are an orphan + LFS_DEBUG("Found orphan %" PRIu32 " %" PRIu32, + pdir.d.tail[0], pdir.d.tail[1]); + + pdir.d.tail[0] = cwd.d.tail[0]; + pdir.d.tail[1] = cwd.d.tail[1]; + + err = lfs_dir_commit(lfs, &pdir, NULL, 0); + if (err) { + return err; + } + + return 0; + } + + if (!lfs_pairsync(entry.d.u.dir, pdir.d.tail)) { + // we have desynced + LFS_DEBUG("Found desync %" PRIu32 " %" PRIu32, + entry.d.u.dir[0], entry.d.u.dir[1]); + + pdir.d.tail[0] = entry.d.u.dir[0]; + pdir.d.tail[1] = entry.d.u.dir[1]; + + err = lfs_dir_commit(lfs, &pdir, NULL, 0); + if (err) { + return err; + } + + return 0; + } + } + + // check entries for moves + lfs_entry_t entry; + while (true) { + err = lfs_dir_next(lfs, &cwd, &entry); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err == LFS_ERR_NOENT) { + break; + } + + // found moved entry + if (entry.d.type & 0x80) { + int moved = lfs_moved(lfs, &entry.d.u); + if (moved < 0) { + return moved; + } + + if (moved) { + LFS_DEBUG("Found move %" PRIu32 " %" PRIu32, + entry.d.u.dir[0], entry.d.u.dir[1]); + err = lfs_dir_remove(lfs, &cwd, &entry); + if (err) { + return err; + } + } else { + LFS_DEBUG("Found partial move %" PRIu32 " %" PRIu32, + entry.d.u.dir[0], entry.d.u.dir[1]); + entry.d.type &= ~0x80; + err = lfs_dir_update(lfs, &cwd, &entry, NULL); + if (err) { + return err; + } + } + } + } + + memcpy(&pdir, &cwd, sizeof(pdir)); + } + + // If we reached here, we have more directory pairs than blocks in the + // filesystem... So something must be horribly wrong + return LFS_ERR_CORRUPT; +} diff --git a/arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/lfs.h b/arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/lfs.h new file mode 100644 index 00000000..9c3174e7 --- /dev/null +++ b/arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/lfs.h @@ -0,0 +1,501 @@ +/* + * The little filesystem + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_H +#define LFS_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/// Version info /// + +// Software library version +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS_VERSION 0x00010007 +#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16)) +#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0)) + +// Version of On-disk data structures +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS_DISK_VERSION 0x00010001 +#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16)) +#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0)) + + +/// Definitions /// + +// Type definitions +typedef uint32_t lfs_size_t; +typedef uint32_t lfs_off_t; + +typedef int32_t lfs_ssize_t; +typedef int32_t lfs_soff_t; + +typedef uint32_t lfs_block_t; + +// Max name size in bytes +#ifndef LFS_NAME_MAX +#define LFS_NAME_MAX 255 +#endif + +// Max file size in bytes +#ifndef LFS_FILE_MAX +#define LFS_FILE_MAX 2147483647 +#endif + +// Possible error codes, these are negative to allow +// valid positive return values +enum lfs_error { + LFS_ERR_OK = 0, // No error + LFS_ERR_IO = -5, // Error during device operation + LFS_ERR_CORRUPT = -52, // Corrupted + LFS_ERR_NOENT = -2, // No directory entry + LFS_ERR_EXIST = -17, // Entry already exists + LFS_ERR_NOTDIR = -20, // Entry is not a dir + LFS_ERR_ISDIR = -21, // Entry is a dir + LFS_ERR_NOTEMPTY = -39, // Dir is not empty + LFS_ERR_BADF = -9, // Bad file number + LFS_ERR_FBIG = -27, // File too large + LFS_ERR_INVAL = -22, // Invalid parameter + LFS_ERR_NOSPC = -28, // No space left on device + LFS_ERR_NOMEM = -12, // No more memory available +}; + +// File types +enum lfs_type { + LFS_TYPE_REG = 0x11, + LFS_TYPE_DIR = 0x22, + LFS_TYPE_SUPERBLOCK = 0x2e, +}; + +// File open flags +enum lfs_open_flags { + // open flags + LFS_O_RDONLY = 1, // Open a file as read only + LFS_O_WRONLY = 2, // Open a file as write only + LFS_O_RDWR = 3, // Open a file as read and write + LFS_O_CREAT = 0x0100, // Create a file if it does not exist + LFS_O_EXCL = 0x0200, // Fail if a file already exists + LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size + LFS_O_APPEND = 0x0800, // Move to end of file on every write + + // internally used flags + LFS_F_DIRTY = 0x10000, // File does not match storage + LFS_F_WRITING = 0x20000, // File has been written since last flush + LFS_F_READING = 0x40000, // File has been read since last flush + LFS_F_ERRED = 0x80000, // An error occured during write +}; + +// File seek flags +enum lfs_whence_flags { + LFS_SEEK_SET = 0, // Seek relative to an absolute position + LFS_SEEK_CUR = 1, // Seek relative to the current file position + LFS_SEEK_END = 2, // Seek relative to the end of the file +}; + + +// Configuration provided during initialization of the littlefs +struct lfs_config { + // Opaque user provided context that can be used to pass + // information to the block device operations + void *context; + + // Read a region in a block. Negative error codes are propogated + // to the user. + int (*read)(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size); + + // Program a region in a block. The block must have previously + // been erased. Negative error codes are propogated to the user. + // May return LFS_ERR_CORRUPT if the block should be considered bad. + int (*prog)(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size); + + // Erase a block. A block must be erased before being programmed. + // The state of an erased block is undefined. Negative error codes + // are propogated to the user. + // May return LFS_ERR_CORRUPT if the block should be considered bad. + int (*erase)(const struct lfs_config *c, lfs_block_t block); + + // Sync the state of the underlying block device. Negative error codes + // are propogated to the user. + int (*sync)(const struct lfs_config *c); + + // Minimum size of a block read. This determines the size of read buffers. + // This may be larger than the physical read size to improve performance + // by caching more of the block device. + lfs_size_t read_size; + + // Minimum size of a block program. This determines the size of program + // buffers. This may be larger than the physical program size to improve + // performance by caching more of the block device. + // Must be a multiple of the read size. + lfs_size_t prog_size; + + // Size of an erasable block. This does not impact ram consumption and + // may be larger than the physical erase size. However, this should be + // kept small as each file currently takes up an entire block. + // Must be a multiple of the program size. + lfs_size_t block_size; + + // Number of erasable blocks on the device. + lfs_size_t block_count; + + // Number of blocks to lookahead during block allocation. A larger + // lookahead reduces the number of passes required to allocate a block. + // The lookahead buffer requires only 1 bit per block so it can be quite + // large with little ram impact. Should be a multiple of 32. + lfs_size_t lookahead; + + // Optional, statically allocated read buffer. Must be read sized. + void *read_buffer; + + // Optional, statically allocated program buffer. Must be program sized. + void *prog_buffer; + + // Optional, statically allocated lookahead buffer. Must be 1 bit per + // lookahead block. + void *lookahead_buffer; + + // Optional, statically allocated buffer for files. Must be program sized. + // If enabled, only one file may be opened at a time. + void *file_buffer; +}; + +// Optional configuration provided during lfs_file_opencfg +struct lfs_file_config { + // Optional, statically allocated buffer for files. Must be program sized. + // If NULL, malloc will be used by default. + void *buffer; +}; + +// File info structure +struct lfs_info { + // Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR + uint8_t type; + + // Size of the file, only valid for REG files + lfs_size_t size; + + // Name of the file stored as a null-terminated string + char name[LFS_NAME_MAX+1]; +}; + + +/// littlefs data structures /// +typedef struct lfs_entry { + lfs_off_t off; + + struct lfs_disk_entry { + uint8_t type; + uint8_t elen; + uint8_t alen; + uint8_t nlen; + union { + struct { + lfs_block_t head; + lfs_size_t size; + } file; + lfs_block_t dir[2]; + } u; + } d; +} lfs_entry_t; + +typedef struct lfs_cache { + lfs_block_t block; + lfs_off_t off; + uint8_t *buffer; +} lfs_cache_t; + +typedef struct lfs_file { + struct lfs_file *next; + lfs_block_t pair[2]; + lfs_off_t poff; + + lfs_block_t head; + lfs_size_t size; + + const struct lfs_file_config *cfg; + uint32_t flags; + lfs_off_t pos; + lfs_block_t block; + lfs_off_t off; + lfs_cache_t cache; +} lfs_file_t; + +typedef struct lfs_dir { + struct lfs_dir *next; + lfs_block_t pair[2]; + lfs_off_t off; + + lfs_block_t head[2]; + lfs_off_t pos; + + struct lfs_disk_dir { + uint32_t rev; + lfs_size_t size; + lfs_block_t tail[2]; + } d; +} lfs_dir_t; + +typedef struct lfs_superblock { + lfs_off_t off; + + struct lfs_disk_superblock { + uint8_t type; + uint8_t elen; + uint8_t alen; + uint8_t nlen; + lfs_block_t root[2]; + uint32_t block_size; + uint32_t block_count; + uint32_t version; + char magic[8]; + } d; +} lfs_superblock_t; + +typedef struct lfs_free { + lfs_block_t off; + lfs_block_t size; + lfs_block_t i; + lfs_block_t ack; + uint32_t *buffer; +} lfs_free_t; + +// The littlefs type +typedef struct lfs { + const struct lfs_config *cfg; + + lfs_block_t root[2]; + lfs_file_t *files; + lfs_dir_t *dirs; + + lfs_cache_t rcache; + lfs_cache_t pcache; + + lfs_free_t free; + bool deorphaned; + bool moving; +} lfs_t; + + +/// Filesystem functions /// + +// Format a block device with the littlefs +// +// Requires a littlefs object and config struct. This clobbers the littlefs +// object, and does not leave the filesystem mounted. The config struct must +// be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_format(lfs_t *lfs, const struct lfs_config *config); + +// Mounts a littlefs +// +// Requires a littlefs object and config struct. Multiple filesystems +// may be mounted simultaneously with multiple littlefs objects. Both +// lfs and config must be allocated while mounted. The config struct must +// be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_mount(lfs_t *lfs, const struct lfs_config *config); + +// Unmounts a littlefs +// +// Does nothing besides releasing any allocated resources. +// Returns a negative error code on failure. +int lfs_unmount(lfs_t *lfs); + +/// General operations /// + +// Removes a file or directory +// +// If removing a directory, the directory must be empty. +// Returns a negative error code on failure. +int lfs_remove(lfs_t *lfs, const char *path); + +// Rename or move a file or directory +// +// If the destination exists, it must match the source in type. +// If the destination is a directory, the directory must be empty. +// +// Returns a negative error code on failure. +int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath); + +// Find info about a file or directory +// +// Fills out the info structure, based on the specified file or directory. +// Returns a negative error code on failure. +int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info); + + +/// File operations /// + +// Open a file +// +// The mode that the file is opened in is determined by the flags, which +// are values from the enum lfs_open_flags that are bitwise-ored together. +// +// Returns a negative error code on failure. +int lfs_file_open(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags); + +// Open a file with extra configuration +// +// The mode that the file is opened in is determined by the flags, which +// are values from the enum lfs_open_flags that are bitwise-ored together. +// +// The config struct provides additional config options per file as described +// above. The config struct must be allocated while the file is open, and the +// config struct must be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags, + const struct lfs_file_config *config); + +// Close a file +// +// Any pending writes are written out to storage as though +// sync had been called and releases any allocated resources. +// +// Returns a negative error code on failure. +int lfs_file_close(lfs_t *lfs, lfs_file_t *file); + +// Synchronize a file on storage +// +// Any pending writes are written out to storage. +// Returns a negative error code on failure. +int lfs_file_sync(lfs_t *lfs, lfs_file_t *file); + +// Read data from file +// +// Takes a buffer and size indicating where to store the read data. +// Returns the number of bytes read, or a negative error code on failure. +lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size); + +// Write data to file +// +// Takes a buffer and size indicating the data to write. The file will not +// actually be updated on the storage until either sync or close is called. +// +// Returns the number of bytes written, or a negative error code on failure. +lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size); + +// Change the position of the file +// +// The change in position is determined by the offset and whence flag. +// Returns the old position of the file, or a negative error code on failure. +lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence); + +// Truncates the size of the file to the specified size +// +// Returns a negative error code on failure. +int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size); + +// Return the position of the file +// +// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR) +// Returns the position of the file, or a negative error code on failure. +lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file); + +// Change the position of the file to the beginning of the file +// +// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR) +// Returns a negative error code on failure. +int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file); + +// Return the size of the file +// +// Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END) +// Returns the size of the file, or a negative error code on failure. +lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file); + + +/// Directory operations /// + +// Create a directory +// +// Returns a negative error code on failure. +int lfs_mkdir(lfs_t *lfs, const char *path); + +// Open a directory +// +// Once open a directory can be used with read to iterate over files. +// Returns a negative error code on failure. +int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path); + +// Close a directory +// +// Releases any allocated resources. +// Returns a negative error code on failure. +int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir); + +// Read an entry in the directory +// +// Fills out the info structure, based on the specified file or directory. +// Returns a negative error code on failure. +int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info); + +// Change the position of the directory +// +// The new off must be a value previous returned from tell and specifies +// an absolute offset in the directory seek. +// +// Returns a negative error code on failure. +int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off); + +// Return the position of the directory +// +// The returned offset is only meant to be consumed by seek and may not make +// sense, but does indicate the current position in the directory iteration. +// +// Returns the position of the directory, or a negative error code on failure. +lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir); + +// Change the position of the directory to the beginning of the directory +// +// Returns a negative error code on failure. +int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir); + + +/// Miscellaneous littlefs specific operations /// + +// Traverse through all blocks in use by the filesystem +// +// The provided callback will be called with each block address that is +// currently in use by the filesystem. This can be used to determine which +// blocks are in use or how much of the storage is available. +// +// Returns a negative error code on failure. +int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); + +// Prunes any recoverable errors that may have occured in the filesystem +// +// Not needed to be called by user unless an operation is interrupted +// but the filesystem is still mounted. This is already called on first +// allocation. +// +// Returns a negative error code on failure. +int lfs_deorphan(lfs_t *lfs); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/lfs_util.c b/arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/lfs_util.c new file mode 100644 index 00000000..9ca0756d --- /dev/null +++ b/arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/lfs_util.c @@ -0,0 +1,31 @@ +/* + * lfs util functions + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "lfs_util.h" + +// Only compile if user does not provide custom config +#ifndef LFS_CONFIG + + +// Software CRC implementation with small lookup table +void lfs_crc(uint32_t *restrict crc, const void *buffer, size_t size) { + static const uint32_t rtable[16] = { + 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, + 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, + 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, + }; + + const uint8_t *data = buffer; + + for (size_t i = 0; i < size; i++) { + *crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 0)) & 0xf]; + *crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 4)) & 0xf]; + } +} + + +#endif diff --git a/arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/lfs_util.h b/arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/lfs_util.h new file mode 100644 index 00000000..4abbb762 --- /dev/null +++ b/arch/stm32/Adafruit_LittleFS_stm32/src/littlefs/lfs_util.h @@ -0,0 +1,197 @@ +/* + * lfs utility functions + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_UTIL_H +#define LFS_UTIL_H + +// Users can override lfs_util.h with their own configuration by defining +// LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h). +// +// If LFS_CONFIG is used, none of the default utils will be emitted and must be +// provided by the config file. To start I would suggest copying lfs_util.h and +// modifying as needed. +#ifdef LFS_CONFIG +#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x) +#define LFS_STRINGIZE2(x) #x +#include LFS_STRINGIZE(LFS_CONFIG) +#else + +// System includes +#include +#include +#include + +#ifndef LFS_NO_MALLOC +#include +#endif +#ifndef LFS_NO_ASSERT +#include +#endif + +#if !CFG_DEBUG +#define LFS_NO_DEBUG +#define LFS_NO_WARN +#define LFS_NO_ERROR +#endif + +#if !defined(LFS_NO_DEBUG) || !defined(LFS_NO_WARN) || !defined(LFS_NO_ERROR) +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + + +// Macros, may be replaced by system specific wrappers. Arguments to these +// macros must not have side-effects as the macros can be removed for a smaller +// code footprint + +// Logging functions +#ifndef LFS_NO_DEBUG +#define LFS_DEBUG(fmt, ...) \ + printf("lfs debug:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#else +#define LFS_DEBUG(fmt, ...) +#endif + +#ifndef LFS_NO_WARN +#define LFS_WARN(fmt, ...) \ + printf("lfs warn:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#else +#define LFS_WARN(fmt, ...) +#endif + +#ifndef LFS_NO_ERROR +#define LFS_ERROR(fmt, ...) \ + printf("lfs error:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#else +#define LFS_ERROR(fmt, ...) +#endif + +// Runtime assertions +#ifndef LFS_NO_ASSERT +#define LFS_ASSERT(test) assert(test) +#else +#define LFS_ASSERT(test) +#endif + + +// Builtin functions, these may be replaced by more efficient +// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more +// expensive basic C implementation for debugging purposes + +// Min/max functions for unsigned 32-bit numbers +static inline uint32_t lfs_max(uint32_t a, uint32_t b) { + return (a > b) ? a : b; +} + +static inline uint32_t lfs_min(uint32_t a, uint32_t b) { + return (a < b) ? a : b; +} + +// Find the next smallest power of 2 less than or equal to a +static inline uint32_t lfs_npw2(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return 32 - __builtin_clz(a-1); +#else + uint32_t r = 0; + uint32_t s; + a -= 1; + s = (a > 0xffff) << 4; a >>= s; r |= s; + s = (a > 0xff ) << 3; a >>= s; r |= s; + s = (a > 0xf ) << 2; a >>= s; r |= s; + s = (a > 0x3 ) << 1; a >>= s; r |= s; + return (r | (a >> 1)) + 1; +#endif +} + +// Count the number of trailing binary zeros in a +// lfs_ctz(0) may be undefined +static inline uint32_t lfs_ctz(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__) + return __builtin_ctz(a); +#else + return lfs_npw2((a & -a) + 1) - 1; +#endif +} + +// Count the number of binary ones in a +static inline uint32_t lfs_popc(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return __builtin_popcount(a); +#else + a = a - ((a >> 1) & 0x55555555); + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); + return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; +#endif +} + +// Find the sequence comparison of a and b, this is the distance +// between a and b ignoring overflow +static inline int lfs_scmp(uint32_t a, uint32_t b) { + return (int)(unsigned)(a - b); +} + +// Convert from 32-bit little-endian to native order +static inline uint32_t lfs_fromle32(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) + return a; +#elif !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + return __builtin_bswap32(a); +#else + return (((uint8_t*)&a)[0] << 0) | + (((uint8_t*)&a)[1] << 8) | + (((uint8_t*)&a)[2] << 16) | + (((uint8_t*)&a)[3] << 24); +#endif +} + +// Convert to 32-bit little-endian from native order +static inline uint32_t lfs_tole32(uint32_t a) { + return lfs_fromle32(a); +} + +// Calculate CRC-32 with polynomial = 0x04c11db7 +void lfs_crc(uint32_t *crc, const void *buffer, size_t size); + +// Allocate memory, only used if buffers are not provided to littlefs +static inline void *lfs_malloc(size_t size) { +#ifndef LFS_NO_MALLOC + //extern void *pvPortMalloc( size_t xWantedSize ); + //return pvPortMalloc(size); + return malloc(size); +#else + (void)size; + return NULL; +#endif +} + +// Deallocate memory, only used if buffers are not provided to littlefs +static inline void lfs_free(void *p) { +#ifndef LFS_NO_MALLOC + //extern void vPortFree( void *pv ); + //vPortFree(p); + free(p); +#else + (void)p; +#endif +} + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif +#endif diff --git a/arch/stm32/build_hex.py b/arch/stm32/build_hex.py new file mode 100644 index 00000000..ffdb5d2f --- /dev/null +++ b/arch/stm32/build_hex.py @@ -0,0 +1,10 @@ +Import("env") + +# Make custom HEX from ELF +env.AddPostAction( + "$BUILD_DIR/${PROGNAME}.elf", + env.VerboseAction(" ".join([ + "$OBJCOPY", "-O", "ihex", "-R", ".eeprom", + '"$BUILD_DIR/${PROGNAME}.elf"', '"$BUILD_DIR/${PROGNAME}.hex"' + ]), "Building $BUILD_DIR/${PROGNAME}.hex") +) \ No newline at end of file diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 4cebc42a..7d522b08 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -1,7 +1,7 @@ #include // needed for PlatformIO #include -#if defined(NRF52_PLATFORM) +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) #include #elif defined(RP2040_PLATFORM) #include @@ -291,7 +291,7 @@ class MyMesh : public BaseChatMesh { } void saveContacts() { -#if defined(NRF52_PLATFORM) +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) File file = _fs->open("/contacts3", FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) @@ -356,7 +356,7 @@ class MyMesh : public BaseChatMesh { } void saveChannels() { - #if defined(NRF52_PLATFORM) + #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) File file = _fs->open("/channels2", FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) @@ -413,7 +413,7 @@ class MyMesh : public BaseChatMesh { mesh::Utils::toHex(fname, key, key_len); sprintf(path, "/bl/%s", fname); - #if defined(NRF52_PLATFORM) + #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) File f = _fs->open(path, FILE_O_WRITE); if (f) { f.seek(0); f.truncate(); } #elif defined(RP2040_PLATFORM) @@ -866,7 +866,7 @@ public: BaseChatMesh::begin(); - #if defined(NRF52_PLATFORM) + #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) _identity_store = new IdentityStore(fs, ""); #elif defined(RP2040_PLATFORM) _identity_store = new IdentityStore(fs, "/identity"); @@ -933,7 +933,7 @@ public: } void savePrefs() { -#if defined(NRF52_PLATFORM) +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) File file = _fs->open("/new_prefs", FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) @@ -1621,6 +1621,9 @@ public: #include ArduinoSerialInterface serial_interface; #endif +#elif defined(STM32_PLATFORM) + #include + ArduinoSerialInterface serial_interface; #else #error "need to define a serial interface" #endif @@ -1654,7 +1657,7 @@ void setup() { fast_rng.begin(radio_get_rng_seed()); -#if defined(NRF52_PLATFORM) +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) InternalFS.begin(); the_mesh.begin(InternalFS, #ifdef HAS_UI diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 9694300e..c1c555c7 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -1,7 +1,7 @@ #include // needed for PlatformIO #include -#if defined(NRF52_PLATFORM) +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) #include #elif defined(RP2040_PLATFORM) #include @@ -230,7 +230,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { } File openAppend(const char* fname) { - #if defined(NRF52_PLATFORM) + #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) return _fs->open(fname, FILE_O_WRITE); #elif defined(RP2040_PLATFORM) return _fs->open(fname, "a"); @@ -597,7 +597,7 @@ public: } bool formatFileSystem() override { -#if defined(NRF52_PLATFORM) +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) return InternalFS.format(); #elif defined(RP2040_PLATFORM) return LittleFS.format(); @@ -738,7 +738,7 @@ void setup() { fast_rng.begin(radio_get_rng_seed()); FILESYSTEM* fs; -#if defined(NRF52_PLATFORM) +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) InternalFS.begin(); fs = &InternalFS; IdentityStore store(InternalFS, ""); diff --git a/platformio.ini b/platformio.ini index 42a19256..80f850ea 100644 --- a/platformio.ini +++ b/platformio.ini @@ -69,3 +69,18 @@ lib_deps = extends = arduino_base build_flags = ${arduino_base.build_flags} -D RP2040_PLATFORM + +; ----------------- STM32 ---------------------- + +[stm32_base] +extends = arduino_base +platform = platformio/ststm32@19.1.0 +platform_packages = platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32/archive/2.10.1.zip +extra_scripts = post:arch/stm32/build_hex.py +build_flags = ${arduino_base.build_flags} + -D STM32_PLATFORM + -I src/helpers/stm32 +build_src_filter = ${arduino_base.build_src_filter} + + +lib_deps = ${arduino_base.lib_deps} + file://arch/stm32/Adafruit_LittleFS_stm32 \ No newline at end of file diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 7c0377e6..d9d83440 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -73,7 +73,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { } void CommonCLI::savePrefs(FILESYSTEM* fs) { -#if defined(NRF52_PLATFORM) +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) File file = fs->open("/com_prefs", FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) diff --git a/src/helpers/CustomSTM32WLx.h b/src/helpers/CustomSTM32WLx.h new file mode 100644 index 00000000..cbdd5c8c --- /dev/null +++ b/src/helpers/CustomSTM32WLx.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received +#define SX126X_IRQ_PREAMBLE_DETECTED 0x04 + +class CustomSTM32WLx : public STM32WLx { + public: + CustomSTM32WLx(STM32WLx_Module *mod) : STM32WLx(mod) { } + + bool isReceiving() { + uint16_t irq = getIrqFlags(); + bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED); + return detected; + } +}; \ No newline at end of file diff --git a/src/helpers/CustomSTM32WLxWrapper.h b/src/helpers/CustomSTM32WLxWrapper.h new file mode 100644 index 00000000..84f78376 --- /dev/null +++ b/src/helpers/CustomSTM32WLxWrapper.h @@ -0,0 +1,30 @@ +#pragma once + +#include "CustomSTM32WLx.h" +#include "RadioLibWrappers.h" +#include + +class CustomSTM32WLxWrapper : public RadioLibWrapper { +public: + CustomSTM32WLxWrapper(CustomSTM32WLx& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } + bool isReceiving() override { + if (((CustomSTM32WLx *)_radio)->isReceiving()) return true; + + idle(); // put sx126x into standby + // do some basic CAD (blocks for ~12780 micros (on SF 10)!) + bool activity = (((CustomSTM32WLx *)_radio)->scanChannel() == RADIOLIB_LORA_DETECTED); + if (activity) { + startRecv(); + } else { + idle(); + } + return activity; + } + float getLastRSSI() const override { return ((CustomSTM32WLx *)_radio)->getRSSI(); } + float getLastSNR() const override { return ((CustomSTM32WLx *)_radio)->getSNR(); } + + float packetScore(float snr, int packet_len) override { + int sf = ((CustomSTM32WLx *)_radio)->spreadingFactor; + return packetScoreInt(snr, sf, packet_len); + } +}; diff --git a/src/helpers/IdentityStore.cpp b/src/helpers/IdentityStore.cpp index d111edfb..85f146c5 100644 --- a/src/helpers/IdentityStore.cpp +++ b/src/helpers/IdentityStore.cpp @@ -46,7 +46,7 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id) { char filename[40]; sprintf(filename, "%s/%s.id", _dir, name); -#if defined(NRF52_PLATFORM) +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) File file = _fs->open(filename, FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) @@ -68,7 +68,7 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id, const char filename[40]; sprintf(filename, "%s/%s.id", _dir, name); -#if defined(NRF52_PLATFORM) +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) File file = _fs->open(filename, FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) diff --git a/src/helpers/IdentityStore.h b/src/helpers/IdentityStore.h index 35b56d97..e81290e7 100644 --- a/src/helpers/IdentityStore.h +++ b/src/helpers/IdentityStore.h @@ -3,7 +3,7 @@ #if defined(ESP32) || defined(RP2040_PLATFORM) #include #define FILESYSTEM fs::FS -#elif defined(NRF52_PLATFORM) +#elif defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) #include #define FILESYSTEM Adafruit_LittleFS diff --git a/src/helpers/stm32/InternalFileSystem.cpp b/src/helpers/stm32/InternalFileSystem.cpp new file mode 100644 index 00000000..47706bac --- /dev/null +++ b/src/helpers/stm32/InternalFileSystem.cpp @@ -0,0 +1,139 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 hathach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include "InternalFileSystem.h" + +//--------------------------------------------------------------------+ +// LFS Disk IO +//--------------------------------------------------------------------+ +static int _internal_flash_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) +{ + if (!buffer || !size) return LFS_ERR_INVAL; + lfs_block_t address = LFS_FLASH_ADDR_BASE + (block * FLASH_PAGE_SIZE + off); + memcpy(buffer, (void *)address, size); + return LFS_ERR_OK; +} + +// Program a region in a block. The block must have previously +// been erased. Negative error codes are propogated to the user. +// May return LFS_ERR_CORRUPT if the block should be considered bad. +static int _internal_flash_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) +{ + HAL_StatusTypeDef hal_rc = HAL_OK; + lfs_block_t addr = LFS_FLASH_ADDR_BASE + (block * FLASH_PAGE_SIZE + off); + uint64_t *bufp = (uint64_t *) buffer; + + if (HAL_FLASH_Unlock() != HAL_OK) return LFS_ERR_IO; + for (uint32_t i = 0; i < size/8; i++) { + if ((addr < LFS_FLASH_ADDR_BASE) || (addr > FLASH_END_ADDR)) { + HAL_FLASH_Lock(); + return LFS_ERR_INVAL; + } + hal_rc = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr, *bufp); + addr += 8; + bufp += 1; + } + if (HAL_FLASH_Lock() != HAL_OK) return LFS_ERR_IO; + + return hal_rc == HAL_OK ? LFS_ERR_OK : LFS_ERR_IO; +} + +// Erase a block. A block must be erased before being programmed. +// The state of an erased block is undefined. Negative error codes +// are propogated to the user. +// May return LFS_ERR_CORRUPT if the block should be considered bad. +static int _internal_flash_erase(const struct lfs_config *c, lfs_block_t block) +{ + HAL_StatusTypeDef hal_rc; + lfs_block_t address = LFS_FLASH_ADDR_BASE + (block * FLASH_PAGE_SIZE); + uint32_t pageError = 0; + FLASH_EraseInitTypeDef EraseInitStruct = { + .TypeErase = FLASH_TYPEERASE_PAGES, + .Page = 0, + .NbPages = 1 + }; + + if ((address < LFS_FLASH_ADDR_BASE) || (address > FLASH_END_ADDR)) { + return LFS_ERR_INVAL; + } + EraseInitStruct.Page = (address - FLASH_BASE) / FLASH_PAGE_SIZE; + HAL_FLASH_Unlock(); + hal_rc = HAL_FLASHEx_Erase(&EraseInitStruct, &pageError); + HAL_FLASH_Lock(); + + return hal_rc == HAL_OK ? LFS_ERR_OK : LFS_ERR_IO; +} + +// Sync the state of the underlying block device. Negative error codes +// are propogated to the user. +static int _internal_flash_sync(const struct lfs_config *c) +{ + return LFS_ERR_OK; // don't need sync +} + +struct lfs_config _InternalFSConfig = { + .context = NULL, + .read = _internal_flash_read, + .prog = _internal_flash_prog, + .erase = _internal_flash_erase, + .sync = _internal_flash_sync, + + .read_size = LFS_BLOCK_SIZE, + .prog_size = LFS_BLOCK_SIZE, + .block_size = LFS_BLOCK_SIZE, + .block_count = LFS_FLASH_TOTAL_SIZE / LFS_BLOCK_SIZE, + .lookahead = 128, + + .read_buffer = NULL, + .prog_buffer = NULL, + .lookahead_buffer = NULL, + .file_buffer = NULL +}; + +InternalFileSystem InternalFS; + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +InternalFileSystem::InternalFileSystem(void) + : Adafruit_LittleFS(&_InternalFSConfig) +{ + +} + +bool InternalFileSystem::begin(void) +{ + // failed to mount, erase all sector then format and mount again + if ( !Adafruit_LittleFS::begin() ) + { + // lfs format + this->format(); + // mount again if still failed, give up + if ( !Adafruit_LittleFS::begin() ) return false; + } + + return true; +} diff --git a/src/helpers/stm32/InternalFileSystem.h b/src/helpers/stm32/InternalFileSystem.h new file mode 100644 index 00000000..1e3a4b4f --- /dev/null +++ b/src/helpers/stm32/InternalFileSystem.h @@ -0,0 +1,48 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 hathach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + #ifndef INTERNALFILESYSTEM_H_ + #define INTERNALFILESYSTEM_H_ + + #include "Adafruit_LittleFS.h" + +#ifndef LFS_FLASH_TOTAL_SIZE /* Flash size can be configured in platformio.ini */ + #define LFS_FLASH_TOTAL_SIZE (16 * 2048) /* defaults to 32k flash */ +#endif +#define LFS_BLOCK_SIZE (2048) +#define LFS_FLASH_ADDR_BASE (FLASH_END_ADDR - LFS_FLASH_TOTAL_SIZE + 1) + + class InternalFileSystem : public Adafruit_LittleFS + { + public: + InternalFileSystem(void); + + // overwrite to also perform low level format (sector erase of whole flash region) + bool begin(void); + }; + + extern InternalFileSystem InternalFS; + + #endif /* INTERNALFILESYSTEM_H_ */ + \ No newline at end of file diff --git a/src/helpers/stm32/STM32Board.h b/src/helpers/stm32/STM32Board.h new file mode 100644 index 00000000..21cbdcdb --- /dev/null +++ b/src/helpers/stm32/STM32Board.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +class STM32Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin() { + startup_reason = BD_STARTUP_NORMAL; + } + + uint8_t getStartupReason() const override { return startup_reason; } + + uint16_t getBattMilliVolts() override { + return 0; // not supported + } + + const char* getManufacturerName() const override { + return "Generic STM32"; + } + + void reboot() override { + } + + bool startOTAUpdate(const char* id, char reply[]) override { return false; }; +}; \ No newline at end of file diff --git a/variants/wio-e5/platformio.ini b/variants/wio-e5/platformio.ini new file mode 100644 index 00000000..4de13e7f --- /dev/null +++ b/variants/wio-e5/platformio.ini @@ -0,0 +1,31 @@ +[lora_e5] +extends = stm32_base +board = lora_e5_dev_board +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 + -I variants/wio-e5 +build_src_filter = ${stm32_base.build_src_filter} + +<../variants/wio-e5> + +[env:wio-e5-repeater] +extends = lora_e5 +build_flags = ${lora_e5.build_flags} + -D LORA_TX_POWER=20 + -D ADVERT_NAME='"WIO-E5 Repeater"' + -D ADMIN_PASSWORD='"password"' +build_src_filter = ${lora_e5.build_src_filter} + +<../examples/simple_repeater/main.cpp> + +[env:wio-e5_companion_radio_usb] +extends = lora_e5 +build_flags = ${lora_e5.build_flags} + -D LORA_TX_POWER=20 + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 +build_src_filter = ${lora_e5.build_src_filter} + +<../examples/companion_radio/*.cpp> +lib_deps = ${lora_e5.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/wio-e5/target.cpp b/variants/wio-e5/target.cpp new file mode 100644 index 00000000..aee0dafb --- /dev/null +++ b/variants/wio-e5/target.cpp @@ -0,0 +1,69 @@ +#include +#include "target.h" +#include + +STM32Board board; + +RADIO_CLASS radio = new STM32WLx_Module(); + +WRAPPER_CLASS radio_driver(radio, board); + +static const uint32_t rfswitch_pins[] = {PA4, PA5, 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_HP, {LOW, HIGH}}, // for LoRa-E5 mini +// {STM32WLx::MODE_TX_LP, {HIGH, HIGH}}, // for LoRa-E5-LE mini + END_OF_MODE_TABLE, +}; + +VolatileRTCClock rtc_clock; +SensorManager sensors; + +#ifndef LORA_CR + #define LORA_CR 5 +#endif + +bool radio_init() { +// rtc_clock.begin(Wire); + +// #ifdef SX126X_DIO3_TCXO_VOLTAGE +// float tcxo = SX126X_DIO3_TCXO_VOLTAGE; +// #else +// float tcxo = 1.6f; +// #endif + + 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, 8, 1.7, 0); + + if (status != RADIOLIB_ERR_NONE) { + Serial.print("ERROR: radio init failed: "); + Serial.println(status); + return false; // fail + } + + 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 +} diff --git a/variants/wio-e5/target.h b/variants/wio-e5/target.h new file mode 100644 index 00000000..f2dae108 --- /dev/null +++ b/variants/wio-e5/target.h @@ -0,0 +1,20 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include + +extern STM32Board 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(); diff --git a/variants/wio-e5/variant.h b/variants/wio-e5/variant.h new file mode 100644 index 00000000..84b9cd16 --- /dev/null +++ b/variants/wio-e5/variant.h @@ -0,0 +1,10 @@ +#pragma once + +// UART Definitions +#ifndef SERIAL_UART_INSTANCE + #define SERIAL_UART_INSTANCE 101 +#endif + +#include + +#undef RNG From e88a710d0f665d7c12254ad89bdd66aa617a06d0 Mon Sep 17 00:00:00 2001 From: JQ Date: Sun, 11 May 2025 09:32:34 -0700 Subject: [PATCH 072/154] don't expose GPD setting unless GPS is connected. --- variants/t114/target.cpp | 29 +++++++++++++++++++++++++---- variants/t114/target.h | 1 + 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/variants/t114/target.cpp b/variants/t114/target.cpp index f59227f4..e8773dbb 100644 --- a/variants/t114/target.cpp +++ b/variants/t114/target.cpp @@ -87,6 +87,25 @@ void T114SensorManager::stop_gps() { bool T114SensorManager::begin() { Serial1.begin(9600); + + // Try to detect if GPS is physically connected to determine if we should expose the setting + pinMode(GPS_EN, OUTPUT); + digitalWrite(GPS_EN, HIGH); // Power on GPS + + // Give GPS a moment to power up and send data + delay(500); + + // We'll consider GPS detected if we see any data on Serial1 + gps_detected = (Serial1.available() > 0); + + if (gps_detected) { + MESH_DEBUG_PRINTLN("GPS detected"); + digitalWrite(GPS_EN, LOW); // Power off GPS until the setting is changed + } else { + MESH_DEBUG_PRINTLN("No GPS detected"); + digitalWrite(GPS_EN, LOW); + } + return true; } @@ -112,21 +131,23 @@ void T114SensorManager::loop() { } } -int T114SensorManager::getNumSettings() const { return 1; } // just one supported: "gps" (power switch) +int T114SensorManager::getNumSettings() const { + return gps_detected ? 1 : 0; // only show GPS setting if GPS is detected +} const char* T114SensorManager::getSettingName(int i) const { - return i == 0 ? "gps" : NULL; + return (gps_detected && i == 0) ? "gps" : NULL; } const char* T114SensorManager::getSettingValue(int i) const { - if (i == 0) { + if (gps_detected && i == 0) { return gps_active ? "1" : "0"; } return NULL; } bool T114SensorManager::setSettingValue(const char* name, const char* value) { - if (strcmp(name, "gps") == 0) { + if (gps_detected && strcmp(name, "gps") == 0) { if (strcmp(value, "0") == 0) { stop_gps(); } else { diff --git a/variants/t114/target.h b/variants/t114/target.h index 308fb7b9..38d0e02a 100644 --- a/variants/t114/target.h +++ b/variants/t114/target.h @@ -11,6 +11,7 @@ class T114SensorManager : public SensorManager { bool gps_active = false; + bool gps_detected = false; LocationProvider* _location; void start_gps(); From c37622b4a093e1b0cfe85c63e2912d70ddee6de5 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 12 May 2025 12:23:58 +1000 Subject: [PATCH 073/154] * repeater: neighbors CLI, now returns secs ago, not timestamp --- examples/simple_repeater/main.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index c1c555c7..26923c77 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -675,7 +675,8 @@ public: mesh::Utils::toHex(hex, neighbour->id.pub_key, 4); // add next neighbour - sprintf(dp, "%s:%d:%d", hex, neighbour->advert_timestamp, neighbour->snr); + uint32_t secs_ago = getRTCClock()->getCurrentTime() - neighbour->heard_timestamp; + sprintf(dp, "%s:%d:%d", hex, secs_ago, neighbour->snr); while (*dp) dp++; // find end of string } #endif From b08436eba774c9cc57d7e1c659996395c7d7d396 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 12 May 2025 17:26:44 +1000 Subject: [PATCH 074/154] * startSendRaw() now returns false if fail --- src/Dispatcher.cpp | 11 ++++++++++- src/Dispatcher.h | 3 ++- src/helpers/RadioLibWrappers.cpp | 11 +++++++---- src/helpers/RadioLibWrappers.h | 2 +- src/helpers/esp32/ESPNOWRadio.cpp | 11 ++++++----- src/helpers/esp32/ESPNOWRadio.h | 2 +- 6 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 7a415f07..2b4c4675 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -234,7 +234,16 @@ void Dispatcher::checkSend() { uint32_t max_airtime = _radio->getEstAirtimeFor(len)*3/2; outbound_start = _ms->getMillis(); - _radio->startSendRaw(raw, len); + bool success = _radio->startSendRaw(raw, len); + if (!success) { + MESH_DEBUG_PRINTLN("%s Dispatcher::loop(): ERROR: send start failed!", getLogDateTime()); + + logTxFail(outbound, outbound->getRawLength()); + + releasePacket(outbound); // return to pool + outbound = NULL; + return; + } outbound_expiry = futureMillis(max_airtime); #if MESH_PACKET_LOGGING diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 393d0856..96db4fc5 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -42,8 +42,9 @@ public: * \brief starts the raw packet send. (no wait) * \param bytes the raw packet data * \param len the length in bytes + * \returns true if successfully started */ - virtual void startSendRaw(const uint8_t* bytes, int len) = 0; + virtual bool startSendRaw(const uint8_t* bytes, int len) = 0; /** * \returns true if the previous 'startSendRaw()' completed successfully. diff --git a/src/helpers/RadioLibWrappers.cpp b/src/helpers/RadioLibWrappers.cpp index 03f8f3ce..fa678431 100644 --- a/src/helpers/RadioLibWrappers.cpp +++ b/src/helpers/RadioLibWrappers.cpp @@ -77,13 +77,16 @@ uint32_t RadioLibWrapper::getEstAirtimeFor(int len_bytes) { return _radio->getTimeOnAir(len_bytes) / 1000; } -void RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) { - state = STATE_TX_WAIT; +bool RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) { _board->onBeforeTransmit(); int err = _radio->startTransmit((uint8_t *) bytes, len); - if (err != RADIOLIB_ERR_NONE) { - MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startTransmit(%d)", err); + if (err == RADIOLIB_ERR_NONE) { + state = STATE_TX_WAIT; + return true; } + MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startTransmit(%d)", err); + idle(); // trigger another startRecv() + return false; } bool RadioLibWrapper::isSendComplete() { diff --git a/src/helpers/RadioLibWrappers.h b/src/helpers/RadioLibWrappers.h index c9621cf6..a97987ef 100644 --- a/src/helpers/RadioLibWrappers.h +++ b/src/helpers/RadioLibWrappers.h @@ -19,7 +19,7 @@ public: void begin() override; int recvRaw(uint8_t* bytes, int sz) override; uint32_t getEstAirtimeFor(int len_bytes) override; - void startSendRaw(const uint8_t* bytes, int len) override; + bool startSendRaw(const uint8_t* bytes, int len) override; bool isSendComplete() override; void onSendFinished() override; diff --git a/src/helpers/esp32/ESPNOWRadio.cpp b/src/helpers/esp32/ESPNOWRadio.cpp index 9cdc0234..a48a3430 100644 --- a/src/helpers/esp32/ESPNOWRadio.cpp +++ b/src/helpers/esp32/ESPNOWRadio.cpp @@ -67,18 +67,19 @@ uint32_t ESPNOWRadio::intID() { return n + m; } -void ESPNOWRadio::startSendRaw(const uint8_t* bytes, int len) { +bool ESPNOWRadio::startSendRaw(const uint8_t* bytes, int len) { // Send message via ESP-NOW is_send_complete = false; esp_err_t result = esp_now_send(broadcastAddress, bytes, len); if (result == ESP_OK) { n_sent++; ESPNOW_DEBUG_PRINTLN("Send success"); - } else { - last_send_result = result; - is_send_complete = true; - ESPNOW_DEBUG_PRINTLN("Send failed: %d", result); + return true; } + last_send_result = result; + is_send_complete = true; + ESPNOW_DEBUG_PRINTLN("Send failed: %d", result); + return false; } bool ESPNOWRadio::isSendComplete() { diff --git a/src/helpers/esp32/ESPNOWRadio.h b/src/helpers/esp32/ESPNOWRadio.h index ab645f12..4b33df58 100644 --- a/src/helpers/esp32/ESPNOWRadio.h +++ b/src/helpers/esp32/ESPNOWRadio.h @@ -12,7 +12,7 @@ public: void init(); int recvRaw(uint8_t* bytes, int sz) override; uint32_t getEstAirtimeFor(int len_bytes) override; - void startSendRaw(const uint8_t* bytes, int len) override; + bool startSendRaw(const uint8_t* bytes, int len) override; bool isSendComplete() override; void onSendFinished() override; From 6218c1e7ae4cf7d0c8bb78274759d5927b758e97 Mon Sep 17 00:00:00 2001 From: hank Date: Mon, 12 May 2025 00:56:30 -0700 Subject: [PATCH 075/154] Fixes to the PMU calls --- src/helpers/TBeamS3SupremeBoard.h | 23 +-- .../lilygo_tbeam_supreme_SX1262/target.cpp | 146 ++++++++++-------- 2 files changed, 93 insertions(+), 76 deletions(-) diff --git a/src/helpers/TBeamS3SupremeBoard.h b/src/helpers/TBeamS3SupremeBoard.h index 2b8232d8..200756a2 100644 --- a/src/helpers/TBeamS3SupremeBoard.h +++ b/src/helpers/TBeamS3SupremeBoard.h @@ -47,15 +47,17 @@ #define I2C_RTC_ADD 0x51 //RTC I2C address on Wire1 #define I2C_PMU_ADD 0x34 //AXP2101 I2C address on Wire1 - +#define PMU_WIRE_PORT Wire1 +#define XPOWERS_CHIP_AXP2101 class TBeamS3SupremeBoard : public ESP32Board { - + XPowersAXP2101 PMU; public: +#ifdef MESH_DEBUG + void printPMU(); +#endif + bool power_init(); void begin() { - - bool power_init(); - ESP32Board::begin(); esp_reset_reason_t reason = esp_reset_reason(); @@ -68,6 +70,7 @@ public: rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); } + power_init(); } void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { @@ -94,12 +97,14 @@ public: } uint16_t getBattMilliVolts() override { - - return 0; + return PMU.getBattVoltage(); } - uint16_t getBattPercent(); - + uint16_t getBattPercent() { + //Read the PMU fuel guage for battery % + uint16_t battPercent = PMU.getBatteryPercent(); + return battPercent; + } const char* getManufacturerName() const override { return "LilyGo T-Beam S3 Supreme SX1262"; } diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.cpp b/variants/lilygo_tbeam_supreme_SX1262/target.cpp index bbdd604e..fe767729 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.cpp +++ b/variants/lilygo_tbeam_supreme_SX1262/target.cpp @@ -3,9 +3,6 @@ TBeamS3SupremeBoard board; -// Using PMU AXP2102 -XPowersAXP2101 PMU; - bool pmuIntFlag; #ifndef LORA_CR @@ -28,103 +25,125 @@ SensorManager sensors; static void setPMUIntFlag(){ pmuIntFlag = true; } +#ifdef MESH_DEBUG +void TBeamS3SupremeBoard::printPMU() +{ + Serial.print("isCharging:"); Serial.println(PMU.isCharging() ? "YES" : "NO"); + Serial.print("isDischarge:"); Serial.println(PMU.isDischarge() ? "YES" : "NO"); + Serial.print("isVbusIn:"); Serial.println(PMU.isVbusIn() ? "YES" : "NO"); + Serial.print("getBattVoltage:"); Serial.print(PMU.getBattVoltage()); Serial.println("mV"); + Serial.print("getVbusVoltage:"); Serial.print(PMU.getVbusVoltage()); Serial.println("mV"); + Serial.print("getSystemVoltage:"); Serial.print(PMU.getSystemVoltage()); Serial.println("mV"); -bool power_init() { - //Start up Wire1 with PMU address - //Serial.println("Starting Wire1 for PMU"); - //Wire1.begin(I2C_PMU_ADD); - //Wire1.begin(PIN_BOARD_SDA1,PIN_BOARD_SCL1); - - //Set LED to indicate charge state - Serial.println("Setting charge led"); - PMU.setChargingLedMode(XPOWERS_CHG_LED_CTRL_CHG); - - //Set up PMU interrupts - Serial.println("Setting up PMU interrupts"); - pinMode(PIN_PMU_IRQ,INPUT_PULLUP); - attachInterrupt(PIN_PMU_IRQ,setPMUIntFlag,FALLING); + // The battery percentage may be inaccurate at first use, the PMU will automatically + // learn the battery curve and will automatically calibrate the battery percentage + // after a charge and discharge cycle + if (PMU.isBatteryConnect()) { + Serial.print("getBatteryPercent:"); Serial.print(PMU.getBatteryPercent()); Serial.println("%"); + } - //GPS - Serial.println("Setting and enabling a-ldo4 for GPS"); - PMU.setALDO4Voltage(3300); - PMU.enableALDO4(); //disable to save power - - //Lora - Serial.println("Setting and enabling a-ldo3 for LoRa"); - PMU.setALDO3Voltage(3300); - PMU.enableALDO3(); + Serial.println(); +} +#endif - //To avoid SPI bus issues during power up, reset OLED, sensor, and SD card supplies - Serial.println("Reset a-ldo1&2 and b-ldo1"); - if(ESP_SLEEP_WAKEUP_UNDEFINED == esp_sleep_get_wakeup_cause()){ +bool TBeamS3SupremeBoard::power_init() +{ + bool result = PMU.begin(PMU_WIRE_PORT, I2C_PMU_ADD, PIN_BOARD_SDA1, PIN_BOARD_SCL1); + if (result == false) { + MESH_DEBUG_PRINTLN("power is not online..."); while (1)delay(50); + } + MESH_DEBUG_PRINTLN("Setting charge led"); + PMU.setChargingLedMode(XPOWERS_CHG_LED_CTRL_CHG); + + // Set up PMU interrupts + MESH_DEBUG_PRINTLN("Setting up PMU interrupts"); + pinMode(PIN_PMU_IRQ, INPUT_PULLUP); + attachInterrupt(PIN_PMU_IRQ, setPMUIntFlag, FALLING); + + // GPS + MESH_DEBUG_PRINTLN("Setting and enabling a-ldo4 for GPS"); + PMU.setALDO4Voltage(3300); + PMU.enableALDO4(); // disable to save power + + // Lora + MESH_DEBUG_PRINTLN("Setting and enabling a-ldo3 for LoRa"); + PMU.setALDO3Voltage(3300); + PMU.enableALDO3(); + + // To avoid SPI bus issues during power up, reset OLED, sensor, and SD card supplies + MESH_DEBUG_PRINTLN("Reset a-ldo1&2 and b-ldo1"); + if (ESP_SLEEP_WAKEUP_UNDEFINED == esp_sleep_get_wakeup_cause()) + { PMU.enableALDO1(); PMU.enableALDO2(); PMU.enableBLDO1(); delay(250); } - - //BME280 and OLED - Serial.println("Setting and enabling a-ldo1 for oled"); + + // BME280 and OLED + MESH_DEBUG_PRINTLN("Setting and enabling a-ldo1 for oled"); PMU.setALDO1Voltage(3300); PMU.enableALDO1(); - //QMC6310U - Serial.println("Setting and enabling a-ldo2 for QMC"); + // QMC6310U + MESH_DEBUG_PRINTLN("Setting and enabling a-ldo2 for QMC"); PMU.setALDO2Voltage(3300); - PMU.enableALDO2(); //disable to save power + PMU.enableALDO2(); // disable to save power - //SD card - Serial.println("Setting and enabling b-ldo2 for SD card"); + // SD card + MESH_DEBUG_PRINTLN("Setting and enabling b-ldo2 for SD card"); PMU.setBLDO1Voltage(3300); PMU.enableBLDO1(); - //Out to header pins - Serial.println("Setting and enabling b-ldo2 for output to header"); + // Out to header pins + MESH_DEBUG_PRINTLN("Setting and enabling b-ldo2 for output to header"); PMU.setBLDO2Voltage(3300); PMU.enableBLDO2(); - Serial.println("Setting and enabling dcdc4 for output to header"); - PMU.setDC4Voltage(XPOWERS_AXP2101_DCDC4_VOL2_MAX); //1.8V + MESH_DEBUG_PRINTLN("Setting and enabling dcdc4 for output to header"); + PMU.setDC4Voltage(XPOWERS_AXP2101_DCDC4_VOL2_MAX); // 1.8V PMU.enableDC4(); - Serial.println("Setting and enabling dcdc5 for output to header"); + MESH_DEBUG_PRINTLN("Setting and enabling dcdc5 for output to header"); PMU.setDC5Voltage(3300); PMU.enableDC5(); - //Other power rails - Serial.println("Setting and enabling dcdc3 for ?"); - PMU.setDC3Voltage(3300); //doesn't go anywhere in the schematic?? + // Other power rails + MESH_DEBUG_PRINTLN("Setting and enabling dcdc3 for ?"); + PMU.setDC3Voltage(3300); // doesn't go anywhere in the schematic?? PMU.enableDC3(); - //Unused power rails - Serial.println("Disabling unused supplies dcdc2, dldo1 and dldo2"); + // Unused power rails + MESH_DEBUG_PRINTLN("Disabling unused supplies dcdc2, dldo1 and dldo2"); PMU.disableDC2(); PMU.disableDLDO1(); - PMU.disableDLDO2(); + PMU.disableDLDO2(); - //Set charge current to 300mA - Serial.println("Setting battery charge current limit and voltage"); + // Set charge current to 300mA + MESH_DEBUG_PRINTLN("Setting battery charge current limit and voltage"); PMU.setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_300MA); PMU.setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2); - //enable battery voltage measurement - Serial.println("Enabling battery measurement"); + // enable battery voltage measurement + MESH_DEBUG_PRINTLN("Enabling battery measurement"); PMU.enableBattVoltageMeasure(); - //Reset and re-enable PMU interrupts - Serial.println("Re-enable interrupts"); + // Reset and re-enable PMU interrupts + MESH_DEBUG_PRINTLN("Re-enable interrupts"); PMU.disableIRQ(XPOWERS_AXP2101_ALL_IRQ); PMU.clearIrqStatus(); PMU.enableIRQ( - XPOWERS_AXP2101_BAT_INSERT_IRQ | XPOWERS_AXP2101_BAT_REMOVE_IRQ | //Battery interrupts - XPOWERS_AXP2101_VBUS_INSERT_IRQ | XPOWERS_AXP2101_VBUS_REMOVE_IRQ | //VBUS interrupts - XPOWERS_AXP2101_PKEY_SHORT_IRQ | XPOWERS_AXP2101_PKEY_LONG_IRQ | //Power Key interrupts - XPOWERS_AXP2101_BAT_CHG_DONE_IRQ | XPOWERS_AXP2101_BAT_CHG_START_IRQ //Charging interrupts + XPOWERS_AXP2101_BAT_INSERT_IRQ | XPOWERS_AXP2101_BAT_REMOVE_IRQ | // Battery interrupts + XPOWERS_AXP2101_VBUS_INSERT_IRQ | XPOWERS_AXP2101_VBUS_REMOVE_IRQ | // VBUS interrupts + XPOWERS_AXP2101_PKEY_SHORT_IRQ | XPOWERS_AXP2101_PKEY_LONG_IRQ | // Power Key interrupts + XPOWERS_AXP2101_BAT_CHG_DONE_IRQ | XPOWERS_AXP2101_BAT_CHG_START_IRQ // Charging interrupts ); +#ifdef MESH_DEBUG + printPMU(); +#endif - //Set the power key off press time + // Set the power key off press time PMU.setPowerKeyPressOffTime(XPOWERS_POWEROFF_4S); - return true; } @@ -154,13 +173,6 @@ bool radio_init() { return true; // success } -uint16_t getBattPercent() { - //Read the PMU fuel guage for battery % - uint16_t battPercent = PMU.getBatteryPercent(); - - return battPercent; -} - uint32_t radio_get_rng_seed() { return radio.random(0x7FFFFFFF); } From 3c2781cce1dbc5aba2aac08376160ecbfa99f9f9 Mon Sep 17 00:00:00 2001 From: hank Date: Mon, 12 May 2025 01:17:28 -0700 Subject: [PATCH 076/154] Disabling MESH_DEBUG by default on TBeam Supreme companion --- variants/lilygo_tbeam_supreme_SX1262/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index 9ccd2e2d..407087bb 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -63,8 +63,8 @@ build_flags = -D BLE_DEBUG_LOGGING=1 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 - -D MESH_PACKET_LOGGING=8 - -D MESH_DEBUG=1 +; -D MESH_PACKET_LOGGING=8 +; -D MESH_DEBUG=1 build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} + +<../examples/companion_radio> From 76639e2a68f92d12784340fc17be7d1e4be6242d Mon Sep 17 00:00:00 2001 From: recrof Date: Mon, 12 May 2025 10:19:33 +0200 Subject: [PATCH 077/154] raise current limit to max for sx126x and sx127x --- variants/generic-e22/platformio.ini | 2 +- variants/heltec_tracker/platformio.ini | 5 ++--- variants/heltec_v2/platformio.ini | 7 ++++--- variants/heltec_v2/target.cpp | 8 ++++++-- variants/heltec_v3/platformio.ini | 2 +- variants/lilygo_t3s3/platformio.ini | 2 +- variants/lilygo_tbeam/target.cpp | 8 ++++++-- variants/lilygo_tlora_v2_1/platformio.ini | 3 ++- variants/lilygo_tlora_v2_1/target.cpp | 8 ++++++-- variants/promicro/platformio.ini | 4 ++-- variants/rak4631/platformio.ini | 2 +- variants/station_g2/platformio.ini | 2 +- variants/t114/platformio.ini | 2 +- variants/techo/platformio.ini | 2 +- variants/thinknode_m1/platformio.ini | 2 +- variants/xiao_c3/platformio.ini | 2 +- variants/xiao_nrf52/platformio.ini | 2 +- variants/xiao_s3_wio/platformio.ini | 4 +--- 18 files changed, 39 insertions(+), 28 deletions(-) diff --git a/variants/generic-e22/platformio.ini b/variants/generic-e22/platformio.ini index 4aa195d9..8b2c293b 100644 --- a/variants/generic-e22/platformio.ini +++ b/variants/generic-e22/platformio.ini @@ -20,7 +20,7 @@ build_flags = -D PIN_BOARD_SCL=22 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 - -D SX126X_CURRENT_LIMIT=130.0f ; for best TX power! + -D SX126X_CURRENT_LIMIT=140 build_src_filter = ${esp32_base.build_src_filter} +<../variants/generic-e22> lib_deps = diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 679fe163..27909036 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -16,7 +16,7 @@ build_flags = -D PIN_TFT_SDA=42 ; SDIN -D PIN_TFT_SCL=41 ; SCLK -D PIN_TFT_DC=40 ; RS (register select) - -D PIN_TFT_RST=39 ; RES + -D PIN_TFT_RST=39 ; RES -D PIN_TFT_CS=38 -D USE_PIN_TFT=1 -D PIN_VEXT_EN=3 ; Vext is connected to VDD which is also connected to OLED & GPS @@ -25,7 +25,7 @@ build_flags = -D PIN_GPS_TX=34 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 - -D SX126X_CURRENT_LIMIT=130.0f ; for best TX power! + -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 build_src_filter = ${esp32_base.build_src_filter} +<../variants/heltec_tracker> @@ -58,4 +58,3 @@ lib_deps = densaugeo/base64 @ ~1.4.0 stevemarple/MicroNMEA @ ^2.0.6 adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 - \ No newline at end of file diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index e2a6e09a..86d5312c 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/heltec_v2/platformio.ini @@ -5,13 +5,14 @@ build_flags = ${esp32_base.build_flags} -I variants/heltec_v2 -D HELTEC_LORA_V2 + -D RADIO_CLASS=CustomSX1276 + -D WRAPPER_CLASS=CustomSX1276Wrapper + -D SX127X_CURRENT_LIMIT=120 + -D LORA_TX_POWER=20 -D PIN_BOARD_SDA=4 -D PIN_BOARD_SCL=15 -D PIN_USER_BTN=0 -D PIN_OLED_RESET=16 - -D RADIO_CLASS=CustomSX1276 - -D WRAPPER_CLASS=CustomSX1276Wrapper - -D LORA_TX_POWER=20 -D P_LORA_TX_LED=25 build_src_filter = ${esp32_base.build_src_filter} +<../variants/heltec_v2> diff --git a/variants/heltec_v2/target.cpp b/variants/heltec_v2/target.cpp index efb86c67..44eb8def 100644 --- a/variants/heltec_v2/target.cpp +++ b/variants/heltec_v2/target.cpp @@ -23,7 +23,7 @@ SensorManager sensors; bool radio_init() { fallback_clock.begin(); rtc_clock.begin(Wire); - + #if defined(P_LORA_SCLK) spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); #endif @@ -34,8 +34,12 @@ bool radio_init() { return false; // fail } +#ifdef SX127X_CURRENT_LIMIT + radio.setCurrentLimit(SX127X_CURRENT_LIMIT); +#endif + radio.setCRC(1); - + return true; // success } diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 0dbf0917..b8d8eb10 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -15,7 +15,7 @@ build_flags = -D PIN_VEXT_EN=36 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 - -D SX126X_CURRENT_LIMIT=130.0f ; for best TX power! + -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 build_src_filter = ${esp32_base.build_src_filter} +<../variants/heltec_v3> diff --git a/variants/lilygo_t3s3/platformio.ini b/variants/lilygo_t3s3/platformio.ini index 4e1c22ce..724a79b8 100644 --- a/variants/lilygo_t3s3/platformio.ini +++ b/variants/lilygo_t3s3/platformio.ini @@ -21,7 +21,7 @@ build_flags = -D PIN_OLED_RESET=21 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 - -D SX126X_CURRENT_LIMIT=130 + -D SX126X_CURRENT_LIMIT=140 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 diff --git a/variants/lilygo_tbeam/target.cpp b/variants/lilygo_tbeam/target.cpp index 101ab09c..4e761cb2 100644 --- a/variants/lilygo_tbeam/target.cpp +++ b/variants/lilygo_tbeam/target.cpp @@ -23,7 +23,7 @@ SensorManager sensors; bool radio_init() { fallback_clock.begin(); rtc_clock.begin(Wire); - + #if defined(P_LORA_SCLK) spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); #endif @@ -34,8 +34,12 @@ bool radio_init() { return false; // fail } +#ifdef SX127X_CURRENT_LIMIT + radio.setCurrentLimit(SX127X_CURRENT_LIMIT); +#endif + radio.setCRC(1); - + return true; // success } diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index bfd3c60d..0e4d96eb 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -19,10 +19,11 @@ build_flags = -D P_LORA_TX_LED=2 ; LED pin for TX indication -D PIN_VBAT_READ=35 ; Battery voltage reading (analog pin) -D PIN_USER_BTN=0 - -D RADIO_CLASS=CustomSX1276 -D ARDUINO_LOOP_STACK_SIZE=16384 -D DISPLAY_CLASS=SSD1306Display + -D RADIO_CLASS=CustomSX1276 -D WRAPPER_CLASS=CustomSX1276Wrapper + -D SX127X_CURRENT_LIMIT=120 -D LORA_TX_POWER=20 build_src_filter = ${esp32_base.build_src_filter} +<../variants/lilygo_tlora_v2_1> diff --git a/variants/lilygo_tlora_v2_1/target.cpp b/variants/lilygo_tlora_v2_1/target.cpp index 76d12382..f5c14fe7 100644 --- a/variants/lilygo_tlora_v2_1/target.cpp +++ b/variants/lilygo_tlora_v2_1/target.cpp @@ -19,7 +19,7 @@ SensorManager sensors; bool radio_init() { fallback_clock.begin(); rtc_clock.begin(Wire); - + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8); if (status != RADIOLIB_ERR_NONE) { @@ -28,8 +28,12 @@ bool radio_init() { return false; // fail } +#ifdef SX127X_CURRENT_LIMIT + radio.setCurrentLimit(SX127X_CURRENT_LIMIT); +#endif + radio.setCRC(1); - + return true; // success } diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index b2b7465c..5599cdd8 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -7,7 +7,7 @@ build_flags = ${nrf52840_base.build_flags} -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 - -D SX126X_CURRENT_LIMIT=130 + -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 -D DISPLAY_CLASS=SSD1306Display -D PIN_BOARD_SCL=7 @@ -108,7 +108,7 @@ build_flags = ${nrf52840_base.build_flags} -D RADIO_CLASS=CustomLLCC68 -D WRAPPER_CLASS=CustomLLCC68Wrapper -D LORA_TX_POWER=22 - -D SX126X_CURRENT_LIMIT=130 + -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 build_src_filter = ${nrf52840_base.build_src_filter} diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 85f59bfa..f3c3d70c 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -13,7 +13,7 @@ build_flags = ${nrf52840_base.build_flags} -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 - -D SX126X_CURRENT_LIMIT=130 + -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 build_src_filter = ${nrf52840_base.build_src_filter} + diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index b81f25e4..d3f45eb0 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -14,7 +14,7 @@ build_flags = -D PIN_USER_BTN=0 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 - -D SX126X_CURRENT_LIMIT=130.0f ; for best TX power! + -D SX126X_CURRENT_LIMIT=140 ; -D SX126X_RX_BOOSTED_GAIN=1 - DO NOT ENABLE THIS! ; https://wiki.uniteng.com/en/meshtastic/station-g2#impact-of-lora-node-dense-areashigh-noise-environments-on-rf-performance build_src_filter = ${esp32_base.build_src_filter} diff --git a/variants/t114/platformio.ini b/variants/t114/platformio.ini index a896adce..13f46b2b 100644 --- a/variants/t114/platformio.ini +++ b/variants/t114/platformio.ini @@ -20,7 +20,7 @@ build_flags = ${nrf52840_t114.build_flags} -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 - -D SX126X_CURRENT_LIMIT=130 + -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 build_src_filter = ${nrf52840_t114.build_src_filter} + diff --git a/variants/techo/platformio.ini b/variants/techo/platformio.ini index b2a9af07..bed06c50 100644 --- a/variants/techo/platformio.ini +++ b/variants/techo/platformio.ini @@ -19,7 +19,7 @@ build_flags = ${nrf52840_techo.build_flags} -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 - -D SX126X_CURRENT_LIMIT=130 + -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 build_src_filter = ${nrf52840_techo.build_src_filter} + diff --git a/variants/thinknode_m1/platformio.ini b/variants/thinknode_m1/platformio.ini index aefafe85..6e570df4 100644 --- a/variants/thinknode_m1/platformio.ini +++ b/variants/thinknode_m1/platformio.ini @@ -19,7 +19,7 @@ build_flags = ${nrf52840_thinknode_m1.build_flags} -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 - -D SX126X_CURRENT_LIMIT=130 + -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 build_src_filter = ${nrf52840_thinknode_m1.build_src_filter} + diff --git a/variants/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index 7af43b0e..6024905f 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -15,7 +15,7 @@ build_flags = -D PIN_BOARD_SCL=D7 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 - -D SX126X_CURRENT_LIMIT=130.0f ; for best TX power! + -D SX126X_CURRENT_LIMIT=140 build_src_filter = ${esp32_base.build_src_filter} +<../variants/xiao_c3> diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index ce515b24..ffc01806 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -33,7 +33,7 @@ build_flags = ${nrf52840_xiao.build_flags} -D P_LORA_NSS=D4 -D SX126X_DIO2_AS_RF_SWITCH=1 -D SX126X_DIO3_TCXO_VOLTAGE=1.8 - -D SX126X_CURRENT_LIMIT=130 + -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 build_src_filter = ${nrf52840_xiao.build_src_filter} + diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index bc28c285..af3e9e40 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -17,7 +17,7 @@ build_flags = ${esp32_base.build_flags} -D PIN_STATUS_LED=48 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 - -D SX126X_CURRENT_LIMIT=130 + -D SX126X_CURRENT_LIMIT=140 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 @@ -111,5 +111,3 @@ build_src_filter = ${Xiao_S3_WIO.build_src_filter} lib_deps = ${Xiao_S3_WIO.lib_deps} densaugeo/base64 @ ~1.4.0 - - From 62a5115cc9abdec21a06c96359bab1597cf8a70f Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 12 May 2025 19:20:02 +1000 Subject: [PATCH 078/154] * T114: lib_deps missing MicroNMEA --- variants/t114/platformio.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/variants/t114/platformio.ini b/variants/t114/platformio.ini index 13f46b2b..43c6e167 100644 --- a/variants/t114/platformio.ini +++ b/variants/t114/platformio.ini @@ -26,6 +26,9 @@ build_src_filter = ${nrf52840_t114.build_src_filter} + + +<../variants/t114> +lib_deps = + ${nrf52840_t114.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 debug_tool = jlink upload_protocol = nrfutil @@ -84,7 +87,6 @@ lib_deps = adafruit/Adafruit GFX Library @ ^1.12.1 ${Heltec_t114.lib_deps} densaugeo/base64 @ ~1.4.0 - stevemarple/MicroNMEA @ ^2.0.6 [env:Heltec_t114_companion_radio_usb] extends = Heltec_t114 From 177dd90ca103c85f3b9f4d9a7debbc0b4e82c3b7 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 13 May 2025 15:38:10 +1000 Subject: [PATCH 079/154] * Repeater/Room server: new diagnostics, stats.n_full_events now repurposed to 'err_events' (bit flags) * new Radio::isInRecvMode() method --- examples/simple_repeater/main.cpp | 4 ++-- examples/simple_room_server/main.cpp | 4 ++-- src/Dispatcher.cpp | 18 ++++++++++++++++-- src/Dispatcher.h | 14 ++++++++++++-- src/helpers/RadioLibWrappers.cpp | 4 ++++ src/helpers/RadioLibWrappers.h | 1 + src/helpers/esp32/ESPNOWRadio.cpp | 9 ++++++++- src/helpers/esp32/ESPNOWRadio.h | 1 + 8 files changed, 46 insertions(+), 9 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 26923c77..76792909 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -91,7 +91,7 @@ struct RepeaterStats { uint32_t total_up_time_secs; uint32_t n_sent_flood, n_sent_direct; uint32_t n_recv_flood, n_recv_direct; - uint16_t n_full_events; + uint16_t err_events; // was 'n_full_events' int16_t last_snr; // x 4 uint16_t n_direct_dups, n_flood_dups; }; @@ -195,7 +195,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { stats.n_sent_direct = getNumSentDirect(); stats.n_recv_flood = getNumRecvFlood(); stats.n_recv_direct = getNumRecvDirect(); - stats.n_full_events = getNumFullEvents(); + stats.err_events = _err_flags; stats.last_snr = (int16_t)(radio_driver.getLastSNR() * 4); stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups(); stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups(); diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index a5d78e3c..756be6d5 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -138,7 +138,7 @@ struct ServerStats { uint32_t total_up_time_secs; uint32_t n_sent_flood, n_sent_direct; uint32_t n_recv_flood, n_recv_direct; - uint16_t n_full_events; + uint16_t err_events; // was 'n_full_events' int16_t last_snr; // x 4 uint16_t n_direct_dups, n_flood_dups; uint16_t n_posted, n_post_push; @@ -301,7 +301,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { stats.n_sent_direct = getNumSentDirect(); stats.n_recv_flood = getNumRecvFlood(); stats.n_recv_direct = getNumRecvDirect(); - stats.n_full_events = getNumFullEvents(); + stats.err_events = _err_flags; stats.last_snr = (int16_t)(radio_driver.getLastSNR() * 4); stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups(); stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups(); diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 2b4c4675..5a09c171 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -13,7 +13,7 @@ namespace mesh { void Dispatcher::begin() { n_sent_flood = n_sent_direct = 0; n_recv_flood = n_recv_direct = 0; - n_full_events = 0; + _err_flags = 0; _radio->begin(); } @@ -34,6 +34,18 @@ uint32_t Dispatcher::getCADFailMaxDuration() const { } void Dispatcher::loop() { + // check for radio 'stuck' in mode other than Rx + bool is_recv = _radio->isInRecvMode(); + if (is_recv != prev_isrecv_mode) { + prev_isrecv_mode = is_recv; + if (!is_recv) { + radio_nonrx_start = _ms->getMillis(); + } + } + if (!is_recv && _ms->getMillis() - radio_nonrx_start > 8000) { // radio has not been in Rx mode for 8 seconds! + _err_flags |= ERR_EVENT_STARTRX_TIMEOUT; + } + if (outbound) { // waiting for outbound send to be completed if (_radio->isSendComplete()) { long t = _ms->getMillis() - outbound_start; @@ -199,6 +211,8 @@ void Dispatcher::checkSend() { } if (_ms->getMillis() - cad_busy_start > getCADFailMaxDuration()) { + _err_flags |= ERR_EVENT_CAD_TIMEOUT; + MESH_DEBUG_PRINTLN("%s Dispatcher::checkSend(): CAD busy max duration reached!", getLogDateTime()); // channel activity has gone on too long... (Radio might be in a bad state) // force the pending transmit below... @@ -264,7 +278,7 @@ void Dispatcher::checkSend() { Packet* Dispatcher::obtainNewPacket() { auto pkt = _mgr->allocNew(); // TODO: zero out all fields if (pkt == NULL) { - n_full_events++; + _err_flags |= ERR_EVENT_FULL; } else { pkt->payload_len = pkt->path_len = 0; pkt->_snr = 0; diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 96db4fc5..01dd76bc 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -56,6 +56,8 @@ public: */ virtual void onSendFinished() = 0; + virtual bool isInRecvMode() const = 0; + /** * \returns true if the radio is currently mid-receive of a packet. */ @@ -91,6 +93,10 @@ typedef uint32_t DispatcherAction; #define ACTION_RETRANSMIT(pri) (((uint32_t)1 + (pri))<<24) #define ACTION_RETRANSMIT_DELAYED(pri, _delay) ((((uint32_t)1 + (pri))<<24) | (_delay)) +#define ERR_EVENT_FULL (1 << 0) +#define ERR_EVENT_CAD_TIMEOUT (1 << 1) +#define ERR_EVENT_STARTRX_TIMEOUT (1 << 2) + /** * \brief The low-level task that manages detecting incoming Packets, and the queueing * and scheduling of outbound Packets. @@ -100,9 +106,10 @@ class Dispatcher { unsigned long outbound_expiry, outbound_start, total_air_time; unsigned long next_tx_time; unsigned long cad_busy_start; + unsigned long radio_nonrx_start; + bool prev_isrecv_mode; uint32_t n_sent_flood, n_sent_direct; uint32_t n_recv_flood, n_recv_direct; - uint32_t n_full_events; void processRecvPacket(Packet* pkt); @@ -110,12 +117,16 @@ protected: PacketManager* _mgr; Radio* _radio; MillisecondClock* _ms; + uint16_t _err_flags; Dispatcher(Radio& radio, MillisecondClock& ms, PacketManager& mgr) : _radio(&radio), _ms(&ms), _mgr(&mgr) { outbound = NULL; total_air_time = 0; next_tx_time = 0; cad_busy_start = 0; + _err_flags = 0; + radio_nonrx_start = 0; + prev_isrecv_mode = true; } virtual DispatcherAction onRecvPacket(Packet* pkt) = 0; @@ -145,7 +156,6 @@ public: uint32_t getNumSentDirect() const { return n_sent_direct; } uint32_t getNumRecvFlood() const { return n_recv_flood; } uint32_t getNumRecvDirect() const { return n_recv_direct; } - uint32_t getNumFullEvents() const { return n_full_events; } // helper methods bool millisHasNowPassed(unsigned long timestamp) const; diff --git a/src/helpers/RadioLibWrappers.cpp b/src/helpers/RadioLibWrappers.cpp index fa678431..39fb340e 100644 --- a/src/helpers/RadioLibWrappers.cpp +++ b/src/helpers/RadioLibWrappers.cpp @@ -44,6 +44,10 @@ void RadioLibWrapper::startRecv() { } } +bool RadioLibWrapper::isInRecvMode() const { + return (state & ~STATE_INT_READY) == STATE_RX; +} + int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) { if (state & STATE_INT_READY) { int len = _radio->getPacketLength(); diff --git a/src/helpers/RadioLibWrappers.h b/src/helpers/RadioLibWrappers.h index a97987ef..8dc03e28 100644 --- a/src/helpers/RadioLibWrappers.h +++ b/src/helpers/RadioLibWrappers.h @@ -22,6 +22,7 @@ public: bool startSendRaw(const uint8_t* bytes, int len) override; bool isSendComplete() override; void onSendFinished() override; + bool isInRecvMode() const override; uint32_t getPacketsRecv() const { return n_recv; } uint32_t getPacketsSent() const { return n_sent; } diff --git a/src/helpers/esp32/ESPNOWRadio.cpp b/src/helpers/esp32/ESPNOWRadio.cpp index a48a3430..ced19f91 100644 --- a/src/helpers/esp32/ESPNOWRadio.cpp +++ b/src/helpers/esp32/ESPNOWRadio.cpp @@ -5,7 +5,7 @@ static uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; static esp_now_peer_info_t peerInfo; -static bool is_send_complete = false; +static volatile bool is_send_complete = false; static esp_err_t last_send_result; static uint8_t rx_buf[256]; static uint8_t last_rx_len = 0; @@ -44,6 +44,8 @@ void ESPNOWRadio::init() { peerInfo.channel = 0; peerInfo.encrypt = false; + is_send_complete = true; + // Add peer if (esp_now_add_peer(&peerInfo) == ESP_OK) { ESPNOW_DEBUG_PRINTLN("init success"); @@ -86,6 +88,11 @@ bool ESPNOWRadio::isSendComplete() { return is_send_complete; } void ESPNOWRadio::onSendFinished() { + is_send_complete = true; +} + +bool ESPNOWRadio::isInRecvMode() const { + return is_send_complete; // if NO send in progress, then we're in Rx mode } float ESPNOWRadio::getLastRSSI() const { return 0; } diff --git a/src/helpers/esp32/ESPNOWRadio.h b/src/helpers/esp32/ESPNOWRadio.h index 4b33df58..5d4ff583 100644 --- a/src/helpers/esp32/ESPNOWRadio.h +++ b/src/helpers/esp32/ESPNOWRadio.h @@ -15,6 +15,7 @@ public: bool startSendRaw(const uint8_t* bytes, int len) override; bool isSendComplete() override; void onSendFinished() override; + bool isInRecvMode() const override; uint32_t getPacketsRecv() const { return n_recv; } uint32_t getPacketsSent() const { return n_sent; } From 2ea05a51826f3eddf6e1e89b649c732de7adb08e Mon Sep 17 00:00:00 2001 From: cod3doomy Date: Mon, 12 May 2025 23:21:37 -0700 Subject: [PATCH 080/154] t-beam supreme: added GPS functionality Enabled GPS and verified with meshcli. All supreme envs build. --- src/helpers/TBeamS3SupremeBoard.h | 3 +- .../platformio.ini | 4 +- .../lilygo_tbeam_supreme_SX1262/target.cpp | 141 +++++++++++++++++- variants/lilygo_tbeam_supreme_SX1262/target.h | 39 ++++- 4 files changed, 179 insertions(+), 8 deletions(-) diff --git a/src/helpers/TBeamS3SupremeBoard.h b/src/helpers/TBeamS3SupremeBoard.h index 200756a2..e30c02ed 100644 --- a/src/helpers/TBeamS3SupremeBoard.h +++ b/src/helpers/TBeamS3SupremeBoard.h @@ -27,7 +27,7 @@ #define P_BOARD_SPI_MOSI 35 //SPI for SD Card and QMI8653 (IMU) #define P_BOARD_SPI_MISO 37 //SPI for SD Card and QMI8653 (IMU) #define P_BOARD_SPI_SCK 36 //SPI for SD Card and QMI8653 (IMU) -#define P_BPARD_SPI_CS 47 //SPI for SD Card and QMI8653 (IMU) +#define P_BPARD_SPI_CS 47 //Pin for SD Card CS #define P_BOARD_IMU_CS 34 //Pin for QMI8653 (IMU) CS #define P_BOARD_IMU_INT 33 //IMU Int pin @@ -37,6 +37,7 @@ #define P_GPS_TX 8 //GPS TX pin #define P_GPS_WAKE 7 //GPS Wakeup pin #define P_GPS_1PPS 6 //GPS 1PPS pin +#define GPS_BAUD_RATE 9600 //I2C Wire addresses #define I2C_BME280_ADD 0x76 //BME280 sensor I2C address on Wire diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index 407087bb..c53c51ac 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -9,14 +9,16 @@ build_flags = -D WRAPPER_CLASS=CustomSX1262Wrapper ;-D DISPLAY_CLASS=SSD1306Display ;Needs to be modified for SH1106 -D SX126X_RX_BOOSTED_GAIN=1 + -D HAS_PMU=1 + -D HAS_GPS=1 build_src_filter = ${esp32_base.build_src_filter} +<../variants/lilygo_tbeam_supreme_SX1262> board_build.partitions = min_spiffs.csv ; get around 4mb flash limit lib_deps = ${esp32_base.lib_deps} - lewisxhe/PCF8563_Library@^1.0.1 lewisxhe/XPowersLib @ ^0.2.7 ;adafruit/Adafruit SSD1306 @ ^2.5.13 + stevemarple/MicroNMEA @ ^2.0.6 ; === LILYGO T-Beam S3 Supreme with SX1262 environments === [env:T_Beam_S3_Supreme_SX1262_repeater] diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.cpp b/variants/lilygo_tbeam_supreme_SX1262/target.cpp index fe767729..382d5111 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.cpp +++ b/variants/lilygo_tbeam_supreme_SX1262/target.cpp @@ -1,5 +1,6 @@ #include #include "target.h" +#include TBeamS3SupremeBoard board; @@ -20,7 +21,8 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +TbeamSupSensorManager sensors = TbeamSupSensorManager(nmea); static void setPMUIntFlag(){ pmuIntFlag = true; @@ -74,9 +76,9 @@ bool TBeamS3SupremeBoard::power_init() MESH_DEBUG_PRINTLN("Reset a-ldo1&2 and b-ldo1"); if (ESP_SLEEP_WAKEUP_UNDEFINED == esp_sleep_get_wakeup_cause()) { - PMU.enableALDO1(); - PMU.enableALDO2(); - PMU.enableBLDO1(); + PMU.disableALDO1(); + PMU.disableALDO2(); + PMU.disableBLDO1(); delay(250); } @@ -121,7 +123,7 @@ bool TBeamS3SupremeBoard::power_init() // Set charge current to 300mA MESH_DEBUG_PRINTLN("Setting battery charge current limit and voltage"); - PMU.setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_300MA); + PMU.setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA); PMU.setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2); // enable battery voltage measurement @@ -147,6 +149,59 @@ bool TBeamS3SupremeBoard::power_init() return true; } +bool l76kProbe() +{ + bool result = false; + uint32_t startTimeout ; + Serial1.write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n"); + delay(5); + // Get version information + startTimeout = millis() + 3000; + MESH_DEBUG_PRINTLN("Trying to init L76K GPS"); + // Serial1.flush(); + while (Serial1.available()) { + int c = Serial1.read(); + // Serial.write(c); + // Serial.print("."); + // Serial.flush(); + // Serial1.flush(); + if (millis() > startTimeout) { + MESH_DEBUG_PRINTLN("L76K NMEA timeout!"); + return false; + } + }; + Serial.println(); + Serial1.flush(); + delay(200); + + Serial1.write("$PCAS06,0*1B\r\n"); + startTimeout = millis() + 500; + String ver = ""; + while (!Serial1.available()) { + if (millis() > startTimeout) { + MESH_DEBUG_PRINTLN("Get L76K timeout!"); + return false; + } + } + Serial1.setTimeout(10); + ver = Serial1.readStringUntil('\n'); + if (ver.startsWith("$GPTXT,01,01,02")) { + MESH_DEBUG_PRINTLN("L76K GNSS init succeeded, using L76K GNSS Module\n"); + result = true; + } + delay(500); + + // Initialize the L76K Chip, use GPS + GLONASS + Serial1.write("$PCAS04,5*1C\r\n"); + delay(250); + // only ask for RMC and GGA + Serial1.write("$PCAS03,1,0,0,0,1,0,0,0,0,0,,,0,0*02\r\n"); + delay(250); + // Switch to Vehicle Mode, since SoftRF enables Aviation < 2g + Serial1.write("$PCAS11,3*1E\r\n"); + return result; +} + bool radio_init() { fallback_clock.begin(); Wire1.begin(PIN_BOARD_SDA1,PIN_BOARD_SCL1); @@ -188,6 +243,82 @@ void radio_set_tx_power(uint8_t dbm) { radio.setOutputPower(dbm); } +void TbeamSupSensorManager::start_gps() +{ + gps_active = true; + pinMode(P_GPS_WAKE, OUTPUT); + digitalWrite(P_GPS_WAKE, HIGH); +} + +void TbeamSupSensorManager::sleep_gps() { + gps_active = false; + pinMode(P_GPS_WAKE, OUTPUT); + digitalWrite(P_GPS_WAKE, LOW); +} + +bool TbeamSupSensorManager::begin() { + // init GPS port + + Serial1.begin(GPS_BAUD_RATE, SERIAL_8N1, P_GPS_RX, P_GPS_TX); + + bool result = false; + for ( int i = 0; i < 3; ++i) { + result = l76kProbe(); + if (result) { + gps_active = true; + return result; + } + } + return result; +} + +bool TbeamSupSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { + if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission? + telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, 0.0f); + } + return true; +} + +void TbeamSupSensorManager::loop() { + static long next_gps_update = 0; + + _nmea->loop(); + + if (millis() > next_gps_update) { + if (_nmea->isValid()) { + node_lat = ((double)_nmea->getLatitude())/1000000.; + node_lon = ((double)_nmea->getLongitude())/1000000.; + //Serial.printf("lat %f lon %f\r\n", _lat, _lon); + } + next_gps_update = millis() + 1000; + } +} + +int TbeamSupSensorManager::getNumSettings() const { return 1; } // just one supported: "gps" (power switch) + +const char* TbeamSupSensorManager::getSettingName(int i) const { + return i == 0 ? "gps" : NULL; +} + +const char* TbeamSupSensorManager::getSettingValue(int i) const { + if (i == 0) { + return gps_active ? "1" : "0"; + } + return NULL; +} + +bool TbeamSupSensorManager::setSettingValue(const char* name, const char* value) { + if (strcmp(name, "gps") == 0) { + if (strcmp(value, "0") == 0) { + sleep_gps(); + } else { + start_gps(); + } + return true; + } + return false; // not supported +} + mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.h b/variants/lilygo_tbeam_supreme_SX1262/target.h index f14b2142..107e2950 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.h +++ b/variants/lilygo_tbeam_supreme_SX1262/target.h @@ -1,17 +1,54 @@ #pragma once +#define RADIOLIB_STATIC_ONLY 1 #include #include #include #include #include #include +#include + +class TbeamSupSensorManager: public SensorManager { + bool gps_active = false; + LocationProvider * _nmea; + + void start_gps(); + void sleep_gps(); + public: + TbeamSupSensorManager(LocationProvider &nmea): _nmea(&nmea) { } + bool begin() override; + bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; + void loop() override; + int getNumSettings() const override; + const char* getSettingName(int i) const override; + const char* getSettingValue(int i) const override; + bool setSettingValue(const char* name, const char* value) override; + }; extern TBeamS3SupremeBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern TbeamSupSensorManager sensors; +enum { + POWERMANAGE_ONLINE = _BV(0), + DISPLAY_ONLINE = _BV(1), + RADIO_ONLINE = _BV(2), + GPS_ONLINE = _BV(3), + PSRAM_ONLINE = _BV(4), + SDCARD_ONLINE = _BV(5), + AXDL345_ONLINE = _BV(6), + BME280_ONLINE = _BV(7), + BMP280_ONLINE = _BV(8), + BME680_ONLINE = _BV(9), + QMC6310_ONLINE = _BV(10), + QMI8658_ONLINE = _BV(11), + PCF8563_ONLINE = _BV(12), + OSC32768_ONLINE = _BV(13), +}; + +bool power_init(); bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); From 805ca7b9007556edb62822970cadaf6fad430bed Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 13 May 2025 18:12:58 +1000 Subject: [PATCH 081/154] * CommonCLI: added "clear stats" command --- examples/simple_repeater/main.cpp | 8 +++++++- examples/simple_room_server/main.cpp | 8 +++++++- src/Dispatcher.cpp | 2 ++ src/Dispatcher.h | 4 ++++ src/helpers/CommonCLI.cpp | 3 +++ src/helpers/CommonCLI.h | 1 + src/helpers/RadioLibWrappers.h | 2 ++ src/helpers/SimpleMeshTables.h | 1 + src/helpers/esp32/ESPNOWRadio.h | 2 ++ 9 files changed, 29 insertions(+), 2 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 76792909..ea8e8443 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -686,7 +686,13 @@ public: *dp = 0; // null terminator } - const uint8_t* getSelfIdPubKey() { return self_id.pub_key; } + const uint8_t* getSelfIdPubKey() override { return self_id.pub_key; } + + void clearStats() override { + radio_driver.resetStats(); + resetStats(); + ((SimpleMeshTables *)getTables())->resetStats(); + } void loop() { mesh::Mesh::loop(); diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 756be6d5..05a076b3 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -821,7 +821,13 @@ public: strcpy(reply, "not supported"); } - const uint8_t* getSelfIdPubKey() { return self_id.pub_key; } + const uint8_t* getSelfIdPubKey() override { return self_id.pub_key; } + + void clearStats() override { + radio_driver.resetStats(); + resetStats(); + ((SimpleMeshTables *)getTables())->resetStats(); + } void loop() { mesh::Mesh::loop(); diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 5a09c171..53a5b2ad 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -14,8 +14,10 @@ void Dispatcher::begin() { n_sent_flood = n_sent_direct = 0; n_recv_flood = n_recv_direct = 0; _err_flags = 0; + radio_nonrx_start = _ms->getMillis(); _radio->begin(); + prev_isrecv_mode = _radio->isInRecvMode(); } float Dispatcher::getAirtimeBudgetFactor() const { diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 01dd76bc..bcfba389 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -156,6 +156,10 @@ public: uint32_t getNumSentDirect() const { return n_sent_direct; } uint32_t getNumRecvFlood() const { return n_recv_flood; } uint32_t getNumRecvDirect() const { return n_recv_direct; } + void resetStats() { + n_sent_flood = n_sent_direct = n_recv_flood = n_recv_direct = 0; + _err_flags = 0; + } // helper methods bool millisHasNowPassed(unsigned long timestamp) const; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index d9d83440..f3f7727c 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -169,6 +169,9 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch checkAdvertInterval(); savePrefs(); sprintf(reply, "password now: %s", _prefs->password); // echo back just to let admin know for sure!! + } else if (memcmp(command, "clear stats", 11) == 0) { + _callbacks->clearStats(); + strcpy(reply, "(OK - stats reset)"); } else if (memcmp(command, "get ", 4) == 0) { const char* config = &command[4]; if (memcmp(config, "af", 2) == 0) { diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 8bd3d150..0e88c266 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -42,6 +42,7 @@ public: virtual void setTxPower(uint8_t power_dbm) = 0; virtual void formatNeighborsReply(char *reply) = 0; virtual const uint8_t* getSelfIdPubKey() = 0; + virtual void clearStats() = 0; }; class CommonCLI { diff --git a/src/helpers/RadioLibWrappers.h b/src/helpers/RadioLibWrappers.h index 8dc03e28..bdbadb19 100644 --- a/src/helpers/RadioLibWrappers.h +++ b/src/helpers/RadioLibWrappers.h @@ -26,6 +26,8 @@ public: uint32_t getPacketsRecv() const { return n_recv; } uint32_t getPacketsSent() const { return n_sent; } + void resetStats() { n_recv = n_sent = 0; } + virtual float getLastRSSI() const override; virtual float getLastSNR() const override; diff --git a/src/helpers/SimpleMeshTables.h b/src/helpers/SimpleMeshTables.h index 0dd6032a..910b8071 100644 --- a/src/helpers/SimpleMeshTables.h +++ b/src/helpers/SimpleMeshTables.h @@ -83,4 +83,5 @@ public: uint32_t getNumDirectDups() const { return _direct_dups; } uint32_t getNumFloodDups() const { return _flood_dups; } + void resetStats() { _direct_dups = _flood_dups = 0; } }; diff --git a/src/helpers/esp32/ESPNOWRadio.h b/src/helpers/esp32/ESPNOWRadio.h index 5d4ff583..c696da3a 100644 --- a/src/helpers/esp32/ESPNOWRadio.h +++ b/src/helpers/esp32/ESPNOWRadio.h @@ -19,6 +19,8 @@ public: uint32_t getPacketsRecv() const { return n_recv; } uint32_t getPacketsSent() const { return n_sent; } + void resetStats() { n_recv = n_sent = 0; } + virtual float getLastRSSI() const override; virtual float getLastSNR() const override; From b0354871019fd7bf7da1fe85b3d7ffd65729a147 Mon Sep 17 00:00:00 2001 From: Normunds Gavars Date: Tue, 13 May 2025 23:52:49 +0300 Subject: [PATCH 082/154] 283 Add support of INA3221 to Promicro telemetry --- .gitignore | 2 + src/helpers/SensorManager.h | 6 ++ src/helpers/sensors/SensorSettingsManager.h | 72 +++++++++++++ variants/promicro/platformio.ini | 3 +- variants/promicro/target.cpp | 106 +++++++++++++++++++- variants/promicro/target.h | 28 +++++- 6 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 src/helpers/sensors/SensorSettingsManager.h diff --git a/.gitignore b/.gitignore index 7e7cc694..51449c2d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ .vscode/ipch out/ .direnv/ +.DS_Store +.vscode/settings.json diff --git a/src/helpers/SensorManager.h b/src/helpers/SensorManager.h index a5d53939..ae783c58 100644 --- a/src/helpers/SensorManager.h +++ b/src/helpers/SensorManager.h @@ -7,6 +7,12 @@ #define TELEM_CHANNEL_SELF 1 // LPP data channel for 'self' device +#define TELEM_INA3221_ADDRESS 0x40 // INA3221 3 channel current, voltage, power sensor I2C address +#define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts +#define TELEM_INA3221_SETTING_CH1 "INA3221 Channel 1" +#define TELEM_INA3221_SETTING_CH2 "INA3221 Channel 2" +#define TELEM_INA3221_SETTING_CH3 "INA3221 Channel 3" + class SensorManager { public: double node_lat, node_lon; // modify these, if you want to affect Advert location diff --git a/src/helpers/sensors/SensorSettingsManager.h b/src/helpers/sensors/SensorSettingsManager.h new file mode 100644 index 00000000..e3561332 --- /dev/null +++ b/src/helpers/sensors/SensorSettingsManager.h @@ -0,0 +1,72 @@ +#include +#include + +class SensorSettingsManager { + private: + std::vector> settings; + + public: + + int getSettingCount() const { + return static_cast(settings.size()); + }; + + bool addSetting(const std::string& name, bool defaultValue = false){ + for (const auto& setting : settings) { + if (setting.first == name) { + return false; + } + } + settings.emplace_back(name, defaultValue); + return true; + }; + + bool removeSetting(const std::string& name) { + for (auto it = settings.begin(); it != settings.end(); ++it) { + if (it->first == name) { + settings.erase(it); + return true; + } + } + return false; + }; + + const char* getSettingValue(const std::string& name) const{ + for (const auto& setting : settings) { + if (setting.first == name) { + return setting.second ? "true" : "false"; + } + } + return NULL; + }; + + const char* getSettingValue(int index) const { + if (index >= 0 && index < getSettingCount()) { + return settings[index].second ? "true" : "false"; + } + return NULL; + }; + + bool setSettingValue(const std::string& name, const std::string& value) { + for (auto& setting : settings) { + if (setting.first == name) { + // Convert value to boolean + if (value == "1" || value == "true") { + setting.second = true; + } else { + setting.second = false; + } + return true; + } + } + return false; + } + + const char* getSettingName(int index) const { + if (index >= 0 && index < getSettingCount()){ + return settings[index].first.c_str(); + } + return NULL; + }; + +}; \ No newline at end of file diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index b2b7465c..7b370412 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -36,7 +36,8 @@ build_flags = ; -D MESH_DEBUG=1 lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 - + robtillaart/INA3221 @ ^0.4.1 + [env:Faketec_room_server] extends = Faketec build_src_filter = ${Faketec.build_src_filter} diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index 212e3b25..a8a27b4e 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -10,7 +10,7 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; +PromicroSensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 @@ -74,3 +74,107 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } + +PromicroSensorManager::PromicroSensorManager(){ + INA_3221 = new INA3221(TELEM_INA3221_ADDRESS, &Wire); +} + +PromicroSensorManager::~PromicroSensorManager(){ + if (INA_3221) { + delete INA_3221; + INA_3221 = nullptr; + } +} + +bool PromicroSensorManager::begin() { + if (INA_3221->begin() ) { + Serial.print("Found INA3221 at address "); + Serial.print(INA_3221->getAddress()); + Serial.println(); + Serial.print(INA_3221->getDieID(), HEX); + Serial.print(INA_3221->getManufacturerID(), HEX); + Serial.print(INA_3221->getConfiguration(), HEX); + Serial.println(); + + for(int i = 0; i < 3; i++) { + INA_3221->setShuntR(i, TELEM_INA3221_SHUNT_VALUE); + } + // add INA3221 settings to settings manager + settingsManager.addSetting(TELEM_INA3221_SETTING_CH1, true); + settingsManager.addSetting(TELEM_INA3221_SETTING_CH2, true); + settingsManager.addSetting(TELEM_INA3221_SETTING_CH3, true); + INA3221initialized = true; + } + else { + INA3221initialized = false; + Serial.print("INA3221 was not found at I2C address "); + Serial.print(TELEM_INA3221_ADDRESS, HEX); + Serial.println(); + } + return true; +} + +bool PromicroSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { + // TODO: what is the correct permission here? + if (requester_permissions && TELEM_PERM_BASE) { + if (INA3221initialized) { + for(int i = 0; i < 3; i++) { + // add only enabled INA3221 channels to telemetry + if (settingsManager.getSettingValue(INA3221_CHANNEL_NAMES[i]) == "true") { + + // TODO: remove when telemetry support gets properly added + // Serial.print("CH"); + // Serial.print(i); + // Serial.print(" Voltage: "); + // Serial.print(INA_3221->getBusVoltage(i)); + // Serial.print("V Current: "); + // Serial.print(INA_3221->getCurrent(i)); + // Serial.print("A Power: "); + // Serial.print(INA_3221->getPower(i)); + // Serial.println(); + + telemetry.addVoltage(INA3221_CHANNELS[i], INA_3221->getBusVoltage(i)); + telemetry.addCurrent(INA3221_CHANNELS[i], INA_3221->getCurrent(i)); + telemetry.addPower(INA3221_CHANNELS[i], INA_3221->getPower(i)); + } + } + } + } + + return true; +} + +int PromicroSensorManager::getNumSettings() const { + return settingsManager.getSettingCount(); +} + +const char* PromicroSensorManager::getSettingName(int i) const { + return settingsManager.getSettingName(i); +} + +const char* PromicroSensorManager::getSettingValue(int i) const { + return settingsManager.getSettingValue(i); +} + +bool PromicroSensorManager::setSettingValue(const char* name, const char* value) { + if (settingsManager.setSettingValue(name, value)) { + onSettingsChanged(); + return true; + } + return false; +} + +void PromicroSensorManager::onSettingsChanged() { + if (INA3221initialized) { + for(int i = 0; i < 3; i++) { + int channelEnabled = INA_3221->getEnableChannel(i); + bool settingEnabled = settingsManager.getSettingValue(INA3221_CHANNEL_NAMES[i]) == "true"; + if (!settingEnabled && channelEnabled) { + INA_3221->disableChannel(i); + } + if (settingEnabled && !channelEnabled) { + INA_3221->enableChannel(i); + } + } + } +} \ No newline at end of file diff --git a/variants/promicro/target.h b/variants/promicro/target.h index b2c4f9d2..4cfe4b75 100644 --- a/variants/promicro/target.h +++ b/variants/promicro/target.h @@ -8,14 +8,40 @@ #include #include #include +#include +#include extern PromicroBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock 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(); + +class PromicroSensorManager: public SensorManager { + INA3221 * INA_3221; + bool INA3221initialized = false; + SensorSettingsManager settingsManager; + + // INA3221 channels in telemetry + int INA3221_CHANNELS[3] = {TELEM_CHANNEL_SELF + 1, TELEM_CHANNEL_SELF + 2, TELEM_CHANNEL_SELF+ 3}; + char * INA3221_CHANNEL_NAMES[3] = { TELEM_INA3221_SETTING_CH1, TELEM_INA3221_SETTING_CH2, TELEM_INA3221_SETTING_CH3}; + void onSettingsChanged(); + +public: + PromicroSensorManager(); + ~PromicroSensorManager(); + bool begin() override; + bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; + int getNumSettings() const override; + const char* getSettingName(int i) const override; + const char* getSettingValue(int i) const override; + bool setSettingValue(const char* name, const char* value) override; +}; + + +extern PromicroSensorManager sensors; \ No newline at end of file From a56e9ef62fe3951fc7ae4df80a99f097789bc9dc Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 14 May 2025 13:11:10 +1000 Subject: [PATCH 083/154] * TBeam Supreme: refactor for readStringUntil() --- .../lilygo_tbeam_supreme_SX1262/target.cpp | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.cpp b/variants/lilygo_tbeam_supreme_SX1262/target.cpp index 382d5111..3808e5fd 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.cpp +++ b/variants/lilygo_tbeam_supreme_SX1262/target.cpp @@ -149,7 +149,23 @@ bool TBeamS3SupremeBoard::power_init() return true; } -bool l76kProbe() +static bool readStringUntil(Stream& s, char dest[], size_t max_len, char term, unsigned int timeout_millis) { + unsigned long timeout = millis() + timeout_millis; + char *dp = dest; + while (millis() < timeout && dp - dest < max_len - 1) { + if (s.available()) { + char c = s.read(); + if (c == term) break; + *dp++ = c; // append to dest[] + } else { + delay(1); + } + } + *dp = 0; // null terminator + return millis() < timeout; // false, if timed out +} + +static bool l76kProbe() { bool result = false; uint32_t startTimeout ; @@ -170,22 +186,19 @@ bool l76kProbe() return false; } }; - Serial.println(); + Serial1.flush(); delay(200); Serial1.write("$PCAS06,0*1B\r\n"); - startTimeout = millis() + 500; - String ver = ""; - while (!Serial1.available()) { - if (millis() > startTimeout) { - MESH_DEBUG_PRINTLN("Get L76K timeout!"); - return false; - } + + char ver[100]; + if (!readStringUntil(Serial1, ver, sizeof(ver), '\n', 500)) { + MESH_DEBUG_PRINTLN("Get L76K timeout!"); + return false; } - Serial1.setTimeout(10); - ver = Serial1.readStringUntil('\n'); - if (ver.startsWith("$GPTXT,01,01,02")) { + + if (memcmp(ver, "$GPTXT,01,01,02", 15) == 0) { MESH_DEBUG_PRINTLN("L76K GNSS init succeeded, using L76K GNSS Module\n"); result = true; } From e291b57a078a76187c24d2e43e2d8b6d8e942b30 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 14 May 2025 16:50:11 +1000 Subject: [PATCH 084/154] * Dispatcher::checkSend() bug: getOutboundCount() should only count non-future packets --- examples/simple_repeater/main.cpp | 2 +- examples/simple_room_server/main.cpp | 2 +- src/Dispatcher.cpp | 2 +- src/Dispatcher.h | 2 +- src/helpers/StaticPoolPacketManager.cpp | 13 +++++++++++-- src/helpers/StaticPoolPacketManager.h | 3 ++- 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index ea8e8443..6452137f 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -184,7 +184,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { case REQ_TYPE_GET_STATUS: { // guests can also access this now RepeaterStats stats; stats.batt_milli_volts = board.getBattMilliVolts(); - stats.curr_tx_queue_len = _mgr->getOutboundCount(); + stats.curr_tx_queue_len = _mgr->getOutboundCount(0xFFFFFFFF); stats.curr_free_queue_len = _mgr->getFreeCount(); stats.last_rssi = (int16_t) radio_driver.getLastRSSI(); stats.n_packets_recv = radio_driver.getPacketsRecv(); diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 05a076b3..9849ba25 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -290,7 +290,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { case REQ_TYPE_GET_STATUS: { ServerStats stats; stats.batt_milli_volts = board.getBattMilliVolts(); - stats.curr_tx_queue_len = _mgr->getOutboundCount(); + stats.curr_tx_queue_len = _mgr->getOutboundCount(0xFFFFFFFF); stats.curr_free_queue_len = _mgr->getFreeCount(); stats.last_rssi = (int16_t) radio_driver.getLastRSSI(); stats.n_packets_recv = radio_driver.getPacketsRecv(); diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 53a5b2ad..3d5b04fc 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -205,7 +205,7 @@ void Dispatcher::processRecvPacket(Packet* pkt) { } void Dispatcher::checkSend() { - if (_mgr->getOutboundCount() == 0) return; // nothing waiting to send + if (_mgr->getOutboundCount(_ms->getMillis()) == 0) return; // nothing waiting to send if (!millisHasNowPassed(next_tx_time)) return; // still in 'radio silence' phase (from airtime budget setting) if (_radio->isReceiving()) { // LBT - check if radio is currently mid-receive, or if channel activity if (cad_busy_start == 0) { diff --git a/src/Dispatcher.h b/src/Dispatcher.h index bcfba389..d03c9f73 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -78,7 +78,7 @@ public: virtual void queueOutbound(Packet* packet, uint8_t priority, uint32_t scheduled_for) = 0; virtual Packet* getNextOutbound(uint32_t now) = 0; // by priority - virtual int getOutboundCount() const = 0; + virtual int getOutboundCount(uint32_t now) const = 0; virtual int getFreeCount() const = 0; virtual Packet* getOutboundByIdx(int i) = 0; virtual Packet* removeOutboundByIdx(int i) = 0; diff --git a/src/helpers/StaticPoolPacketManager.cpp b/src/helpers/StaticPoolPacketManager.cpp index 07bc8f2e..4f28eac6 100644 --- a/src/helpers/StaticPoolPacketManager.cpp +++ b/src/helpers/StaticPoolPacketManager.cpp @@ -8,6 +8,15 @@ PacketQueue::PacketQueue(int max_entries) { _num = 0; } +int PacketQueue::countBefore(uint32_t now) const { + int n = 0; + for (int j = 0; j < _num; j++) { + if (_schedule_table[j] > now) continue; // scheduled for future... ignore for now + n++; + } + return n; +} + mesh::Packet* PacketQueue::get(uint32_t now) { uint8_t min_pri = 0xFF; int best_idx = -1; @@ -81,8 +90,8 @@ mesh::Packet* StaticPoolPacketManager::getNextOutbound(uint32_t now) { return send_queue.get(now); } -int StaticPoolPacketManager::getOutboundCount() const { - return send_queue.count(); +int StaticPoolPacketManager::getOutboundCount(uint32_t now) const { + return send_queue.countBefore(now); } int StaticPoolPacketManager::getFreeCount() const { diff --git a/src/helpers/StaticPoolPacketManager.h b/src/helpers/StaticPoolPacketManager.h index 09c2fdec..bbf4b193 100644 --- a/src/helpers/StaticPoolPacketManager.h +++ b/src/helpers/StaticPoolPacketManager.h @@ -13,6 +13,7 @@ public: mesh::Packet* get(uint32_t now); void add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for); int count() const { return _num; } + int countBefore(uint32_t now) const; mesh::Packet* itemAt(int i) const { return _table[i]; } mesh::Packet* removeByIdx(int i); }; @@ -27,7 +28,7 @@ public: void free(mesh::Packet* packet) override; void queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) override; mesh::Packet* getNextOutbound(uint32_t now) override; - int getOutboundCount() const override; + int getOutboundCount(uint32_t now) const override; int getFreeCount() const override; mesh::Packet* getOutboundByIdx(int i) override; mesh::Packet* removeOutboundByIdx(int i) override; From c69657a13b7e0b1e993c44d2d812c9afb3e619b0 Mon Sep 17 00:00:00 2001 From: Normunds Gavars Date: Wed, 14 May 2025 13:27:57 +0300 Subject: [PATCH 085/154] 283 remove settingsManager and avoid the String class --- src/helpers/SensorManager.h | 2 +- src/helpers/sensors/SensorSettingsManager.h | 72 ----------------- variants/promicro/target.cpp | 86 +++++++++------------ variants/promicro/target.h | 17 ++-- 4 files changed, 47 insertions(+), 130 deletions(-) delete mode 100644 src/helpers/sensors/SensorSettingsManager.h diff --git a/src/helpers/SensorManager.h b/src/helpers/SensorManager.h index ae783c58..36181923 100644 --- a/src/helpers/SensorManager.h +++ b/src/helpers/SensorManager.h @@ -7,7 +7,7 @@ #define TELEM_CHANNEL_SELF 1 // LPP data channel for 'self' device -#define TELEM_INA3221_ADDRESS 0x40 // INA3221 3 channel current, voltage, power sensor I2C address +#define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current, voltage, power sensor I2C address #define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts #define TELEM_INA3221_SETTING_CH1 "INA3221 Channel 1" #define TELEM_INA3221_SETTING_CH2 "INA3221 Channel 2" diff --git a/src/helpers/sensors/SensorSettingsManager.h b/src/helpers/sensors/SensorSettingsManager.h deleted file mode 100644 index e3561332..00000000 --- a/src/helpers/sensors/SensorSettingsManager.h +++ /dev/null @@ -1,72 +0,0 @@ -#include -#include - -class SensorSettingsManager { - private: - std::vector> settings; - - public: - - int getSettingCount() const { - return static_cast(settings.size()); - }; - - bool addSetting(const std::string& name, bool defaultValue = false){ - for (const auto& setting : settings) { - if (setting.first == name) { - return false; - } - } - settings.emplace_back(name, defaultValue); - return true; - }; - - bool removeSetting(const std::string& name) { - for (auto it = settings.begin(); it != settings.end(); ++it) { - if (it->first == name) { - settings.erase(it); - return true; - } - } - return false; - }; - - const char* getSettingValue(const std::string& name) const{ - for (const auto& setting : settings) { - if (setting.first == name) { - return setting.second ? "true" : "false"; - } - } - return NULL; - }; - - const char* getSettingValue(int index) const { - if (index >= 0 && index < getSettingCount()) { - return settings[index].second ? "true" : "false"; - } - return NULL; - }; - - bool setSettingValue(const std::string& name, const std::string& value) { - for (auto& setting : settings) { - if (setting.first == name) { - // Convert value to boolean - if (value == "1" || value == "true") { - setting.second = true; - } else { - setting.second = false; - } - return true; - } - } - return false; - } - - const char* getSettingName(int index) const { - if (index >= 0 && index < getSettingCount()){ - return settings[index].first.c_str(); - } - return NULL; - }; - -}; \ No newline at end of file diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index a8a27b4e..835a9be0 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -75,34 +75,21 @@ mesh::LocalIdentity radio_new_identity() { return mesh::LocalIdentity(&rng); // create new random identity } -PromicroSensorManager::PromicroSensorManager(){ - INA_3221 = new INA3221(TELEM_INA3221_ADDRESS, &Wire); -} - -PromicroSensorManager::~PromicroSensorManager(){ - if (INA_3221) { - delete INA_3221; - INA_3221 = nullptr; - } -} +INA3221 INA_3221(TELEM_INA3221_ADDRESS, &Wire); bool PromicroSensorManager::begin() { - if (INA_3221->begin() ) { + if (INA_3221.begin() ) { Serial.print("Found INA3221 at address "); - Serial.print(INA_3221->getAddress()); + Serial.print(INA_3221.getAddress()); Serial.println(); - Serial.print(INA_3221->getDieID(), HEX); - Serial.print(INA_3221->getManufacturerID(), HEX); - Serial.print(INA_3221->getConfiguration(), HEX); + Serial.print(INA_3221.getDieID(), HEX); + Serial.print(INA_3221.getManufacturerID(), HEX); + Serial.print(INA_3221.getConfiguration(), HEX); Serial.println(); for(int i = 0; i < 3; i++) { - INA_3221->setShuntR(i, TELEM_INA3221_SHUNT_VALUE); + INA_3221.setShuntR(i, TELEM_INA3221_SHUNT_VALUE); } - // add INA3221 settings to settings manager - settingsManager.addSetting(TELEM_INA3221_SETTING_CH1, true); - settingsManager.addSetting(TELEM_INA3221_SETTING_CH2, true); - settingsManager.addSetting(TELEM_INA3221_SETTING_CH3, true); INA3221initialized = true; } else { @@ -120,22 +107,21 @@ bool PromicroSensorManager::querySensors(uint8_t requester_permissions, CayenneL if (INA3221initialized) { for(int i = 0; i < 3; i++) { // add only enabled INA3221 channels to telemetry - if (settingsManager.getSettingValue(INA3221_CHANNEL_NAMES[i]) == "true") { - + if (INA3221_CHANNEL_ENABLED[i]) { // TODO: remove when telemetry support gets properly added // Serial.print("CH"); // Serial.print(i); // Serial.print(" Voltage: "); - // Serial.print(INA_3221->getBusVoltage(i)); + // Serial.print(INA_3221.getBusVoltage(i)); // Serial.print("V Current: "); - // Serial.print(INA_3221->getCurrent(i)); + // Serial.print(INA_3221.getCurrent(i)); // Serial.print("A Power: "); - // Serial.print(INA_3221->getPower(i)); + // Serial.print(INA_3221.getPower(i)); // Serial.println(); - telemetry.addVoltage(INA3221_CHANNELS[i], INA_3221->getBusVoltage(i)); - telemetry.addCurrent(INA3221_CHANNELS[i], INA_3221->getCurrent(i)); - telemetry.addPower(INA3221_CHANNELS[i], INA_3221->getPower(i)); + telemetry.addVoltage(INA3221_CHANNELS[i], INA_3221.getBusVoltage(i)); + telemetry.addCurrent(INA3221_CHANNELS[i], INA_3221.getCurrent(i)); + telemetry.addPower(INA3221_CHANNELS[i], INA_3221.getPower(i)); } } } @@ -145,36 +131,40 @@ bool PromicroSensorManager::querySensors(uint8_t requester_permissions, CayenneL } int PromicroSensorManager::getNumSettings() const { - return settingsManager.getSettingCount(); + return NUM_SENSOR_SETTINGS; } const char* PromicroSensorManager::getSettingName(int i) const { - return settingsManager.getSettingName(i); + if (i >= 0 && i < NUM_SENSOR_SETTINGS) { + return INA3221_CHANNEL_NAMES[i]; + } + return NULL; } const char* PromicroSensorManager::getSettingValue(int i) const { - return settingsManager.getSettingValue(i); + if (i >= 0 && i < NUM_SENSOR_SETTINGS) { + return INA3221_CHANNEL_ENABLED[i] ? "1" : "0"; + } + return NULL; } bool PromicroSensorManager::setSettingValue(const char* name, const char* value) { - if (settingsManager.setSettingValue(name, value)) { - onSettingsChanged(); - return true; - } - return false; -} - -void PromicroSensorManager::onSettingsChanged() { - if (INA3221initialized) { - for(int i = 0; i < 3; i++) { - int channelEnabled = INA_3221->getEnableChannel(i); - bool settingEnabled = settingsManager.getSettingValue(INA3221_CHANNEL_NAMES[i]) == "true"; - if (!settingEnabled && channelEnabled) { - INA_3221->disableChannel(i); - } - if (settingEnabled && !channelEnabled) { - INA_3221->enableChannel(i); + for (int i = 0; i < NUM_SENSOR_SETTINGS; i++) { + if (strcmp(name, INA3221_CHANNEL_NAMES[i]) == 0) { + int channelEnabled = INA_3221.getEnableChannel(i); + if (strcmp(value, "1") == 0) { + INA3221_CHANNEL_ENABLED[i] = true; + if (!channelEnabled) { + INA_3221.enableChannel(i); + } + } else { + INA3221_CHANNEL_ENABLED[i] = false; + if (channelEnabled) { + INA_3221.disableChannel(i); + } } + return true; } } + return false; } \ No newline at end of file diff --git a/variants/promicro/target.h b/variants/promicro/target.h index 4cfe4b75..225afd4d 100644 --- a/variants/promicro/target.h +++ b/variants/promicro/target.h @@ -8,9 +8,10 @@ #include #include #include -#include #include +#define NUM_SENSOR_SETTINGS 3 + extern PromicroBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; @@ -22,19 +23,17 @@ 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(); + class PromicroSensorManager: public SensorManager { - INA3221 * INA_3221; bool INA3221initialized = false; - SensorSettingsManager settingsManager; // INA3221 channels in telemetry - int INA3221_CHANNELS[3] = {TELEM_CHANNEL_SELF + 1, TELEM_CHANNEL_SELF + 2, TELEM_CHANNEL_SELF+ 3}; - char * INA3221_CHANNEL_NAMES[3] = { TELEM_INA3221_SETTING_CH1, TELEM_INA3221_SETTING_CH2, TELEM_INA3221_SETTING_CH3}; - void onSettingsChanged(); - + int INA3221_CHANNELS[NUM_SENSOR_SETTINGS] = {TELEM_CHANNEL_SELF + 1, TELEM_CHANNEL_SELF + 2, TELEM_CHANNEL_SELF+ 3}; + char * INA3221_CHANNEL_NAMES[NUM_SENSOR_SETTINGS] = { TELEM_INA3221_SETTING_CH1, TELEM_INA3221_SETTING_CH2, TELEM_INA3221_SETTING_CH3}; + bool INA3221_CHANNEL_ENABLED[NUM_SENSOR_SETTINGS] = {true, true, true}; + public: - PromicroSensorManager(); - ~PromicroSensorManager(); + PromicroSensorManager(){}; bool begin() override; bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; int getNumSettings() const override; From 8b3d60abe763d4549906393e8feac1295890fb94 Mon Sep 17 00:00:00 2001 From: Normunds Gavars Date: Wed, 14 May 2025 13:55:45 +0300 Subject: [PATCH 086/154] 283 add new permision for access to environment sensors --- src/helpers/SensorManager.h | 5 +++-- variants/promicro/target.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/helpers/SensorManager.h b/src/helpers/SensorManager.h index 36181923..8d3d4aef 100644 --- a/src/helpers/SensorManager.h +++ b/src/helpers/SensorManager.h @@ -2,8 +2,9 @@ #include -#define TELEM_PERM_BASE 0x01 // 'base' permission includes battery -#define TELEM_PERM_LOCATION 0x02 +#define TELEM_PERM_BASE 0x01 // 'base' permission includes battery +#define TELEM_PERM_LOCATION 0x02 +#define TELEM_PERM_ENVIRONMENT 0x04 // permission to access environment sensors #define TELEM_CHANNEL_SELF 1 // LPP data channel for 'self' device diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index 835a9be0..49566342 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -103,7 +103,7 @@ bool PromicroSensorManager::begin() { bool PromicroSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { // TODO: what is the correct permission here? - if (requester_permissions && TELEM_PERM_BASE) { + if (requester_permissions && TELEM_PERM_ENVIRONMENT) { if (INA3221initialized) { for(int i = 0; i < 3; i++) { // add only enabled INA3221 channels to telemetry From 74c1ff3d6dae7f22fc9f12325cc48c2342a61452 Mon Sep 17 00:00:00 2001 From: Normunds Gavars Date: Wed, 14 May 2025 13:58:52 +0300 Subject: [PATCH 087/154] 283 minor cleanup --- variants/promicro/target.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index 49566342..304bb54a 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -102,23 +102,11 @@ bool PromicroSensorManager::begin() { } bool PromicroSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { - // TODO: what is the correct permission here? if (requester_permissions && TELEM_PERM_ENVIRONMENT) { if (INA3221initialized) { for(int i = 0; i < 3; i++) { // add only enabled INA3221 channels to telemetry if (INA3221_CHANNEL_ENABLED[i]) { - // TODO: remove when telemetry support gets properly added - // Serial.print("CH"); - // Serial.print(i); - // Serial.print(" Voltage: "); - // Serial.print(INA_3221.getBusVoltage(i)); - // Serial.print("V Current: "); - // Serial.print(INA_3221.getCurrent(i)); - // Serial.print("A Power: "); - // Serial.print(INA_3221.getPower(i)); - // Serial.println(); - telemetry.addVoltage(INA3221_CHANNELS[i], INA_3221.getBusVoltage(i)); telemetry.addCurrent(INA3221_CHANNELS[i], INA_3221.getCurrent(i)); telemetry.addPower(INA3221_CHANNELS[i], INA_3221.getPower(i)); From 6c0d94aa2d4ca5361cc762491cc54cac8ee6d3b7 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Wed, 14 May 2025 23:02:49 +1200 Subject: [PATCH 088/154] increase offline queue size from 16 to 256 for all companion ble firmwares --- variants/heltec_tracker/platformio.ini | 1 + variants/heltec_v2/platformio.ini | 1 + variants/heltec_v3/platformio.ini | 2 ++ variants/lilygo_t3s3/platformio.ini | 1 + variants/lilygo_tbeam/platformio.ini | 1 + variants/lilygo_tbeam_SX1262/platformio.ini | 1 + variants/lilygo_tbeam_supreme_SX1262/platformio.ini | 1 + variants/lilygo_tlora_v2_1/platformio.ini | 1 + variants/promicro/platformio.ini | 2 ++ variants/rak4631/platformio.ini | 1 + variants/t1000-e/platformio.ini | 1 + variants/t114/platformio.ini | 1 + variants/techo/platformio.ini | 1 + variants/thinknode_m1/platformio.ini | 1 + variants/xiao_nrf52/platformio.ini | 1 + variants/xiao_s3_wio/platformio.ini | 1 + 16 files changed, 18 insertions(+) diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 27909036..e0ad8b0f 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -44,6 +44,7 @@ build_flags = -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 ; HWT will use display for pin + -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index 86d5312c..495f20f8 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/heltec_v2/platformio.ini @@ -98,6 +98,7 @@ build_flags = -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=0 -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index b8d8eb10..0814ad4d 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -102,6 +102,7 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display -D BLE_PIN_CODE=0 ; dynamic, random PIN -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 ; -D MESH_PACKET_LOGGING=1 @@ -178,6 +179,7 @@ build_flags = -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/lilygo_t3s3/platformio.ini b/variants/lilygo_t3s3/platformio.ini index 724a79b8..94ec87af 100644 --- a/variants/lilygo_t3s3/platformio.ini +++ b/variants/lilygo_t3s3/platformio.ini @@ -112,6 +112,7 @@ build_flags = -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/lilygo_tbeam/platformio.ini b/variants/lilygo_tbeam/platformio.ini index 15c9919d..14ce144e 100644 --- a/variants/lilygo_tbeam/platformio.ini +++ b/variants/lilygo_tbeam/platformio.ini @@ -30,6 +30,7 @@ build_flags = -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 ; -D RADIOLIB_DEBUG_BASIC=1 diff --git a/variants/lilygo_tbeam_SX1262/platformio.ini b/variants/lilygo_tbeam_SX1262/platformio.ini index c16548e9..517fc2e0 100644 --- a/variants/lilygo_tbeam_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_SX1262/platformio.ini @@ -33,6 +33,7 @@ build_flags = -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index c53c51ac..b9118e3f 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -63,6 +63,7 @@ build_flags = -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 ; -D MESH_PACKET_LOGGING=8 diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index 0e4d96eb..5591a400 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -90,6 +90,7 @@ build_flags = -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 5599cdd8..20112b0c 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -89,6 +89,7 @@ build_flags = ${Faketec.build_flags} -D BLE_DEBUG_LOGGING=1 -D ENABLE_PRIVATE_KEY_EXPORT=1 -D ENABLE_PRIVATE_KEY_IMPORT=1 + -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Faketec.build_src_filter} @@ -176,6 +177,7 @@ build_flags = ${ProMicroLLCC68.build_flags} -D BLE_DEBUG_LOGGING=1 -D ENABLE_PRIVATE_KEY_EXPORT=1 -D ENABLE_PRIVATE_KEY_IMPORT=1 + -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${ProMicroLLCC68.build_src_filter} diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index f3c3d70c..d7584456 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -81,6 +81,7 @@ build_flags = -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index fd70503b..9f1a3b06 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -43,6 +43,7 @@ build_flags = ${t1000-e.build_flags} ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 + -D OFFLINE_QUEUE_SIZE=256 -D RX_BOOSTED_GAIN=true -D RF_SWITCH_TABLE -D HAS_UI diff --git a/variants/t114/platformio.ini b/variants/t114/platformio.ini index 43c6e167..9e72bbd6 100644 --- a/variants/t114/platformio.ini +++ b/variants/t114/platformio.ini @@ -71,6 +71,7 @@ build_flags = -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/techo/platformio.ini b/variants/techo/platformio.ini index bed06c50..d9346948 100644 --- a/variants/techo/platformio.ini +++ b/variants/techo/platformio.ini @@ -63,6 +63,7 @@ build_flags = -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D DISPLAY_CLASS=GxEPDDisplay + -D OFFLINE_QUEUE_SIZE=256 -D HAS_GxEPD ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 diff --git a/variants/thinknode_m1/platformio.ini b/variants/thinknode_m1/platformio.ini index 6e570df4..eec255d0 100644 --- a/variants/thinknode_m1/platformio.ini +++ b/variants/thinknode_m1/platformio.ini @@ -71,6 +71,7 @@ build_flags = -D DISPLAY_ROTATION=4 -D DISPLAY_CLASS=GxEPDDisplay -D HAS_GxEPD + -D OFFLINE_QUEUE_SIZE=256 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index ffc01806..52c11653 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -49,6 +49,7 @@ build_flags = -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=254 ; -D BLE_DEBUG_LOGGING=1 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index af3e9e40..841a50c3 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -81,6 +81,7 @@ build_flags = -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D DISPLAY_CLASS=SSD1306Display + -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 From d2377c91ab8332c6b9bf16d5b6f8a6fece24a45b Mon Sep 17 00:00:00 2001 From: liamcottle Date: Wed, 14 May 2025 23:10:27 +1200 Subject: [PATCH 089/154] fix offline queue size for xiao nrf52 --- variants/xiao_nrf52/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index 52c11653..5083b1a3 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -49,7 +49,7 @@ build_flags = -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 - -D OFFLINE_QUEUE_SIZE=254 + -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D MESH_PACKET_LOGGING=1 From 8007aad7a3aa1e75bd99001bc6af158f39dcf98b Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 14 May 2025 21:22:26 +1000 Subject: [PATCH 090/154] * Promicro: some refactors, minor fixes for INA3221 sensors --- src/helpers/SensorManager.h | 6 ------ variants/promicro/target.cpp | 20 ++++++-------------- variants/promicro/target.h | 7 ++++++- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/helpers/SensorManager.h b/src/helpers/SensorManager.h index 8d3d4aef..839e2736 100644 --- a/src/helpers/SensorManager.h +++ b/src/helpers/SensorManager.h @@ -8,12 +8,6 @@ #define TELEM_CHANNEL_SELF 1 // LPP data channel for 'self' device -#define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current, voltage, power sensor I2C address -#define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts -#define TELEM_INA3221_SETTING_CH1 "INA3221 Channel 1" -#define TELEM_INA3221_SETTING_CH2 "INA3221 Channel 2" -#define TELEM_INA3221_SETTING_CH3 "INA3221 Channel 3" - class SensorManager { public: double node_lat, node_lon; // modify these, if you want to affect Advert location diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index 304bb54a..1a42f120 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -75,34 +75,26 @@ mesh::LocalIdentity radio_new_identity() { return mesh::LocalIdentity(&rng); // create new random identity } -INA3221 INA_3221(TELEM_INA3221_ADDRESS, &Wire); +static INA3221 INA_3221(TELEM_INA3221_ADDRESS, &Wire); bool PromicroSensorManager::begin() { if (INA_3221.begin() ) { - Serial.print("Found INA3221 at address "); - Serial.print(INA_3221.getAddress()); - Serial.println(); - Serial.print(INA_3221.getDieID(), HEX); - Serial.print(INA_3221.getManufacturerID(), HEX); - Serial.print(INA_3221.getConfiguration(), HEX); - Serial.println(); + MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", INA_3221.getAddress()); + MESH_DEBUG_PRINTLN("%04X %04X %04X", INA_3221.getDieID(), INA_3221.getManufacturerID(), INA_3221.getConfiguration()); for(int i = 0; i < 3; i++) { INA_3221.setShuntR(i, TELEM_INA3221_SHUNT_VALUE); } INA3221initialized = true; - } - else { + } else { INA3221initialized = false; - Serial.print("INA3221 was not found at I2C address "); - Serial.print(TELEM_INA3221_ADDRESS, HEX); - Serial.println(); + MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS); } return true; } bool PromicroSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { - if (requester_permissions && TELEM_PERM_ENVIRONMENT) { + if (requester_permissions & TELEM_PERM_ENVIRONMENT) { if (INA3221initialized) { for(int i = 0; i < 3; i++) { // add only enabled INA3221 channels to telemetry diff --git a/variants/promicro/target.h b/variants/promicro/target.h index 225afd4d..92fbd07e 100644 --- a/variants/promicro/target.h +++ b/variants/promicro/target.h @@ -23,13 +23,18 @@ 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(); +#define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current, voltage, power sensor I2C address +#define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts +#define TELEM_INA3221_SETTING_CH1 "INA3221-1" +#define TELEM_INA3221_SETTING_CH2 "INA3221-2" +#define TELEM_INA3221_SETTING_CH3 "INA3221-3" class PromicroSensorManager: public SensorManager { bool INA3221initialized = false; // INA3221 channels in telemetry int INA3221_CHANNELS[NUM_SENSOR_SETTINGS] = {TELEM_CHANNEL_SELF + 1, TELEM_CHANNEL_SELF + 2, TELEM_CHANNEL_SELF+ 3}; - char * INA3221_CHANNEL_NAMES[NUM_SENSOR_SETTINGS] = { TELEM_INA3221_SETTING_CH1, TELEM_INA3221_SETTING_CH2, TELEM_INA3221_SETTING_CH3}; + const char * INA3221_CHANNEL_NAMES[NUM_SENSOR_SETTINGS] = { TELEM_INA3221_SETTING_CH1, TELEM_INA3221_SETTING_CH2, TELEM_INA3221_SETTING_CH3}; bool INA3221_CHANNEL_ENABLED[NUM_SENSOR_SETTINGS] = {true, true, true}; public: From 9f5d7a28ceb12333efd00586091ec4c448a94c13 Mon Sep 17 00:00:00 2001 From: Normunds Gavars Date: Wed, 14 May 2025 18:19:53 +0300 Subject: [PATCH 091/154] 283 Promicro: add INA3221 library dependency to all build targets --- variants/promicro/platformio.ini | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index e823e57f..6d106973 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -53,6 +53,7 @@ build_flags = ${Faketec.build_flags} ; -D MESH_DEBUG=1 lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 + robtillaart/INA3221 @ ^0.4.1 [env:Faketec_terminal_chat] extends = Faketec @@ -66,6 +67,7 @@ build_src_filter = ${Faketec.build_src_filter} lib_deps = ${Faketec.lib_deps} densaugeo/base64 @ ~1.4.0 adafruit/RTClib @ ^2.1.3 + robtillaart/INA3221 @ ^0.4.1 [env:Faketec_companion_radio_usb] extends = Faketec @@ -80,6 +82,7 @@ build_src_filter = ${Faketec.build_src_filter} lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 + robtillaart/INA3221 @ ^0.4.1 [env:Faketec_companion_radio_ble] extends = Faketec @@ -100,6 +103,7 @@ build_src_filter = ${Faketec.build_src_filter} lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 + robtillaart/INA3221 @ ^0.4.1 [ProMicroLLCC68] extends = nrf52840_base @@ -129,6 +133,7 @@ build_flags = ${ProMicroLLCC68.build_flags} ; -D MESH_DEBUG=1 lib_deps = ${ProMicroLLCC68.lib_deps} adafruit/RTClib @ ^2.1.3 + robtillaart/INA3221 @ ^0.4.1 [env:ProMicroLLCC68_room_server] extends = ProMicroLLCC68 @@ -142,6 +147,7 @@ build_flags = ${ProMicroLLCC68.build_flags} ; -D MESH_DEBUG=1 lib_deps = ${ProMicroLLCC68.lib_deps} adafruit/RTClib @ ^2.1.3 + robtillaart/INA3221 @ ^0.4.1 [env:ProMicroLLCC68_terminal_chat] extends = ProMicroLLCC68 @@ -155,6 +161,7 @@ build_src_filter = ${ProMicroLLCC68.build_src_filter} lib_deps = ${ProMicroLLCC68.lib_deps} densaugeo/base64 @ ~1.4.0 adafruit/RTClib @ ^2.1.3 + robtillaart/INA3221 @ ^0.4.1 [env:ProMicroLLCC68_companion_radio_usb] extends = ProMicroLLCC68 @@ -168,6 +175,7 @@ build_src_filter = ${ProMicroLLCC68.build_src_filter} lib_deps = ${ProMicroLLCC68.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 + robtillaart/INA3221 @ ^0.4.1 [env:ProMicroLLCC68_companion_radio_ble] extends = ProMicroLLCC68 @@ -187,3 +195,4 @@ build_src_filter = ${ProMicroLLCC68.build_src_filter} lib_deps = ${ProMicroLLCC68.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 + robtillaart/INA3221 @ ^0.4.1 From faf043327d40e508a6f35fc1100bdb764dde5d66 Mon Sep 17 00:00:00 2001 From: Adam Mealings Date: Wed, 14 May 2025 21:46:39 +0100 Subject: [PATCH 092/154] RAK4631 analogue user button on input 31 --- examples/companion_radio/UITask.cpp | 68 +++++++++++++++++------------ src/helpers/nrf52/RAK4631Board.cpp | 3 ++ variants/rak4631/platformio.ini | 1 + 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index e0c2ec51..6eae88c9 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -209,40 +209,50 @@ void UITask::userLedHandler() { } void UITask::buttonHandler() { -#ifdef PIN_USER_BTN - static int prev_btn_state = !USER_BTN_PRESSED; - static unsigned long btn_state_change_time = 0; - static unsigned long next_read = 0; - int cur_time = millis(); - if (cur_time >= next_read) { - int btn_state = digitalRead(PIN_USER_BTN); - if (btn_state != prev_btn_state) { - if (btn_state == USER_BTN_PRESSED) { // pressed? - if (_display != NULL) { - if (_display->isOn()) { - clearMsgPreview(); - } else { - _display->turnOn(); - _need_refresh = true; + #ifdef PIN_USER_BTN || PIN_USER_BTN_ANA + static int prev_btn_state = !USER_BTN_PRESSED; + static int prev_btn_state_ana = !USER_BTN_PRESSED; + static unsigned long btn_state_change_time = 0; + static unsigned long next_read = 0; + int cur_time = millis(); + if (cur_time >= next_read) { + int btn_state = 0; + int btn_state_ana = 0; + #ifdef PIN_USER_BTN + btn_state = digitalRead(PIN_USER_BTN); + #endif + #ifdef PIN_USER_BTN_ANA + btn_state_ana = (analogRead(PIN_USER_BTN_ANA) < 20); // analogRead returns a value hopefully below 20 when button is pressed. + #endif + //Serial.println(analogRead(PIN_USER_BTN_ANA)); + if (btn_state != prev_btn_state || btn_state_ana != prev_btn_state_ana) { // check for either digital or analogue button change of state + if (btn_state == USER_BTN_PRESSED || btn_state_ana == USER_BTN_PRESSED) { // pressed? + if (_display != NULL) { + if (_display->isOn()) { + clearMsgPreview(); + } else { + _display->turnOn(); + _need_refresh = true; + } + _auto_off = cur_time + AUTO_OFF_MILLIS; // extend auto-off timer + } + } else { // unpressed ? check pressed time ... + if ((cur_time - btn_state_change_time) > 5000) { + #ifdef PIN_STATUS_LED + digitalWrite(PIN_STATUS_LED, LOW); + delay(10); + #endif + _board->powerOff(); } - _auto_off = cur_time + AUTO_OFF_MILLIS; // extend auto-off timer - } - } else { // unpressed ? check pressed time ... - if ((cur_time - btn_state_change_time) > 5000) { - #ifdef PIN_STATUS_LED - digitalWrite(PIN_STATUS_LED, LOW); - delay(10); - #endif - _board->powerOff(); } + btn_state_change_time = millis(); + prev_btn_state = btn_state; + prev_btn_state_ana = btn_state_ana; } - btn_state_change_time = millis(); - prev_btn_state = btn_state; + next_read = millis() + 100; // 10 reads per second } - next_read = millis() + 100; // 10 reads per second + #endif } -#endif -} void UITask::loop() { buttonHandler(); diff --git a/src/helpers/nrf52/RAK4631Board.cpp b/src/helpers/nrf52/RAK4631Board.cpp index 8b368734..6deed5b4 100644 --- a/src/helpers/nrf52/RAK4631Board.cpp +++ b/src/helpers/nrf52/RAK4631Board.cpp @@ -25,6 +25,9 @@ void RAK4631Board::begin() { #ifdef PIN_USER_BTN pinMode(PIN_USER_BTN, INPUT_PULLUP); #endif +#ifdef PIN_USER_BTN_ANA + pinMode(PIN_USER_BTN_ANA, INPUT_PULLUP); +#endif #if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL); diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 85f59bfa..af1eb3d4 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -7,6 +7,7 @@ build_flags = ${nrf52840_base.build_flags} -I variants/rak4631 -D RAK_4631 -D PIN_USER_BTN=9 + -D PIN_USER_BTN_ANA=31 -D PIN_BOARD_SCL=14 -D PIN_BOARD_SDA=13 -D PIN_OLED_RESET=-1 From f1df9f7c3b5ad3d118e5001bc4bafab72a408be9 Mon Sep 17 00:00:00 2001 From: adam2872 <53094349+adam2872@users.noreply.github.com> Date: Wed, 14 May 2025 22:04:28 +0100 Subject: [PATCH 093/154] Revert "RAK4631 analogue user button on input 31" --- examples/companion_radio/UITask.cpp | 68 ++++++++++++----------------- src/helpers/nrf52/RAK4631Board.cpp | 3 -- variants/rak4631/platformio.ini | 1 - 3 files changed, 29 insertions(+), 43 deletions(-) diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index 6eae88c9..e0c2ec51 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -209,50 +209,40 @@ void UITask::userLedHandler() { } void UITask::buttonHandler() { - #ifdef PIN_USER_BTN || PIN_USER_BTN_ANA - static int prev_btn_state = !USER_BTN_PRESSED; - static int prev_btn_state_ana = !USER_BTN_PRESSED; - static unsigned long btn_state_change_time = 0; - static unsigned long next_read = 0; - int cur_time = millis(); - if (cur_time >= next_read) { - int btn_state = 0; - int btn_state_ana = 0; - #ifdef PIN_USER_BTN - btn_state = digitalRead(PIN_USER_BTN); - #endif - #ifdef PIN_USER_BTN_ANA - btn_state_ana = (analogRead(PIN_USER_BTN_ANA) < 20); // analogRead returns a value hopefully below 20 when button is pressed. - #endif - //Serial.println(analogRead(PIN_USER_BTN_ANA)); - if (btn_state != prev_btn_state || btn_state_ana != prev_btn_state_ana) { // check for either digital or analogue button change of state - if (btn_state == USER_BTN_PRESSED || btn_state_ana == USER_BTN_PRESSED) { // pressed? - if (_display != NULL) { - if (_display->isOn()) { - clearMsgPreview(); - } else { - _display->turnOn(); - _need_refresh = true; - } - _auto_off = cur_time + AUTO_OFF_MILLIS; // extend auto-off timer - } - } else { // unpressed ? check pressed time ... - if ((cur_time - btn_state_change_time) > 5000) { - #ifdef PIN_STATUS_LED - digitalWrite(PIN_STATUS_LED, LOW); - delay(10); - #endif - _board->powerOff(); +#ifdef PIN_USER_BTN + static int prev_btn_state = !USER_BTN_PRESSED; + static unsigned long btn_state_change_time = 0; + static unsigned long next_read = 0; + int cur_time = millis(); + if (cur_time >= next_read) { + int btn_state = digitalRead(PIN_USER_BTN); + if (btn_state != prev_btn_state) { + if (btn_state == USER_BTN_PRESSED) { // pressed? + if (_display != NULL) { + if (_display->isOn()) { + clearMsgPreview(); + } else { + _display->turnOn(); + _need_refresh = true; } + _auto_off = cur_time + AUTO_OFF_MILLIS; // extend auto-off timer + } + } else { // unpressed ? check pressed time ... + if ((cur_time - btn_state_change_time) > 5000) { + #ifdef PIN_STATUS_LED + digitalWrite(PIN_STATUS_LED, LOW); + delay(10); + #endif + _board->powerOff(); } - btn_state_change_time = millis(); - prev_btn_state = btn_state; - prev_btn_state_ana = btn_state_ana; } - next_read = millis() + 100; // 10 reads per second + btn_state_change_time = millis(); + prev_btn_state = btn_state; } - #endif + next_read = millis() + 100; // 10 reads per second } +#endif +} void UITask::loop() { buttonHandler(); diff --git a/src/helpers/nrf52/RAK4631Board.cpp b/src/helpers/nrf52/RAK4631Board.cpp index 6deed5b4..8b368734 100644 --- a/src/helpers/nrf52/RAK4631Board.cpp +++ b/src/helpers/nrf52/RAK4631Board.cpp @@ -25,9 +25,6 @@ void RAK4631Board::begin() { #ifdef PIN_USER_BTN pinMode(PIN_USER_BTN, INPUT_PULLUP); #endif -#ifdef PIN_USER_BTN_ANA - pinMode(PIN_USER_BTN_ANA, INPUT_PULLUP); -#endif #if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL); diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index af1eb3d4..85f59bfa 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -7,7 +7,6 @@ build_flags = ${nrf52840_base.build_flags} -I variants/rak4631 -D RAK_4631 -D PIN_USER_BTN=9 - -D PIN_USER_BTN_ANA=31 -D PIN_BOARD_SCL=14 -D PIN_BOARD_SDA=13 -D PIN_OLED_RESET=-1 From 22ee164ff6e122273c40d02f80bfb1296b26cb7f Mon Sep 17 00:00:00 2001 From: Adam Mealings Date: Wed, 14 May 2025 22:17:54 +0100 Subject: [PATCH 094/154] Make the battery fill based on the percentage slightly smaller to give it a more modern look --- examples/companion_radio/UITask.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index e0c2ec51..79224de6 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -107,8 +107,8 @@ void renderBatteryIndicator(DisplayDriver* _display, uint16_t batteryMilliVolts) _display->fillRect(iconX + iconWidth, iconY + (iconHeight / 4), 3, iconHeight / 2); // fill the battery based on the percentage - int fillWidth = (batteryPercentage * (iconWidth - 2)) / 100; - _display->fillRect(iconX + 1, iconY + 1, fillWidth, iconHeight - 2); + int fillWidth = (batteryPercentage * (iconWidth - 4)) / 100; + _display->fillRect(iconX + 2, iconY + 2, fillWidth, iconHeight - 4); } void UITask::renderCurrScreen() { From 1de46eae4c6f7642551acf05ae120b832a24264e Mon Sep 17 00:00:00 2001 From: Normunds Gavars Date: Thu, 15 May 2025 00:21:51 +0300 Subject: [PATCH 095/154] Promicro: add support for INA219 current sensor --- variants/promicro/platformio.ini | 16 +++++++++++++--- variants/promicro/target.cpp | 17 ++++++++++++++++- variants/promicro/target.h | 10 +++++++++- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 6d106973..376e6c74 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -37,7 +37,8 @@ build_flags = lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 robtillaart/INA3221 @ ^0.4.1 - + robtillaart/INA219 @ ^0.4.1 + [env:Faketec_room_server] extends = Faketec build_src_filter = ${Faketec.build_src_filter} @@ -53,7 +54,8 @@ build_flags = ${Faketec.build_flags} ; -D MESH_DEBUG=1 lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 - robtillaart/INA3221 @ ^0.4.1 + robtillaart/INA3221 @ ^0.4.1 + robtillaart/INA219 @ ^0.4.1 [env:Faketec_terminal_chat] extends = Faketec @@ -68,6 +70,7 @@ lib_deps = ${Faketec.lib_deps} densaugeo/base64 @ ~1.4.0 adafruit/RTClib @ ^2.1.3 robtillaart/INA3221 @ ^0.4.1 + robtillaart/INA219 @ ^0.4.1 [env:Faketec_companion_radio_usb] extends = Faketec @@ -82,7 +85,8 @@ build_src_filter = ${Faketec.build_src_filter} lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 - robtillaart/INA3221 @ ^0.4.1 + robtillaart/INA3221 @ ^0.4.1 + robtillaart/INA219 @ ^0.4.1 [env:Faketec_companion_radio_ble] extends = Faketec @@ -104,6 +108,7 @@ lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 robtillaart/INA3221 @ ^0.4.1 + robtillaart/INA219 @ ^0.4.1 [ProMicroLLCC68] extends = nrf52840_base @@ -134,6 +139,7 @@ build_flags = ${ProMicroLLCC68.build_flags} lib_deps = ${ProMicroLLCC68.lib_deps} adafruit/RTClib @ ^2.1.3 robtillaart/INA3221 @ ^0.4.1 + robtillaart/INA219 @ ^0.4.1 [env:ProMicroLLCC68_room_server] extends = ProMicroLLCC68 @@ -148,6 +154,7 @@ build_flags = ${ProMicroLLCC68.build_flags} lib_deps = ${ProMicroLLCC68.lib_deps} adafruit/RTClib @ ^2.1.3 robtillaart/INA3221 @ ^0.4.1 + robtillaart/INA219 @ ^0.4.1 [env:ProMicroLLCC68_terminal_chat] extends = ProMicroLLCC68 @@ -162,6 +169,7 @@ lib_deps = ${ProMicroLLCC68.lib_deps} densaugeo/base64 @ ~1.4.0 adafruit/RTClib @ ^2.1.3 robtillaart/INA3221 @ ^0.4.1 + robtillaart/INA219 @ ^0.4.1 [env:ProMicroLLCC68_companion_radio_usb] extends = ProMicroLLCC68 @@ -176,6 +184,7 @@ lib_deps = ${ProMicroLLCC68.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 robtillaart/INA3221 @ ^0.4.1 + robtillaart/INA219 @ ^0.4.1 [env:ProMicroLLCC68_companion_radio_ble] extends = ProMicroLLCC68 @@ -196,3 +205,4 @@ lib_deps = ${ProMicroLLCC68.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 robtillaart/INA3221 @ ^0.4.1 + robtillaart/INA219 @ ^0.4.1 \ No newline at end of file diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index 1a42f120..072395ff 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -76,9 +76,10 @@ mesh::LocalIdentity radio_new_identity() { } static INA3221 INA_3221(TELEM_INA3221_ADDRESS, &Wire); +static INA219 INA_219(TELEM_INA219_ADDRESS, &Wire); bool PromicroSensorManager::begin() { - if (INA_3221.begin() ) { + if (INA_3221.begin()) { MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", INA_3221.getAddress()); MESH_DEBUG_PRINTLN("%04X %04X %04X", INA_3221.getDieID(), INA_3221.getManufacturerID(), INA_3221.getConfiguration()); @@ -90,6 +91,15 @@ bool PromicroSensorManager::begin() { INA3221initialized = false; MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS); } + if (INA_219.begin()) { + MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", INA_219.getAddress()); + INA219_CHANNEL = INA3221initialized ? TELEM_CHANNEL_SELF + 4 : TELEM_CHANNEL_SELF + 1; + INA_219.setMaxCurrentShunt(TELEM_INA219_MAX_CURRENT, TELEM_INA219_SHUNT_VALUE); + INA219initialized = true; + } else { + INA219initialized = false; + MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS); + } return true; } @@ -105,6 +115,11 @@ bool PromicroSensorManager::querySensors(uint8_t requester_permissions, CayenneL } } } + if(INA219initialized) { + telemetry.addVoltage(INA219_CHANNEL, INA_219.getBusVoltage()); + telemetry.addCurrent(INA219_CHANNEL, INA_219.getCurrent()); + telemetry.addPower(INA219_CHANNEL, INA_219.getPower()); + } } return true; diff --git a/variants/promicro/target.h b/variants/promicro/target.h index 92fbd07e..aae02dd1 100644 --- a/variants/promicro/target.h +++ b/variants/promicro/target.h @@ -9,6 +9,7 @@ #include #include #include +#include #define NUM_SENSOR_SETTINGS 3 @@ -23,20 +24,27 @@ 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(); -#define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current, voltage, power sensor I2C address +#define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current sensor I2C address +#define TELEM_INA219_ADDRESS 0x40 // INA219 single channel current sensor I2C address + #define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts #define TELEM_INA3221_SETTING_CH1 "INA3221-1" #define TELEM_INA3221_SETTING_CH2 "INA3221-2" #define TELEM_INA3221_SETTING_CH3 "INA3221-3" +#define TELEM_INA219_SHUNT_VALUE 0.100 // shunt value in ohms (may differ between manufacturers) +#define TELEM_INA219_MAX_CURRENT 5 + class PromicroSensorManager: public SensorManager { bool INA3221initialized = false; + bool INA219initialized = false; // INA3221 channels in telemetry int INA3221_CHANNELS[NUM_SENSOR_SETTINGS] = {TELEM_CHANNEL_SELF + 1, TELEM_CHANNEL_SELF + 2, TELEM_CHANNEL_SELF+ 3}; const char * INA3221_CHANNEL_NAMES[NUM_SENSOR_SETTINGS] = { TELEM_INA3221_SETTING_CH1, TELEM_INA3221_SETTING_CH2, TELEM_INA3221_SETTING_CH3}; bool INA3221_CHANNEL_ENABLED[NUM_SENSOR_SETTINGS] = {true, true, true}; + int INA219_CHANNEL; public: PromicroSensorManager(){}; bool begin() override; From 7576d45a8ddf9ac99c03fbafbd33b43d54a41ee5 Mon Sep 17 00:00:00 2001 From: cod3doomy Date: Wed, 14 May 2025 20:27:59 -0700 Subject: [PATCH 096/154] t-beam supreme: enabled lora tx led enabled lora tx led and verified it flashes with message transmit --- src/helpers/TBeamS3SupremeBoard.h | 6 ++++-- variants/lilygo_tbeam_supreme_SX1262/platformio.ini | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/helpers/TBeamS3SupremeBoard.h b/src/helpers/TBeamS3SupremeBoard.h index e30c02ed..5a8070b9 100644 --- a/src/helpers/TBeamS3SupremeBoard.h +++ b/src/helpers/TBeamS3SupremeBoard.h @@ -36,7 +36,7 @@ #define P_GPS_RX 9 //GPS RX pin #define P_GPS_TX 8 //GPS TX pin #define P_GPS_WAKE 7 //GPS Wakeup pin -#define P_GPS_1PPS 6 //GPS 1PPS pin +//#define P_GPS_1PPS 6 //GPS 1PPS pin - repurposed for lora tx led #define GPS_BAUD_RATE 9600 //I2C Wire addresses @@ -59,6 +59,9 @@ public: #endif bool power_init(); void begin() { + + power_init(); + ESP32Board::begin(); esp_reset_reason_t reason = esp_reset_reason(); @@ -71,7 +74,6 @@ public: rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); } - power_init(); } void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index b9118e3f..abcd89bc 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -5,12 +5,11 @@ build_flags = ${esp32_base.build_flags} -I variants/lilygo_tbeam_supreme_SX1262 -D LORA_TX_POWER=22 + -D P_LORA_TX_LED=6 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper ;-D DISPLAY_CLASS=SSD1306Display ;Needs to be modified for SH1106 -D SX126X_RX_BOOSTED_GAIN=1 - -D HAS_PMU=1 - -D HAS_GPS=1 build_src_filter = ${esp32_base.build_src_filter} +<../variants/lilygo_tbeam_supreme_SX1262> board_build.partitions = min_spiffs.csv ; get around 4mb flash limit From 1680eb29aa99358f9e80c4fe038b2eee93522031 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 15 May 2025 20:36:09 +1000 Subject: [PATCH 097/154] * repeater: MAX_CLIENTS now defaults to 32 --- examples/simple_repeater/main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 6452137f..d272abb8 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -105,7 +105,9 @@ struct ClientInfo { uint8_t out_path[MAX_PATH_SIZE]; }; -#define MAX_CLIENTS 4 +#ifndef MAX_CLIENTS + #define MAX_CLIENTS 32 +#endif struct NeighbourInfo { mesh::Identity id; From b11f43987b2a423c13bec1c701d5fa340dad58ae Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 16 May 2025 19:57:09 +1000 Subject: [PATCH 098/154] * companion: fix for importContact(). Now removes the packet-hash from table, before 'replaying' --- src/Mesh.h | 1 + src/helpers/BaseChatMesh.cpp | 1 + src/helpers/SimpleMeshTables.h | 24 ++++++++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/src/Mesh.h b/src/Mesh.h index cb81f8de..9649187c 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -16,6 +16,7 @@ public: class MeshTables { public: virtual bool hasSeen(const Packet* packet) = 0; + virtual void clear(const Packet* packet) = 0; // remove this packet hash from table }; /** diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index b6be2582..36ddcbb4 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -373,6 +373,7 @@ bool BaseChatMesh::importContact(const uint8_t src_buf[], uint8_t len) { if (pkt) { if (pkt->readFrom(src_buf, len) && pkt->getPayloadType() == PAYLOAD_TYPE_ADVERT) { pkt->header |= ROUTE_TYPE_FLOOD; // simulate it being received flood-mode + getTables()->clear(pkt); // remove packet hash from table, so we can receive/process it again _pendingLoopback = pkt; // loop-back, as if received over radio return true; // success } else { diff --git a/src/helpers/SimpleMeshTables.h b/src/helpers/SimpleMeshTables.h index 910b8071..2f8af52a 100644 --- a/src/helpers/SimpleMeshTables.h +++ b/src/helpers/SimpleMeshTables.h @@ -80,6 +80,30 @@ public: return false; } + void clear(const mesh::Packet* packet) override { + if (packet->getPayloadType() == PAYLOAD_TYPE_ACK) { + uint32_t ack; + memcpy(&ack, packet->payload, 4); + for (int i = 0; i < MAX_PACKET_ACKS; i++) { + if (ack == _acks[i]) { + _acks[i] = 0; + break; + } + } + } else { + uint8_t hash[MAX_HASH_SIZE]; + packet->calculatePacketHash(hash); + + uint8_t* sp = _hashes; + for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) { + if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) { + memset(sp, 0, MAX_HASH_SIZE); + break; + } + } + } + } + uint32_t getNumDirectDups() const { return _direct_dups; } uint32_t getNumFloodDups() const { return _flood_dups; } From e5925e5f41ef6f3c4f94af88da82f42cbcbbae58 Mon Sep 17 00:00:00 2001 From: Normunds Gavars Date: Fri, 16 May 2025 15:03:42 +0300 Subject: [PATCH 099/154] Telemetry: add support of AHT10/AHT20 temp/humidity sensor to Promicro --- variants/promicro/platformio.ini | 27 +++------- variants/promicro/target.cpp | 85 +++++++++++++++++++++----------- variants/promicro/target.h | 8 ++- 3 files changed, 70 insertions(+), 50 deletions(-) diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 376e6c74..1cae8cdd 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -19,6 +19,9 @@ build_src_filter = ${nrf52840_base.build_src_filter} +<../variants/promicro> lib_deps= ${nrf52840_base.lib_deps} adafruit/Adafruit SSD1306 @ ^2.5.13 + robtillaart/INA3221 @ ^0.4.1 + robtillaart/INA219 @ ^0.4.1 + adafruit/Adafruit AHTX0@^2.0.5 [env:Faketec_Repeater] extends = Faketec @@ -36,8 +39,6 @@ build_flags = ; -D MESH_DEBUG=1 lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 - robtillaart/INA3221 @ ^0.4.1 - robtillaart/INA219 @ ^0.4.1 [env:Faketec_room_server] extends = Faketec @@ -54,8 +55,6 @@ build_flags = ${Faketec.build_flags} ; -D MESH_DEBUG=1 lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 - robtillaart/INA3221 @ ^0.4.1 - robtillaart/INA219 @ ^0.4.1 [env:Faketec_terminal_chat] extends = Faketec @@ -69,8 +68,6 @@ build_src_filter = ${Faketec.build_src_filter} lib_deps = ${Faketec.lib_deps} densaugeo/base64 @ ~1.4.0 adafruit/RTClib @ ^2.1.3 - robtillaart/INA3221 @ ^0.4.1 - robtillaart/INA219 @ ^0.4.1 [env:Faketec_companion_radio_usb] extends = Faketec @@ -85,8 +82,6 @@ build_src_filter = ${Faketec.build_src_filter} lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 - robtillaart/INA3221 @ ^0.4.1 - robtillaart/INA219 @ ^0.4.1 [env:Faketec_companion_radio_ble] extends = Faketec @@ -107,8 +102,6 @@ build_src_filter = ${Faketec.build_src_filter} lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 - robtillaart/INA3221 @ ^0.4.1 - robtillaart/INA219 @ ^0.4.1 [ProMicroLLCC68] extends = nrf52840_base @@ -125,6 +118,10 @@ build_src_filter = ${nrf52840_base.build_src_filter} + +<../variants/promicro> +lib_deps= ${nrf52840_base.lib_deps} + robtillaart/INA3221 @ ^0.4.1 + robtillaart/INA219 @ ^0.4.1 + adafruit/Adafruit AHTX0@^2.0.5 [env:ProMicroLLCC68_Repeater] extends = ProMicroLLCC68 @@ -138,8 +135,6 @@ build_flags = ${ProMicroLLCC68.build_flags} ; -D MESH_DEBUG=1 lib_deps = ${ProMicroLLCC68.lib_deps} adafruit/RTClib @ ^2.1.3 - robtillaart/INA3221 @ ^0.4.1 - robtillaart/INA219 @ ^0.4.1 [env:ProMicroLLCC68_room_server] extends = ProMicroLLCC68 @@ -153,8 +148,6 @@ build_flags = ${ProMicroLLCC68.build_flags} ; -D MESH_DEBUG=1 lib_deps = ${ProMicroLLCC68.lib_deps} adafruit/RTClib @ ^2.1.3 - robtillaart/INA3221 @ ^0.4.1 - robtillaart/INA219 @ ^0.4.1 [env:ProMicroLLCC68_terminal_chat] extends = ProMicroLLCC68 @@ -168,8 +161,6 @@ build_src_filter = ${ProMicroLLCC68.build_src_filter} lib_deps = ${ProMicroLLCC68.lib_deps} densaugeo/base64 @ ~1.4.0 adafruit/RTClib @ ^2.1.3 - robtillaart/INA3221 @ ^0.4.1 - robtillaart/INA219 @ ^0.4.1 [env:ProMicroLLCC68_companion_radio_usb] extends = ProMicroLLCC68 @@ -183,8 +174,6 @@ build_src_filter = ${ProMicroLLCC68.build_src_filter} lib_deps = ${ProMicroLLCC68.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 - robtillaart/INA3221 @ ^0.4.1 - robtillaart/INA219 @ ^0.4.1 [env:ProMicroLLCC68_companion_radio_ble] extends = ProMicroLLCC68 @@ -204,5 +193,3 @@ build_src_filter = ${ProMicroLLCC68.build_src_filter} lib_deps = ${ProMicroLLCC68.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 - robtillaart/INA3221 @ ^0.4.1 - robtillaart/INA219 @ ^0.4.1 \ No newline at end of file diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index 072395ff..b945fcf4 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -77,48 +77,41 @@ mesh::LocalIdentity radio_new_identity() { static INA3221 INA_3221(TELEM_INA3221_ADDRESS, &Wire); static INA219 INA_219(TELEM_INA219_ADDRESS, &Wire); +static Adafruit_AHTX0 AHTX; bool PromicroSensorManager::begin() { - if (INA_3221.begin()) { - MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", INA_3221.getAddress()); - MESH_DEBUG_PRINTLN("%04X %04X %04X", INA_3221.getDieID(), INA_3221.getManufacturerID(), INA_3221.getConfiguration()); + initINA3221(); + initINA219(); + initAHTX(); - for(int i = 0; i < 3; i++) { - INA_3221.setShuntR(i, TELEM_INA3221_SHUNT_VALUE); - } - INA3221initialized = true; - } else { - INA3221initialized = false; - MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS); - } - if (INA_219.begin()) { - MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", INA_219.getAddress()); - INA219_CHANNEL = INA3221initialized ? TELEM_CHANNEL_SELF + 4 : TELEM_CHANNEL_SELF + 1; - INA_219.setMaxCurrentShunt(TELEM_INA219_MAX_CURRENT, TELEM_INA219_SHUNT_VALUE); - INA219initialized = true; - } else { - INA219initialized = false; - MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS); - } return true; } bool PromicroSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { + int nextAvalableChannel = TELEM_CHANNEL_SELF + 1; if (requester_permissions & TELEM_PERM_ENVIRONMENT) { if (INA3221initialized) { for(int i = 0; i < 3; i++) { // add only enabled INA3221 channels to telemetry if (INA3221_CHANNEL_ENABLED[i]) { - telemetry.addVoltage(INA3221_CHANNELS[i], INA_3221.getBusVoltage(i)); - telemetry.addCurrent(INA3221_CHANNELS[i], INA_3221.getCurrent(i)); - telemetry.addPower(INA3221_CHANNELS[i], INA_3221.getPower(i)); + telemetry.addVoltage(nextAvalableChannel, INA_3221.getBusVoltage(i)); + telemetry.addCurrent(nextAvalableChannel, INA_3221.getCurrent(i)); + telemetry.addPower(nextAvalableChannel, INA_3221.getPower(i)); + nextAvalableChannel++; } } } - if(INA219initialized) { - telemetry.addVoltage(INA219_CHANNEL, INA_219.getBusVoltage()); - telemetry.addCurrent(INA219_CHANNEL, INA_219.getCurrent()); - telemetry.addPower(INA219_CHANNEL, INA_219.getPower()); + if (INA219initialized) { + telemetry.addVoltage(nextAvalableChannel, INA_219.getBusVoltage()); + telemetry.addCurrent(nextAvalableChannel, INA_219.getCurrent()); + telemetry.addPower(nextAvalableChannel, INA_219.getPower()); + nextAvalableChannel++; + } + if (AHTXinitialized) { + sensors_event_t humidity, temp; + AHTX.getEvent(&humidity, &temp); + telemetry.addTemperature(TELEM_CHANNEL_SELF, temp.temperature); + telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, humidity.relative_humidity); } } @@ -162,4 +155,40 @@ bool PromicroSensorManager::setSettingValue(const char* name, const char* value) } } return false; -} \ No newline at end of file +} + +void PromicroSensorManager::initINA3221() { + if (INA_3221.begin()) { + MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", INA_3221.getAddress()); + MESH_DEBUG_PRINTLN("%04X %04X %04X", INA_3221.getDieID(), INA_3221.getManufacturerID(), INA_3221.getConfiguration()); + + for(int i = 0; i < 3; i++) { + INA_3221.setShuntR(i, TELEM_INA3221_SHUNT_VALUE); + } + INA3221initialized = true; + } else { + INA3221initialized = false; + MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS); + } +} + +void PromicroSensorManager::initINA219() { + if (INA_219.begin()) { + MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", INA_219.getAddress()); + INA_219.setMaxCurrentShunt(TELEM_INA219_MAX_CURRENT, TELEM_INA219_SHUNT_VALUE); + INA219initialized = true; + } else { + INA219initialized = false; + MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS); + } +} + +void PromicroSensorManager::initAHTX() { + if (AHTX.begin(&Wire, 0, TELEM_AHTX_ADDRESS)) { + MESH_DEBUG_PRINTLN("Found AHT10/AHT20 at address: %02X", TELEM_AHTX_ADDRESS); + AHTXinitialized = true; + } else { + AHTXinitialized = false; + MESH_DEBUG_PRINTLN("AHT10/AHT20 was not found at I2C address %02X", TELEM_AHTX_ADDRESS); + } +} diff --git a/variants/promicro/target.h b/variants/promicro/target.h index aae02dd1..d77c4d1d 100644 --- a/variants/promicro/target.h +++ b/variants/promicro/target.h @@ -10,6 +10,7 @@ #include #include #include +#include #define NUM_SENSOR_SETTINGS 3 @@ -26,6 +27,7 @@ mesh::LocalIdentity radio_new_identity(); #define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current sensor I2C address #define TELEM_INA219_ADDRESS 0x40 // INA219 single channel current sensor I2C address +#define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address #define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts #define TELEM_INA3221_SETTING_CH1 "INA3221-1" @@ -38,13 +40,15 @@ mesh::LocalIdentity radio_new_identity(); class PromicroSensorManager: public SensorManager { bool INA3221initialized = false; bool INA219initialized = false; + bool AHTXinitialized = false; // INA3221 channels in telemetry - int INA3221_CHANNELS[NUM_SENSOR_SETTINGS] = {TELEM_CHANNEL_SELF + 1, TELEM_CHANNEL_SELF + 2, TELEM_CHANNEL_SELF+ 3}; const char * INA3221_CHANNEL_NAMES[NUM_SENSOR_SETTINGS] = { TELEM_INA3221_SETTING_CH1, TELEM_INA3221_SETTING_CH2, TELEM_INA3221_SETTING_CH3}; bool INA3221_CHANNEL_ENABLED[NUM_SENSOR_SETTINGS] = {true, true, true}; - int INA219_CHANNEL; + void initINA3221(); + void initINA219(); + void initAHTX(); public: PromicroSensorManager(){}; bool begin() override; From 25b534a29d0d74605125e0c523c28629da04085a Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Fri, 16 May 2025 08:45:55 -0700 Subject: [PATCH 100/154] add support for SH1106 OLED display --- examples/companion_radio/main.cpp | 2 + src/helpers/ui/SH1106Display.cpp | 91 +++++++++++++++++++++++++++++++ src/helpers/ui/SH1106Display.h | 43 +++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 src/helpers/ui/SH1106Display.cpp create mode 100644 src/helpers/ui/SH1106Display.h diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 7d522b08..80fa3430 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -66,6 +66,8 @@ #include #elif ST7789 #include + #elif SH1106 + #include #elif defined(HAS_GxEPD) #include #else diff --git a/src/helpers/ui/SH1106Display.cpp b/src/helpers/ui/SH1106Display.cpp new file mode 100644 index 00000000..f383bb00 --- /dev/null +++ b/src/helpers/ui/SH1106Display.cpp @@ -0,0 +1,91 @@ +#include "SH1106Display.h" +#include +#include "Adafruit_SH110X.h" + +bool SH1106Display::i2c_probe(TwoWire &wire, uint8_t addr) +{ + wire.beginTransmission(addr); + uint8_t error = wire.endTransmission(); + return (error == 0); +} + +bool SH1106Display::begin() +{ + return display.begin(DISPLAY_ADDRESS, true) && i2c_probe(Wire, DISPLAY_ADDRESS); +} + +void SH1106Display::turnOn() +{ + display.oled_command(SH110X_DISPLAYON); + _isOn = true; +} + +void SH1106Display::turnOff() +{ + display.oled_command(SH110X_DISPLAYOFF); + _isOn = false; +} + +void SH1106Display::clear() +{ + display.clearDisplay(); + display.display(); +} + +void SH1106Display::startFrame(Color bkg) +{ + display.clearDisplay(); // TODO: apply 'bkg' + _color = SH110X_WHITE; + display.setTextColor(_color); + display.setTextSize(1); + display.cp437(true); // Use full 256 char 'Code Page 437' font +} + +void SH1106Display::setTextSize(int sz) +{ + display.setTextSize(sz); +} + +void SH1106Display::setColor(Color c) +{ + _color = (c != 0) ? SH110X_WHITE : SH110X_BLACK; + display.setTextColor(_color); +} + +void SH1106Display::setCursor(int x, int y) +{ + display.setCursor(x, y); +} + +void SH1106Display::print(const char *str) +{ + display.print(str); +} + +void SH1106Display::fillRect(int x, int y, int w, int h) +{ + display.fillRect(x, y, w, h, _color); +} + +void SH1106Display::drawRect(int x, int y, int w, int h) +{ + display.drawRect(x, y, w, h, _color); +} + +void SH1106Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) +{ + display.drawBitmap(x, y, bits, w, h, SH110X_WHITE); +} + +uint16_t SH1106Display::getTextWidth(const char *str) +{ + int16_t x1, y1; + uint16_t w, h; + display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); + return w; +} + +void SH1106Display::endFrame() +{ + display.display(); +} diff --git a/src/helpers/ui/SH1106Display.h b/src/helpers/ui/SH1106Display.h new file mode 100644 index 00000000..b52a6adf --- /dev/null +++ b/src/helpers/ui/SH1106Display.h @@ -0,0 +1,43 @@ +#pragma once + +#include "DisplayDriver.h" +#include +#include +#define SH110X_NO_SPLASH +#include + +#ifndef PIN_OLED_RESET +#define PIN_OLED_RESET -1 +#endif + +#ifndef DISPLAY_ADDRESS +#define DISPLAY_ADDRESS 0x3C +#endif + +class SH1106Display : public DisplayDriver +{ + Adafruit_SH1106G display; + bool _isOn; + uint8_t _color; + + bool i2c_probe(TwoWire &wire, uint8_t addr); + +public: + SH1106Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; } + bool begin(); + + bool isOn() override { return _isOn; } + void turnOn() override; + void turnOff() override; + void clear() override; + void startFrame(Color bkg = DARK) override; + void setTextSize(int sz) override; + void setColor(Color c) override; + void setCursor(int x, int y) override; + void print(const char *str) override; + void fillRect(int x, int y, int w, int h) override; + void drawRect(int x, int y, int w, int h) override; + void drawXbm(int x, int y, const uint8_t *bits, int w, int h) override; + uint16_t getTextWidth(const char *str) override; + void endFrame() override; +}; From 4196fd4ab74c4f51aa38b6ef7a6b0372ddfad543 Mon Sep 17 00:00:00 2001 From: uncle lit <43320854+LitBomb@users.noreply.github.com> Date: Fri, 16 May 2025 17:09:17 -0700 Subject: [PATCH 101/154] Update faq.md revert a bad merge https://github.com/ripplebiz/MeshCore/commit/2818749a09cc4e39cba665f181b5fd4779cffd76 in main that wiped out the last changes to faq.md Update to note both SF 10 and SF 11 can be used based on local use case needs. There are presets in Liam's smartphone apps for both SF 10 and SF 11. --- docs/faq.md | 350 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 291 insertions(+), 59 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 632f369c..1bb4a0e5 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,13 +1,75 @@ -# MeshCore-FAQ +**MeshCore-FAQ** A list of frequently-asked questions and answers for MeshCore The current version of this MeshCore FAQ is at https://github.com/ripplebiz/MeshCore/blob/main/docs/faq.md. This MeshCore FAQ is also mirrored at https://github.com/LitBomb/MeshCore-FAQ and might have newer updates if pull requests on Scott's MeshCore repo are not approved yet. -author: https://github.com/LitBomb +author: https://github.com/LitBomb --- -## Q: What is MeshCore? +- [1. Introduction](#1-introduction) + - [1.1. Q: What is MeshCore?](#11-q-what-is-meshcore) + - [1.2. Q: What do you need to start using MeshCore?](#12-q-what-do-you-need-to-start-using-meshcore) + - [1.2.1. Hardware](#121-hardware) + - [1.2.2. Firmware](#122-firmware) + - [1.2.3. Companion Radio Firmware](#123-companion-radio-firmware) + - [1.2.4. Repeater](#124-repeater) + - [1.2.5. Room Server](#125-room-server) +- [2. Initial Setup](#2-initial-setup) + - [2.1. Q: How many devices do I need to start using meshcore?](#21-q-how-many-devices-do-i-need-to-start-using-meshcore) + - [2.2. Q: Does MeshCore cost any money?](#22-q-does-meshcore-cost-any-money) + - [2.3. Q: What frequencies are supported by MeshCore?](#23-q-what-frequencies-are-supported-by-meshcore) + - [2.4. Q: What is an "advert" in MeshCore?](#24-q-what-is-an-advert-in-meshcore) + - [2.5. Q: Is there a hop limit?](#25-q-is-there-a-hop-limit) +- [3. Server Administration](#3-server-administration) + - [3.1. Q: How do you configure a repeater or a room server?](#31-q-how-do-you-configure-a-repeater-or-a-room-server) + - [3.2. Q: Do I need to set the location for a repeater?](#32-q-do-i-need-to-set-the-location-for-a-repeater) + - [3.3. Q: What is the password to administer a repeater or a room server?](#33-q-what-is-the-password-to-administer-a-repeater-or-a-room-server) + - [3.4. Q: What is the password to join a room server?](#34-q-what-is-the-password-to-join-a-room-server) +- [4. T-Deck Related](#4-t-deck-related) + - [4.1. Q: What are the steps to get a T-Deck into DFU (Device Firmware Update) mode?](#41-q-what-are-the-steps-to-get-a-t-deck-into-dfu-device-firmware-update-mode) + - [4.2. Q: Why is my T-Deck Plus not getting any satellite lock?](#42-q-why-is-my-t-deck-plus-not-getting-any-satellite-lock) + - [4.3. Q: Why is my OG (non-Plus) T-Deck not getting any satellite lock?](#43-q-why-is-my-og-non-plus-t-deck-not-getting-any-satellite-lock) + - [4.4. Q: What size of SD card does the T-Deck support?](#44-q-what-size-of-sd-card-does-the-t-deck-support) + - [4.5. Q: How do I get maps on T-Deck?](#45-q-how-do-i-get-maps-on-t-deck) + - [4.6. Q: Where do the map tiles go?](#46-q-where-do-the-map-tiles-go) + - [4.7. Q: How to unlock deeper map zoom and server management features on T-Deck?](#47-q-how-to-unlock-deeper-map-zoom-and-server-management-features-on-t-deck) + - [4.8. Q: How to decipher the diagnostics screen on T-Deck?](#48-q-how-to-decipher-the-diagnostics-screen-on-t-deck) + - [4.9. Q: The T-Deck sound is too loud?](#49-q-the-t-deck-sound-is-too-loud) + - [4.10. Q: Can you customize the sound?](#410-q-can-you-customize-the-sound) + - [4.11. Q: What is the 'Import from Clipboard' feature on the t-deck and is there a way to manually add nodes without having to receive adverts?](#411-q-what-is-the-import-from-clipboard-feature-on-the-t-deck-and-is-there-a-way-to-manually-add-nodes-without-having-to-receive-adverts) +- [5. General](#5-general) + - [5.1. Q: What are BW, SF, and CR?](#51-q-what-are-bw-sf-and-cr) + - [5.2. Q: Do MeshCore clients repeat?](#52-q-do-meshcore-clients-repeat) + - [5.3. Q: What happens when a node learns a route via a mobile repeater, and that repeater is gone?](#53-q-what-happens-when-a-node-learns-a-route-via-a-mobile-repeater-and-that-repeater-is-gone) + - [5.4. Q: How does a node discovery a path to its destination and then use it to send messages in the future, instead of flooding every message it sends like Meshtastic?](#54-q-how-does-a-node-discovery-a-path-to-its-destination-and-then-use-it-to-send-messages-in-the-future-instead-of-flooding-every-message-it-sends-like-meshtastic) + - [5.5. Q: Do public channels always flood? Do private channels always flood?](#55-q-do-public-channels-always-flood-do-private-channels-always-flood) + - [5.6. Q: what is the public key for the default public channel?](#56-q-what-is-the-public-key-for-the-default-public-channel) + - [5.7. Q: Is MeshCore open source?](#57-q-is-meshcore-open-source) + - [5.8. Q: How can I support MeshCore?](#58-q-how-can-i-support-meshcore) + - [5.9. Q: How do I build MeshCore firmware from source?](#59-q-how-do-i-build-meshcore-firmware-from-source) + - [5.10. Q: Are there other MeshCore related open source projects?](#510-q-are-there-other-meshcore-related-open-source-projects) + - [5.11. Q: Does MeshCore support ATAK](#511-q-does-meshcore-support-atak) + - [5.12. Q: How do I add a node to the MeshCore Map](#512-q-how-do-i-add-a-node-to-the-meshcore-map) + - [5.13. Q: Can I use a Raspberry Pi to update a MeshCore radio?](#513-q-can-i-use-a-raspberry-pi-to-update-a-meshcore-radio) + - [5.14. Q: Are there are projects built around MeshCore?](#514-q-are-there-are-projects-built-around-meshcore) + - [5.14.1. meshcoremqtt](#5141-meshcoremqtt) + - [5.14.2. MeshCore for Home Assistant](#5142-meshcore-for-home-assistant) + - [5.14.3. Python MeshCore](#5143-python-meshcore) + - [5.14.4. meshcore-cli](#5144-meshcore-cli) + - [5.14.5. meshcore.js](#5145-meshcorejs) +- [6. Troubleshooting](#6-troubleshooting) + - [6.1. Q: My client says another client or a repeater or a room server was last seen many, many days ago.](#61-q-my-client-says-another-client-or-a-repeater-or-a-room-server-was-last-seen-many-many-days-ago) + - [6.2. Q: A repeater or a client or a room server I expect to see on my discover list (on T-Deck) or contact list (on a smart device client) are not listed.](#62-q-a-repeater-or-a-client-or-a-room-server-i-expect-to-see-on-my-discover-list-on-t-deck-or-contact-list-on-a-smart-device-client-are-not-listed) + - [6.3. Q: How to connect to a repeater via BLE (bluetooth)?](#63-q-how-to-connect-to-a-repeater-via-ble-bluetooth) + - [6.4. Q: I can't connect via bluetooth, what is the bluetooth pairing code?](#64-q-i-cant-connect-via-bluetooth-what-is-the-bluetooth-pairing-code) + - [6.5. Q: My Heltec V3 keeps disconnecting from my smartphone. It can't hold a solid Bluetooth connection.](#65-q-my-heltec-v3-keeps-disconnecting-from-my-smartphone--it-cant-hold-a-solid-bluetooth-connection) +- [7. Other Questions:](#7-other-questions) + - [7.1. Q: How to Update repeater and room server firmware over the air?](#71-q-how-to--update-repeater-and-room-server-firmware-over-the-air) + +## 1. Introduction + +### 1.1. Q: What is MeshCore? **A:** MeshCore is free and open source * MeshCore is the routing and firmware etc, available on GitHub under MIT license @@ -22,7 +84,7 @@ These features are completely optional and aren't needed for the core messaging Anyone is able to build anything they like on top of MeshCore without paying anything. -## Q: What do you need to start using MeshCore? +### 1.2. Q: What do you need to start using MeshCore? **A:** Everything you need for MeshCore is available at: Main web site: [https://meshcore.co.uk/](https://meshcore.co.uk/) Firmware Flasher: https://flasher.meshcore.co.uk/ @@ -34,15 +96,15 @@ Anyone is able to build anything they like on top of MeshCore without paying any You need LoRa hardware devices to run MeshCore firmware as clients or server (repeater and room server). -### Hardware +#### 1.2.1. Hardware To use MeshCore without using a phone as the client interface, you can run MeshCore on a T-Deck or T-Deck Plus. It is a complete off-grid secure communication solution. MeshCore is also available on a variety of 868MHz and 915MHz LoRa devices. For example, RAK4631 devices (19003, 19007, 19026), Heltec V3, Xiao S3 WIO, Xiao C3, Heltec T114, Station G2, Seeed Studio T1000-E. More devices will be supported later. -### Firmware +#### 1.2.2. Firmware MeshCore has four firmware types that are not available on other LoRa systems. MeshCore has the following: -#### Companion Radio Firmware +#### 1.2.3. Companion Radio Firmware Companion radios are for connecting to the Android app or web app as a messenger client. There are two different companion radio firmware versions: 1. **BLE Companion** @@ -54,12 +116,12 @@ Companion radios are for connecting to the Android app or web app as a messenger -#### Repeater +#### 1.2.4. Repeater Repeaters are used to extend the range of a MeshCore network. Repeater firmware runs on the same devices that run client firmware. A repeater's job is to forward MeshCore packets to the destination device. It does **not** forward or retransmit every packet it receives, unlike other LoRa mesh systems. A repeater can be remotely administered using a T-Deck running the MeshCore firwmware with remote admistration features unlocked, or from a BLE Companion client connected to a smartphone running the MeshCore app. -#### Room Server +#### 1.2.5. Room Server A room server is a simple BBS server for sharing posts. T-Deck devices running MeshCore firmware or a BLE Companion client connected to a smartphone running the MeshCore app can connect to a room server. room servers store message history on them, and push the stored messages to users. Room servers allow roaming users to come back later and retrieve message history. Contrast to channels, messages are either received when it's sent, or not received and missed if the a room user is out of range. You can think of room servers like email servers where you can come back later and get your emails from your mail server @@ -74,9 +136,9 @@ A room server can also take on the repeater role. To enable repeater role on a --- -## Initial Setup +## 2. Initial Setup -### Q: How many devices do I need to start using meshcore? +### 2.1. Q: How many devices do I need to start using meshcore? **A:** If you have one supported device, flash the BLE Companion firmware and use your device as a client. You can connect to the device using the Android client via bluetooth (iOS client will be available later). You can start communiating with other MeshCore users near you. If you have two supported devices, and there are not many MeshCore users near you, flash both of them to BLE Companion firmware so you can use your devices to communiate with your near-by friends and family. @@ -91,7 +153,7 @@ The repeater and room server CLI reference is here: https://github.com/ripplebiz If you have more supported devices, you can use your additional deivces with the room server firmware. -### Q: Does MeshCore cost any money? +### 2.2. Q: Does MeshCore cost any money? **A:** All radio firmware versions (e.g. for Heltec V3, RAK, T-1000E, etc) are free and open source developed by Scott at Ripple Radios. @@ -100,19 +162,25 @@ The native Android and iOS client uses the freemium model and is developed by Li The T-Deck firmware is free to download and most features are available without cost. To support the firmware developer, you can pay for a registration key to unlock your T-Deck for deeper map zoom and remote server administration over RF using the T-Deck. You do not need to pay for the registration to use your T-Deck for direct messaging and connecting to repeaters and room servers. -### Q: What frequencies are supported by MeshCore? +### 2.3. Q: What frequencies are supported by MeshCore? **A:** It supports the 868MHz range in the UK/EU and the 915MHz range in New Zealand, Australia, and the USA. Countries and regions in these two frequency ranges are also supported. The firmware and client allow users to set their preferred frequency. - Australia and New Zealand are on **915.8MHz** - UK and EU are on **869.525MHz** - Canada and USA are on **910.525MHz** - For other regions and countries, please check your local LoRa frequency +In UK and EU, 867.5MHz is not allowed to use 250kHz bandwidth and it only allows 2.5% duty cycle for clients. 869.525Mhz allows an airtime of 10%, 250KHz bandwidth, and a higher EIRP, therefore MeshCore nodes can send more often and with more power. That is why this frequency is chosen for UK and EU. This is also why Meshtastic also uses this frequency. + +[Source]([https://](https://discord.com/channels/826570251612323860/1330643963501351004/1356540643853209641)) + the rest of the radio settings are the same for all frequencies: -- Spread Factor (SF): 10 +- Spread Factor (SF): 11 - Coding Rate (CR): 5 - Bandwidth (BW): 250.00 -### Q: What is an "advert" in MeshCore? +(originally MeshCore started with SF 10. recently (as of late April 2025) the community has avocated SF 11 also a viable option for longer range but a little slower transmissions. Currently there are MeshCore meshes with SF 10 and SF 11. Liam Cottle's smartphone app's presets now recommend SF 10 for Australia and SF 11 for all other regions and countries. EU and UK has SF 10 and SF 11 presets. Work with your local meshers on diciding with SF number is best for your use cases. In the future, there may be bridge nodes that can bridge SF 10 and SF 11 (or even different frequencies) traffic.) + +### 2.4. Q: What is an "advert" in MeshCore? **A:** Advert means to advertise yourself on the network. In Reticulum terms it would be to announce. In Meshtastic terms it would be the node sending it's node info. @@ -125,7 +193,7 @@ MeshCore clients only advertise themselves when the user initiates it. A repeate `set advert.interval {minutes}` -### Q: Is there a hop limit? +### 2.5. Q: Is there a hop limit? **A:** Internally the firmware has maximum limit of 64 hops. In real world settings it will be difficult to get close to the limit due to the environments and timing as packets travel further and further. We want to hear how far your MeshCore conversations go. @@ -133,30 +201,43 @@ MeshCore clients only advertise themselves when the user initiates it. A repeate --- -## Server Administration +## 3. Server Administration -### Q: How do you configure a repeater or a room server? -**A:** One of these servers can be administered with one of the options below: +### 3.1. Q: How do you configure a repeater or a room server? + +**A:** - When MeshCore is flashed onto a LoRa device is for the first time, it is necessary to set the server device's frequency to make it utilize the frequency that is legal in your country or region. + +Repeater or room server can be administered with one of the options below: + +- After a repeater or room server firmware is flashed on to a LoRa device, go to and use the web user interface to connect to the LoRa device via USB serial. From there you can set the name of the server, its frequency and other related settings, location, passwords etc. + +![image](https://github.com/user-attachments/assets/bec28ff3-a7d6-4a1e-8602-cb6b290dd150) + + - Connect the server device using a USB cable to a computer running Chrome on https://flasher.meshcore.co.uk/, then use the `console` feature to connect to the device - - this is necessary to set the server device's frequency if it doesn't match the frequency for your local region or country -- MeshCore smart device clients have the ability to remotely administer servers. -- A T-Deck running unlocked/registered MeshCore firmware. Remote server administration is enabled through registering your T-Deck with Ripple Radios. It is one of the ways to support MeshCore development. You can register your T-Deck at: - -### Q: Do I need to set the location for a repeater? +- Use a MeshCore smartphone clients to remotely administer servers via LoRa. + +- A T-Deck running unlocked/registered MeshCore firmware. Remote server administration is enabled through registering your T-Deck with Ripple Radios. It is one of the ways to support MeshCore development. You can register your T-Deck at: + + + + + +### 3.2. Q: Do I need to set the location for a repeater? **A:** With location set for a repeater, it can show up on a MeshCore map in the future. Set location with the following commands: `set lat set long ` You can get the latitude and longitude from Google Maps by right-clicking the location you are at on the map. -### Q: What is the password to administer a repeater or a room server? +### 3.3. Q: What is the password to administer a repeater or a room server? **A:** The default admin password to a repeater and room server is `password`. Use the following command to change the admin password: `password {new-password}` -### Q: What is the password to join a room server? +### 3.4. Q: What is the password to join a room server? **A:** The default guest password to a room server is `hello`. Use the following command to change the guest password: `set guest.password {guest-password}` @@ -164,9 +245,9 @@ You can get the latitude and longitude from Google Maps by right-clicking the lo --- -## T-Deck Related +## 4. T-Deck Related -### Q: What are the steps to get a T-Deck into DFU (Device Firmware Update) mode? +### 4.1. Q: What are the steps to get a T-Deck into DFU (Device Firmware Update) mode? **A:** 1. Device off 2. Connect USB cable to device @@ -177,16 +258,20 @@ You can get the latitude and longitude from Google Maps by right-clicking the lo 7. T-Deck in DFU mode now 8. At this point you can begin flashing using -### Q: Why is my T-Deck Plus not getting any satellite lock? +### 4.2. Q: Why is my T-Deck Plus not getting any satellite lock? **A:** For T-Deck Plus, the GPS baud rate should be set to **38400**. Also, a number of T-Deck Plus devices were found to have the GPS module installed upside down, with the GPS antenna facing down instead of up. If your T-Deck Plus still doesn't get any satellite lock after setting the baud rate to 38400, you might need to open up the device to check the GPS orientation. -### Q: Why is my OG (non-Plus) T-Deck not getting any satellite lock? +GPS on T-Deck is always enabled. You can skip the "GPS clock sync" and the T-Deck will continue to try to get a GPS lock. You can go to the `GPS Info` screen, you should see the `Sentences:` coutner increasing if the baud rate is correct. + +[Source]([https://](https://discord.com/channels/826570251612323860/1330643963501351004/1356609240302616689)) + +### 4.3. Q: Why is my OG (non-Plus) T-Deck not getting any satellite lock? **A:** The OG (non-Plus) T-Deck doesn't come with a GPS. If you added a GPS to your OG T-Deck, please refer to the manual of your GPS to see what baud rate it requires. Alternatively, you can try to set the baud rate from 9600, 19200, etc., and up to 115200 to see which one works. -### Q: What size of SD card does the T-Deck support? +### 4.4. Q: What size of SD card does the T-Deck support? **A:** Users have had no issues using 16GB or 32GB SD cards. Format the SD card to **FAT32**. -### Q: How do I get maps on T-Deck? +### 4.5. Q: How do I get maps on T-Deck? **A:** You need map tiles. You can get pre-downloaded map tiles here (a good way to support development): - (Europe) - (US) @@ -200,28 +285,46 @@ There is also a modified script that adds additional error handling and parallel UK map tiles are available separately from Andy Kirby on his discord server: -### Q: Where do the map tiles go? +### 4.6. Q: Where do the map tiles go? Once you have the tiles downloaded, copy the `\tiles` folder to the root of your T-Deck's SD card. -### Q: How to unlock deeper map zoom and server management features on T-Deck? +### 4.7. Q: How to unlock deeper map zoom and server management features on T-Deck? **A:** You can download, install, and use the T-Deck firmware for free, but it has some features (map zoom, server administration) that are enabled if you purchase an unlock code for \$10 per T-Deck device. Unlock page: +### 4.8. Q: How to decipher the diagnostics screen on T-Deck? -### Q: The T-Deck sound is too loud? -### Q: Can you customize the sound? +**A: ** Space is tight on T-Deck's screen so the information is a bit cryptic. Format is : +`{hops} l:{packet-length}({payload-len}) t:{packet-type} snr:{n} rssi:{n}` + +See here for packet-type: [https://github.com/ripplebiz/MeshCore/blob/main/src/Packet.h#L19](https://github.com/ripplebiz/MeshCore/blob/main/src/Packet.h#L19 "https://github.com/ripplebiz/MeshCore/blob/main/src/Packet.h#L19") + + + #define PAYLOAD_TYPE_REQ 0x00 // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob) + #define PAYLOAD_TYPE_RESPONSE 0x01 // response to REQ or ANON_REQ (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob) + #define PAYLOAD_TYPE_TXT_MSG 0x02 // a plain text message (prefixed with dest/src hashes, MAC) (enc data: timestamp, text) + #define PAYLOAD_TYPE_ACK 0x03 // a simple ack #define PAYLOAD_TYPE_ADVERT 0x04 // a node advertising its Identity + #define PAYLOAD_TYPE_GRP_TXT 0x05 // an (unverified) group text message (prefixed with channel hash, MAC) (enc data: timestamp, "name: msg") + #define PAYLOAD_TYPE_GRP_DATA 0x06 // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: timestamp, blob) + #define PAYLOAD_TYPE_ANON_REQ 0x07 // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...) + #define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra) + +[Source](https://discord.com/channels/1343693475589263471/1343693475589263474/1350611321040932966) + +### 4.9. Q: The T-Deck sound is too loud? +### 4.10. Q: Can you customize the sound? **A:** You can customise the sounds on the T-Deck, just by placing `.mp3` files onto the `root` dir of the SD card. `startup.mp3`, `alert.mp3` and `new-advert.mp3` -### Q: What is the 'Import from Clipboard' feature on the t-deck and is there a way to manually add nodes without having to receive adverts? +### 4.11. Q: What is the 'Import from Clipboard' feature on the t-deck and is there a way to manually add nodes without having to receive adverts? **A:** 'Import from Clipboard' is for importing a contact via a file named 'clipboard.txt' on the SD card. The opposite, is in the Identity screen, the 'Card to Clipboard' menu, which writes to 'clipboard.txt' so you can share yourself (call these 'biz cards', that start with "meshcore://...") --- -## General +## 5. General -### Q: What are BW, SF, and CR? +### 5.1. Q: What are BW, SF, and CR? **A:** @@ -237,74 +340,201 @@ Lowering the spreading factor makes it more difficult for the gateway to receive So it's balancing act between speed of the transmission and resistance to noise. things network is mainly focused on LoRaWAN, but the LoRa low-level stuff still checks out for any LoRa project -### Q: What happens when a node learns a route via a mobile repeater, and that repeater is gone? +### 5.2. Q: Do MeshCore clients repeat? +**A:** No, MeshCore clients do not repeat. This is the core of MeshCore's messaging-first design. This is to avoid devices flooding the air ware and create endless collisions so messages sent aren't received. +In MeshCore, only repeaters and room server with '`set repeat on` repeat. + +### 5.3. Q: What happens when a node learns a route via a mobile repeater, and that repeater is gone? **A:** If you used to reach a node through a repeater and the repeater is no longer reachable, the client will send the message using the existing (but now broken) known path, the message will fail after 3 retries, and the app will reset the path and send the message as flood on the last retry by default. This can be turned off in settings. If the destination is reachable directly or through another repeater, the new path will be used going forward. Or you can set the path manually if you know a specific repeater to use to reach that destination. In the case if users are moving around frequently, and the paths are breaking, they just see the phone client retries and revert to flood to attempt to reestablish a path. +### 5.4. Q: How does a node discovery a path to its destination and then use it to send messages in the future, instead of flooding every message it sends like Meshtastic? -### Q: Is MeshCore open source? +Routes are stored in sender's contact list. When you send a message the first time, the message first gets to your destination by flood routing, When your destination node gets the message, it sends back to the sender a delivery report with all repeaters that the original message went through. This delivery report is flood-routed back to you the sender and is a basis for future direct path. when you send the next message, the path will get embedded into the packet and be evaluated by repeaters. if the hop and address of the repeater matches, it will retransmit the message, otherwise it will not retransmit, hence minimizing utilization. + +[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1351279141630119996) + +### 5.5. Q: Do public channels always flood? Do private channels always flood? + +**A:** Yes, group channels are A to B, so there is no defined path. They have to flood. Repeaters can however deny flood traffic up to some hop limit, with the `set flood.max` CLI command. Admistrators of repeaters get to set the rules of their repeaters. + +[Source](https://discord.com/channels/1343693475589263471/1343693475589263474/1350023009527664672) + + +### 5.6. Q: what is the public key for the default public channel? +**A:** The smartphone app key is in hex: +` 8b3387e9c5cdea6ac9e5edbaa115cd72` + +T-Deck uses the same key but in base64 +`izOH6cXN6mrJ5e26oRXNcg==` +The third character is the capital letter 'O', not zero `0` +[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1354194409213792388) + +### 5.7. Q: Is MeshCore open source? **A:** Most of the firmware is freely available. Everything is open source except the T-Deck firmware and Liam's native mobile apps. - Firmware repo: -### Q: How can I support MeshCore? +### 5.8. Q: How can I support MeshCore? **A:** Provide your honest feedback on GitHub and on AndyKirby's Discord server . Spread the word of MeshCore to your friends and communities; help them get started with MeshCore. Support Scott's MeshCore development at . Support Liam Cottle's smartphone client development by unlocking the server administration wait gate with in-app purchase Support Rastislav Vysoky (recrof)'s flasher web site and the map web site development through [PayPal](https://www.paypal.com/donate/?business=DREHF5HM265ES&no_recurring=0&item_name=If+you+enjoy+my+work%2C+you+can+support+me+here%3A¤cy_code=EUR) or [Revolut](https://revolut.me/recrof) -### Q: How do I build MeshCore firmware from source? +### 5.9. Q: How do I build MeshCore firmware from source? **A:** See instructions here: - +https://discord.com/channels/826570251612323860/1330643963501351004/1341826372120608769 + +Build instructions for MeshCore: + +For Windows, first install WSL and Python+pip via: https://plainenglish.io/blog/setting-up-python-on-windows-subsystem-for-linux-wsl-26510f1b2d80 + +(Linux, Windows+WSL) In the terminal/shell: +``` +sudo apt update +sudo apt install libpython3-dev +sudo apt install python3-venv +``` +Mac: python3 should be already installed. + +Then it should be the same for all platforms: +``` +python3 -m venv meshcore +cd meshcore && source bin/activate +pip install -U platformio +git clone https://github.com/ripplebiz/MeshCore.git +cd MeshCore +``` +open platformio.ini and in `[arduino_base]` edit the `LORA_FREQ=867.5` +save, then run: +``` +pio run -e RAK_4631_Repeater +``` +then you'll find `firmware.zip` in `.pio/build/RAK_4631_Repeater` Andy also has a video on how to build using VS Code: *How to build and flash Meshcore repeater firmware | Heltec V3* *(Link referenced in the Discord post)* -### Q: Are there other MeshCore related open source projects? +### 5.10. Q: Are there other MeshCore related open source projects? **A:** [Liam Cottle](https://liamcottle.net)'s MeshCore web client and MeshCore Javascript libary are open source under MIT license. Web client: https://github.com/liamcottle/meshcore-web Javascript: https://github.com/liamcottle/meshcore.js -### Q: Does MeshCore support ATAK +### 5.11. Q: Does MeshCore support ATAK **A:** ATAK is not currently on MeshCore's roadmap. -### Q: How do I add a node to the [MeshCore Map]([url](https://meshcore.co.uk/map.html)) +Meshcore would not be best suited to ATAK because MeshCore: +clients do not repeat and therefore you would need a network of repeaters in place +will not have a stable path where all clients are constantly moving between repeaters + +MeshCore clients would need to reset path constantly and flood traffic across the network which could lead to lots of collisions with something as chatty as ATAK. + +This could change in the future if MeshCore develops a client firmware that repeats. +[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1354780032140054659) + +### 5.12. Q: How do I add a node to the [MeshCore Map]([url](https://meshcore.co.uk/map.html)) **A:** From the smartphone app, connect to a BLE Companion radio - To add the BLE Companion radio your smartphone is connected to to the map, tap the `advert` icon, then tap `Advert (To Clipboard)`. - To add a Repeater or Room Server to the map, tap the 3 dots next to the Repeater or Room Server you want to add to the map, then tap `Share (To Clipboard)`. - Go to the [MeshCore Map web site]([url](https://meshcore.co.uk/map.html)), tap the plus sign on the lower right corner and paste in the meshcore://... blob, then tap `Add Node` - + +### 5.13. Q: Can I use a Raspberry Pi to update a MeshCore radio? +** A:** Yes. +You will need to install picocom on the pi. +`sudo apt install picocom` + +Then run the following commands to setup the repeater. +``` +picocom -b 115200 /dev/ttyUSB0 --imap lfcrlf +set name your_repeater_name +time epoch_time +password your_unique_password +set advert.interval 240 +advert +``` +Note: If using a RAK the path will most likely be /dev/ttyACM0 + +Epoch time comes from https://www.epochconverter.com/ + +You can also flash the repeater using esptool. You will need to install esptool with the following command... + +`pip install esptool --break-system-packages` + +Then to flash the firmware to Heltec, obtain the .bin file from https://flasher.meshcore.co.uk/ (download all firmware link) + +For Heltec: +`esptool.py -p /dev/ttyUSB0 --chip esp32-s3 write_flash 0x00000 firmware.bin` + +If flashing a visual studio code build bin file, flash with the following offset: +`esptool.py -p /dev/ttyUSB0 --chip esp32-s3 write_flash 0x10000 firmware.bin` + +For Pi +Download the zip from the online flasher website and use the following command: + +Note: Requires adafruit-nrfutil command which can be installed as follows. +`pip install adafruit-nrfutil --break-system-packages` + +``` +adafruit-nrfutil --verbose dfu serial --package t1000_e_bootloader-0.9.1-5-g488711a_s140_7.3.0.zip -p /dev/ttyACM0 -b 115200 --singlebank --touch 1200 +``` + +[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1342120825251299388) + +### 5.14. Q: Are there are projects built around MeshCore? + +**A:** Yes. See the following: + +#### 5.14.1. meshcoremqtt +A python based script to send meshore debug and packet capture data to MQTT for analysis +https://github.com/Andrew-a-g/meshcoretomqtt + +#### 5.14.2. MeshCore for Home Assistant +A custom Home Assistant integration for MeshCore mesh radio nodes. It allows you to monitor and control MeshCore nodes via USB, BLE, or TCP connections. +https://github.com/awolden/meshcore-ha + +#### 5.14.3. Python MeshCore +Bindings to access your MeshCore companion radio nodes in python. +https://github.com/fdlamotte/meshcore_py + +#### 5.14.4. meshcore-cli +CLI interface to MeshCore companion radio over BLE, TCP, or serial. Uses Pyton MeshCore above. + https://github.com/fdlamotte/meshcore-cli + +#### 5.14.5. meshcore.js +A Javascript library for interacting with a MeshCore device running the companion radio firmware +https://github.com/liamcottle/meshcore.js + --- -## Troubleshooting +## 6. Troubleshooting -### Q: My client says another client or a repeater or a room server was last seen many, many days ago. -### Q: A repeater or a client or a room server I expect to see on my discover list (on T-Deck) or contact list (on a smart device client) are not listed. +### 6.1. Q: My client says another client or a repeater or a room server was last seen many, many days ago. +### 6.2. Q: A repeater or a client or a room server I expect to see on my discover list (on T-Deck) or contact list (on a smart device client) are not listed. **A:** - If your client is a T-Deck, it may not have its time set (no GPS installed, no GPS lock, or wrong GPS baud rate). - If you are using the Android or iOS client, the other client, repeater, or room server may have the wrong time. You can get the epoch time on and use it to set your T-Deck clock. For a repeater and room server, the admin can use a T-Deck to remotely set their clock (clock sync), or use the `time` command in the USB serial console with the server device connected. -### Q: How to connect to a repeater via BLE (bluetooth)? +### 6.3. Q: How to connect to a repeater via BLE (bluetooth)? **A:** You can't connect to a device running repeater firmware via bluetooth. Devices running the BLE companion firmware you can connect to it via bluetooth using the android app -### Q: I can't connect via bluetooth, what is the bluetooth pairing code? +### 6.4. Q: I can't connect via bluetooth, what is the bluetooth pairing code? **A:** the default bluetooth pairing code is `123456` -### Q: My Heltec V3 keeps disconnecting from my smartphone. It can't hold a solid Bluetooth connection. +### 6.5. Q: My Heltec V3 keeps disconnecting from my smartphone. It can't hold a solid Bluetooth connection. **A:** Heltec V3 has a very small coil antenna on its PCB for WiFi and Bluetooth connectivty. It has a very short range, only a few feet. It is possible to remove the coil antenna and replace it with a 31mm wire. The BT range is much improved with the modification. --- -## Other Questions: -### Q: How to Update repeater and room server firmware over the air? +## 7. Other Questions: +### 7.1. Q: How to Update repeater and room server firmware over the air? **A:** Only nRF-based RAK4631 and Heltec T114 OTA firmware update are verified using nRF smartphone app. Lilygo T-Echo doesn't work currently. You can update repeater and room server firmware with a bluetooth connection between your smartphone and your LoRa radio using the nRF app. @@ -313,7 +543,8 @@ You can update repeater and room server firmware with a bluetooth connection bet 2. On the phone client, log on to the repeater as administrator (default password is `password`) to issue the `start ota`command to the repeater or room server to get the device into OTA DFU mode ![image](https://github.com/user-attachments/assets/889bb81b-7214-4a1c-955a-396b5a05d8ad) - 1. `start ota` can be initiated from USB serial console on the web flasher page or a T-Deck + +1. `start ota` can be initiated from USB serial console on the web flasher page or a T-Deck 4. On the smartphone, download and run the nRF app and scan for Bluetooth devices 5. Connect to the repeater/room server node you want to update 1. nRF app is available on both Android and iOS @@ -322,7 +553,8 @@ You can update repeater and room server firmware with a bluetooth connection bet **iOS continues here:** 5. Once connected successfully, a `DFU` icon ![Pasted image 20250309173039](https://github.com/user-attachments/assets/af7a9f78-8739-4946-b734-02bade9c8e71) - appears in the top right corner of the app![Pasted image 20250309171919](https://github.com/user-attachments/assets/08007ec8-4924-49c1-989f-ca2611e78793) + appears in the top right corner of the app + ![Pasted image 20250309171919](https://github.com/user-attachments/assets/08007ec8-4924-49c1-989f-ca2611e78793) 6. Scroll down to change the `PRN(s)` number: @@ -372,4 +604,4 @@ You can update repeater and room server firmware with a bluetooth connection bet --- - \ No newline at end of file + From 436a99f088178bcb8ccc6cc5ad42bd55d2497527 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 17 May 2025 19:54:31 +1000 Subject: [PATCH 102/154] * BLE_WRITE_MIN_INTERVAL upped to 60 millis --- src/helpers/esp32/SerialBLEInterface.cpp | 2 +- src/helpers/nrf52/SerialBLEInterface.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers/esp32/SerialBLEInterface.cpp b/src/helpers/esp32/SerialBLEInterface.cpp index eb98b58b..8a8710a7 100644 --- a/src/helpers/esp32/SerialBLEInterface.cpp +++ b/src/helpers/esp32/SerialBLEInterface.cpp @@ -169,7 +169,7 @@ size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) { return 0; } -#define BLE_WRITE_MIN_INTERVAL 20 +#define BLE_WRITE_MIN_INTERVAL 60 bool SerialBLEInterface::isWriteBusy() const { return millis() < _last_write + BLE_WRITE_MIN_INTERVAL; // still too soon to start another write? diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index 61b570eb..f43a3767 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -94,7 +94,7 @@ size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) { return 0; } -#define BLE_WRITE_MIN_INTERVAL 20 +#define BLE_WRITE_MIN_INTERVAL 60 bool SerialBLEInterface::isWriteBusy() const { return millis() < _last_write + BLE_WRITE_MIN_INTERVAL; // still too soon to start another write? From 65d398fcbc099add915bff83fdfb6c400c520399 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 17 May 2025 20:04:55 +1000 Subject: [PATCH 103/154] * ver bump to v1.6.1 --- examples/companion_radio/main.cpp | 4 ++-- examples/simple_repeater/main.cpp | 4 ++-- examples/simple_room_server/main.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 7d522b08..23d71438 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -102,11 +102,11 @@ static uint32_t _atoi(const char* sp) { #define FIRMWARE_VER_CODE 5 #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "9 May 2025" + #define FIRMWARE_BUILD_DATE "17 May 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.6.0" + #define FIRMWARE_VERSION "v1.6.1" #endif #define CMD_APP_START 1 diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index d272abb8..7b38f5c3 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -22,11 +22,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "9 May 2025" + #define FIRMWARE_BUILD_DATE "17 May 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.6.0" + #define FIRMWARE_VERSION "v1.6.1" #endif #ifndef LORA_FREQ diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 9849ba25..c041c621 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -22,11 +22,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "9 May 2025" + #define FIRMWARE_BUILD_DATE "17 May 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.6.0" + #define FIRMWARE_VERSION "v1.6.1" #endif #ifndef LORA_FREQ From 2f5cc94d0488be2438068d60f754f21f6ca39410 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sun, 18 May 2025 02:18:32 +1200 Subject: [PATCH 104/154] add info about flasher and clients --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 1413a4b4..f655d149 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,36 @@ Monitor & Communicate using the Serial Monitor (e.g., Serial USB Terminal on And * 📡 Companion Radio: For use with an external chat app, over BLE or USB. * 📡 Room Server: A simple BBS server for shared Posts. +## ⚡️ MeshCore Flasher + +We have prebuilt firmware ready to flash on supported devices. + +- Launch https://flasher.meshcore.co.uk +- Select a supported device +- Flash one of the firmware types: + - Companion, Repeater or Room Server +- Once flashing is complete, you can connect with one of the MeshCore clients below. + +## 📱 MeshCore Clients + +**Companion Firmware** + +The companion firmware can be connected to via BLE, USB or WiFi depending on the firmware type you flashed. + +- Web: https://app.meshcore.nz +- Android: https://play.google.com/store/apps/details?id=com.liamcottle.meshcore.android +- iOS: https://apps.apple.com/us/app/meshcore/id6742354151?platform=iphone +- NodeJS: https://github.com/liamcottle/meshcore.js +- Python: https://github.com/fdlamotte/meshcore-cli + +**Repeater and Room Server Firmware** + +The repeater and room server firmwares can be setup via USB in the web config tool. + +- https://config.meshcore.dev + +They can also be managed via LoRa in the mobile app by using the Remote Management feature. + ## 🛠 Hardware Compatibility MeshCore is designed for use with: From aa272ecc0c3f9c952d20e0ab4dfe81d36987d3cf Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sun, 18 May 2025 02:26:53 +1200 Subject: [PATCH 105/154] adjust getting started info --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f655d149..d98499f0 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,8 @@ MeshCore provides the ability to create wireless mesh networks, similar to Mesht ## 🚀 How to Get Started -Andy Kirby has published a very useful [intro video](https://www.youtube.com/watch?v=t1qne8uJBAc) which explains the steps for beginners. +- Watch the [MeshCore Intro Video](https://www.youtube.com/watch?v=t1qne8uJBAc) by Andy Kirby. +- Read through our [Frequently Asked Questions](./docs/faq.md) section. For developers, install [PlatformIO](https://docs.platformio.org) in Visual Studio Code. Download & Open the MeshCore repository. From bb5650a998aba1151c82d161b3a50d9c86532002 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sun, 18 May 2025 02:47:09 +1200 Subject: [PATCH 106/154] update how to get started --- README.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d98499f0..156dbbab 100644 --- a/README.md +++ b/README.md @@ -27,17 +27,20 @@ MeshCore provides the ability to create wireless mesh networks, similar to Mesht - Watch the [MeshCore Intro Video](https://www.youtube.com/watch?v=t1qne8uJBAc) by Andy Kirby. - Read through our [Frequently Asked Questions](./docs/faq.md) section. +- Flash the MeshCore firmware on a supported device. +- Connect with a supported client. -For developers, install [PlatformIO](https://docs.platformio.org) in Visual Studio Code. -Download & Open the MeshCore repository. -Select a Sample Application: Choose from chat, repeater, other example app. -Monitor & Communicate using the Serial Monitor (e.g., Serial USB Terminal on Android). +For developers; -📁 Included Example Applications -* 📡 Terminal Chat: Secure text communication between devices. -* 📡 Simple Repeater: Extends network coverage by relaying messages. -* 📡 Companion Radio: For use with an external chat app, over BLE or USB. -* 📡 Room Server: A simple BBS server for shared Posts. +- Install [PlatformIO](https://docs.platformio.org) in [Visual Studio Code](https://code.visualstudio.com). +- Clone and open the MeshCore repository in Visual Studio Code. +- See the example applications you can modify and run: + - [Companion Radio](./examples/companion_radio) - For use with an external chat app, over BLE, USB or WiFi. + - [Simple Repeater](./examples/simple_repeater) - Extends network coverage by relaying messages. + - [Simple Room Server](./examples/simple_room_server) - A simple BBS server for shared Posts. + - [Simple Secure Chat](./examples/simple_secure_chat) - Secure terminal based text communication between devices. + +The Simple Secure Chat example can be interacted with through the Serial Monitor in Visual Studio Code, or with a Serial USB Terminal on Android. ## ⚡️ MeshCore Flasher From 69a70c4f71e8e71d71c382a3e5374cc5bddb9dd7 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sun, 18 May 2025 02:53:05 +1200 Subject: [PATCH 107/154] update get support --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 156dbbab..740704aa 100644 --- a/README.md +++ b/README.md @@ -96,9 +96,9 @@ For minor changes just submit your PR and I'll try to review it, but for anythin ## 📞 Get Support -Check out the GitHub Issues page to report bugs or request features. - -You will be able to find additional guides and components at [my site](https://buymeacoffee.com/ripplebiz), or [join Andy Kirby's Discord](https://discord.gg/GBxVx2JMAy) for discussions. +- Report bugs and request features on the [GitHub Issues](https://github.com/ripplebiz/MeshCore/issues) page. +- Find additional guides and components on [my site](https://buymeacoffee.com/ripplebiz). +- Join [Andy Kirby's Discord](https://discord.gg/GBxVx2JMAy) to chat with the developers and get help from the community. ## RAK Wireless Board Support in PlatformIO From 86d1c807042f805031b8e8a8a23284a55bfe5a33 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sun, 18 May 2025 02:54:53 +1200 Subject: [PATCH 108/154] fix formatting --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 740704aa..b4943dea 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ MeshCore is designed for use with: * LilyGo TLora32 v1.6 ## 📜 License + MeshCore is open-source software released under the MIT License. You are free to use, modify, and distribute it for personal and commercial projects. ## Contributing From 7e14fb3f65902be0ffda1aec143b356e7218e6bf Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Thu, 15 May 2025 11:15:55 -0700 Subject: [PATCH 109/154] Initial support for nano g2 ultra not yet implemented are GPS and external notification LED and buzzer --- boards/nano-g2-ultra.json | 72 ++++++++++ variants/nano_g2_ultra/nano-g2.cpp | 103 ++++++++++++++ variants/nano_g2_ultra/nano-g2.h | 57 ++++++++ variants/nano_g2_ultra/platformio.ini | 57 ++++++++ variants/nano_g2_ultra/target.cpp | 76 +++++++++++ variants/nano_g2_ultra/target.h | 20 +++ variants/nano_g2_ultra/variant.cpp | 36 +++++ variants/nano_g2_ultra/variant.h | 189 ++++++++++++++++++++++++++ 8 files changed, 610 insertions(+) create mode 100644 boards/nano-g2-ultra.json create mode 100644 variants/nano_g2_ultra/nano-g2.cpp create mode 100644 variants/nano_g2_ultra/nano-g2.h create mode 100644 variants/nano_g2_ultra/platformio.ini create mode 100644 variants/nano_g2_ultra/target.cpp create mode 100644 variants/nano_g2_ultra/target.h create mode 100644 variants/nano_g2_ultra/variant.cpp create mode 100644 variants/nano_g2_ultra/variant.h diff --git a/boards/nano-g2-ultra.json b/boards/nano-g2-ultra.json new file mode 100644 index 00000000..11e7ebaa --- /dev/null +++ b/boards/nano-g2-ultra.json @@ -0,0 +1,72 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x8029" + ], + [ + "0x239A", + "0x0029" + ], + [ + "0x239A", + "0x002A" + ], + [ + "0x239A", + "0x802A" + ] + ], + "usb_product": "BQ nRF52840", + "mcu": "nrf52840", + "variant": "nano-g2-ultra", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": [ + "bluetooth" + ], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd" + }, + "frameworks": [ + "arduino" + ], + "name": "BQ nRF52840", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://wiki.uniteng.com/en/meshtastic/nano-g2-ultra", + "vendor": "BQ Consulting" +} \ No newline at end of file diff --git a/variants/nano_g2_ultra/nano-g2.cpp b/variants/nano_g2_ultra/nano-g2.cpp new file mode 100644 index 00000000..731fa873 --- /dev/null +++ b/variants/nano_g2_ultra/nano-g2.cpp @@ -0,0 +1,103 @@ +#include +#include "nano-g2.h" + +#ifdef NANO_G2_ULTRA + +#include +#include + +static BLEDfu bledfu; + +static void connect_callback(uint16_t conn_handle) +{ + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) +{ + (void)conn_handle; + (void)reason; + + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + +void NanoG2Ultra::begin() +{ + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + + pinMode(PIN_BUTTON1, INPUT); + // the external notification circuit is shared for both buzzer and led + // need to find out the switch state or somehow write a function that can + // sound the buzzer or signal the led. the led will stay on once brought HIGH + // and can be then brought LOW. It turns off with a hardware btn. + pinMode(EXT_NOTIFY_OUT, OUTPUT); + digitalWrite(EXT_NOTIFY_OUT, LOW); + + Wire.begin(); + pinMode(SX126X_POWER_EN, OUTPUT); + digitalWrite(SX126X_POWER_EN, HIGH); + + delay(10); +} + +uint16_t NanoG2Ultra::getBattMilliVolts() +{ + int adcvalue = 0; + + analogReference(AR_INTERNAL_3_0); + analogReadResolution(12); + delay(10); + + // ADC range is 0..3000mV and resolution is 12-bit (0..4095) + adcvalue = analogRead(PIN_VBAT_READ); + // Convert the raw value to compensated mv, taking the resistor- + // divider into account (providing the actual LIPO voltage) + return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB); +} + +bool NanoG2Ultra::startOTAUpdate(const char *id, char reply[]) +{ + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 + Bluefruit.setTxPower(4); + // Set the BLE device name + Bluefruit.setName("TECHO_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // To be consistent OTA DFU should be added first if it exists + bledfu.begin(); + + // Set up and start advertising + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addName(); + + /* Start Advertising + - Enable auto advertising if disconnected + - Interval: fast mode = 20 ms, slow mode = 152.5 ms + - Timeout for fast mode is 30 seconds + - Start(timeout) with timeout = 0 will advertise forever (until connected) + + For recommended advertising interval + https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + + strcpy(reply, "OK - started"); + return true; +} +#endif diff --git a/variants/nano_g2_ultra/nano-g2.h b/variants/nano_g2_ultra/nano-g2.h new file mode 100644 index 00000000..99dc75fa --- /dev/null +++ b/variants/nano_g2_ultra/nano-g2.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +// LoRa radio module pins +#define P_LORA_DIO_1 (32 + 10) +#define P_LORA_NSS (32 + 13) +#define P_LORA_RESET (32 + 15) +#define P_LORA_BUSY (32 + 11) +#define P_LORA_SCLK (0 + 12) +#define P_LORA_MISO (32 + 9) +#define P_LORA_MOSI (0 + 11) + +#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define SX126X_POWER_EN 37 + +// buttons +#define PIN_BUTTON1 (32 + 6) +#define BUTTON_PIN PIN_BUTTON1 +#define PIN_USER_BTN BUTTON_PIN + +// built-ins +#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 + +#define VBAT_DIVIDER (0.5F) // 150K + 150K voltage divider on VBAT, actually 100K + 100K +#define VBAT_DIVIDER_COMP (2.0F) // Compensation factor for the VBAT divider + +#define PIN_VBAT_READ (0 + 2) +#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) + +class NanoG2Ultra : public mesh::MainBoard +{ +protected: + uint8_t startup_reason; + +public: + void begin(); + uint16_t getBattMilliVolts() override; + bool startOTAUpdate(const char *id, char reply[]) override; + + uint8_t getStartupReason() const override + { + return startup_reason; + } + + const char *getManufacturerName() const override + { + return "Nano G2 Ultra"; + } + + void reboot() override + { + NVIC_SystemReset(); + } +}; diff --git a/variants/nano_g2_ultra/platformio.ini b/variants/nano_g2_ultra/platformio.ini new file mode 100644 index 00000000..e4a25423 --- /dev/null +++ b/variants/nano_g2_ultra/platformio.ini @@ -0,0 +1,57 @@ +[nrf52840_g2_ultra] +extends = nrf52_base +platform_packages = framework-arduinoadafruitnrf52 +build_flags = ${nrf52_base.build_flags} + -I src/helpers/nrf52 + -I lib/nrf52/s140_nrf52_6.1.1_API/include + -I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52 +lib_deps = + ${nrf52_base.lib_deps} + rweather/Crypto @ ^0.4.0 + lewisxhe/PCF8563_Library@^1.0.1 + +[Nano_G2_Ultra] +extends = nrf52840_g2_ultra +board = nano-g2-ultra +board_build.ldscript = boards/nrf52840_s140_v6.ld +build_flags = ${nrf52840_g2_ultra.build_flags} + -I variants/nano_g2_ultra + -D NANO_G2_ULTRA + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_USER_BTN=38 +build_src_filter = ${nrf52840_g2_ultra.build_src_filter} + + + +<../variants/nano_g2_ultra> +debug_tool = jlink +upload_protocol = nrfutil + +[env:Nano_G2_Ultra_companion_radio_ble] +extends = Nano_G2_Ultra +build_flags = + ${Nano_G2_Ultra.build_flags} + -I src/helpers/ui + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=0 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + -D SH1106 + -D DISPLAY_CLASS=SH1106Display +; -D ENABLE_PRIVATE_KEY_IMPORT=1 +; -D ENABLE_PRIVATE_KEY_EXPORT=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Nano_G2_Ultra.build_src_filter} + + + + + +<../examples/companion_radio> +lib_deps = + ${Nano_G2_Ultra.lib_deps} + densaugeo/base64 @ ~1.4.0 + adafruit/Adafruit SH110X @ ~2.1.13 + adafruit/Adafruit GFX Library @ ^1.12.1 + diff --git a/variants/nano_g2_ultra/target.cpp b/variants/nano_g2_ultra/target.cpp new file mode 100644 index 00000000..e3f2eb7e --- /dev/null +++ b/variants/nano_g2_ultra/target.cpp @@ -0,0 +1,76 @@ +#include +#include "target.h" +#include + +NanoG2Ultra board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +#ifndef LORA_CR +#define LORA_CR 5 +#endif + +bool radio_init() +{ + rtc_clock.begin(Wire); + +#ifdef SX126X_DIO3_TCXO_VOLTAGE + float tcxo = SX126X_DIO3_TCXO_VOLTAGE; +#else + float tcxo = 1.6f; +#endif + + SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); + SPI.begin(); + 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); + +#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 +} diff --git a/variants/nano_g2_ultra/target.h b/variants/nano_g2_ultra/target.h new file mode 100644 index 00000000..e8f3974a --- /dev/null +++ b/variants/nano_g2_ultra/target.h @@ -0,0 +1,20 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include "nano-g2.h" +#include +#include +#include +#include + +extern NanoG2Ultra board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock 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(); diff --git a/variants/nano_g2_ultra/variant.cpp b/variants/nano_g2_ultra/variant.cpp new file mode 100644 index 00000000..4ad554cd --- /dev/null +++ b/variants/nano_g2_ultra/variant.cpp @@ -0,0 +1,36 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 - pins 0 and 1 are hardwired for xtal and should never be enabled + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // Nothing need to be inited for now +} \ No newline at end of file diff --git a/variants/nano_g2_ultra/variant.h b/variants/nano_g2_ultra/variant.h new file mode 100644 index 00000000..b0cc3100 --- /dev/null +++ b/variants/nano_g2_ultra/variant.h @@ -0,0 +1,189 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_Nano_G2_ +#define _VARIANT_Nano_G2_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +// #define USE_LFRC // Board uses 32khz RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (-1) +#define PIN_LED2 (-1) +#define PIN_LED3 (-1) + +#define LED_RED PIN_LED3 +#define LED_BLUE PIN_LED1 +#define LED_GREEN PIN_LED2 + +#define LED_BUILTIN LED_BLUE +#define LED_CONN PIN_GREEN + +#define LED_STATE_ON 0 // State when LED is lit + +/* + * Buttons + */ +#define PIN_BUTTON1 (32 + 6) + +#define EXT_NOTIFY_OUT (0 + 4) // Default pin to use for Ext Notify Module. + +/* + * Analog pins + */ +#define PIN_A4 (0 + 2) // Battery ADC + +#define BATTERY_PIN PIN_A4 + + static const uint8_t A4 = PIN_A4; + +#define ADC_RESOLUTION 14 + +/* + * Serial interfaces + */ +#define PIN_SERIAL2_RX (0 + 22) +#define PIN_SERIAL2_TX (0 + 20) + +/** + Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (0 + 17) +#define PIN_WIRE_SCL (0 + 15) + +#define PIN_RTC_INT (0 + 14) // Interrupt from the PCF8563 RTC + +/* +External serial flash W25Q16JV_IQ +*/ + +// QSPI Pins +#define PIN_QSPI_SCK (0 + 8) +#define PIN_QSPI_CS (32 + 7) +#define PIN_QSPI_IO0 (0 + 6) // MOSI if using two bit interface +#define PIN_QSPI_IO1 (0 + 26) // MISO if using two bit interface +#define PIN_QSPI_IO2 (32 + 4) // WP if using two bit interface (i.e. not used) +#define PIN_QSPI_IO3 (32 + 2) // HOLD if using two bit interface (i.e. not used) + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES W25Q16JV_IQ +#define EXTERNAL_FLASH_USE_QSPI + + /* + * Lora radio + */ + +#define USE_SX1262 +#define SX126X_CS (32 + 13) // FIXME - we really should define LORA_CS instead +#define SX126X_DIO1 (32 + 10) +// Note DIO2 is attached internally to the module to an analog switch for TX/RX switching +// #define SX1262_DIO3 (0 + 21) +// This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the main CPU? +#define SX126X_BUSY (32 + 11) +#define SX126X_RESET (32 + 15) +// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + + // #define LORA_DISABLE_SENDING // Define this to disable transmission for testing (power testing etc...) + + // #undef SX126X_CS + + /* + * GPS pins + */ + +#define GPS_L76K + +#define PIN_GPS_STANDBY (0 + 13) // An output to wake GPS, low means allow sleep, high means force wake STANDBY +#define PIN_GPS_TX (0 + 9) // This is for bits going TOWARDS the CPU +#define PIN_GPS_RX (0 + 10) // This is for bits going TOWARDS the GPS + + // #define GPS_THREAD_INTERVAL 50 + +#define PIN_SERIAL1_RX PIN_GPS_TX +#define PIN_SERIAL1_TX PIN_GPS_RX + +// PCF8563 RTC Module +#define PCF8563_RTC 0x51 + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 1 + +// For LORA, spi 0 +#define PIN_SPI_MISO (32 + 9) +#define PIN_SPI_MOSI (0 + 11) +#define PIN_SPI_SCK (0 + 12) + +// #define PIN_PWR_EN (0 + 6) + +// To debug via the segger JLINK console rather than the CDC-ACM serial device +// #define USE_SEGGER + +// Battery +// The battery sense is hooked to pin A0 (2) +// it is defined in the anlaolgue pin section of this file +// and has 12 bit resolution +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER (2.0F) + +#define HAS_RTC 1 + +/** + OLED Screen Model + */ +#define ARDUINO_ARCH_AVR +#define USE_SH1107_128_64 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif \ No newline at end of file From ee41d6e2d34c53904fc12ae46916b71b2fe75180 Mon Sep 17 00:00:00 2001 From: cod3doomy Date: Sat, 17 May 2025 22:01:13 -0700 Subject: [PATCH 110/154] t-beam supreme: PMU and i2c fixes Fixed i2c (Wire) init issue by defining pins in platformio Added an i2c scanning function for debug Corrected the pmu power up sequence --- src/helpers/TBeamS3SupremeBoard.h | 5 +- .../platformio.ini | 2 + .../lilygo_tbeam_supreme_SX1262/target.cpp | 152 +++++++++++++----- 3 files changed, 117 insertions(+), 42 deletions(-) diff --git a/src/helpers/TBeamS3SupremeBoard.h b/src/helpers/TBeamS3SupremeBoard.h index 5a8070b9..74d9ca2e 100644 --- a/src/helpers/TBeamS3SupremeBoard.h +++ b/src/helpers/TBeamS3SupremeBoard.h @@ -15,8 +15,8 @@ #define P_LORA_MISO 13 //SX1262 MISO pin #define P_LORA_MOSI 11 //SX1262 MOSI pin -#define PIN_BOARD_SDA 17 //SDA for OLED, BME280, and QMC6310U (0x1C) -#define PIN_BOARD_SCL 18 //SCL for OLED, BME280, and QMC6310U (0x1C) +//#define PIN_BOARD_SDA 17 //SDA for OLED, BME280, and QMC6310U (0x1C) +//#define PIN_BOARD_SCL 18 //SCL for OLED, BME280, and QMC6310U (0x1C) #define PIN_BOARD_SDA1 42 //SDA for PMU and PFC8563 (RTC) #define PIN_BOARD_SCL1 41 //SCL for PMU and PFC8563 (RTC) @@ -58,6 +58,7 @@ public: void printPMU(); #endif bool power_init(); + void begin() { power_init(); diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index abcd89bc..d3447673 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -6,6 +6,8 @@ build_flags = -I variants/lilygo_tbeam_supreme_SX1262 -D LORA_TX_POWER=22 -D P_LORA_TX_LED=6 + -D PIN_BOARD_SDA=17 + -D PIN_BOARD_SCL=18 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper ;-D DISPLAY_CLASS=SSD1306Display ;Needs to be modified for SH1106 diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.cpp b/variants/lilygo_tbeam_supreme_SX1262/target.cpp index 3808e5fd..913c6a37 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.cpp +++ b/variants/lilygo_tbeam_supreme_SX1262/target.cpp @@ -27,6 +27,69 @@ TbeamSupSensorManager sensors = TbeamSupSensorManager(nmea); static void setPMUIntFlag(){ pmuIntFlag = true; } + +uint32_t deviceOnline = 0x00; + +void scanDevices(TwoWire *w) +{ + uint8_t err, addr; + int nDevices = 0; + uint32_t start = 0; + + Serial.println("Scanning I2C for Devices"); + for (addr = 1; addr < 127; addr++) { + start = millis(); + w->beginTransmission(addr); delay(2); + err = w->endTransmission(); + if (err == 0) { + nDevices++; + switch (addr) { + case 0x77: + case 0x76: + Serial.println("\tFound BMX280 Sensor"); + deviceOnline |= BME280_ONLINE; + break; + case 0x34: + Serial.println("\tFound AXP192/AXP2101 PMU"); + deviceOnline |= POWERMANAGE_ONLINE; + break; + case 0x3C: + Serial.println("\tFound SSD1306/SH1106 dispaly"); + deviceOnline |= DISPLAY_ONLINE; + break; + case 0x51: + Serial.println("\tFound PCF8563 RTC"); + deviceOnline |= PCF8563_ONLINE; + break; + case 0x1C: + Serial.println("\tFound QMC6310 MAG Sensor"); + deviceOnline |= QMC6310_ONLINE; + break; + default: + Serial.print("\tI2C device found at address 0x"); + if (addr < 16) { + Serial.print("0"); + } + Serial.print(addr, HEX); + Serial.println(" !"); + break; + } + + } else if (err == 4) { + Serial.print("Unknow error at address 0x"); + if (addr < 16) { + Serial.print("0"); + } + Serial.println(addr, HEX); + } + } + if (nDevices == 0) + Serial.println("No I2C devices found\n"); + + Serial.println("Scan for devices is complete."); + Serial.println("\n"); +} + #ifdef MESH_DEBUG void TBeamS3SupremeBoard::printPMU() { @@ -58,9 +121,9 @@ bool TBeamS3SupremeBoard::power_init() PMU.setChargingLedMode(XPOWERS_CHG_LED_CTRL_CHG); // Set up PMU interrupts - MESH_DEBUG_PRINTLN("Setting up PMU interrupts"); - pinMode(PIN_PMU_IRQ, INPUT_PULLUP); - attachInterrupt(PIN_PMU_IRQ, setPMUIntFlag, FALLING); + // MESH_DEBUG_PRINTLN("Setting up PMU interrupts"); + // pinMode(PIN_PMU_IRQ, INPUT_PULLUP); + // attachInterrupt(PIN_PMU_IRQ, setPMUIntFlag, FALLING); // GPS MESH_DEBUG_PRINTLN("Setting and enabling a-ldo4 for GPS"); @@ -73,74 +136,83 @@ bool TBeamS3SupremeBoard::power_init() PMU.enableALDO3(); // To avoid SPI bus issues during power up, reset OLED, sensor, and SD card supplies - MESH_DEBUG_PRINTLN("Reset a-ldo1&2 and b-ldo1"); - if (ESP_SLEEP_WAKEUP_UNDEFINED == esp_sleep_get_wakeup_cause()) - { - PMU.disableALDO1(); - PMU.disableALDO2(); - PMU.disableBLDO1(); - delay(250); - } + // MESH_DEBUG_PRINTLN("Reset a-ldo1&2 and b-ldo1"); + // if (ESP_SLEEP_WAKEUP_UNDEFINED == esp_sleep_get_wakeup_cause()) + // { + // PMU.disableALDO1(); + // PMU.disableALDO2(); + // PMU.disableBLDO1(); + // delay(250); + // } - // BME280 and OLED - MESH_DEBUG_PRINTLN("Setting and enabling a-ldo1 for oled"); - PMU.setALDO1Voltage(3300); - PMU.enableALDO1(); + // m.2 interface + MESH_DEBUG_PRINTLN("Setting and enabling dcdc3 for m.2 interface"); + PMU.setDC3Voltage(3300); // doesn't go anywhere in the schematic?? + PMU.enableDC3(); // QMC6310U MESH_DEBUG_PRINTLN("Setting and enabling a-ldo2 for QMC"); PMU.setALDO2Voltage(3300); PMU.enableALDO2(); // disable to save power + // BME280 and OLED + MESH_DEBUG_PRINTLN("Setting and enabling a-ldo1 for oled"); + PMU.setALDO1Voltage(3300); + PMU.enableALDO1(); + // SD card MESH_DEBUG_PRINTLN("Setting and enabling b-ldo2 for SD card"); PMU.setBLDO1Voltage(3300); PMU.enableBLDO1(); // Out to header pins - MESH_DEBUG_PRINTLN("Setting and enabling b-ldo2 for output to header"); - PMU.setBLDO2Voltage(3300); - PMU.enableBLDO2(); + // MESH_DEBUG_PRINTLN("Setting and enabling b-ldo2 for output to header"); + // PMU.setBLDO2Voltage(3300); + // PMU.enableBLDO2(); - MESH_DEBUG_PRINTLN("Setting and enabling dcdc4 for output to header"); - PMU.setDC4Voltage(XPOWERS_AXP2101_DCDC4_VOL2_MAX); // 1.8V - PMU.enableDC4(); + // MESH_DEBUG_PRINTLN("Setting and enabling dcdc4 for output to header"); + // PMU.setDC4Voltage(XPOWERS_AXP2101_DCDC4_VOL2_MAX); // 1.8V + // PMU.enableDC4(); - MESH_DEBUG_PRINTLN("Setting and enabling dcdc5 for output to header"); - PMU.setDC5Voltage(3300); - PMU.enableDC5(); - - // Other power rails - MESH_DEBUG_PRINTLN("Setting and enabling dcdc3 for ?"); - PMU.setDC3Voltage(3300); // doesn't go anywhere in the schematic?? - PMU.enableDC3(); + // MESH_DEBUG_PRINTLN("Setting and enabling dcdc5 for output to header"); + // PMU.setDC5Voltage(3300); + // PMU.enableDC5(); // Unused power rails MESH_DEBUG_PRINTLN("Disabling unused supplies dcdc2, dldo1 and dldo2"); PMU.disableDC2(); + PMU.disableDC5(); PMU.disableDLDO1(); PMU.disableDLDO2(); - // Set charge current to 300mA + PMU.disableIRQ(XPOWERS_AXP2101_ALL_IRQ); + + // Set charge current to 500mA MESH_DEBUG_PRINTLN("Setting battery charge current limit and voltage"); PMU.setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA); PMU.setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2); + PMU.clearIrqStatus(); + PMU.disableTSPinMeasure(); + // enable battery voltage measurement MESH_DEBUG_PRINTLN("Enabling battery measurement"); PMU.enableBattVoltageMeasure(); + PMU.enableVbusVoltageMeasure(); // Reset and re-enable PMU interrupts - MESH_DEBUG_PRINTLN("Re-enable interrupts"); - PMU.disableIRQ(XPOWERS_AXP2101_ALL_IRQ); - PMU.clearIrqStatus(); - PMU.enableIRQ( - XPOWERS_AXP2101_BAT_INSERT_IRQ | XPOWERS_AXP2101_BAT_REMOVE_IRQ | // Battery interrupts - XPOWERS_AXP2101_VBUS_INSERT_IRQ | XPOWERS_AXP2101_VBUS_REMOVE_IRQ | // VBUS interrupts - XPOWERS_AXP2101_PKEY_SHORT_IRQ | XPOWERS_AXP2101_PKEY_LONG_IRQ | // Power Key interrupts - XPOWERS_AXP2101_BAT_CHG_DONE_IRQ | XPOWERS_AXP2101_BAT_CHG_START_IRQ // Charging interrupts - ); + // MESH_DEBUG_PRINTLN("Re-enable interrupts"); + // PMU.disableIRQ(XPOWERS_AXP2101_ALL_IRQ); + // PMU.clearIrqStatus(); + // PMU.enableIRQ( + // XPOWERS_AXP2101_BAT_INSERT_IRQ | XPOWERS_AXP2101_BAT_REMOVE_IRQ | // Battery interrupts + // XPOWERS_AXP2101_VBUS_INSERT_IRQ | XPOWERS_AXP2101_VBUS_REMOVE_IRQ | // VBUS interrupts + // XPOWERS_AXP2101_PKEY_SHORT_IRQ | XPOWERS_AXP2101_PKEY_LONG_IRQ | // Power Key interrupts + // XPOWERS_AXP2101_BAT_CHG_DONE_IRQ | XPOWERS_AXP2101_BAT_CHG_START_IRQ // Charging interrupts + // ); #ifdef MESH_DEBUG + // scanDevices(&Wire); + // scanDevices(&Wire1); printPMU(); #endif @@ -217,7 +289,7 @@ static bool l76kProbe() bool radio_init() { fallback_clock.begin(); - Wire1.begin(PIN_BOARD_SDA1,PIN_BOARD_SCL1); + rtc_clock.begin(Wire1); #ifdef SX126X_DIO3_TCXO_VOLTAGE From b59606d5b5b89ef9618404c9a3255e484c70b96a Mon Sep 17 00:00:00 2001 From: Memo <58190287+memo-567@users.noreply.github.com> Date: Sun, 18 May 2025 06:14:08 +0000 Subject: [PATCH 111/154] Update variant.h --- variants/t114/variant.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/t114/variant.h b/variants/t114/variant.h index bceb1954..d4802341 100644 --- a/variants/t114/variant.h +++ b/variants/t114/variant.h @@ -72,7 +72,7 @@ #define LED_BLUE (-1) // No blue led, prevents Bluefruit flashing the green LED during advertising #define LED_PIN LED_BUILTIN -#define LED_STATE_ON HIGH +#define LED_STATE_ON LOW #define PIN_NEOPIXEL (14) #define NEOPIXEL_NUM (2) From a155587b7fff77f01799af821794dd1810e50007 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 18 May 2025 21:22:27 +1000 Subject: [PATCH 112/154] * possible bug when forwarding direct mode packets --- src/Mesh.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Mesh.cpp b/src/Mesh.cpp index fe3b2473..6029c192 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -71,7 +71,15 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { // remove our hash from 'path', then re-broadcast pkt->path_len -= PATH_HASH_SIZE; + #if 0 memcpy(pkt->path, &pkt->path[PATH_HASH_SIZE], pkt->path_len); + #elif PATH_HASH_SIZE == 1 + for (int k = 0; k < pkt->path_len; k++) { // shuffle bytes by 1 + pkt->path[k] = pkt->path[k + 1]; + } + #else + #error "need path remove impl" + #endif uint32_t d = getDirectRetransmitDelay(pkt); return ACTION_RETRANSMIT_DELAYED(0, d); // Routed traffic is HIGHEST priority From a79e9a79e0173c2408d35982a64558739a29a9cf Mon Sep 17 00:00:00 2001 From: cod3doomy Date: Sun, 18 May 2025 10:20:32 -0700 Subject: [PATCH 113/154] t-beam supreme: debug move Moved scanDevices into ifdef MESH_DEBUG since it only needs to run under debug sequence --- variants/lilygo_tbeam_supreme_SX1262/target.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.cpp b/variants/lilygo_tbeam_supreme_SX1262/target.cpp index 913c6a37..290a1031 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.cpp +++ b/variants/lilygo_tbeam_supreme_SX1262/target.cpp @@ -28,8 +28,8 @@ static void setPMUIntFlag(){ pmuIntFlag = true; } +#ifdef MESH_DEBUG uint32_t deviceOnline = 0x00; - void scanDevices(TwoWire *w) { uint8_t err, addr; @@ -89,8 +89,6 @@ void scanDevices(TwoWire *w) Serial.println("Scan for devices is complete."); Serial.println("\n"); } - -#ifdef MESH_DEBUG void TBeamS3SupremeBoard::printPMU() { Serial.print("isCharging:"); Serial.println(PMU.isCharging() ? "YES" : "NO"); From d4e6ece75da7b8552ce135503a582c046f92f1fb Mon Sep 17 00:00:00 2001 From: JQ Date: Sun, 18 May 2025 16:36:45 -0700 Subject: [PATCH 114/154] fix altitude for telemetry, instead of using zero --- src/helpers/SensorManager.h | 3 ++- src/helpers/sensors/LocationProvider.h | 1 + src/helpers/sensors/MicroNMEALocationProvider.h | 5 +++++ variants/heltec_tracker/target.cpp | 3 ++- variants/lilygo_tbeam_supreme_SX1262/target.cpp | 3 ++- variants/t1000-e/target.cpp | 3 ++- variants/t114/target.cpp | 3 ++- 7 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/helpers/SensorManager.h b/src/helpers/SensorManager.h index 839e2736..0e4bc27d 100644 --- a/src/helpers/SensorManager.h +++ b/src/helpers/SensorManager.h @@ -11,8 +11,9 @@ class SensorManager { public: double node_lat, node_lon; // modify these, if you want to affect Advert location + double node_altitude; // altitude in meters - SensorManager() { node_lat = 0; node_lon = 0; } + SensorManager() { node_lat = 0; node_lon = 0; node_altitude = 0; } virtual bool begin() { return false; } virtual bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { return false; } virtual void loop() { } diff --git a/src/helpers/sensors/LocationProvider.h b/src/helpers/sensors/LocationProvider.h index b5ac5812..056e61e0 100644 --- a/src/helpers/sensors/LocationProvider.h +++ b/src/helpers/sensors/LocationProvider.h @@ -8,6 +8,7 @@ class LocationProvider { public: virtual long getLatitude() = 0; virtual long getLongitude() = 0; + virtual long getAltitude() = 0; virtual bool isValid() = 0; virtual long getTimestamp() = 0; virtual void reset(); diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index da9f74f6..9f439e25 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -61,6 +61,11 @@ public : long getLatitude() override { return nmea.getLatitude(); } long getLongitude() override { return nmea.getLongitude(); } + long getAltitude() override { + long alt = 0; + nmea.getAltitude(alt); + return alt; + } bool isValid() override { return nmea.isValid(); } long getTimestamp() override { diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp index c82b70ad..461b7cac 100644 --- a/variants/heltec_tracker/target.cpp +++ b/variants/heltec_tracker/target.cpp @@ -103,7 +103,7 @@ bool HWTSensorManager::begin() { bool HWTSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission? - telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, 0.0f); + telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); } return true; } @@ -117,6 +117,7 @@ void HWTSensorManager::loop() { if (gps_active && _location->isValid()) { node_lat = ((double)_location->getLatitude())/1000000.; node_lon = ((double)_location->getLongitude())/1000000.; + node_altitude = ((double)_location->getAltitude()) / 1000.0; MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); } next_gps_update = millis() + 1000; diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.cpp b/variants/lilygo_tbeam_supreme_SX1262/target.cpp index 3808e5fd..4422e27e 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.cpp +++ b/variants/lilygo_tbeam_supreme_SX1262/target.cpp @@ -287,7 +287,7 @@ bool TbeamSupSensorManager::begin() { bool TbeamSupSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission? - telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, 0.0f); + telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); } return true; } @@ -301,6 +301,7 @@ void TbeamSupSensorManager::loop() { if (_nmea->isValid()) { node_lat = ((double)_nmea->getLatitude())/1000000.; node_lon = ((double)_nmea->getLongitude())/1000000.; + node_altitude = ((double)_nmea->getAltitude()) / 1000.0; //Serial.printf("lat %f lon %f\r\n", _lat, _lon); } next_gps_update = millis() + 1000; diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index f4bb75f6..9449dd2c 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -154,7 +154,7 @@ bool T1000SensorManager::begin() { bool T1000SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission? - telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, 0.0f); + telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); } return true; } @@ -168,6 +168,7 @@ void T1000SensorManager::loop() { if (_nmea->isValid()) { node_lat = ((double)_nmea->getLatitude())/1000000.; node_lon = ((double)_nmea->getLongitude())/1000000.; + node_altitude = ((double)_nmea->getAltitude()) / 1000.0; //Serial.printf("lat %f lon %f\r\n", _lat, _lon); } next_gps_update = millis() + 1000; diff --git a/variants/t114/target.cpp b/variants/t114/target.cpp index e8773dbb..3e34ff92 100644 --- a/variants/t114/target.cpp +++ b/variants/t114/target.cpp @@ -111,7 +111,7 @@ bool T114SensorManager::begin() { bool T114SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission? - telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, 0.0f); + telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); } return true; } @@ -125,6 +125,7 @@ void T114SensorManager::loop() { if (_location->isValid()) { node_lat = ((double)_location->getLatitude())/1000000.; node_lon = ((double)_location->getLongitude())/1000000.; + node_altitude = ((double)_location->getAltitude()) / 1000.0; MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); } next_gps_update = millis() + 1000; From a73eb9823d7698c7b40e8a9e89393815bf307ab7 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 19 May 2025 14:16:55 +1000 Subject: [PATCH 115/154] * big refactor of the 'display' object. Now defined in variants/*/target modules. --- examples/companion_radio/UITask.cpp | 4 +-- examples/companion_radio/UITask.h | 1 + examples/companion_radio/main.cpp | 47 ++++++------------------- examples/simple_repeater/main.cpp | 6 +--- examples/simple_room_server/main.cpp | 6 +--- src/helpers/ui/ST7735Display.cpp | 4 --- variants/heltec_tracker/platformio.ini | 1 - variants/heltec_tracker/target.cpp | 4 +++ variants/heltec_tracker/target.h | 7 ++++ variants/heltec_v2/target.cpp | 4 +++ variants/heltec_v2/target.h | 7 ++++ variants/heltec_v3/target.cpp | 4 +++ variants/heltec_v3/target.h | 7 ++++ variants/lilygo_t3s3/target.cpp | 4 +++ variants/lilygo_t3s3/target.h | 7 ++++ variants/lilygo_tbeam/target.cpp | 4 +++ variants/lilygo_tbeam/target.h | 7 ++++ variants/lilygo_tbeam_SX1262/target.cpp | 4 +++ variants/lilygo_tbeam_SX1262/target.h | 7 ++++ variants/lilygo_tlora_v2_1/target.cpp | 4 +++ variants/lilygo_tlora_v2_1/target.h | 7 ++++ variants/nano_g2_ultra/platformio.ini | 1 - variants/nano_g2_ultra/target.cpp | 4 +++ variants/nano_g2_ultra/target.h | 7 ++++ variants/promicro/platformio.ini | 5 ++- variants/promicro/target.cpp | 4 +++ variants/promicro/target.h | 6 ++++ variants/rak4631/target.cpp | 4 +++ variants/rak4631/target.h | 7 ++++ variants/t1000-e/NullDisplayDriver.h | 24 +++++++++++++ variants/t1000-e/platformio.ini | 2 +- variants/t1000-e/target.cpp | 4 +++ variants/t1000-e/target.h | 7 ++++ variants/t114/target.cpp | 4 +++ variants/t114/target.h | 7 ++++ variants/techo/platformio.ini | 1 - variants/techo/target.cpp | 4 +++ variants/techo/target.h | 8 +++++ variants/thinknode_m1/platformio.ini | 1 - variants/thinknode_m1/target.cpp | 4 +++ variants/thinknode_m1/target.h | 7 ++++ variants/xiao_s3_wio/target.cpp | 4 +++ variants/xiao_s3_wio/target.h | 7 ++++ 43 files changed, 210 insertions(+), 58 deletions(-) create mode 100644 variants/t1000-e/NullDisplayDriver.h diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index e0c2ec51..4a04337f 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -85,7 +85,7 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i } } -void renderBatteryIndicator(DisplayDriver* _display, uint16_t batteryMilliVolts) { +void UITask::renderBatteryIndicator(uint16_t batteryMilliVolts) { // Convert millivolts to percentage const int minMilliVolts = 3000; // Minimum voltage (e.g., 3.0V) const int maxMilliVolts = 4200; // Maximum voltage (e.g., 4.2V) @@ -155,7 +155,7 @@ void UITask::renderCurrScreen() { _display->print(_node_prefs->node_name); // battery voltage - renderBatteryIndicator(_display, _board->getBattMilliVolts()); + renderBatteryIndicator(_board->getBattMilliVolts()); // freq / sf _display->setCursor(0, 20); diff --git a/examples/companion_radio/UITask.h b/examples/companion_radio/UITask.h index 5edaa8e2..58d68564 100644 --- a/examples/companion_radio/UITask.h +++ b/examples/companion_radio/UITask.h @@ -22,6 +22,7 @@ class UITask { void renderCurrScreen(); void buttonHandler(); void userLedHandler(); + void renderBatteryIndicator(uint16_t batteryMilliVolts); public: diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 02f16ab0..2cfb833b 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -60,30 +60,7 @@ #define PUBLIC_GROUP_PSK "izOH6cXN6mrJ5e26oRXNcg==" -#ifdef DISPLAY_CLASS // TODO: refactor this -- move to variants/*/target - #include "UITask.h" - #ifdef ST7735 - #include - #elif ST7789 - #include - #elif SH1106 - #include - #elif defined(HAS_GxEPD) - #include - #else - #include - #endif - - #if defined(HELTEC_LORA_V3) && defined(ST7735) - static DISPLAY_CLASS display(&board.periph_power); // peripheral power pin is shared - #else - static DISPLAY_CLASS display; - #endif - - #define HAS_UI -#endif - -#if defined(HAS_UI) +#ifdef DISPLAY_CLASS #include "UITask.h" static UITask ui_task(&board); @@ -609,7 +586,7 @@ protected: } else { soundBuzzer(); } - #ifdef HAS_UI + #ifdef DISPLAY_CLASS ui_task.newMsg(path_len, from.name, text, offline_queue_len); #endif } @@ -660,7 +637,7 @@ protected: } else { soundBuzzer(); } - #ifdef HAS_UI + #ifdef DISPLAY_CLASS ui_task.newMsg(path_len, "Public", text, offline_queue_len); #endif } @@ -895,7 +872,7 @@ public: #ifdef BLE_PIN_CODE if (_prefs.ble_pin == 0) { - #ifdef HAS_UI + #ifdef DISPLAY_CLASS if (has_display) { StdRNG rng; _active_ble_pin = rng.nextInt(100000, 999999); // random pin each session @@ -1244,7 +1221,7 @@ public: int out_len; if ((out_len = getFromOfflineQueue(out_frame)) > 0) { _serial->writeFrame(out_frame, out_len); - #ifdef HAS_UI + #ifdef DISPLAY_CLASS ui_task.msgRead(offline_queue_len); #endif } else { @@ -1572,7 +1549,7 @@ public: checkConnections(); } - #ifdef HAS_UI + #ifdef DISPLAY_CLASS ui_task.setHasConnection(_serial->isConnected()); ui_task.loop(); #endif @@ -1643,16 +1620,14 @@ void setup() { board.begin(); -#ifdef HAS_UI +#ifdef DISPLAY_CLASS DisplayDriver* disp = NULL; - #ifdef DISPLAY_CLASS if (display.begin()) { disp = &display; disp->startFrame(); disp->print("Please wait..."); disp->endFrame(); } - #endif #endif if (!radio_init()) { halt(); } @@ -1662,7 +1637,7 @@ void setup() { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) InternalFS.begin(); the_mesh.begin(InternalFS, - #ifdef HAS_UI + #ifdef DISPLAY_CLASS disp != NULL #else false @@ -1680,7 +1655,7 @@ void setup() { #elif defined(RP2040_PLATFORM) LittleFS.begin(); the_mesh.begin(LittleFS, - #ifdef HAS_UI + #ifdef DISPLAY_CLASS disp != NULL #else false @@ -1705,7 +1680,7 @@ void setup() { #elif defined(ESP32) SPIFFS.begin(true); the_mesh.begin(SPIFFS, - #ifdef HAS_UI + #ifdef DISPLAY_CLASS disp != NULL #else false @@ -1733,7 +1708,7 @@ void setup() { sensors.begin(); -#ifdef HAS_UI +#ifdef DISPLAY_CLASS ui_task.begin(disp, the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION, the_mesh.getBLEPin()); #endif } diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 7b38f5c3..5e04de54 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -60,10 +60,6 @@ #endif #ifdef DISPLAY_CLASS - #include - - static DISPLAY_CLASS display; - #include "UITask.h" static UITask ui_task(display); #endif @@ -735,7 +731,7 @@ void setup() { board.begin(); #ifdef DISPLAY_CLASS - if(display.begin()){ + if (display.begin()) { display.startFrame(); display.print("Please wait..."); display.endFrame(); diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index c041c621..5ffef515 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -68,10 +68,6 @@ #endif #ifdef DISPLAY_CLASS - #include - - static DISPLAY_CLASS display; - #include "UITask.h" static UITask ui_task(display); #endif @@ -909,7 +905,7 @@ void setup() { board.begin(); #ifdef DISPLAY_CLASS - if(display.begin()){ + if (display.begin()) { display.startFrame(); display.print("Please wait..."); display.endFrame(); diff --git a/src/helpers/ui/ST7735Display.cpp b/src/helpers/ui/ST7735Display.cpp index 91c40ea5..e9eea69b 100644 --- a/src/helpers/ui/ST7735Display.cpp +++ b/src/helpers/ui/ST7735Display.cpp @@ -1,5 +1,3 @@ -#ifdef ST7735 - #include "ST7735Display.h" #ifndef DISPLAY_ROTATION @@ -130,5 +128,3 @@ uint16_t ST7735Display::getTextWidth(const char* str) { void ST7735Display::endFrame() { // display.display(); } - -#endif \ No newline at end of file diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index e0ad8b0f..f54eda86 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -38,7 +38,6 @@ build_flags = ${Heltec_tracker_base.build_flags} -I src/helpers/ui ; -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for debugging - -D ST7735 -D DISPLAY_ROTATION=1 -D DISPLAY_CLASS=ST7735Display -D MAX_CONTACTS=100 diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp index 461b7cac..042c7c0d 100644 --- a/variants/heltec_tracker/target.cpp +++ b/variants/heltec_tracker/target.cpp @@ -19,6 +19,10 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); HWTSensorManager sensors = HWTSensorManager(nmea); +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display(&board.periph_power); // peripheral power pin is shared +#endif + #ifndef LORA_CR #define LORA_CR 5 #endif diff --git a/variants/heltec_tracker/target.h b/variants/heltec_tracker/target.h index 422e6f14..3184bec9 100644 --- a/variants/heltec_tracker/target.h +++ b/variants/heltec_tracker/target.h @@ -8,6 +8,9 @@ #include #include #include +#ifdef DISPLAY_CLASS + #include +#endif class HWTSensorManager : public SensorManager { bool gps_active = false; @@ -31,6 +34,10 @@ extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern HWTSensorManager sensors; +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; +#endif + bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); diff --git a/variants/heltec_v2/target.cpp b/variants/heltec_v2/target.cpp index 44eb8def..a7e9fa67 100644 --- a/variants/heltec_v2/target.cpp +++ b/variants/heltec_v2/target.cpp @@ -16,6 +16,10 @@ ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + #ifndef LORA_CR #define LORA_CR 5 #endif diff --git a/variants/heltec_v2/target.h b/variants/heltec_v2/target.h index 92aec7f2..f758c193 100644 --- a/variants/heltec_v2/target.h +++ b/variants/heltec_v2/target.h @@ -7,12 +7,19 @@ #include #include #include +#ifdef DISPLAY_CLASS + #include +#endif extern HeltecV2Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; +#endif + bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); diff --git a/variants/heltec_v3/target.cpp b/variants/heltec_v3/target.cpp index 27e0ebf9..ab9b709f 100644 --- a/variants/heltec_v3/target.cpp +++ b/variants/heltec_v3/target.cpp @@ -16,6 +16,10 @@ ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + #ifndef LORA_CR #define LORA_CR 5 #endif diff --git a/variants/heltec_v3/target.h b/variants/heltec_v3/target.h index b6ab2577..76ad58a7 100644 --- a/variants/heltec_v3/target.h +++ b/variants/heltec_v3/target.h @@ -7,12 +7,19 @@ #include #include #include +#ifdef DISPLAY_CLASS + #include +#endif extern HeltecV3Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; +#endif + bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); diff --git a/variants/lilygo_t3s3/target.cpp b/variants/lilygo_t3s3/target.cpp index 4073518f..7fa45e54 100644 --- a/variants/lilygo_t3s3/target.cpp +++ b/variants/lilygo_t3s3/target.cpp @@ -16,6 +16,10 @@ ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + #ifndef LORA_CR #define LORA_CR 5 #endif diff --git a/variants/lilygo_t3s3/target.h b/variants/lilygo_t3s3/target.h index eef923ab..609248ae 100644 --- a/variants/lilygo_t3s3/target.h +++ b/variants/lilygo_t3s3/target.h @@ -7,12 +7,19 @@ #include #include #include +#ifdef DISPLAY_CLASS + #include +#endif extern ESP32Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; +#endif + bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); diff --git a/variants/lilygo_tbeam/target.cpp b/variants/lilygo_tbeam/target.cpp index 4e761cb2..116088d6 100644 --- a/variants/lilygo_tbeam/target.cpp +++ b/variants/lilygo_tbeam/target.cpp @@ -16,6 +16,10 @@ ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + #ifndef LORA_CR #define LORA_CR 5 #endif diff --git a/variants/lilygo_tbeam/target.h b/variants/lilygo_tbeam/target.h index 2c1897ef..4a9f0b3c 100644 --- a/variants/lilygo_tbeam/target.h +++ b/variants/lilygo_tbeam/target.h @@ -7,12 +7,19 @@ #include #include #include +#ifdef DISPLAY_CLASS + #include +#endif extern TBeamBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; +#endif + bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); diff --git a/variants/lilygo_tbeam_SX1262/target.cpp b/variants/lilygo_tbeam_SX1262/target.cpp index 4dcc3cf1..2f6666c5 100644 --- a/variants/lilygo_tbeam_SX1262/target.cpp +++ b/variants/lilygo_tbeam_SX1262/target.cpp @@ -16,6 +16,10 @@ ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + #ifndef LORA_CR #define LORA_CR 5 #endif diff --git a/variants/lilygo_tbeam_SX1262/target.h b/variants/lilygo_tbeam_SX1262/target.h index bcc80ef1..3f4d77fa 100644 --- a/variants/lilygo_tbeam_SX1262/target.h +++ b/variants/lilygo_tbeam_SX1262/target.h @@ -7,12 +7,19 @@ #include #include #include +#ifdef DISPLAY_CLASS + #include +#endif extern TBeamBoardSX1262 board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; +#endif + bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); diff --git a/variants/lilygo_tlora_v2_1/target.cpp b/variants/lilygo_tlora_v2_1/target.cpp index f5c14fe7..4d513ed9 100644 --- a/variants/lilygo_tlora_v2_1/target.cpp +++ b/variants/lilygo_tlora_v2_1/target.cpp @@ -12,6 +12,10 @@ ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + #ifndef LORA_CR #define LORA_CR 5 #endif diff --git a/variants/lilygo_tlora_v2_1/target.h b/variants/lilygo_tlora_v2_1/target.h index 9e73bc37..edf28eb4 100644 --- a/variants/lilygo_tlora_v2_1/target.h +++ b/variants/lilygo_tlora_v2_1/target.h @@ -7,12 +7,19 @@ #include #include #include +#ifdef DISPLAY_CLASS + #include +#endif extern LilyGoTLoraBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; +#endif + bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); diff --git a/variants/nano_g2_ultra/platformio.ini b/variants/nano_g2_ultra/platformio.ini index e4a25423..dc8e81ac 100644 --- a/variants/nano_g2_ultra/platformio.ini +++ b/variants/nano_g2_ultra/platformio.ini @@ -39,7 +39,6 @@ build_flags = -D BLE_PIN_CODE=0 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 - -D SH1106 -D DISPLAY_CLASS=SH1106Display ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 diff --git a/variants/nano_g2_ultra/target.cpp b/variants/nano_g2_ultra/target.cpp index e3f2eb7e..d8c5ef8e 100644 --- a/variants/nano_g2_ultra/target.cpp +++ b/variants/nano_g2_ultra/target.cpp @@ -12,6 +12,10 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + #ifndef LORA_CR #define LORA_CR 5 #endif diff --git a/variants/nano_g2_ultra/target.h b/variants/nano_g2_ultra/target.h index e8f3974a..b0af0a8d 100644 --- a/variants/nano_g2_ultra/target.h +++ b/variants/nano_g2_ultra/target.h @@ -7,12 +7,19 @@ #include #include #include +#ifdef DISPLAY_CLASS + #include +#endif extern NanoG2Ultra board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; +#endif + bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 1cae8cdd..51fbc92f 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -9,7 +9,6 @@ build_flags = ${nrf52840_base.build_flags} -D LORA_TX_POWER=22 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 - -D DISPLAY_CLASS=SSD1306Display -D PIN_BOARD_SCL=7 -D PIN_BOARD_SDA=8 -D PIN_OLED_RESET=-1 @@ -35,6 +34,7 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 + -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = ${Faketec.lib_deps} @@ -51,6 +51,7 @@ build_flags = ${Faketec.build_flags} -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D ROOM_PASSWORD='"hello"' + -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = ${Faketec.lib_deps} @@ -74,6 +75,7 @@ extends = Faketec build_flags = ${Faketec.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 + -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Faketec.build_src_filter} @@ -93,6 +95,7 @@ build_flags = ${Faketec.build_flags} -D ENABLE_PRIVATE_KEY_EXPORT=1 -D ENABLE_PRIVATE_KEY_IMPORT=1 -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Faketec.build_src_filter} diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index b945fcf4..6e3dc938 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -12,6 +12,10 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); PromicroSensorManager sensors; +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + #ifndef LORA_CR #define LORA_CR 5 #endif diff --git a/variants/promicro/target.h b/variants/promicro/target.h index d77c4d1d..b7475b4a 100644 --- a/variants/promicro/target.h +++ b/variants/promicro/target.h @@ -11,6 +11,9 @@ #include #include #include +#ifdef DISPLAY_CLASS + #include +#endif #define NUM_SENSOR_SETTINGS 3 @@ -18,6 +21,9 @@ extern PromicroBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; +#endif bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/rak4631/target.cpp b/variants/rak4631/target.cpp index aaa3e8f1..9b23af83 100644 --- a/variants/rak4631/target.cpp +++ b/variants/rak4631/target.cpp @@ -12,6 +12,10 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + #ifndef LORA_CR #define LORA_CR 5 #endif diff --git a/variants/rak4631/target.h b/variants/rak4631/target.h index 91f1d719..a50d6f2c 100644 --- a/variants/rak4631/target.h +++ b/variants/rak4631/target.h @@ -7,12 +7,19 @@ #include #include #include +#ifdef DISPLAY_CLASS + #include +#endif extern RAK4631Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; +#endif + bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); diff --git a/variants/t1000-e/NullDisplayDriver.h b/variants/t1000-e/NullDisplayDriver.h new file mode 100644 index 00000000..2a9670bd --- /dev/null +++ b/variants/t1000-e/NullDisplayDriver.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +class NullDisplayDriver : public DisplayDriver { +public: + NullDisplayDriver() : DisplayDriver(128, 64) { } + bool begin() { return false; } // not present + + bool isOn() override { return false; } + void turnOn() override { } + void turnOff() override { } + void clear() override { } + void startFrame(Color bkg = DARK) override { } + void setTextSize(int sz) override { } + void setColor(Color c) override { } + void setCursor(int x, int y) override { } + void print(const char* str) override { } + void fillRect(int x, int y, int w, int h) override { } + void drawRect(int x, int y, int w, int h) override { } + void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override { } + uint16_t getTextWidth(const char* str) override { return 0; } + void endFrame() { } +}; diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index 9f1a3b06..927655af 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -46,7 +46,7 @@ build_flags = ${t1000-e.build_flags} -D OFFLINE_QUEUE_SIZE=256 -D RX_BOOSTED_GAIN=true -D RF_SWITCH_TABLE - -D HAS_UI + -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${t1000-e.build_src_filter} + +<../examples/companion_radio/*.cpp> diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index 9449dd2c..be82ca76 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -12,6 +12,10 @@ VolatileRTCClock rtc_clock; MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); T1000SensorManager sensors = T1000SensorManager(nmea); +#ifdef DISPLAY_CLASS + NullDisplayDriver display; +#endif + #ifndef LORA_CR #define LORA_CR 5 #endif diff --git a/variants/t1000-e/target.h b/variants/t1000-e/target.h index 8e9fd9c7..1b569481 100644 --- a/variants/t1000-e/target.h +++ b/variants/t1000-e/target.h @@ -8,6 +8,9 @@ #include #include #include +#ifdef DISPLAY_CLASS + #include "NullDisplayDriver.h" +#endif class T1000SensorManager: public SensorManager { bool gps_active = false; @@ -27,6 +30,10 @@ public: bool setSettingValue(const char* name, const char* value) override; }; +#ifdef DISPLAY_CLASS + extern NullDisplayDriver display; +#endif + extern T1000eBoard board; extern WRAPPER_CLASS radio_driver; extern VolatileRTCClock rtc_clock; diff --git a/variants/t114/target.cpp b/variants/t114/target.cpp index 3e34ff92..fe3d14f9 100644 --- a/variants/t114/target.cpp +++ b/variants/t114/target.cpp @@ -14,6 +14,10 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); T114SensorManager sensors = T114SensorManager(nmea); +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + #ifndef LORA_CR #define LORA_CR 5 #endif diff --git a/variants/t114/target.h b/variants/t114/target.h index 38d0e02a..0f6ebaa1 100644 --- a/variants/t114/target.h +++ b/variants/t114/target.h @@ -8,6 +8,9 @@ #include #include #include +#ifdef DISPLAY_CLASS + #include +#endif class T114SensorManager : public SensorManager { bool gps_active = false; @@ -32,6 +35,10 @@ extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern T114SensorManager sensors; +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; +#endif + bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); diff --git a/variants/techo/platformio.ini b/variants/techo/platformio.ini index d9346948..c4bc8f85 100644 --- a/variants/techo/platformio.ini +++ b/variants/techo/platformio.ini @@ -64,7 +64,6 @@ build_flags = -D BLE_DEBUG_LOGGING=1 -D DISPLAY_CLASS=GxEPDDisplay -D OFFLINE_QUEUE_SIZE=256 - -D HAS_GxEPD ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/techo/target.cpp b/variants/techo/target.cpp index 0aabfd47..880af612 100644 --- a/variants/techo/target.cpp +++ b/variants/techo/target.cpp @@ -12,6 +12,10 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + #ifndef LORA_CR #define LORA_CR 5 #endif diff --git a/variants/techo/target.h b/variants/techo/target.h index 0ec9ab04..15524111 100644 --- a/variants/techo/target.h +++ b/variants/techo/target.h @@ -7,12 +7,20 @@ #include #include #include +#ifdef DISPLAY_CLASS + #include +#endif + extern TechoBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; +#endif + bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); diff --git a/variants/thinknode_m1/platformio.ini b/variants/thinknode_m1/platformio.ini index eec255d0..9bad98e2 100644 --- a/variants/thinknode_m1/platformio.ini +++ b/variants/thinknode_m1/platformio.ini @@ -70,7 +70,6 @@ build_flags = -D BLE_DEBUG_LOGGING=1 -D DISPLAY_ROTATION=4 -D DISPLAY_CLASS=GxEPDDisplay - -D HAS_GxEPD -D OFFLINE_QUEUE_SIZE=256 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 diff --git a/variants/thinknode_m1/target.cpp b/variants/thinknode_m1/target.cpp index c31749f6..5a09eb9a 100644 --- a/variants/thinknode_m1/target.cpp +++ b/variants/thinknode_m1/target.cpp @@ -12,6 +12,10 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + #ifndef LORA_CR #define LORA_CR 5 #endif diff --git a/variants/thinknode_m1/target.h b/variants/thinknode_m1/target.h index 73e8a134..c958d0e3 100644 --- a/variants/thinknode_m1/target.h +++ b/variants/thinknode_m1/target.h @@ -7,12 +7,19 @@ #include #include #include +#ifdef DISPLAY_CLASS + #include +#endif extern ThinkNodeM1Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; +#endif + bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); diff --git a/variants/xiao_s3_wio/target.cpp b/variants/xiao_s3_wio/target.cpp index 517b9ef6..09ee5daa 100644 --- a/variants/xiao_s3_wio/target.cpp +++ b/variants/xiao_s3_wio/target.cpp @@ -16,6 +16,10 @@ ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + #ifndef LORA_CR #define LORA_CR 5 #endif diff --git a/variants/xiao_s3_wio/target.h b/variants/xiao_s3_wio/target.h index eef923ab..609248ae 100644 --- a/variants/xiao_s3_wio/target.h +++ b/variants/xiao_s3_wio/target.h @@ -7,12 +7,19 @@ #include #include #include +#ifdef DISPLAY_CLASS + #include +#endif extern ESP32Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; +#endif + bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); From d5eb83a92148b58d61e7f89df43b196c116b8388 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 19 May 2025 22:40:53 +1000 Subject: [PATCH 116/154] * AdvertDataHelpers: prospective changes to first byte bit-field --- src/helpers/AdvertDataHelpers.cpp | 8 -------- src/helpers/AdvertDataHelpers.h | 9 +++++++-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/helpers/AdvertDataHelpers.cpp b/src/helpers/AdvertDataHelpers.cpp index 5972bce2..f1aff21f 100644 --- a/src/helpers/AdvertDataHelpers.cpp +++ b/src/helpers/AdvertDataHelpers.cpp @@ -8,8 +8,6 @@ memcpy(&app_data[i], &_lat, 4); i += 4; memcpy(&app_data[i], &_lon, 4); i += 4; } - // TODO: BATTERY encoding - // TODO: TEMPERATURE encoding if (_name && *_name != 0) { app_data[0] |= ADV_NAME_MASK; const char* sp = _name; @@ -31,12 +29,6 @@ memcpy(&_lat, &app_data[i], 4); i += 4; memcpy(&_lon, &app_data[i], 4); i += 4; } - if (_flags & ADV_BATTERY_MASK) { - /* TODO: somewhere to store battery volts? */ i += 2; - } - if (_flags & ADV_TEMPERATURE_MASK) { - /* TODO: somewhere to store temperature? */ i += 2; - } if (app_data_len >= i) { int nlen = 0; diff --git a/src/helpers/AdvertDataHelpers.h b/src/helpers/AdvertDataHelpers.h index a8a24a0a..f41996eb 100644 --- a/src/helpers/AdvertDataHelpers.h +++ b/src/helpers/AdvertDataHelpers.h @@ -11,8 +11,8 @@ //FUTURE: 4..15 #define ADV_LATLON_MASK 0x10 -#define ADV_BATTERY_MASK 0x20 -#define ADV_TEMPERATURE_MASK 0x40 +#define ADV_FEAT1_MASK 0x20 // FUTURE +#define ADV_FEAT2_MASK 0x40 // FUTURE #define ADV_NAME_MASK 0x80 class AdvertDataBuilder { @@ -25,6 +25,9 @@ public: AdvertDataBuilder(uint8_t adv_type, const char* name, double lat, double lon) : _type(adv_type), _name(name), _lat(lat * 1E6), _lon(lon * 1E6) { } + void setFeat1(bool enabled) { if (enabled) _type |= ADV_FEAT1_MASK; else _type &= ~ADV_FEAT1_MASK; } + void setFeat2(bool enabled) { if (enabled) _type |= ADV_FEAT2_MASK; else _type &= ~ADV_FEAT2_MASK; } + /** * \brief encode the given advertisement data. * \param app_data dest array, must be MAX_ADVERT_DATA_SIZE @@ -43,6 +46,8 @@ public: bool isValid() const { return _valid; } uint8_t getType() const { return _flags & 0x0F; } + bool hasFeat1() const { return (_flags & ADV_FEAT1_MASK) != 0; } + bool hasFeat2() const { return (_flags & ADV_FEAT2_MASK) != 0; } bool hasName() const { return _name[0] != 0; } const char* getName() const { return _name; } From 5d0a8d9d7cdd069d108ddbf863f9b1a58592f257 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 19 May 2025 23:21:57 +1000 Subject: [PATCH 117/154] * AdvertDataHelpers: reverting parsing logic, but changed meanings of 'battery' and 'temperature' to just two generic uint16 'feature' properties --- src/helpers/AdvertDataHelpers.cpp | 17 ++++++++++++++++- src/helpers/AdvertDataHelpers.h | 12 ++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/helpers/AdvertDataHelpers.cpp b/src/helpers/AdvertDataHelpers.cpp index f1aff21f..88253f61 100644 --- a/src/helpers/AdvertDataHelpers.cpp +++ b/src/helpers/AdvertDataHelpers.cpp @@ -8,6 +8,14 @@ memcpy(&app_data[i], &_lat, 4); i += 4; memcpy(&app_data[i], &_lon, 4); i += 4; } + if (_extra1) { + app_data[0] |= ADV_FEAT1_MASK; + memcpy(&app_data[i], &_extra1, 2); i += 2; + } + if (_extra2) { + app_data[0] |= ADV_FEAT2_MASK; + memcpy(&app_data[i], &_extra2, 2); i += 2; + } if (_name && *_name != 0) { app_data[0] |= ADV_NAME_MASK; const char* sp = _name; @@ -23,12 +31,19 @@ _lat = _lon = 0; _flags = app_data[0]; _valid = false; - + _extra1 = _extra2 = 0; + int i = 1; if (_flags & ADV_LATLON_MASK) { memcpy(&_lat, &app_data[i], 4); i += 4; memcpy(&_lon, &app_data[i], 4); i += 4; } + if (_flags & ADV_FEAT1_MASK) { + memcpy(&_extra1, &app_data[i], 2); i += 2; + } + if (_flags & ADV_FEAT2_MASK) { + memcpy(&_extra2, &app_data[i], 2); i += 2; + } if (app_data_len >= i) { int nlen = 0; diff --git a/src/helpers/AdvertDataHelpers.h b/src/helpers/AdvertDataHelpers.h index f41996eb..14815eac 100644 --- a/src/helpers/AdvertDataHelpers.h +++ b/src/helpers/AdvertDataHelpers.h @@ -19,14 +19,16 @@ class AdvertDataBuilder { uint8_t _type; const char* _name; int32_t _lat, _lon; + uint16_t _extra1 = 0; + uint16_t _extra2 = 0; public: AdvertDataBuilder(uint8_t adv_type) : _type(adv_type), _name(NULL), _lat(0), _lon(0) { } AdvertDataBuilder(uint8_t adv_type, const char* name) : _type(adv_type), _name(name), _lat(0), _lon(0) { } AdvertDataBuilder(uint8_t adv_type, const char* name, double lat, double lon) : _type(adv_type), _name(name), _lat(lat * 1E6), _lon(lon * 1E6) { } - void setFeat1(bool enabled) { if (enabled) _type |= ADV_FEAT1_MASK; else _type &= ~ADV_FEAT1_MASK; } - void setFeat2(bool enabled) { if (enabled) _type |= ADV_FEAT2_MASK; else _type &= ~ADV_FEAT2_MASK; } + void setFeat1(uint16_t extra) { _extra1 = extra; } + void setFeat2(uint16_t extra) { _extra2 = extra; } /** * \brief encode the given advertisement data. @@ -41,13 +43,15 @@ class AdvertDataParser { bool _valid; char _name[MAX_ADVERT_DATA_SIZE]; int32_t _lat, _lon; + uint16_t _extra1; + uint16_t _extra2; public: AdvertDataParser(const uint8_t app_data[], uint8_t app_data_len); bool isValid() const { return _valid; } uint8_t getType() const { return _flags & 0x0F; } - bool hasFeat1() const { return (_flags & ADV_FEAT1_MASK) != 0; } - bool hasFeat2() const { return (_flags & ADV_FEAT2_MASK) != 0; } + uint16_t getFeat1() const { return _extra1; } + uint16_t getFeat2() const { return _extra2; } bool hasName() const { return _name[0] != 0; } const char* getName() const { return _name; } From f9c0056955fc3dd3b546d369a756a40e70eabfa6 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 19 May 2025 23:39:34 +1000 Subject: [PATCH 118/154] * bug fix for CommonCLI, when entering long unknown command --- src/helpers/CommonCLI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index f3f7727c..29b3a4e2 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -358,6 +358,6 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _callbacks->dumpLogFile(); strcpy(reply, " EOF"); } else { - sprintf(reply, "Unknown: %s", command); + strcpy(reply, "Unknown command"); } } From 8a27743e43ba40eb13d2fd6ed9b5f91598d09402 Mon Sep 17 00:00:00 2001 From: Normunds Gavars Date: Mon, 19 May 2025 17:24:54 +0300 Subject: [PATCH 119/154] Create sensor classes that can be shared across variants --- src/helpers/sensors/AHTX0Sensor.h | 39 ++++++ .../sensors/EnvironmentSensorManager.cpp | 67 ++++++++++ .../sensors/EnvironmentSensorManager.h | 30 +++++ src/helpers/sensors/INA219Sensor.h | 48 +++++++ src/helpers/sensors/INA3221Sensor.h | 71 +++++++++++ variants/promicro/platformio.ini | 1 + variants/promicro/target.cpp | 119 +----------------- variants/promicro/target.h | 41 +----- 8 files changed, 259 insertions(+), 157 deletions(-) create mode 100644 src/helpers/sensors/AHTX0Sensor.h create mode 100644 src/helpers/sensors/EnvironmentSensorManager.cpp create mode 100644 src/helpers/sensors/EnvironmentSensorManager.h create mode 100644 src/helpers/sensors/INA219Sensor.h create mode 100644 src/helpers/sensors/INA3221Sensor.h diff --git a/src/helpers/sensors/AHTX0Sensor.h b/src/helpers/sensors/AHTX0Sensor.h new file mode 100644 index 00000000..04af3057 --- /dev/null +++ b/src/helpers/sensors/AHTX0Sensor.h @@ -0,0 +1,39 @@ +#pragma once +#include +#include + +#define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address + +static Adafruit_AHTX0 AHTX0; + +class AHTX0Sensor { + bool initialized = false; +public: + void begin() { + if (AHTX0.begin(&Wire, 0, TELEM_AHTX_ADDRESS)) { + MESH_DEBUG_PRINTLN("Found AHT10/AHT20 at address: %02X", TELEM_AHTX_ADDRESS); + initialized = true; + } else { + initialized = false; + MESH_DEBUG_PRINTLN("AHT10/AHT20 was not found at I2C address %02X", TELEM_AHTX_ADDRESS); + } + } + + bool isInitialized() const { return initialized; }; + + float getRelativeHumidity() const { + if (initialized) { + sensors_event_t humidity, temp; + AHTX0.getEvent(&humidity, &temp); + return humidity.relative_humidity; + } + } + + float getTemperature() const { + if (initialized) { + sensors_event_t humidity, temp; + AHTX0.getEvent(&humidity, &temp); + return temp.temperature; + } + } +}; diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp new file mode 100644 index 00000000..b0497471 --- /dev/null +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -0,0 +1,67 @@ +#include "EnvironmentSensorManager.h" + +bool EnvironmentSensorManager::begin() { + INA3221_sensor.begin(); + INA219_sensor.begin(); + AHTX0_sensor.begin(); + + return true; +} + +bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { + next_available_channel = TELEM_CHANNEL_SELF + 1; + if (requester_permissions & TELEM_PERM_ENVIRONMENT) { + if (INA3221_sensor.isInitialized()) { + for(int i = 0; i < 3; i++) { + // add only enabled INA3221 channels to telemetry + if (INA3221_sensor.getChannelEnabled(i)) { + telemetry.addVoltage(next_available_channel, INA3221_sensor.getVoltage(i)); + telemetry.addCurrent(next_available_channel, INA3221_sensor.getCurrent(i)); + telemetry.addPower(next_available_channel, INA3221_sensor.getPower(i)); + next_available_channel++; + } + } + } + if (INA219_sensor.isInitialized()) { + telemetry.addVoltage(next_available_channel, INA219_sensor.getVoltage()); + telemetry.addCurrent(next_available_channel, INA219_sensor.getCurrent()); + telemetry.addPower(next_available_channel, INA219_sensor.getPower()); + next_available_channel++; + } + if (AHTX0_sensor.isInitialized()) { + telemetry.addTemperature(TELEM_CHANNEL_SELF, AHTX0_sensor.getTemperature()); + telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, AHTX0_sensor.getRelativeHumidity()); + } + } + + return true; +} + +int EnvironmentSensorManager::getNumSettings() const { + return NUM_SENSOR_SETTINGS; +} + +const char* EnvironmentSensorManager::getSettingName(int i) const { + if (i >= 0 && i < NUM_SENSOR_SETTINGS) { + return INA3221_CHANNEL_NAMES[i]; + } + return NULL; +} + +const char* EnvironmentSensorManager::getSettingValue(int i) const { + if (i >= 0 && i < NUM_SENSOR_SETTINGS) { + return INA3221_sensor.getChannelEnabled(i) == true ? "1" : "0"; + } + return NULL; +} + +bool EnvironmentSensorManager::setSettingValue(const char* name, const char* value) { + for (int i = 0; i < NUM_SENSOR_SETTINGS; i++) { + if (strcmp(name, INA3221_CHANNEL_NAMES[i]) == 0) { + bool channel_enabled = strcmp(value, "1") == 0 ? true : false; + INA3221_sensor.setChannelEnabled(i, channel_enabled); + return true; + } + } + return false; +} \ No newline at end of file diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h new file mode 100644 index 00000000..95a7476b --- /dev/null +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include "INA3221Sensor.h" +#include "INA219Sensor.h" +#include "AHTX0Sensor.h" + +#define NUM_SENSOR_SETTINGS 3 +#define TELEM_INA3221_SETTING_CH1 "INA3221-1" +#define TELEM_INA3221_SETTING_CH2 "INA3221-2" +#define TELEM_INA3221_SETTING_CH3 "INA3221-3" + +class EnvironmentSensorManager : public SensorManager { +// INA3221 channels in telemetry +const char * INA3221_CHANNEL_NAMES[NUM_SENSOR_SETTINGS] = { TELEM_INA3221_SETTING_CH1, TELEM_INA3221_SETTING_CH2, TELEM_INA3221_SETTING_CH3}; + +protected: + int next_available_channel = TELEM_CHANNEL_SELF + 1; + INA3221Sensor INA3221_sensor; + AHTX0Sensor AHTX0_sensor; + INA219Sensor INA219_sensor; +public: + EnvironmentSensorManager(){}; + bool begin() override; + bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; + int getNumSettings() const override; + const char* getSettingName(int i) const override; + const char* getSettingValue(int i) const override; + bool setSettingValue(const char* name, const char* value) override; +}; diff --git a/src/helpers/sensors/INA219Sensor.h b/src/helpers/sensors/INA219Sensor.h new file mode 100644 index 00000000..1f641ab2 --- /dev/null +++ b/src/helpers/sensors/INA219Sensor.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +#define TELEM_INA219_ADDRESS 0x40 // INA219 single channel current sensor I2C address +#define TELEM_INA219_SHUNT_VALUE 0.100 // shunt value in ohms (may differ between manufacturers) +#define TELEM_INA219_MAX_CURRENT 5 + +static INA219 INA_219(TELEM_INA219_ADDRESS, &Wire); + +class INA219Sensor { + bool initialized = false; +public: + void begin() { + if (INA_219.begin()) { + MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", INA_219.getAddress()); + INA_219.setMaxCurrentShunt(TELEM_INA219_MAX_CURRENT, TELEM_INA219_SHUNT_VALUE); + initialized = true; + } else { + initialized = false; + MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS); + } + } + + bool isInitialized() const { return initialized; } + + float getVoltage() const { + if (initialized) { + return INA_219.getBusVoltage(); + } + return 0; + } + + float getCurrent() const { + if (initialized) { + return INA_219.getCurrent(); + } + return 0; + } + + float getPower() const { + if (initialized) { + return INA_219.getPower(); + } + return 0; + } +}; diff --git a/src/helpers/sensors/INA3221Sensor.h b/src/helpers/sensors/INA3221Sensor.h new file mode 100644 index 00000000..f3618a9f --- /dev/null +++ b/src/helpers/sensors/INA3221Sensor.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include + +#define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current sensor I2C address +#define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts + +#define NUM_CHANNELS 3 + +static INA3221 INA_3221(TELEM_INA3221_ADDRESS, &Wire); + +class INA3221Sensor { + bool initialized = false; + +public: + void begin() { + if (INA_3221.begin()) { + MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", INA_3221.getAddress()); + MESH_DEBUG_PRINTLN("%04X %04X %04X", INA_3221.getDieID(), INA_3221.getManufacturerID(), INA_3221.getConfiguration()); + + for(int i = 0; i < 3; i++) { + INA_3221.setShuntR(i, TELEM_INA3221_SHUNT_VALUE); + } + initialized = true; + } else { + initialized = false; + MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS); + } + } + + bool isInitialized() const { return initialized; } + + int numChannels() const { return NUM_CHANNELS; } + + float getVoltage(int channel) const { + if (initialized && channel >=0 && channel < NUM_CHANNELS) { + return INA_3221.getBusVoltage(channel); + } + return 0; + } + + float getCurrent(int channel) const { + if (initialized && channel >=0 && channel < NUM_CHANNELS) { + return INA_3221.getCurrent(channel); + } + return 0; + } + + float getPower (int channel) const { + if (initialized && channel >=0 && channel < NUM_CHANNELS) { + return INA_3221.getPower(channel); + } + return 0; + } + + bool setChannelEnabled(int channel, bool enabled) { + if (initialized && channel >=0 && channel < NUM_CHANNELS) { + INA_3221.enableChannel(channel); + return true; + } + return false; + } + + bool getChannelEnabled(int channel) const { + if (initialized && channel >=0 && channel < NUM_CHANNELS) { + return INA_3221.getEnableChannel(channel); + } + return false; + } +}; diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 51fbc92f..8cabf6e6 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -15,6 +15,7 @@ build_flags = ${nrf52840_base.build_flags} -D PIN_USER_BTN=6 build_src_filter = ${nrf52840_base.build_src_filter} + + + +<../variants/promicro> lib_deps= ${nrf52840_base.lib_deps} adafruit/Adafruit SSD1306 @ ^2.5.13 diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index 6e3dc938..841bd1dc 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -10,7 +10,7 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -PromicroSensorManager sensors; +EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; @@ -79,120 +79,3 @@ mesh::LocalIdentity radio_new_identity() { return mesh::LocalIdentity(&rng); // create new random identity } -static INA3221 INA_3221(TELEM_INA3221_ADDRESS, &Wire); -static INA219 INA_219(TELEM_INA219_ADDRESS, &Wire); -static Adafruit_AHTX0 AHTX; - -bool PromicroSensorManager::begin() { - initINA3221(); - initINA219(); - initAHTX(); - - return true; -} - -bool PromicroSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { - int nextAvalableChannel = TELEM_CHANNEL_SELF + 1; - if (requester_permissions & TELEM_PERM_ENVIRONMENT) { - if (INA3221initialized) { - for(int i = 0; i < 3; i++) { - // add only enabled INA3221 channels to telemetry - if (INA3221_CHANNEL_ENABLED[i]) { - telemetry.addVoltage(nextAvalableChannel, INA_3221.getBusVoltage(i)); - telemetry.addCurrent(nextAvalableChannel, INA_3221.getCurrent(i)); - telemetry.addPower(nextAvalableChannel, INA_3221.getPower(i)); - nextAvalableChannel++; - } - } - } - if (INA219initialized) { - telemetry.addVoltage(nextAvalableChannel, INA_219.getBusVoltage()); - telemetry.addCurrent(nextAvalableChannel, INA_219.getCurrent()); - telemetry.addPower(nextAvalableChannel, INA_219.getPower()); - nextAvalableChannel++; - } - if (AHTXinitialized) { - sensors_event_t humidity, temp; - AHTX.getEvent(&humidity, &temp); - telemetry.addTemperature(TELEM_CHANNEL_SELF, temp.temperature); - telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, humidity.relative_humidity); - } - } - - return true; -} - -int PromicroSensorManager::getNumSettings() const { - return NUM_SENSOR_SETTINGS; -} - -const char* PromicroSensorManager::getSettingName(int i) const { - if (i >= 0 && i < NUM_SENSOR_SETTINGS) { - return INA3221_CHANNEL_NAMES[i]; - } - return NULL; -} - -const char* PromicroSensorManager::getSettingValue(int i) const { - if (i >= 0 && i < NUM_SENSOR_SETTINGS) { - return INA3221_CHANNEL_ENABLED[i] ? "1" : "0"; - } - return NULL; -} - -bool PromicroSensorManager::setSettingValue(const char* name, const char* value) { - for (int i = 0; i < NUM_SENSOR_SETTINGS; i++) { - if (strcmp(name, INA3221_CHANNEL_NAMES[i]) == 0) { - int channelEnabled = INA_3221.getEnableChannel(i); - if (strcmp(value, "1") == 0) { - INA3221_CHANNEL_ENABLED[i] = true; - if (!channelEnabled) { - INA_3221.enableChannel(i); - } - } else { - INA3221_CHANNEL_ENABLED[i] = false; - if (channelEnabled) { - INA_3221.disableChannel(i); - } - } - return true; - } - } - return false; -} - -void PromicroSensorManager::initINA3221() { - if (INA_3221.begin()) { - MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", INA_3221.getAddress()); - MESH_DEBUG_PRINTLN("%04X %04X %04X", INA_3221.getDieID(), INA_3221.getManufacturerID(), INA_3221.getConfiguration()); - - for(int i = 0; i < 3; i++) { - INA_3221.setShuntR(i, TELEM_INA3221_SHUNT_VALUE); - } - INA3221initialized = true; - } else { - INA3221initialized = false; - MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS); - } -} - -void PromicroSensorManager::initINA219() { - if (INA_219.begin()) { - MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", INA_219.getAddress()); - INA_219.setMaxCurrentShunt(TELEM_INA219_MAX_CURRENT, TELEM_INA219_SHUNT_VALUE); - INA219initialized = true; - } else { - INA219initialized = false; - MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS); - } -} - -void PromicroSensorManager::initAHTX() { - if (AHTX.begin(&Wire, 0, TELEM_AHTX_ADDRESS)) { - MESH_DEBUG_PRINTLN("Found AHT10/AHT20 at address: %02X", TELEM_AHTX_ADDRESS); - AHTXinitialized = true; - } else { - AHTXinitialized = false; - MESH_DEBUG_PRINTLN("AHT10/AHT20 was not found at I2C address %02X", TELEM_AHTX_ADDRESS); - } -} diff --git a/variants/promicro/target.h b/variants/promicro/target.h index b7475b4a..c289d744 100644 --- a/variants/promicro/target.h +++ b/variants/promicro/target.h @@ -8,14 +8,12 @@ #include #include #include -#include -#include -#include #ifdef DISPLAY_CLASS #include #endif #define NUM_SENSOR_SETTINGS 3 +#include extern PromicroBoard board; extern WRAPPER_CLASS radio_driver; @@ -31,39 +29,4 @@ 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(); -#define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current sensor I2C address -#define TELEM_INA219_ADDRESS 0x40 // INA219 single channel current sensor I2C address -#define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address - -#define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts -#define TELEM_INA3221_SETTING_CH1 "INA3221-1" -#define TELEM_INA3221_SETTING_CH2 "INA3221-2" -#define TELEM_INA3221_SETTING_CH3 "INA3221-3" - -#define TELEM_INA219_SHUNT_VALUE 0.100 // shunt value in ohms (may differ between manufacturers) -#define TELEM_INA219_MAX_CURRENT 5 - -class PromicroSensorManager: public SensorManager { - bool INA3221initialized = false; - bool INA219initialized = false; - bool AHTXinitialized = false; - - // INA3221 channels in telemetry - const char * INA3221_CHANNEL_NAMES[NUM_SENSOR_SETTINGS] = { TELEM_INA3221_SETTING_CH1, TELEM_INA3221_SETTING_CH2, TELEM_INA3221_SETTING_CH3}; - bool INA3221_CHANNEL_ENABLED[NUM_SENSOR_SETTINGS] = {true, true, true}; - - void initINA3221(); - void initINA219(); - void initAHTX(); -public: - PromicroSensorManager(){}; - bool begin() override; - bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; - int getNumSettings() const override; - const char* getSettingName(int i) const override; - const char* getSettingValue(int i) const override; - bool setSettingValue(const char* name, const char* value) override; -}; - - -extern PromicroSensorManager sensors; \ No newline at end of file +extern EnvironmentSensorManager sensors; \ No newline at end of file From a950343f057ce2035e5dd853de890459bd4ded4a Mon Sep 17 00:00:00 2001 From: AndreaB Date: Mon, 19 May 2025 16:52:24 +0100 Subject: [PATCH 120/154] Increase the delay to 1500 to allow enough time for T114 GPS to start up successfully. --- variants/t114/target.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/t114/target.cpp b/variants/t114/target.cpp index fe3d14f9..a57a55e2 100644 --- a/variants/t114/target.cpp +++ b/variants/t114/target.cpp @@ -97,7 +97,7 @@ bool T114SensorManager::begin() { digitalWrite(GPS_EN, HIGH); // Power on GPS // Give GPS a moment to power up and send data - delay(500); + delay(1500); // We'll consider GPS detected if we see any data on Serial1 gps_detected = (Serial1.available() > 0); From 3cf78a952ba9e23bbfb05706bb17f318879c7c1c Mon Sep 17 00:00:00 2001 From: Normunds Gavars Date: Mon, 19 May 2025 19:37:30 +0300 Subject: [PATCH 121/154] Telemetry: Create BME280 sensor that can bu used across variants. Add to promicro. --- src/helpers/sensors/BME280Sensor.h | 55 +++++++++++++++++++ .../sensors/EnvironmentSensorManager.cpp | 7 +++ .../sensors/EnvironmentSensorManager.h | 2 + variants/promicro/platformio.ini | 1 + 4 files changed, 65 insertions(+) create mode 100644 src/helpers/sensors/BME280Sensor.h diff --git a/src/helpers/sensors/BME280Sensor.h b/src/helpers/sensors/BME280Sensor.h new file mode 100644 index 00000000..1226fce5 --- /dev/null +++ b/src/helpers/sensors/BME280Sensor.h @@ -0,0 +1,55 @@ +#pragma once +#include +#include + +#define TELEM_BME280_ADDRESS 0x76 // BME280 environmental sensor I2C address +#define SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level + +static Adafruit_BME280 BME280; + +class BME280Sensor { + bool initialized = false; +public: + void begin() { + if (BME280.begin(TELEM_BME280_ADDRESS, &Wire)) { + MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS); + MESH_DEBUG_PRINTLN("BME sensor ID: %02X", BME280.sensorID); + initialized = true; + } else { + initialized = false; + MESH_DEBUG_PRINTLN("BME280 was not found at I2C address %02X", TELEM_BME280_ADDRESS); + } + } + + bool isInitialized() const { return initialized; }; + + float getRelativeHumidity() const { + if (initialized) { + return BME280.readHumidity();; + } + } + + float getTemperature() const { + if (initialized) { + return BME280.readTemperature();; + } + } + + float getBarometricPressure() const { + if (initialized) { + return BME280.readPressure(); + } + } + + float getAltitude() const { + if (initialized) { + return BME280.readAltitude(SEALEVELPRESSURE_HPA); + } + } + + void setTemperatureCompensation(float delta) { + if (initialized) { + BME280.setTemperatureCompensation(delta); + } + } +}; diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index b0497471..cf931470 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -4,6 +4,7 @@ bool EnvironmentSensorManager::begin() { INA3221_sensor.begin(); INA219_sensor.begin(); AHTX0_sensor.begin(); + BME280_sensor.begin(); return true; } @@ -32,6 +33,12 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen telemetry.addTemperature(TELEM_CHANNEL_SELF, AHTX0_sensor.getTemperature()); telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, AHTX0_sensor.getRelativeHumidity()); } + if (BME280_sensor.isInitialized()) { + telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280_sensor.getTemperature()); + telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME280_sensor.getRelativeHumidity()); + telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME280_sensor.getBarometricPressure()); + telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280_sensor.getAltitude()); + } } return true; diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 95a7476b..6d02f9fc 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -4,6 +4,7 @@ #include "INA3221Sensor.h" #include "INA219Sensor.h" #include "AHTX0Sensor.h" +#include "BME280Sensor.h" #define NUM_SENSOR_SETTINGS 3 #define TELEM_INA3221_SETTING_CH1 "INA3221-1" @@ -19,6 +20,7 @@ protected: INA3221Sensor INA3221_sensor; AHTX0Sensor AHTX0_sensor; INA219Sensor INA219_sensor; + BME280Sensor BME280_sensor; public: EnvironmentSensorManager(){}; bool begin() override; diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 8cabf6e6..7b6771e0 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -22,6 +22,7 @@ lib_deps= ${nrf52840_base.lib_deps} robtillaart/INA3221 @ ^0.4.1 robtillaart/INA219 @ ^0.4.1 adafruit/Adafruit AHTX0@^2.0.5 + adafruit/Adafruit BME280 Library@^2.3.0 [env:Faketec_Repeater] extends = Faketec From 5d9e7b4567b216f9a6f4c0926ce5c1b000d5b20e Mon Sep 17 00:00:00 2001 From: Normunds Gavars Date: Mon, 19 May 2025 20:30:58 +0300 Subject: [PATCH 122/154] Remove unnecessary include --- src/helpers/sensors/EnvironmentSensorManager.h | 1 + variants/promicro/target.h | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 6d02f9fc..318de001 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -7,6 +7,7 @@ #include "BME280Sensor.h" #define NUM_SENSOR_SETTINGS 3 + #define TELEM_INA3221_SETTING_CH1 "INA3221-1" #define TELEM_INA3221_SETTING_CH2 "INA3221-2" #define TELEM_INA3221_SETTING_CH3 "INA3221-3" diff --git a/variants/promicro/target.h b/variants/promicro/target.h index c289d744..c634d18a 100644 --- a/variants/promicro/target.h +++ b/variants/promicro/target.h @@ -7,17 +7,16 @@ #include #include #include -#include #ifdef DISPLAY_CLASS #include #endif -#define NUM_SENSOR_SETTINGS 3 #include extern PromicroBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; @@ -29,4 +28,3 @@ 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(); -extern EnvironmentSensorManager sensors; \ No newline at end of file From 4a90042b08b6fe36dcb13c04e4d8f9855ed2fe0f Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Sun, 18 May 2025 15:38:57 -0700 Subject: [PATCH 123/154] add GPS for nano g2 hardcoded interval of 1 minute after first fix obtained --- variants/nano_g2_ultra/nano-g2.cpp | 5 +- variants/nano_g2_ultra/platformio.ini | 2 +- variants/nano_g2_ultra/target.cpp | 107 +++++++++++++++++++++++++- variants/nano_g2_ultra/target.h | 26 ++++++- variants/nano_g2_ultra/variant.h | 4 + 5 files changed, 135 insertions(+), 9 deletions(-) diff --git a/variants/nano_g2_ultra/nano-g2.cpp b/variants/nano_g2_ultra/nano-g2.cpp index 731fa873..8088ddd0 100644 --- a/variants/nano_g2_ultra/nano-g2.cpp +++ b/variants/nano_g2_ultra/nano-g2.cpp @@ -27,11 +27,10 @@ void NanoG2Ultra::begin() // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; + // set user button pinMode(PIN_BUTTON1, INPUT); + // the external notification circuit is shared for both buzzer and led - // need to find out the switch state or somehow write a function that can - // sound the buzzer or signal the led. the led will stay on once brought HIGH - // and can be then brought LOW. It turns off with a hardware btn. pinMode(EXT_NOTIFY_OUT, OUTPUT); digitalWrite(EXT_NOTIFY_OUT, LOW); diff --git a/variants/nano_g2_ultra/platformio.ini b/variants/nano_g2_ultra/platformio.ini index dc8e81ac..af652e3e 100644 --- a/variants/nano_g2_ultra/platformio.ini +++ b/variants/nano_g2_ultra/platformio.ini @@ -53,4 +53,4 @@ lib_deps = densaugeo/base64 @ ~1.4.0 adafruit/Adafruit SH110X @ ~2.1.13 adafruit/Adafruit GFX Library @ ^1.12.1 - + stevemarple/MicroNMEA @ ^2.0.6 diff --git a/variants/nano_g2_ultra/target.cpp b/variants/nano_g2_ultra/target.cpp index d8c5ef8e..33824f62 100644 --- a/variants/nano_g2_ultra/target.cpp +++ b/variants/nano_g2_ultra/target.cpp @@ -1,6 +1,7 @@ #include #include "target.h" #include +#include NanoG2Ultra board; @@ -10,10 +11,11 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +NanoG2UltraSensorManager sensors = NanoG2UltraSensorManager(nmea); #ifdef DISPLAY_CLASS - DISPLAY_CLASS display; +DISPLAY_CLASS display; #endif #ifndef LORA_CR @@ -73,6 +75,107 @@ void radio_set_tx_power(uint8_t dbm) radio.setOutputPower(dbm); } +void NanoG2UltraSensorManager::start_gps() +{ + if (!gps_active) + { + MESH_DEBUG_PRINTLN("starting GPS"); + digitalWrite(PIN_GPS_STANDBY, HIGH); + gps_active = true; + } +} + +void NanoG2UltraSensorManager::stop_gps() +{ + if (gps_active) + { + MESH_DEBUG_PRINTLN("stopping GPS"); + digitalWrite(PIN_GPS_STANDBY, LOW); + gps_active = false; + } +} + +bool NanoG2UltraSensorManager::begin() +{ + Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX); // be sure to tx into rx and rx into tx + Serial1.begin(115200); + + pinMode(PIN_GPS_STANDBY, OUTPUT); + digitalWrite(PIN_GPS_STANDBY, HIGH); // Wake GPS from standby + delay(500); + + // We'll consider GPS detected if we see any data on Serial1 + if (Serial1.available() > 0) + { + MESH_DEBUG_PRINTLN("GPS detected"); + } + else + { + MESH_DEBUG_PRINTLN("No GPS detected"); + } + digitalWrite(GPS_EN, LOW); // Put GPS back into standby mode + return true; +} + +bool NanoG2UltraSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP &telemetry) +{ + if (requester_permissions & TELEM_PERM_LOCATION) + { // does requester have permission? + telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); + } + return true; +} + +void NanoG2UltraSensorManager::loop() +{ + static long next_gps_update = 0; + _location->loop(); + if (millis() > next_gps_update && gps_active) // don't bother if gps position is not enabled + { + if (_location->isValid()) + { + node_lat = ((double)_location->getLatitude()) / 1000000.; + node_lon = ((double)_location->getLongitude()) / 1000000.; + node_altitude = ((double)_location->getAltitude()) / 1000.0; + MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); + } + next_gps_update = millis() + (1000 * 60); // after initial update, only check every minute TODO: should be configurable + } +} + +int NanoG2UltraSensorManager::getNumSettings() const { return 1; } // just one supported: "gps" (power switch) + +const char *NanoG2UltraSensorManager::getSettingName(int i) const +{ + return i == 0 ? "gps" : NULL; +} + +const char *NanoG2UltraSensorManager::getSettingValue(int i) const +{ + if (i == 0) + { + return gps_active ? "1" : "0"; + } + return NULL; +} + +bool NanoG2UltraSensorManager::setSettingValue(const char *name, const char *value) +{ + if (strcmp(name, "gps") == 0) + { + if (strcmp(value, "0") == 0) + { + stop_gps(); + } + else + { + start_gps(); + } + return true; + } + return false; // not supported +} + mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); diff --git a/variants/nano_g2_ultra/target.h b/variants/nano_g2_ultra/target.h index b0af0a8d..e0d891e9 100644 --- a/variants/nano_g2_ultra/target.h +++ b/variants/nano_g2_ultra/target.h @@ -8,16 +8,36 @@ #include #include #ifdef DISPLAY_CLASS - #include +#include #endif +#include + +class NanoG2UltraSensorManager : public SensorManager +{ + bool gps_active = false; + LocationProvider *_location; + + void start_gps(); + void stop_gps(); + +public: + NanoG2UltraSensorManager(LocationProvider &location) : _location(&location) {} + bool begin() override; + bool querySensors(uint8_t requester_permissions, CayenneLPP &telemetry) override; + void loop() override; + int getNumSettings() const override; + const char *getSettingName(int i) const override; + const char *getSettingValue(int i) const override; + bool setSettingValue(const char *name, const char *value) override; +}; extern NanoG2Ultra board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern NanoG2UltraSensorManager sensors; #ifdef DISPLAY_CLASS - extern DISPLAY_CLASS display; +extern DISPLAY_CLASS display; #endif bool radio_init(); diff --git a/variants/nano_g2_ultra/variant.h b/variants/nano_g2_ultra/variant.h index b0cc3100..b8a53fcf 100644 --- a/variants/nano_g2_ultra/variant.h +++ b/variants/nano_g2_ultra/variant.h @@ -130,11 +130,15 @@ External serial flash W25Q16JV_IQ * GPS pins */ +#define HAS_GPS 1 #define GPS_L76K #define PIN_GPS_STANDBY (0 + 13) // An output to wake GPS, low means allow sleep, high means force wake STANDBY #define PIN_GPS_TX (0 + 9) // This is for bits going TOWARDS the CPU #define PIN_GPS_RX (0 + 10) // This is for bits going TOWARDS the GPS +#define GPS_RX_PIN PIN_GPS_RX +#define GPS_TX_PIN PIN_GPS_TX + // #define GPS_THREAD_INTERVAL 50 From be88bea42d45919ecccd11aefefc9ab0b559fed9 Mon Sep 17 00:00:00 2001 From: seagull9000 Date: Tue, 20 May 2025 13:26:40 +1200 Subject: [PATCH 124/154] initial support for generic RTTTL notifier --- examples/companion_radio/buzzer.cpp | 54 +++++++++++++++++++++++++++++ examples/companion_radio/buzzer.h | 36 +++++++++++++++++++ examples/companion_radio/main.cpp | 20 ++++++++++- variants/t1000-e/platformio.ini | 3 ++ 4 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 examples/companion_radio/buzzer.cpp create mode 100644 examples/companion_radio/buzzer.h diff --git a/examples/companion_radio/buzzer.cpp b/examples/companion_radio/buzzer.cpp new file mode 100644 index 00000000..0465bf16 --- /dev/null +++ b/examples/companion_radio/buzzer.cpp @@ -0,0 +1,54 @@ +#ifdef PIN_BUZZER +#include "buzzer.h" + +void genericBuzzer::begin() { + Serial.print("DBG: Setting up buzzer on pin "); + Serial.println(PIN_BUZZER); + #ifdef PIN_BUZZER_EN + pinMode(PIN_BUZZER_EN, OUTPUT); + digitalWrite(PIN_BUZZER_EN, HIGH); + #endif + + quiet(false); + pinMode(PIN_BUZZER, OUTPUT); + startup(); +} + +void genericBuzzer::play(const char *melody) { + if (isPlaying()) // interrupt existing + { + rtttl::stop(); + } + + if (_is_quiet) return; + + rtttl::begin(PIN_BUZZER,melody); +// Serial.print("DBG: Playing melody - isQuiet: "); +// Serial.println(isQuiet()); +} + +bool genericBuzzer::isPlaying() { + return rtttl::isPlaying(); +} + +void genericBuzzer::loop() { + if (!rtttl::done()) rtttl::play(); +} + +void genericBuzzer::startup() { + play(startup_song); +} + +void genericBuzzer::shutdown() { + play(shutdown_song); +} + +void genericBuzzer::quiet(bool buzzer_state) { + _is_quiet = buzzer_state; +} + +bool genericBuzzer::isQuiet() { + return _is_quiet; +} + +#endif // ifdef PIN_BUZZER \ No newline at end of file diff --git a/examples/companion_radio/buzzer.h b/examples/companion_radio/buzzer.h new file mode 100644 index 00000000..9f3f3fd3 --- /dev/null +++ b/examples/companion_radio/buzzer.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +/* class abstracts underlying RTTTL library + + Just a simple imlementation to start. At the moment use same + melody for message and discovery + Suggest enum type for different sounds + - on message + - on discovery + + TODO + - make message ring tone configurable + +*/ +class genericBuzzer +{ + public: + void begin(); // set up buzzer port + void play(const char *melody); // Generic play function + void loop(); // loop driven-nonblocking + void startup(); // play startup sound + void shutdown(); // play shutdown sound + bool isPlaying(); // returns true if a sound is still playing else false + void quiet(bool buzzer_state); // enables or disables the buzzer + bool isQuiet(); // get buzzer state on/off + + private: + // gemini's picks: + const char *startup_song = "Startup:d=4,o=5,b=160:16c6,16e6,8g6"; + const char *shutdown_song = "Shutdown:d=4,o=5,b=100:8g5,16e5,16c5"; + + bool _is_quiet = true; +}; diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 2cfb833b..8b4b437f 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -66,6 +66,11 @@ static UITask ui_task(&board); #endif +#ifdef PIN_BUZZER + #include "buzzer.h" + genericBuzzer buzzer; +#endif + // Believe it or not, this std C function is busted on some platforms! static uint32_t _atoi(const char* sp) { uint32_t n = 0; @@ -484,7 +489,12 @@ class MyMesh : public BaseChatMesh { } void soundBuzzer() { - // TODO + #if defined(PIN_BUZZER) + // gemini's pick + buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7"); + //Serial.println("DBG: Buzzzzzz"); + #endif + } protected: @@ -1553,6 +1563,10 @@ public: ui_task.setHasConnection(_serial->isConnected()); ui_task.loop(); #endif + + #ifdef PIN_BUZZER + if (buzzer.isPlaying()) buzzer.loop(); + #endif } }; @@ -1620,6 +1634,10 @@ void setup() { board.begin(); +#ifdef PIN_BUZZER + buzzer.begin(); +#endif + #ifdef DISPLAY_CLASS DisplayDriver* disp = NULL; if (display.begin()) { diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index 927655af..e61ea939 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -47,9 +47,12 @@ build_flags = ${t1000-e.build_flags} -D RX_BOOSTED_GAIN=true -D RF_SWITCH_TABLE -D DISPLAY_CLASS=NullDisplayDriver + -D PIN_BUZZER=25 + -D PIN_BUZZER_EN=37 ; P1/5 - required for T1000-E build_src_filter = ${t1000-e.build_src_filter} + +<../examples/companion_radio/*.cpp> lib_deps = ${t1000-e.lib_deps} densaugeo/base64 @ ~1.4.0 stevemarple/MicroNMEA @ ^2.0.6 + end2endzone/NonBlockingRTTTL@^1.3.0 \ No newline at end of file From 7e90d386e2312743ad21d80edd7e258982a687d9 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 20 May 2025 11:52:55 +1000 Subject: [PATCH 125/154] * refactored buzzer concept to UITask * moved buzzer.h/cpp to helpers/ui --- examples/companion_radio/UITask.cpp | 16 +++++++++ examples/companion_radio/UITask.h | 8 +++++ examples/companion_radio/main.cpp | 34 +++++-------------- .../helpers/ui}/buzzer.cpp | 0 .../helpers/ui}/buzzer.h | 0 variants/t1000-e/platformio.ini | 1 + variants/t114/variant.h | 2 +- 7 files changed, 35 insertions(+), 26 deletions(-) rename {examples/companion_radio => src/helpers/ui}/buzzer.cpp (100%) rename {examples/companion_radio => src/helpers/ui}/buzzer.h (100%) diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index 5293c0c7..7a031b76 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -53,6 +53,18 @@ void UITask::begin(DisplayDriver* display, NodePrefs* node_prefs, const char* bu // v1.2.3 (1 Jan 2025) sprintf(_version_info, "%s (%s)", version, build_date); + +#ifdef PIN_BUZZER + buzzer.begin(); +#endif +} + +void UITask::soundBuzzer() { +#if defined(PIN_BUZZER) + // gemini's pick + buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7"); + //Serial.println("DBG: Buzzzzzz"); +#endif } void UITask::msgRead(int msgcount) { @@ -248,6 +260,10 @@ void UITask::loop() { buttonHandler(); userLedHandler(); +#ifdef PIN_BUZZER + if (buzzer.isPlaying()) buzzer.loop(); +#endif + if (_display != NULL && _display->isOn()) { static bool _firstBoot = true; if(_firstBoot && millis() >= BOOT_SCREEN_MILLIS) { diff --git a/examples/companion_radio/UITask.h b/examples/companion_radio/UITask.h index 58d68564..30374145 100644 --- a/examples/companion_radio/UITask.h +++ b/examples/companion_radio/UITask.h @@ -4,11 +4,18 @@ #include #include +#ifdef PIN_BUZZER + #include +#endif + #include "NodePrefs.h" class UITask { DisplayDriver* _display; mesh::MainBoard* _board; +#ifdef PIN_BUZZER + genericBuzzer buzzer; +#endif unsigned long _next_refresh, _auto_off; bool _connected; uint32_t _pin_code; @@ -37,5 +44,6 @@ public: void clearMsgPreview(); void msgRead(int msgcount); void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount); + void soundBuzzer(); void loop(); }; diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 8b4b437f..c71fcd81 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -66,11 +66,6 @@ static UITask ui_task(&board); #endif -#ifdef PIN_BUZZER - #include "buzzer.h" - genericBuzzer buzzer; -#endif - // Believe it or not, this std C function is busted on some platforms! static uint32_t _atoi(const char* sp) { uint32_t n = 0; @@ -488,15 +483,6 @@ class MyMesh : public BaseChatMesh { return 0; // queue is empty } - void soundBuzzer() { - #if defined(PIN_BUZZER) - // gemini's pick - buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7"); - //Serial.println("DBG: Buzzzzzz"); - #endif - - } - protected: float getAirtimeBudgetFactor() const override { return _prefs.airtime_factor; @@ -533,7 +519,9 @@ protected: _serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE); } } else { - soundBuzzer(); + #ifdef DISPLAY_CLASS + ui_task.soundBuzzer(); + #endif } saveContacts(); @@ -594,7 +582,9 @@ protected: frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle' _serial->writeFrame(frame, 1); } else { - soundBuzzer(); + #ifdef DISPLAY_CLASS + ui_task.soundBuzzer(); + #endif } #ifdef DISPLAY_CLASS ui_task.newMsg(path_len, from.name, text, offline_queue_len); @@ -645,7 +635,9 @@ protected: frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle' _serial->writeFrame(frame, 1); } else { - soundBuzzer(); + #ifdef DISPLAY_CLASS + ui_task.soundBuzzer(); + #endif } #ifdef DISPLAY_CLASS ui_task.newMsg(path_len, "Public", text, offline_queue_len); @@ -1563,10 +1555,6 @@ public: ui_task.setHasConnection(_serial->isConnected()); ui_task.loop(); #endif - - #ifdef PIN_BUZZER - if (buzzer.isPlaying()) buzzer.loop(); - #endif } }; @@ -1634,10 +1622,6 @@ void setup() { board.begin(); -#ifdef PIN_BUZZER - buzzer.begin(); -#endif - #ifdef DISPLAY_CLASS DisplayDriver* disp = NULL; if (display.begin()) { diff --git a/examples/companion_radio/buzzer.cpp b/src/helpers/ui/buzzer.cpp similarity index 100% rename from examples/companion_radio/buzzer.cpp rename to src/helpers/ui/buzzer.cpp diff --git a/examples/companion_radio/buzzer.h b/src/helpers/ui/buzzer.h similarity index 100% rename from examples/companion_radio/buzzer.h rename to src/helpers/ui/buzzer.h diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index e61ea939..00974208 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -51,6 +51,7 @@ build_flags = ${t1000-e.build_flags} -D PIN_BUZZER_EN=37 ; P1/5 - required for T1000-E build_src_filter = ${t1000-e.build_src_filter} + + + +<../examples/companion_radio/*.cpp> lib_deps = ${t1000-e.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/t114/variant.h b/variants/t114/variant.h index d4802341..a0fd2e4f 100644 --- a/variants/t114/variant.h +++ b/variants/t114/variant.h @@ -109,7 +109,7 @@ //////////////////////////////////////////////////////////////////////////////// // Buzzer -#define PIN_BUZZER (46) +// #define PIN_BUZZER (46) //////////////////////////////////////////////////////////////////////////////// From c31c48025a3618ca48aaeb059bd7ff0fbeed06d7 Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Mon, 19 May 2025 19:28:44 -0700 Subject: [PATCH 126/154] enable external notify for nano g2 ultra uses new non blocking rtttl --- variants/nano_g2_ultra/platformio.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/variants/nano_g2_ultra/platformio.ini b/variants/nano_g2_ultra/platformio.ini index af652e3e..98feb35c 100644 --- a/variants/nano_g2_ultra/platformio.ini +++ b/variants/nano_g2_ultra/platformio.ini @@ -40,6 +40,7 @@ build_flags = -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 -D DISPLAY_CLASS=SH1106Display + -D PIN_BUZZER=4 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 ; -D MESH_PACKET_LOGGING=1 @@ -47,6 +48,7 @@ build_flags = build_src_filter = ${Nano_G2_Ultra.build_src_filter} + + + + +<../examples/companion_radio> lib_deps = ${Nano_G2_Ultra.lib_deps} @@ -54,3 +56,4 @@ lib_deps = adafruit/Adafruit SH110X @ ~2.1.13 adafruit/Adafruit GFX Library @ ^1.12.1 stevemarple/MicroNMEA @ ^2.0.6 + end2endzone/NonBlockingRTTTL@^1.3.0 From 56b84408e48f371f580a4e6ed47c499c5a172bfd Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 20 May 2025 16:29:09 +1000 Subject: [PATCH 127/154] * workaround for nRF + LittleFS glitch with seek/truncate --- examples/companion_radio/main.cpp | 8 ++++---- examples/simple_secure_chat/main.cpp | 4 ++-- src/helpers/CommonCLI.cpp | 2 +- src/helpers/IdentityStore.cpp | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index c71fcd81..fab8a86f 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -271,8 +271,8 @@ class MyMesh : public BaseChatMesh { void saveContacts() { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + _fs->remove("/contacts3"); File file = _fs->open("/contacts3", FILE_O_WRITE); - if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) File file = _fs->open("/contacts3", "w"); #else @@ -336,8 +336,8 @@ class MyMesh : public BaseChatMesh { void saveChannels() { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + _fs->remove("/channels2"); File file = _fs->open("/channels2", FILE_O_WRITE); - if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) File file = _fs->open("/channels2", "w"); #else @@ -393,8 +393,8 @@ class MyMesh : public BaseChatMesh { sprintf(path, "/bl/%s", fname); #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + _fs->remove(path); File f = _fs->open(path, FILE_O_WRITE); - if (f) { f.seek(0); f.truncate(); } #elif defined(RP2040_PLATFORM) File f = _fs->open(path, "w"); #else @@ -915,8 +915,8 @@ public: void savePrefs() { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + _fs->remove("/new_prefs"); File file = _fs->open("/new_prefs", FILE_O_WRITE); - if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) File file = _fs->open("/new_prefs", "w"); #else diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index 1d0d847f..63ff20da 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -127,8 +127,8 @@ class MyMesh : public BaseChatMesh, ContactVisitor { void saveContacts() { #if defined(NRF52_PLATFORM) + _fs->remove("/contacts"); File file = _fs->open("/contacts", FILE_O_WRITE); - if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) File file = _fs->open("/contacts", "w"); #else @@ -341,8 +341,8 @@ public: void savePrefs() { #if defined(NRF52_PLATFORM) + _fs->remove("/node_prefs"); File file = _fs->open("/node_prefs", FILE_O_WRITE); - if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) File file = _fs->open("/node_prefs", "w"); #else diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 29b3a4e2..8b8296f5 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -74,8 +74,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { void CommonCLI::savePrefs(FILESYSTEM* fs) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + fs->remove("/com_prefs"); File file = fs->open("/com_prefs", FILE_O_WRITE); - if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) File file = fs->open("/com_prefs", "w"); #else diff --git a/src/helpers/IdentityStore.cpp b/src/helpers/IdentityStore.cpp index 85f146c5..dc85d69c 100644 --- a/src/helpers/IdentityStore.cpp +++ b/src/helpers/IdentityStore.cpp @@ -47,8 +47,8 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id) { sprintf(filename, "%s/%s.id", _dir, name); #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + _fs->remove(filename); File file = _fs->open(filename, FILE_O_WRITE); - if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) File file = _fs->open(filename, "w"); #else @@ -69,8 +69,8 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id, const sprintf(filename, "%s/%s.id", _dir, name); #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + _fs->remove(filename); File file = _fs->open(filename, FILE_O_WRITE); - if (file) { file.seek(0); file.truncate(); } #elif defined(RP2040_PLATFORM) File file = _fs->open(filename, "w"); #else From f82844f43f5ae01d8942d3ed281a84821a08eda3 Mon Sep 17 00:00:00 2001 From: seagull9000 Date: Tue, 20 May 2025 19:09:49 +1200 Subject: [PATCH 128/154] RTTTL on message types --- examples/companion_radio/UITask.cpp | 19 +++++++++++++++---- examples/companion_radio/UITask.h | 2 +- examples/companion_radio/main.cpp | 4 ++-- src/helpers/ui/buzzer.h | 10 ++++++++++ 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index 7a031b76..7d0fb613 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -59,12 +59,23 @@ void UITask::begin(DisplayDriver* display, NodePrefs* node_prefs, const char* bu #endif } -void UITask::soundBuzzer() { +void UITask::soundBuzzer(buzzerEventType bet) { #if defined(PIN_BUZZER) - // gemini's pick - buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7"); - //Serial.println("DBG: Buzzzzzz"); +switch(bet){ + case buzzerEventType::contactMessage: + // gemini's pick + buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7"); + break; + case buzzerEventType::channelMessage: + case buzzerEventType::roomMessage: + case buzzerEventType::newContactMessage: + case buzzerEventType::noBuzzer: + default: + break; +} #endif + Serial.print("DBG: Buzzzzzz -> "); + Serial.println((int) bet); } void UITask::msgRead(int msgcount) { diff --git a/examples/companion_radio/UITask.h b/examples/companion_radio/UITask.h index 30374145..02125e6f 100644 --- a/examples/companion_radio/UITask.h +++ b/examples/companion_radio/UITask.h @@ -44,6 +44,6 @@ public: void clearMsgPreview(); void msgRead(int msgcount); void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount); - void soundBuzzer(); + void soundBuzzer(buzzerEventType bet = buzzerEventType::noBuzzer); void loop(); }; diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index c71fcd81..14676ad8 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -520,7 +520,7 @@ protected: } } else { #ifdef DISPLAY_CLASS - ui_task.soundBuzzer(); + ui_task.soundBuzzer(buzzerEventType::newContactMessage); #endif } @@ -583,7 +583,7 @@ protected: _serial->writeFrame(frame, 1); } else { #ifdef DISPLAY_CLASS - ui_task.soundBuzzer(); + ui_task.soundBuzzer(buzzerEventType::contactMessage); #endif } #ifdef DISPLAY_CLASS diff --git a/src/helpers/ui/buzzer.h b/src/helpers/ui/buzzer.h index 9f3f3fd3..aa3e418c 100644 --- a/src/helpers/ui/buzzer.h +++ b/src/helpers/ui/buzzer.h @@ -15,6 +15,16 @@ - make message ring tone configurable */ + +enum class buzzerEventType +{ + noBuzzer, + contactMessage, + channelMessage, + roomMessage, + newContactMessage +}; + class genericBuzzer { public: From 7507f889a5871dda08048d945352d21339b47372 Mon Sep 17 00:00:00 2001 From: seagull9000 Date: Tue, 20 May 2025 19:33:21 +1200 Subject: [PATCH 129/154] fix location and naming of enum --- examples/companion_radio/UITask.cpp | 16 ++++++++-------- examples/companion_radio/UITask.h | 12 +++++++++++- examples/companion_radio/main.cpp | 6 +++--- src/helpers/ui/buzzer.cpp | 4 ++-- src/helpers/ui/buzzer.h | 9 --------- 5 files changed, 24 insertions(+), 23 deletions(-) diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index 7d0fb613..f97b47f4 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -59,23 +59,23 @@ void UITask::begin(DisplayDriver* display, NodePrefs* node_prefs, const char* bu #endif } -void UITask::soundBuzzer(buzzerEventType bet) { +void UITask::soundBuzzer(UIEventType bet) { #if defined(PIN_BUZZER) switch(bet){ - case buzzerEventType::contactMessage: + case UIEventType::contactMessage: // gemini's pick buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7"); break; - case buzzerEventType::channelMessage: - case buzzerEventType::roomMessage: - case buzzerEventType::newContactMessage: - case buzzerEventType::noBuzzer: + case UIEventType::channelMessage: + case UIEventType::roomMessage: + case UIEventType::newContactMessage: + case UIEventType::none: default: break; } #endif - Serial.print("DBG: Buzzzzzz -> "); - Serial.println((int) bet); +// Serial.print("DBG: Buzzzzzz -> "); +// Serial.println((int) bet); } void UITask::msgRead(int msgcount) { diff --git a/examples/companion_radio/UITask.h b/examples/companion_radio/UITask.h index 02125e6f..134b5a16 100644 --- a/examples/companion_radio/UITask.h +++ b/examples/companion_radio/UITask.h @@ -10,6 +10,15 @@ #include "NodePrefs.h" + enum class UIEventType +{ + none, + contactMessage, + channelMessage, + roomMessage, + newContactMessage +}; + class UITask { DisplayDriver* _display; mesh::MainBoard* _board; @@ -31,6 +40,7 @@ class UITask { void userLedHandler(); void renderBatteryIndicator(uint16_t batteryMilliVolts); + public: UITask(mesh::MainBoard* board) : _board(board), _display(NULL) { @@ -44,6 +54,6 @@ public: void clearMsgPreview(); void msgRead(int msgcount); void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount); - void soundBuzzer(buzzerEventType bet = buzzerEventType::noBuzzer); + void soundBuzzer(UIEventType bet = UIEventType::none); void loop(); }; diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 14676ad8..a7878b51 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -520,7 +520,7 @@ protected: } } else { #ifdef DISPLAY_CLASS - ui_task.soundBuzzer(buzzerEventType::newContactMessage); + ui_task.soundBuzzer(UIEventType::newContactMessage); #endif } @@ -583,7 +583,7 @@ protected: _serial->writeFrame(frame, 1); } else { #ifdef DISPLAY_CLASS - ui_task.soundBuzzer(buzzerEventType::contactMessage); + ui_task.soundBuzzer(UIEventType::contactMessage); #endif } #ifdef DISPLAY_CLASS @@ -636,7 +636,7 @@ protected: _serial->writeFrame(frame, 1); } else { #ifdef DISPLAY_CLASS - ui_task.soundBuzzer(); + ui_task.soundBuzzer(UIEventType::channelMessage); #endif } #ifdef DISPLAY_CLASS diff --git a/src/helpers/ui/buzzer.cpp b/src/helpers/ui/buzzer.cpp index 0465bf16..ccc18cd3 100644 --- a/src/helpers/ui/buzzer.cpp +++ b/src/helpers/ui/buzzer.cpp @@ -2,8 +2,8 @@ #include "buzzer.h" void genericBuzzer::begin() { - Serial.print("DBG: Setting up buzzer on pin "); - Serial.println(PIN_BUZZER); +// Serial.print("DBG: Setting up buzzer on pin "); +// Serial.println(PIN_BUZZER); #ifdef PIN_BUZZER_EN pinMode(PIN_BUZZER_EN, OUTPUT); digitalWrite(PIN_BUZZER_EN, HIGH); diff --git a/src/helpers/ui/buzzer.h b/src/helpers/ui/buzzer.h index aa3e418c..0a500552 100644 --- a/src/helpers/ui/buzzer.h +++ b/src/helpers/ui/buzzer.h @@ -16,15 +16,6 @@ */ -enum class buzzerEventType -{ - noBuzzer, - contactMessage, - channelMessage, - roomMessage, - newContactMessage -}; - class genericBuzzer { public: From d9c1cffac2878ccf6964c93870df976cd74582eb Mon Sep 17 00:00:00 2001 From: liamcottle Date: Tue, 20 May 2025 20:51:46 +1200 Subject: [PATCH 130/154] allow setting default node name for companion via build flag --- examples/companion_radio/main.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index d97244b2..ecf03b6a 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -863,6 +863,11 @@ public: mesh::Utils::toHex(pub_key_hex, self_id.pub_key, 4); strcpy(_prefs.node_name, pub_key_hex); + // if name is provided as a build flag, use that as default node name instead + #ifdef ADVERT_NAME + strcpy(_prefs.node_name, ADVERT_NAME); + #endif + // load persisted prefs if (_fs->exists("/new_prefs")) { loadPrefsInt("/new_prefs"); // new filename From d42c3f91a2467beadad0ace509e87985db455b28 Mon Sep 17 00:00:00 2001 From: recrof Date: Tue, 20 May 2025 14:05:11 +0200 Subject: [PATCH 131/154] lilygo tbeam sx1276: forgot to add SX127X_CURRENT_LIMIT=120 --- variants/lilygo_tbeam/platformio.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/variants/lilygo_tbeam/platformio.ini b/variants/lilygo_tbeam/platformio.ini index 14ce144e..c471e44c 100644 --- a/variants/lilygo_tbeam/platformio.ini +++ b/variants/lilygo_tbeam/platformio.ini @@ -7,6 +7,7 @@ build_flags = -D LILYGO_TBEAM -D RADIO_CLASS=CustomSX1276 -D WRAPPER_CLASS=CustomSX1276Wrapper + -D SX127X_CURRENT_LIMIT=120 -D LORA_TX_POWER=20 -D P_LORA_TX_LED=4 -D PIN_BOARD_SDA=21 @@ -31,8 +32,6 @@ build_flags = -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 ; -D RADIOLIB_DEBUG_BASIC=1 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 From e14ea72699bd346ddf98fd97682c69182bacb656 Mon Sep 17 00:00:00 2001 From: recrof Date: Tue, 20 May 2025 14:20:42 +0200 Subject: [PATCH 132/154] fix: missing SX126X_CURRENT_LIMIT --- variants/lilygo_tbeam_supreme_SX1262/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index d3447673..e618613c 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -12,6 +12,7 @@ build_flags = -D WRAPPER_CLASS=CustomSX1262Wrapper ;-D DISPLAY_CLASS=SSD1306Display ;Needs to be modified for SH1106 -D SX126X_RX_BOOSTED_GAIN=1 + -D SX126X_CURRENT_LIMIT=140 build_src_filter = ${esp32_base.build_src_filter} +<../variants/lilygo_tbeam_supreme_SX1262> board_build.partitions = min_spiffs.csv ; get around 4mb flash limit From 9a0b6e532629354550bc6a5636e7e12bf8f489f5 Mon Sep 17 00:00:00 2001 From: Adam Mealings Date: Tue, 20 May 2025 13:54:31 +0100 Subject: [PATCH 133/154] Updated to use #if defined... instead of #ifdef --- examples/companion_radio/UITask.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index 6eae88c9..3bbf87f2 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -209,7 +209,7 @@ void UITask::userLedHandler() { } void UITask::buttonHandler() { - #ifdef PIN_USER_BTN || PIN_USER_BTN_ANA + #if defined(PIN_USER_BTN) || defined(PIN_USER_BTN_ANA) static int prev_btn_state = !USER_BTN_PRESSED; static int prev_btn_state_ana = !USER_BTN_PRESSED; static unsigned long btn_state_change_time = 0; @@ -224,7 +224,6 @@ void UITask::buttonHandler() { #ifdef PIN_USER_BTN_ANA btn_state_ana = (analogRead(PIN_USER_BTN_ANA) < 20); // analogRead returns a value hopefully below 20 when button is pressed. #endif - //Serial.println(analogRead(PIN_USER_BTN_ANA)); if (btn_state != prev_btn_state || btn_state_ana != prev_btn_state_ana) { // check for either digital or analogue button change of state if (btn_state == USER_BTN_PRESSED || btn_state_ana == USER_BTN_PRESSED) { // pressed? if (_display != NULL) { From 009173ab9ecfcafb724a30ba9d5fb16a9c5172d4 Mon Sep 17 00:00:00 2001 From: Adam Mealings Date: Tue, 20 May 2025 15:16:56 +0100 Subject: [PATCH 134/154] added missing variable defs and pinmode --- src/helpers/nrf52/RAK4631Board.cpp | 4 ++++ variants/rak4631/platformio.ini | 1 + 2 files changed, 5 insertions(+) diff --git a/src/helpers/nrf52/RAK4631Board.cpp b/src/helpers/nrf52/RAK4631Board.cpp index 8b368734..eb1a42c5 100644 --- a/src/helpers/nrf52/RAK4631Board.cpp +++ b/src/helpers/nrf52/RAK4631Board.cpp @@ -26,6 +26,10 @@ void RAK4631Board::begin() { pinMode(PIN_USER_BTN, INPUT_PULLUP); #endif +#ifdef PIN_USER_BTN_ANA + pinMode(PIN_USER_BTN_ANA, INPUT_PULLUP); +#endif + #if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL); #endif diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index d7584456..c2845fd4 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -7,6 +7,7 @@ build_flags = ${nrf52840_base.build_flags} -I variants/rak4631 -D RAK_4631 -D PIN_USER_BTN=9 + -D PIN_USER_BTN_ANA=31 -D PIN_BOARD_SCL=14 -D PIN_BOARD_SDA=13 -D PIN_OLED_RESET=-1 From 1c8aaebb90445d0190f4f415619d20ff012fe2c5 Mon Sep 17 00:00:00 2001 From: webmonkey Date: Tue, 20 May 2025 19:24:20 +0100 Subject: [PATCH 135/154] Proof-reading fixes to the FAQ Fixed spelling and grammar issues. Also changed the number of stored Room server messages from 16 to 32 --- docs/faq.md | 68 ++++++++++++++++++++++++++--------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 1bb4a0e5..3ac8894d 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -16,7 +16,7 @@ author: https://github.com/LitBomb - [1.2.4. Repeater](#124-repeater) - [1.2.5. Room Server](#125-room-server) - [2. Initial Setup](#2-initial-setup) - - [2.1. Q: How many devices do I need to start using meshcore?](#21-q-how-many-devices-do-i-need-to-start-using-meshcore) + - [2.1. Q: How many devices do I need to start using MeshCore?](#21-q-how-many-devices-do-i-need-to-start-using-meshcore) - [2.2. Q: Does MeshCore cost any money?](#22-q-does-meshcore-cost-any-money) - [2.3. Q: What frequencies are supported by MeshCore?](#23-q-what-frequencies-are-supported-by-meshcore) - [2.4. Q: What is an "advert" in MeshCore?](#24-q-what-is-an-advert-in-meshcore) @@ -40,7 +40,7 @@ author: https://github.com/LitBomb - [4.11. Q: What is the 'Import from Clipboard' feature on the t-deck and is there a way to manually add nodes without having to receive adverts?](#411-q-what-is-the-import-from-clipboard-feature-on-the-t-deck-and-is-there-a-way-to-manually-add-nodes-without-having-to-receive-adverts) - [5. General](#5-general) - [5.1. Q: What are BW, SF, and CR?](#51-q-what-are-bw-sf-and-cr) - - [5.2. Q: Do MeshCore clients repeat?](#52-q-do-meshcore-clients-repeat) + - [5.2. Q: Do MeshCore clients repeat?](#52-q-do--clients-repeat) - [5.3. Q: What happens when a node learns a route via a mobile repeater, and that repeater is gone?](#53-q-what-happens-when-a-node-learns-a-route-via-a-mobile-repeater-and-that-repeater-is-gone) - [5.4. Q: How does a node discovery a path to its destination and then use it to send messages in the future, instead of flooding every message it sends like Meshtastic?](#54-q-how-does-a-node-discovery-a-path-to-its-destination-and-then-use-it-to-send-messages-in-the-future-instead-of-flooding-every-message-it-sends-like-meshtastic) - [5.5. Q: Do public channels always flood? Do private channels always flood?](#55-q-do-public-channels-always-flood-do-private-channels-always-flood) @@ -61,8 +61,8 @@ author: https://github.com/LitBomb - [6. Troubleshooting](#6-troubleshooting) - [6.1. Q: My client says another client or a repeater or a room server was last seen many, many days ago.](#61-q-my-client-says-another-client-or-a-repeater-or-a-room-server-was-last-seen-many-many-days-ago) - [6.2. Q: A repeater or a client or a room server I expect to see on my discover list (on T-Deck) or contact list (on a smart device client) are not listed.](#62-q-a-repeater-or-a-client-or-a-room-server-i-expect-to-see-on-my-discover-list-on-t-deck-or-contact-list-on-a-smart-device-client-are-not-listed) - - [6.3. Q: How to connect to a repeater via BLE (bluetooth)?](#63-q-how-to-connect-to-a-repeater-via-ble-bluetooth) - - [6.4. Q: I can't connect via bluetooth, what is the bluetooth pairing code?](#64-q-i-cant-connect-via-bluetooth-what-is-the-bluetooth-pairing-code) + - [6.3. Q: How to connect to a repeater via BLE (Bluetooth)?](#63-q-how-to-connect-to-a-repeater-via-ble-bluetooth) + - [6.4. Q: I can't connect via Bluetooth, what is the Bluetooth pairing code?](#64-q-i-cant-connect-via-bluetooth-what-is-the-bluetooth-pairing-code) - [6.5. Q: My Heltec V3 keeps disconnecting from my smartphone. It can't hold a solid Bluetooth connection.](#65-q-my-heltec-v3-keeps-disconnecting-from-my-smartphone--it-cant-hold-a-solid-bluetooth-connection) - [7. Other Questions:](#7-other-questions) - [7.1. Q: How to Update repeater and room server firmware over the air?](#71-q-how-to--update-repeater-and-room-server-firmware-over-the-air) @@ -89,7 +89,7 @@ Anyone is able to build anything they like on top of MeshCore without paying any Main web site: [https://meshcore.co.uk/](https://meshcore.co.uk/) Firmware Flasher: https://flasher.meshcore.co.uk/ Phone Client Applications: https://meshcore.co.uk/apps.html - MeshCore Fimrware Github: https://github.com/ripplebiz/MeshCore + MeshCore Fimrware GitHub: https://github.com/ripplebiz/MeshCore NOTE: Andy Kirby has a very useful [intro video](https://www.youtube.com/watch?v=t1qne8uJBAc) for beginners. @@ -119,16 +119,16 @@ Companion radios are for connecting to the Android app or web app as a messenger #### 1.2.4. Repeater Repeaters are used to extend the range of a MeshCore network. Repeater firmware runs on the same devices that run client firmware. A repeater's job is to forward MeshCore packets to the destination device. It does **not** forward or retransmit every packet it receives, unlike other LoRa mesh systems. -A repeater can be remotely administered using a T-Deck running the MeshCore firwmware with remote admistration features unlocked, or from a BLE Companion client connected to a smartphone running the MeshCore app. +A repeater can be remotely administered using a T-Deck running the MeshCore firmware with remote administration features unlocked, or from a BLE Companion client connected to a smartphone running the MeshCore app. #### 1.2.5. Room Server A room server is a simple BBS server for sharing posts. T-Deck devices running MeshCore firmware or a BLE Companion client connected to a smartphone running the MeshCore app can connect to a room server. -room servers store message history on them, and push the stored messages to users. Room servers allow roaming users to come back later and retrieve message history. Contrast to channels, messages are either received when it's sent, or not received and missed if the a room user is out of range. You can think of room servers like email servers where you can come back later and get your emails from your mail server +Room servers store message history on them and push the stored messages to users. Room servers allow roaming users to come back later and retrieve message history. With channels, messages are either received when it's sent, or not received and missed if the channel user is out of range. Room servers are different and more like email servers where you can come back later and get your emails from your mail server. -A room server can be remotely administered using a T-Deck running the MeshCore firwmware with remote admistration features unlocked, or from a BLE Companion client connected to a smartphone running the MeshCore app. +A room server can be remotely administered using a T-Deck running the MeshCore firmware with remote administration features unlocked, or from a BLE Companion client connected to a smartphone running the MeshCore app. -When a client logs into a room server, the client will receive the previously 16 unseen messages. +When a client logs into a room server, the client will receive the previously 32 unseen messages. A room server can also take on the repeater role. To enable repeater role on a room server, use this command: @@ -138,26 +138,26 @@ A room server can also take on the repeater role. To enable repeater role on a ## 2. Initial Setup -### 2.1. Q: How many devices do I need to start using meshcore? -**A:** If you have one supported device, flash the BLE Companion firmware and use your device as a client. You can connect to the device using the Android client via bluetooth (iOS client will be available later). You can start communiating with other MeshCore users near you. +### 2.1. Q: How many devices do I need to start using MeshCore? +**A:** If you have one supported device, flash the BLE Companion firmware and use your device as a client. You can connect to the device using the Android client via Bluetooth (iOS client will be available later). You can start communicating with other MeshCore users near you. -If you have two supported devices, and there are not many MeshCore users near you, flash both of them to BLE Companion firmware so you can use your devices to communiate with your near-by friends and family. +If you have two supported devices, and there are not many MeshCore users near you, flash both to BLE Companion firmware so you can use your devices to communicate with your near-by friends and family. -If you have two supported devices, and there are other MeshcCore users near by, you can flash one of your devices with BLE Companion firmware, and flash another supported device to repeater firmware. Place the repeater high above ground o extend your MeshCore network's reach. +If you have two supported devices, and there are other MeshcCore users nearby, you can flash one of your devices with BLE Companion firmware and flash another supported device to repeater firmware. Place the repeater high above ground to extend your MeshCore network's reach. -After you flashed the latest firmware onto your repeater device, keep the device connected to your computer via USB serial, use the console feature on the web flasher and set the frequency for your region or country, so your client can remote administer the rpeater or room server over RF: +After you flashed the latest firmware onto your repeater device, keep the device connected to your computer via USB serial, use the console feature on the web flasher and set the frequency for your region or country, so your client can remote administer the repeater or room server over RF: `set freq {frequency}` The repeater and room server CLI reference is here: https://github.com/ripplebiz/MeshCore/wiki/Repeater-&-Room-Server-CLI-Reference -If you have more supported devices, you can use your additional deivces with the room server firmware. +If you have more supported devices, you can use your additional devices with the room server firmware. ### 2.2. Q: Does MeshCore cost any money? **A:** All radio firmware versions (e.g. for Heltec V3, RAK, T-1000E, etc) are free and open source developed by Scott at Ripple Radios. -The native Android and iOS client uses the freemium model and is developed by Liam Cottle, developer of meshtastic map at [meshtastic.liamcottle.net](https://meshtastic.liamcottle.net) on [github ](https://github.com/liamcottle/meshtastic-map)and [reticulum-meshchat on github](https://github.com/liamcottle/reticulum-meshchat). +The native Android and iOS client uses the freemium model and is developed by Liam Cottle, developer of meshtastic map at [meshtastic.liamcottle.net](https://meshtastic.liamcottle.net) on [GitHub](https://github.com/liamcottle/meshtastic-map) and [reticulum-meshchat on github](https://github.com/liamcottle/reticulum-meshchat). The T-Deck firmware is free to download and most features are available without cost. To support the firmware developer, you can pay for a registration key to unlock your T-Deck for deeper map zoom and remote server administration over RF using the T-Deck. You do not need to pay for the registration to use your T-Deck for direct messaging and connecting to repeaters and room servers. @@ -178,16 +178,16 @@ the rest of the radio settings are the same for all frequencies: - Coding Rate (CR): 5 - Bandwidth (BW): 250.00 -(originally MeshCore started with SF 10. recently (as of late April 2025) the community has avocated SF 11 also a viable option for longer range but a little slower transmissions. Currently there are MeshCore meshes with SF 10 and SF 11. Liam Cottle's smartphone app's presets now recommend SF 10 for Australia and SF 11 for all other regions and countries. EU and UK has SF 10 and SF 11 presets. Work with your local meshers on diciding with SF number is best for your use cases. In the future, there may be bridge nodes that can bridge SF 10 and SF 11 (or even different frequencies) traffic.) +(Originally MeshCore started with SF 10. recently (as of late April 2025) the community has advocated SF 11 also a viable option for longer range but a little slower transmission. Currently there are MeshCore meshes with SF 10 and SF 11. Liam Cottle's smartphone app's presets now recommend SF 10 for Australia and SF 11 for all other regions and countries. EU and UK has SF 10 and SF 11 presets. Work with your local meshers on deciding with SF number is best for your use cases. In the future, there may be bridge nodes that can bridge SF 10 and SF 11 (or even different frequencies) traffic.) ### 2.4. Q: What is an "advert" in MeshCore? **A:** -Advert means to advertise yourself on the network. In Reticulum terms it would be to announce. In Meshtastic terms it would be the node sending it's node info. +Advert means to advertise yourself on the network. In Reticulum terms it would be to announce. In Meshtastic terms it would be the node sending its node info. MeshCore allows you to manually broadcast your name, position and public encryption key, which is also signed to prevent spoofing. When you click the advert button, it broadcasts that data over LoRa. MeshCore calls that an Advert. There's two ways to advert, "zero hop" and "flood". * Zero hop means your advert is broadcasted out to anyone that can hear it, and that's it. -* Flooded means it's broadcasted out, and then repeated by all the repeaters that hear it. +* Flooded means it's broadcasted out and then repeated by all the repeaters that hear it. MeshCore clients only advertise themselves when the user initiates it. A repeater (and room server?) advertises its presence once every 240 minutes. This interval can be configured using the following command: @@ -259,9 +259,9 @@ You can get the latitude and longitude from Google Maps by right-clicking the lo 8. At this point you can begin flashing using ### 4.2. Q: Why is my T-Deck Plus not getting any satellite lock? -**A:** For T-Deck Plus, the GPS baud rate should be set to **38400**. Also, a number of T-Deck Plus devices were found to have the GPS module installed upside down, with the GPS antenna facing down instead of up. If your T-Deck Plus still doesn't get any satellite lock after setting the baud rate to 38400, you might need to open up the device to check the GPS orientation. +**A:** For T-Deck Plus, the GPS baud rate should be set to **38400**. Also, some T-Deck Plus devices were found to have the GPS module installed upside down, with the GPS antenna facing down instead of up. If your T-Deck Plus still doesn't get any satellite lock after setting the baud rate to 38400, you might need to open the device to check the GPS orientation. -GPS on T-Deck is always enabled. You can skip the "GPS clock sync" and the T-Deck will continue to try to get a GPS lock. You can go to the `GPS Info` screen, you should see the `Sentences:` coutner increasing if the baud rate is correct. +GPS on T-Deck is always enabled. You can skip the "GPS clock sync" and the T-Deck will continue to try to get a GPS lock. You can go to the `GPS Info` screen; you should see the `Sentences:` counter increasing if the baud rate is correct. [Source]([https://](https://discord.com/channels/826570251612323860/1330643963501351004/1356609240302616689)) @@ -294,7 +294,7 @@ Unlock page: ### 4.8. Q: How to decipher the diagnostics screen on T-Deck? -**A: ** Space is tight on T-Deck's screen so the information is a bit cryptic. Format is : +**A: ** Space is tight on T-Deck's screen, so the information is a bit cryptic. The format is : `{hops} l:{packet-length}({payload-len}) t:{packet-type} snr:{n} rssi:{n}` See here for packet-type: [https://github.com/ripplebiz/MeshCore/blob/main/src/Packet.h#L19](https://github.com/ripplebiz/MeshCore/blob/main/src/Packet.h#L19 "https://github.com/ripplebiz/MeshCore/blob/main/src/Packet.h#L19") @@ -337,11 +337,11 @@ Making the bandwidth 2x wider (from BW125 to BW250) allows you to send 2x more b Lowering the spreading factor makes it more difficult for the gateway to receive a transmission, as it will be more sensitive to noise. You could compare this to two people taking in a noisy place (a bar for example). If you’re far from each other, you have to talk slow (SF10), but if you’re close, you can talk faster (SF7) -So it's balancing act between speed of the transmission and resistance to noise. +So, it's balancing act between speed of the transmission and resistance to noise. things network is mainly focused on LoRaWAN, but the LoRa low-level stuff still checks out for any LoRa project ### 5.2. Q: Do MeshCore clients repeat? -**A:** No, MeshCore clients do not repeat. This is the core of MeshCore's messaging-first design. This is to avoid devices flooding the air ware and create endless collisions so messages sent aren't received. +**A:** No, MeshCore clients do not repeat. This is the core of MeshCore's messaging-first design. This is to avoid devices flooding the air ware and create endless collisions, so messages sent aren't received. In MeshCore, only repeaters and room server with '`set repeat on` repeat. ### 5.3. Q: What happens when a node learns a route via a mobile repeater, and that repeater is gone? @@ -352,13 +352,13 @@ In the case if users are moving around frequently, and the paths are breaking, t ### 5.4. Q: How does a node discovery a path to its destination and then use it to send messages in the future, instead of flooding every message it sends like Meshtastic? -Routes are stored in sender's contact list. When you send a message the first time, the message first gets to your destination by flood routing, When your destination node gets the message, it sends back to the sender a delivery report with all repeaters that the original message went through. This delivery report is flood-routed back to you the sender and is a basis for future direct path. when you send the next message, the path will get embedded into the packet and be evaluated by repeaters. if the hop and address of the repeater matches, it will retransmit the message, otherwise it will not retransmit, hence minimizing utilization. +Routes are stored in sender's contact list. When you send a message the first time, the message first gets to your destination by flood routing, when your destination node gets the message, it sends back to the sender a delivery report with all repeaters that the original message went through. This delivery report is flood-routed back to you the sender and is a basis for future direct path. when you send the next message, the path will get embedded into the packet and be evaluated by repeaters. if the hop and address of the repeater matches, it will retransmit the message, otherwise it will not retransmit, hence minimizing utilization. [Source](https://discord.com/channels/826570251612323860/1330643963501351004/1351279141630119996) ### 5.5. Q: Do public channels always flood? Do private channels always flood? -**A:** Yes, group channels are A to B, so there is no defined path. They have to flood. Repeaters can however deny flood traffic up to some hop limit, with the `set flood.max` CLI command. Admistrators of repeaters get to set the rules of their repeaters. +**A:** Yes, group channels are A to B, so there is no defined path. They have to flood. Repeaters can however deny flood traffic up to some hop limit, with the `set flood.max` CLI command. Administrators of repeaters get to set the rules of their repeaters. [Source](https://discord.com/channels/1343693475589263471/1343693475589263474/1350023009527664672) @@ -490,7 +490,7 @@ adafruit-nrfutil --verbose dfu serial --package t1000_e_bootloader-0.9.1-5-g4887 **A:** Yes. See the following: #### 5.14.1. meshcoremqtt -A python based script to send meshore debug and packet capture data to MQTT for analysis +A Python script to send meshore debug and packet capture data to MQTT for analysis https://github.com/Andrew-a-g/meshcoretomqtt #### 5.14.2. MeshCore for Home Assistant @@ -506,7 +506,7 @@ CLI interface to MeshCore companion radio over BLE, TCP, or serial. Uses Pyton https://github.com/fdlamotte/meshcore-cli #### 5.14.5. meshcore.js -A Javascript library for interacting with a MeshCore device running the companion radio firmware +A JavaScript library for interacting with a MeshCore device running the companion radio firmware https://github.com/liamcottle/meshcore.js --- @@ -521,23 +521,23 @@ https://github.com/liamcottle/meshcore.js You can get the epoch time on and use it to set your T-Deck clock. For a repeater and room server, the admin can use a T-Deck to remotely set their clock (clock sync), or use the `time` command in the USB serial console with the server device connected. -### 6.3. Q: How to connect to a repeater via BLE (bluetooth)? -**A:** You can't connect to a device running repeater firmware via bluetooth. Devices running the BLE companion firmware you can connect to it via bluetooth using the android app +### 6.3. Q: How to connect to a repeater via BLE (Bluetooth)? +**A:** You can't connect to a device running repeater firmware via Bluetooth. Devices running the BLE companion firmware you can connect to it via Bluetooth using the android app -### 6.4. Q: I can't connect via bluetooth, what is the bluetooth pairing code? +### 6.4. Q: I can't connect via Bluetooth, what is the Bluetooth pairing code? -**A:** the default bluetooth pairing code is `123456` +**A:** the default Bluetooth pairing code is `123456` ### 6.5. Q: My Heltec V3 keeps disconnecting from my smartphone. It can't hold a solid Bluetooth connection. -**A:** Heltec V3 has a very small coil antenna on its PCB for WiFi and Bluetooth connectivty. It has a very short range, only a few feet. It is possible to remove the coil antenna and replace it with a 31mm wire. The BT range is much improved with the modification. +**A:** Heltec V3 has a very small coil antenna on its PCB for Wi-Fi and Bluetooth connectivity. It has a very short range, only a few feet. It is possible to remove the coil antenna and replace it with a 31mm wire. The BT range is much improved with the modification. --- ## 7. Other Questions: ### 7.1. Q: How to Update repeater and room server firmware over the air? **A:** Only nRF-based RAK4631 and Heltec T114 OTA firmware update are verified using nRF smartphone app. Lilygo T-Echo doesn't work currently. -You can update repeater and room server firmware with a bluetooth connection between your smartphone and your LoRa radio using the nRF app. +You can update repeater and room server firmware with a Bluetooth connection between your smartphone and your LoRa radio using the nRF app. 1. Download the ZIP file for the specific node from the web flasher to your smartphone 2. On the phone client, log on to the repeater as administrator (default password is `password`) to issue the `start ota`command to the repeater or room server to get the device into OTA DFU mode From 7839cb29a16bc083901d1de4091d11690a6120dc Mon Sep 17 00:00:00 2001 From: webmonkey Date: Tue, 20 May 2025 21:42:36 +0100 Subject: [PATCH 136/154] Small fixes --- docs/faq.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 3ac8894d..030c5531 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -40,7 +40,7 @@ author: https://github.com/LitBomb - [4.11. Q: What is the 'Import from Clipboard' feature on the t-deck and is there a way to manually add nodes without having to receive adverts?](#411-q-what-is-the-import-from-clipboard-feature-on-the-t-deck-and-is-there-a-way-to-manually-add-nodes-without-having-to-receive-adverts) - [5. General](#5-general) - [5.1. Q: What are BW, SF, and CR?](#51-q-what-are-bw-sf-and-cr) - - [5.2. Q: Do MeshCore clients repeat?](#52-q-do--clients-repeat) + - [5.2. Q: Do MeshCore clients repeat?](#52-q-do-meshcore-clients-repeat) - [5.3. Q: What happens when a node learns a route via a mobile repeater, and that repeater is gone?](#53-q-what-happens-when-a-node-learns-a-route-via-a-mobile-repeater-and-that-repeater-is-gone) - [5.4. Q: How does a node discovery a path to its destination and then use it to send messages in the future, instead of flooding every message it sends like Meshtastic?](#54-q-how-does-a-node-discovery-a-path-to-its-destination-and-then-use-it-to-send-messages-in-the-future-instead-of-flooding-every-message-it-sends-like-meshtastic) - [5.5. Q: Do public channels always flood? Do private channels always flood?](#55-q-do-public-channels-always-flood-do-private-channels-always-flood) @@ -342,7 +342,7 @@ things network is mainly focused on LoRaWAN, but the LoRa low-level stuff still ### 5.2. Q: Do MeshCore clients repeat? **A:** No, MeshCore clients do not repeat. This is the core of MeshCore's messaging-first design. This is to avoid devices flooding the air ware and create endless collisions, so messages sent aren't received. -In MeshCore, only repeaters and room server with '`set repeat on` repeat. +In MeshCore, only repeaters and room server with `set repeat on` repeat. ### 5.3. Q: What happens when a node learns a route via a mobile repeater, and that repeater is gone? @@ -352,7 +352,7 @@ In the case if users are moving around frequently, and the paths are breaking, t ### 5.4. Q: How does a node discovery a path to its destination and then use it to send messages in the future, instead of flooding every message it sends like Meshtastic? -Routes are stored in sender's contact list. When you send a message the first time, the message first gets to your destination by flood routing, when your destination node gets the message, it sends back to the sender a delivery report with all repeaters that the original message went through. This delivery report is flood-routed back to you the sender and is a basis for future direct path. when you send the next message, the path will get embedded into the packet and be evaluated by repeaters. if the hop and address of the repeater matches, it will retransmit the message, otherwise it will not retransmit, hence minimizing utilization. +Routes are stored in sender's contact list. When you send a message the first time, the message first gets to your destination by flood routing. When your destination node gets the message, it will send back a delivery report to the sender with all repeaters that the original message went through. This delivery report is flood-routed back to you the sender and is a basis for future direct path. When you send the next message, the path will get embedded into the packet and be evaluated by repeaters. If the hop and address of the repeater matches, it will retransmit the message, otherwise it will not retransmit, hence minimizing utilization. [Source](https://discord.com/channels/826570251612323860/1330643963501351004/1351279141630119996) From 98d94d9423066518c9f61aff7a0564b1651ef842 Mon Sep 17 00:00:00 2001 From: Normunds Gavars Date: Thu, 22 May 2025 00:19:00 +0300 Subject: [PATCH 137/154] Remove sensor wrapper classes and simplify. Switch to Adafruit libs for sensors. --- src/helpers/sensors/AHTX0Sensor.h | 39 ------ src/helpers/sensors/BME280Sensor.h | 55 -------- .../sensors/EnvironmentSensorManager.cpp | 118 ++++++++++-------- .../sensors/EnvironmentSensorManager.h | 40 +++--- src/helpers/sensors/INA3221Sensor.h | 71 ----------- variants/promicro/platformio.ini | 15 +-- 6 files changed, 97 insertions(+), 241 deletions(-) delete mode 100644 src/helpers/sensors/AHTX0Sensor.h delete mode 100644 src/helpers/sensors/BME280Sensor.h delete mode 100644 src/helpers/sensors/INA3221Sensor.h diff --git a/src/helpers/sensors/AHTX0Sensor.h b/src/helpers/sensors/AHTX0Sensor.h deleted file mode 100644 index 04af3057..00000000 --- a/src/helpers/sensors/AHTX0Sensor.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once -#include -#include - -#define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address - -static Adafruit_AHTX0 AHTX0; - -class AHTX0Sensor { - bool initialized = false; -public: - void begin() { - if (AHTX0.begin(&Wire, 0, TELEM_AHTX_ADDRESS)) { - MESH_DEBUG_PRINTLN("Found AHT10/AHT20 at address: %02X", TELEM_AHTX_ADDRESS); - initialized = true; - } else { - initialized = false; - MESH_DEBUG_PRINTLN("AHT10/AHT20 was not found at I2C address %02X", TELEM_AHTX_ADDRESS); - } - } - - bool isInitialized() const { return initialized; }; - - float getRelativeHumidity() const { - if (initialized) { - sensors_event_t humidity, temp; - AHTX0.getEvent(&humidity, &temp); - return humidity.relative_humidity; - } - } - - float getTemperature() const { - if (initialized) { - sensors_event_t humidity, temp; - AHTX0.getEvent(&humidity, &temp); - return temp.temperature; - } - } -}; diff --git a/src/helpers/sensors/BME280Sensor.h b/src/helpers/sensors/BME280Sensor.h deleted file mode 100644 index 1226fce5..00000000 --- a/src/helpers/sensors/BME280Sensor.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once -#include -#include - -#define TELEM_BME280_ADDRESS 0x76 // BME280 environmental sensor I2C address -#define SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level - -static Adafruit_BME280 BME280; - -class BME280Sensor { - bool initialized = false; -public: - void begin() { - if (BME280.begin(TELEM_BME280_ADDRESS, &Wire)) { - MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS); - MESH_DEBUG_PRINTLN("BME sensor ID: %02X", BME280.sensorID); - initialized = true; - } else { - initialized = false; - MESH_DEBUG_PRINTLN("BME280 was not found at I2C address %02X", TELEM_BME280_ADDRESS); - } - } - - bool isInitialized() const { return initialized; }; - - float getRelativeHumidity() const { - if (initialized) { - return BME280.readHumidity();; - } - } - - float getTemperature() const { - if (initialized) { - return BME280.readTemperature();; - } - } - - float getBarometricPressure() const { - if (initialized) { - return BME280.readPressure(); - } - } - - float getAltitude() const { - if (initialized) { - return BME280.readAltitude(SEALEVELPRESSURE_HPA); - } - } - - void setTemperatureCompensation(float delta) { - if (initialized) { - BME280.setTemperatureCompensation(delta); - } - } -}; diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index cf931470..6d39e905 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -1,10 +1,49 @@ #include "EnvironmentSensorManager.h" +static Adafruit_AHTX0 AHTX0; +static Adafruit_BME280 BME280; +static Adafruit_INA3221 INA3221; +static Adafruit_INA219 INA219(TELEM_INA219_ADDRESS); + bool EnvironmentSensorManager::begin() { - INA3221_sensor.begin(); - INA219_sensor.begin(); - AHTX0_sensor.begin(); - BME280_sensor.begin(); + + if (INA3221.begin(TELEM_INA3221_ADDRESS, &Wire)) { + MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", TELEM_INA3221_ADDRESS); + MESH_DEBUG_PRINTLN("%04X %04X", INA3221.getDieID(), INA3221.getManufacturerID()); + + for(int i = 0; i < 3; i++) { + INA3221.setShuntResistance(i, TELEM_INA3221_SHUNT_VALUE); + } + INA3221_initialized = true; + } else { + INA3221_initialized = false; + MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS); + } + + if (INA219.begin(&Wire)) { + MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", TELEM_INA219_ADDRESS); + INA219_initialized = true; + } else { + INA219_initialized = false; + MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS); + } + + if (AHTX0.begin(&Wire, 0, TELEM_AHTX_ADDRESS)) { + MESH_DEBUG_PRINTLN("Found AHT10/AHT20 at address: %02X", TELEM_AHTX_ADDRESS); + AHTX0_initialized = true; + } else { + AHTX0_initialized = false; + MESH_DEBUG_PRINTLN("AHT10/AHT20 was not found at I2C address %02X", TELEM_AHTX_ADDRESS); + } + + if (BME280.begin(TELEM_BME280_ADDRESS, &Wire)) { + MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS); + MESH_DEBUG_PRINTLN("BME sensor ID: %02X", BME280.sensorID); + BME280_initialized = true; + } else { + BME280_initialized = false; + MESH_DEBUG_PRINTLN("BME280 was not found at I2C address %02X", TELEM_BME280_ADDRESS); + } return true; } @@ -12,63 +51,40 @@ bool EnvironmentSensorManager::begin() { bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { next_available_channel = TELEM_CHANNEL_SELF + 1; if (requester_permissions & TELEM_PERM_ENVIRONMENT) { - if (INA3221_sensor.isInitialized()) { - for(int i = 0; i < 3; i++) { + if (INA3221_initialized) { + for(int i = 0; i < TELEM_INA3221_NUM_CHANNELS; i++) { // add only enabled INA3221 channels to telemetry - if (INA3221_sensor.getChannelEnabled(i)) { - telemetry.addVoltage(next_available_channel, INA3221_sensor.getVoltage(i)); - telemetry.addCurrent(next_available_channel, INA3221_sensor.getCurrent(i)); - telemetry.addPower(next_available_channel, INA3221_sensor.getPower(i)); + if (INA3221.isChannelEnabled(i)) { + float voltage = INA3221.getBusVoltage(i); + float current = INA3221.getCurrentAmps(i); + telemetry.addVoltage(next_available_channel, voltage); + telemetry.addCurrent(next_available_channel, current); + telemetry.addPower(next_available_channel, voltage * current); next_available_channel++; } } } - if (INA219_sensor.isInitialized()) { - telemetry.addVoltage(next_available_channel, INA219_sensor.getVoltage()); - telemetry.addCurrent(next_available_channel, INA219_sensor.getCurrent()); - telemetry.addPower(next_available_channel, INA219_sensor.getPower()); + if (INA219_initialized) { + telemetry.addVoltage(next_available_channel, INA219.getBusVoltage_V()); + telemetry.addCurrent(next_available_channel, INA219.getCurrent_mA() / 1000); + telemetry.addPower(next_available_channel, INA219.getPower_mW() / 1000); next_available_channel++; } - if (AHTX0_sensor.isInitialized()) { - telemetry.addTemperature(TELEM_CHANNEL_SELF, AHTX0_sensor.getTemperature()); - telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, AHTX0_sensor.getRelativeHumidity()); + if (AHTX0_initialized) { + sensors_event_t humidity, temp; + AHTX0.getEvent(&humidity, &temp); + + telemetry.addTemperature(TELEM_CHANNEL_SELF, temp.temperature); + telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, humidity.relative_humidity); } - if (BME280_sensor.isInitialized()) { - telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280_sensor.getTemperature()); - telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME280_sensor.getRelativeHumidity()); - telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME280_sensor.getBarometricPressure()); - telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280_sensor.getAltitude()); + + if (BME280_initialized) { + telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280.readTemperature()); + telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME280.readHumidity()); + telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME280.readPressure()); + telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA)); } } return true; -} - -int EnvironmentSensorManager::getNumSettings() const { - return NUM_SENSOR_SETTINGS; -} - -const char* EnvironmentSensorManager::getSettingName(int i) const { - if (i >= 0 && i < NUM_SENSOR_SETTINGS) { - return INA3221_CHANNEL_NAMES[i]; - } - return NULL; -} - -const char* EnvironmentSensorManager::getSettingValue(int i) const { - if (i >= 0 && i < NUM_SENSOR_SETTINGS) { - return INA3221_sensor.getChannelEnabled(i) == true ? "1" : "0"; - } - return NULL; -} - -bool EnvironmentSensorManager::setSettingValue(const char* name, const char* value) { - for (int i = 0; i < NUM_SENSOR_SETTINGS; i++) { - if (strcmp(name, INA3221_CHANNEL_NAMES[i]) == 0) { - bool channel_enabled = strcmp(value, "1") == 0 ? true : false; - INA3221_sensor.setChannelEnabled(i, channel_enabled); - return true; - } - } - return false; } \ No newline at end of file diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 318de001..2852805a 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -1,33 +1,37 @@ #pragma once #include -#include "INA3221Sensor.h" -#include "INA219Sensor.h" -#include "AHTX0Sensor.h" -#include "BME280Sensor.h" +#include +#include +#include +#include +#include #define NUM_SENSOR_SETTINGS 3 -#define TELEM_INA3221_SETTING_CH1 "INA3221-1" -#define TELEM_INA3221_SETTING_CH2 "INA3221-2" -#define TELEM_INA3221_SETTING_CH3 "INA3221-3" +#define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address +#define TELEM_BME280_ADDRESS 0x76 // BME280 environmental sensor I2C address +#define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current sensor I2C address +#define TELEM_INA219_ADDRESS 0x40 // INA219 single channel current sensor I2C address + +#define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts +#define TELEM_INA3221_NUM_CHANNELS 3 + +#define TELEM_INA219_SHUNT_VALUE 0.100 // shunt value in ohms (may differ between manufacturers) +#define TELEM_INA219_MAX_CURRENT 5 + +#define TELEM_BME280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level class EnvironmentSensorManager : public SensorManager { -// INA3221 channels in telemetry -const char * INA3221_CHANNEL_NAMES[NUM_SENSOR_SETTINGS] = { TELEM_INA3221_SETTING_CH1, TELEM_INA3221_SETTING_CH2, TELEM_INA3221_SETTING_CH3}; - protected: int next_available_channel = TELEM_CHANNEL_SELF + 1; - INA3221Sensor INA3221_sensor; - AHTX0Sensor AHTX0_sensor; - INA219Sensor INA219_sensor; - BME280Sensor BME280_sensor; + + bool INA3221_initialized = false; + bool INA219_initialized = false; + bool BME280_initialized = false; + bool AHTX0_initialized = false; public: EnvironmentSensorManager(){}; bool begin() override; bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; - int getNumSettings() const override; - const char* getSettingName(int i) const override; - const char* getSettingValue(int i) const override; - bool setSettingValue(const char* name, const char* value) override; }; diff --git a/src/helpers/sensors/INA3221Sensor.h b/src/helpers/sensors/INA3221Sensor.h deleted file mode 100644 index f3618a9f..00000000 --- a/src/helpers/sensors/INA3221Sensor.h +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#include -#include - -#define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current sensor I2C address -#define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts - -#define NUM_CHANNELS 3 - -static INA3221 INA_3221(TELEM_INA3221_ADDRESS, &Wire); - -class INA3221Sensor { - bool initialized = false; - -public: - void begin() { - if (INA_3221.begin()) { - MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", INA_3221.getAddress()); - MESH_DEBUG_PRINTLN("%04X %04X %04X", INA_3221.getDieID(), INA_3221.getManufacturerID(), INA_3221.getConfiguration()); - - for(int i = 0; i < 3; i++) { - INA_3221.setShuntR(i, TELEM_INA3221_SHUNT_VALUE); - } - initialized = true; - } else { - initialized = false; - MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS); - } - } - - bool isInitialized() const { return initialized; } - - int numChannels() const { return NUM_CHANNELS; } - - float getVoltage(int channel) const { - if (initialized && channel >=0 && channel < NUM_CHANNELS) { - return INA_3221.getBusVoltage(channel); - } - return 0; - } - - float getCurrent(int channel) const { - if (initialized && channel >=0 && channel < NUM_CHANNELS) { - return INA_3221.getCurrent(channel); - } - return 0; - } - - float getPower (int channel) const { - if (initialized && channel >=0 && channel < NUM_CHANNELS) { - return INA_3221.getPower(channel); - } - return 0; - } - - bool setChannelEnabled(int channel, bool enabled) { - if (initialized && channel >=0 && channel < NUM_CHANNELS) { - INA_3221.enableChannel(channel); - return true; - } - return false; - } - - bool getChannelEnabled(int channel) const { - if (initialized && channel >=0 && channel < NUM_CHANNELS) { - return INA_3221.getEnableChannel(channel); - } - return false; - } -}; diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 7b6771e0..e70223e2 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -19,10 +19,10 @@ build_src_filter = ${nrf52840_base.build_src_filter} +<../variants/promicro> lib_deps= ${nrf52840_base.lib_deps} adafruit/Adafruit SSD1306 @ ^2.5.13 - robtillaart/INA3221 @ ^0.4.1 - robtillaart/INA219 @ ^0.4.1 - adafruit/Adafruit AHTX0@^2.0.5 - adafruit/Adafruit BME280 Library@^2.3.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 [env:Faketec_Repeater] extends = Faketec @@ -124,9 +124,10 @@ build_src_filter = + +<../variants/promicro> lib_deps= ${nrf52840_base.lib_deps} - robtillaart/INA3221 @ ^0.4.1 - robtillaart/INA219 @ ^0.4.1 - adafruit/Adafruit AHTX0@^2.0.5 + 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 [env:ProMicroLLCC68_Repeater] extends = ProMicroLLCC68 From af0d55548c46f7085813e068e19d987319b68128 Mon Sep 17 00:00:00 2001 From: Normunds Gavars Date: Thu, 22 May 2025 00:22:43 +0300 Subject: [PATCH 138/154] Remove unused defines --- src/helpers/sensors/EnvironmentSensorManager.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 2852805a..e335a413 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -17,9 +17,6 @@ #define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts #define TELEM_INA3221_NUM_CHANNELS 3 -#define TELEM_INA219_SHUNT_VALUE 0.100 // shunt value in ohms (may differ between manufacturers) -#define TELEM_INA219_MAX_CURRENT 5 - #define TELEM_BME280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level class EnvironmentSensorManager : public SensorManager { From 375a31a436b15468a8c7da2018173293658e530f Mon Sep 17 00:00:00 2001 From: Normunds Gavars Date: Thu, 22 May 2025 00:28:20 +0300 Subject: [PATCH 139/154] Remove INA219 wrapper --- src/helpers/sensors/INA219Sensor.h | 48 ------------------------------ 1 file changed, 48 deletions(-) delete mode 100644 src/helpers/sensors/INA219Sensor.h diff --git a/src/helpers/sensors/INA219Sensor.h b/src/helpers/sensors/INA219Sensor.h deleted file mode 100644 index 1f641ab2..00000000 --- a/src/helpers/sensors/INA219Sensor.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include -#include - -#define TELEM_INA219_ADDRESS 0x40 // INA219 single channel current sensor I2C address -#define TELEM_INA219_SHUNT_VALUE 0.100 // shunt value in ohms (may differ between manufacturers) -#define TELEM_INA219_MAX_CURRENT 5 - -static INA219 INA_219(TELEM_INA219_ADDRESS, &Wire); - -class INA219Sensor { - bool initialized = false; -public: - void begin() { - if (INA_219.begin()) { - MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", INA_219.getAddress()); - INA_219.setMaxCurrentShunt(TELEM_INA219_MAX_CURRENT, TELEM_INA219_SHUNT_VALUE); - initialized = true; - } else { - initialized = false; - MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS); - } - } - - bool isInitialized() const { return initialized; } - - float getVoltage() const { - if (initialized) { - return INA_219.getBusVoltage(); - } - return 0; - } - - float getCurrent() const { - if (initialized) { - return INA_219.getCurrent(); - } - return 0; - } - - float getPower() const { - if (initialized) { - return INA_219.getPower(); - } - return 0; - } -}; From 5a0ac2a0310c67ea56b0e30a9fb6a8623f0066db Mon Sep 17 00:00:00 2001 From: Normunds Gavars Date: Thu, 22 May 2025 00:35:03 +0300 Subject: [PATCH 140/154] Add sensors to build path for ProMicroLLCC68 --- variants/promicro/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index e70223e2..568e20d7 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -122,6 +122,7 @@ build_flags = ${nrf52840_base.build_flags} build_src_filter = ${nrf52840_base.build_src_filter} + + + +<../variants/promicro> lib_deps= ${nrf52840_base.lib_deps} adafruit/Adafruit INA3221 Library @ ^1.0.1 From c4df0ed1c55522871d7915d89ad29e04757d1a7a Mon Sep 17 00:00:00 2001 From: Normunds Gavars Date: Thu, 22 May 2025 00:38:51 +0300 Subject: [PATCH 141/154] Remove NUM_SENSOR_SETTINGS --- src/helpers/sensors/EnvironmentSensorManager.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index e335a413..fa3a1a1e 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -7,8 +7,6 @@ #include #include -#define NUM_SENSOR_SETTINGS 3 - #define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address #define TELEM_BME280_ADDRESS 0x76 // BME280 environmental sensor I2C address #define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current sensor I2C address From 02b6f4a2856cdc4b2fad4d675d8444637aa92e81 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 22 May 2025 15:26:30 +1000 Subject: [PATCH 142/154] * Companion: telemetry_mode_env added to prefs --- examples/companion_radio/NodePrefs.h | 1 + examples/companion_radio/main.cpp | 13 ++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 2f209c54..09d04266 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -19,6 +19,7 @@ struct NodePrefs { // persisted to file uint8_t tx_power_dbm; uint8_t telemetry_mode_base; uint8_t telemetry_mode_loc; + uint8_t telemetry_mode_env; float rx_delay_base; uint32_t ble_pin; }; diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index ecf03b6a..03a8c90a 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -661,6 +661,12 @@ protected: permissions |= cp & TELEM_PERM_LOCATION; } + if (_prefs.telemetry_mode_env == TELEM_MODE_ALLOW_ALL) { + permissions |= TELEM_PERM_ENVIRONMENT; + } else if (_prefs.telemetry_mode_env == TELEM_MODE_ALLOW_FLAGS) { + permissions |= cp & TELEM_PERM_ENVIRONMENT; + } + if (permissions & TELEM_PERM_BASE) { // only respond if base permission bit is set telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); @@ -824,7 +830,7 @@ public: file.read((uint8_t *) &_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 file.read((uint8_t *) &_prefs.telemetry_mode_base, sizeof(_prefs.telemetry_mode_base)); // 69 file.read((uint8_t *) &_prefs.telemetry_mode_loc, sizeof(_prefs.telemetry_mode_loc)); // 70 - file.read(pad, 1); // 71 + file.read((uint8_t *) &_prefs.telemetry_mode_env, sizeof(_prefs.telemetry_mode_env)); // 71 file.read((uint8_t *) &_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 file.read(pad, 4); // 76 file.read((uint8_t *) &_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 @@ -945,7 +951,7 @@ public: file.write((uint8_t *) &_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 file.write((uint8_t *) &_prefs.telemetry_mode_base, sizeof(_prefs.telemetry_mode_base)); // 69 file.write((uint8_t *) &_prefs.telemetry_mode_loc, sizeof(_prefs.telemetry_mode_loc)); // 70 - file.write(pad, 1); // 71 + file.write((uint8_t *) &_prefs.telemetry_mode_env, sizeof(_prefs.telemetry_mode_env)); // 71 file.write((uint8_t *) &_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 file.write(pad, 4); // 76 file.write((uint8_t *) &_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 @@ -990,7 +996,7 @@ public: memcpy(&out_frame[i], &lon, 4); i += 4; out_frame[i++] = 0; // reserved out_frame[i++] = 0; // reserved - out_frame[i++] = (_prefs.telemetry_mode_loc << 2) | (_prefs.telemetry_mode_base); // v5+ + out_frame[i++] = (_prefs.telemetry_mode_env << 4) | (_prefs.telemetry_mode_loc << 2) | (_prefs.telemetry_mode_base); // v5+ out_frame[i++] = _prefs.manual_add_contacts; uint32_t freq = _prefs.freq * 1000; @@ -1282,6 +1288,7 @@ public: if (len >= 3) { _prefs.telemetry_mode_base = cmd_frame[2] & 0x03; // v5+ _prefs.telemetry_mode_loc = (cmd_frame[2] >> 2) & 0x03; + _prefs.telemetry_mode_env = (cmd_frame[2] >> 4) & 0x03; } savePrefs(); writeOKFrame(); From a466d3cf80d91ab7a16e882dd7ba29af6dce81b9 Mon Sep 17 00:00:00 2001 From: taco Date: Thu, 22 May 2025 15:36:20 +1000 Subject: [PATCH 143/154] added serial GPS support to EnvironmentSensorClass based on T114 serial GPS and EnvironmentSensorClass. --- .../sensors/EnvironmentSensorManager.cpp | 93 ++++++++++++++++++- .../sensors/EnvironmentSensorManager.h | 19 +++- variants/promicro/platformio.ini | 6 +- variants/promicro/target.cpp | 4 +- 4 files changed, 117 insertions(+), 5 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 6d39e905..c82235c6 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -1,5 +1,8 @@ #include "EnvironmentSensorManager.h" + +#include + static Adafruit_AHTX0 AHTX0; static Adafruit_BME280 BME280; static Adafruit_INA3221 INA3221; @@ -44,11 +47,14 @@ bool EnvironmentSensorManager::begin() { BME280_initialized = false; MESH_DEBUG_PRINTLN("BME280 was not found at I2C address %02X", TELEM_BME280_ADDRESS); } - + initSerialGPS(); return true; } bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { + if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission? + telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, 0.0f); + } next_available_channel = TELEM_CHANNEL_SELF + 1; if (requester_permissions & TELEM_PERM_ENVIRONMENT) { if (INA3221_initialized) { @@ -86,5 +92,90 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen } } + initSerialGPS(); + return true; +} + + +int EnvironmentSensorManager::getNumSettings() const { + return gps_detected ? 1 : 0; // only show GPS setting if GPS is detected +} + +const char* EnvironmentSensorManager::getSettingName(int i) const { + return (gps_detected && i == 0) ? "gps" : NULL; +} + +const char* EnvironmentSensorManager::getSettingValue(int i) const { + if (gps_detected && i == 0) { + return gps_active ? "1" : "0"; + } + return NULL; +} + +bool EnvironmentSensorManager::setSettingValue(const char* name, const char* value) { + if (gps_detected && strcmp(name, "gps") == 0) { + if (strcmp(value, "0") == 0) { + stop_gps(); + } else { + start_gps(); + } + return true; + } + return false; // not supported +} + + + + +void EnvironmentSensorManager::initSerialGPS() { + Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX); + Serial1.begin(9600); + + // Try to detect if GPS is physically connected to determine if we should expose the setting + pinMode(PIN_GPS_EN, OUTPUT); + digitalWrite(PIN_GPS_EN, HIGH); // Power on GPS + + // Give GPS a moment to power up and send data + delay(1000); + + // We'll consider GPS detected if we see any data on Serial1 + gps_detected = (Serial1.available() > 0); + + if (gps_detected) { + MESH_DEBUG_PRINTLN("GPS detected"); + digitalWrite(PIN_GPS_EN, LOW); // Power off GPS until the setting is changed + } else { + MESH_DEBUG_PRINTLN("No GPS detected"); + digitalWrite(PIN_GPS_EN, LOW); + } + +} + + +void EnvironmentSensorManager::start_gps() { + gps_active = true; + pinMode(PIN_GPS_EN, OUTPUT); + digitalWrite(PIN_GPS_EN, HIGH); +} + +void EnvironmentSensorManager::stop_gps() { + gps_active = false; + pinMode(PIN_GPS_EN, OUTPUT); + digitalWrite(PIN_GPS_EN, LOW); +} + +void EnvironmentSensorManager::loop() { + static long next_gps_update = 0; + + _location->loop(); + + if (millis() > next_gps_update) { + if (_location->isValid()) { + node_lat = ((double)_location->getLatitude())/1000000.; + node_lon = ((double)_location->getLongitude())/1000000.; + MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); + } + next_gps_update = millis() + 1000; + } } \ No newline at end of file diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index fa3a1a1e..51ac3051 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -25,8 +26,22 @@ protected: bool INA219_initialized = false; bool BME280_initialized = false; bool AHTX0_initialized = false; + + bool gps_active = false; + bool gps_detected = false; + LocationProvider* _location; + + void start_gps(); + void stop_gps(); + void initSerialGPS(); + public: - EnvironmentSensorManager(){}; + EnvironmentSensorManager(LocationProvider &location): _location(&location){}; bool begin() override; - bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; + bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; + void loop() override; + int getNumSettings() const override; + const char* getSettingName(int i) const override; + const char* getSettingValue(int i) const override; + bool setSettingValue(const char* name, const char* value) override; }; diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 568e20d7..9e2ee075 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -13,6 +13,9 @@ build_flags = ${nrf52840_base.build_flags} -D PIN_BOARD_SDA=8 -D PIN_OLED_RESET=-1 -D PIN_USER_BTN=6 + -D PIN_GPS_RX=3 + -D PIN_GPS_TX=4 + -D PIN_GPS_EN=5 build_src_filter = ${nrf52840_base.build_src_filter} + + @@ -23,7 +26,8 @@ lib_deps= ${nrf52840_base.lib_deps} adafruit/Adafruit INA219 @ ^1.2.3 adafruit/Adafruit AHTX0 @ ^2.0.5 adafruit/Adafruit BME280 Library @ ^2.3.0 - + stevemarple/MicroNMEA @ ^2.0.6 + [env:Faketec_Repeater] extends = Faketec build_src_filter = ${Faketec.build_src_filter} diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index 841bd1dc..666f43f5 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -1,6 +1,7 @@ #include #include "target.h" #include +#include PromicroBoard board; @@ -10,7 +11,8 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -EnvironmentSensorManager sensors; +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; From f9473235c618306fcba5284a6b948bba3a3c4ad1 Mon Sep 17 00:00:00 2001 From: Florent Date: Thu, 22 May 2025 11:46:05 +0200 Subject: [PATCH 144/154] rak3x72 : first commit --- boards/rak3172.json | 33 ++++++++++++ src/helpers/stm32/InternalFileSystem.cpp | 3 ++ variants/rak3x72/platformio.ini | 33 ++++++++++++ variants/rak3x72/target.cpp | 67 ++++++++++++++++++++++++ variants/rak3x72/target.h | 27 ++++++++++ variants/rak3x72/variant.h | 5 ++ variants/wio-e5/platformio.ini | 5 +- variants/wio-e5/target.cpp | 8 ++- variants/wio-e5/target.h | 9 +++- 9 files changed, 185 insertions(+), 5 deletions(-) create mode 100644 boards/rak3172.json create mode 100644 variants/rak3x72/platformio.ini create mode 100644 variants/rak3x72/target.cpp create mode 100644 variants/rak3x72/target.h create mode 100644 variants/rak3x72/variant.h diff --git a/boards/rak3172.json b/boards/rak3172.json new file mode 100644 index 00000000..dbb70b03 --- /dev/null +++ b/boards/rak3172.json @@ -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://store.rakwireless.com/products/wisduo-lpwan-module-rak3172", + "vendor": "RAK" +} diff --git a/src/helpers/stm32/InternalFileSystem.cpp b/src/helpers/stm32/InternalFileSystem.cpp index 47706bac..2714ec6b 100644 --- a/src/helpers/stm32/InternalFileSystem.cpp +++ b/src/helpers/stm32/InternalFileSystem.cpp @@ -126,6 +126,9 @@ InternalFileSystem::InternalFileSystem(void) bool InternalFileSystem::begin(void) { + #ifdef FORMAT_FS + this->format(); + #endif // failed to mount, erase all sector then format and mount again if ( !Adafruit_LittleFS::begin() ) { diff --git a/variants/rak3x72/platformio.ini b/variants/rak3x72/platformio.ini new file mode 100644 index 00000000..94e8f595 --- /dev/null +++ b/variants/rak3x72/platformio.ini @@ -0,0 +1,33 @@ +[rak3x72] +extends = stm32_base +board = rak3172 +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 + -I variants/rak3x72 +build_src_filter = ${stm32_base.build_src_filter} + +<../variants/rak3x72> + +[env:rak3x72-repeater] +extends = rak3x72 +build_flags = ${rak3x72.build_flags} + -D LORA_TX_POWER=22 + -D ADVERT_NAME='"RAK3x72 Repeater"' + -D ADMIN_PASSWORD='"password"' +build_src_filter = ${rak3x72.build_src_filter} + +<../examples/simple_repeater/main.cpp> + +[env:rak3x72_companion_radio_usb] +extends = rak3x72 +build_flags = ${rak3x72.build_flags} +; -D FORMAT_FS=true + -D LORA_TX_POWER=22 + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 +build_src_filter = ${rak3x72.build_src_filter} + +<../examples/companion_radio/*.cpp> +lib_deps = ${rak3x72.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/rak3x72/target.cpp b/variants/rak3x72/target.cpp new file mode 100644 index 00000000..4cb5929a --- /dev/null +++ b/variants/rak3x72/target.cpp @@ -0,0 +1,67 @@ +#include +#include "target.h" +#include + +RAK3x72Board 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 + +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, 8, 0, 0); // TCXO set to 0 for RAK3172 + + 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 +} diff --git a/variants/rak3x72/target.h b/variants/rak3x72/target.h new file mode 100644 index 00000000..a4c47bc6 --- /dev/null +++ b/variants/rak3x72/target.h @@ -0,0 +1,27 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include + +class RAK3x72Board : public STM32Board { +public: + const char* getManufacturerName() const override { + return "RAK 3x72"; + } +}; + +extern RAK3x72Board 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(); diff --git a/variants/rak3x72/variant.h b/variants/rak3x72/variant.h new file mode 100644 index 00000000..4405be0b --- /dev/null +++ b/variants/rak3x72/variant.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +#undef RNG diff --git a/variants/wio-e5/platformio.ini b/variants/wio-e5/platformio.ini index 4de13e7f..d8f7954f 100644 --- a/variants/wio-e5/platformio.ini +++ b/variants/wio-e5/platformio.ini @@ -6,6 +6,7 @@ build_flags = ${stm32_base.build_flags} -D RADIO_CLASS=CustomSTM32WLx -D WRAPPER_CLASS=CustomSTM32WLxWrapper -D SPI_INTERFACES_COUNT=0 + -D RX_BOOSTED_GAIN=true -I variants/wio-e5 build_src_filter = ${stm32_base.build_src_filter} +<../variants/wio-e5> @@ -13,7 +14,7 @@ build_src_filter = ${stm32_base.build_src_filter} [env:wio-e5-repeater] extends = lora_e5 build_flags = ${lora_e5.build_flags} - -D LORA_TX_POWER=20 + -D LORA_TX_POWER=22 -D ADVERT_NAME='"WIO-E5 Repeater"' -D ADMIN_PASSWORD='"password"' build_src_filter = ${lora_e5.build_src_filter} @@ -22,7 +23,7 @@ build_src_filter = ${lora_e5.build_src_filter} [env:wio-e5_companion_radio_usb] extends = lora_e5 build_flags = ${lora_e5.build_flags} - -D LORA_TX_POWER=20 + -D LORA_TX_POWER=22 -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 build_src_filter = ${lora_e5.build_src_filter} diff --git a/variants/wio-e5/target.cpp b/variants/wio-e5/target.cpp index aee0dafb..8ccbe384 100644 --- a/variants/wio-e5/target.cpp +++ b/variants/wio-e5/target.cpp @@ -2,7 +2,7 @@ #include "target.h" #include -STM32Board board; +WIOE5Board board; RADIO_CLASS radio = new STM32WLx_Module(); @@ -42,7 +42,11 @@ bool radio_init() { Serial.println(status); return false; // fail } - + + #ifdef RX_BOOSTED_GAIN + radio.setRxBoostedGainMode(RX_BOOSTED_GAIN); + #endif + radio.setCRC(1); return true; // success diff --git a/variants/wio-e5/target.h b/variants/wio-e5/target.h index f2dae108..cc6131cf 100644 --- a/variants/wio-e5/target.h +++ b/variants/wio-e5/target.h @@ -8,7 +8,14 @@ #include #include -extern STM32Board board; +class WIOE5Board : public STM32Board { +public: + const char* getManufacturerName() const override { + return "Seeed Wio E5"; + } +}; + +extern WIOE5Board board; extern WRAPPER_CLASS radio_driver; extern VolatileRTCClock rtc_clock; extern SensorManager sensors; From c7fe21184020a9968e6c61e77087f3637368b53a Mon Sep 17 00:00:00 2001 From: Florent de Lamotte Date: Thu, 22 May 2025 16:24:20 +0200 Subject: [PATCH 145/154] rak3x72 : report bat voltage --- variants/rak3x72/target.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/variants/rak3x72/target.h b/variants/rak3x72/target.h index a4c47bc6..c39b69b1 100644 --- a/variants/rak3x72/target.h +++ b/variants/rak3x72/target.h @@ -8,11 +8,19 @@ #include #include +#define PIN_VBAT_READ A0 +#define ADC_MULTIPLIER (5 * 1.73 * 1000) + class RAK3x72Board : public STM32Board { public: const char* getManufacturerName() const override { return "RAK 3x72"; } + + uint16_t getBattMilliVolts() override { + uint32_t raw = analogRead(PIN_VBAT_READ); + return (ADC_MULTIPLIER * raw) / 1024; + } }; extern RAK3x72Board board; From 23f54dd9248655fcad9e17d3ba90adb544c94dff Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 23 May 2025 14:34:34 +1000 Subject: [PATCH 146/154] fix: remove stray initSerialGPS call --- src/helpers/sensors/EnvironmentSensorManager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index c82235c6..2594d91b 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -92,7 +92,6 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen } } - initSerialGPS(); return true; } From efa2b4b1b73e02f4da010fb47eff948d6870d19a Mon Sep 17 00:00:00 2001 From: seagull9000 Date: Fri, 23 May 2025 17:58:13 +1200 Subject: [PATCH 147/154] RTTTL-tone-for-Channel-Message I was a bit remiss in removing the tone for channel message event - this puts one in. So: DM event - plays a tone (per current) Channel Message - new shorter tone All others aren't defined at present. Need muting function before we get too carried away. --- examples/companion_radio/UITask.cpp | 2 + src/helpers/ui/button.cpp | 292 ++++++++++++++++++++++++++++ src/helpers/ui/button.h | 79 ++++++++ 3 files changed, 373 insertions(+) create mode 100644 src/helpers/ui/button.cpp create mode 100644 src/helpers/ui/button.h diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index f97b47f4..58ffc4d0 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -67,6 +67,8 @@ switch(bet){ buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7"); break; case UIEventType::channelMessage: + buzzer.play("kerplop:d=16,o=6,b=120:32g#,32c#"); + break; case UIEventType::roomMessage: case UIEventType::newContactMessage: case UIEventType::none: diff --git a/src/helpers/ui/button.cpp b/src/helpers/ui/button.cpp new file mode 100644 index 00000000..7e715b44 --- /dev/null +++ b/src/helpers/ui/button.cpp @@ -0,0 +1,292 @@ +#include "Button.h" + +Button::Button() : + _pin(0), + _inputMode(INPUT_PULLUP), + _buttonPressedState(HIGH), // Assuming INPUT_PULLUP, so HIGH is unpressed + _lastButtonState(HIGH), + _buttonState(false), // Debounced state: false is unpressed + _lastDebouncedState(false), + _lastDebounceTime(0), + _pressStartTime(0), + _releaseTime(0), + _currentSequenceCount(0), + _lastPressEndTime(0), + _callbackCount(0) +{ + for (uint8_t i = 0; i < MAX_PRESSES_IN_SEQUENCE; ++i) { + _currentSequence[i] = ButtonPressType::NONE; + } + for (uint8_t i = 0; i < MAX_CALLBACKS; ++i) { + _registeredCallbacks[i].count = 0; + _registeredCallbacks[i].callback = nullptr; + _registeredCallbacks[i].triggeredInSequence = false; + for (uint8_t j = 0; j < MAX_PRESSES_IN_SEQUENCE; ++j) { + _registeredCallbacks[i].sequence[j] = ButtonPressType::NONE; + } + } +} + +void Button::attach(uint8_t pin, uint8_t inputMode) { + _pin = pin; + _inputMode = inputMode; + pinMode(_pin, _inputMode); + if (_inputMode == INPUT_PULLUP) { + _buttonPressedState = LOW; // Pressed is LOW for PULLUP + _lastButtonState = HIGH; // Initial unpressed state + } else { + _buttonPressedState = HIGH; // Pressed is HIGH for PULLDOWN or regular INPUT + _lastButtonState = LOW; // Initial unpressed state + } + _lastDebouncedState = false; // Not pressed + _buttonState = false; // Not pressed +} + +void Button::update() { + if (_pin == 0) return; // Not attached + + bool reading = digitalRead(_pin); + unsigned long currentTime = millis(); + + // Debounce logic + if (reading != _lastButtonState) { + _lastDebounceTime = currentTime; + } + _lastButtonState = reading; + + if ((currentTime - _lastDebounceTime) > DEBOUNCE_DELAY) { + bool currentDebouncedState = (reading == _buttonPressedState); + + // Edge detection + if (currentDebouncedState != _lastDebouncedState) { + _lastDebouncedState = currentDebouncedState; + + if (currentDebouncedState) { // Button pressed + _pressStartTime = currentTime; + // If a new press starts too long after the previous one, reset sequence + if (_currentSequenceCount > 0 && (currentTime - _lastPressEndTime) > SHORT_PRESS_TIMEOUT) { + _resetSequence(); + } + } else { // Button released + _releaseTime = currentTime; + unsigned long pressDuration = _releaseTime - _pressStartTime; + ButtonPressType pressType = _determinePressType(pressDuration); + + if (pressType != ButtonPressType::NONE) { + _addPressToSequence(pressType); + _lastPressEndTime = currentTime; // Mark end time for next press timeout + _evaluateSequences(); + } + } + } + } + + // Check for sequence timeout if a sequence is in progress + if (_currentSequenceCount > 0 && (currentTime - _lastPressEndTime) > SHORT_PRESS_TIMEOUT) { + // If we timed out, and haven't triggered anything specific from the partial sequence, + // it's time to reset. Callbacks for partial matches should have already fired in _evaluateSequences. + // This handles the case where a sequence like S1 is defined, and the user presses S, then waits. + // We need to ensure S1 callback fires if it exists, even if S2 or S3 are also defined. + // _evaluateSequences called on release handles this. This timeout mainly resets for the next input. + _resetSequence(); + } +} + + +ButtonPressType Button::_determinePressType(unsigned long pressDuration) { + if (pressDuration >= LONG_LONG_PRESS_MIN_DURATION) { + return ButtonPressType::LL; + } else if (pressDuration >= LONGISH_PRESS_MIN_DURATION && pressDuration < LONGISH_PRESS_MAX_DURATION) { + return ButtonPressType::LS; + } else if (pressDuration > DEBOUNCE_DELAY && pressDuration <= SHORT_PRESS_MAX_DURATION) { // Ensure it's more than debounce + return ButtonPressType::S; + } + return ButtonPressType::NONE; +} + +void Button::_addPressToSequence(ButtonPressType pressType) { + if (_currentSequenceCount < MAX_PRESSES_IN_SEQUENCE) { + // Specific handling for LL: it can only be LL1 + if (pressType == ButtonPressType::LL) { + if (_currentSequenceCount == 0) { // LL only valid as the first press in its "sequence" + _currentSequence[_currentSequenceCount++] = pressType; + } else { + // An LL press occurred after other presses, which is not a defined LL1 sequence. + // Treat as an invalid sequence progression, so reset. + _resetSequence(); + // Potentially start a new sequence with this LL if it was intended as LL1. + // For simplicity now, we just reset. More complex logic could try to salvage. + } + } else if (pressType == ButtonPressType::LS) { + // LS can form sequences up to LS3 + // Check if previous was also LS or if sequence is empty + if (_currentSequenceCount == 0 || _currentSequence[_currentSequenceCount -1] == ButtonPressType::LS) { + _currentSequence[_currentSequenceCount++] = pressType; + } else { + // Mixing LS with S in a sequence is not explicitly defined as LS1/2/3 or S1/2/3 + // So, reset the sequence and start new with this LS. + _resetSequence(); + _currentSequence[_currentSequenceCount++] = pressType; + } + } else if (pressType == ButtonPressType::S) { + // S can form sequences up to S3 + // Check if previous was also S or if sequence is empty + if (_currentSequenceCount == 0 || _currentSequence[_currentSequenceCount -1] == ButtonPressType::S) { + _currentSequence[_currentSequenceCount++] = pressType; + } else { + // Mixing S with LS in a sequence is not S1/2/3 or LS1/2/3 + // So, reset the sequence and start new with this S. + _resetSequence(); + _currentSequence[_currentSequenceCount++] = pressType; + } + } + } else { + // Sequence array is full, this shouldn't normally happen if MAX_PRESSES_IN_SEQUENCE is large enough. + // Reset to be safe. + _resetSequence(); + // And add the current press as the start of a new sequence. + if (pressType != ButtonPressType::NONE) { // ensure it's a valid press + _currentSequence[_currentSequenceCount++] = pressType; + } + } +} + +void Button::_evaluateSequences() { + if (_currentSequenceCount == 0) return; + + bool exactMatchFound = false; + + for (uint8_t i = 0; i < _callbackCount; ++i) { + ButtonSequence& registered = _registeredCallbacks[i]; + if (registered.callback == nullptr) continue; + + if (registered.count == _currentSequenceCount) { + bool match = true; + for (uint8_t j = 0; j < _currentSequenceCount; ++j) { + if (_currentSequence[j] != registered.sequence[j]) { + match = false; + break; + } + } + if (match) { + // Exact match found + if (!registered.triggeredInSequence) { // Check if already triggered for this evaluation pass + registered.callback(); + registered.triggeredInSequence = true; // Mark as triggered for this pass + } + exactMatchFound = true; + // For an exact match, we generally reset the current sequence + // as it has been fully consumed by a callback. + } + } + } + + // After checking all callbacks, reset their triggeredInSequence flags for the next evaluation pass + for (uint8_t i = 0; i < _callbackCount; ++i) { + _registeredCallbacks[i].triggeredInSequence = false; + } + + // If an exact match was found, the sequence is "consumed" and should be reset. + // This allows, for example, S1 to trigger, then S2 to trigger, then S3. + // If we don't reset, S1 would trigger, then S1+S2 would require an (S,S) callback. + // The current logic means S triggers S1 callback, then if another S comes, (S,S) triggers S2 callback. + if (exactMatchFound) { + _resetSequence(); + } else { + // If no exact match, but the sequence is full (e.g., S, S, S and no S3 callback) + // or if it's an LL press (which is always a sequence of 1) + // then we should reset to allow new sequences. + if (_currentSequenceCount >= MAX_PRESSES_IN_SEQUENCE || + (_currentSequenceCount > 0 && _currentSequence[_currentSequenceCount -1] == ButtonPressType::LL)) { + _resetSequence(); + } + // Also, if the current sequence is S,S and there's no S2 callback, + // but there IS an S1 callback, S1 should have triggered. + // The current _evaluateSequences iterates all callbacks. + // The problem is if a user defines S and S,S. + // Press S: S callback fires. Current sequence is {S}. + // Press S again: Current sequence becomes {S,S}. S,S callback fires. Sequence resets. This is good. + + // What if user defines S, S,S, S,S,S. + // Press 1: S fires. Seq={S}. + // Press 2: S,S fires. Seq={S,S}. + // Press 3: S,S,S fires. Seq={S,S,S}. + // The reset on exact match handles this. + + // What if only S,S,S is defined? + // Press 1: Seq={S}. No exact match. No reset. + // Press 2: Seq={S,S}. No exact match. No reset. + // Press 3: Seq={S,S,S}. S,S,S fires. Seq resets. Good. + + // What if only S is defined? + // Press 1: Seq={S}. S fires. Seq resets. Good. + // User presses S, S, S. + // S -> S fires, seq resets. + // S -> S fires, seq resets. + // S -> S fires, seq resets. + // This seems correct. A sequence ends once it matches *a* callback. + + // Exception: LL is always a single event. + if (_currentSequenceCount == 1 && _currentSequence[0] == ButtonPressType::LL) { + // LL callback would have fired if registered. Sequence should reset. + // This is handled by `exactMatchFound` leading to reset, + // or by the `_currentSequence[_currentSequenceCount -1] == ButtonPressType::LL` condition above. + } + } +} + + +void Button::_resetSequence() { + _currentSequenceCount = 0; + for (uint8_t i = 0; i < MAX_PRESSES_IN_SEQUENCE; ++i) { + _currentSequence[i] = ButtonPressType::NONE; + } + // _lastPressEndTime is not reset here, because it's used to see if a *new* press + // is starting a new sequence or continuing one (within SHORT_PRESS_TIMEOUT). + // When a sequence fully resets (e.g. due to timeout or completion), + // the _pressStartTime of the *next* press will be compared to the _lastPressEndTime + // of the *previous* press that completed/timed-out the sequence. +} + +bool Button::onPress(ButtonPressType pressType, ButtonCallback callback) { + if (pressType == ButtonPressType::NONE) return false; + // This is a shorthand for a sequence of one. + return onPressSequence(&pressType, 1, callback); +} + +bool Button::onPressSequence(const ButtonPressType types[], uint8_t count, ButtonCallback callback) { + if (_callbackCount >= MAX_CALLBACKS || count == 0 || count > MAX_PRESSES_IN_SEQUENCE) { + return false; // No space or invalid sequence + } + + // Restriction: LL can only be a sequence of 1. + if (count > 1) { + for(uint8_t i=0; i < count; ++i) { + if (types[i] == ButtonPressType::LL) return false; // LL cannot be part of a multi-press sequence definition. + } + } + // Restriction: Sequences must be homogenous (all S or all LS), or a single LL. + if (count > 1) { + ButtonPressType firstType = types[0]; + if (firstType == ButtonPressType::LL) return false; // Should have been caught above. + for (uint8_t i = 1; i < count; ++i) { + if (types[i] != firstType || types[i] == ButtonPressType::LL) { + // Heterogeneous sequence (e.g. S, LS) or LL in multi-press is not allowed by problem def. + return false; + } + } + } + + + _registeredCallbacks[_callbackCount].count = count; + for (uint8_t i = 0; i < count; ++i) { + _registeredCallbacks[_callbackCount].sequence[i] = types[i]; + } + for (uint8_t i = count; i < MAX_PRESSES_IN_SEQUENCE; ++i) { // Zero out rest + _registeredCallbacks[_callbackCount].sequence[i] = ButtonPressType::NONE; + } + _registeredCallbacks[_callbackCount].callback = callback; + _registeredCallbacks[_callbackCount].triggeredInSequence = false; + _callbackCount++; + return true; +} \ No newline at end of file diff --git a/src/helpers/ui/button.h b/src/helpers/ui/button.h new file mode 100644 index 00000000..0f0e3b92 --- /dev/null +++ b/src/helpers/ui/button.h @@ -0,0 +1,79 @@ +#ifndef BUTTON_H +#define BUTTON_H + +#include + +// Maximum number of presses in a sequence (e.g., S3, LS3) +#define MAX_PRESSES_IN_SEQUENCE 3 +// Maximum number of sequences that can be registered +#define MAX_CALLBACKS 10 +// Debounce delay in milliseconds +#define DEBOUNCE_DELAY 50 +// Time to distinguish between short presses in a sequence (ms) +#define SHORT_PRESS_TIMEOUT 500 +// Short press max duration (ms) +#define SHORT_PRESS_MAX_DURATION (SHORT_PRESS_TIMEOUT - 1) // Must be less than timeout +// Longish press min duration (ms) +#define LONGISH_PRESS_MIN_DURATION 1000 +// Longish press max duration (ms) +#define LONGISH_PRESS_MAX_DURATION 2500 +// Long-Long press min duration (ms) +#define LONG_LONG_PRESS_MIN_DURATION 4000 + + +// Enum to represent different press types +enum class ButtonPressType { + NONE, + S, // Short Press + LS, // Longish Press + LL // Long-Long Press +}; + +// Type alias for the callback function +typedef void (*ButtonCallback)(); + +struct ButtonSequence { + ButtonPressType sequence[MAX_PRESSES_IN_SEQUENCE]; + uint8_t count; // Number of presses in this specific sequence + ButtonCallback callback; + bool triggeredInSequence; // To prevent multiple triggers during a sequence evaluation +}; + +class Button { +public: + Button(); + void attach(uint8_t pin, uint8_t inputMode = INPUT_PULLUP); + void update(); + + // Methods to add callbacks + // For single press types (e.g., one long-long press) + bool onPress(ButtonPressType pressType, ButtonCallback callback); + // For sequences of presses (e.g., S, S, S or LS, LS) + bool onPressSequence(const ButtonPressType types[], uint8_t count, ButtonCallback callback); + +private: + uint8_t _pin; + uint8_t _inputMode; + bool _buttonPressedState; // Physical state of the button (HIGH/LOW) + bool _lastButtonState; // Previous physical state for change detection + bool _buttonState; // Debounced button state (true for pressed) + bool _lastDebouncedState; // Last debounced state + + unsigned long _lastDebounceTime; + unsigned long _pressStartTime; + unsigned long _releaseTime; + + ButtonPressType _currentSequence[MAX_PRESSES_IN_SEQUENCE]; + uint8_t _currentSequenceCount; + unsigned long _lastPressEndTime; // Time when the last press in a potential sequence ended + + ButtonSequence _registeredCallbacks[MAX_CALLBACKS]; + uint8_t _callbackCount; + + void _resetSequence(); + void _addPressToSequence(ButtonPressType pressType); + void _evaluateSequences(); + ButtonPressType _determinePressType(unsigned long pressDuration); +}; + +#endif // BUTTON_H \ No newline at end of file From 400c4353dc72141140906c9cfcdf06123c093994 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 23 May 2025 17:08:23 +1000 Subject: [PATCH 148/154] REFACTOR: sensors are now wrapped in conditionals --- .../sensors/EnvironmentSensorManager.cpp | 119 +++++++++++------- .../sensors/EnvironmentSensorManager.h | 40 ++++-- variants/promicro/platformio.ini | 7 +- 3 files changed, 110 insertions(+), 56 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 2594d91b..b5f5cd48 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -1,15 +1,45 @@ #include "EnvironmentSensorManager.h" - -#include - +#if ENV_INCLUDE_AHTX0 static Adafruit_AHTX0 AHTX0; +#endif +#if ENV_INCLUDE_BME280 static Adafruit_BME280 BME280; +#endif +#if ENV_INCLUDE_INA3221 static Adafruit_INA3221 INA3221; +#endif +#if ENV_INCLUDE_INA219 static Adafruit_INA219 INA219(TELEM_INA219_ADDRESS); +#endif bool EnvironmentSensorManager::begin() { + #if ENV_INCLUDE_GPS + initBasicGPS(); + #endif + #if ENV_INCLUDE_AHTX0 + if (AHTX0.begin(&Wire, 0, TELEM_AHTX_ADDRESS)) { + MESH_DEBUG_PRINTLN("Found AHT10/AHT20 at address: %02X", TELEM_AHTX_ADDRESS); + AHTX0_initialized = true; + } else { + AHTX0_initialized = false; + MESH_DEBUG_PRINTLN("AHT10/AHT20 was not found at I2C address %02X", TELEM_AHTX_ADDRESS); + } + #endif + + #if ENV_INCLUDE_BME280 + if (BME280.begin(TELEM_BME280_ADDRESS, &Wire)) { + MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS); + MESH_DEBUG_PRINTLN("BME sensor ID: %02X", BME280.sensorID()); + BME280_initialized = true; + } else { + BME280_initialized = false; + MESH_DEBUG_PRINTLN("BME280 was not found at I2C address %02X", TELEM_BME280_ADDRESS); + } + #endif + + #if ENV_INCLUDE_INA3221 if (INA3221.begin(TELEM_INA3221_ADDRESS, &Wire)) { MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", TELEM_INA3221_ADDRESS); MESH_DEBUG_PRINTLN("%04X %04X", INA3221.getDieID(), INA3221.getManufacturerID()); @@ -22,7 +52,9 @@ bool EnvironmentSensorManager::begin() { INA3221_initialized = false; MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS); } + #endif + #if ENV_INCLUDE_INA219 if (INA219.begin(&Wire)) { MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", TELEM_INA219_ADDRESS); INA219_initialized = true; @@ -30,33 +62,39 @@ bool EnvironmentSensorManager::begin() { INA219_initialized = false; MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS); } + #endif - if (AHTX0.begin(&Wire, 0, TELEM_AHTX_ADDRESS)) { - MESH_DEBUG_PRINTLN("Found AHT10/AHT20 at address: %02X", TELEM_AHTX_ADDRESS); - AHTX0_initialized = true; - } else { - AHTX0_initialized = false; - MESH_DEBUG_PRINTLN("AHT10/AHT20 was not found at I2C address %02X", TELEM_AHTX_ADDRESS); - } - - if (BME280.begin(TELEM_BME280_ADDRESS, &Wire)) { - MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS); - MESH_DEBUG_PRINTLN("BME sensor ID: %02X", BME280.sensorID); - BME280_initialized = true; - } else { - BME280_initialized = false; - MESH_DEBUG_PRINTLN("BME280 was not found at I2C address %02X", TELEM_BME280_ADDRESS); - } - initSerialGPS(); return true; } bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { - if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission? - telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, 0.0f); - } next_available_channel = TELEM_CHANNEL_SELF + 1; + + if (requester_permissions & TELEM_PERM_LOCATION) { + telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, 0.0f); // allow lat/lon via telemetry even if no GPS is detected + } + if (requester_permissions & TELEM_PERM_ENVIRONMENT) { + + #if ENV_INCLUDE_AHTX0 + if (AHTX0_initialized) { + sensors_event_t humidity, temp; + AHTX0.getEvent(&humidity, &temp); + telemetry.addTemperature(TELEM_CHANNEL_SELF, temp.temperature); + telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, humidity.relative_humidity); + } + #endif + + #if ENV_INCLUDE_BME280 + if (BME280_initialized) { + telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280.readTemperature()); + telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME280.readHumidity()); + telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME280.readPressure()); + telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA)); + } + #endif + + #if ENV_INCLUDE_INA3221 if (INA3221_initialized) { for(int i = 0; i < TELEM_INA3221_NUM_CHANNELS; i++) { // add only enabled INA3221 channels to telemetry @@ -70,49 +108,46 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen } } } + #endif + + #if ENV_INCLUDE_INA219 if (INA219_initialized) { telemetry.addVoltage(next_available_channel, INA219.getBusVoltage_V()); telemetry.addCurrent(next_available_channel, INA219.getCurrent_mA() / 1000); telemetry.addPower(next_available_channel, INA219.getPower_mW() / 1000); next_available_channel++; } - if (AHTX0_initialized) { - sensors_event_t humidity, temp; - AHTX0.getEvent(&humidity, &temp); + #endif - telemetry.addTemperature(TELEM_CHANNEL_SELF, temp.temperature); - telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, humidity.relative_humidity); - } - - if (BME280_initialized) { - telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280.readTemperature()); - telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME280.readHumidity()); - telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME280.readPressure()); - telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA)); - } } - return true; } int EnvironmentSensorManager::getNumSettings() const { + #if ENV_INCLUDE_GPS return gps_detected ? 1 : 0; // only show GPS setting if GPS is detected + #endif } const char* EnvironmentSensorManager::getSettingName(int i) const { + #if ENV_INCLUDE_GPS return (gps_detected && i == 0) ? "gps" : NULL; + #endif } const char* EnvironmentSensorManager::getSettingValue(int i) const { + #if ENV_INCLUDE_GPS if (gps_detected && i == 0) { return gps_active ? "1" : "0"; } + #endif return NULL; } bool EnvironmentSensorManager::setSettingValue(const char* name, const char* value) { + #if ENV_INCLUDE_GPS if (gps_detected && strcmp(name, "gps") == 0) { if (strcmp(value, "0") == 0) { stop_gps(); @@ -121,13 +156,12 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val } return true; } + #endif return false; // not supported } - - - -void EnvironmentSensorManager::initSerialGPS() { +#if ENV_INCLUDE_GPS +void EnvironmentSensorManager::initBasicGPS() { Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX); Serial1.begin(9600); @@ -177,4 +211,5 @@ void EnvironmentSensorManager::loop() { } next_gps_update = millis() + 1000; } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 51ac3051..2a2d6b81 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -1,45 +1,59 @@ #pragma once +#include #include #include -#include -#include -#include -#include -#include + +#if ENV_INCLUDE_AHTX0 #define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address +#include +#endif +#if ENV_INCLUDE_BME280 #define TELEM_BME280_ADDRESS 0x76 // BME280 environmental sensor I2C address +#define TELEM_BME280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level +#include +#endif +#if ENV_INCLUDE_INA3221 #define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current sensor I2C address -#define TELEM_INA219_ADDRESS 0x40 // INA219 single channel current sensor I2C address - #define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts #define TELEM_INA3221_NUM_CHANNELS 3 +#include +#endif +#if ENV_INCLUDE_INA219 +#define TELEM_INA219_ADDRESS 0x40 // INA219 single channel current sensor I2C address +#include +#endif + -#define TELEM_BME280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level class EnvironmentSensorManager : public SensorManager { protected: int next_available_channel = TELEM_CHANNEL_SELF + 1; + bool AHTX0_initialized = false; + bool BME280_initialized = false; bool INA3221_initialized = false; bool INA219_initialized = false; - bool BME280_initialized = false; - bool AHTX0_initialized = false; - bool gps_active = false; - bool gps_detected = false; LocationProvider* _location; + bool gps_detected = false; + bool gps_active = false; + #if ENV_INCLUDE_GPS void start_gps(); void stop_gps(); - void initSerialGPS(); + void initBasicGPS(); + #endif + public: EnvironmentSensorManager(LocationProvider &location): _location(&location){}; bool begin() override; bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; + #if ENV_INCLUDE_GPS void loop() override; + #endif int getNumSettings() const override; const char* getSettingName(int i) const override; const char* getSettingValue(int i) const override; diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 9e2ee075..11f73d81 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -16,6 +16,11 @@ build_flags = ${nrf52840_base.build_flags} -D PIN_GPS_RX=3 -D PIN_GPS_TX=4 -D PIN_GPS_EN=5 + -D ENV_INCLUDE_GPS=1 + -D ENV_INCLUDE_AHTX0=1 + -D ENV_INCLUDE_BME280=1 + -D ENV_INCLUDE_INA3221=1 + -D ENV_INCLUDE_INA219=1 build_src_filter = ${nrf52840_base.build_src_filter} + + @@ -103,7 +108,7 @@ build_flags = ${Faketec.build_flags} -D OFFLINE_QUEUE_SIZE=256 -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 + -D MESH_DEBUG=1 build_src_filter = ${Faketec.build_src_filter} + +<../examples/companion_radio> From 5630533d220171e538663dc915a3dcc7ee8265cc Mon Sep 17 00:00:00 2001 From: seagull9000 Date: Fri, 23 May 2025 17:58:13 +1200 Subject: [PATCH 149/154] RTTTL-tone-for-Channel-Message I was a bit remiss in removing the tone for channel message event - this puts one in. So: DM event - plays a tone (per current) Channel Message - new shorter tone All others aren't defined at present. Need muting function before we get too carried away. --- examples/companion_radio/UITask.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index f97b47f4..58ffc4d0 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -67,6 +67,8 @@ switch(bet){ buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7"); break; case UIEventType::channelMessage: + buzzer.play("kerplop:d=16,o=6,b=120:32g#,32c#"); + break; case UIEventType::roomMessage: case UIEventType::newContactMessage: case UIEventType::none: From 5987e95ce909fb2e4a9aba381b4d8e3924eb2f9f Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 23 May 2025 18:58:45 +1000 Subject: [PATCH 150/154] refactor: more conditionals for GPS also re-added some missing returns. --- src/helpers/sensors/EnvironmentSensorManager.cpp | 6 ++++-- src/helpers/sensors/EnvironmentSensorManager.h | 6 +++++- variants/promicro/target.cpp | 13 ++++++++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index b5f5cd48..c300cbe4 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -127,14 +127,16 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen int EnvironmentSensorManager::getNumSettings() const { #if ENV_INCLUDE_GPS - return gps_detected ? 1 : 0; // only show GPS setting if GPS is detected + return gps_detected ? 1 : 0; // only show GPS setting if GPS is detected #endif + return NULL; } const char* EnvironmentSensorManager::getSettingName(int i) const { #if ENV_INCLUDE_GPS - return (gps_detected && i == 0) ? "gps" : NULL; + return (gps_detected && i == 0) ? "gps" : NULL; #endif + return NULL; } const char* EnvironmentSensorManager::getSettingValue(int i) const { diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 2a2d6b81..2a72f2d0 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -36,11 +36,11 @@ protected: bool INA3221_initialized = false; bool INA219_initialized = false; - LocationProvider* _location; bool gps_detected = false; bool gps_active = false; #if ENV_INCLUDE_GPS + LocationProvider* _location; void start_gps(); void stop_gps(); void initBasicGPS(); @@ -48,7 +48,11 @@ protected: public: + #if ENV_INCLUDE_GPS EnvironmentSensorManager(LocationProvider &location): _location(&location){}; + #else + EnvironmentSensorManager(){}; + #endif bool begin() override; bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; #if ENV_INCLUDE_GPS diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index 666f43f5..2369bd7a 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -1,7 +1,9 @@ #include #include "target.h" #include -#include + +#if ENV_INCLUDE_GPS +#endif PromicroBoard board; @@ -11,8 +13,13 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); -EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#if ENV_INCLUDE_GPS + #include + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif #ifdef DISPLAY_CLASS DISPLAY_CLASS display; From 0defa837d8394932463044ccfc62e87d259cdf99 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 23 May 2025 19:12:32 +1000 Subject: [PATCH 151/154] * EnvironmentSensorManager: some tidy ups --- .../sensors/EnvironmentSensorManager.cpp | 22 ++++++++++++++---- .../sensors/EnvironmentSensorManager.h | 23 ------------------- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index c300cbe4..dda1fd55 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -1,15 +1,29 @@ #include "EnvironmentSensorManager.h" #if ENV_INCLUDE_AHTX0 +#define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address +#include static Adafruit_AHTX0 AHTX0; #endif + #if ENV_INCLUDE_BME280 +#define TELEM_BME280_ADDRESS 0x76 // BME280 environmental sensor I2C address +#define TELEM_BME280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level +#include static Adafruit_BME280 BME280; #endif + #if ENV_INCLUDE_INA3221 +#define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current sensor I2C address +#define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts +#define TELEM_INA3221_NUM_CHANNELS 3 +#include static Adafruit_INA3221 INA3221; #endif + #if ENV_INCLUDE_INA219 +#define TELEM_INA219_ADDRESS 0x40 // INA219 single channel current sensor I2C address +#include static Adafruit_INA219 INA219(TELEM_INA219_ADDRESS); #endif @@ -128,15 +142,17 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen int EnvironmentSensorManager::getNumSettings() const { #if ENV_INCLUDE_GPS return gps_detected ? 1 : 0; // only show GPS setting if GPS is detected + #else + return 0; #endif - return NULL; } const char* EnvironmentSensorManager::getSettingName(int i) const { #if ENV_INCLUDE_GPS return (gps_detected && i == 0) ? "gps" : NULL; + #else + return NULL; #endif - return NULL; } const char* EnvironmentSensorManager::getSettingValue(int i) const { @@ -184,10 +200,8 @@ void EnvironmentSensorManager::initBasicGPS() { MESH_DEBUG_PRINTLN("No GPS detected"); digitalWrite(PIN_GPS_EN, LOW); } - } - void EnvironmentSensorManager::start_gps() { gps_active = true; pinMode(PIN_GPS_EN, OUTPUT); diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 2a72f2d0..c9ec6870 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -4,29 +4,6 @@ #include #include - -#if ENV_INCLUDE_AHTX0 -#define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address -#include -#endif -#if ENV_INCLUDE_BME280 -#define TELEM_BME280_ADDRESS 0x76 // BME280 environmental sensor I2C address -#define TELEM_BME280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level -#include -#endif -#if ENV_INCLUDE_INA3221 -#define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current sensor I2C address -#define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts -#define TELEM_INA3221_NUM_CHANNELS 3 -#include -#endif -#if ENV_INCLUDE_INA219 -#define TELEM_INA219_ADDRESS 0x40 // INA219 single channel current sensor I2C address -#include -#endif - - - class EnvironmentSensorManager : public SensorManager { protected: int next_available_channel = TELEM_CHANNEL_SELF + 1; From f8b45ec01ed30ebbfaff089d728ad923c6e52c25 Mon Sep 17 00:00:00 2001 From: Normunds Gavars Date: Fri, 23 May 2025 21:24:02 +0300 Subject: [PATCH 152/154] Add sensor support to Xiao Nrf --- variants/xiao_nrf52/platformio.ini | 12 ++++++++++++ variants/xiao_nrf52/target.cpp | 2 +- variants/xiao_nrf52/target.h | 4 ++-- variants/xiao_nrf52/variant.h | 4 ++-- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index 5083b1a3..2f468f4f 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -16,6 +16,11 @@ lib_ignore = 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 @@ -35,8 +40,15 @@ build_flags = ${nrf52840_xiao.build_flags} -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_WIRE_SCL=6 + -D PIN_WIRE_SDA=7 + -D ENV_INCLUDE_AHTX0=1 + -D ENV_INCLUDE_BME280=1 + -D ENV_INCLUDE_INA3221=1 + -D ENV_INCLUDE_INA219=1 build_src_filter = ${nrf52840_xiao.build_src_filter} + + + + +<../variants/xiao_nrf52> debug_tool = jlink diff --git a/variants/xiao_nrf52/target.cpp b/variants/xiao_nrf52/target.cpp index 1d23e76a..c78ea159 100644 --- a/variants/xiao_nrf52/target.cpp +++ b/variants/xiao_nrf52/target.cpp @@ -9,7 +9,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock rtc_clock; -SensorManager sensors; +EnvironmentSensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 diff --git a/variants/xiao_nrf52/target.h b/variants/xiao_nrf52/target.h index 868664b5..ec298a43 100644 --- a/variants/xiao_nrf52/target.h +++ b/variants/xiao_nrf52/target.h @@ -6,12 +6,12 @@ #include #include #include -#include +#include extern XiaoNrf52Board board; extern WRAPPER_CLASS radio_driver; extern VolatileRTCClock rtc_clock; -extern SensorManager sensors; +extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); diff --git a/variants/xiao_nrf52/variant.h b/variants/xiao_nrf52/variant.h index 0d0950af..d941463e 100644 --- a/variants/xiao_nrf52/variant.h +++ b/variants/xiao_nrf52/variant.h @@ -110,8 +110,8 @@ static const uint8_t A5 = PIN_A5; // Wire Interfaces #define WIRE_INTERFACES_COUNT (1) -#define PIN_WIRE_SDA (17) // 4 and 5 are used for the sx1262 ! -#define PIN_WIRE_SCL (16) // use WIRE1_SDA +// #define PIN_WIRE_SDA (17) // 4 and 5 are used for the sx1262 ! +// #define PIN_WIRE_SCL (16) // use WIRE1_SDA static const uint8_t SDA = PIN_WIRE_SDA; static const uint8_t SCL = PIN_WIRE_SCL; From 5cb2ba8c6283a62b2a2802129f8c47717d85ce72 Mon Sep 17 00:00:00 2001 From: recrof Date: Sat, 24 May 2025 07:05:33 +0200 Subject: [PATCH 153/154] added repeater and room server roles to heltec wireless tracker --- variants/heltec_tracker/platformio.ini | 44 ++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index f54eda86..d62771f4 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -31,6 +31,8 @@ build_src_filter = ${esp32_base.build_src_filter} +<../variants/heltec_tracker> lib_deps = ${esp32_base.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 + adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 [env:Heltec_Wireless_Tracker_companion_radio_ble] extends = Heltec_tracker_base @@ -56,5 +58,43 @@ build_src_filter = ${Heltec_tracker_base.build_src_filter} lib_deps = ${Heltec_tracker_base.lib_deps} densaugeo/base64 @ ~1.4.0 - stevemarple/MicroNMEA @ ^2.0.6 - adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + +[env:Heltec_Wireless_Tracker_repeater] +extends = Heltec_tracker_base +build_flags = + ${Heltec_tracker_base.build_flags} + -D DISPLAY_ROTATION=1 + -D DISPLAY_CLASS=ST7735Display + -D ADVERT_NAME='"Heltec 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 = ${Heltec_tracker_base.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_tracker_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:Heltec_Wireless_Tracker_room_server] +extends = Heltec_tracker_base +build_flags = + ${Heltec_tracker_base.build_flags} + -D DISPLAY_ROTATION=1 + -D DISPLAY_CLASS=ST7735Display + -D ADVERT_NAME='"Heltec 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 = ${Heltec_tracker_base.build_src_filter} + + + +<../examples/simple_room_server> +lib_deps = + ${Heltec_tracker_base.lib_deps} + ${esp32_ota.lib_deps} From 0bad7ee1068e66965a3569e7f8b748af8ed14dee Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 24 May 2025 16:19:19 +1000 Subject: [PATCH 154/154] * ver bump to 1.6.2 --- examples/companion_radio/main.cpp | 4 ++-- examples/simple_repeater/main.cpp | 4 ++-- examples/simple_room_server/main.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 03a8c90a..13db88c0 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -81,11 +81,11 @@ static uint32_t _atoi(const char* sp) { #define FIRMWARE_VER_CODE 5 #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "17 May 2025" + #define FIRMWARE_BUILD_DATE "24 May 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.6.1" + #define FIRMWARE_VERSION "v1.6.2" #endif #define CMD_APP_START 1 diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 5e04de54..12c843b7 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -22,11 +22,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "17 May 2025" + #define FIRMWARE_BUILD_DATE "24 May 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.6.1" + #define FIRMWARE_VERSION "v1.6.2" #endif #ifndef LORA_FREQ diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 5ffef515..dad7ce78 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -22,11 +22,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "17 May 2025" + #define FIRMWARE_BUILD_DATE "24 May 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.6.1" + #define FIRMWARE_VERSION "v1.6.2" #endif #ifndef LORA_FREQ