diff --git a/.gitignore b/.gitignore index 7ca9335a..db044b5a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,8 @@ out/ .DS_Store .vscode/settings.json .vscode/extensions.json +.idea +cmake-* +.cache +.ccls +compile_commands.json diff --git a/boards/heltec_mesh_solar.json b/boards/heltec_mesh_solar.json new file mode 100644 index 00000000..c9125811 --- /dev/null +++ b/boards/heltec_mesh_solar.json @@ -0,0 +1,61 @@ +{ + "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": "HT-n5262", + "mcu": "nrf52840", + "variant": "heltec_mesh_solar", + "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", + "openocd_target": "nrf52.cfg" + }, + "frameworks": [ + "arduino" + ], + "name": "Heltec Mesh Solar Board", + "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://heltec.org/", + "vendor": "Heltec" +} \ No newline at end of file diff --git a/boards/heltec_vision_master_e213.json b/boards/heltec_vision_master_e213.json new file mode 100644 index 00000000..81efd8f5 --- /dev/null +++ b/boards/heltec_vision_master_e213.json @@ -0,0 +1,44 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_16MB.csv", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "psram_type": "opi", + "hwids": [ + ["0x303A", "0x1001"], + ["0x303A", "0x0002"] + ], + "mcu": "esp32s3", + "variant": "heltec_vision_master_e213" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "Heltec Vision Master E213", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 8388608, + "maximum_size": 16777216, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://heltec.org/project/vision-master-e213/", + "vendor": "Heltec" +} diff --git a/boards/heltec_vision_master_e290.json b/boards/heltec_vision_master_e290.json new file mode 100644 index 00000000..07577557 --- /dev/null +++ b/boards/heltec_vision_master_e290.json @@ -0,0 +1,44 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_16MB.csv", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "psram_type": "opi", + "hwids": [ + ["0x303A", "0x1001"], + ["0x303A", "0x0002"] + ], + "mcu": "esp32s3", + "variant": "heltec_vision_master_e290" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "Heltec Vision Master E290", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 8388608, + "maximum_size": 16777216, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://heltec.org/project/vision-master-e290/", + "vendor": "Heltec" +} diff --git a/boards/heltec_vision_master_t190.json b/boards/heltec_vision_master_t190.json new file mode 100644 index 00000000..6aa674fc --- /dev/null +++ b/boards/heltec_vision_master_t190.json @@ -0,0 +1,44 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_16MB.csv", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "psram_type": "opi", + "hwids": [ + ["0x303A", "0x1001"], + ["0x303A", "0x0002"] + ], + "mcu": "esp32s3", + "variant": "heltec_vision_master_t190" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "Heltec Vision Master T190", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 8388608, + "maximum_size": 16777216, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://heltec.org/project/vision-master-t190/", + "vendor": "Heltec" +} diff --git a/boards/seeed-wio-tracker-l1.json b/boards/seeed-wio-tracker-l1.json index 3602baab..6235b8bf 100644 --- a/boards/seeed-wio-tracker-l1.json +++ b/boards/seeed-wio-tracker-l1.json @@ -40,8 +40,8 @@ ], "name": "Seeed Wio Tracker L1", "upload": { - "maximum_ram_size": 248832, - "maximum_size": 815104, + "maximum_ram_size": 237568, + "maximum_size": 811008, "protocol": "nrfutil", "speed": 115200, "protocols": [ diff --git a/build.sh b/build.sh index 095a1633..47fec4a3 100755 --- a/build.sh +++ b/build.sh @@ -3,6 +3,7 @@ # usage # sh build.sh build-firmware RAK_4631_Repeater # sh build.sh build-firmwares +# sh build.sh build-matching-firmwares RAK_4631 # sh build.sh build-companion-firmwares # sh build.sh build-repeater-firmwares # sh build.sh build-room-server-firmwares @@ -144,6 +145,16 @@ mkdir -p out if [[ $1 == "build-firmware" ]]; then if [ "$2" ]; then build_firmware $2 + else + echo "usage: $0 build-firmware " + exit 1 + fi +elif [[ $1 == "build-matching-firmwares" ]]; then + if [ "$2" ]; then + build_all_firmwares_matching $2 + else + echo "usage: $0 build-matching-firmwares " + exit 1 fi elif [[ $1 == "build-firmwares" ]]; then build_firmwares diff --git a/build_as_lib.py b/build_as_lib.py new file mode 100644 index 00000000..d8e95378 --- /dev/null +++ b/build_as_lib.py @@ -0,0 +1,64 @@ +from os.path import realpath + +Import("env") # type: ignore +menv=env # type: ignore + +src_filter = [ + '+<*.cpp>', + '+', + '+', + '+', + '+', + '+', +] + +# add build and include dirs according to CPPDEFINES +for item in menv.get("CPPDEFINES", []): + + # PLATFORM HANDLING + if item == "STM32_PLATFORM": + src_filter.append("+") + elif item == "ESP32": + src_filter.append("+") + elif item == "NRF52_PLATFORM": + src_filter.append("+") + elif item == "RP2040_PLATFORM": + src_filter.append("+") + + # DISPLAY HANDLING + elif isinstance(item, tuple) and item[0] == "DISPLAY_CLASS": + display_class = item[1] + src_filter.append(f"+") + if (display_class == "ST7789Display") : + src_filter.append(f"+") + src_filter.append(f"+") + + # VARIANTS HANDLING + elif isinstance(item, tuple) and item[0] == "MC_VARIANT": + variant_name = item[1] + src_filter.append(f"+<../variants/{variant_name}>") + + # INCLUDE EXAMPLE CODE IN BUILD (to provide your own support files without touching the tree) + elif isinstance(item, tuple) and item[0] == "BUILD_EXAMPLE": + example_name = item[1] + src_filter.append(f"+<../examples/{example_name}/*.cpp>") + + # EXCLUDE A SOURCE FILE FROM AN EXAMPLE (must be placed after example name or boom) + elif isinstance(item, tuple) and item[0] == "EXCLUDE_FROM_EXAMPLE": + exclude_name = item[1] + if example_name is None: + print("***** PLEASE DEFINE EXAMPLE FIRST *****") + break + src_filter.append(f"-<../examples/{example_name}/{exclude_name}>") + + # DEAL WITH UI VARIANT FOR AN EXAMPLE + elif isinstance(item, tuple) and item[0] == "MC_UI_FLAVOR": + ui_flavor = item[1] + if example_name is None: + print("***** PLEASE DEFINE EXAMPLE FIRST *****") + break + src_filter.append(f"+<../examples/{example_name}/{ui_flavor}/*.cpp>") + +menv.Replace(SRC_FILTER=src_filter) + +#print (menv.Dump()) diff --git a/examples/companion_radio/AbstractUITask.h b/examples/companion_radio/AbstractUITask.h new file mode 100644 index 00000000..1277bba9 --- /dev/null +++ b/examples/companion_radio/AbstractUITask.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#ifdef PIN_BUZZER + #include +#endif + +#include "NodePrefs.h" + +enum class UIEventType { + none, + contactMessage, + channelMessage, + roomMessage, + newContactMessage, + ack +}; + +class AbstractUITask { +protected: + mesh::MainBoard* _board; + BaseSerialInterface* _serial; + bool _connected; + + AbstractUITask(mesh::MainBoard* board, BaseSerialInterface* serial) : _board(board), _serial(serial) { + _connected = false; + } + +public: + void setHasConnection(bool connected) { _connected = connected; } + bool hasConnection() const { return _connected; } + uint16_t getBattMilliVolts() const { return _board->getBattMilliVolts(); } + bool isSerialEnabled() const { return _serial->isEnabled(); } + void enableSerial() { _serial->enable(); } + void disableSerial() { _serial->disable(); } + virtual void msgRead(int msgcount) = 0; + virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) = 0; + virtual void soundBuzzer(UIEventType bet = UIEventType::none) = 0; + virtual void loop() = 0; +}; diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 939c93de..1fa5478b 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -41,7 +41,7 @@ #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 CMD_SEND_TELEMETRY_REQ 39 // can deprecate this #define CMD_GET_CUSTOM_VARS 40 #define CMD_SET_CUSTOM_VAR 41 #define CMD_GET_ADVERT_PATH 42 @@ -49,6 +49,7 @@ // NOTE: CMD range 44..49 parked, potentially for WiFi operations #define CMD_SEND_BINARY_REQ 50 #define CMD_FACTORY_RESET 51 +#define CMD_SEND_PATH_DISCOVERY_REQ 52 #define RESP_CODE_OK 0 #define RESP_CODE_ERR 1 @@ -97,6 +98,7 @@ #define PUSH_CODE_NEW_ADVERT 0x8A #define PUSH_CODE_TELEMETRY_RESPONSE 0x8B #define PUSH_CODE_BINARY_RESPONSE 0x8C +#define PUSH_CODE_PATH_DISCOVERY_RESPONSE 0x8D #define ERR_CODE_UNSUPPORTED_CMD 1 #define ERR_CODE_NOT_FOUND 2 @@ -107,10 +109,6 @@ #define MAX_SIGN_DATA_LEN (8 * 1024) // 8K -#ifdef DISPLAY_CLASS -#include "UITask.h" -#endif - void MyMesh::writeOKFrame() { uint8_t buf[1]; buf[0] = RESP_CODE_OK; @@ -152,7 +150,7 @@ void MyMesh::writeContactRespFrame(uint8_t code, const ContactInfo &contact) { _serial->writeFrame(out_frame, i); } -void MyMesh::updateContactFromFrame(ContactInfo &contact, const uint8_t *frame, int len) { +void MyMesh::updateContactFromFrame(ContactInfo &contact, uint32_t& last_mod, const uint8_t *frame, int len) { int i = 0; uint8_t code = frame[i++]; // eg. CMD_ADD_UPDATE_CONTACT memcpy(contact.id.pub_key, &frame[i], PUB_KEY_SIZE); @@ -166,11 +164,14 @@ void MyMesh::updateContactFromFrame(ContactInfo &contact, const uint8_t *frame, i += 32; memcpy(&contact.last_advert_timestamp, &frame[i], 4); i += 4; - if (i + 8 >= len) { // optional fields + if (len >= i + 8) { // optional fields memcpy(&contact.gps_lat, &frame[i], 4); i += 4; memcpy(&contact.gps_lon, &frame[i], 4); i += 4; + if (len >= i + 4) { + memcpy(&last_mod, &frame[i], 4); + } } } @@ -242,7 +243,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path } } else { #ifdef DISPLAY_CLASS - ui_task.soundBuzzer(UIEventType::newContactMessage); + if (_ui) _ui->soundBuzzer(UIEventType::newContactMessage); #endif } @@ -262,6 +263,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path } memcpy(p->pubkey_prefix, contact.id.pub_key, sizeof(p->pubkey_prefix)); + strcpy(p->name, contact.name); p->recv_timestamp = getRTCClock()->getCurrentTime(); p->path_len = path_len; memcpy(p->path, path, p->path_len); @@ -270,6 +272,20 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); } +static int sort_by_recent(const void *a, const void *b) { + return ((AdvertPath *) b)->recv_timestamp - ((AdvertPath *) a)->recv_timestamp; +} + +int MyMesh::getRecentlyHeard(AdvertPath dest[], int max_num) { + if (max_num > ADVERT_PATH_TABLE_SIZE) max_num = ADVERT_PATH_TABLE_SIZE; + qsort(advert_paths, ADVERT_PATH_TABLE_SIZE, sizeof(advert_paths[0]), sort_by_recent); + + for (int i = 0; i < max_num; i++) { + dest[i] = advert_paths[i]; + } + return max_num; +} + void MyMesh::onContactPathUpdated(const ContactInfo &contact) { out_frame[0] = PUSH_CODE_PATH_UPDATED; memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE); @@ -334,10 +350,10 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe #ifdef DISPLAY_CLASS // we only want to show text messages on display, not cli data bool should_display = txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_SIGNED_PLAIN; - if (should_display) { - ui_task.newMsg(path_len, from.name, text, offline_queue_len); + if (should_display && _ui) { + _ui->newMsg(path_len, from.name, text, offline_queue_len); if (!_serial->isConnected()) { - ui_task.soundBuzzer(UIEventType::contactMessage); + _ui->soundBuzzer(UIEventType::contactMessage); } } #endif @@ -396,7 +412,7 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe _serial->writeFrame(frame, 1); } else { #ifdef DISPLAY_CLASS - ui_task.soundBuzzer(UIEventType::channelMessage); + if (_ui) _ui->soundBuzzer(UIEventType::channelMessage); #endif } #ifdef DISPLAY_CLASS @@ -406,7 +422,7 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe if (getChannel(channel_idx, channel_details)) { channel_name = channel_details.name; } - ui_task.newMsg(path_len, channel_name, text, offline_queue_len); + if (_ui) _ui->newMsg(path_len, channel_name, text, offline_queue_len); #endif } @@ -434,6 +450,9 @@ uint8_t MyMesh::onContactRequest(const ContactInfo &contact, uint32_t sender_tim permissions |= cp & TELEM_PERM_ENVIRONMENT; } + uint8_t perm_mask = ~(data[1]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions + permissions &= perm_mask; + 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); @@ -524,6 +543,39 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data, } } +bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { + if (extra_type == PAYLOAD_TYPE_RESPONSE && extra_len > 4) { + uint32_t tag; + memcpy(&tag, extra, 4); + + if (tag == pending_discovery) { // check for matching response tag) + pending_discovery = 0; + + if (in_path_len > MAX_PATH_SIZE || out_path_len > MAX_PATH_SIZE) { + MESH_DEBUG_PRINTLN("onContactPathRecv, invalid path sizes: %d, %d", in_path_len, out_path_len); + } else { + int i = 0; + out_frame[i++] = PUSH_CODE_PATH_DISCOVERY_RESPONSE; + out_frame[i++] = 0; // reserved + memcpy(&out_frame[i], contact.id.pub_key, 6); + i += 6; // pub_key_prefix + out_frame[i++] = out_path_len; + memcpy(&out_frame[i], out_path, out_path_len); + i += out_path_len; + out_frame[i++] = in_path_len; + memcpy(&out_frame[i], in_path, in_path_len); + i += in_path_len; + // NOTE: telemetry data in 'extra' is discarded at present + + _serial->writeFrame(out_frame, i); + } + return false; // DON'T send reciprocal path! + } + } + // let base class handle received path and data + return BaseChatMesh::onContactPathRecv(contact, in_path, in_path_len, out_path, out_path_len, extra_type, extra, extra_len); +} + void MyMesh::onRawDataRecv(mesh::Packet *packet) { if (packet->payload_len + 4 > sizeof(out_frame)) { MESH_DEBUG_PRINTLN("onRawDataRecv(), payload_len too long: %d", packet->payload_len); @@ -579,14 +631,14 @@ uint32_t MyMesh::calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t void MyMesh::onSendTimeout() {} -MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store) +MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui) : BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables), - _serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store) { + _serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui) { _iter_started = false; _cli_rescue = false; offline_queue_len = 0; app_target_ver = 0; - pending_login = pending_status = pending_telemetry = pending_req = 0; + clearPendingReqs(); next_ack_idx = 0; sign_data = NULL; dirty_contacts_expiry = 0; @@ -658,6 +710,7 @@ void MyMesh::begin(bool has_display) { _active_ble_pin = 0; #endif + resetContacts(); _store->loadContacts(this); addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel _store->loadChannels(this); @@ -896,15 +949,16 @@ void MyMesh::handleCmdFrame(size_t len) { } else if (cmd_frame[0] == CMD_ADD_UPDATE_CONTACT && len >= 1 + 32 + 2 + 1) { uint8_t *pub_key = &cmd_frame[1]; ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE); + uint32_t last_mod = getRTCClock()->getCurrentTime(); // fallback value if not present in cmd_frame if (recipient) { - updateContactFromFrame(*recipient, cmd_frame, len); - // recipient->lastmod = ?? shouldn't be needed, app already has this version of contact + updateContactFromFrame(*recipient, last_mod, cmd_frame, len); + recipient->lastmod = last_mod; dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); writeOKFrame(); } else { ContactInfo contact; - updateContactFromFrame(contact, cmd_frame, len); - contact.lastmod = getRTCClock()->getCurrentTime(); + updateContactFromFrame(contact, last_mod, cmd_frame, len); + contact.lastmod = last_mod; contact.sync_since = 0; if (addContact(contact)) { dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); @@ -983,7 +1037,7 @@ void MyMesh::handleCmdFrame(size_t len) { if ((out_len = getFromOfflineQueue(out_frame)) > 0) { _serial->writeFrame(out_frame, out_len); #ifdef DISPLAY_CLASS - ui_task.msgRead(offline_queue_len); + if (_ui) _ui->msgRead(offline_queue_len); #endif } else { out_frame[0] = RESP_CODE_NO_MORE_MESSAGES; @@ -1093,6 +1147,9 @@ void MyMesh::handleCmdFrame(size_t len) { if (_store->saveMainIdentity(identity)) { self_id = identity; writeOKFrame(); + // re-load contacts, to recalc shared secrets + resetContacts(); + _store->loadContacts(this); } else { writeErrFrame(ERR_CODE_FILE_IO_ERROR); } @@ -1126,7 +1183,7 @@ void MyMesh::handleCmdFrame(size_t len) { if (result == MSG_SEND_FAILED) { writeErrFrame(ERR_CODE_TABLE_FULL); } else { - pending_req = pending_telemetry = pending_status = 0; + clearPendingReqs(); 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; @@ -1146,7 +1203,7 @@ void MyMesh::handleCmdFrame(size_t len) { if (result == MSG_SEND_FAILED) { writeErrFrame(ERR_CODE_TABLE_FULL); } else { - pending_req = pending_telemetry = pending_login = 0; + clearPendingReqs(); // 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; @@ -1158,6 +1215,35 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found } + } else if (cmd_frame[0] == CMD_SEND_PATH_DISCOVERY_REQ && cmd_frame[1] == 0 && len >= 2 + PUB_KEY_SIZE) { + uint8_t *pub_key = &cmd_frame[2]; + ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE); + if (recipient) { + uint32_t tag, est_timeout; + // 'Path Discovery' is just a special case of flood + Telemetry req + uint8_t req_data[9]; + req_data[0] = REQ_TYPE_GET_TELEMETRY_DATA; + req_data[1] = ~(TELEM_PERM_BASE); // NEW: inverse permissions mask (ie. we only want BASE telemetry) + memset(&req_data[2], 0, 3); // reserved + getRNG()->random(&req_data[5], 4); // random blob to help make packet-hash unique + auto save = recipient->out_path_len; // temporarily force sendRequest() to flood + recipient->out_path_len = -1; + int result = sendRequest(*recipient, req_data, sizeof(req_data), tag, est_timeout); + recipient->out_path_len = save; + if (result == MSG_SEND_FAILED) { + writeErrFrame(ERR_CODE_TABLE_FULL); + } else { + clearPendingReqs(); + pending_discovery = 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); + } + } else { + writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found + } } else if (cmd_frame[0] == CMD_SEND_TELEMETRY_REQ && len >= 4 + PUB_KEY_SIZE) { // can deprecate, in favour of CMD_SEND_BINARY_REQ uint8_t *pub_key = &cmd_frame[4]; ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE); @@ -1167,7 +1253,7 @@ void MyMesh::handleCmdFrame(size_t len) { if (result == MSG_SEND_FAILED) { writeErrFrame(ERR_CODE_TABLE_FULL); } else { - pending_status = pending_login = pending_req = 0; + clearPendingReqs(); pending_telemetry = tag; // match this in onContactResponse() out_frame[0] = RESP_CODE_SENT; out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0; @@ -1203,7 +1289,7 @@ void MyMesh::handleCmdFrame(size_t len) { if (result == MSG_SEND_FAILED) { writeErrFrame(ERR_CODE_TABLE_FULL); } else { - pending_status = pending_login = pending_telemetry = 0; + clearPendingReqs(); pending_req = tag; // match this in onContactResponse() out_frame[0] = RESP_CODE_SENT; out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0; @@ -1553,7 +1639,7 @@ void MyMesh::loop() { } #ifdef DISPLAY_CLASS - ui_task.setHasConnection(_serial->isConnected()); + if (_ui) _ui->setHasConnection(_serial->isConnected()); #endif } diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 5ad6198f..89ee8133 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -2,19 +2,17 @@ #include #include -#ifdef DISPLAY_CLASS -#include "UITask.h" -#endif +#include "AbstractUITask.h" /*------------ Frame Protocol --------------*/ #define FIRMWARE_VER_CODE 7 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "24 Jul 2025" +#define FIRMWARE_BUILD_DATE "31 Aug 2025" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "v1.7.4" +#define FIRMWARE_VERSION "v1.8.0" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) @@ -77,9 +75,17 @@ #define REQ_TYPE_KEEP_ALIVE 0x02 #define REQ_TYPE_GET_TELEMETRY_DATA 0x03 +struct AdvertPath { + uint8_t pubkey_prefix[7]; + uint8_t path_len; + char name[32]; + uint32_t recv_timestamp; + uint8_t path[MAX_PATH_SIZE]; +}; + class MyMesh : public BaseChatMesh, public DataStoreHost { public: - MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store); + MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui=NULL); void begin(bool has_display); void startInterface(BaseSerialInterface &serial); @@ -93,6 +99,8 @@ public: bool advert(); void enterCLIRescue(); + int getRecentlyHeard(AdvertPath dest[], int max_num); + protected: float getAirtimeBudgetFactor() const override; int getInterferenceThreshold() const override; @@ -101,6 +109,7 @@ protected: void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override; bool isAutoAddEnabled() const override; + bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; void onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) override; void onContactPathUpdated(const ContactInfo &contact) override; bool processAck(const uint8_t *data) override; @@ -133,12 +142,16 @@ protected: bool onChannelLoaded(uint8_t channel_idx, const ChannelDetails& ch) override { return setChannel(channel_idx, ch); } bool getChannelForSave(uint8_t channel_idx, ChannelDetails& ch) override { return getChannel(channel_idx, ch); } + void clearPendingReqs() { + pending_login = pending_status = pending_telemetry = pending_discovery = pending_req = 0; + } + private: void writeOKFrame(); void writeErrFrame(uint8_t err_code); void writeDisabledFrame(); void writeContactRespFrame(uint8_t code, const ContactInfo &contact); - void updateContactFromFrame(ContactInfo &contact, const uint8_t *frame, int len); + void updateContactFromFrame(ContactInfo &contact, uint32_t& last_mod, const uint8_t *frame, int len); void addToOfflineQueue(const uint8_t frame[], int len); int getFromOfflineQueue(uint8_t frame[]); int getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) override { @@ -161,9 +174,10 @@ private: NodePrefs _prefs; uint32_t pending_login; uint32_t pending_status; - uint32_t pending_telemetry; // pending _TELEMETRY_REQ + uint32_t pending_telemetry, pending_discovery; // pending _TELEMETRY_REQ uint32_t pending_req; // pending _BINARY_REQ BaseSerialInterface *_serial; + AbstractUITask* _ui; ContactsIterator _iter; uint32_t _iter_filter_since; @@ -196,17 +210,8 @@ private: AckTableEntry expected_ack_table[EXPECTED_ACK_TABLE_SIZE]; // circular table int next_ack_idx; - struct AdvertPath { - uint8_t pubkey_prefix[7]; - uint8_t path_len; - uint32_t recv_timestamp; - uint8_t path[MAX_PATH_SIZE]; - }; #define ADVERT_PATH_TABLE_SIZE 16 AdvertPath advert_paths[ADVERT_PATH_TABLE_SIZE]; // circular table }; extern MyMesh the_mesh; -#ifdef DISPLAY_CLASS -extern UITask ui_task; -#endif \ No newline at end of file diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 34c30498..1d5ec564 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -75,14 +75,19 @@ static uint32_t _atoi(const char* sp) { #endif /* GLOBAL OBJECTS */ -StdRNG fast_rng; -SimpleMeshTables tables; -MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store); - #ifdef DISPLAY_CLASS #include "UITask.h" - UITask ui_task(&board); + UITask ui_task(&board, &serial_interface); #endif + +StdRNG fast_rng; +SimpleMeshTables tables; +MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store + #ifdef DISPLAY_CLASS + , &ui_task + #endif +); + /* END GLOBAL OBJECTS */ void halt() { @@ -99,7 +104,10 @@ void setup() { if (display.begin()) { disp = &display; disp->startFrame(); - disp->print("Please wait..."); + #ifdef ST7789 + disp->setTextSize(2); + #endif + disp->drawTextCentered(disp->width() / 2, 28, "Loading..."); disp->endFrame(); } #endif diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp new file mode 100644 index 00000000..22f394e7 --- /dev/null +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -0,0 +1,612 @@ +#include "UITask.h" +#include +#include "../MyMesh.h" +#include "target.h" + +#define AUTO_OFF_MILLIS 15000 // 15 seconds +#define BOOT_SCREEN_MILLIS 3000 // 3 seconds + +#ifdef PIN_STATUS_LED +#define LED_ON_MILLIS 20 +#define LED_ON_MSG_MILLIS 200 +#define LED_CYCLE_MILLIS 4000 +#endif + +#define LONG_PRESS_MILLIS 1200 + +#ifndef UI_RECENT_LIST_SIZE + #define UI_RECENT_LIST_SIZE 4 +#endif + +#define PRESS_LABEL "long press" + +#include "icons.h" + +class SplashScreen : public UIScreen { + UITask* _task; + unsigned long dismiss_after; + char _version_info[12]; + +public: + SplashScreen(UITask* task) : _task(task) { + // strip off dash and commit hash by changing dash to null terminator + // e.g: v1.2.3-abcdef -> v1.2.3 + const char *ver = FIRMWARE_VERSION; + const char *dash = strchr(ver, '-'); + + int len = dash ? dash - ver : strlen(ver); + if (len >= sizeof(_version_info)) len = sizeof(_version_info) - 1; + memcpy(_version_info, ver, len); + _version_info[len] = 0; + + dismiss_after = millis() + BOOT_SCREEN_MILLIS; + } + + int render(DisplayDriver& display) override { + // meshcore logo + display.setColor(DisplayDriver::BLUE); + int logoWidth = 128; + display.drawXbm((display.width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); + + // version info + display.setColor(DisplayDriver::LIGHT); + display.setTextSize(2); + display.drawTextCentered(display.width()/2, 22, _version_info); + + display.setTextSize(1); + display.drawTextCentered(display.width()/2, 42, FIRMWARE_BUILD_DATE); + + return 1000; + } + + void poll() override { + if (millis() >= dismiss_after) { + _task->gotoHomeScreen(); + } + } +}; + +class HomeScreen : public UIScreen { + enum HomePage { + FIRST, + RECENT, + RADIO, + BLUETOOTH, + ADVERT, + SHUTDOWN, + Count // keep as last + }; + + UITask* _task; + mesh::RTCClock* _rtc; + SensorManager* _sensors; + NodePrefs* _node_prefs; + uint8_t _page; + bool _shutdown_init; + AdvertPath recent[UI_RECENT_LIST_SIZE]; + + 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 = 10; + 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 - 4)) / 100; + display.fillRect(iconX + 2, iconY + 2, fillWidth, iconHeight - 4); + } + +public: + HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs) + : _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0), _shutdown_init(false) { } + + void poll() override { + if (_shutdown_init && !_task->isButtonPressed()) { // must wait for USR button to be released + _task->shutdown(); + } + } + + int render(DisplayDriver& display) override { + char tmp[80]; + // node name + display.setCursor(0, 0); + display.setTextSize(1); + display.setColor(DisplayDriver::GREEN); + display.print(_node_prefs->node_name); + + // battery voltage + renderBatteryIndicator(display, _task->getBattMilliVolts()); + + // curr page indicator + int y = 14; + int x = display.width() / 2 - 25; + for (uint8_t i = 0; i < HomePage::Count; i++, x += 10) { + if (i == _page) { + display.fillRect(x-1, y-1, 3, 3); + } else { + display.fillRect(x, y, 1, 1); + } + } + + if (_page == HomePage::FIRST) { + display.setColor(DisplayDriver::YELLOW); + display.setTextSize(2); + sprintf(tmp, "MSG: %d", _task->getMsgCount()); + display.drawTextCentered(display.width() / 2, 20, tmp); + + if (_task->hasConnection()) { + display.setColor(DisplayDriver::GREEN); + display.setTextSize(1); + display.drawTextCentered(display.width() / 2, 43, "< Connected >"); + } else if (the_mesh.getBLEPin() != 0) { // BT pin + display.setColor(DisplayDriver::RED); + display.setTextSize(2); + sprintf(tmp, "Pin:%d", the_mesh.getBLEPin()); + display.drawTextCentered(display.width() / 2, 43, tmp); + } + } else if (_page == HomePage::RECENT) { + the_mesh.getRecentlyHeard(recent, UI_RECENT_LIST_SIZE); + display.setColor(DisplayDriver::GREEN); + int y = 20; + for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += 11) { + auto a = &recent[i]; + if (a->name[0] == 0) continue; // empty slot + display.setCursor(0, y); + display.print(a->name); + int secs = _rtc->getCurrentTime() - a->recv_timestamp; + if (secs < 60) { + sprintf(tmp, "%ds", secs); + } else if (secs < 60*60) { + sprintf(tmp, "%dm", secs / 60); + } else { + sprintf(tmp, "%dh", secs / (60*60)); + } + display.setCursor(display.width() - display.getTextWidth(tmp) - 1, y); + display.print(tmp); + } + } else if (_page == HomePage::RADIO) { + display.setColor(DisplayDriver::YELLOW); + display.setTextSize(1); + // freq / sf + display.setCursor(0, 20); + sprintf(tmp, "FQ: %06.3f SF: %d", _node_prefs->freq, _node_prefs->sf); + display.print(tmp); + + display.setCursor(0, 31); + sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr); + display.print(tmp); + + // tx power, noise floor + display.setCursor(0, 42); + sprintf(tmp, "TX: %ddBm", _node_prefs->tx_power_dbm); + display.print(tmp); + display.setCursor(0, 53); + sprintf(tmp, "Noise floor: %d", radio_driver.getNoiseFloor()); + display.print(tmp); + } else if (_page == HomePage::BLUETOOTH) { + display.setColor(DisplayDriver::GREEN); + display.drawXbm((display.width() - 32) / 2, 18, + _task->isSerialEnabled() ? bluetooth_on : bluetooth_off, + 32, 32); + display.setTextSize(1); + display.drawTextCentered(display.width() / 2, 64 - 11, "toggle: " PRESS_LABEL); + } else if (_page == HomePage::ADVERT) { + display.setColor(DisplayDriver::GREEN); + display.drawXbm((display.width() - 32) / 2, 18, advert_icon, 32, 32); + display.drawTextCentered(display.width() / 2, 64 - 11, "advert: " PRESS_LABEL); + } else if (_page == HomePage::SHUTDOWN) { + display.setColor(DisplayDriver::GREEN); + display.setTextSize(1); + if (_shutdown_init) { + display.drawTextCentered(display.width() / 2, 34, "hibernating..."); + } else { + display.drawXbm((display.width() - 32) / 2, 18, power_icon, 32, 32); + display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate: " PRESS_LABEL); + } + } + return 5000; // next render after 5000 ms + } + + bool handleInput(char c) override { + if (c == KEY_LEFT) { + _page = (_page + HomePage::Count - 1) % HomePage::Count; + return true; + } + if (c == KEY_RIGHT || c == KEY_SELECT) { + _page = (_page + 1) % HomePage::Count; + if (_page == HomePage::RECENT) { + _task->showAlert("Recent adverts", 800); + } + return true; + } + if (c == KEY_ENTER && _page == HomePage::BLUETOOTH) { + if (_task->isSerialEnabled()) { // toggle Bluetooth on/off + _task->disableSerial(); + } else { + _task->enableSerial(); + } + return true; + } + if (c == KEY_ENTER && _page == HomePage::ADVERT) { + #ifdef PIN_BUZZER + _task->soundBuzzer(UIEventType::ack); + #endif + if (the_mesh.advert()) { + _task->showAlert("Advert sent!", 1000); + } else { + _task->showAlert("Advert failed..", 1000); + } + return true; + } + if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) { + _shutdown_init = true; // need to wait for button to be released + return true; + } + return false; + } +}; + +class MsgPreviewScreen : public UIScreen { + UITask* _task; + mesh::RTCClock* _rtc; + + struct MsgEntry { + uint32_t timestamp; + char origin[62]; + char msg[78]; + }; + #define MAX_UNREAD_MSGS 32 + int num_unread; + MsgEntry unread[MAX_UNREAD_MSGS]; + +public: + MsgPreviewScreen(UITask* task, mesh::RTCClock* rtc) : _task(task), _rtc(rtc) { num_unread = 0; } + + void addPreview(uint8_t path_len, const char* from_name, const char* msg) { + if (num_unread >= MAX_UNREAD_MSGS) return; // full + + auto p = &unread[num_unread++]; + p->timestamp = _rtc->getCurrentTime(); + if (path_len == 0xFF) { + sprintf(p->origin, "(D) %s:", from_name); + } else { + sprintf(p->origin, "(%d) %s:", (uint32_t) path_len, from_name); + } + StrHelper::strncpy(p->msg, msg, sizeof(p->msg)); + } + + int render(DisplayDriver& display) override { + char tmp[16]; + display.setCursor(0, 0); + display.setTextSize(1); + display.setColor(DisplayDriver::GREEN); + sprintf(tmp, "Unread: %d", num_unread); + display.print(tmp); + + auto p = &unread[0]; + + int secs = _rtc->getCurrentTime() - p->timestamp; + if (secs < 60) { + sprintf(tmp, "%ds", secs); + } else if (secs < 60*60) { + sprintf(tmp, "%dm", secs / 60); + } else { + sprintf(tmp, "%dh", secs / (60*60)); + } + display.setCursor(display.width() - display.getTextWidth(tmp) - 2, 0); + display.print(tmp); + + display.drawRect(0, 11, display.width(), 1); // horiz line + + display.setCursor(0, 14); + display.setColor(DisplayDriver::YELLOW); + display.print(p->origin); + + display.setCursor(0, 25); + display.setColor(DisplayDriver::LIGHT); + display.printWordWrap(p->msg, display.width()); + + return 1000; // next render after 1000 ms + } + + bool handleInput(char c) override { + if (c == KEY_SELECT || c == KEY_RIGHT) { + num_unread--; + if (num_unread == 0) { + _task->gotoHomeScreen(); + } else { + // delete first/curr item from unread queue + for (int i = 0; i < num_unread; i++) { + unread[i] = unread[i + 1]; + } + } + return true; + } + if (c == KEY_ENTER) { + num_unread = 0; // clear unread queue + _task->gotoHomeScreen(); + return true; + } + return false; + } +}; + +void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs) { + _display = display; + _sensors = sensors; + _auto_off = millis() + AUTO_OFF_MILLIS; + +#if defined(PIN_USER_BTN) + user_btn.begin(); +#endif + + _node_prefs = node_prefs; + if (_display != NULL) { + _display->turnOn(); + } + +#ifdef PIN_BUZZER + buzzer.begin(); +#endif + + ui_started_at = millis(); + _alert_expiry = 0; + + splash = new SplashScreen(this); + home = new HomeScreen(this, &rtc_clock, sensors, node_prefs); + msg_preview = new MsgPreviewScreen(this, &rtc_clock); + setCurrScreen(splash); +} + +void UITask::showAlert(const char* text, int duration_millis) { + strcpy(_alert, text); + _alert_expiry = millis() + duration_millis; +} + +void UITask::soundBuzzer(UIEventType bet) { +#if defined(PIN_BUZZER) +switch(bet){ + case UIEventType::contactMessage: + // gemini's pick + 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::ack: + buzzer.play("ack:d=32,o=8,b=120:c"); + break; + case UIEventType::roomMessage: + case UIEventType::newContactMessage: + case UIEventType::none: + default: + break; +} +#endif +} + +void UITask::msgRead(int msgcount) { + _msgcount = msgcount; + if (msgcount == 0) { + gotoHomeScreen(); + } +} + +void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) { + _msgcount = msgcount; + + ((MsgPreviewScreen *) msg_preview)->addPreview(path_len, from_name, text); + setCurrScreen(msg_preview); + + if (_display != NULL) { + if (!_display->isOn()) _display->turnOn(); + _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer + _next_refresh = 0; // trigger refresh + } +} + +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; + if (_msgcount > 0) { + last_increment = LED_ON_MSG_MILLIS; + } else { + last_increment = LED_ON_MILLIS; + } + next_change = cur_time + last_increment; + } else { + state = 0; + next_change = cur_time + LED_CYCLE_MILLIS - last_increment; + } + digitalWrite(PIN_STATUS_LED, state); + } +#endif +} + +void UITask::setCurrScreen(UIScreen* c) { + curr = c; + _next_refresh = 0; +} + +/* + hardware-agnostic pre-shutdown activity should be done here +*/ +void UITask::shutdown(bool restart){ + + #ifdef PIN_BUZZER + /* note: we have a choice here - + we can do a blocking buzzer.loop() with non-deterministic consequences + or we can set a flag and delay the shutdown for a couple of seconds + while a non-blocking buzzer.loop() plays out in UITask::loop() + */ + buzzer.shutdown(); + uint32_t buzzer_timer = millis(); // fail-safe shutdown + while (buzzer.isPlaying() && (millis() - 2500) < buzzer_timer) + buzzer.loop(); + + #endif // PIN_BUZZER + + if (restart) { + _board->reboot(); + } else { + _display->turnOff(); + _board->powerOff(); + } +} + +bool UITask::isButtonPressed() const { +#ifdef PIN_USER_BTN + return user_btn.isPressed(); +#else + return false; +#endif +} + +void UITask::loop() { + char c = 0; +#if defined(PIN_USER_BTN) + int ev = user_btn.check(); + if (ev == BUTTON_EVENT_CLICK) { + c = checkDisplayOn(KEY_SELECT); + } else if (ev == BUTTON_EVENT_LONG_PRESS) { + c = handleLongPress(KEY_ENTER); + } +#endif +#if defined(WIO_TRACKER_L1) + ev = joystick_left.check(); + if (ev == BUTTON_EVENT_CLICK) { + c = checkDisplayOn(KEY_LEFT); + } else if (ev == BUTTON_EVENT_LONG_PRESS) { + c = handleLongPress(KEY_LEFT); + } + ev = joystick_right.check(); + if (ev == BUTTON_EVENT_CLICK) { + c = checkDisplayOn(KEY_RIGHT); + } else if (ev == BUTTON_EVENT_LONG_PRESS) { + c = handleLongPress(KEY_RIGHT); + } +#endif + + if (c != 0 && curr) { + curr->handleInput(c); + _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer + _next_refresh = 0; // trigger refresh + } + + userLedHandler(); + +#ifdef PIN_BUZZER + if (buzzer.isPlaying()) buzzer.loop(); +#endif + + if (curr) curr->poll(); + + if (_display != NULL && _display->isOn()) { + if (millis() >= _next_refresh && curr) { + _display->startFrame(); + int delay_millis = curr->render(*_display); + if (millis() < _alert_expiry) { // render alert popup + _display->setTextSize(1); + int y = _display->height() / 3; + int p = _display->height() / 32; + _display->setColor(DisplayDriver::DARK); + _display->fillRect(p, y, _display->width() - p*2, y); + _display->setColor(DisplayDriver::LIGHT); // draw box border + _display->drawRect(p, y, _display->width() - p*2, y); + _display->drawTextCentered(_display->width() / 2, y + p*3, _alert); + _next_refresh = _alert_expiry; // will need refresh when alert is dismissed + } else { + _next_refresh = millis() + delay_millis; + } + _display->endFrame(); + } + if (millis() > _auto_off) { + _display->turnOff(); + } + } + +#ifdef AUTO_SHUTDOWN_MILLIVOLTS + if (millis() > next_batt_chck) { + uint16_t milliVolts = getBattMilliVolts(); + if (milliVolts > 0 && milliVolts < AUTO_SHUTDOWN_MILLIVOLTS) { + + // show low battery shutdown alert + // we should only do this for eink displays, which will persist after power loss + #ifdef THINKNODE_M1 + if (_display != NULL) { + _display->startFrame(); + _display->setTextSize(2); + _display->setColor(DisplayDriver::RED); + _display->drawTextCentered(_display->width() / 2, 20, "Low Battery."); + _display->drawTextCentered(_display->width() / 2, 40, "Shutting Down!"); + _display->endFrame(); + } + #endif + + shutdown(); + + } + next_batt_chck = millis() + 8000; + } +#endif +} + +char UITask::checkDisplayOn(char c) { + if (_display != NULL) { + if (!_display->isOn()) { + _display->turnOn(); // turn display on and consume event + c = 0; + } + _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer + _next_refresh = 0; // trigger refresh + } + return c; +} + +char UITask::handleLongPress(char c) { + if (millis() - ui_started_at < 8000) { // long press in first 8 seconds since startup -> CLI/rescue + the_mesh.enterCLIRescue(); + c = 0; // consume event + } + return c; +} + +/* +void UITask::handleButtonTriplePress() { + MESH_DEBUG_PRINTLN("UITask: triple press triggered"); + // Toggle buzzer quiet mode + #ifdef PIN_BUZZER + if (buzzer.isQuiet()) { + buzzer.quiet(false); + soundBuzzer(UIEventType::ack); + showAlert("Buzzer: ON", 600); + } else { + buzzer.quiet(true); + showAlert("Buzzer: OFF", 600); + } + _next_refresh = 0; // trigger refresh + #endif +} +*/ diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h new file mode 100644 index 00000000..f9e01550 --- /dev/null +++ b/examples/companion_radio/ui-new/UITask.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#ifdef PIN_BUZZER + #include +#endif + +#include "../AbstractUITask.h" +#include "../NodePrefs.h" + +class UITask : public AbstractUITask { + DisplayDriver* _display; + SensorManager* _sensors; +#ifdef PIN_BUZZER + genericBuzzer buzzer; +#endif + unsigned long _next_refresh, _auto_off; + NodePrefs* _node_prefs; + char _alert[80]; + unsigned long _alert_expiry; + int _msgcount; + unsigned long ui_started_at, next_batt_chck; + + UIScreen* splash; + UIScreen* home; + UIScreen* msg_preview; + UIScreen* curr; + + void userLedHandler(); + + // Button action handlers + char checkDisplayOn(char c); + char handleLongPress(char c); + + void setCurrScreen(UIScreen* c); + +public: + + UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : AbstractUITask(board, serial), _display(NULL), _sensors(NULL) { + next_batt_chck = _next_refresh = 0; + ui_started_at = 0; + curr = NULL; + } + void begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs); + + void gotoHomeScreen() { setCurrScreen(home); } + void showAlert(const char* text, int duration_millis); + int getMsgCount() const { return _msgcount; } + bool hasDisplay() const { return _display != NULL; } + bool isButtonPressed() const; + + // from AbstractUITask + void msgRead(int msgcount) override; + void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override; + void soundBuzzer(UIEventType bet = UIEventType::none) override; + void loop() override; + + void shutdown(bool restart = false); +}; diff --git a/examples/companion_radio/ui-new/icons.h b/examples/companion_radio/ui-new/icons.h new file mode 100644 index 00000000..5220f409 --- /dev/null +++ b/examples/companion_radio/ui-new/icons.h @@ -0,0 +1,118 @@ +#pragma once + +#include + +// 'meshcore', 128x13px +static const uint8_t meshcore_logo [] = { + 0x3c, 0x01, 0xe3, 0xff, 0xc7, 0xff, 0x8f, 0x03, 0x87, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, + 0x3c, 0x03, 0xe3, 0xff, 0xc7, 0xff, 0x8e, 0x03, 0x8f, 0xfe, 0x3f, 0xfe, 0x1f, 0xff, 0x1f, 0xfe, + 0x3e, 0x03, 0xc3, 0xff, 0x8f, 0xff, 0x0e, 0x07, 0x8f, 0xfe, 0x7f, 0xfe, 0x1f, 0xff, 0x1f, 0xfc, + 0x3e, 0x07, 0xc7, 0x80, 0x0e, 0x00, 0x0e, 0x07, 0x9e, 0x00, 0x78, 0x0e, 0x3c, 0x0f, 0x1c, 0x00, + 0x3e, 0x0f, 0xc7, 0x80, 0x1e, 0x00, 0x0e, 0x07, 0x1e, 0x00, 0x70, 0x0e, 0x38, 0x0f, 0x3c, 0x00, + 0x7f, 0x0f, 0xc7, 0xfe, 0x1f, 0xfc, 0x1f, 0xff, 0x1c, 0x00, 0x70, 0x0e, 0x38, 0x0e, 0x3f, 0xf8, + 0x7f, 0x1f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x0e, 0x38, 0x0e, 0x3f, 0xf8, + 0x7f, 0x3f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x1e, 0x3f, 0xfe, 0x3f, 0xf0, + 0x77, 0x3b, 0x87, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xfc, 0x38, 0x00, + 0x77, 0xfb, 0x8f, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xf8, 0x38, 0x00, + 0x73, 0xf3, 0x8f, 0xff, 0x0f, 0xff, 0x1c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x78, 0x7f, 0xf8, + 0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfe, 0x3c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x3c, 0x7f, 0xf8, + 0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8, +}; + +static const uint8_t bluetooth_on[] = { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x30, 0x00, 0x00, + 0x00, 0x3C, 0x00, 0x00, + 0x00, 0x3E, 0x00, 0x00, + 0x00, 0x3F, 0x80, 0x00, + 0x00, 0x3F, 0xC0, 0x00, + 0x00, 0x3B, 0xE0, 0x00, + 0x30, 0x38, 0xF8, 0x00, + 0x3C, 0x38, 0x7C, 0x00, + 0x3E, 0x38, 0x7C, 0x00, + 0x1F, 0xB8, 0xF8, 0x70, + 0x07, 0xF9, 0xF0, 0x78, + 0x03, 0xFF, 0xC0, 0x78, + 0x00, 0xFF, 0x80, 0x3C, + 0x00, 0x7F, 0x07, 0x1C, + 0x00, 0x7E, 0x07, 0x1C, + 0x03, 0xFF, 0x82, 0x1C, + 0x03, 0xFF, 0xC0, 0x78, + 0x07, 0xFB, 0xE0, 0x78, + 0x0F, 0xB8, 0xF8, 0x70, + 0x3E, 0x38, 0x7C, 0x00, + 0x3C, 0x38, 0x7C, 0x00, + 0x38, 0x38, 0xF8, 0x00, + 0x00, 0x39, 0xF0, 0x00, + 0x00, 0x3F, 0xC0, 0x00, + 0x00, 0x3F, 0x80, 0x00, + 0x00, 0x3E, 0x00, 0x00, + 0x00, 0x3C, 0x00, 0x00, + 0x00, 0x38, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t bluetooth_off[] = { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x80, 0x00, + 0x00, 0x03, 0xC0, 0x00, + 0x00, 0x03, 0xE0, 0x00, + 0x38, 0x03, 0xF8, 0x00, + 0x3C, 0x03, 0xFC, 0x00, + 0x3E, 0x03, 0xBF, 0x00, + 0x0F, 0x83, 0x8F, 0x80, + 0x07, 0xC3, 0x87, 0xC0, + 0x03, 0xF0, 0x03, 0xC0, + 0x00, 0xF8, 0x0F, 0x80, + 0x00, 0x7C, 0x0F, 0x00, + 0x00, 0x1F, 0x0E, 0x00, + 0x00, 0x0F, 0x80, 0x00, + 0x00, 0x07, 0xE0, 0x00, + 0x00, 0x07, 0xF0, 0x00, + 0x00, 0x0F, 0xF8, 0x00, + 0x00, 0x3F, 0xBE, 0x00, + 0x00, 0x7F, 0x9F, 0x00, + 0x00, 0xFB, 0x8F, 0xC0, + 0x03, 0xE3, 0x83, 0xE0, + 0x03, 0xC3, 0x87, 0xF0, + 0x03, 0x83, 0x8F, 0xFC, + 0x00, 0x03, 0xBF, 0x3C, + 0x00, 0x03, 0xFC, 0x1C, + 0x00, 0x03, 0xF8, 0x00, + 0x00, 0x03, 0xE0, 0x00, + 0x00, 0x03, 0xC0, 0x00, + 0x00, 0x03, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t power_icon[] = { + 0x00, 0x01, 0x80, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00, + 0x00, 0x33, 0xCC, 0x00, 0x00, 0xF3, 0xCF, 0x00, 0x01, 0xF3, 0xCF, 0x80, + 0x03, 0xF3, 0xCF, 0xC0, 0x07, 0xF3, 0xCF, 0xE0, 0x0F, 0xE3, 0xC7, 0xF0, + 0x1F, 0xC3, 0xC3, 0xF8, 0x1F, 0x83, 0xC1, 0xF8, 0x3F, 0x03, 0xC0, 0xFC, + 0x3E, 0x03, 0xC0, 0x7C, 0x3E, 0x03, 0xC0, 0x7C, 0x7E, 0x01, 0x80, 0x7E, + 0x7C, 0x00, 0x00, 0x3E, 0x7C, 0x00, 0x00, 0x3E, 0x7C, 0x00, 0x00, 0x3E, + 0x7C, 0x00, 0x00, 0x3E, 0x7C, 0x00, 0x00, 0x3E, 0x3E, 0x00, 0x00, 0x7C, + 0x3E, 0x00, 0x00, 0x7C, 0x3F, 0x00, 0x00, 0xFC, 0x1F, 0x80, 0x01, 0xF8, + 0x1F, 0xC0, 0x03, 0xF8, 0x0F, 0xE0, 0x07, 0xF0, 0x0F, 0xF8, 0x1F, 0xF0, + 0x07, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x3F, 0xFC, 0x00, 0x00, 0x0F, 0xF0, 0x00, +}; + +static const uint8_t advert_icon[] = { +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x30, +0x1C, 0x00, 0x00, 0x38, 0x18, 0x00, 0x00, 0x18, 0x30, 0x00, 0x00, 0x0C, +0x30, 0x60, 0x06, 0x0C, 0x60, 0xE0, 0x07, 0x06, 0x61, 0xC0, 0x03, 0x86, +0xE1, 0x81, 0x81, 0x87, 0xC3, 0x07, 0xE0, 0xC3, 0xC3, 0x0F, 0xF0, 0xC3, +0xC3, 0x0F, 0xF0, 0xC3, 0xC3, 0x0F, 0xF0, 0xC3, 0xC3, 0x0F, 0xF0, 0xC3, +0xC3, 0x07, 0xE0, 0xC3, 0xC1, 0x83, 0xC1, 0x83, 0x61, 0x80, 0x01, 0x86, +0x60, 0xC0, 0x03, 0x06, 0x70, 0xE0, 0x07, 0x0E, 0x30, 0x40, 0x02, 0x0C, +0x38, 0x00, 0x00, 0x1C, 0x18, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00, 0x30, +0x04, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; \ No newline at end of file diff --git a/examples/companion_radio/Button.cpp b/examples/companion_radio/ui-orig/Button.cpp similarity index 100% rename from examples/companion_radio/Button.cpp rename to examples/companion_radio/ui-orig/Button.cpp diff --git a/examples/companion_radio/Button.h b/examples/companion_radio/ui-orig/Button.h similarity index 100% rename from examples/companion_radio/Button.h rename to examples/companion_radio/ui-orig/Button.h diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp similarity index 99% rename from examples/companion_radio/UITask.cpp rename to examples/companion_radio/ui-orig/UITask.cpp index a7f03a26..29d995a7 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -1,8 +1,7 @@ #include "UITask.h" #include #include -#include "NodePrefs.h" -#include "MyMesh.h" +#include "../MyMesh.h" #define AUTO_OFF_MILLIS 15000 // 15 seconds #define BOOT_SCREEN_MILLIS 3000 // 3 seconds diff --git a/examples/companion_radio/UITask.h b/examples/companion_radio/ui-orig/UITask.h similarity index 73% rename from examples/companion_radio/UITask.h rename to examples/companion_radio/ui-orig/UITask.h index 77ef875f..a59ddc41 100644 --- a/examples/companion_radio/UITask.h +++ b/examples/companion_radio/ui-orig/UITask.h @@ -9,28 +9,18 @@ #include #endif -#include "NodePrefs.h" +#include "../AbstractUITask.h" +#include "../NodePrefs.h" + #include "Button.h" - enum class UIEventType -{ - none, - contactMessage, - channelMessage, - roomMessage, - newContactMessage, - ack -}; - -class UITask { +class UITask : public AbstractUITask { DisplayDriver* _display; - mesh::MainBoard* _board; SensorManager* _sensors; #ifdef PIN_BUZZER genericBuzzer buzzer; #endif unsigned long _next_refresh, _auto_off; - bool _connected; NodePrefs* _node_prefs; char _version_info[32]; char _origin[62]; @@ -64,19 +54,20 @@ class UITask { public: - UITask(mesh::MainBoard* board) : _board(board), _display(NULL), _sensors(NULL) { + UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : AbstractUITask(board, serial), _display(NULL), _sensors(NULL) { _next_refresh = 0; ui_started_at = 0; - _connected = false; } void begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs); - void setHasConnection(bool connected) { _connected = connected; } bool hasDisplay() const { return _display != NULL; } void clearMsgPreview(); - void msgRead(int msgcount); - void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount); - void soundBuzzer(UIEventType bet = UIEventType::none); + + // from AbstractUITask + void msgRead(int msgcount) override; + void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override; + void soundBuzzer(UIEventType bet = UIEventType::none) override; + void loop() override; + void shutdown(bool restart = false); - void loop(); }; diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index a28df7a4..2fda6b85 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 "24 Jul 2025" + #define FIRMWARE_BUILD_DATE "31 Aug 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.7.4" + #define FIRMWARE_VERSION "v1.8.0" #endif #ifndef LORA_FREQ @@ -98,6 +98,7 @@ struct RepeaterStats { uint16_t err_events; // was 'n_full_events' int16_t last_snr; // x 4 uint16_t n_direct_dups, n_flood_dups; + uint32_t total_rx_air_time_secs; }; struct ClientInfo { @@ -158,7 +159,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { } void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr) { - #if MAX_NEIGHBOURS // check if neighbours enabled + #if MAX_NEIGHBOURS // check if neighbours enabled // find existing neighbour, else use least recently updated uint32_t oldest_timestamp = 0xFFFFFFFF; NeighbourInfo* neighbour = &neighbours[0]; @@ -208,16 +209,19 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { stats.last_snr = (int16_t)(radio_driver.getLastSNR() * 4); stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups(); stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups(); + stats.total_rx_air_time_secs = getReceiveAirTime() / 1000; memcpy(&reply_data[4], &stats, sizeof(stats)); return 4 + sizeof(stats); // reply_len } case REQ_TYPE_GET_TELEMETRY_DATA: { + uint8_t perm_mask = ~(payload[1]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions + 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); + sensors.querySensors((sender->is_admin ? 0xFF : 0x00) & perm_mask, telemetry); uint8_t tlen = telemetry.getSize(); memcpy(&reply_data[4], telemetry.getBuffer(), tlen); @@ -585,7 +589,7 @@ public: _prefs.cr = LORA_CR; _prefs.tx_power_dbm = LORA_TX_POWER; _prefs.advert_interval = 1; // default to 2 minutes for NEW installs - _prefs.flood_advert_interval = 3; // 3 hours + _prefs.flood_advert_interval = 12; // 12 hours _prefs.flood_max = 64; _prefs.interference_threshold = 0; // disabled } @@ -607,8 +611,8 @@ 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; + NodePrefs* getNodePrefs() { + return &_prefs; } void savePrefs() override { @@ -715,7 +719,18 @@ public: *dp = 0; // null terminator } - const uint8_t* getSelfIdPubKey() override { return self_id.pub_key; } + void removeNeighbor(const uint8_t* pubkey, int key_len) override { +#if MAX_NEIGHBOURS + for (int i = 0; i < MAX_NEIGHBOURS; i++) { + NeighbourInfo* neighbour = &neighbours[i]; + if(memcmp(neighbour->id.pub_key, pubkey, key_len) == 0){ + neighbours[i] = NeighbourInfo(); // clear neighbour entry + } + } +#endif + } + + mesh::LocalIdentity& getSelfId() override { return self_id; } void clearStats() override { radio_driver.resetStats(); @@ -778,7 +793,7 @@ void halt() { while (1) ; } -static char command[80]; +static char command[160]; void setup() { Serial.begin(115200); diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 9a416835..77e59d99 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 "24 Jul 2025" + #define FIRMWARE_BUILD_DATE "31 Aug 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.7.4" + #define FIRMWARE_VERSION "v1.8.0" #endif #ifndef LORA_FREQ @@ -298,7 +298,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { // 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; @@ -326,10 +326,12 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { } case REQ_TYPE_GET_TELEMETRY_DATA: { + uint8_t perm_mask = ~(payload[1]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions + 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); + sensors.querySensors((sender->permission == RoomPermission::ADMIN ? 0xFF : 0x00) & perm_mask, telemetry); uint8_t tlen = telemetry.getSize(); memcpy(&reply_data[4], telemetry.getBuffer(), tlen); @@ -744,9 +746,9 @@ public: _prefs.tx_power_dbm = LORA_TX_POWER; _prefs.disable_fwd = 1; _prefs.advert_interval = 1; // default to 2 minutes for NEW installs - _prefs.flood_advert_interval = 3; // 3 hours + _prefs.flood_advert_interval = 12; // 12 hours _prefs.flood_max = 64; - _prefs.interference_threshold = 0; // disabled + _prefs.interference_threshold = 0; // disabled #ifdef ROOM_PASSWORD StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password)); #endif @@ -776,8 +778,8 @@ 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; + NodePrefs* getNodePrefs() { + return &_prefs; } void savePrefs() override { @@ -861,7 +863,7 @@ public: strcpy(reply, "not supported"); } - const uint8_t* getSelfIdPubKey() override { return self_id.pub_key; } + mesh::LocalIdentity& getSelfId() override { return self_id; } void clearStats() override { radio_driver.resetStats(); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 0816af72..ad295501 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -244,10 +244,12 @@ uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag') if (req_type == REQ_TYPE_GET_TELEMETRY_DATA) { // allow all + uint8_t perm_mask = ~(payload[0]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions + telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); // query other sensors -- target specific - sensors.querySensors(0xFF, telemetry); // allow all telemetry permissions for admin or guest + sensors.querySensors(0xFF & perm_mask, telemetry); // allow all telemetry permissions for admin or guest // TODO: let requester know permissions they have: telemetry.addPresence(TELEM_CHANNEL_SELF, perms); uint8_t tlen = telemetry.getSize(); @@ -545,7 +547,26 @@ void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* r Serial.printf("\n"); } reply[0] = 0; - } else { + } else if (memcmp(command, "io ", 2) == 0) { // io {value}: write, io: read + if (command[2] == ' ') { // it's a write + uint32_t val; + uint32_t g = board.getGpio(); + if (command[3] == 'r') { // reset bits + sscanf(&command[4], "%x", &val); + val = g & ~val; + } else if (command[3] == 's') { // set bits + sscanf(&command[4], "%x", &val); + val |= g; + } else if (command[3] == 't') { // toggle bits + sscanf(&command[4], "%x", &val); + val ^= g; + } else { // set value + sscanf(&command[3], "%x", &val); + } + board.setGpio(val); + } + sprintf(reply, "%x", board.getGpio()); + } else{ _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands } } @@ -592,6 +613,23 @@ void SensorMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { } } +void SensorMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) { + if (dest.out_path_len < 0) { + mesh::Packet* ack = createAck(ack_hash); + if (ack) sendFlood(ack, TXT_ACK_DELAY); + } else { + uint32_t d = TXT_ACK_DELAY; + if (getExtraAckTransmitCount() > 0) { + mesh::Packet* a1 = createMultiAck(ack_hash, 1); + if (a1) sendDirect(a1, dest.out_path, dest.out_path_len, d); + d += 300; + } + + mesh::Packet* a2 = createAck(ack_hash); + if (a2) sendDirect(a2, dest.out_path, dest.out_path_len, d); + } +} + void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) { int i = matching_peer_indexes[sender_idx]; if (i < 0 || i >= num_contacts) { @@ -635,38 +673,55 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) uint flags = (data[4] >> 2); // message attempt number, and other flags - if (!(flags == TXT_TYPE_CLI_DATA)) { - MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported text type received: flags=%02x", (uint32_t)flags); - } else if (sender_timestamp > from.last_timestamp) { // prevent replay attacks - from.last_timestamp = sender_timestamp; - from.last_activity = getRTCClock()->getCurrentTime(); + if (sender_timestamp > from.last_timestamp) { // prevent replay attacks + if (flags == TXT_TYPE_PLAIN) { + bool handled = handleIncomingMsg(from, sender_timestamp, &data[5], flags, len - 5); + if (handled) { // if msg was handled then send an ack + uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it + mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, 5 + strlen((char *)&data[5]), from.id.pub_key, PUB_KEY_SIZE); - // len can be > original length, but 'text' will be padded with zeroes - data[len] = 0; // need to make a C string again, with null terminator - - uint8_t temp[166]; - char *command = (char *) &data[5]; - char *reply = (char *) &temp[5]; - handleCommand(sender_timestamp, command, reply); - - int text_len = strlen(reply); - if (text_len > 0) { - uint32_t timestamp = getRTCClock()->getCurrentTimeUnique(); - if (timestamp == sender_timestamp) { - // WORKAROUND: the two timestamps need to be different, in the CLI view - timestamp++; - } - memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique - temp[4] = (TXT_TYPE_CLI_DATA << 2); - - auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, from.id, secret, temp, 5 + text_len); - if (reply) { - if (from.out_path_len < 0) { - sendFlood(reply, CLI_REPLY_DELAY_MILLIS); + if (packet->isRouteFlood()) { + // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK + mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, + PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); + if (path) sendFlood(path, TXT_ACK_DELAY); } else { - sendDirect(reply, from.out_path, from.out_path_len, CLI_REPLY_DELAY_MILLIS); + sendAckTo(from, ack_hash); + } + } + } else if (flags == TXT_TYPE_CLI_DATA) { + from.last_timestamp = sender_timestamp; + from.last_activity = getRTCClock()->getCurrentTime(); + + // len can be > original length, but 'text' will be padded with zeroes + data[len] = 0; // need to make a C string again, with null terminator + + uint8_t temp[166]; + char *command = (char *) &data[5]; + char *reply = (char *) &temp[5]; + handleCommand(sender_timestamp, command, reply); + + int text_len = strlen(reply); + if (text_len > 0) { + uint32_t timestamp = getRTCClock()->getCurrentTimeUnique(); + if (timestamp == sender_timestamp) { + // WORKAROUND: the two timestamps need to be different, in the CLI view + timestamp++; + } + memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique + temp[4] = (TXT_TYPE_CLI_DATA << 2); + + auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, from.id, secret, temp, 5 + text_len); + if (reply) { + if (from.out_path_len < 0) { + sendFlood(reply, CLI_REPLY_DELAY_MILLIS); + } else { + sendDirect(reply, from.out_path, from.out_path_len, CLI_REPLY_DELAY_MILLIS); + } } } + } else { + MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported text type received: flags=%02x", (uint32_t)flags); } } else { MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected"); @@ -674,6 +729,15 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i } } +bool SensorMesh::handleIncomingMsg(ContactInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len) { + MESH_DEBUG_PRINT("handleIncomingMsg: unhandled msg from "); + #ifdef MESH_DEBUG + mesh::Utils::printHex(Serial, from.id.pub_key, PUB_KEY_SIZE); + Serial.printf(": %s\n", data); + #endif + return false; +} + bool SensorMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { int i = matching_peer_indexes[sender_idx]; if (i < 0 || i >= num_contacts) { diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 8f6e3bc3..ddcdf685 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -49,11 +49,11 @@ struct ContactInfo { }; #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "24 Jul 2025" + #define FIRMWARE_BUILD_DATE "31 Aug 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.7.4" + #define FIRMWARE_VERSION "v1.8.0" #endif #define FIRMWARE_ROLE "sensor" @@ -88,7 +88,7 @@ public: void formatNeighborsReply(char *reply) override { strcpy(reply, "not supported"); } - const uint8_t* getSelfIdPubKey() override { return self_id.pub_key; } + mesh::LocalIdentity& getSelfId() override { return self_id; } void clearStats() override { } void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override; @@ -140,7 +140,8 @@ protected: void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; - + virtual bool handleIncomingMsg(ContactInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len); + void sendAckTo(const ContactInfo& dest, uint32_t ack_hash); private: FILESYSTEM* _fs; unsigned long next_local_advert, next_flood_advert; diff --git a/examples/simple_sensor/main.cpp b/examples/simple_sensor/main.cpp index c9e282a2..2dacd1b4 100644 --- a/examples/simple_sensor/main.cpp +++ b/examples/simple_sensor/main.cpp @@ -50,7 +50,7 @@ void halt() { while (1) ; } -static char command[120]; +static char command[160]; void setup() { Serial.begin(115200); diff --git a/library.json b/library.json new file mode 100644 index 00000000..982983a3 --- /dev/null +++ b/library.json @@ -0,0 +1,16 @@ +{ + "name": "MeshCore", + "version" : "1.7.4", + "dependencies": { + "SPI": "*", + "Wire": "*", + "jgromes/RadioLib": "^7.1.2", + "rweather/Crypto": "^0.4.0", + "adafruit/RTClib": "^2.1.3", + "melopero/Melopero RV3028": "^1.1.0", + "electroniccats/CayenneLPP": "1.4.0" + }, + "build": { + "extraScript": "build_as_lib.py" + } +} \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index cd1c21ad..c7d4eff8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -47,6 +47,7 @@ build_src_filter = +<*.cpp> + + + + ; ----------------- ESP32 --------------------- @@ -67,7 +68,7 @@ lib_deps = ; esp32c6 uses arduino framework 3.x [esp32c6_base] extends = esp32_base -platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.12/platform-espressif32.zip ; ----------------- NRF52 --------------------- @@ -102,3 +103,33 @@ build_src_filter = ${arduino_base.build_src_filter} + lib_deps = ${arduino_base.lib_deps} file://arch/stm32/Adafruit_LittleFS_stm32 + +[sensor_base] +build_flags = + -D ENV_INCLUDE_GPS=1 + -D ENV_INCLUDE_AHTX0=1 + -D ENV_INCLUDE_BME280=1 + -D ENV_INCLUDE_BMP280=1 + -D ENV_INCLUDE_SHTC3=1 + -D ENV_INCLUDE_SHT4X=1 + -D ENV_INCLUDE_LPS22HB=1 + -D ENV_INCLUDE_INA3221=1 + -D ENV_INCLUDE_INA219=1 + -D ENV_INCLUDE_INA226=1 + -D ENV_INCLUDE_INA260=1 + -D ENV_INCLUDE_MLX90614=1 + -D ENV_INCLUDE_VL53L0X=1 +lib_deps = + adafruit/Adafruit INA3221 Library @ ^1.0.1 + adafruit/Adafruit INA219 @ ^1.2.3 + robtillaart/INA226 @ ^0.6.4 + adafruit/Adafruit INA260 Library @ ^1.5.3 + adafruit/Adafruit AHTX0 @ ^2.0.5 + adafruit/Adafruit BME280 Library @ ^2.3.0 + adafruit/Adafruit BMP280 Library @ ^2.6.8 + adafruit/Adafruit SHTC3 Library @ ^1.0.1 + sensirion/Sensirion I2C SHT4x @ ^1.1.2 + arduino-libraries/Arduino_LPS22HB @ ^1.0.2 + adafruit/Adafruit MLX90614 Library @ ^2.1.5 + adafruit/Adafruit_VL53L0X @ ^1.2.4 + stevemarple/MicroNMEA @ ^2.0.6 diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 7f39dc49..0a154985 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -159,6 +159,7 @@ void Dispatcher::checkRecv() { pkt->_snr = _radio->getLastSNR() * 4.0f; score = _radio->packetScore(_radio->getLastSNR(), len); air_time = _radio->getEstAirtimeFor(len); + rx_air_time += air_time; } } } @@ -169,9 +170,9 @@ void Dispatcher::checkRecv() { if (pkt) { #if MESH_PACKET_LOGGING Serial.print(getLogDateTime()); - Serial.printf(": RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d", + Serial.printf(": RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d time=%d", pkt->getRawLength(), pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len, - (int)pkt->getSNR(), (int)_radio->getLastRSSI(), (int)(score*1000)); + (int)pkt->getSNR(), (int)_radio->getLastRSSI(), (int)(score*1000), air_time); static uint8_t packet_hash[MAX_HASH_SIZE]; pkt->calculatePacketHash(packet_hash); diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 2200f81b..25a41d82 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -114,7 +114,7 @@ typedef uint32_t DispatcherAction; */ class Dispatcher { Packet* outbound; // current outbound packet - unsigned long outbound_expiry, outbound_start, total_air_time; + unsigned long outbound_expiry, outbound_start, total_air_time, rx_air_time; unsigned long next_tx_time; unsigned long cad_busy_start; unsigned long radio_nonrx_start; @@ -134,7 +134,9 @@ protected: Dispatcher(Radio& radio, MillisecondClock& ms, PacketManager& mgr) : _radio(&radio), _ms(&ms), _mgr(&mgr) { - outbound = NULL; total_air_time = 0; next_tx_time = 0; + outbound = NULL; + total_air_time = rx_air_time = 0; + next_tx_time = 0; cad_busy_start = 0; next_floor_calib_time = next_agc_reset_time = 0; _err_flags = 0; @@ -167,6 +169,7 @@ public: void sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis=0); unsigned long getTotalAirTime() const { return total_air_time; } // in milliseconds + unsigned long getReceiveAirTime() const {return rx_air_time; } uint32_t getNumSentFlood() const { return n_sent_flood; } uint32_t getNumSentDirect() const { return n_sent_direct; } uint32_t getNumRecvFlood() const { return n_recv_flood; } diff --git a/src/MeshCore.h b/src/MeshCore.h index 98134e50..d8886136 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -41,6 +41,8 @@ public: virtual void onAfterTransmit() { } virtual void reboot() = 0; virtual void powerOff() { /* no op */ } + virtual uint32_t getGpio() { return 0; } + virtual void setGpio(uint32_t values) {} virtual uint8_t getStartupReason() const = 0; virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported }; diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 476e6e8f..60366c65 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -235,9 +235,13 @@ bool BaseChatMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const ui ContactInfo& from = contacts[i]; - // NOTE: for this impl, we just replace the current 'out_path' regardless, whenever sender sends us a new out_path. + return onContactPathRecv(from, packet->path, packet->path_len, path, path_len, extra_type, extra, extra_len); +} + +bool BaseChatMesh::onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { + // NOTE: default impl, we just replace the current 'out_path' regardless, whenever sender sends us a new out_path. // FUTURE: could store multiple out_paths per contact, and try to find which is the 'best'(?) - memcpy(from.out_path, path, from.out_path_len = path_len); // store a copy of path, for sendDirect() + memcpy(from.out_path, out_path, from.out_path_len = out_path_len); // store a copy of path, for sendDirect() from.lastmod = getRTCClock()->getCurrentTime(); onContactPathUpdated(from); diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 683af852..9a4aa810 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -88,11 +88,14 @@ protected: memset(connections, 0, sizeof(connections)); } + void resetContacts() { num_contacts = 0; } + // 'UI' concepts, for sub-classes to implement virtual bool isAutoAddEnabled() const { return true; } virtual void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) = 0; virtual bool processAck(const uint8_t *data) = 0; virtual void onContactPathUpdated(const ContactInfo& contact) = 0; + virtual bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len); virtual void onMessageRecv(const ContactInfo& contact, mesh::Packet* pkt, uint32_t sender_timestamp, const char *text) = 0; virtual void onCommandDataRecv(const ContactInfo& contact, mesh::Packet* pkt, uint32_t sender_timestamp, const char *text) = 0; virtual void onSignedMessageRecv(const ContactInfo& contact, mesh::Packet* pkt, uint32_t sender_timestamp, const uint8_t *sender_prefix, const char *text) = 0; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index d62253f9..45dfe29b 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -165,6 +165,17 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } } else if (memcmp(command, "neighbors", 9) == 0) { _callbacks->formatNeighborsReply(reply); + } else if (memcmp(command, "neighbor.remove ", 16) == 0) { + const char* hex = &command[16]; + uint8_t pubkey[PUB_KEY_SIZE]; + int hex_len = min((int)strlen(hex), PUB_KEY_SIZE*2); + int pubkey_len = hex_len / 2; + if (mesh::Utils::fromHex(pubkey, pubkey_len, hex)) { + _callbacks->removeNeighbor(pubkey, pubkey_len); + strcpy(reply, "OK"); + } else { + strcpy(reply, "ERR: bad pubkey"); + } } else if (memcmp(command, "tempradio ", 10) == 0) { strcpy(tmp, &command[10]); const char *parts[5]; @@ -206,6 +217,11 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "> %d", ((uint32_t) _prefs->advert_interval) * 2); } else if (memcmp(config, "guest.password", 14) == 0) { sprintf(reply, "> %s", _prefs->guest_password); + } else if (sender_timestamp == 0 && memcmp(config, "prv.key", 7) == 0) { // from serial command line only + uint8_t prv_key[PRV_KEY_SIZE]; + int len = _callbacks->getSelfId().writeTo(prv_key, PRV_KEY_SIZE); + mesh::Utils::toHex(tmp, prv_key, len); + sprintf(reply, "> %s", tmp); } else if (memcmp(config, "name", 4) == 0) { sprintf(reply, "> %s", _prefs->node_name); } else if (memcmp(config, "repeat", 6) == 0) { @@ -233,7 +249,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], _callbacks->getSelfIdPubKey(), PUB_KEY_SIZE); + mesh::Utils::toHex(&reply[2], _callbacks->getSelfId().pub_key, PUB_KEY_SIZE); } else if (memcmp(config, "role", 4) == 0) { sprintf(reply, "> %s", _callbacks->getRole()); } else { @@ -252,7 +268,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else if (memcmp(config, "agc.reset.interval ", 19) == 0) { _prefs->agc_reset_interval = atoi(&config[19]) / 4; savePrefs(); - strcpy(reply, "OK"); + sprintf(reply, "OK - interval rounded to %d", ((uint32_t) _prefs->agc_reset_interval) * 4); } else if (memcmp(config, "multi.acks ", 11) == 0) { _prefs->multi_acks = atoi(&config[11]); savePrefs(); @@ -285,6 +301,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch StrHelper::strncpy(_prefs->guest_password, &config[15], sizeof(_prefs->guest_password)); savePrefs(); strcpy(reply, "OK"); + } else if (sender_timestamp == 0 && memcmp(config, "prv.key ", 8) == 0) { // from serial command line only + uint8_t prv_key[PRV_KEY_SIZE]; + bool success = mesh::Utils::fromHex(prv_key, PRV_KEY_SIZE, &config[8]); + if (success) { + _callbacks->getSelfId().readFrom(prv_key, PRV_KEY_SIZE); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error, invalid key"); + } } else if (memcmp(config, "name ", 5) == 0) { StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name)); savePrefs(); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index e2608379..d1e49873 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -43,7 +43,10 @@ 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; + virtual void removeNeighbor(const uint8_t* pubkey, int key_len) { + // no op by default + }; + virtual mesh::LocalIdentity& getSelfId() = 0; virtual void clearStats() = 0; virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0; }; @@ -53,7 +56,7 @@ class CommonCLI { NodePrefs* _prefs; CommonCLICallbacks* _callbacks; mesh::MainBoard* _board; - char tmp[80]; + char tmp[PRV_KEY_SIZE*2 + 4]; mesh::RTCClock* getRTCClock() { return _rtc; } void savePrefs(); diff --git a/src/helpers/RefCountedDigitalPin.h b/src/helpers/RefCountedDigitalPin.h index 14b67fb1..753f6c30 100644 --- a/src/helpers/RefCountedDigitalPin.h +++ b/src/helpers/RefCountedDigitalPin.h @@ -5,25 +5,25 @@ class RefCountedDigitalPin { uint8_t _pin; int8_t _claims = 0; - + uint8_t _active = 0; public: - RefCountedDigitalPin(uint8_t pin): _pin(pin) { } + RefCountedDigitalPin(uint8_t pin,uint8_t active=HIGH): _pin(pin), _active(active) { } void begin() { pinMode(_pin, OUTPUT); - digitalWrite(_pin, LOW); // initial state + digitalWrite(_pin, !_active); // initial state } void claim() { _claims++; if (_claims > 0) { - digitalWrite(_pin, HIGH); + digitalWrite(_pin, _active); } } void release() { _claims--; if (_claims == 0) { - digitalWrite(_pin, LOW); + digitalWrite(_pin, !_active); } } }; diff --git a/src/helpers/esp32/SerialBLEInterface.cpp b/src/helpers/esp32/SerialBLEInterface.cpp index 8a8710a7..1be703a8 100644 --- a/src/helpers/esp32/SerialBLEInterface.cpp +++ b/src/helpers/esp32/SerialBLEInterface.cpp @@ -83,6 +83,7 @@ void SerialBLEInterface::onConnect(BLEServer* pServer) { void SerialBLEInterface::onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param) { BLE_DEBUG_PRINTLN("onConnect(), conn_id=%d, mtu=%d", param->connect.conn_id, pServer->getPeerMTU(param->connect.conn_id)); + last_conn_id = param->connect.conn_id; } void SerialBLEInterface::onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) { @@ -143,6 +144,7 @@ void SerialBLEInterface::disable() { BLE_DEBUG_PRINTLN("SerialBLEInterface::disable"); pServer->getAdvertising()->stop(); + pServer->disconnect(last_conn_id); pService->stop(); oldDeviceConnected = deviceConnected = false; adv_restart_time = 0; diff --git a/src/helpers/esp32/SerialBLEInterface.h b/src/helpers/esp32/SerialBLEInterface.h index bf1eee09..29ad897a 100644 --- a/src/helpers/esp32/SerialBLEInterface.h +++ b/src/helpers/esp32/SerialBLEInterface.h @@ -13,6 +13,7 @@ class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLE bool deviceConnected; bool oldDeviceConnected; bool _isEnabled; + uint16_t last_conn_id; uint32_t _pin_code; unsigned long _last_write; unsigned long adv_restart_time; @@ -56,6 +57,7 @@ public: adv_restart_time = 0; _isEnabled = false; _last_write = 0; + last_conn_id = 0; send_queue_len = recv_queue_len = 0; } diff --git a/src/helpers/esp32/SerialWifiInterface.cpp b/src/helpers/esp32/SerialWifiInterface.cpp index e7dc055e..2df9980a 100644 --- a/src/helpers/esp32/SerialWifiInterface.cpp +++ b/src/helpers/esp32/SerialWifiInterface.cpp @@ -44,7 +44,18 @@ bool SerialWifiInterface::isWriteBusy() const { } size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { - if (!client) client = server.available(); + // check if new client connected + auto newClient = server.available(); + if (newClient) { + + // disconnect existing client + deviceConnected = false; + client.stop(); + + // switch active connection to new client + client = newClient; + + } if (client.connected()) { if (!deviceConnected) { diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index a8c11d97..170a7331 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -115,6 +115,20 @@ void SerialBLEInterface::enable() { void SerialBLEInterface::disable() { _isEnabled = false; BLE_DEBUG_PRINTLN("SerialBLEInterface::disable"); + +#ifdef RAK_BOARD + Bluefruit.disconnect(Bluefruit.connHandle()); +#else + uint16_t conn_id; + if (Bluefruit.getConnectedHandles(&conn_id, 1) > 0) { + Bluefruit.disconnect(conn_id); + } +#endif + + Bluefruit.Advertising.restartOnDisconnect(false); + Bluefruit.Advertising.stop(); + Bluefruit.Advertising.clearData(); + stopAdv(); } diff --git a/src/helpers/nrf52/T114Board.cpp b/src/helpers/nrf52/T114Board.cpp index 1f8c5854..78c21b33 100644 --- a/src/helpers/nrf52/T114Board.cpp +++ b/src/helpers/nrf52/T114Board.cpp @@ -26,6 +26,45 @@ void T114Board::begin() { pinMode(PIN_VBAT_READ, INPUT); + // Enable SoftDevice low-power mode + sd_power_mode_set(NRF_POWER_MODE_LOWPWR); + + // Enable DC/DC converter for better efficiency (REG1 stage) + NRF_POWER->DCDCEN = 1; + + // Power down unused communication peripherals + // UART1 - Not used on T114 + NRF_UARTE1->ENABLE = 0; + + // SPIM2/SPIS2 - Not used (SPI is on SPIM0) + NRF_SPIM2->ENABLE = 0; + NRF_SPIS2->ENABLE = 0; + + // TWI1 (I2C1) - Not used (I2C is on TWI0) + NRF_TWIM1->ENABLE = 0; + NRF_TWIS1->ENABLE = 0; + + // PWM modules - Not used for standard T114 functions + NRF_PWM1->ENABLE = 0; + NRF_PWM2->ENABLE = 0; + NRF_PWM3->ENABLE = 0; + + // PDM (Digital Microphone Interface) - Not used + NRF_PDM->ENABLE = 0; + + // I2S - Not used + NRF_I2S->ENABLE = 0; + + // QSPI - Not used (no external flash) + NRF_QSPI->ENABLE = 0; + + // Disable unused analog peripherals + // SAADC channels - only keep what's needed for battery monitoring + NRF_SAADC->ENABLE = 0; // Re-enable only when needed for measurements + + // COMP - Comparator not used + NRF_COMP->ENABLE = 0; + #if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL); #endif diff --git a/src/helpers/nrf52/T114Board.h b/src/helpers/nrf52/T114Board.h index 154ccb22..cf345937 100644 --- a/src/helpers/nrf52/T114Board.h +++ b/src/helpers/nrf52/T114Board.h @@ -40,6 +40,9 @@ public: uint16_t getBattMilliVolts() override { int adcvalue = 0; + + NRF_SAADC->ENABLE = 1; + analogReadResolution(12); analogReference(AR_INTERNAL_3_0); pinMode(PIN_BAT_CTL, OUTPUT); // battery adc can be read only ctrl pin 6 set to high @@ -49,6 +52,8 @@ public: adcvalue = analogRead(PIN_VBAT_READ); digitalWrite(6, 0); + NRF_SAADC->ENABLE = 0; + return (uint16_t)((float)adcvalue * MV_LSB * 4.9); } @@ -60,5 +65,9 @@ public: NVIC_SystemReset(); } + void powerOff() override { + sd_power_system_off(); + } + bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/src/helpers/nrf52/TechoBoard.h b/src/helpers/nrf52/TechoBoard.h index c8ef7006..2c05c4ed 100644 --- a/src/helpers/nrf52/TechoBoard.h +++ b/src/helpers/nrf52/TechoBoard.h @@ -43,6 +43,25 @@ public: return "LilyGo T-Echo"; } + void powerOff() override { + #ifdef LED_RED + digitalWrite(LED_RED, LOW); + #endif + #ifdef LED_GREEN + digitalWrite(LED_GREEN, LOW); + #endif + #ifdef LED_BLUE + digitalWrite(LED_BLUE, LOW); + #endif + #ifdef DISP_BACKLIGHT + digitalWrite(DISP_BACKLIGHT, LOW); + #endif + #ifdef PIN_PWR_EN + digitalWrite(PIN_PWR_EN, LOW); + #endif + sd_power_system_off(); + } + void reboot() override { NVIC_SystemReset(); } diff --git a/src/helpers/nrf52/ThinkNodeM1Board.h b/src/helpers/nrf52/ThinkNodeM1Board.h index 97334bd3..fc752223 100644 --- a/src/helpers/nrf52/ThinkNodeM1Board.h +++ b/src/helpers/nrf52/ThinkNodeM1Board.h @@ -55,4 +55,16 @@ public: void reboot() override { NVIC_SystemReset(); } + + void powerOff() override { + + // turn off all leds, sd_power_system_off will not do this for us + #ifdef P_LORA_TX_LED + digitalWrite(P_LORA_TX_LED, LOW); + #endif + + // power off board + sd_power_system_off(); + + } }; diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index c95b1828..f444b67b 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -35,6 +35,12 @@ static Adafruit_BMP280 BMP280; static Adafruit_SHTC3 SHTC3; #endif +#if ENV_INCLUDE_SHT4X +#define TELEM_SHT4X_ADDRESS 0x44 //0x44 - 0x46 +#include +static SensirionI2cSht4x SHT4X; +#endif + #if ENV_INCLUDE_LPS22HB #include #endif @@ -53,6 +59,20 @@ static Adafruit_INA3221 INA3221; static Adafruit_INA219 INA219(TELEM_INA219_ADDRESS); #endif +#if ENV_INCLUDE_INA260 +#define TELEM_INA260_ADDRESS 0x41 // INA260 single channel current sensor I2C address +#include +static Adafruit_INA260 INA260; +#endif + +#if ENV_INCLUDE_INA226 +#define TELEM_INA226_ADDRESS 0x44 +#define TELEM_INA226_SHUNT_VALUE 0.100 +#define TELEM_INA226_MAX_AMP 0.8 +#include +static INA226 INA226(TELEM_INA226_ADDRESS); +#endif + #if ENV_INCLUDE_MLX90614 #define TELEM_MLX90614_ADDRESS 0x5A // MLX90614 IR temperature sensor I2C address #include @@ -130,6 +150,21 @@ bool EnvironmentSensorManager::begin() { } #endif + + #if ENV_INCLUDE_SHT4X + SHT4X.begin(*TELEM_WIRE, TELEM_SHT4X_ADDRESS); + uint32_t serialNumber = 0; + int16_t sht4x_error; + sht4x_error = SHT4X.serialNumber(serialNumber); + if (sht4x_error == 0) { + MESH_DEBUG_PRINTLN("Found SHT4X at address: %02X", TELEM_SHT4X_ADDRESS); + SHT4X_initialized = true; + } else { + SHT4X_initialized = false; + MESH_DEBUG_PRINTLN("SHT4X was not found at I2C address %02X", TELEM_SHT4X_ADDRESS); + } + #endif + #if ENV_INCLUDE_LPS22HB if (BARO.begin()) { MESH_DEBUG_PRINTLN("Found sensor: LPS22HB"); @@ -165,6 +200,27 @@ bool EnvironmentSensorManager::begin() { } #endif + #if ENV_INCLUDE_INA260 + if (INA260.begin(TELEM_INA260_ADDRESS, TELEM_WIRE)) { + MESH_DEBUG_PRINTLN("Found INA260 at address: %02X", TELEM_INA260_ADDRESS); + INA260_initialized = true; + } else { + INA260_initialized = false; + MESH_DEBUG_PRINTLN("INA260 was not found at I2C address %02X", TELEM_INA219_ADDRESS); + } + #endif + + #if ENV_INCLUDE_INA226 + if (INA226.begin()) { + MESH_DEBUG_PRINTLN("Found INA226 at address: %02X", TELEM_INA226_ADDRESS); + INA226.setMaxCurrentShunt(TELEM_INA226_MAX_AMP, TELEM_INA226_SHUNT_VALUE); + INA226_initialized = true; + } else { + INA226_initialized = false; + MESH_DEBUG_PRINTLN("INA226 was not found at I2C address %02X", TELEM_INA226_ADDRESS); + } + #endif + #if ENV_INCLUDE_MLX90614 if (MLX90614.begin(TELEM_MLX90614_ADDRESS, TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found MLX90614 at address: %02X", TELEM_MLX90614_ADDRESS); @@ -219,7 +275,7 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen if (BMP280_initialized) { telemetry.addTemperature(TELEM_CHANNEL_SELF, BMP280.readTemperature()); telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BMP280.readPressure()/100); - telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA)); + telemetry.addAltitude(TELEM_CHANNEL_SELF, BMP280.readAltitude(TELEM_BMP280_SEALEVELPRESSURE_HPA)); } #endif @@ -233,6 +289,18 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen } #endif + #if ENV_INCLUDE_SHT4X + if (SHT4X_initialized) { + float sht4x_humidity, sht4x_temperature; + int16_t sht4x_error; + sht4x_error = SHT4X.measureLowestPrecision(sht4x_temperature, sht4x_humidity); + if (sht4x_error == 0) { + telemetry.addTemperature(TELEM_CHANNEL_SELF, sht4x_temperature); + telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, sht4x_humidity); + } + } + #endif + #if ENV_INCLUDE_LPS22HB if (LPS22HB_initialized) { telemetry.addTemperature(TELEM_CHANNEL_SELF, BARO.readTemperature()); @@ -265,6 +333,24 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen } #endif + #if ENV_INCLUDE_INA260 + if (INA260_initialized) { + telemetry.addVoltage(next_available_channel, INA260.readBusVoltage() / 1000); + telemetry.addCurrent(next_available_channel, INA260.readCurrent() / 1000); + telemetry.addPower(next_available_channel, INA260.readPower() / 1000); + next_available_channel++; + } + #endif + + #if ENV_INCLUDE_INA226 + if (INA226_initialized) { + telemetry.addVoltage(next_available_channel, INA226.getBusVoltage()); + telemetry.addCurrent(next_available_channel, INA226.getCurrent_mA() / 1000.0); + telemetry.addPower(next_available_channel, INA226.getPower_mW() / 1000.0); + next_available_channel++; + } + #endif + #if ENV_INCLUDE_MLX90614 if (MLX90614_initialized) { telemetry.addTemperature(TELEM_CHANNEL_SELF, MLX90614.readObjectTempC()); diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 63c56643..3302d6f6 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -13,10 +13,13 @@ protected: bool BMP280_initialized = false; bool INA3221_initialized = false; bool INA219_initialized = false; + bool INA260_initialized = false; + bool INA226_initialized = false; bool SHTC3_initialized = false; bool LPS22HB_initialized = false; bool MLX90614_initialized = false; bool VL53L0X_initialized = false; + bool SHT4X_initialized = false; bool gps_detected = false; bool gps_active = false; diff --git a/src/helpers/ui/DisplayDriver.h b/src/helpers/ui/DisplayDriver.h index 2d8b69c1..d81d99fb 100644 --- a/src/helpers/ui/DisplayDriver.h +++ b/src/helpers/ui/DisplayDriver.h @@ -21,9 +21,15 @@ public: virtual void setColor(Color c) = 0; virtual void setCursor(int x, int y) = 0; virtual void print(const char* str) = 0; + virtual void printWordWrap(const char* str, int max_width) { print(str); } // fallback to basic print() if no override 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 drawTextCentered(int mid_x, int y, const char* str) { // helper method (override to optimise) + int w = getTextWidth(str); + setCursor(mid_x - w/2, y); + print(str); + } virtual void endFrame() = 0; }; diff --git a/src/helpers/ui/E213Display.cpp b/src/helpers/ui/E213Display.cpp index 92bf37fb..a0e71f31 100644 --- a/src/helpers/ui/E213Display.cpp +++ b/src/helpers/ui/E213Display.cpp @@ -2,20 +2,58 @@ #include "../../MeshCore.h" +BaseDisplay* E213Display::detectEInk() +{ + // Test 1: Logic of BUSY pin + + // Determines controller IC manufacturer + // Fitipower: busy when LOW + // Solomon Systech: busy when HIGH + + // Force display BUSY by holding reset pin active + pinMode(DISP_RST, OUTPUT); + digitalWrite(DISP_RST, LOW); + + delay(10); + + // Read whether pin is HIGH or LOW while busy + pinMode(DISP_BUSY, INPUT); + bool busyLogic = digitalRead(DISP_BUSY); + + // Test complete. Release pin + pinMode(DISP_RST, INPUT); + + if (busyLogic == LOW) { +#ifdef VISION_MASTER_E213 + return new EInkDisplay_VisionMasterE213 ; +#else + return new EInkDisplay_WirelessPaperV1_1 ; +#endif + } else {// busy HIGH +#ifdef VISION_MASTER_E213 + return new EInkDisplay_VisionMasterE213V1_1 ; +#else + return new EInkDisplay_WirelessPaperV1_1_1 ; +#endif + } +} + bool E213Display::begin() { if (_init) return true; powerOn(); - display.begin(); - + if(display==NULL) { + display = detectEInk(); + } + display->begin(); // Set to landscape mode rotated 180 degrees - display.setRotation(3); + display->setRotation(3); _init = true; _isOn = true; clear(); - display.fastmodeOn(); // Enable fast mode for quicker (partial) updates + display->fastmodeOn(); // Enable fast mode for quicker (partial) updates return true; } @@ -23,15 +61,23 @@ bool E213Display::begin() { void E213Display::powerOn() { #ifdef PIN_VEXT_EN pinMode(PIN_VEXT_EN, OUTPUT); +#ifdef PIN_VEXT_EN_ACTIVE + digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); +#else digitalWrite(PIN_VEXT_EN, LOW); // Active low +#endif delay(50); // Allow power to stabilize #endif } void E213Display::powerOff() { #ifdef PIN_VEXT_EN +#ifdef PIN_VEXT_EN_ACTIVE + digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); +#else digitalWrite(PIN_VEXT_EN, HIGH); // Turn off power #endif +#endif } void E213Display::turnOn() { @@ -46,21 +92,23 @@ void E213Display::turnOff() { } void E213Display::clear() { - display.clear(); + display->clear(); + } void E213Display::startFrame(Color bkg) { // Fill screen with white first to ensure clean background - display.fillRect(0, 0, width(), height(), WHITE); + display->fillRect(0, 0, width(), height(), WHITE); + if (bkg == LIGHT) { // Fill with black if light background requested (inverted for e-ink) - display.fillRect(0, 0, width(), height(), BLACK); + display->fillRect(0, 0, width(), height(), BLACK); } } void E213Display::setTextSize(int sz) { // The library handles text size internally - display.setTextSize(sz); + display->setTextSize(sz); } void E213Display::setColor(Color c) { @@ -68,19 +116,19 @@ void E213Display::setColor(Color c) { } void E213Display::setCursor(int x, int y) { - display.setCursor(x, y); + display->setCursor(x, y); } void E213Display::print(const char *str) { - display.print(str); + display->print(str); } void E213Display::fillRect(int x, int y, int w, int h) { - display.fillRect(x, y, w, h, BLACK); + display->fillRect(x, y, w, h, BLACK); } void E213Display::drawRect(int x, int y, int w, int h) { - display.drawRect(x, y, w, h, BLACK); + display->drawRect(x, y, w, h, BLACK); } void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { @@ -98,7 +146,7 @@ void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { // If the bit is set, draw the pixel if (bitSet) { - display.drawPixel(x + bx, y + by, BLACK); + display->drawPixel(x + bx, y + by, BLACK); } } } @@ -107,10 +155,10 @@ void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { uint16_t E213Display::getTextWidth(const char *str) { int16_t x1, y1; uint16_t w, h; - display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); + display->getTextBounds(str, 0, 0, &x1, &y1, &w, &h); return w; } void E213Display::endFrame() { - display.update(); + display->update(); } diff --git a/src/helpers/ui/E213Display.h b/src/helpers/ui/E213Display.h index 330a2b6d..657bfb4c 100644 --- a/src/helpers/ui/E213Display.h +++ b/src/helpers/ui/E213Display.h @@ -8,13 +8,17 @@ // Display driver for E213 e-ink display class E213Display : public DisplayDriver { - EInkDisplay_VisionMasterE213 display; + BaseDisplay* display=NULL; bool _init = false; bool _isOn = false; public: E213Display() : DisplayDriver(250, 122) {} - + ~E213Display(){ + if(display!=NULL) { + delete display; + } + } bool begin(); bool isOn() override { return _isOn; } void turnOn() override; @@ -32,6 +36,7 @@ public: void endFrame() override; private: + BaseDisplay* detectEInk(); void powerOn(); void powerOff(); }; \ No newline at end of file diff --git a/src/helpers/ui/E290Display.cpp b/src/helpers/ui/E290Display.cpp new file mode 100644 index 00000000..23ff2d95 --- /dev/null +++ b/src/helpers/ui/E290Display.cpp @@ -0,0 +1,116 @@ +#include "E290Display.h" + +#include "../../MeshCore.h" + +bool E290Display::begin() { + if (_init) return true; + + powerOn(); + display.begin(); + + // Set to landscape mode rotated 180 degrees + display.setRotation(3); + + _init = true; + _isOn = true; + + clear(); + display.fastmodeOn(); // Enable fast mode for quicker (partial) updates + + return true; +} + +void E290Display::powerOn() { +#ifdef PIN_VEXT_EN + pinMode(PIN_VEXT_EN, OUTPUT); + digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); + delay(50); // Allow power to stabilize +#endif +} + +void E290Display::powerOff() { +#ifdef PIN_VEXT_EN + digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // Turn off power +#endif +} + +void E290Display::turnOn() { + if (!_init) begin(); + powerOn(); + _isOn = true; +} + +void E290Display::turnOff() { + powerOff(); + _isOn = false; +} + +void E290Display::clear() { + display.clear(); +} + +void E290Display::startFrame(Color bkg) { + // Fill screen with white first to ensure clean background + display.fillRect(0, 0, width(), height(), WHITE); + if (bkg == LIGHT) { + // Fill with black if light background requested (inverted for e-ink) + display.fillRect(0, 0, width(), height(), BLACK); + } +} + +void E290Display::setTextSize(int sz) { + // The library handles text size internally + display.setTextSize(sz); +} + +void E290Display::setColor(Color c) { + // implemented in individual display methods +} + +void E290Display::setCursor(int x, int y) { + display.setCursor(x, y); +} + +void E290Display::print(const char *str) { + display.print(str); +} + +void E290Display::fillRect(int x, int y, int w, int h) { + display.fillRect(x, y, w, h, BLACK); +} + +void E290Display::drawRect(int x, int y, int w, int h) { + display.drawRect(x, y, w, h, BLACK); +} + +void E290Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { + // Width in bytes for bitmap processing + uint16_t widthInBytes = (w + 7) / 8; + + // Process the bitmap row by row + for (int by = 0; by < h; by++) { + // Scan across the row bit by bit + for (int bx = 0; bx < w; bx++) { + // Get the current bit using MSB ordering (like GxEPDDisplay) + uint16_t byteOffset = (by * widthInBytes) + (bx / 8); + uint8_t bitMask = 0x80 >> (bx & 7); + bool bitSet = bits[byteOffset] & bitMask; + + // If the bit is set, draw the pixel + if (bitSet) { + display.drawPixel(x + bx, y + by, BLACK); + } + } + } +} + +uint16_t E290Display::getTextWidth(const char *str) { + int16_t x1, y1; + uint16_t w, h; + display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); + return w; +} + +void E290Display::endFrame() { + display.update(); +} diff --git a/src/helpers/ui/E290Display.h b/src/helpers/ui/E290Display.h new file mode 100644 index 00000000..16f45382 --- /dev/null +++ b/src/helpers/ui/E290Display.h @@ -0,0 +1,37 @@ +#pragma once + +#include "DisplayDriver.h" + +#include +#include +#include + +// Display driver for E290 e-ink display +class E290Display : public DisplayDriver { + EInkDisplay_VisionMasterE290 display; + bool _init = false; + bool _isOn = false; + +public: + E290Display() : DisplayDriver(296, 128) {} + + 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; + +private: + void powerOn(); + void powerOff(); +}; \ No newline at end of file diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index 875e29ac..ace25460 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -29,8 +29,8 @@ void GxEPDDisplay::turnOn() { if (!_init) begin(); #if DISP_BACKLIGHT digitalWrite(DISP_BACKLIGHT, HIGH); - _isOn = true; #endif + _isOn = true; } void GxEPDDisplay::turnOff() { @@ -47,6 +47,7 @@ void GxEPDDisplay::clear() { void GxEPDDisplay::startFrame(Color bkg) { display.fillScreen(GxEPD_WHITE); + display.setTextColor(_curr_color = GxEPD_BLACK); } void GxEPDDisplay::setTextSize(int sz) { @@ -67,7 +68,12 @@ void GxEPDDisplay::setTextSize(int sz) { } void GxEPDDisplay::setColor(Color c) { - display.setTextColor(GxEPD_BLACK); + // colours need to be inverted for epaper displays + if (c == DARK) { + display.setTextColor(_curr_color = GxEPD_WHITE); + } else { + display.setTextColor(_curr_color = GxEPD_BLACK); + } } void GxEPDDisplay::setCursor(int x, int y) { @@ -79,11 +85,11 @@ void GxEPDDisplay::print(const char* str) { } void GxEPDDisplay::fillRect(int x, int y, int w, int h) { - display.fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, GxEPD_BLACK); + display.fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _curr_color); } void GxEPDDisplay::drawRect(int x, int y, int w, int h) { - display.drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, GxEPD_BLACK); + display.drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _curr_color); } void GxEPDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { @@ -116,7 +122,7 @@ void GxEPDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { // If the bit is set, draw a block of pixels if (bitSet) { // Draw the block as a filled rectangle - display.fillRect(x1, y1, block_w, block_h, GxEPD_BLACK); + display.fillRect(x1, y1, block_w, block_h, _curr_color); } } } @@ -126,7 +132,7 @@ 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 / SCALE_X; + return ceil((w + 1) / SCALE_X); } void GxEPDDisplay::endFrame() { diff --git a/src/helpers/ui/GxEPDDisplay.h b/src/helpers/ui/GxEPDDisplay.h index ec2bcec0..49746dee 100644 --- a/src/helpers/ui/GxEPDDisplay.h +++ b/src/helpers/ui/GxEPDDisplay.h @@ -28,6 +28,7 @@ class GxEPDDisplay : public DisplayDriver { GxEPD2_BW display; bool _init = false; bool _isOn = false; + uint16_t _curr_color; public: // there is a margin in y... diff --git a/src/helpers/ui/MomentaryButton.cpp b/src/helpers/ui/MomentaryButton.cpp new file mode 100644 index 00000000..9ddf1327 --- /dev/null +++ b/src/helpers/ui/MomentaryButton.cpp @@ -0,0 +1,75 @@ +#include "MomentaryButton.h" + +MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse, bool pulldownup) { + _pin = pin; + _reverse = reverse; + _pull = pulldownup; + down_at = 0; + prev = _reverse ? HIGH : LOW; + cancel = 0; + _long_millis = long_press_millis; +} + +void MomentaryButton::begin() { + if (_pin >= 0) { + pinMode(_pin, _pull ? (_reverse ? INPUT_PULLUP : INPUT_PULLDOWN) : INPUT); + } +} + +bool MomentaryButton::isPressed() const { + return isPressed(digitalRead(_pin)); +} + +void MomentaryButton::cancelClick() { + cancel = 1; +} + +bool MomentaryButton::isPressed(int level) const { + if (_reverse) { + return level == LOW; + } else { + return level != LOW; + } +} + +int MomentaryButton::check(bool repeat_click) { + if (_pin < 0) return BUTTON_EVENT_NONE; + + int event = BUTTON_EVENT_NONE; + int btn = digitalRead(_pin); + if (btn != prev) { + if (isPressed(btn)) { + down_at = millis(); + } else { + // button UP + if (_long_millis > 0) { + if (down_at > 0 && (unsigned long)(millis() - down_at) < _long_millis) { // only a CLICK if still within the long_press millis + event = BUTTON_EVENT_CLICK; + } + } else { + event = BUTTON_EVENT_CLICK; // any UP results in CLICK event when NOT using long_press feature + } + if (event == BUTTON_EVENT_CLICK && cancel) { + event = BUTTON_EVENT_NONE; + } + down_at = 0; + } + prev = btn; + } + if (!isPressed(btn) && cancel) { // always clear the pending 'cancel' once button is back in UP state + cancel = 0; + } + + if (_long_millis > 0 && down_at > 0 && (unsigned long)(millis() - down_at) >= _long_millis) { + event = BUTTON_EVENT_LONG_PRESS; + down_at = 0; + } + if (down_at > 0 && repeat_click) { + unsigned long diff = (unsigned long)(millis() - down_at); + if (diff >= 700) { + event = BUTTON_EVENT_CLICK; // wait 700 millis before repeating the click events + } + } + + return event; +} \ No newline at end of file diff --git a/src/helpers/ui/MomentaryButton.h b/src/helpers/ui/MomentaryButton.h new file mode 100644 index 00000000..0bcc776c --- /dev/null +++ b/src/helpers/ui/MomentaryButton.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#define BUTTON_EVENT_NONE 0 +#define BUTTON_EVENT_CLICK 1 +#define BUTTON_EVENT_LONG_PRESS 2 + +class MomentaryButton { + int8_t _pin; + int8_t prev, cancel; + bool _reverse, _pull; + int _long_millis; + unsigned long down_at; + + bool isPressed(int level) const; + +public: + MomentaryButton(int8_t pin, int long_press_mills=0, bool reverse=false, bool pulldownup=false); + void begin(); + int check(bool repeat_click=false); // returns one of BUTTON_EVENT_* + void cancelClick(); // suppress next BUTTON_EVENT_CLICK (if already in DOWN state) + uint8_t getPin() { return _pin; } + bool isPressed() const; +}; diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp index 8d977db0..c9da0cf8 100644 --- a/src/helpers/ui/SSD1306Display.cpp +++ b/src/helpers/ui/SSD1306Display.cpp @@ -7,6 +7,9 @@ bool SSD1306Display::i2c_probe(TwoWire& wire, uint8_t addr) { } bool SSD1306Display::begin() { + #ifdef DISPLAY_ROTATION + display.setRotation(DISPLAY_ROTATION); + #endif return display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_ADDRESS, true, false) && i2c_probe(Wire, DISPLAY_ADDRESS); } diff --git a/src/helpers/ui/ST7789Display.cpp b/src/helpers/ui/ST7789Display.cpp index 9e71e6bd..7ea35187 100644 --- a/src/helpers/ui/ST7789Display.cpp +++ b/src/helpers/ui/ST7789Display.cpp @@ -18,7 +18,11 @@ bool ST7789Display::begin() { pinMode(PIN_TFT_VDD_CTL, OUTPUT); pinMode(PIN_TFT_LEDA_CTL, OUTPUT); digitalWrite(PIN_TFT_VDD_CTL, LOW); + #ifdef PIN_TFT_LEDA_CTL_ACTIVE + digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE); + #else digitalWrite(PIN_TFT_LEDA_CTL, LOW); + #endif digitalWrite(PIN_TFT_RST, HIGH); display.init(); @@ -43,15 +47,22 @@ void ST7789Display::turnOn() { delay(20); // Now turn on the backlight + #ifdef PIN_TFT_LEDA_CTL_ACTIVE + digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE); + #else digitalWrite(PIN_TFT_LEDA_CTL, LOW); - + #endif _isOn = true; } } void ST7789Display::turnOff() { digitalWrite(PIN_TFT_VDD_CTL, HIGH); +#ifdef PIN_TFT_LEDA_CTL_ACTIVE + digitalWrite(PIN_TFT_LEDA_CTL, !PIN_TFT_LEDA_CTL_ACTIVE); +#else digitalWrite(PIN_TFT_LEDA_CTL, HIGH); +#endif digitalWrite(PIN_TFT_RST, LOW); _isOn = false; } @@ -62,6 +73,9 @@ void ST7789Display::clear() { void ST7789Display::startFrame(Color bkg) { display.clear(); + _color = ST77XX_WHITE; + display.setRGB(_color); + display.setFont(ArialMT_Plain_16); } void ST7789Display::setTextSize(int sz) { @@ -81,7 +95,9 @@ void ST7789Display::setColor(Color c) { switch (c) { case DisplayDriver::DARK : _color = ST77XX_BLACK; + display.setColor(OLEDDISPLAY_COLOR::BLACK); break; +#if 0 case DisplayDriver::LIGHT : _color = ST77XX_WHITE; break; @@ -100,8 +116,10 @@ void ST7789Display::setColor(Color c) { case DisplayDriver::ORANGE : _color = ST77XX_ORANGE; break; +#endif default: _color = ST77XX_WHITE; + display.setColor(OLEDDISPLAY_COLOR::WHITE); break; } display.setRGB(_color); @@ -116,6 +134,10 @@ void ST7789Display::print(const char* str) { display.drawString(_x, _y, str); } +void ST7789Display::printWordWrap(const char* str, int max_width) { + display.drawStringMaxWidth(_x, _y, max_width*SCALE_X, str); +} + void ST7789Display::fillRect(int x, int y, int w, int h) { display.fillRect(x*SCALE_X + X_OFFSET, y*SCALE_Y + Y_OFFSET, w*SCALE_X, h*SCALE_Y); } diff --git a/src/helpers/ui/ST7789Display.h b/src/helpers/ui/ST7789Display.h index b267a2cb..cb56ff8a 100644 --- a/src/helpers/ui/ST7789Display.h +++ b/src/helpers/ui/ST7789Display.h @@ -4,7 +4,7 @@ #include #include #include -#include +#include "ST7789Spi.h" class ST7789Display : public DisplayDriver { ST7789Spi display; @@ -14,8 +14,11 @@ class ST7789Display : public DisplayDriver { bool i2c_probe(TwoWire& wire, uint8_t addr); public: +#ifdef HELTEC_VISION_MASTER_T190 + ST7789Display() : DisplayDriver(128, 64), display(&SPI, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 320, 170,PIN_TFT_SDA,-1,PIN_TFT_SCL) {_isOn = false;} +#else ST7789Display() : DisplayDriver(128, 64), display(&SPI1, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 240, 135) {_isOn = false;} - +#endif bool begin(); bool isOn() override { return _isOn; } @@ -27,6 +30,7 @@ public: void setColor(Color c) override; void setCursor(int x, int y) override; void print(const char* str) override; + void printWordWrap(const char* str, int max_width) 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; diff --git a/src/helpers/ui/UIScreen.h b/src/helpers/ui/UIScreen.h new file mode 100644 index 00000000..6faa591a --- /dev/null +++ b/src/helpers/ui/UIScreen.h @@ -0,0 +1,21 @@ +#pragma once + +#include "DisplayDriver.h" + +#define KEY_LEFT 0xB4 +#define KEY_UP 0xB5 +#define KEY_DOWN 0xB6 +#define KEY_RIGHT 0xB7 +#define KEY_SELECT 10 +#define KEY_ENTER 13 +#define KEY_BACK 27 // Esc + +class UIScreen { +protected: + UIScreen() { } +public: + virtual int render(DisplayDriver& display) =0; // return value is number of millis until next render + virtual bool handleInput(char c) { return false; } + virtual void poll() { } +}; + diff --git a/variants/generic_espnow/platformio.ini b/variants/generic_espnow/platformio.ini index dbc902f0..1c14dfee 100644 --- a/variants/generic_espnow/platformio.ini +++ b/variants/generic_espnow/platformio.ini @@ -26,7 +26,7 @@ build_src_filter = ${esp32_base.build_src_filter} extends = Generic_ESPNOW build_flags = ${Generic_ESPNOW.build_flags} - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=1 build_src_filter = ${Generic_ESPNOW.build_src_filter} +<../examples/simple_secure_chat/main.cpp> @@ -54,13 +54,13 @@ lib_deps = extends = Generic_ESPNOW build_flags = ${Generic_ESPNOW.build_flags} - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 ; NOTE: DO NOT ENABLE --> -D ESPNOW_DEBUG_LOGGING=1 build_src_filter = ${Generic_ESPNOW.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${Generic_ESPNOW.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/heltec_ct62/HT-CT62Board.h b/variants/heltec_ct62/HT-CT62Board.h index e5a627b8..d26e7b26 100644 --- a/variants/heltec_ct62/HT-CT62Board.h +++ b/variants/heltec_ct62/HT-CT62Board.h @@ -8,9 +8,35 @@ #include class Heltec_CT62_Board : public ESP32Board { -public: + uint32_t gpio_state = 0; -uint16_t getBattMilliVolts() override { +public: + void begin() { + ESP32Board::begin(); +#if defined(PIN_BOARD_RELAY_CH1) && defined(PIN_BOARD_RELAY_CH2) + pinMode(PIN_BOARD_RELAY_CH1, OUTPUT); + pinMode(PIN_BOARD_RELAY_CH2, OUTPUT); +#endif +#if defined(PIN_BOARD_DIGITAL_IN) + pinMode(PIN_BOARD_DIGITAL_IN, INPUT); +#endif + } + uint32_t getGpio() override { +#if defined(PIN_BOARD_DIGITAL_IN) + return gpio_state | (digitalRead(PIN_BOARD_DIGITAL_IN) ? 1 : 0); +#else + return 0; +#endif + } + void setGpio(uint32_t values) override { +#if defined(PIN_BOARD_RELAY_CH1) && defined(PIN_BOARD_RELAY_CH2) + gpio_state = values; + digitalWrite(PIN_BOARD_RELAY_CH1, values & 2); + digitalWrite(PIN_BOARD_RELAY_CH2, values & 4); +#endif + } + + uint16_t getBattMilliVolts() override { #ifdef PIN_VBAT_READ analogReadResolution(12); // ESP32-C3 ADC is 12-bit - 3.3/4096 (ref voltage/max counts) uint32_t raw = 0; diff --git a/variants/heltec_ct62/platformio.ini b/variants/heltec_ct62/platformio.ini index ba23a5a6..0dc512b9 100644 --- a/variants/heltec_ct62/platformio.ini +++ b/variants/heltec_ct62/platformio.ini @@ -55,13 +55,13 @@ build_flags = ${Heltec_ct62.build_flags} ; -D ARDUINO_USB_MODE=1 ; -D ARDUINO_USB_CDC_ON_BOOT=1 - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_ct62.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${Heltec_ct62.lib_deps} ${esp32_ota.lib_deps} @@ -73,16 +73,37 @@ build_flags = ${Heltec_ct62.build_flags} ; -D ARDUINO_USB_MODE=1 ; -D ARDUINO_USB_CDC_ON_BOOT=1 - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D OFFLINE_QUEUE_SIZE=256 -D BLE_PIN_CODE=123456 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_ct62.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + lib_deps = ${Heltec_ct62.lib_deps} ${esp32_ota.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:Heltec_ct62_sensor] +extends = Heltec_ct62 +build_flags = + ${Heltec_ct62.build_flags} + -D ADVERT_NAME='"HT-CT62 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D PIN_BOARD_SDA=-1 + -D PIN_BOARD_SCL=-1 + -D PIN_BOARD_RELAY_CH1=0 + -D PIN_BOARD_RELAY_CH2=1 + -D PIN_BOARD_DIGITAL_IN=19 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_ct62.build_src_filter} + +<../examples/simple_sensor> +lib_deps = + ${Heltec_ct62.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.cpp b/variants/heltec_mesh_solar/MeshSolarBoard.cpp new file mode 100644 index 00000000..54929cd1 --- /dev/null +++ b/variants/heltec_mesh_solar/MeshSolarBoard.cpp @@ -0,0 +1,77 @@ +#include +#include "MeshSolarBoard.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 MeshSolarBoard::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + + meshSolarStart(); + +#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) + Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL); +#endif + + Wire.begin(); +} + +bool MeshSolarBoard::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("MESH_SOLAR_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; +} diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.h b/variants/heltec_mesh_solar/MeshSolarBoard.h new file mode 100644 index 00000000..3bec144f --- /dev/null +++ b/variants/heltec_mesh_solar/MeshSolarBoard.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#ifdef HELTEC_MESH_SOLAR +#include "meshSolarApp.h" +#endif + +// LoRa radio module pins for Heltec T114 +#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_DIO2_AS_RF_SWITCH true +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + + +class MeshSolarBoard : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin(); + uint8_t getStartupReason() const override { return startup_reason; } + + uint16_t getBattMilliVolts() override { + return meshSolarGetBattVoltage(); + } + + const char* getManufacturerName() const override { + return "Heltec Mesh Solar"; + } + + void reboot() override { + NVIC_SystemReset(); + } + + bool startOTAUpdate(const char* id, char reply[]) override; +}; diff --git a/variants/heltec_mesh_solar/platformio.ini b/variants/heltec_mesh_solar/platformio.ini new file mode 100644 index 00000000..9fd3edd5 --- /dev/null +++ b/variants/heltec_mesh_solar/platformio.ini @@ -0,0 +1,91 @@ +[Heltec_mesh_solar] +extends = nrf52_base +board = heltec_mesh_solar +platform_packages = framework-arduinoadafruitnrf52 +board_build.ldscript = boards/nrf52840_s140_v6.ld +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 + -I variants/heltec_mesh_solar + -D HELTEC_MESH_SOLAR + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 +build_src_filter = ${nrf52_base.build_src_filter} + + + +<../variants/heltec_mesh_solar> +lib_deps = + ${nrf52_base.lib_deps} + rweather/Crypto @ ^0.4.0 + stevemarple/MicroNMEA @ ^2.0.6 + adafruit/Adafruit NeoPixel@^1.10.0 + https://github.com/NMIoT/meshsolar/archive/dfc5330dad443982e6cdd37a61d33fc7252f468b.zip +debug_tool = jlink +upload_protocol = nrfutil + +[env:Heltec_mesh_solar_repeater] +extends = Heltec_mesh_solar +build_src_filter = ${Heltec_mesh_solar.build_src_filter} + +<../examples/simple_repeater> + +build_flags = + ${Heltec_mesh_solar.build_flags} + -D ADVERT_NAME='"Heltec_Mesh_Solar 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 + +[env:Heltec_mesh_solar_room_server] +extends = Heltec_mesh_solar +build_src_filter = ${Heltec_mesh_solar.build_src_filter} + +<../examples/simple_room_server> +build_flags = + ${Heltec_mesh_solar.build_flags} + -D ADVERT_NAME='"Heltec_Mesh_Solar 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 + +[env:Heltec_mesh_solar_companion_radio_ble] +extends = Heltec_mesh_solar +build_flags = + ${Heltec_mesh_solar.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 +; -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_mesh_solar.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +lib_deps = + ${Heltec_mesh_solar.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Heltec_mesh_solar_companion_radio_usb] +extends = Heltec_mesh_solar +build_flags = + ${Heltec_mesh_solar.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 +build_src_filter = ${Heltec_mesh_solar.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +lib_deps = + ${Heltec_mesh_solar.lib_deps} + densaugeo/base64 @ ~1.4.0 \ No newline at end of file diff --git a/variants/heltec_mesh_solar/target.cpp b/variants/heltec_mesh_solar/target.cpp new file mode 100644 index 00000000..ad79f717 --- /dev/null +++ b/variants/heltec_mesh_solar/target.cpp @@ -0,0 +1,123 @@ +#include +#include "target.h" +#include +#include + +MeshSolarBoard 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); +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +SolarSensorManager sensors = SolarSensorManager(nmea); + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + return radio.std_init(&SPI); +} + +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 +} + +void SolarSensorManager::start_gps() { + if (!gps_active) { + gps_active = true; + _location->begin(); + } +} + +void SolarSensorManager::stop_gps() { + if (gps_active) { + gps_active = false; + _location->stop(); + } +} + +bool SolarSensorManager::begin() { + Serial1.begin(9600); + + // 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"); + } else { + MESH_DEBUG_PRINTLN("No GPS detected"); + } + + return true; +} + +bool SolarSensorManager::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 SolarSensorManager::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.; + node_altitude = ((double)_location->getAltitude()) / 1000.0; + MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); + } + next_gps_update = millis() + 1000; + } +} + +int SolarSensorManager::getNumSettings() const { + return gps_detected ? 1 : 0; // only show GPS setting if GPS is detected +} + +const char* SolarSensorManager::getSettingName(int i) const { + return (gps_detected && i == 0) ? "gps" : NULL; +} + +const char* SolarSensorManager::getSettingValue(int i) const { + if (gps_detected && i == 0) { + return gps_active ? "1" : "0"; + } + return NULL; +} + +bool SolarSensorManager::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 +} diff --git a/variants/heltec_mesh_solar/target.h b/variants/heltec_mesh_solar/target.h new file mode 100644 index 00000000..e301a273 --- /dev/null +++ b/variants/heltec_mesh_solar/target.h @@ -0,0 +1,46 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include +#endif + +class SolarSensorManager : public SensorManager { + bool gps_active = false; + bool gps_detected = false; + LocationProvider* _location; + + void start_gps(); + void stop_gps(); +public: + SolarSensorManager(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 MeshSolarBoard board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SolarSensorManager 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); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_mesh_solar/variant.cpp b/variants/heltec_mesh_solar/variant.cpp new file mode 100644 index 00000000..03dd54b7 --- /dev/null +++ b/variants/heltec_mesh_solar/variant.cpp @@ -0,0 +1,16 @@ +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +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_USER_BTN, INPUT); + pinMode(BQ4050_EMERGENCY_SHUTDOWN_PIN, INPUT); +} diff --git a/variants/heltec_mesh_solar/variant.h b/variants/heltec_mesh_solar/variant.h new file mode 100644 index 00000000..14956619 --- /dev/null +++ b/variants/heltec_mesh_solar/variant.h @@ -0,0 +1,127 @@ +/* + * 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) + + +//////////////////////////////////////////////////////////////////////////////// +// Power + +#define NRF_APM + +//////////////////////////////////////////////////////////////////////////////// +// 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 (37) +#define PIN_SERIAL1_TX (39) + +#define PIN_SERIAL2_RX (9) +#define PIN_SERIAL2_TX (10) + +//////////////////////////////////////////////////////////////////////////////// +// I2C pin definition +#define WIRE_INTERFACES_COUNT (2) + +#define PIN_WIRE_SDA (6) +#define PIN_WIRE_SCL (26) + +#define PIN_WIRE1_SDA (30) +#define PIN_WIRE1_SCL (5) + +//////////////////////////////////////////////////////////////////////////////// +// 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_BUILTIN (12) +#define PIN_LED LED_BUILTIN +#define LED_RED 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 LOW + +#define PIN_NEOPIXEL (47) +#define NEOPIXEL_NUM (1) + +//////////////////////////////////////////////////////////////////////////////// +// Builtin buttons + +#define PIN_BUTTON1 (42) +#define BUTTON_PIN PIN_BUTTON1 + +// #define PIN_BUTTON2 (11) +// #define BUTTON_PIN2 PIN_BUTTON2 + +#define PIN_USER_BTN BUTTON_PIN + +#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 + +#define PIN_SPI1_MISO (43) +#define PIN_SPI1_MOSI (41) +#define PIN_SPI1_SCK (40) + +//////////////////////////////////////////////////////////////////////////////// +// Buzzer + +// #define PIN_BUZZER (46) + + +//////////////////////////////////////////////////////////////////////////////// +// GPS + +#define GPS_RESET (38) + +//////////////////////////////////////////////////////////////////////////////// +// TFT +// #define PIN_TFT_SCL (40) +// #define PIN_TFT_SDA (41) +// #define PIN_TFT_RST (2) +// #define PIN_TFT_VDD_CTL (3) +// #define PIN_TFT_LEDA_CTL (15) +// #define PIN_TFT_CS (11) +// #define PIN_TFT_DC (12) + +//////////////////////////////////////////////////////////////////////////////// +#define BQ4050_SDA_PIN (33) // I2C data line pin +#define BQ4050_SCL_PIN (32) // I2C clock line pin +#define BQ4050_EMERGENCY_SHUTDOWN_PIN (35) // Emergency shutdown pin \ No newline at end of file diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 8d9013a5..f1477e9f 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -39,10 +39,11 @@ extends = Heltec_tracker_base build_flags = ${Heltec_tracker_base.build_flags} -I src/helpers/ui + -I examples/companion_radio/ui-new -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial -D DISPLAY_ROTATION=1 -D DISPLAY_CLASS=ST7735Display - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 ; HWT will use display for pin -D OFFLINE_QUEUE_SIZE=256 @@ -51,7 +52,9 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_tracker_base.build_src_filter} + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> + lib_deps = ${Heltec_tracker_base.lib_deps} diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp index f41702c5..5ba9a8fb 100644 --- a/variants/heltec_tracker/target.cpp +++ b/variants/heltec_tracker/target.cpp @@ -21,6 +21,7 @@ HWTSensorManager sensors = HWTSensorManager(nmea); #ifdef DISPLAY_CLASS DISPLAY_CLASS display(&board.periph_power); // peripheral power pin is shared + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/heltec_tracker/target.h b/variants/heltec_tracker/target.h index c08be80a..8ac5eb72 100644 --- a/variants/heltec_tracker/target.h +++ b/variants/heltec_tracker/target.h @@ -10,6 +10,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif class HWTSensorManager : public SensorManager { @@ -36,6 +37,7 @@ extern HWTSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index 43bc43ad..ea41f845 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/heltec_v2/platformio.ini @@ -35,6 +35,7 @@ build_flags = build_src_filter = ${Heltec_lora32_v2.build_src_filter} +<../examples/simple_repeater> + + + lib_deps = ${Heltec_lora32_v2.lib_deps} ${esp32_ota.lib_deps} @@ -53,6 +54,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v2.build_src_filter} + + + +<../examples/simple_room_server> lib_deps = ${Heltec_lora32_v2.lib_deps} @@ -76,6 +78,7 @@ lib_deps = extends = Heltec_lora32_v2 build_flags = ${Heltec_lora32_v2.build_flags} + -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 @@ -84,7 +87,9 @@ build_flags = build_src_filter = ${Heltec_lora32_v2.build_src_filter} + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Heltec_lora32_v2.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -93,6 +98,7 @@ lib_deps = extends = Heltec_lora32_v2 build_flags = ${Heltec_lora32_v2.build_flags} + -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 @@ -104,7 +110,9 @@ build_flags = build_src_filter = ${Heltec_lora32_v2.build_src_filter} + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Heltec_lora32_v2.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/heltec_v2/target.cpp b/variants/heltec_v2/target.cpp index 418f1f7f..df71d3f4 100644 --- a/variants/heltec_v2/target.cpp +++ b/variants/heltec_v2/target.cpp @@ -18,6 +18,7 @@ SensorManager sensors; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/heltec_v2/target.h b/variants/heltec_v2/target.h index 0c330316..2e5b17de 100644 --- a/variants/heltec_v2/target.h +++ b/variants/heltec_v2/target.h @@ -9,6 +9,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif extern HeltecV2Board board; @@ -18,6 +19,7 @@ extern SensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 67415ae9..ba34cead 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -3,8 +3,10 @@ extends = esp32_base board = esp32-s3-devkitc-1 build_flags = ${esp32_base.build_flags} + ${sensor_base.build_flags} -I variants/heltec_v3 -D HELTEC_LORA_V3 + -D ESP32_CPU_FREQ=80 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 @@ -15,14 +17,8 @@ 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=140 + -D SX126X_CURRENT_LIMIT=160 -D SX126X_RX_BOOSTED_GAIN=1 - -D ENV_INCLUDE_AHTX0=1 - -D ENV_INCLUDE_BME280=1 - -D ENV_INCLUDE_BMP280=1 - -D ENV_INCLUDE_INA3221=1 - -D ENV_INCLUDE_INA219=1 - -D ENV_INCLUDE_GPS=1 -D PIN_GPS_RX=47 -D PIN_GPS_TX=48 -D PIN_GPS_EN=26 @@ -31,13 +27,7 @@ build_src_filter = ${esp32_base.build_src_filter} + lib_deps = ${esp32_base.lib_deps} - adafruit/Adafruit SSD1306 @ ^2.5.13 - 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 - adafruit/Adafruit BMP280 Library@^2.6.8 - stevemarple/MicroNMEA @ ^2.0.6 + ${sensor_base.lib_deps} [env:Heltec_v3_repeater] extends = Heltec_lora32_v3 @@ -57,6 +47,7 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter} lib_deps = ${Heltec_lora32_v3.lib_deps} ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 [env:Heltec_v3_room_server] extends = Heltec_lora32_v3 @@ -81,7 +72,7 @@ lib_deps = extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -95,6 +86,7 @@ lib_deps = extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D DISPLAY_CLASS=SSD1306Display @@ -102,7 +94,9 @@ build_flags = ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Heltec_lora32_v3.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -111,18 +105,22 @@ lib_deps = extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} - -D MAX_CONTACTS=100 + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=160 -D MAX_GROUP_CHANNELS=8 -D DISPLAY_CLASS=SSD1306Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D AUTO_SHUTDOWN_MILLIVOLTS=3400 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + + + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Heltec_lora32_v3.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -131,6 +129,7 @@ lib_deps = extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D DISPLAY_CLASS=SSD1306Display @@ -141,8 +140,10 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + + + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Heltec_lora32_v3.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -157,8 +158,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D ENV_PIN_SDA=33 -D ENV_PIN_SCL=34 - -D ENV_INCLUDE_MLX90614=1 - -D ENV_INCLUDE_VL53L0X=1 -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -168,8 +167,6 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter} lib_deps = ${Heltec_lora32_v3.lib_deps} ${esp32_ota.lib_deps} - adafruit/Adafruit MLX90614 Library @ ^2.1.5 - adafruit/Adafruit_VL53L0X @ ^1.2.4 [env:Heltec_WSL3_repeater] extends = Heltec_lora32_v3 @@ -187,6 +184,7 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter} lib_deps = ${Heltec_lora32_v3.lib_deps} ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 [env:Heltec_WSL3_room_server] extends = Heltec_lora32_v3 @@ -218,7 +216,21 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> +lib_deps = + ${Heltec_lora32_v3.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Heltec_WSL3_companion_radio_usb] +extends = Heltec_lora32_v3 +build_flags = + ${Heltec_lora32_v3.build_flags} + -D MAX_CONTACTS=140 + -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 = ${Heltec_lora32_v3.build_src_filter} + +<../examples/companion_radio/*.cpp> lib_deps = ${Heltec_lora32_v3.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/heltec_v3/target.cpp b/variants/heltec_v3/target.cpp index 4cbc78fb..78b88197 100644 --- a/variants/heltec_v3/target.cpp +++ b/variants/heltec_v3/target.cpp @@ -25,6 +25,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/heltec_v3/target.h b/variants/heltec_v3/target.h index 992a3d2c..b2125664 100644 --- a/variants/heltec_v3/target.h +++ b/variants/heltec_v3/target.h @@ -10,6 +10,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif extern HeltecV3Board board; @@ -19,6 +20,7 @@ extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/heltec_vision_master_e213/HeltecE213Board.cpp b/variants/heltec_vision_master_e213/HeltecE213Board.cpp new file mode 100644 index 00000000..d32d274e --- /dev/null +++ b/variants/heltec_vision_master_e213/HeltecE213Board.cpp @@ -0,0 +1,69 @@ +#include "HeltecE213Board.h" + +void HeltecE213Board::begin() { + ESP32Board::begin(); + + pinMode(PIN_ADC_CTRL, OUTPUT); + digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + + periph_power.begin(); + + 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 HeltecE213Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { + 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! + } + + void HeltecE213Board::powerOff() { + // TODO: re-enable this when there is a definite wake-up source pin: + // enterDeepSleep(0); + } + + uint16_t HeltecE213Board::getBattMilliVolts() { + analogReadResolution(10); + digitalWrite(PIN_ADC_CTRL, HIGH); + + uint32_t raw = 0; + for (int i = 0; i < 8; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / 8; + + digitalWrite(PIN_ADC_CTRL, LOW); + + return (5.42 * (3.3 / 1024.0) * raw) * 1000; + } + + const char* HeltecE213Board::getManufacturerName() const { + return "Heltec E213"; + } + diff --git a/variants/heltec_vision_master_e213/HeltecE213Board.h b/variants/heltec_vision_master_e213/HeltecE213Board.h new file mode 100644 index 00000000..dd622064 --- /dev/null +++ b/variants/heltec_vision_master_e213/HeltecE213Board.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +// LoRa radio module pins for heltec_vision_master_e213 +#define P_LORA_DIO_1 14 +#define P_LORA_NSS 8 +#define P_LORA_RESET 12 +#define P_LORA_BUSY 13 +#define P_LORA_SCLK 9 +#define P_LORA_MISO 11 +#define P_LORA_MOSI 10 + +class HeltecE213Board : public ESP32Board { + +public: + RefCountedDigitalPin periph_power; + + HeltecE213Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { } + + void begin(); + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); + void powerOff() override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override ; + +}; diff --git a/variants/heltec_vision_master_e213/pins_arduino.h b/variants/heltec_vision_master_e213/pins_arduino.h new file mode 100644 index 00000000..56f5ef15 --- /dev/null +++ b/variants/heltec_vision_master_e213/pins_arduino.h @@ -0,0 +1,61 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +static const uint8_t LED_BUILTIN = 45; // LED is not populated on earliest board variant +#define BUILTIN_LED LED_BUILTIN // Backward compatibility +#define LED_BUILTIN LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 39; +static const uint8_t SCL = 38; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO1 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_vision_master_e213/platformio.ini b/variants/heltec_vision_master_e213/platformio.ini new file mode 100644 index 00000000..c6b68724 --- /dev/null +++ b/variants/heltec_vision_master_e213/platformio.ini @@ -0,0 +1,86 @@ +[Heltec_Vision_Master_E213_base] +extends = esp32_base +board = heltec_vision_master_e213 +build_flags = + ${esp32_base.build_flags} + -I variants/heltec_vision_master_e213 + -D HELTEC_VISION_MASTER_E213 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D P_LORA_TX_LED=45 + -D PIN_USER_BTN=0 + -D PIN_VEXT_EN=18 + -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_VBAT_READ=7 + -D PIN_ADC_CTRL=46 + -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 PIN_BOARD_SDA=39 + -D PIN_BOARD_SCL=38 + -D DISP_CS=5 + -D DISP_BUSY=1 + -D DISP_DC=2 + -D DISP_RST=3 + -D DISP_SCLK=4 + -D DISP_MOSI=6 + -D Vision_Master_E213 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/heltec_vision_master_e213> +lib_deps = + ${esp32_base.lib_deps} + https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip + +[env:Heltec_Vision_Master_E213_radio_ble] +extends = Heltec_Vision_Master_E213_base +build_flags = + ${Heltec_Vision_Master_E213_base.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D DISPLAY_CLASS=E213Display + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${Heltec_Vision_Master_E213_base.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_Vision_Master_E213_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Heltec_Vision_Master_E213_repeater] +extends = Heltec_Vision_Master_E213_base +build_flags = + ${Heltec_Vision_Master_E213_base.build_flags} + -D DISPLAY_CLASS=E213Display + -D ADVERT_NAME='"Heltec E213 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 +build_src_filter = ${Heltec_Vision_Master_E213_base.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_Vision_Master_E213_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:Heltec_Vision_Master_E213_room_server] +extends = Heltec_Vision_Master_E213_base +build_flags = + ${Heltec_Vision_Master_E213_base.build_flags} + -D DISPLAY_CLASS=E213Display + -D ADVERT_NAME='"Heltec E213 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +build_src_filter = ${Heltec_Vision_Master_E213_base.build_src_filter} + + + +<../examples/simple_room_server> +lib_deps = + ${Heltec_Vision_Master_E213_base.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/heltec_vision_master_e213/target.cpp b/variants/heltec_vision_master_e213/target.cpp new file mode 100644 index 00000000..23561850 --- /dev/null +++ b/variants/heltec_vision_master_e213/target.cpp @@ -0,0 +1,54 @@ +#include "target.h" +#include + +HeltecE213Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi(FSPI); + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +SensorManager sensors; + +#ifdef DISPLAY_CLASS +DISPLAY_CLASS display; +MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +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_vision_master_e213/target.h b/variants/heltec_vision_master_e213/target.h new file mode 100644 index 00000000..9ecdc212 --- /dev/null +++ b/variants/heltec_vision_master_e213/target.h @@ -0,0 +1,29 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS +#include +#include +#endif + +extern HeltecE213Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef DISPLAY_CLASS +extern DISPLAY_CLASS display; +extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/heltec_vision_master_e290/HeltecE290Board.cpp b/variants/heltec_vision_master_e290/HeltecE290Board.cpp new file mode 100644 index 00000000..7d8c654d --- /dev/null +++ b/variants/heltec_vision_master_e290/HeltecE290Board.cpp @@ -0,0 +1,69 @@ +#include "HeltecE290Board.h" + +void HeltecE290Board::begin() { + ESP32Board::begin(); + + pinMode(PIN_ADC_CTRL, OUTPUT); + digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + + periph_power.begin(); + + 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 HeltecE290Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { + 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! + } + + void HeltecE290Board::powerOff() { + // TODO: re-enable this when there is a definite wake-up source pin: + // enterDeepSleep(0); + } + + uint16_t HeltecE290Board::getBattMilliVolts() { + analogReadResolution(10); + digitalWrite(PIN_ADC_CTRL, HIGH); + + uint32_t raw = 0; + for (int i = 0; i < 8; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / 8; + + digitalWrite(PIN_ADC_CTRL, LOW); + + return (5.42 * (3.3 / 1024.0) * raw) * 1000; + } + + const char* HeltecE290Board::getManufacturerName() const { + return "Heltec E290"; + } + diff --git a/variants/heltec_vision_master_e290/HeltecE290Board.h b/variants/heltec_vision_master_e290/HeltecE290Board.h new file mode 100644 index 00000000..95f8c03e --- /dev/null +++ b/variants/heltec_vision_master_e290/HeltecE290Board.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +// LoRa radio module pins for heltec_vision_master_e290 +#define P_LORA_DIO_1 14 +#define P_LORA_NSS 8 +#define P_LORA_RESET 12 +#define P_LORA_BUSY 13 +#define P_LORA_SCLK 9 +#define P_LORA_MISO 11 +#define P_LORA_MOSI 10 + +class HeltecE290Board : public ESP32Board { + +public: + RefCountedDigitalPin periph_power; + + HeltecE290Board() : periph_power(PIN_VEXT_EN) { } + + void begin(); + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); + void powerOff() override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override ; + +}; diff --git a/variants/heltec_vision_master_e290/pins_arduino.h b/variants/heltec_vision_master_e290/pins_arduino.h new file mode 100644 index 00000000..56f5ef15 --- /dev/null +++ b/variants/heltec_vision_master_e290/pins_arduino.h @@ -0,0 +1,61 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +static const uint8_t LED_BUILTIN = 45; // LED is not populated on earliest board variant +#define BUILTIN_LED LED_BUILTIN // Backward compatibility +#define LED_BUILTIN LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 39; +static const uint8_t SCL = 38; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO1 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_vision_master_e290/platformio.ini b/variants/heltec_vision_master_e290/platformio.ini new file mode 100644 index 00000000..4150adc8 --- /dev/null +++ b/variants/heltec_vision_master_e290/platformio.ini @@ -0,0 +1,80 @@ +[Heltec_Vision_Master_E290_base] +extends = esp32_base +board = heltec_vision_master_e290 +build_flags = + ${esp32_base.build_flags} + -I variants/heltec_vision_master_e290 + -D HELTEC_VISION_MASTER_E290 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D P_LORA_TX_LED=45 + -D PIN_USER_BTN=0 + -D PIN_VEXT_EN=18 + -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_VBAT_READ=7 + -D PIN_ADC_CTRL=46 + -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 PIN_BOARD_SDA=39 + -D PIN_BOARD_SCL=38 + -D Vision_Master_E290 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/heltec_vision_master_e290> +lib_deps = + ${esp32_base.lib_deps} + https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip + +[env:Heltec_Vision_Master_E290_radio_ble] +extends = Heltec_Vision_Master_E290_base +build_flags = + ${Heltec_Vision_Master_E290_base.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D DISPLAY_CLASS=E290Display + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${Heltec_Vision_Master_E290_base.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_Vision_Master_E290_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Heltec_Vision_Master_E290_repeater] +extends = Heltec_Vision_Master_E290_base +build_flags = + ${Heltec_Vision_Master_E290_base.build_flags} + -D DISPLAY_CLASS=E290Display + -D ADVERT_NAME='"Heltec E290 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 +build_src_filter = ${Heltec_Vision_Master_E290_base.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_Vision_Master_E290_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:Heltec_Vision_Master_E290_room_server] +extends = Heltec_Vision_Master_E290_base +build_flags = + ${Heltec_Vision_Master_E290_base.build_flags} + -D DISPLAY_CLASS=E290Display + -D ADVERT_NAME='"Heltec E290 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +build_src_filter = ${Heltec_Vision_Master_E290_base.build_src_filter} + + + +<../examples/simple_room_server> +lib_deps = + ${Heltec_Vision_Master_E290_base.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/heltec_vision_master_e290/target.cpp b/variants/heltec_vision_master_e290/target.cpp new file mode 100644 index 00000000..92b02092 --- /dev/null +++ b/variants/heltec_vision_master_e290/target.cpp @@ -0,0 +1,54 @@ +#include "target.h" +#include + +HeltecE290Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi(FSPI); + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +SensorManager sensors; + +#ifdef DISPLAY_CLASS +DISPLAY_CLASS display; +MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +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_vision_master_e290/target.h b/variants/heltec_vision_master_e290/target.h new file mode 100644 index 00000000..60770112 --- /dev/null +++ b/variants/heltec_vision_master_e290/target.h @@ -0,0 +1,29 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS +#include +#include +#endif + +extern HeltecE290Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef DISPLAY_CLASS +extern DISPLAY_CLASS display; +extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/heltec_vision_master_t190/HeltecT190Board.cpp b/variants/heltec_vision_master_t190/HeltecT190Board.cpp new file mode 100644 index 00000000..c5b03bc8 --- /dev/null +++ b/variants/heltec_vision_master_t190/HeltecT190Board.cpp @@ -0,0 +1,69 @@ +#include "HeltecT190Board.h" + +void HeltecT190Board::begin() { + ESP32Board::begin(); + + pinMode(PIN_ADC_CTRL, OUTPUT); + digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + + periph_power.begin(); + + 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 HeltecT190Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { + 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! + } + + void HeltecT190Board::powerOff() { + // TODO: re-enable this when there is a definite wake-up source pin: + // enterDeepSleep(0); + } + + uint16_t HeltecT190Board::getBattMilliVolts() { + analogReadResolution(10); + digitalWrite(PIN_ADC_CTRL, HIGH); + delay(10); + uint32_t raw = 0; + for (int i = 0; i < 8; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / 8; + + digitalWrite(PIN_ADC_CTRL, LOW); + + return (5.42 * (3.3 / 1024.0) * raw) * 1000; + } + + const char* HeltecT190Board::getManufacturerName() const { + return "Heltec T190"; + } + diff --git a/variants/heltec_vision_master_t190/HeltecT190Board.h b/variants/heltec_vision_master_t190/HeltecT190Board.h new file mode 100644 index 00000000..96c09c1c --- /dev/null +++ b/variants/heltec_vision_master_t190/HeltecT190Board.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +// LoRa radio module pins for heltec_vision_master_e290 +#define P_LORA_DIO_1 14 +#define P_LORA_NSS 8 +#define P_LORA_RESET 12 +#define P_LORA_BUSY 13 +#define P_LORA_SCLK 9 +#define P_LORA_MISO 11 +#define P_LORA_MOSI 10 + +class HeltecT190Board : public ESP32Board { + +public: + RefCountedDigitalPin periph_power; + + HeltecT190Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { } + + void begin(); + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); + void powerOff() override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override ; + +}; diff --git a/variants/heltec_vision_master_t190/pins_arduino.h b/variants/heltec_vision_master_t190/pins_arduino.h new file mode 100644 index 00000000..56856106 --- /dev/null +++ b/variants/heltec_vision_master_t190/pins_arduino.h @@ -0,0 +1,61 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +static const uint8_t LED_BUILTIN = 45; // LED is not populated on earliest board variant +#define BUILTIN_LED LED_BUILTIN // Backward compatibility +#define LED_BUILTIN LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 2; +static const uint8_t SCL = 1; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO1 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_vision_master_t190/platformio.ini b/variants/heltec_vision_master_t190/platformio.ini new file mode 100644 index 00000000..e8492335 --- /dev/null +++ b/variants/heltec_vision_master_t190/platformio.ini @@ -0,0 +1,87 @@ +[Heltec_Vision_Master_T190_base] +extends = esp32_base +board = heltec_vision_master_t190 +build_flags = + ${esp32_base.build_flags} + -I variants/heltec_vision_master_t190 + -I src/helpers/ui + -D HELTEC_VISION_MASTER_T190 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D PIN_USER_BTN=0 + -D PIN_VEXT_EN=5 + -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_VBAT_READ=6 + -D PIN_ADC_CTRL=46 + -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 PIN_BOARD_SDA=2 + -D PIN_BOARD_SCL=1 + -D PIN_TFT_SCL=38 + -D PIN_TFT_SDA=48 + -D PIN_TFT_RST=40 + -D PIN_TFT_VDD_CTL=7 + -D PIN_TFT_LEDA_CTL=17 + -D PIN_TFT_LEDA_CTL_ACTIVE=HIGH + -D PIN_TFT_CS=39 + -D PIN_TFT_DC=47 + -D ST7789 + -D DISPLAY_CLASS=ST7789Display +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/heltec_vision_master_t190> + + + + + + + + +lib_deps = + ${esp32_base.lib_deps} + adafruit/Adafruit GFX Library @ ^1.12.1 + +[env:Heltec_Vision_Master_T190_radio_ble] +extends = Heltec_Vision_Master_T190_base +build_flags = + ${Heltec_Vision_Master_T190_base.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${Heltec_Vision_Master_T190_base.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_Vision_Master_T190_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Heltec_Vision_Master_T190_repeater] +extends = Heltec_Vision_Master_T190_base +build_flags = + ${Heltec_Vision_Master_T190_base.build_flags} + -D ADVERT_NAME='"Heltec T190 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 +build_src_filter = ${Heltec_Vision_Master_T190_base.build_src_filter} + +<../examples/simple_repeater> +lib_deps = + ${Heltec_Vision_Master_T190_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:Heltec_Vision_Master_T190_room_server] +extends = Heltec_Vision_Master_T190_base +build_flags = + ${Heltec_Vision_Master_T190_base.build_flags} + -D ADVERT_NAME='"Heltec T190 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +build_src_filter = ${Heltec_Vision_Master_T190_base.build_src_filter} + +<../examples/simple_room_server> +lib_deps = + ${Heltec_Vision_Master_T190_base.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/heltec_vision_master_t190/target.cpp b/variants/heltec_vision_master_t190/target.cpp new file mode 100644 index 00000000..b9357594 --- /dev/null +++ b/variants/heltec_vision_master_t190/target.cpp @@ -0,0 +1,54 @@ +#include "target.h" +#include + +HeltecT190Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi(FSPI); + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +SensorManager sensors; + +#ifdef DISPLAY_CLASS +DISPLAY_CLASS display; +MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +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_vision_master_t190/target.h b/variants/heltec_vision_master_t190/target.h new file mode 100644 index 00000000..8a5fc716 --- /dev/null +++ b/variants/heltec_vision_master_t190/target.h @@ -0,0 +1,29 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS +#include +#include +#endif + +extern HeltecT190Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef DISPLAY_CLASS +extern DISPLAY_CLASS display; +extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 513ba4b9..8de826e4 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -28,16 +28,17 @@ build_flags = -D ARDUINO_heltec_wifi_lora_32_V3 -D WIRELESS_PAPER build_src_filter = ${esp32_base.build_src_filter} - +<../variants/heltec_wireless_paper> + +<../variants/heltec_wireless_paper> lib_deps = ${esp32_base.lib_deps} - todd-herbert/heltec-eink-modules @ 4.5.0 + https://github.com/todd-herbert/heltec-eink-modules/archive/9207eb6ab2b96f66298e0488740218c17b006af7.zip [env:Heltec_Wireless_Paper_companion_radio_ble] extends = Heltec_Wireless_Paper_base build_flags = ${Heltec_Wireless_Paper_base.build_flags} - -D MAX_CONTACTS=100 + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D DISPLAY_CLASS=E213Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN @@ -46,7 +47,8 @@ build_flags = build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter} + + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Heltec_Wireless_Paper_base.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/heltec_wireless_paper/target.cpp b/variants/heltec_wireless_paper/target.cpp index d434b241..dd2d51c0 100644 --- a/variants/heltec_wireless_paper/target.cpp +++ b/variants/heltec_wireless_paper/target.cpp @@ -19,6 +19,7 @@ SensorManager sensors; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; +MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/heltec_wireless_paper/target.h b/variants/heltec_wireless_paper/target.h index cb95905c..65b972d0 100644 --- a/variants/heltec_wireless_paper/target.h +++ b/variants/heltec_wireless_paper/target.h @@ -9,6 +9,7 @@ #include #ifdef DISPLAY_CLASS #include +#include #endif extern HeltecV3Board board; @@ -18,6 +19,7 @@ extern SensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; +extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.cpp b/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.cpp new file mode 100644 index 00000000..8634cda1 --- /dev/null +++ b/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.cpp @@ -0,0 +1,99 @@ +#ifdef XIAO_NRF52 + +#include +#include "ikoka_stick_nrf_board.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 ikoka_stick_nrf_board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + + pinMode(PIN_VBAT, INPUT); + pinMode(VBAT_ENABLE, OUTPUT); + digitalWrite(VBAT_ENABLE, HIGH); + +#ifdef PIN_USER_BTN + pinMode(PIN_USER_BTN, INPUT_PULLUP); +#endif + +#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) + Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL); +#endif + + Wire.begin(); + +#ifdef P_LORA_TX_LED + pinMode(P_LORA_TX_LED, OUTPUT); + digitalWrite(P_LORA_TX_LED, HIGH); +#endif + +// pinMode(SX126X_POWER_EN, OUTPUT); +// digitalWrite(SX126X_POWER_EN, HIGH); + delay(10); // give sx1262 some time to power up +} + +bool ikoka_stick_nrf_board::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("XIAO_NRF52_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; + + + return false; +} + +#endif diff --git a/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h b/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h new file mode 100644 index 00000000..c66f4827 --- /dev/null +++ b/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +#ifdef XIAO_NRF52 + +class ikoka_stick_nrf_board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin(); + uint8_t getStartupReason() const override { return startup_reason; } + +#if defined(P_LORA_TX_LED) + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off + } +#endif + + uint16_t getBattMilliVolts() override { + // Please read befor going further ;) + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + + // We can't drive VBAT_ENABLE to HIGH as long + // as we don't know wether we are charging or not ... + // this is a 3mA loss (4/1500) + digitalWrite(VBAT_ENABLE, LOW); + int adcvalue = 0; + analogReadResolution(12); + analogReference(AR_INTERNAL_3_0); + delay(10); + adcvalue = analogRead(PIN_VBAT); + return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096; + } + + const char* getManufacturerName() const override { + return "Ikoka Stick (Xiao-nrf52)"; + } + + void reboot() override { + NVIC_SystemReset(); + } + + bool startOTAUpdate(const char* id, char reply[]) override; +}; + +#endif diff --git a/variants/ikoka_stick_nrf/platformio.ini b/variants/ikoka_stick_nrf/platformio.ini new file mode 100644 index 00000000..6e6ae101 --- /dev/null +++ b/variants/ikoka_stick_nrf/platformio.ini @@ -0,0 +1,307 @@ +[nrf52840_xiao] +extends = nrf52_base +platform_packages = + toolchain-gccarmnoneeabi@~1.100301.0 + framework-arduinoadafruitnrf52 +board = seeed-xiao-afruitnrf52-nrf52840 +board_build.ldscript = boards/nrf52840_s140_v7.ld +build_flags = ${nrf52_base.build_flags} + -D NRF52_PLATFORM -D XIAO_NRF52 + -I lib/nrf52/s140_nrf52_7.3.0_API/include + -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 +lib_ignore = + BluetoothOTA + lvgl + lib5b4 +lib_deps = + ${nrf52_base.lib_deps} + rweather/Crypto @ ^0.4.0 + adafruit/Adafruit INA3221 Library @ ^1.0.1 + adafruit/Adafruit INA219 @ ^1.2.3 + adafruit/Adafruit AHTX0 @ ^2.0.5 + adafruit/Adafruit BME280 Library @ ^2.3.0 + adafruit/Adafruit SSD1306 @ ^2.5.13 + +[ikoka_stick_nrf_baseboard] +extends = nrf52840_xiao +;board_build.ldscript = boards/nrf52840_s140_v7.ld +build_flags = ${nrf52840_xiao.build_flags} + -D P_LORA_TX_LED=11 + -I variants/ikoka_stick_nrf + -I src/helpers/nrf52 + -D DISPLAY_CLASS=SSD1306Display + -D DISPLAY_ROTATION=2 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_DIO_1=D1 + -D P_LORA_RESET=D2 + -D P_LORA_BUSY=D3 + -D P_LORA_NSS=D4 + -D SX126X_RXEN=D5 + -D SX126X_TXEN=RADIOLIB_NC + -D SX126X_DIO2_AS_RF_SWITCH=1 + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_USER_BTN=0 + -D PIN_WIRE_SCL=7 + -D PIN_WIRE_SDA=6 + -D ENV_INCLUDE_AHTX0=1 + -D ENV_INCLUDE_BME280=1 + -D ENV_INCLUDE_INA3221=1 + -D ENV_INCLUDE_INA219=1 +debug_tool = jlink +upload_protocol = nrfutil + + +;;; abstracted hardware variants + +[ikoka_stick_nrf_e22_22dbm] +extends = ikoka_stick_nrf_baseboard +; No PA in this model, full 22dBm +build_flags = + ${ikoka_stick_nrf_baseboard.build_flags} + -D LORA_TX_POWER=22 +build_src_filter = ${nrf52840_xiao.build_src_filter} + + + + + + + + + +<../variants/ikoka_stick_nrf> + +[ikoka_stick_nrf_e22_30dbm] +extends = ikoka_stick_nrf_baseboard +; limit txpower to 20dBm on E22-900M30S. Anything higher will +; cause distortion in the PA output. 20dBm in -> 30dBm out +build_flags = + ${ikoka_stick_nrf_baseboard.build_flags} + -D LORA_TX_POWER=20 +build_src_filter = ${nrf52840_xiao.build_src_filter} + + + + + + + + + +<../variants/ikoka_stick_nrf> + +[ikoka_stick_nrf_e22_33dbm] +extends = ikoka_stick_nrf_baseboard +; limit txpower to 9dBm on E22-900M33S to avoid hardware damage +; to the rf amplifier frontend. 9dBm in -> 33dBm out +build_flags = + ${ikoka_stick_nrf_baseboard.build_flags} + -D LORA_TX_POWER=9 +build_src_filter = ${nrf52840_xiao.build_src_filter} + + + + + + + + + +<../variants/ikoka_stick_nrf> + +;;; abstracted firmware roles + +[ikoka_stick_nrf_companion_radio_ble] +extends = ikoka_stick_nrf_baseboard +build_flags = + ${ikoka_stick_nrf_baseboard.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 + -I examples/companion_radio/ui-new +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ikoka_stick_nrf_baseboard.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ikoka_stick_nrf_baseboard.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[ikoka_stick_nrf_companion_radio_usb] +extends = ikoka_stick_nrf_baseboard +build_flags = + ${ikoka_stick_nrf_baseboard.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -I examples/companion_radio/ui-new +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ikoka_stick_nrf_baseboard.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ikoka_stick_nrf_baseboard.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[ikoka_stick_nrf_repeater] +extends = ikoka_stick_nrf_baseboard +build_flags = + ${ikoka_stick_nrf_baseboard.build_flags} + -D ADVERT_NAME='"Ikoka Stick 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 = ${ikoka_stick_nrf_baseboard.build_src_filter} + + + +<../examples/simple_repeater/*.cpp> + +[ikoka_stick_nrf_room_server] +extends = ikoka_stick_nrf_baseboard +build_flags = + ${ikoka_stick_nrf_baseboard.build_flags} + -D ADVERT_NAME='"Ikoka Stick 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 = ${ikoka_stick_nrf_baseboard.build_src_filter} + +<../examples/simple_room_server/*.cpp> + +;;; hardware + firmware variants + +;;; 22dBm EBYTE E22-900M22 variants + +[env:ikoka_stick_nrf_22dbm_companion_radio_usb] +extends = + ikoka_stick_nrf_e22_22dbm + ikoka_stick_nrf_companion_radio_usb +build_flags = + ${ikoka_stick_nrf_companion_radio_usb.build_flags} + ${ikoka_stick_nrf_e22_22dbm.build_flags} +build_src_filter = + ${ikoka_stick_nrf_companion_radio_usb.build_src_filter} + ${ikoka_stick_nrf_e22_22dbm.build_src_filter} + +[env:ikoka_stick_nrf_22dbm_companion_radio_ble] +extends = + ikoka_stick_nrf_e22_22dbm + ikoka_stick_nrf_companion_radio_ble +build_flags = + ${ikoka_stick_nrf_companion_radio_ble.build_flags} + ${ikoka_stick_nrf_e22_22dbm.build_flags} +build_src_filter = + ${ikoka_stick_nrf_companion_radio_ble.build_src_filter} + ${ikoka_stick_nrf_e22_22dbm.build_src_filter} + +[env:ikoka_stick_nrf_22dbm_repeater] +extends = + ikoka_stick_nrf_e22_22dbm + ikoka_stick_nrf_repeater +build_flags = + ${ikoka_stick_nrf_repeater.build_flags} + ${ikoka_stick_nrf_e22_22dbm.build_flags} +build_src_filter = + ${ikoka_stick_nrf_repeater.build_src_filter} + ${ikoka_stick_nrf_e22_22dbm.build_src_filter} + +[env:ikoka_stick_nrf_22dbm_room_server] +extends = + ikoka_stick_nrf_e22_22dbm + ikoka_stick_nrf_room_server +build_flags = + ${ikoka_stick_nrf_room_server.build_flags} + ${ikoka_stick_nrf_e22_22dbm.build_flags} +build_src_filter = + ${ikoka_stick_nrf_room_server.build_src_filter} + ${ikoka_stick_nrf_e22_22dbm.build_src_filter} + + +;;; 30dBm EBYTE E22-900M30 variants + +[env:ikoka_stick_nrf_30dbm_companion_radio_usb] +extends = + ikoka_stick_nrf_e22_30dbm + ikoka_stick_nrf_companion_radio_usb +build_flags = + ${ikoka_stick_nrf_companion_radio_usb.build_flags} + ${ikoka_stick_nrf_e22_30dbm.build_flags} +build_src_filter = + ${ikoka_stick_nrf_companion_radio_usb.build_src_filter} + ${ikoka_stick_nrf_e22_30dbm.build_src_filter} + +[env:ikoka_stick_nrf_30dbm_companion_radio_ble] +extends = + ikoka_stick_nrf_e22_30dbm + ikoka_stick_nrf_companion_radio_ble +build_flags = + ${ikoka_stick_nrf_companion_radio_ble.build_flags} + ${ikoka_stick_nrf_e22_30dbm.build_flags} +build_src_filter = + ${ikoka_stick_nrf_companion_radio_ble.build_src_filter} + ${ikoka_stick_nrf_e22_30dbm.build_src_filter} + +[env:ikoka_stick_nrf_30dbm_repeater] +extends = + ikoka_stick_nrf_e22_30dbm + ikoka_stick_nrf_repeater +build_flags = + ${ikoka_stick_nrf_repeater.build_flags} + ${ikoka_stick_nrf_e22_30dbm.build_flags} +build_src_filter = + ${ikoka_stick_nrf_repeater.build_src_filter} + ${ikoka_stick_nrf_e22_30dbm.build_src_filter} + +[env:ikoka_stick_nrf_30dbm_room_server] +extends = + ikoka_stick_nrf_e22_30dbm + ikoka_stick_nrf_room_server +build_flags = + ${ikoka_stick_nrf_room_server.build_flags} + ${ikoka_stick_nrf_e22_30dbm.build_flags} +build_src_filter = + ${ikoka_stick_nrf_room_server.build_src_filter} + ${ikoka_stick_nrf_e22_30dbm.build_src_filter} + + +;;; 33dBm EBYTE E22-900M33 variants + +[env:ikoka_stick_nrf_33dbm_companion_radio_usb] +extends = + ikoka_stick_nrf_e22_33dbm + ikoka_stick_nrf_companion_radio_usb +build_flags = + ${ikoka_stick_nrf_companion_radio_usb.build_flags} + ${ikoka_stick_nrf_e22_33dbm.build_flags} +build_src_filter = + ${ikoka_stick_nrf_companion_radio_usb.build_src_filter} + ${ikoka_stick_nrf_e22_33dbm.build_src_filter} + +[env:ikoka_stick_nrf_33dbm_companion_radio_ble] +extends = + ikoka_stick_nrf_e22_33dbm + ikoka_stick_nrf_companion_radio_ble +build_flags = + ${ikoka_stick_nrf_companion_radio_ble.build_flags} + ${ikoka_stick_nrf_e22_33dbm.build_flags} +build_src_filter = + ${ikoka_stick_nrf_companion_radio_ble.build_src_filter} + ${ikoka_stick_nrf_e22_33dbm.build_src_filter} + +[env:ikoka_stick_nrf_33dbm_repeater] +extends = + ikoka_stick_nrf_e22_33dbm + ikoka_stick_nrf_repeater +build_flags = + ${ikoka_stick_nrf_repeater.build_flags} + ${ikoka_stick_nrf_e22_33dbm.build_flags} +build_src_filter = + ${ikoka_stick_nrf_repeater.build_src_filter} + ${ikoka_stick_nrf_e22_33dbm.build_src_filter} + +[env:ikoka_stick_nrf_33dbm_room_server] +extends = + ikoka_stick_nrf_e22_33dbm + ikoka_stick_nrf_room_server +build_flags = + ${ikoka_stick_nrf_room_server.build_flags} + ${ikoka_stick_nrf_e22_33dbm.build_flags} +build_src_filter = + ${ikoka_stick_nrf_room_server.build_src_filter} + ${ikoka_stick_nrf_e22_33dbm.build_src_filter} diff --git a/variants/ikoka_stick_nrf/target.cpp b/variants/ikoka_stick_nrf/target.cpp new file mode 100644 index 00000000..c2712761 --- /dev/null +++ b/variants/ikoka_stick_nrf/target.cpp @@ -0,0 +1,44 @@ +#include +#include "target.h" +#include + +ikoka_stick_nrf_board board; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +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); +EnvironmentSensorManager sensors; + +bool radio_init() { + rtc_clock.begin(Wire); + + return radio.std_init(&SPI); +} + +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/ikoka_stick_nrf/target.h b/variants/ikoka_stick_nrf/target.h new file mode 100644 index 00000000..8311503a --- /dev/null +++ b/variants/ikoka_stick_nrf/target.h @@ -0,0 +1,28 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include + +#ifdef DISPLAY_CLASS + #include + #include + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +extern ikoka_stick_nrf_board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager 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/ikoka_stick_nrf/variant.cpp b/variants/ikoka_stick_nrf/variant.cpp new file mode 100644 index 00000000..16542e27 --- /dev/null +++ b/variants/ikoka_stick_nrf/variant.cpp @@ -0,0 +1,86 @@ +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" +#include "nrf.h" + +const uint32_t g_ADigitalPinMap[] = +{ + // D0 .. D10 + 2, // D0 is P0.02 (A0) + 3, // D1 is P0.03 (A1) + 28, // D2 is P0.28 (A2) + 29, // D3 is P0.29 (A3) + 4, // D4 is P0.04 (A4,SDA) + 5, // D5 is P0.05 (A5,SCL) + 43, // D6 is P1.11 (TX) + 44, // D7 is P1.12 (RX) + 45, // D8 is P1.13 (SCK) + 46, // D9 is P1.14 (MISO) + 47, // D10 is P1.15 (MOSI) + + // LEDs + 26, // D11 is P0.26 (LED RED) + 6, // D12 is P0.06 (LED BLUE) + 30, // D13 is P0.30 (LED GREEN) + 14, // D14 is P0.14 (READ_BAT) + + // LSM6DS3TR + 40, // D15 is P1.08 (6D_PWR) + 27, // D16 is P0.27 (6D_I2C_SCL) + 7, // D17 is P0.07 (6D_I2C_SDA) + 11, // D18 is P0.11 (6D_INT1) + + // MIC + 42, // D19 is P1.10 (MIC_PWR) + 32, // D20 is P1.00 (PDM_CLK) + 16, // D21 is P0.16 (PDM_DATA) + + // BQ25100 + 13, // D22 is P0.13 (HICHG) + 17, // D23 is P0.17 (~CHG) + + // + 21, // D24 is P0.21 (QSPI_SCK) + 25, // D25 is P0.25 (QSPI_CSN) + 20, // D26 is P0.20 (QSPI_SIO_0 DI) + 24, // D27 is P0.24 (QSPI_SIO_1 DO) + 22, // D28 is P0.22 (QSPI_SIO_2 WP) + 23, // D29 is P0.23 (QSPI_SIO_3 HOLD) + + // NFC + 9, // D30 is P0.09 (NFC1) + 10, // D31 is P0.10 (NFC2) + + // VBAT + 31, // D32 is P0.31 (VBAT) +}; + +void initVariant() +{ + // Disable reading of the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + pinMode(VBAT_ENABLE, OUTPUT); + //digitalWrite(VBAT_ENABLE, HIGH); + // This was taken from Seeed github butis not coherent with the doc, + // VBAT_ENABLE should be kept to LOW to protect P0.14, (1500/500)*(4.2-3.3)+3.3 = 3.9V > 3.6V + // This induces a 3mA current in the resistors :( but it's better than burning the nrf + digitalWrite(VBAT_ENABLE, LOW); + + // Low charging current (50mA) + // https://wiki.seeedstudio.com/XIAO_BLE#battery-charging-current + //pinMode(PIN_CHARGING_CURRENT, INPUT); + + // High charging current (100mA) + pinMode(PIN_CHARGING_CURRENT, OUTPUT); + digitalWrite(PIN_CHARGING_CURRENT, LOW); + + pinMode(PIN_QSPI_CS, OUTPUT); + digitalWrite(PIN_QSPI_CS, HIGH); + + pinMode(LED_RED, OUTPUT); + digitalWrite(LED_RED, HIGH); + pinMode(LED_GREEN, OUTPUT); + digitalWrite(LED_GREEN, HIGH); + pinMode(LED_BLUE, OUTPUT); + digitalWrite(LED_BLUE, HIGH); +} diff --git a/variants/ikoka_stick_nrf/variant.h b/variants/ikoka_stick_nrf/variant.h new file mode 100644 index 00000000..f94ebe49 --- /dev/null +++ b/variants/ikoka_stick_nrf/variant.h @@ -0,0 +1,149 @@ +#ifndef _IKOKA_STICK_NRF_H_ +#define _IKOKA_STICK_NRF_H_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +//#define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +#define PINS_COUNT (33) +#define NUM_DIGITAL_PINS (33) +#define NUM_ANALOG_INPUTS (8) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED (LED_RED) +#define LED_PWR (PINS_COUNT) +#define PIN_NEOPIXEL (PINS_COUNT) +#define NEOPIXEL_NUM (0) + +#define LED_BUILTIN (PIN_LED) + +#define LED_RED (11) +#define LED_GREEN (13) +#define LED_BLUE (12) + +#define LED_STATE_ON (1) // State when LED is litted + +// Buttons +#define PIN_BUTTON1 (PINS_COUNT) + +// Digital PINs +static const uint8_t D0 = 0 ; +static const uint8_t D1 = 1 ; +static const uint8_t D2 = 2 ; +static const uint8_t D3 = 3 ; +static const uint8_t D4 = 4 ; +static const uint8_t D5 = 5 ; +static const uint8_t D6 = 6 ; +static const uint8_t D7 = 7 ; +static const uint8_t D8 = 8 ; +static const uint8_t D9 = 9 ; +static const uint8_t D10 = 10; + +#define VBAT_ENABLE (14) // Output LOW to enable reading of the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + +#define PIN_CHARGING_CURRENT (22) // Battery Charging current + // https://wiki.seeedstudio.com/XIAO_BLE#battery-charging-current + +// Analog pins +#define PIN_A0 (0) +#define PIN_A1 (1) +#define PIN_A2 (2) +#define PIN_A3 (3) +#define PIN_A4 (4) +#define PIN_A5 (5) +#define PIN_VBAT (32) // Read the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + +#define BAT_NOT_CHARGING (23) // LOW when charging + +#define AREF_VOLTAGE (3.0) +#define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; + +#define ADC_RESOLUTION (12) + +// Other pins +#define PIN_NFC1 (30) +#define PIN_NFC2 (31) + +// Serial interfaces +#define PIN_SERIAL1_RX (7) +#define PIN_SERIAL1_TX (6) + +// SPI Interfaces +#define SPI_INTERFACES_COUNT (2) + +#define PIN_SPI_MISO (9) +#define PIN_SPI_MOSI (10) +#define PIN_SPI_SCK (8) + +#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) + +// #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; + +//#define PIN_WIRE1_SDA (17) +//#define PIN_WIRE1_SCL (16) +#define PIN_LSM6DS3TR_C_POWER (15) +#define PIN_LSM6DS3TR_C_INT1 (18) + +// PDM Interfaces +#define PIN_PDM_PWR (19) +#define PIN_PDM_CLK (20) +#define PIN_PDM_DIN (21) + +// QSPI Pins +#define PIN_QSPI_SCK (24) +#define PIN_QSPI_CS (25) +#define PIN_QSPI_IO0 (26) +#define PIN_QSPI_IO1 (27) +#define PIN_QSPI_IO2 (28) +#define PIN_QSPI_IO3 (29) + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES (P25Q16H) +#define EXTERNAL_FLASH_USE_QSPI + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif diff --git a/variants/lilygo_t3s3/platformio.ini b/variants/lilygo_t3s3/platformio.ini index f3a95e96..637cc123 100644 --- a/variants/lilygo_t3s3/platformio.ini +++ b/variants/lilygo_t3s3/platformio.ini @@ -56,7 +56,7 @@ lib_deps = extends = LilyGo_T3S3_sx1262 build_flags = ${LilyGo_T3S3_sx1262.build_flags} - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -89,14 +89,17 @@ lib_deps = extends = LilyGo_T3S3_sx1262 build_flags = ${LilyGo_T3S3_sx1262.build_flags} + -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter} + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_T3S3_sx1262.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -105,8 +108,9 @@ lib_deps = extends = LilyGo_T3S3_sx1262 build_flags = ${LilyGo_T3S3_sx1262.build_flags} + -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 @@ -116,7 +120,9 @@ build_flags = build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter} + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_T3S3_sx1262.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/lilygo_t3s3/target.cpp b/variants/lilygo_t3s3/target.cpp index b7c4542c..1c7b3b09 100644 --- a/variants/lilygo_t3s3/target.cpp +++ b/variants/lilygo_t3s3/target.cpp @@ -13,6 +13,7 @@ SensorManager sensors; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif #ifndef LORA_CR diff --git a/variants/lilygo_t3s3/target.h b/variants/lilygo_t3s3/target.h index b768b2b0..f184c757 100644 --- a/variants/lilygo_t3s3/target.h +++ b/variants/lilygo_t3s3/target.h @@ -9,6 +9,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif extern ESP32Board board; @@ -18,6 +19,7 @@ extern SensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/lilygo_t3s3_sx1276/platformio.ini b/variants/lilygo_t3s3_sx1276/platformio.ini index 74eced9c..23c58fb8 100644 --- a/variants/lilygo_t3s3_sx1276/platformio.ini +++ b/variants/lilygo_t3s3_sx1276/platformio.ini @@ -54,7 +54,7 @@ lib_deps = extends = LilyGo_T3S3_sx1276 build_flags = ${LilyGo_T3S3_sx1276.build_flags} - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -88,14 +88,17 @@ extends = LilyGo_T3S3_sx1276 upload_speed = 115200 build_flags = ${LilyGo_T3S3_sx1276.build_flags} + -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D MESH_PACKET_LOGGING=1 -D MESH_DEBUG=1 build_src_filter = ${LilyGo_T3S3_sx1276.build_src_filter} + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_T3S3_sx1276.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -104,8 +107,9 @@ lib_deps = extends = LilyGo_T3S3_sx1276 build_flags = ${LilyGo_T3S3_sx1276.build_flags} + -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 @@ -115,7 +119,9 @@ build_flags = build_src_filter = ${LilyGo_T3S3_sx1276.build_src_filter} + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_T3S3_sx1276.lib_deps} densaugeo/base64 @ ~1.4.0 \ No newline at end of file diff --git a/variants/lilygo_t3s3_sx1276/target.cpp b/variants/lilygo_t3s3_sx1276/target.cpp index b2ee4455..042ff206 100644 --- a/variants/lilygo_t3s3_sx1276/target.cpp +++ b/variants/lilygo_t3s3_sx1276/target.cpp @@ -18,6 +18,7 @@ SensorManager sensors; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/lilygo_t3s3_sx1276/target.h b/variants/lilygo_t3s3_sx1276/target.h index 52ecf867..98a0fe35 100644 --- a/variants/lilygo_t3s3_sx1276/target.h +++ b/variants/lilygo_t3s3_sx1276/target.h @@ -9,6 +9,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif extern ESP32Board board; @@ -18,6 +19,7 @@ extern SensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/lilygo_tbeam_SX1262/platformio.ini b/variants/lilygo_tbeam_SX1262/platformio.ini index eac899f0..7bb008ac 100644 --- a/variants/lilygo_tbeam_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_SX1262/platformio.ini @@ -37,6 +37,7 @@ extends = LilyGo_TBeam_SX1262 board_build.upload.maximum_ram_size=2000000 build_flags = ${LilyGo_TBeam_SX1262.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -49,7 +50,9 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_TBeam_SX1262.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/lilygo_tbeam_SX1262/target.cpp b/variants/lilygo_tbeam_SX1262/target.cpp index fb05958b..a8caecb3 100644 --- a/variants/lilygo_tbeam_SX1262/target.cpp +++ b/variants/lilygo_tbeam_SX1262/target.cpp @@ -25,6 +25,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/lilygo_tbeam_SX1262/target.h b/variants/lilygo_tbeam_SX1262/target.h index cb9b28df..5f33abb8 100644 --- a/variants/lilygo_tbeam_SX1262/target.h +++ b/variants/lilygo_tbeam_SX1262/target.h @@ -9,6 +9,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif extern TBeamBoard board; @@ -18,6 +19,7 @@ extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/lilygo_tbeam_SX1276/platformio.ini b/variants/lilygo_tbeam_SX1276/platformio.ini index b0f6c001..e0391f1d 100644 --- a/variants/lilygo_tbeam_SX1276/platformio.ini +++ b/variants/lilygo_tbeam_SX1276/platformio.ini @@ -36,6 +36,7 @@ extends = LilyGo_TBeam_SX1276 board_build.upload.maximum_ram_size=2000000 build_flags = ${LilyGo_TBeam_SX1276.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -46,7 +47,9 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_TBeam_SX1276.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -67,4 +70,20 @@ build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} +<../examples/simple_repeater> lib_deps = ${LilyGo_TBeam_SX1276.lib_deps} - ${esp32_ota.lib_deps} \ No newline at end of file + ${esp32_ota.lib_deps} + +[env:Tbeam_SX1276_room_server] +extends = LilyGo_TBeam_SX1276 +build_flags = + ${LilyGo_TBeam_SX1276.build_flags} + -D ADVERT_NAME='"Tbeam 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 = ${LilyGo_TBeam_SX1276.build_src_filter} + +<../examples/simple_room_server> +lib_deps = + ${LilyGo_TBeam_SX1276.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/lilygo_tbeam_SX1276/target.cpp b/variants/lilygo_tbeam_SX1276/target.cpp index 7e2537bb..0a7517a2 100644 --- a/variants/lilygo_tbeam_SX1276/target.cpp +++ b/variants/lilygo_tbeam_SX1276/target.cpp @@ -25,6 +25,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/lilygo_tbeam_SX1276/target.h b/variants/lilygo_tbeam_SX1276/target.h index bcd8cb0b..b382b652 100644 --- a/variants/lilygo_tbeam_SX1276/target.h +++ b/variants/lilygo_tbeam_SX1276/target.h @@ -9,6 +9,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif extern TBeamBoard board; @@ -18,6 +19,7 @@ extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index 5c2d6e86..e6135872 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -25,7 +25,7 @@ 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} @@ -73,7 +73,8 @@ lib_deps = extends = T_Beam_S3_Supreme_SX1262 build_flags = ${T_Beam_S3_Supreme_SX1262.build_flags} - -D MAX_CONTACTS=100 + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 @@ -82,7 +83,9 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${T_Beam_S3_Supreme_SX1262.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.cpp b/variants/lilygo_tbeam_supreme_SX1262/target.cpp index 68d54396..8ad306f1 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.cpp +++ b/variants/lilygo_tbeam_supreme_SX1262/target.cpp @@ -5,6 +5,7 @@ TBeamBoard board; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif static SPIClass spi; diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.h b/variants/lilygo_tbeam_supreme_SX1262/target.h index 62a92329..c6ffa0a6 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.h +++ b/variants/lilygo_tbeam_supreme_SX1262/target.h @@ -11,6 +11,8 @@ #ifdef DISPLAY_CLASS #include extern DISPLAY_CLASS display; + #include + extern MomentaryButton user_btn; #endif extern TBeamBoard board; diff --git a/variants/lilygo_tlora_c6/platformio.ini b/variants/lilygo_tlora_c6/platformio.ini index 7f89c3da..76a897d6 100644 --- a/variants/lilygo_tlora_c6/platformio.ini +++ b/variants/lilygo_tlora_c6/platformio.ini @@ -67,7 +67,7 @@ lib_deps = [env:LilyGo_Tlora_C6_companion_radio_ble] extends = tlora_c6 build_flags = ${tlora_c6.build_flags} - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 @@ -79,7 +79,7 @@ build_flags = ${tlora_c6.build_flags} build_src_filter = ${tlora_c6.build_src_filter} + - - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${tlora_c6.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index 71b49aab..0ed06856 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -83,13 +83,16 @@ lib_deps = extends = LilyGo_TLora_V2_1_1_6 build_flags = ${LilyGo_TLora_V2_1_1_6.build_flags} + -I examples/companion_radio/ui-new -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 = ${LilyGo_TLora_V2_1_1_6.build_src_filter} + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -98,6 +101,7 @@ lib_deps = extends = LilyGo_TLora_V2_1_1_6 build_flags = ${LilyGo_TLora_V2_1_1_6.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -108,7 +112,9 @@ build_flags = build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/lilygo_tlora_v2_1/target.cpp b/variants/lilygo_tlora_v2_1/target.cpp index 5e8f15b2..65a78c19 100644 --- a/variants/lilygo_tlora_v2_1/target.cpp +++ b/variants/lilygo_tlora_v2_1/target.cpp @@ -14,6 +14,7 @@ EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/lilygo_tlora_v2_1/target.h b/variants/lilygo_tlora_v2_1/target.h index f05b8055..380d733b 100644 --- a/variants/lilygo_tlora_v2_1/target.h +++ b/variants/lilygo_tlora_v2_1/target.h @@ -10,6 +10,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif extern LilyGoTLoraBoard board; @@ -19,6 +20,7 @@ extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/meshadventurer/platformio.ini b/variants/meshadventurer/platformio.ini index f8995e18..3ea09ba7 100644 --- a/variants/meshadventurer/platformio.ini +++ b/variants/meshadventurer/platformio.ini @@ -78,10 +78,11 @@ lib_deps = [env:Meshadventurer_sx1262_companion_radio_usb] extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + build_flags = ${Meshadventurer.build_flags} + -I examples/companion_radio/ui-new -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 @@ -96,11 +97,14 @@ lib_deps = [env:Meshadventurer_sx1262_companion_radio_ble] extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> build_flags = ${Meshadventurer.build_flags} + -I examples/companion_radio/ui-new -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 @@ -157,10 +161,13 @@ lib_deps = [env:Meshadventurer_sx1268_companion_radio_usb] extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> build_flags = ${Meshadventurer.build_flags} + -I examples/companion_radio/ui-new -D RADIO_CLASS=CustomSX1268 -D WRAPPER_CLASS=CustomSX1268Wrapper -D LORA_TX_POWER=22 @@ -175,11 +182,14 @@ lib_deps = [env:Meshadventurer_sx1268_companion_radio_ble] extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> build_flags = ${Meshadventurer.build_flags} + -I examples/companion_radio/ui-new -D RADIO_CLASS=CustomSX1268 -D WRAPPER_CLASS=CustomSX1268Wrapper -D LORA_TX_POWER=22 diff --git a/variants/meshadventurer/target.cpp b/variants/meshadventurer/target.cpp index a1d6dcad..cabcee58 100644 --- a/variants/meshadventurer/target.cpp +++ b/variants/meshadventurer/target.cpp @@ -18,10 +18,6 @@ MASensorManager sensors = MASensorManager(nmea); DISPLAY_CLASS display; #endif -#ifndef LORA_CR - #define LORA_CR 5 -#endif - bool radio_init() { fallback_clock.begin(); rtc_clock.begin(Wire); diff --git a/variants/minewsemi_me25ls01/platformio.ini b/variants/minewsemi_me25ls01/platformio.ini index f7265af4..3436062f 100644 --- a/variants/minewsemi_me25ls01/platformio.ini +++ b/variants/minewsemi_me25ls01/platformio.ini @@ -51,6 +51,7 @@ lib_deps = ${nrf52840_me25ls01.lib_deps} [env:Minewsemi_me25ls01_companion_radio_ble] extends = me25ls01 build_flags = ${me25ls01.build_flags} + -I examples/companion_radio/ui-orig -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -66,6 +67,7 @@ build_flags = ${me25ls01.build_flags} build_src_filter = ${me25ls01.build_src_filter} + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${me25ls01.lib_deps} adafruit/RTClib @ ^2.1.3 @@ -146,6 +148,7 @@ lib_deps = ${me25ls01.lib_deps} [env:Minewsemi_me25ls01_companion_radio_usb] extends = me25ls01 build_flags = ${me25ls01.build_flags} + -I examples/companion_radio/ui-orig -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 ;-D BLE_PIN_CODE=123456 @@ -158,7 +161,8 @@ build_flags = ${me25ls01.build_flags} -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${me25ls01.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${me25ls01.lib_deps} adafruit/RTClib @ ^2.1.3 diff --git a/variants/nano_g2_ultra/nano-g2.cpp b/variants/nano_g2_ultra/nano-g2.cpp index 08a1b091..9a278287 100644 --- a/variants/nano_g2_ultra/nano-g2.cpp +++ b/variants/nano_g2_ultra/nano-g2.cpp @@ -34,6 +34,8 @@ void NanoG2Ultra::begin() pinMode(EXT_NOTIFY_OUT, OUTPUT); digitalWrite(EXT_NOTIFY_OUT, LOW); + pinMode(GPS_EN, OUTPUT); // Initialize GPS power pin + Wire.begin(); pinMode(SX126X_POWER_EN, OUTPUT); digitalWrite(SX126X_POWER_EN, HIGH); diff --git a/variants/nano_g2_ultra/nano-g2.h b/variants/nano_g2_ultra/nano-g2.h index 99dc75fa..69df0c65 100644 --- a/variants/nano_g2_ultra/nano-g2.h +++ b/variants/nano_g2_ultra/nano-g2.h @@ -1,37 +1,40 @@ #pragma once -#include +#include "variant.h" + #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 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 +#define SX126X_POWER_EN 37 // buttons -#define PIN_BUTTON1 (32 + 6) -#define BUTTON_PIN PIN_BUTTON1 -#define PIN_USER_BTN BUTTON_PIN +#define PIN_BUTTON1 (32 + 6) +#define BUTTON_PIN PIN_BUTTON1 +#define PIN_USER_BTN BUTTON_PIN +// GPS +#define GPS_EN PIN_GPS_STANDBY // built-ins -#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 +#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 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) +#define PIN_VBAT_READ (0 + 2) +#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class NanoG2Ultra : public mesh::MainBoard -{ +class NanoG2Ultra : public mesh::MainBoard { protected: uint8_t startup_reason; @@ -40,18 +43,21 @@ public: uint16_t getBattMilliVolts() override; bool startOTAUpdate(const char *id, char reply[]) override; - uint8_t getStartupReason() const override - { - return startup_reason; - } + uint8_t getStartupReason() const override { return startup_reason; } - const char *getManufacturerName() const override - { - return "Nano G2 Ultra"; - } + const char *getManufacturerName() const override { return "Nano G2 Ultra"; } - void reboot() override - { - NVIC_SystemReset(); + void reboot() override { NVIC_SystemReset(); } + + void powerOff() override { + // put GPS chip to sleep + digitalWrite(PIN_GPS_STANDBY, LOW); +// unset buzzer to prevent notification circuit activating on hibernate +#undef PIN_BUZZER + + nrf_gpio_cfg_sense_input(digitalPinToInterrupt(PIN_USER_BTN), NRF_GPIO_PIN_NOPULL, + NRF_GPIO_PIN_SENSE_LOW); + + sd_power_system_off(); } }; diff --git a/variants/nano_g2_ultra/platformio.ini b/variants/nano_g2_ultra/platformio.ini index c2bb1f23..7fb2de28 100644 --- a/variants/nano_g2_ultra/platformio.ini +++ b/variants/nano_g2_ultra/platformio.ini @@ -34,10 +34,11 @@ extends = Nano_G2_Ultra build_flags = ${Nano_G2_Ultra.build_flags} -I src/helpers/ui + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 - -D BLE_DEBUG_LOGGING=1 +; -D BLE_DEBUG_LOGGING=0 -D OFFLINE_QUEUE_SIZE=256 -D DISPLAY_CLASS=SH1106Display -D PIN_BUZZER=4 @@ -47,7 +48,36 @@ build_src_filter = ${Nano_G2_Ultra.build_src_filter} + + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +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 + stevemarple/MicroNMEA @ ^2.0.6 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:Nano_G2_Ultra_companion_radio_usb] +extends = Nano_G2_Ultra +build_flags = + ${Nano_G2_Ultra.build_flags} + -I src/helpers/ui + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=SH1106Display + -D PIN_BUZZER=4 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Nano_G2_Ultra.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Nano_G2_Ultra.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/nano_g2_ultra/target.cpp b/variants/nano_g2_ultra/target.cpp index a67085ce..81e7744f 100644 --- a/variants/nano_g2_ultra/target.cpp +++ b/variants/nano_g2_ultra/target.cpp @@ -1,5 +1,6 @@ -#include #include "target.h" + +#include #include #include @@ -16,126 +17,119 @@ NanoG2UltraSensorManager sensors = NanoG2UltraSensorManager(nmea); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; +MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif -bool radio_init() -{ +bool radio_init() { rtc_clock.begin(Wire); return radio.std_init(&SPI); } -uint32_t radio_get_rng_seed() -{ +uint32_t radio_get_rng_seed() { return radio.random(0x7FFFFFFF); } -void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) -{ +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) -{ +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); +void NanoG2UltraSensorManager::start_gps() { + MESH_DEBUG_PRINTLN("Starting GPS"); + if (!gps_active) { + digitalWrite(PIN_GPS_STANDBY, HIGH); // Wake GPS from standby + Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX); + Serial1.begin(9600); + MESH_DEBUG_PRINTLN("Waiting for gps to power up"); + delay(1000); gps_active = true; } + _location->begin(); } -void NanoG2UltraSensorManager::stop_gps() -{ - if (gps_active) - { - MESH_DEBUG_PRINTLN("stopping GPS"); - digitalWrite(PIN_GPS_STANDBY, LOW); +void NanoG2UltraSensorManager::stop_gps() { + MESH_DEBUG_PRINTLN("Stopping GPS"); + if (gps_active) { + digitalWrite(PIN_GPS_STANDBY, LOW); // sleep GPS gps_active = false; } + _location->stop(); } -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); +bool NanoG2UltraSensorManager::begin() { digitalWrite(PIN_GPS_STANDBY, HIGH); // Wake GPS from standby - delay(500); + Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX); + Serial1.begin(9600); + MESH_DEBUG_PRINTLN("Checking GPS switch state"); + delay(1000); - // We'll consider GPS detected if we see any data on Serial1 - if (Serial1.available() > 0) - { - MESH_DEBUG_PRINTLN("GPS detected"); + // Check initial switch state to determine if GPS should be active + if (Serial1.available() > 0) { + MESH_DEBUG_PRINTLN("GPS was on at boot, GPS enabled"); + start_gps(); + } else { + MESH_DEBUG_PRINTLN("GPS was not on at boot, GPS disabled"); } - 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? +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() -{ +void NanoG2UltraSensorManager::loop() { static long next_gps_update = 0; + + if (!gps_active) { + return; // GPS is not active, skip further processing + } + _location->loop(); - if (millis() > next_gps_update && gps_active) // don't bother if gps position is not enabled - { - if (_location->isValid()) - { + + if (millis() > next_gps_update) { + 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); + MESH_DEBUG_PRINTLN("VALID location: lat %f lon %f", node_lat, node_lon); + } else { + MESH_DEBUG_PRINTLN("INVALID location, waiting for fix"); } - next_gps_update = millis() + (1000 * 60); // after initial update, only check every minute TODO: should be configurable + MESH_DEBUG_PRINTLN("GPS satellites: %d", _location->satellitesCount()); + next_gps_update = millis() + 1000; } } -int NanoG2UltraSensorManager::getNumSettings() const { return 1; } // just one supported: "gps" (power switch) +int NanoG2UltraSensorManager::getNumSettings() const { + return 1; +} // just one supported: "gps" (power switch) -const char *NanoG2UltraSensorManager::getSettingName(int i) const -{ +const char *NanoG2UltraSensorManager::getSettingName(int i) const { return i == 0 ? "gps" : NULL; } -const char *NanoG2UltraSensorManager::getSettingValue(int i) const -{ - if (i == 0) - { +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) - { +bool NanoG2UltraSensorManager::setSettingValue(const char *name, const char *value) { + if (strcmp(name, "gps") == 0) { + if (strcmp(value, "0") == 0) { stop_gps(); - } - else - { + } else { start_gps(); } return true; @@ -143,8 +137,7 @@ bool NanoG2UltraSensorManager::setSettingValue(const char *name, const char *val return false; // not supported } -mesh::LocalIdentity radio_new_identity() -{ +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 index 5cde6405..3e58b900 100644 --- a/variants/nano_g2_ultra/target.h +++ b/variants/nano_g2_ultra/target.h @@ -1,13 +1,15 @@ #pragma once #define RADIOLIB_STATIC_ONLY 1 -#include #include "nano-g2.h" -#include -#include + +#include #include #include +#include +#include #ifdef DISPLAY_CLASS +#include #include #endif #include @@ -37,6 +39,7 @@ extern NanoG2UltraSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; +extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/picow/platformio.ini b/variants/picow/platformio.ini index 8b6c2506..7b75c224 100644 --- a/variants/picow/platformio.ini +++ b/variants/picow/platformio.ini @@ -52,7 +52,7 @@ build_flags = ${picow.build_flags} ; 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> + +<../examples/companion_radio/*.cpp> lib_deps = ${picow.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -66,7 +66,7 @@ lib_deps = ${picow.lib_deps} ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${picow.build_src_filter} -; +<../examples/companion_radio> +; +<../examples/companion_radio/*.cpp> ; lib_deps = ${picow.lib_deps} ; densaugeo/base64 @ ~1.4.0 @@ -81,7 +81,7 @@ lib_deps = ${picow.lib_deps} ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${picow.build_src_filter} -; +<../examples/companion_radio> +; +<../examples/companion_radio/*.cpp> ; lib_deps = ${picow.lib_deps} ; densaugeo/base64 @ ~1.4.0 diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 4e5edc22..6b77bdcf 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -39,6 +39,7 @@ extends = Faketec build_src_filter = ${Faketec.build_src_filter} +<../examples/simple_repeater> + + + build_flags = ${Faketec.build_flags} -D ADVERT_NAME='"Faketec Repeater"' @@ -57,6 +58,7 @@ extends = Faketec 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 @@ -85,14 +87,17 @@ lib_deps = ${Faketec.lib_deps} [env:Faketec_companion_radio_usb] extends = Faketec build_flags = ${Faketec.build_flags} + -I examples/companion_radio/ui-new -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} - +<../examples/companion_radio> + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 @@ -100,6 +105,7 @@ lib_deps = ${Faketec.lib_deps} [env:Faketec_companion_radio_ble] extends = Faketec build_flags = ${Faketec.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -110,98 +116,28 @@ build_flags = ${Faketec.build_flags} -D MESH_DEBUG=1 build_src_filter = ${Faketec.build_src_filter} + - +<../examples/companion_radio> + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Faketec.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 -[ProMicroLLCC68] -extends = nrf52_base -board = promicro_nrf52840 -build_flags = ${nrf52_base.build_flags} - -I variants/promicro - -D PROMICROLLCC68 - -D RADIO_CLASS=CustomLLCC68 - -D WRAPPER_CLASS=CustomLLCC68Wrapper - -D LORA_TX_POWER=22 - -D SX126X_CURRENT_LIMIT=140 - -D SX126X_RX_BOOSTED_GAIN=1 -build_src_filter = - ${nrf52_base.build_src_filter} - + - +<../variants/promicro> -lib_deps= ${nrf52_base.lib_deps} - 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 -build_src_filter = ${ProMicroLLCC68.build_src_filter} - +<../examples/simple_repeater/main.cpp> -build_flags = ${ProMicroLLCC68.build_flags} - -D ADVERT_NAME='"ProMicroLLCC68 Repeater"' +[env:Faketec_sensor] +extends = Faketec +build_flags = + ${Faketec.build_flags} + -D ADVERT_NAME='"Faketec Sensor"' + -D ADVERT_LAT=0.0 + -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 = ${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"' -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -lib_deps = ${ProMicroLLCC68.lib_deps} - adafruit/RTClib @ ^2.1.3 - -[env:ProMicroLLCC68_terminal_chat] -extends = ProMicroLLCC68 -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} - densaugeo/base64 @ ~1.4.0 - adafruit/RTClib @ ^2.1.3 - -[env:ProMicroLLCC68_companion_radio_usb] -extends = ProMicroLLCC68 -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> -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} - -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=8 - -D BLE_PIN_CODE=123456 - -D BLE_DEBUG_LOGGING=1 - -D OFFLINE_QUEUE_SIZE=256 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${ProMicroLLCC68.build_src_filter} - + - +<../examples/companion_radio> -lib_deps = ${ProMicroLLCC68.lib_deps} - adafruit/RTClib @ ^2.1.3 - densaugeo/base64 @ ~1.4.0 +build_src_filter = ${Faketec.build_src_filter} + + + + + +<../examples/simple_sensor> +lib_deps = + ${Faketec.lib_deps} diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index 03a5a16a..b26320e4 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -20,6 +20,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true); #endif bool radio_init() { diff --git a/variants/promicro/target.h b/variants/promicro/target.h index de2719e6..38c4b4e8 100644 --- a/variants/promicro/target.h +++ b/variants/promicro/target.h @@ -8,6 +8,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif #include @@ -19,6 +20,7 @@ extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/rak3x72/target.h b/variants/rak3x72/target.h index 61e4747d..e0c1441e 100644 --- a/variants/rak3x72/target.h +++ b/variants/rak3x72/target.h @@ -13,6 +13,12 @@ class RAK3x72Board : public STM32Board { public: + void begin() override { + STM32Board::begin(); + pinMode(PA0, OUTPUT); + pinMode(PA1, OUTPUT); + } + const char* getManufacturerName() const override { return "RAK 3x72"; } @@ -25,6 +31,17 @@ public: } return ((double)raw) * ADC_MULTIPLIER / 8 / 4096; } + + void setGpio(uint32_t values) override { + // set led values + digitalWrite(PA0, values & 1); + digitalWrite(PA1, (values & 2) >> 1); + } + + uint32_t getGpio() override { + // get led value + return (digitalRead(PA1) << 1) | digitalRead(PA0); + } }; extern RAK3x72Board board; diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 29d07a78..c9091878 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -4,6 +4,7 @@ platform = https://github.com/maxgerhardt/platform-nordicnrf52.git#rak board = wiscore_rak4631 board_check = true build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} -I variants/rak4631 -D RAK_4631 -D RAK_BOARD @@ -18,28 +19,15 @@ build_flags = ${nrf52_base.build_flags} -D LORA_TX_POWER=22 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 - -D ENV_INCLUDE_GPS=1 - -D ENV_INCLUDE_AHTX0=1 - -D ENV_INCLUDE_BME280=1 - -D ENV_INCLUDE_BMP280=1 - -D ENV_INCLUDE_SHTC3=1 - -D ENV_INCLUDE_LPS22HB=1 - -D ENV_INCLUDE_INA3221=1 - -D ENV_INCLUDE_INA219=1 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631> + + + + + lib_deps = ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} adafruit/Adafruit SSD1306 @ ^2.5.13 - stevemarple/MicroNMEA @ ^2.0.6 - arduino-libraries/Arduino_LPS22HB@^1.0.2 - 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 - adafruit/Adafruit BMP280 Library @ ^2.6.8 - adafruit/Adafruit SHTC3 Library @ ^1.0.1 sparkfun/SparkFun u-blox GNSS Arduino Library@^2.2.27 [env:RAK_4631_Repeater] @@ -78,6 +66,7 @@ build_src_filter = ${rak4631.build_src_filter} extends = rak4631 build_flags = ${rak4631.build_flags} + -I examples/companion_radio/ui-new -D PIN_USER_BTN=9 -D PIN_USER_BTN_ANA=31 -D DISPLAY_CLASS=SSD1306Display @@ -86,8 +75,8 @@ build_flags = ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${rak4631.build_src_filter} - + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${rak4631.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -96,6 +85,7 @@ lib_deps = extends = rak4631 build_flags = ${rak4631.build_flags} + -I examples/companion_radio/ui-new -D PIN_USER_BTN=9 -D PIN_USER_BTN_ANA=31 -D DISPLAY_CLASS=SSD1306Display @@ -107,9 +97,9 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${rak4631.build_src_filter} - + + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${rak4631.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -129,3 +119,18 @@ build_src_filter = ${rak4631.build_src_filter} lib_deps = ${rak4631.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:RAK_4631_sensor] +extends = rak4631 +build_flags = + ${rak4631.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RAK4631 Sensor"' + -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 = ${rak4631.build_src_filter} + + + +<../examples/simple_sensor> \ No newline at end of file diff --git a/variants/rak4631/target.cpp b/variants/rak4631/target.cpp index 4e9d3cce..618c9fc5 100644 --- a/variants/rak4631/target.cpp +++ b/variants/rak4631/target.cpp @@ -4,8 +4,13 @@ RAK4631Board board; +#ifndef PIN_USER_BTN + #define PIN_USER_BTN (-1) +#endif + #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); diff --git a/variants/rak4631/target.h b/variants/rak4631/target.h index c4c88183..5e93b7fc 100644 --- a/variants/rak4631/target.h +++ b/variants/rak4631/target.h @@ -11,6 +11,8 @@ #ifdef DISPLAY_CLASS #include extern DISPLAY_CLASS display; + #include + extern MomentaryButton user_btn; #endif extern RAK4631Board board; diff --git a/variants/sensecap_solar/platformio.ini b/variants/sensecap_solar/platformio.ini index 9626e9cd..bbac5d99 100644 --- a/variants/sensecap_solar/platformio.ini +++ b/variants/sensecap_solar/platformio.ini @@ -85,7 +85,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${SenseCap_Solar.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${SenseCap_Solar.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -100,7 +100,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${SenseCap_Solar.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${SenseCap_Solar.lib_deps} densaugeo/base64 @ ~1.4.0 \ No newline at end of file diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index 5d9a6823..0e1631a8 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -22,6 +22,7 @@ build_flags = build_src_filter = ${esp32_base.build_src_filter} +<../variants/station_g2> + + + lib_deps = ${esp32_base.lib_deps} adafruit/Adafruit SH110X @ ~2.1.13 @@ -44,6 +45,25 @@ lib_deps = ${Station_G2.lib_deps} ${esp32_ota.lib_deps} +[env:Station_G2_logging_repeater] +extends = Station_G2 +build_flags = + ${Station_G2.build_flags} + -D ADVERT_NAME='"Station G2 Logging 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 SX126X_RX_BOOSTED_GAIN=1 +; https://wiki.uniteng.com/en/meshtastic/station-g2#impact-of-lora-node-dense-areashigh-noise-environments-on-rf-performance +; -D MESH_DEBUG=1 +build_src_filter = ${Station_G2.build_src_filter} + +<../examples/simple_repeater> +lib_deps = + ${Station_G2.lib_deps} + ${esp32_ota.lib_deps} + [env:Station_G2_room_server] extends = Station_G2 build_src_filter = ${Station_G2.build_src_filter} @@ -65,14 +85,16 @@ lib_deps = extends = Station_G2 build_flags = ${Station_G2.build_flags} + -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SH1106Display - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Station_G2.build_src_filter} - + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Station_G2.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -81,8 +103,9 @@ lib_deps = extends = Station_G2 build_flags = ${Station_G2.build_flags} + -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SH1106Display - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 @@ -91,8 +114,8 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Station_G2.build_src_filter} + - + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Station_G2.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/station_g2/target.cpp b/variants/station_g2/target.cpp index 2b19f5f0..5423af68 100644 --- a/variants/station_g2/target.cpp +++ b/variants/station_g2/target.cpp @@ -18,10 +18,7 @@ SensorManager sensors; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; -#endif - -#ifndef LORA_CR - #define LORA_CR 5 + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/station_g2/target.h b/variants/station_g2/target.h index 6d80f098..3f67af3a 100644 --- a/variants/station_g2/target.h +++ b/variants/station_g2/target.h @@ -10,6 +10,7 @@ #ifdef DISPLAY_CLASS #include + #include #endif extern StationG2Board board; @@ -19,6 +20,7 @@ extern SensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index 24584757..f87d71e0 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -84,12 +84,21 @@ public: digitalWrite(PIN_3V3_EN, LOW); #endif + // set led on and wait for button release before poweroff + #ifdef LED_PIN + digitalWrite(LED_PIN, HIGH); + #endif + #ifdef BUTTON_PIN + while(digitalRead(BUTTON_PIN)); + #endif #ifdef LED_PIN digitalWrite(LED_PIN, LOW); #endif + #ifdef BUTTON_PIN nrf_gpio_cfg_sense_input(digitalPinToInterrupt(BUTTON_PIN), NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_HIGH); #endif + sd_power_system_off(); } diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index 00974208..b1826139 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -34,9 +34,69 @@ build_src_filter = ${nrf52840_t1000e.build_src_filter} debug_tool = jlink upload_protocol = nrfutil +[env:t1000e_repeater] +extends = t1000-e +build_flags = ${t1000-e.build_flags} + -I examples/companion_radio/ui-orig + -D ADVERT_NAME='"t1000-e 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 + -D RX_BOOSTED_GAIN=true + -D RF_SWITCH_TABLE +build_src_filter = ${t1000-e.build_src_filter} + +<../examples/simple_repeater> +lib_deps = ${t1000-e.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 + +[env:t1000e_room_server] +extends = t1000-e +build_flags = ${t1000-e.build_flags} + -I examples/companion_radio/ui-orig + -D ADVERT_NAME='"t1000-e 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 + -D RX_BOOSTED_GAIN=true + -D RF_SWITCH_TABLE +build_src_filter = ${t1000-e.build_src_filter} + +<../examples/simple_room_server> +lib_deps = ${t1000-e.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 + +[env:t1000e_companion_radio_usb] +extends = t1000-e +build_flags = ${t1000-e.build_flags} + -I examples/companion_radio/ui-orig + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + -D OFFLINE_QUEUE_SIZE=256 + -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> + +<../examples/companion_radio/ui-orig/*.cpp> +lib_deps = ${t1000-e.lib_deps} + densaugeo/base64 @ ~1.4.0 + stevemarple/MicroNMEA @ ^2.0.6 + end2endzone/NonBlockingRTTTL@^1.3.0 + [env:t1000e_companion_radio_ble] extends = t1000-e build_flags = ${t1000-e.build_flags} + -I examples/companion_radio/ui-orig -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -53,7 +113,8 @@ build_src_filter = ${t1000-e.build_src_filter} + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.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 + end2endzone/NonBlockingRTTTL@^1.3.0 diff --git a/variants/t114/platformio.ini b/variants/t114/platformio.ini index dac12da9..e48ee121 100644 --- a/variants/t114/platformio.ini +++ b/variants/t114/platformio.ini @@ -30,6 +30,7 @@ build_src_filter = ${nrf52840_t114.build_src_filter} + +<../variants/t114> + + + + + lib_deps = @@ -72,6 +73,7 @@ build_flags = extends = Heltec_t114 build_flags = ${Heltec_t114.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -81,7 +83,8 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_t114.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Heltec_t114.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -90,6 +93,7 @@ lib_deps = extends = Heltec_t114 build_flags = ${Heltec_t114.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 ; -D BLE_PIN_CODE=123456 @@ -98,7 +102,8 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_t114.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Heltec_t114.lib_deps} densaugeo/base64 @ ~1.4.0 \ No newline at end of file diff --git a/variants/t114/target.cpp b/variants/t114/target.cpp index d97c03f6..d2fa6c4c 100644 --- a/variants/t114/target.cpp +++ b/variants/t114/target.cpp @@ -16,6 +16,7 @@ T114SensorManager sensors = T114SensorManager(nmea); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/t114/target.h b/variants/t114/target.h index 8831d9f7..35e86f60 100644 --- a/variants/t114/target.h +++ b/variants/t114/target.h @@ -10,6 +10,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif class T114SensorManager : public SensorManager { @@ -37,6 +38,7 @@ extern T114SensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/techo/platformio.ini b/variants/techo/platformio.ini index 8ad5ca03..76712178 100644 --- a/variants/techo/platformio.ini +++ b/variants/techo/platformio.ini @@ -21,6 +21,7 @@ build_flags = ${nrf52840_techo.build_flags} -D LORA_TX_POWER=22 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 + -D P_LORA_TX_LED=LED_GREEN build_src_filter = ${nrf52840_techo.build_src_filter} + + @@ -61,19 +62,23 @@ extends = LilyGo_Techo build_flags = ${LilyGo_Techo.build_flags} -I src/helpers/ui + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D DISPLAY_CLASS=GxEPDDisplay -D OFFLINE_QUEUE_SIZE=256 + -D UI_RECENT_LIST_SIZE=9 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_Techo.build_src_filter} + + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_Techo.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/techo/target.cpp b/variants/techo/target.cpp index 1e413531..9a10491d 100644 --- a/variants/techo/target.cpp +++ b/variants/techo/target.cpp @@ -16,6 +16,7 @@ TechoSensorManager sensors = TechoSensorManager(nmea); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/techo/target.h b/variants/techo/target.h index 7c05e742..58fba687 100644 --- a/variants/techo/target.h +++ b/variants/techo/target.h @@ -10,6 +10,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif class TechoSensorManager : public SensorManager { @@ -36,6 +37,7 @@ extern TechoSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/techo/variant.cpp b/variants/techo/variant.cpp index ad1fd560..0bad7829 100644 --- a/variants/techo/variant.cpp +++ b/variants/techo/variant.cpp @@ -24,6 +24,8 @@ void initVariant() { pinMode(LED_GREEN, OUTPUT); pinMode(LED_BLUE, OUTPUT); digitalWrite(LED_BLUE, HIGH); + digitalWrite(LED_GREEN, HIGH); + digitalWrite(LED_RED, HIGH); pinMode(PIN_TXCO, OUTPUT); digitalWrite(PIN_TXCO, HIGH); diff --git a/variants/techo/variant.h b/variants/techo/variant.h index 6aebf82f..da8d81d4 100644 --- a/variants/techo/variant.h +++ b/variants/techo/variant.h @@ -61,19 +61,15 @@ //////////////////////////////////////////////////////////////////////////////// // Builtin LEDs -#define LED_RED (34) -#define LED_GREEN (33) +#define LED_RED (13) #define LED_BLUE (14) +#define LED_GREEN (15) -#define PIN_STATUS_LED LED_GREEN -#define LED_BUILTIN LED_GREEN -#define PIN_LED LED_BUILTIN +//#define PIN_STATUS_LED LED_BLUE +#define LED_BUILTIN (-1) #define LED_PIN LED_BUILTIN #define LED_STATE_ON LOW -#define PIN_NEOPIXEL (14) -#define NEOPIXEL_NUM (2) - //////////////////////////////////////////////////////////////////////////////// // Builtin buttons diff --git a/variants/thinknode_m1/platformio.ini b/variants/thinknode_m1/platformio.ini index 2104a080..590f2100 100644 --- a/variants/thinknode_m1/platformio.ini +++ b/variants/thinknode_m1/platformio.ini @@ -68,6 +68,7 @@ extends = ThinkNode_M1 build_flags = ${ThinkNode_M1.build_flags} -I src/helpers/ui + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -76,6 +77,7 @@ build_flags = -D DISPLAY_CLASS=GxEPDDisplay -D OFFLINE_QUEUE_SIZE=256 -D PIN_BUZZER=6 + -D AUTO_SHUTDOWN_MILLIVOLTS=3300 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${ThinkNode_M1.build_src_filter} @@ -83,7 +85,9 @@ build_src_filter = ${ThinkNode_M1.build_src_filter} + + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${ThinkNode_M1.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/thinknode_m1/target.cpp b/variants/thinknode_m1/target.cpp index 19230232..2b04d7c6 100644 --- a/variants/thinknode_m1/target.cpp +++ b/variants/thinknode_m1/target.cpp @@ -16,6 +16,7 @@ ThinkNodeM1SensorManager sensors = ThinkNodeM1SensorManager(nmea); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/thinknode_m1/target.h b/variants/thinknode_m1/target.h index c938d422..eac221c3 100644 --- a/variants/thinknode_m1/target.h +++ b/variants/thinknode_m1/target.h @@ -10,6 +10,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif class ThinkNodeM1SensorManager : public SensorManager { @@ -37,6 +38,7 @@ extern ThinkNodeM1SensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/waveshare_rp2040_lora/platformio.ini b/variants/waveshare_rp2040_lora/platformio.ini index 2730734d..933c7661 100644 --- a/variants/waveshare_rp2040_lora/platformio.ini +++ b/variants/waveshare_rp2040_lora/platformio.ini @@ -61,7 +61,7 @@ build_flags = ${waveshare_rp2040_lora.build_flags} ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${waveshare_rp2040_lora.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${waveshare_rp2040_lora.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -75,7 +75,7 @@ lib_deps = ${waveshare_rp2040_lora.lib_deps} ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${waveshare_rp2040_lora.build_src_filter} -; +<../examples/companion_radio> +; +<../examples/companion_radio/*.cpp> ; lib_deps = ${waveshare_rp2040_lora.lib_deps} ; densaugeo/base64 @ ~1.4.0 @@ -90,7 +90,7 @@ lib_deps = ${waveshare_rp2040_lora.lib_deps} ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${waveshare_rp2040_lora.build_src_filter} -; +<../examples/companion_radio> +; +<../examples/companion_radio/*.cpp> ; lib_deps = ${waveshare_rp2040_lora.lib_deps} ; densaugeo/base64 @ ~1.4.0 diff --git a/variants/waveshare_rp2040_lora/target.cpp b/variants/waveshare_rp2040_lora/target.cpp index 895f6db2..7bc1d043 100644 --- a/variants/waveshare_rp2040_lora/target.cpp +++ b/variants/waveshare_rp2040_lora/target.cpp @@ -12,19 +12,9 @@ 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 - SPI1.setSCK(P_LORA_SCLK); SPI1.setTX(P_LORA_MOSI); SPI1.setRX(P_LORA_MISO); @@ -34,30 +24,8 @@ bool radio_init() { SPI1.begin(false); - 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 + //passing NULL skips init of SPI + return radio.std_init(NULL); } uint32_t radio_get_rng_seed() { diff --git a/variants/wio-e5-mini/platformio.ini b/variants/wio-e5-mini/platformio.ini index 93508d8e..3d98d93e 100644 --- a/variants/wio-e5-mini/platformio.ini +++ b/variants/wio-e5-mini/platformio.ini @@ -37,11 +37,13 @@ build_src_filter = ${lora_e5_mini.build_src_filter} [env:wio-e5-mini_companion_radio_usb] extends = lora_e5_mini build_flags = ${lora_e5_mini.build_flags} + -I examples/companion_radio/ui-orig -D LORA_TX_POWER=22 -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${lora_e5_mini.build_src_filter} +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${lora_e5_mini.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.h b/variants/wio-tracker-l1/WioTrackerL1Board.h index 03aef79c..f04b673f 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.h +++ b/variants/wio-tracker-l1/WioTrackerL1Board.h @@ -38,5 +38,9 @@ public: NVIC_SystemReset(); } + void powerOff() override { + sd_power_system_off(); + } + bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 380ff90f..ddf7cf47 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -56,13 +56,16 @@ lib_deps = ${WioTrackerL1.lib_deps} [env:WioTrackerL1_companion_radio_usb] extends = WioTrackerL1 build_flags = ${WioTrackerL1.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D DISPLAY_CLASS=SH1106Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${WioTrackerL1.build_src_filter} - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> + + lib_deps = ${WioTrackerL1.lib_deps} @@ -73,6 +76,7 @@ lib_deps = ${WioTrackerL1.lib_deps} [env:WioTrackerL1_companion_radio_ble] extends = WioTrackerL1 build_flags = ${WioTrackerL1.build_flags} + -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -84,8 +88,10 @@ build_flags = ${WioTrackerL1.build_flags} -D PIN_BUZZER=12 build_src_filter = ${WioTrackerL1.build_src_filter} + - +<../examples/companion_radio> + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${WioTrackerL1.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 diff --git a/variants/wio-tracker-l1/target.cpp b/variants/wio-tracker-l1/target.cpp index 0809e19e..349d73b4 100644 --- a/variants/wio-tracker-l1/target.cpp +++ b/variants/wio-tracker-l1/target.cpp @@ -16,6 +16,9 @@ WioTrackerL1SensorManager sensors = WioTrackerL1SensorManager(nmea); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); + MomentaryButton joystick_left(JOYSTICK_LEFT, 1000, true); + MomentaryButton joystick_right(JOYSTICK_RIGHT, 1000, true); #endif bool radio_init() { diff --git a/variants/wio-tracker-l1/target.h b/variants/wio-tracker-l1/target.h index ab42b7b5..6f5da7c6 100644 --- a/variants/wio-tracker-l1/target.h +++ b/variants/wio-tracker-l1/target.h @@ -9,6 +9,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif #include @@ -38,6 +39,9 @@ extern AutoDiscoverRTCClock rtc_clock; extern WioTrackerL1SensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; + extern MomentaryButton joystick_left; + extern MomentaryButton joystick_right; #endif bool radio_init(); diff --git a/variants/wio-tracker-l1/variant.h b/variants/wio-tracker-l1/variant.h index 094f8edf..af01177e 100644 --- a/variants/wio-tracker-l1/variant.h +++ b/variants/wio-tracker-l1/variant.h @@ -91,12 +91,12 @@ #define PIN_GPS_EN (18) // QSPI Pins -#define PIN_QSPI_SCK (21) -#define PIN_QSPI_CS (22) -#define PIN_QSPI_IO0 (23) -#define PIN_QSPI_IO1 (24) -#define PIN_QSPI_IO2 (25) -#define PIN_QSPI_IO3 (26) +#define PIN_QSPI_SCK (19) +#define PIN_QSPI_CS (20) +#define PIN_QSPI_IO0 (21) +#define PIN_QSPI_IO1 (22) +#define PIN_QSPI_IO2 (23) +#define PIN_QSPI_IO3 (24) #define EXTERNAL_FLASH_DEVICES P25Q16H #define EXTERNAL_FLASH_USE_QSPI diff --git a/variants/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index 3e4bfdb4..de79fb10 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -19,28 +19,6 @@ build_flags = build_src_filter = ${esp32_base.build_src_filter} +<../variants/xiao_c3> -[Xiao_esp32_C3_custom] -extends = esp32_base -board = seeed_xiao_esp32c3 -build_flags = - ${esp32_base.build_flags} - -I variants/xiao_c3 - -D ESP32_CPU_FREQ=80 - -D LORA_TX_BOOST_PIN=D3 - -D P_LORA_TX_LED=D5 - -D PIN_VBAT_READ=D0 - -D P_LORA_DIO_1=D2 - -D P_LORA_NSS=D4 - -D P_LORA_RESET=RADIOLIB_NC - -D P_LORA_BUSY=D1 - -D PIN_BOARD_SDA=D6 - -D PIN_BOARD_SCL=D7 - -D SX126X_DIO2_AS_RF_SWITCH=true - -D SX126X_DIO3_TCXO_VOLTAGE=1.8 - -D SX126X_CURRENT_LIMIT=140 -build_src_filter = ${esp32_base.build_src_filter} - +<../variants/xiao_c3> - [env:Xiao_C3_Repeater_sx1262] extends = Xiao_esp32_C3 build_src_filter = ${Xiao_esp32_C3.build_src_filter} @@ -61,11 +39,12 @@ build_flags = lib_deps = ${Xiao_esp32_C3.lib_deps} ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 [env:Xiao_C3_companion_radio_ble] extends = Xiao_esp32_C3 build_src_filter = ${Xiao_esp32_C3.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + build_flags = ${Xiao_esp32_C3.build_flags} @@ -73,7 +52,7 @@ build_flags = -D WRAPPER_CLASS=CustomSX1262Wrapper -D SX126X_RX_BOOSTED_GAIN=1 -D LORA_TX_POWER=22 - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 @@ -88,7 +67,7 @@ lib_deps = [env:Xiao_C3_companion_radio_usb] extends = Xiao_esp32_C3 build_src_filter = ${Xiao_esp32_C3.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> + build_flags = ${Xiao_esp32_C3.build_flags} @@ -96,7 +75,7 @@ build_flags = -D WRAPPER_CLASS=CustomSX1262Wrapper -D SX126X_RX_BOOSTED_GAIN=1 -D LORA_TX_POWER=22 - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 @@ -106,44 +85,3 @@ lib_deps = ${Xiao_esp32_C3.lib_deps} ${esp32_ota.lib_deps} densaugeo/base64 @ ~1.4.0 - -[env:Xiao_C3_Repeater_sx1262_custom] -extends = Xiao_esp32_C3_custom -build_src_filter = ${Xiao_esp32_C3_custom.build_src_filter} - +<../examples/simple_repeater/main.cpp> -build_flags = - ${Xiao_esp32_C3_custom.build_flags} - -D RADIO_CLASS=CustomSX1262 - -D WRAPPER_CLASS=CustomSX1262Wrapper - -D SX126X_RX_BOOSTED_GAIN=1 - -D LORA_TX_POWER=22 - -D ADVERT_NAME='"Xiao 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 -lib_deps = - ${Xiao_esp32_C3_custom.lib_deps} - ${esp32_ota.lib_deps} - -[env:Xiao_C3_Repeater_sx1268_custom] -extends = Xiao_esp32_C3_custom -build_src_filter = ${Xiao_esp32_C3_custom.build_src_filter} - +<../examples/simple_repeater/main.cpp> -build_flags = - ${Xiao_esp32_C3_custom.build_flags} - -D RADIO_CLASS=CustomSX1268 - -D WRAPPER_CLASS=CustomSX1268Wrapper - -D LORA_TX_POWER=22 - -D ADVERT_NAME='"Xiao 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 -lib_deps = - ${Xiao_esp32_C3_custom.lib_deps} - ${esp32_ota.lib_deps} \ No newline at end of file diff --git a/variants/xiao_c6/target.cpp b/variants/xiao_c6/XiaoC6Board.cpp similarity index 98% rename from variants/xiao_c6/target.cpp rename to variants/xiao_c6/XiaoC6Board.cpp index caca57bc..555fed62 100644 --- a/variants/xiao_c6/target.cpp +++ b/variants/xiao_c6/XiaoC6Board.cpp @@ -1,7 +1,7 @@ #include #include "target.h" -ESP32Board board; +XiaoC6Board board; #if defined(P_LORA_SCLK) static SPIClass spi(0); @@ -47,3 +47,5 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } + + diff --git a/variants/xiao_c6/XiaoC6Board.h b/variants/xiao_c6/XiaoC6Board.h new file mode 100644 index 00000000..86c3475c --- /dev/null +++ b/variants/xiao_c6/XiaoC6Board.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +class XiaoC6Board : public ESP32Board { +public: + void begin() { + ESP32Board::begin(); + +#ifdef USE_XIAO_ESP32C6_EXTERNAL_ANTENNA +// Connect an external antenna to your XIAO ESP32C6 otherwise, it may be damaged! + pinMode(3, OUTPUT); + digitalWrite(3, LOW); // Activate RF switch control + + delay(100); + + pinMode(14, OUTPUT); + digitalWrite(14, HIGH); // Use external antenna +#endif + } + + const char* getManufacturerName() const override { + return "Xiao C6"; + } +}; + + diff --git a/variants/xiao_c6/platformio.ini b/variants/xiao_c6/platformio.ini index bc1c789b..24a17e06 100644 --- a/variants/xiao_c6/platformio.ini +++ b/variants/xiao_c6/platformio.ini @@ -28,6 +28,7 @@ build_flags = -D DISABLE_WIFI_OTA=1 build_src_filter = ${esp32c6_base.build_src_filter} +<../variants/xiao_c6> + + [env:Xiao_C6_Repeater] extends = Xiao_C6 @@ -49,7 +50,7 @@ lib_deps = [env:Xiao_C6_companion_radio_ble] extends = Xiao_C6 build_flags = ${Xiao_C6.build_flags} - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 @@ -61,7 +62,124 @@ build_flags = ${Xiao_C6.build_flags} build_src_filter = ${Xiao_C6.build_src_filter} + - - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${Xiao_C6.lib_deps} densaugeo/base64 @ ~1.4.0 + +; Meshimi variant +[Meshimi] +extends = Xiao_C6 +board_build.partitions = max_app_4MB.csv +build_flags = + ${Xiao_C6.build_flags} + -D P_LORA_TX_LED=15 + -D P_LORA_SCLK=19 + -D P_LORA_MISO=20 + -D P_LORA_MOSI=18 + -D P_LORA_NSS=21 + -D P_LORA_DIO_1=7 + -D P_LORA_BUSY=6 + -D P_LORA_RESET=2 + -D PIN_BOARD_SDA=22 + -D PIN_BOARD_SCL=23 + -D SX126X_RXEN=4 + -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 USE_XIAO_ESP32C6_EXTERNAL_ANTENNA=1 + +[env:Meshimi_Repeater] +extends = Meshimi +build_src_filter = ${Meshimi.build_src_filter} + +<../examples/simple_repeater/main.cpp> +build_flags = + ${Meshimi.build_flags} + -D ADVERT_NAME='"Meshimi Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 +lib_deps = + ${Meshimi.lib_deps} + +[env:Meshimi_companion_radio_ble] +extends = Meshimi +build_flags = ${Meshimi.build_flags} + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + -D ENABLE_PRIVATE_KEY_IMPORT=1 + -D ENABLE_PRIVATE_KEY_EXPORT=1 +build_src_filter = ${Meshimi.build_src_filter} + + + - + +<../examples/companion_radio/*.cpp> +lib_deps = + ${Meshimi.lib_deps} + densaugeo/base64 @ ~1.4.0 + +; WHY2025 badge variant +; requires soldering 2 pins between the esp32-C6 and the lora chip as shown here: https://wiki.why2025.org/Project:Meshtastic_on_the_WHY2025_badge +; also requires wiping the esp32-P4 +[WHY2025_badge] +extends = Xiao_C6 +board_build.partitions = default_8MB.csv +board_upload.flash_size = 8MB +board_upload.maximum_size = 8388608 +build_flags = + ${Xiao_C6.build_flags} + -D P_LORA_SCLK=6 + -D P_LORA_MISO=2 + -D P_LORA_MOSI=7 + -D P_LORA_NSS=4 + -D P_LORA_DIO_1=5 + -D P_LORA_BUSY=11 + -D P_LORA_RESET=1 + -D SX126X_TXEN=3 + -UPIN_BOARD_SDA + -UPIN_BOARD_SCL + -UP_LORA_TX_LED + -USX126X_RXEN + -USX126X_DIO2_AS_RF_SWITCH + -USX126X_DIO3_TCXO_VOLTAGE + +[env:WHY2025_badge_Repeater] +extends = WHY2025_badge +build_src_filter = ${WHY2025_badge.build_src_filter} + +<../examples/simple_repeater/main.cpp> +build_flags = + ${WHY2025_badge.build_flags} + -D ADVERT_NAME='"WHY2025 Badge 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 +lib_deps = + ${WHY2025_badge.lib_deps} +; ${esp32_ota.lib_deps} + +[env:WHY2025_badge_companion_radio_ble] +extends = WHY2025_badge +build_flags = ${WHY2025_badge.build_flags} + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + -D ENABLE_PRIVATE_KEY_IMPORT=1 + -D ENABLE_PRIVATE_KEY_EXPORT=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${WHY2025_badge.build_src_filter} + + + - + +<../examples/companion_radio/*.cpp> +lib_deps = + ${WHY2025_badge.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/xiao_c6/target.h b/variants/xiao_c6/target.h index c26d5958..0fbb0bb2 100644 --- a/variants/xiao_c6/target.h +++ b/variants/xiao_c6/target.h @@ -2,13 +2,14 @@ #define RADIOLIB_STATIC_ONLY 1 #include +#include #include #include #include #include #include -extern ESP32Board board; +extern XiaoC6Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index bba3e632..fd4c362b 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -68,7 +68,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Xiao_nrf52.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${Xiao_nrf52.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -83,7 +83,7 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Xiao_nrf52.build_src_filter} + - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${Xiao_nrf52.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/xiao_rp2040/platformio.ini b/variants/xiao_rp2040/platformio.ini index 960fdbba..619350ec 100644 --- a/variants/xiao_rp2040/platformio.ini +++ b/variants/xiao_rp2040/platformio.ini @@ -58,7 +58,7 @@ build_flags = ${Xiao_rp2040.build_flags} ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Xiao_rp2040.build_src_filter} - +<../examples/companion_radio> + +<../examples/companion_radio/*.cpp> lib_deps = ${Xiao_rp2040.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -72,7 +72,7 @@ lib_deps = ${Xiao_rp2040.lib_deps} ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${Xiao_rp2040.build_src_filter} -; +<../examples/companion_radio> +; +<../examples/companion_radio/*.cpp> ; lib_deps = ${Xiao_rp2040.lib_deps} ; densaugeo/base64 @ ~1.4.0 @@ -87,7 +87,7 @@ lib_deps = ${Xiao_rp2040.lib_deps} ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${Xiao_rp2040.build_src_filter} -; +<../examples/companion_radio> +; +<../examples/companion_radio/*.cpp> ; lib_deps = ${Xiao_rp2040.lib_deps} ; densaugeo/base64 @ ~1.4.0 diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 4d6fed88..b4f25e53 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -65,7 +65,7 @@ lib_deps = extends = Xiao_S3_WIO build_flags = ${Xiao_S3_WIO.build_flags} - -D MAX_CONTACTS=100 + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -79,7 +79,8 @@ lib_deps = extends = Xiao_S3_WIO build_flags = ${Xiao_S3_WIO.build_flags} - -D MAX_CONTACTS=100 + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D DISPLAY_CLASS=SSD1306Display @@ -90,7 +91,9 @@ build_flags = build_src_filter = ${Xiao_S3_WIO.build_src_filter} + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Xiao_S3_WIO.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -100,15 +103,21 @@ lib_deps = extends = Xiao_S3_WIO build_flags = ${Xiao_S3_WIO.build_flags} - -D MAX_CONTACTS=100 + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 + -D DISPLAY_CLASS=SSD1306Display -D SERIAL_TX=D6 -D SERIAL_RX=D7 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Xiao_S3_WIO.build_src_filter} + + + - +<../examples/companion_radio> + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Xiao_S3_WIO.lib_deps} densaugeo/base64 @ ~1.4.0 + adafruit/Adafruit SSD1306 @ ^2.5.13 diff --git a/variants/xiao_s3_wio/target.cpp b/variants/xiao_s3_wio/target.cpp index 4b2b059f..ed8584ff 100644 --- a/variants/xiao_s3_wio/target.cpp +++ b/variants/xiao_s3_wio/target.cpp @@ -18,6 +18,7 @@ SensorManager sensors; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/xiao_s3_wio/target.h b/variants/xiao_s3_wio/target.h index b768b2b0..f184c757 100644 --- a/variants/xiao_s3_wio/target.h +++ b/variants/xiao_s3_wio/target.h @@ -9,6 +9,7 @@ #include #ifdef DISPLAY_CLASS #include + #include #endif extern ESP32Board board; @@ -18,6 +19,7 @@ extern SensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif bool radio_init();