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/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index a7c0333c..901e3db5 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 @@ -166,12 +168,12 @@ void MyMesh::updateContactFromFrame(ContactInfo &contact, uint32_t& last_mod, co 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 (i + 4 >= len) { + if (len >= i + 4) { memcpy(&last_mod, &frame[i], 4); } } @@ -437,6 +439,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); @@ -527,6 +532,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); @@ -589,7 +627,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe _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; @@ -661,6 +699,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); @@ -1097,6 +1136,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); } @@ -1130,7 +1172,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; @@ -1150,7 +1192,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; @@ -1162,6 +1204,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); @@ -1171,7 +1242,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; @@ -1207,7 +1278,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; diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index e2e96ff4..01889223 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -101,6 +101,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,6 +134,10 @@ 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); @@ -161,7 +166,7 @@ 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; diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index a28df7a4..7ad0e64a 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -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 { @@ -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); @@ -715,7 +719,7 @@ public: *dp = 0; // null terminator } - const uint8_t* getSelfIdPubKey() override { return self_id.pub_key; } + mesh::LocalIdentity& getSelfId() override { return self_id; } void clearStats() override { radio_driver.resetStats(); @@ -778,7 +782,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..34b94ad2 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -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); @@ -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..ce36e0c2 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 } } diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 8f6e3bc3..0d87617b 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -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; 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/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..2abb4f7c 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -206,6 +206,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 +238,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 { @@ -285,6 +290,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..92deb718 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -43,7 +43,7 @@ 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 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 +53,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/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index c95b1828..0f3289b8 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,12 @@ 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_MLX90614 #define TELEM_MLX90614_ADDRESS 0x5A // MLX90614 IR temperature sensor I2C address #include @@ -130,6 +142,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 +192,16 @@ 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_MLX90614 if (MLX90614.begin(TELEM_MLX90614_ADDRESS, TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found MLX90614 at address: %02X", TELEM_MLX90614_ADDRESS); @@ -219,7 +256,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 +270,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 +314,15 @@ 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_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..bb0fd2b9 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -13,10 +13,12 @@ protected: bool BMP280_initialized = false; bool INA3221_initialized = false; bool INA219_initialized = false; + bool INA260_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/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 67415ae9..97eef510 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -223,6 +223,20 @@ 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> +lib_deps = + ${Heltec_lora32_v3.lib_deps} + densaugeo/base64 @ ~1.4.0 + [env:Heltec_WSL3_sensor] extends = Heltec_lora32_v3 build_flags = 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..ba4a8e2f 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -26,6 +26,8 @@ build_flags = ${nrf52_base.build_flags} -D ENV_INCLUDE_LPS22HB=1 -D ENV_INCLUDE_INA3221=1 -D ENV_INCLUDE_INA219=1 + -D ENV_INCLUDE_INA260=1 + -D ENV_INCLUDE_SHT4X=1 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak4631> + @@ -40,7 +42,9 @@ lib_deps = 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 + adafruit/Adafruit INA260 Library @ ^1.5.3 + sparkfun/SparkFun u-blox GNSS Arduino Library @ ^2.2.27 + sensirion/Sensirion I2C SHT4x @ ^1.1.2 [env:RAK_4631_Repeater] extends = rak4631 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