From 832f73f2ceaced123f97885b8ef818f5c82d0072 Mon Sep 17 00:00:00 2001 From: John Erickson Date: Wed, 4 Mar 2026 14:01:43 -0800 Subject: [PATCH] SEN0658 weather station --- examples/companion_radio/MyMesh.cpp | 14 +- examples/companion_radio/ui-new/UITask.cpp | 8 +- examples/companion_radio/ui-orig/UITask.cpp | 4 +- examples/simple_repeater/MyMesh.cpp | 6 +- examples/simple_repeater/MyMesh.h | 3 +- examples/simple_repeater/main.cpp | 4 +- examples/simple_room_server/MyMesh.cpp | 4 +- examples/simple_room_server/MyMesh.h | 2 +- examples/simple_room_server/main.cpp | 2 +- examples/simple_sensor/SensorMesh.cpp | 12 +- examples/simple_sensor/SensorMesh.h | 2 +- examples/simple_sensor/main.cpp | 2 +- src/EndianTypes.h | 50 ++ src/MeshCore.h | 4 + src/helpers/CommonCLI.cpp | 34 +- src/helpers/CommonCLI.h | 8 +- src/helpers/SensorManager.h | 15 +- src/helpers/sensors/DFROBOT_SEN0658.cpp | 493 ++++++++++++++++++ src/helpers/sensors/DFROBOT_SEN0658.h | 99 ++++ .../sensors/EnvironmentSensorManager.cpp | 86 ++- .../sensors/EnvironmentSensorManager.h | 9 +- variants/heltec_mesh_solar/target.cpp | 8 +- variants/heltec_mesh_solar/target.h | 4 +- variants/heltec_tracker/target.cpp | 8 +- variants/heltec_tracker/target.h | 4 +- variants/meshadventurer/target.cpp | 8 +- variants/meshadventurer/target.h | 4 +- variants/nano_g2_ultra/target.cpp | 8 +- variants/nano_g2_ultra/target.h | 4 +- variants/rak4631/platformio.ini | 22 + variants/rak4631/variant.cpp | 5 + variants/t1000-e/target.cpp | 8 +- variants/t1000-e/target.h | 4 +- variants/thinknode_m1/target.cpp | 8 +- variants/thinknode_m1/target.h | 4 +- variants/wio-e5-mini/target.cpp | 2 +- variants/wio-e5-mini/target.h | 2 +- 37 files changed, 854 insertions(+), 110 deletions(-) create mode 100644 src/EndianTypes.h create mode 100644 src/helpers/sensors/DFROBOT_SEN0658.cpp create mode 100644 src/helpers/sensors/DFROBOT_SEN0658.h diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 7412e75102..131626ba0d 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1775,15 +1775,11 @@ void MyMesh::handleCmdFrame(size_t len) { } else if (cmd_frame[0] == CMD_GET_CUSTOM_VARS) { out_frame[0] = RESP_CODE_CUSTOM_VARS; char *dp = (char *)&out_frame[1]; - for (int i = 0; i < sensors.getNumSettings() && dp - (char *)&out_frame[1] < 140; i++) { - if (i > 0) { - *dp++ = ','; - } - strcpy(dp, sensors.getSettingName(i)); - dp = strchr(dp, 0); - *dp++ = ':'; - strcpy(dp, sensors.getSettingValue(i)); - dp = strchr(dp, 0); + char *end = (char *)out_frame + sizeof(out_frame); + for (int i = 0; i < sensors.getNumSettings(); i++) { + if (i > 0) *dp++ = ','; + dp += snprintf(dp, end - dp, "%s:", sensors.getSettingName(i)); + dp += sensors.getSettingValue(i, dp, end - dp); } _serial->writeFrame(out_frame, dp - (char *)out_frame); } else if (cmd_frame[0] == CMD_SET_CUSTOM_VAR && len >= 4) { diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 6f363d7f96..13ddff88d9 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -884,7 +884,9 @@ bool UITask::getGPSState() { int num = _sensors->getNumSettings(); for (int i = 0; i < num; i++) { if (strcmp(_sensors->getSettingName(i), "gps") == 0) { - return !strcmp(_sensors->getSettingValue(i), "1"); + char val[4]; + _sensors->getSettingValue(i, val, sizeof(val)); + return !strcmp(val, "1"); } } } @@ -897,7 +899,9 @@ void UITask::toggleGPS() { int num = _sensors->getNumSettings(); for (int i = 0; i < num; i++) { if (strcmp(_sensors->getSettingName(i), "gps") == 0) { - if (strcmp(_sensors->getSettingValue(i), "1") == 0) { + char val[4]; + _sensors->getSettingValue(i, val, sizeof(val)); + if (strcmp(val, "1") == 0) { _sensors->setSettingValue("gps", "0"); _node_prefs->gps_enabled = 0; notify(UIEventType::ack); diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 12a374d91d..2b6fb52ba5 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -421,7 +421,9 @@ void UITask::handleButtonQuadruplePress() { int num = _sensors->getNumSettings(); for (int i = 0; i < num; i++) { if (strcmp(_sensors->getSettingName(i), "gps") == 0) { - if (strcmp(_sensors->getSettingValue(i), "1") == 0) { + char val[4]; + _sensors->getSettingValue(i, val, sizeof(val)); + if (strcmp(val, "1") == 0) { _sensors->setSettingValue("gps", "0"); notify(UIEventType::ack); sprintf(_alert, "GPS: Disabled"); diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 1f68c6f2a0..104ec48f1c 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -92,9 +92,7 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr if (data[0] == 0) { // blank password, just check if sender is in ACL client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); if (client == NULL) { - #if MESH_DEBUG MESH_DEBUG_PRINTLN("Login, sender not in ACL"); - #endif } } if (client == NULL) { @@ -104,9 +102,7 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr } else if (strcmp((char *)data, _prefs.guest_password) == 0) { // check guest password perms = PERM_ACL_GUEST; } else { -#if MESH_DEBUG MESH_DEBUG_PRINTLN("Invalid password: %s", data); -#endif return 0; } @@ -1165,7 +1161,7 @@ void MyMesh::clearStats() { ((SimpleMeshTables *)getTables())->resetStats(); } -void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) { +void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char reply[MAX_CLI_REPLY_LEN]) { if (region_load_active) { if (StrHelper::isBlank(command)) { // empty/blank line, signal to terminate 'load' operation region_map = temp_map; // copy over the temp instance as new current map diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 8ed0317e69..31a0dc88df 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -222,8 +222,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void saveIdentity(const mesh::LocalIdentity& new_id) override; void clearStats() override; - - void handleCommand(uint32_t sender_timestamp, char* command, char* reply); + void handleCommand(uint32_t sender_timestamp, char* command, char reply[MAX_CLI_REPLY_LEN]); void loop(); #if defined(WITH_BRIDGE) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 297337ab5c..aaa4907252 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -91,7 +91,7 @@ void setup() { command[0] = 0; - sensors.begin(); + sensors.begin(fs); the_mesh.begin(fs); @@ -125,7 +125,7 @@ void loop() { if (len > 0 && command[len - 1] == '\r') { // received complete line Serial.print('\n'); command[len - 1] = 0; // replace newline with C string null terminator - char reply[160]; + char reply[MAX_CLI_REPLY_LEN]; the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial! if (reply[0]) { Serial.print(" -> "); Serial.println(reply); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 2fb80be24c..8b58f9f2e6 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -317,9 +317,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m if (data[8] == 0) { // blank password, just check if sender is in ACL client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); if (client == NULL) { - #if MESH_DEBUG MESH_DEBUG_PRINTLN("Login, sender not in ACL"); - #endif } } if (client == NULL) { @@ -850,7 +848,7 @@ void MyMesh::formatPacketStatsReply(char *reply) { getNumRecvFlood(), getNumRecvDirect()); } -void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) { +void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char reply[MAX_CLI_REPLY_LEN]) { if (region_load_active) { if (StrHelper::isBlank(command)) { // empty/blank line, signal to terminate 'load' operation region_map = temp_map; // copy over the temp instance as new current map diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 1b35ae95a1..22ba34dea6 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -220,6 +220,6 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void saveIdentity(const mesh::LocalIdentity& new_id) override; void clearStats() override; - void handleCommand(uint32_t sender_timestamp, char* command, char* reply); + void handleCommand(uint32_t sender_timestamp, char* command, char reply[MAX_CLI_REPLY_LEN]); void loop(); }; diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index a3798b2175..1040935b19 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -100,7 +100,7 @@ void loop() { if (len > 0 && command[len - 1] == '\r') { // received complete line command[len - 1] = 0; // replace newline with C string null terminator - char reply[160]; + char reply[MAX_CLI_REPLY_LEN]; the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial! if (reply[0]) { Serial.print(" -> "); Serial.println(reply); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 879fcbf026..9d3b919c87 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -332,16 +332,12 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* if (data[0] == 0) { // blank password, just check if sender is in ACL client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); if (client == NULL) { - #if MESH_DEBUG MESH_DEBUG_PRINTLN("Login, sender not in ACL"); - #endif return 0; } } else { if (strcmp((char *) data, _prefs.password) != 0) { // check for valid admin password - #if MESH_DEBUG MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]); - #endif return 0; } @@ -376,7 +372,7 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* return 13; // reply length } -void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* reply) { +void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char reply[MAX_CLI_REPLY_LEN]) { while (*command == ' ') command++; // skip leading spaces if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI) @@ -615,10 +611,8 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i bool SensorMesh::handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint8_t 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 + MESH_DEBUG_PRINT_HEX(from.id.pub_key, PUB_KEY_SIZE); + MESH_DEBUG_PRINT_RAW(": %s\n", data); return false; } diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 424b16c175..90b0c75204 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -51,7 +51,7 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables); void begin(FILESYSTEM* fs); void loop(); - void handleCommand(uint32_t sender_timestamp, char* command, char* reply); + void handleCommand(uint32_t sender_timestamp, char* command, char reply[MAX_CLI_REPLY_LEN]); // CommonCLI callbacks const char* getFirmwareVer() override { return FIRMWARE_VERSION; } diff --git a/examples/simple_sensor/main.cpp b/examples/simple_sensor/main.cpp index cace67a08c..bf95d641e2 100644 --- a/examples/simple_sensor/main.cpp +++ b/examples/simple_sensor/main.cpp @@ -132,7 +132,7 @@ void loop() { if (len > 0 && command[len - 1] == '\r') { // received complete line command[len - 1] = 0; // replace newline with C string null terminator - char reply[160]; + char reply[MAX_CLI_REPLY_LEN]; the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial! if (reply[0]) { Serial.print(" -> "); Serial.println(reply); diff --git a/src/EndianTypes.h b/src/EndianTypes.h new file mode 100644 index 0000000000..708f20c9c0 --- /dev/null +++ b/src/EndianTypes.h @@ -0,0 +1,50 @@ +#pragma once + +#include + +/** + * @brief Endianness-safe integer types for network/protocol communication + * + * These types automatically handle byte order conversion between wire format + * and host format, preventing endianness bugs in protocol implementations. + */ + +// Big-endian 32-bit unsigned integer +struct be_uint32_t { + uint32_t wireBytes; +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + operator uint32_t() const { return __builtin_bswap32(wireBytes); } +#else + operator uint32_t() const { return wireBytes; } +#endif +} __attribute__((packed)); + +// Big-endian 16-bit unsigned integer +struct be_uint16_t { + uint16_t wireBytes; +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + operator uint16_t() const { return __builtin_bswap16(wireBytes); } +#else + operator uint16_t() const { return wireBytes; } +#endif +} __attribute__((packed)); + +// Little-endian 16-bit unsigned integer +struct le_uint16_t { + uint16_t wireBytes; +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + operator uint16_t() const { return wireBytes; } +#else + operator uint16_t() const { return __builtin_bswap16(wireBytes); } +#endif +} __attribute__((packed)); + +// Little-endian 32-bit unsigned integer +struct le_uint32_t { + uint32_t wireBytes; +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + operator uint32_t() const { return wireBytes; } +#else + operator uint32_t() const { return __builtin_bswap32(wireBytes); } +#endif +} __attribute__((packed)); \ No newline at end of file diff --git a/src/MeshCore.h b/src/MeshCore.h index b4c57faf32..5cdf28417a 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -25,9 +25,13 @@ #include #define MESH_DEBUG_PRINT(F, ...) Serial.printf("DEBUG: " F, ##__VA_ARGS__) #define MESH_DEBUG_PRINTLN(F, ...) Serial.printf("DEBUG: " F "\n", ##__VA_ARGS__) + #define MESH_DEBUG_PRINT_RAW(F, ...) Serial.printf(F, ##__VA_ARGS__) + #define MESH_DEBUG_PRINT_HEX(src,len) mesh::Utils::printHex(Serial, src, len) #else #define MESH_DEBUG_PRINT(...) {} #define MESH_DEBUG_PRINTLN(...) {} + #define MESH_DEBUG_PRINT_RAW(F, ...) {} + #define MESH_DEBUG_PRINT_HEX(src,len) {} #endif #if BRIDGE_DEBUG && ARDUINO diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index cae8bfd8a4..9c11af5518 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -208,7 +208,8 @@ uint8_t CommonCLI::buildAdvertData(uint8_t node_type, uint8_t* app_data) { } } -void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* reply) { +void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char reply[MAX_CLI_REPLY_LEN]) { + char *reply_end= reply + MAX_CLI_REPLY_LEN; if (memcmp(command, "poweroff", 8) == 0 || memcmp(command, "shutdown", 8) == 0) { _board->powerOff(); // doesn't return } else if (memcmp(command, "reboot", 6) == 0) { @@ -304,8 +305,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re sprintf(reply, "%s", _board->getManufacturerName()); } else if (memcmp(command, "sensor get ", 11) == 0) { const char* key = command + 11; - const char* val = _sensors->getSettingByKey(key); - if (val != NULL) { + char val[MAX_SETTING_BUF_LEN]; + if (_sensors->getSettingByKey(key, val, sizeof(val)) > 0) { sprintf(reply, "> %s", val); } else { strcpy(reply, "null"); @@ -328,22 +329,20 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re if (strlen(command) > 11) { start = _atoi(command+12); } + const char *continue_fmt = "... next:%d"; if (start >= end) { - strcpy(reply, "no custom var"); + strlcpy(reply, "no custom var", reply_end - reply); } else { - sprintf(dp, "%d vars\n", end); - dp = strchr(dp, 0); + const char *vars_end = reply_end - strlen(continue_fmt) - 1; // leave space for continue message if needed + dp += snprintf(dp, vars_end - dp, "%d vars\n", end); int i; - for (i = start; i < end && (dp-reply < 134); i++) { - sprintf(dp, "%s=%s\n", - _sensors->getSettingName(i), - _sensors->getSettingValue(i)); - dp = strchr(dp, 0); + for (i = start; i < end && dp < vars_end; i++) { + dp += snprintf(dp, vars_end - dp, "%s=", _sensors->getSettingName(i)); + dp += _sensors->getSettingValue(i, dp, vars_end - dp); + dp += snprintf(dp, vars_end - dp, "\n"); } if (i < end) { - sprintf(dp, "... next:%d", i); - } else { - *(dp-1) = 0; // remove last CR + snprintf(dp, reply_end - dp, continue_fmt, i); } } } else if (memcmp(command, "region", 6) == 0) { @@ -414,7 +413,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re bool enabled = l->isEnabled(); // is EN pin on ? bool fix = l->isValid(); // has fix ? int sats = l->satellitesCount(); - bool active = !strcmp(_sensors->getSettingByKey("gps"), "1"); + char gpsVal[4]; + bool active = _sensors->getSettingByKey("gps", gpsVal, sizeof(gpsVal)) > 0 && !strcmp(gpsVal, "1"); if (enabled) { sprintf(reply, "on, %s, %s, %d sats", active?"active":"deactivated", @@ -464,7 +464,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re } } -void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* reply) { +void CommonCLI::handleSetCmd(uint32_t sender_timestamp, const char* command, char* reply) { const char* config = &command[4]; if (memcmp(config, "dutycycle ", 10) == 0) { float dc = atof(&config[10]); @@ -733,7 +733,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep } } -void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* reply) { +void CommonCLI::handleGetCmd(uint32_t sender_timestamp, const char* command, char* reply) { const char* config = &command[4]; if (memcmp(config, "dutycycle", 9) == 0) { float dc = 100.0f / (_prefs->airtime_factor + 1.0f); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index ffdc7c6536..19c154240e 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -6,6 +6,8 @@ #include #include +#define MAX_CLI_REPLY_LEN 160 + #if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) #define WITH_BRIDGE #endif @@ -127,8 +129,8 @@ class CommonCLI { void loadPrefsInt(FILESYSTEM* _fs, const char* filename); void handleRegionCmd(char* command, char* reply); - void handleGetCmd(uint32_t sender_timestamp, char* command, char* reply); - void handleSetCmd(uint32_t sender_timestamp, char* command, char* reply); + void handleGetCmd(uint32_t sender_timestamp, const char* command, char* reply); + void handleSetCmd(uint32_t sender_timestamp, const char* command, char* reply); public: CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, SensorManager& sensors, RegionMap& region_map, ClientACL& acl, NodePrefs* prefs, CommonCLICallbacks* callbacks) @@ -136,6 +138,6 @@ class CommonCLI { void loadPrefs(FILESYSTEM* _fs); void savePrefs(FILESYSTEM* _fs); - void handleCommand(uint32_t sender_timestamp, char* command, char* reply); + void handleCommand(uint32_t sender_timestamp, char* command, char reply[MAX_CLI_REPLY_LEN]); uint8_t buildAdvertData(uint8_t node_type, uint8_t* app_data); }; diff --git a/src/helpers/SensorManager.h b/src/helpers/SensorManager.h index 89a174c228..80e037b627 100644 --- a/src/helpers/SensorManager.h +++ b/src/helpers/SensorManager.h @@ -2,6 +2,7 @@ #include #include "sensors/LocationProvider.h" +#include "helpers/IdentityStore.h" #define TELEM_PERM_BASE 0x01 // 'base' permission includes battery #define TELEM_PERM_LOCATION 0x02 @@ -9,29 +10,33 @@ #define TELEM_CHANNEL_SELF 1 // LPP data channel for 'self' device +#define MAX_SETTING_VALUE_LEN 7 // max chars returned by getSettingValue (excluding null terminator) +#define MAX_SETTING_BUF_LEN (MAX_SETTING_VALUE_LEN+1) + class SensorManager { public: double node_lat, node_lon; // modify these, if you want to affect Advert location double node_altitude; // altitude in meters SensorManager() { node_lat = 0; node_lon = 0; node_altitude = 0; } - virtual bool begin() { return false; } + virtual bool begin(FILESYSTEM* fs = nullptr) { return false; } + virtual bool hasPendingWork() { return false; } virtual bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { return false; } virtual void loop() { } virtual int getNumSettings() const { return 0; } virtual const char* getSettingName(int i) const { return NULL; } - virtual const char* getSettingValue(int i) const { return NULL; } + virtual int getSettingValue(int i, char* buf, int bufLen) const { return 0; } virtual bool setSettingValue(const char* name, const char* value) { return false; } virtual LocationProvider* getLocationProvider() { return NULL; } // Helper functions to manage setting by keys (useful in many places ...) - const char* getSettingByKey(const char* key) { + int getSettingByKey(const char* key, char* buf, int bufLen) { int num = getNumSettings(); for (int i = 0; i < num; i++) { if (strcmp(getSettingName(i), key) == 0) { - return getSettingValue(i); + return getSettingValue(i, buf, bufLen); } } - return NULL; + return 0; } }; diff --git a/src/helpers/sensors/DFROBOT_SEN0658.cpp b/src/helpers/sensors/DFROBOT_SEN0658.cpp new file mode 100644 index 0000000000..99fe932fc1 --- /dev/null +++ b/src/helpers/sensors/DFROBOT_SEN0658.cpp @@ -0,0 +1,493 @@ +#include +#include "target.h" +#include "DFROBOT_SEN0658.h" +#include "EndianTypes.h" + +#if ENV_INCLUDE_SEN0658 + +struct PacketHeader { + uint8_t address; + uint8_t function; + uint8_t validBytes; +} __attribute__((packed)); + +void DFROBOT_SEN0658::flushSerial() { + while (WITH_SEN0658_SERIAL.available() > 0) { + WITH_SEN0658_SERIAL.read(); + } +} + +bool DFROBOT_SEN0658::readBytes(uint8_t *buffer, int len) { + const int16_t timeout = 500; + uint8_t *start_buf = buffer; + int remaining = len; + long start = millis(); + while (remaining > 0) { + if (WITH_SEN0658_SERIAL.available()) { + *buffer = WITH_SEN0658_SERIAL.read(); + buffer++; + remaining--; + } + if (millis() - start > timeout) { + MESH_DEBUG_PRINTLN("Timed out reading bytes SEN0658. Read %i of %i bytes.", (int)(len - remaining), (int)len); + return false; + } + } + MESH_DEBUG_PRINT("SEN0658 RX [%i]:", len); + MESH_DEBUG_PRINT_HEX(start_buf, len); + MESH_DEBUG_PRINT_RAW("\n"); + return true; +} + +bool DFROBOT_SEN0658::begin() { + WITH_SEN0658_SERIAL.setPins(WITH_SEN0658_RX, WITH_SEN0658_TX); + + powerOn(); + + bool result; + int i; + for(i = 0; i < 15; i++) { + + WITH_SEN0658_SERIAL.begin(4800); + sendWriteCommand(0x07D1, 0x0006); // set baud rate to 115200 + WITH_SEN0658_SERIAL.end(); + + WITH_SEN0658_SERIAL.begin(115200); + delay(100); + flushSerial(); + + uint16_t offset; + if (result = readWindDirectionOffset(offset)) { + break; + } + MESH_DEBUG_PRINTLN("SEN0658 not detected."); + delay(100); + } + + if (result) { + MESH_DEBUG_PRINTLN("SEN0658 started after %i attempts.", i+1); + } else { + MESH_DEBUG_PRINTLN("SEN0658 failed to start after %i attempts.", i+1); + } + + return result; +} + +bool DFROBOT_SEN0658::hasPendingWork() { +#if defined(WITH_SEN0658_EN) + // if power is on, don't go into powersaving mode until we turn it off. + return _powerOnTime != 0; +#else + return false; +#endif +} + +template +bool DFROBOT_SEN0658::readRegisters(uint16_t registerStart, PacketType& packet) { + const uint16_t dataBytes = sizeof(PacketType) - sizeof(PacketHeader) - sizeof(le_uint16_t); + const uint16_t registerLength = dataBytes / 2; + flushSerial(); + sendCommand(0x03, registerStart, registerLength); + if (!readBytes((uint8_t *)&packet, sizeof(PacketType))) { + MESH_DEBUG_PRINTLN("Could not read packet from SEN0658."); + return false; + } + if (packet.header.address != 1 || packet.header.function != 3 || packet.header.validBytes != dataBytes) { + MESH_DEBUG_PRINTLN( + "Packet header mismatch reading packet from SEN0658. Address: %02X Function: %02X ValidBytes: %02X", + (int)packet.header.address, (int)packet.header.function, (int)packet.header.validBytes); + return false; + } + uint16_t crc = DFROBOT_SEN0658::CRC16_2((uint8_t *)&packet, sizeof(PacketType) - sizeof(le_uint16_t)); + if (crc != (uint16_t)packet.crc) { + MESH_DEBUG_PRINTLN("CRC mismatch %04X != %04X reading packet from SEN0658.", (int)crc, (int)packet.crc); + return false; + } + return true; +} + +bool DFROBOT_SEN0658::readWind(DFROBOT_SEN0658_Sample &sample) { + struct WindPacket { + PacketHeader header; + be_uint16_t windSpeed; + be_uint16_t Reserved; + be_uint16_t ignoredWindDirection; + be_uint16_t windAngle; + le_uint16_t crc; + } __attribute__((packed)) packet; + + if (!readRegisters(0x01F4, packet)) { + MESH_DEBUG_PRINTLN("Could not read wind data from SEN0658."); + return false; + } + // Wind is sometimes really all zeros + // if (packet.windSpeed == 0 && packet.windAngle == 0) { + // MESH_DEBUG_PRINTLN("WindPacket is all zeros."); + // return false; + // } + sample.windSpeed = packet.windSpeed / 100.0; + sample.windAngle = packet.windAngle; + return true; +} + +bool DFROBOT_SEN0658::readLight(DFROBOT_SEN0658_Sample &sample) { + struct LightPacket { + PacketHeader header; + be_uint32_t light; + le_uint16_t crc; + } __attribute__((packed)) packet; + + if (!readRegisters(0x01FE, packet)) { + MESH_DEBUG_PRINTLN("Could not read light data from SEN0658."); + return false; + } + + // lux is 0 at night + // if (packet.light == 0) { + // MESH_DEBUG_PRINTLN("LightPacket is all zeros."); + // return false; + // } + + sample.luminosity = packet.light; + return true; +} + +bool DFROBOT_SEN0658::readTemperature(DFROBOT_SEN0658_Sample &sample) { + struct TemperaturePacket { + PacketHeader header; + be_uint16_t humidity; + be_uint16_t temperature; + be_uint16_t noise; + le_uint16_t crc; + } __attribute__((packed)) packet; + + if (!readRegisters(0x01F8, packet)) { + MESH_DEBUG_PRINTLN("Could not read temperature data from SEN0658."); + return false; + } + if (packet.humidity == 0 || packet.temperature == 0 || packet.noise == 0) { + MESH_DEBUG_PRINTLN("TemperaturePacket is all zeros."); + return false; + } + sample.humidity = packet.humidity / 10.0; + sample.temperature = packet.temperature / 10.0; + sample.noiseDb = packet.noise / 10.0; + return true; +} + +bool DFROBOT_SEN0658::readAir(DFROBOT_SEN0658_Sample &sample) { + struct AirPacket { + PacketHeader header; + be_uint16_t pm2_5; + be_uint16_t pm10; + be_uint16_t airPressure; + le_uint16_t crc; + } __attribute__((packed)) packet; + + if (!readRegisters(0x01FB, packet)) { + MESH_DEBUG_PRINTLN("Could not read air data from SEN0658."); + return false; + } + if (packet.pm2_5 == 0 || packet.pm10 == 0 || packet.airPressure == 0) { + MESH_DEBUG_PRINTLN("AirPacket is all zeros."); + return false; + } + sample.pm2_5 = packet.pm2_5; + sample.pm10 = packet.pm10; + sample.airPressure = packet.airPressure; // register is 0.1 kPa = 1 hPa + return true; +} + +bool DFROBOT_SEN0658::readWindDirectionOffset(uint16_t &offset) { + struct OffsetPacket { + PacketHeader header; + be_uint16_t windOffset; + le_uint16_t crc; + } __attribute__((packed)) packet; + + if (!readRegisters(0x6000, packet)) { + MESH_DEBUG_PRINTLN("Could not read wind direction offset from SEN0658."); + return false; + } + offset = (uint16_t)packet.windOffset; + return true; +} + +bool DFROBOT_SEN0658::writeRegister(uint16_t registerIndex, uint16_t value) { + powerOn(); + sendWriteCommand(registerIndex, value); + + struct SingleRegisterPacket { + uint8_t address; + uint8_t function; + be_uint16_t regiterIndex; + be_uint16_t registerValue; + le_uint16_t crc; + } __attribute__((packed)) packet; + + if (!readBytes((uint8_t *)&packet, sizeof(SingleRegisterPacket))) { + MESH_DEBUG_PRINTLN("Could not read packet from SEN0658."); + return false; + } + if (packet.address != 1 || packet.function != 6 || packet.regiterIndex != registerIndex) { + MESH_DEBUG_PRINTLN( + "Packet header mismatch reading packet from SEN0658. Address: %02X Function: %02X RegisterIndex: %02X", + (int)packet.address, (int)packet.function, (int)packet.regiterIndex); + return false; + } + uint16_t crc = DFROBOT_SEN0658::CRC16_2((uint8_t *)&packet, sizeof(SingleRegisterPacket) - sizeof(le_uint16_t)); + if (crc != (uint16_t)packet.crc) { + MESH_DEBUG_PRINTLN("CRC mismatch %04X != %04X reading packet from SEN0658.", + (int)crc, (int)packet.crc); + return false; + } + + if (packet.registerValue != value) { + MESH_DEBUG_PRINTLN("Verification mismatch after writing to SEN0658. Wrote %i but read back %i.", + (int)value, (int)packet.registerValue); + return false; + } + + return true; +} + +bool DFROBOT_SEN0658::writeWindDirectionOffset(uint16_t offset) { + if (offset > 1) { + MESH_DEBUG_PRINTLN("Invalid wind direction offset value %i. Must be 0 or 1.", (int)offset); + return false; + } + return writeRegister(0x6000, offset); +} + +bool DFROBOT_SEN0658::zeroWindSpeed() { + return writeRegister(0x6001, 0x00AA); +} + +bool DFROBOT_SEN0658::zeroRainfall() { + return writeRegister(0x6002, 0x005A); +} + +void DFROBOT_SEN0658::sendCommand(uint8_t function, uint16_t registerStart, uint16_t registerLengthOrValue) { + uint8_t command[6]; + command[0] = 1; + command[1] = function; + command[2] = (registerStart >> 8) & 0xFF; + command[3] = registerStart & 0xFF; + command[4] = (registerLengthOrValue >> 8) & 0xFF; + command[5] = registerLengthOrValue & 0xFF; + + _lastActivityTime = millis(); + + WITH_SEN0658_SERIAL.write(command, 6); + uint16_t crc = CRC16_2(&command[0], 6); + WITH_SEN0658_SERIAL.write(crc & 0xFF); + WITH_SEN0658_SERIAL.write((crc >> 8) & 0xFF); +} + + +void DFROBOT_SEN0658::sendWriteCommand(uint16_t registerIndex, uint16_t value) { + sendCommand(0x06, registerIndex, value); +} + +void DFROBOT_SEN0658::powerOn() { + if (_powerOnTime == 0) { + MESH_DEBUG_PRINTLN("SEN0658 power on and ready to read sensor values in %u seconds.", (unsigned)config.warmupSeconds); +#if defined(WITH_SEN0658_EN) + digitalWrite(WITH_SEN0658_EN, HIGH); + delay(100); // wait for power rail to stabilize +#endif + _powerOnTime = millis(); + } + _lastActivityTime = millis(); +} + +bool DFROBOT_SEN0658::updateSample(DFROBOT_SEN0658_Sample &sample) { + powerOn(); + // If still warming up, return cached data if available + if (!isSensorReady()) { + uint32_t timeSincePowerOn = millis() - _powerOnTime; + uint32_t time_remaining = config.warmupSeconds * 1000 - timeSincePowerOn; + + // limit debug spew + if (_lastWarmupLog == 0 || millis() - _lastWarmupLog >= 1000) { + MESH_DEBUG_PRINTLN("SEN0658 is warming up. %lu ms until ready.", time_remaining); + _lastWarmupLog = millis(); + } + + return false; + } else if (readTemperature(sample) && readAir(sample) && readLight(sample) && readWind(sample)) { + sample.timestamp = rtc_clock.getCurrentTime(); + _cachedSample = sample; + _lastCacheTime = millis(); + MESH_DEBUG_PRINTLN("SEN0658 sample updated and cached at timestamp %lu.", sample.timestamp); + return true; + } else { + MESH_DEBUG_PRINTLN("SEN0658 sample update failed."); + return false; + } +} + +bool DFROBOT_SEN0658::readSample(DFROBOT_SEN0658_Sample &sample) { + bool result; + if (result = updateSample(sample)) { + MESH_DEBUG_PRINTLN("SEN0658 poll OK."); + } else if (!hasCachedSample()) { + MESH_DEBUG_PRINTLN("SEN0658 poll failed; no cached sample available."); + } else if (!isCachedSampleValid()) { + MESH_DEBUG_PRINTLN("SEN0658 poll failed; cached sample expired."); + } else { + sample = _cachedSample; + MESH_DEBUG_PRINTLN("SEN0658 poll failed; returning cached sample."); + result = true; + } + + return result; +} + +void DFROBOT_SEN0658::loop() { + DFROBOT_SEN0658_Sample sample; + + // Check if it is time to poll + if (isPollDue()) { + if (updateSample(sample)) { + _lastPollTime = millis(); + MESH_DEBUG_PRINTLN("SEN0658 poll successful in loop."); + } + } + + // Idle timeout: power off if no activity +#if defined(WITH_SEN0658_EN) + if (isPoweredOn() && isSensorReady() && isIdle()) { + // opportunistically read a sample before shutting down + if(updateSample(sample)) { + _lastPollTime = millis(); + MESH_DEBUG_PRINTLN("SEN0658 poll successful before idle shutdown."); + } else { + MESH_DEBUG_PRINTLN("SEN0658 poll failed before idle shutdown."); + } + digitalWrite(WITH_SEN0658_EN, LOW); + _powerOnTime = 0; + _lastActivityTime = 0; + } +#endif +} + +uint16_t DFROBOT_SEN0658::CRC16_2(const uint8_t *buf, int len) { + uint16_t crc = 0xFFFF; + for (int pos = 0; pos < len; pos++) { + crc ^= (uint16_t)buf[pos]; + for (int i = 8; i != 0; i--) { + if ((crc & 0x0001) != 0) { + crc >>= 1; + crc ^= 0xA001; + } else { + crc >>= 1; + } + } + } + return crc; +} + +#define SEN0658_PREFS_FILE "/sen0658_prefs" + +void DFROBOT_SEN0658::loadPrefs(FILESYSTEM* fs) { +#if defined(RP2040_PLATFORM) + File file = fs->open(SEN0658_PREFS_FILE, "r"); +#else + File file = fs->open(SEN0658_PREFS_FILE); +#endif + if (file) { + Config loaded; + if (file.read((uint8_t *)&loaded, sizeof(loaded)) == sizeof(loaded) && loaded.version == config.version) { + config = loaded; + MESH_DEBUG_PRINTLN("SEN0658 prefs loaded: poll=%u cache=%u warmup=%u idle=%u", + (unsigned)config.pollPeriodSeconds, (unsigned)config.cacheMaxAgeSeconds, + (unsigned)config.warmupSeconds, (unsigned)config.idleTimeoutSeconds); + } else { + MESH_DEBUG_PRINTLN("SEN0658 prefs version mismatch or short read, using defaults."); + } + file.close(); + } +} + +void DFROBOT_SEN0658::savePrefs(FILESYSTEM* fs) { +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + fs->remove(SEN0658_PREFS_FILE); + File file = fs->open(SEN0658_PREFS_FILE, FILE_O_WRITE); +#elif defined(RP2040_PLATFORM) + File file = fs->open(SEN0658_PREFS_FILE, "w"); +#else + File file = fs->open(SEN0658_PREFS_FILE, "w", true); +#endif + if (file) { + file.write((uint8_t *)&config, sizeof(config)); + file.close(); + MESH_DEBUG_PRINTLN("SEN0658 prefs saved."); + } +} + +static const char* sen0658_setting_names[] = { + "wind_dir_offset", "wind_speed_zero", "rainfall_zero", + "poll_period", "cache_max_age", "warmup", "idle_timeout" +}; + +int DFROBOT_SEN0658::getNumSettings() const { + return sizeof(sen0658_setting_names) / sizeof(sen0658_setting_names[0]); +} + +const char* DFROBOT_SEN0658::getSettingName(int i) const { + if (i >= 0 && i < getNumSettings()) return sen0658_setting_names[i]; + return nullptr; +} + +int DFROBOT_SEN0658::getSettingValue(int i, char* buf, int bufLen) { + switch (i) { + case 0: { // wind_dir_offset + uint16_t offset; + if (readWindDirectionOffset(offset)) { + return snprintf(buf, bufLen, "%u", (unsigned)offset); + } else { + return snprintf(buf, bufLen, "[error]"); + } + } + case 1: return snprintf(buf, bufLen, "[n/a]"); // wind_speed_zero + case 2: return snprintf(buf, bufLen, "[n/a]"); // rainfall_zero + case 3: return snprintf(buf, bufLen, "%u", (unsigned)config.pollPeriodSeconds); + case 4: return snprintf(buf, bufLen, "%u", (unsigned)config.cacheMaxAgeSeconds); + case 5: return snprintf(buf, bufLen, "%u", (unsigned)config.warmupSeconds); + case 6: return snprintf(buf, bufLen, "%u", (unsigned)config.idleTimeoutSeconds); + default: return 0; + } +} + +bool DFROBOT_SEN0658::setSettingValue(const char* name, const char* value) { + if (strcmp(name, "wind_speed_zero") == 0) { + return zeroWindSpeed(); + } else if (strcmp(name, "rainfall_zero") == 0) { + return zeroRainfall(); + } + + // parse uint16_t value + char *end; + unsigned long v = strtoul(value, &end, 10); + if (end == value || v > UINT16_MAX) return false; + uint16_t val = (uint16_t)v; + + if (strcmp(name, "wind_dir_offset") == 0) { + if (val > 1) return false; + return writeWindDirectionOffset(val); + } else if (strcmp(name, "poll_period") == 0) { + config.pollPeriodSeconds = val; + } else if (strcmp(name, "cache_max_age") == 0) { + config.cacheMaxAgeSeconds = val; + } else if (strcmp(name, "warmup") == 0) { + config.warmupSeconds = val; + } else if (strcmp(name, "idle_timeout") == 0) { + config.idleTimeoutSeconds = val; + } else { + return false; + } + return true; +} + +#endif diff --git a/src/helpers/sensors/DFROBOT_SEN0658.h b/src/helpers/sensors/DFROBOT_SEN0658.h new file mode 100644 index 0000000000..48a6f39f71 --- /dev/null +++ b/src/helpers/sensors/DFROBOT_SEN0658.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include +#include + +#if defined(ENV_INCLUDE_GPS) && \ + (PIN_GPS_TX == WITH_SEN0658_TX || PIN_GPS_TX == WITH_SEN0658_RX || \ + PIN_GPS_RX == WITH_SEN0658_TX || PIN_GPS_RX == WITH_SEN0658_RX) + #error "GPS pins conflict with SEN0658 pins. Please change the pin definitions to avoid conflicts" +#endif + + +#ifndef SEN0658_POLL_PERIOD_SECONDS +#define SEN0658_POLL_PERIOD_SECONDS (30 * 60) +#endif + +#ifndef SEN0658_CACHE_MAX_AGE_SECONDS +#define SEN0658_CACHE_MAX_AGE_SECONDS (SEN0658_POLL_PERIOD_SECONDS * 5 / 2) +#endif + +#ifndef SEN0658_WARMUP_SECONDS +#define SEN0658_WARMUP_SECONDS 30 +#endif + +#ifndef SEN0658_IDLE_TIMEOUT_SECONDS +#define SEN0658_IDLE_TIMEOUT_SECONDS 30 +#endif + +// not I2C +#define TELEM_SEN0658_ADDRESS 0 + +struct DFROBOT_SEN0658_Sample { + float temperature; + float humidity; + float airPressure; + float pm2_5; + float pm10; + float windSpeed; + float windAngle; + float noiseDb; + float luminosity; + uint32_t timestamp; +}; + +class DFROBOT_SEN0658 +{ + public: + bool begin(); + bool readSample(DFROBOT_SEN0658_Sample &sample); + bool readWindDirectionOffset(uint16_t &offset); + bool writeWindDirectionOffset(uint16_t offset); + bool zeroWindSpeed(); + bool zeroRainfall(); + void loop(); + bool hasPendingWork(); + void loadPrefs(FILESYSTEM* fs); + void savePrefs(FILESYSTEM* fs); + int getNumSettings() const; + const char* getSettingName(int i) const; + int getSettingValue(int i, char* buf, int bufLen); + bool setSettingValue(const char* name, const char* value); + + private: + struct Config { + uint16_t version = 1; + uint16_t pollPeriodSeconds = SEN0658_POLL_PERIOD_SECONDS; + uint16_t cacheMaxAgeSeconds = SEN0658_CACHE_MAX_AGE_SECONDS; + uint16_t warmupSeconds = SEN0658_WARMUP_SECONDS; + uint16_t idleTimeoutSeconds = SEN0658_IDLE_TIMEOUT_SECONDS; + } __attribute__((packed)) config; + + DFROBOT_SEN0658_Sample _cachedSample = {0}; + uint32_t _lastCacheTime = 0; + uint32_t _lastPollTime = 0; + uint32_t _powerOnTime = 0; + uint32_t _lastActivityTime = 0; + uint32_t _lastWarmupLog = 0; + void powerOn(); + static uint16_t CRC16_2(const uint8_t *buf, int len); + void sendCommand(uint8_t function, uint16_t registerStart, uint16_t registerLengthOrValue); + void sendWriteCommand(uint16_t registerIndex, uint16_t value); + bool readBytes(uint8_t *buffer, int len); + void flushSerial(); + bool updateSample(DFROBOT_SEN0658_Sample &sample); + template bool readRegisters(uint16_t registerStart, PacketType& packet); + bool writeRegister(uint16_t registerIndex, uint16_t value); + bool readWind(DFROBOT_SEN0658_Sample &sample); + bool readTemperature(DFROBOT_SEN0658_Sample &sample); + bool readAir(DFROBOT_SEN0658_Sample &sample); + bool readLight(DFROBOT_SEN0658_Sample &sample); + // helpers + bool isPollDue() { return config.pollPeriodSeconds > 0 && millis() - _lastPollTime >= config.pollPeriodSeconds * 1000; } + bool isPoweredOn() { return _powerOnTime != 0; } + bool isSensorReady() { return isPoweredOn() && millis() - _powerOnTime >= config.warmupSeconds * 1000; } + bool hasCachedSample() { return _lastCacheTime != 0; } + bool isCachedSampleValid() { return hasCachedSample() && config.cacheMaxAgeSeconds > 0 && millis() - _lastCacheTime < config.cacheMaxAgeSeconds * 1000; } + bool isIdle() { return isSensorReady() && config.idleTimeoutSeconds > 0 && millis() - _lastActivityTime >= config.idleTimeoutSeconds * 1000; } +}; diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index b5e23b3fe8..f34fad130f 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -53,6 +53,11 @@ static Adafruit_BME280 BME280; static Adafruit_BMP280 BMP280(TELEM_WIRE); #endif +#if ENV_INCLUDE_SEN0658 +#include "DFROBOT_SEN0658.h" +static DFROBOT_SEN0658 SEN0658; +#endif + #if ENV_INCLUDE_SHTC3 #include static Adafruit_SHTC3 SHTC3; @@ -449,6 +454,30 @@ static void query_rak12035(uint8_t ch, uint8_t sub_ch, CayenneLPP& lpp) { } #endif +#if ENV_INCLUDE_SEN0658 +static uint8_t init_sen0658(TwoWire* wire, uint8_t addr) { + // SEN0658 requires setup() before begin(). + if (!SEN0658.begin()) return 0; + return 1; +} +static void query_sen0658(uint8_t ch, uint8_t sub_ch, CayenneLPP& lpp) { + DFROBOT_SEN0658_Sample sample = {0}; + if (SEN0658.readSample(sample)) { + // this is an external sensor, so don't start with SELF channel + lpp.addTemperature(ch, sample.temperature); + lpp.addRelativeHumidity(ch, sample.humidity); + lpp.addBarometricPressure(ch, sample.airPressure); + lpp.addConcentration(ch, sample.pm2_5); + lpp.addAnalogInput(ch, sample.pm10); + lpp.addDistance(ch, sample.windSpeed); + lpp.addDirection(ch, sample.windAngle); + lpp.addLuminosity(ch, sample.luminosity); + lpp.addGenericSensor(ch, sample.noiseDb); + lpp.addUnixTime(ch, sample.timestamp); + } +} +#endif + // ============================================================ // Sensor descriptor table // @@ -514,6 +543,9 @@ static const SensorDef SENSOR_TABLE[] = { #endif #if ENV_INCLUDE_RAK12035 { TELEM_RAK12035_ADDRESS,"RAK12035", init_rak12035, query_rak12035 }, +#endif +#if ENV_INCLUDE_SEN0658 + { TELEM_SEN0658_ADDRESS, "SEN0658", init_sen0658, query_sen0658 }, #endif { 0, nullptr, nullptr, nullptr } // sentinel — keeps the array non-empty }; @@ -527,7 +559,8 @@ static const size_t SENSOR_TABLE_SIZE = (sizeof(SENSOR_TABLE) / sizeof(SENSOR_TA // crashes caused by absent or misbehaving hardware. // ============================================================ -bool EnvironmentSensorManager::begin() { +bool EnvironmentSensorManager::begin(FILESYSTEM* fs) { + _fs = fs; #if ENV_INCLUDE_GPS #ifdef RAK_WISBLOCK_GPS rakGPSInit(); @@ -547,6 +580,10 @@ bool EnvironmentSensorManager::begin() { MESH_DEBUG_PRINTLN("Second I2C initialized on pins SDA: %d SCL: %d", ENV_PIN_SDA, ENV_PIN_SCL); #endif + #if ENV_INCLUDE_SEN0658 + if (_fs) SEN0658.loadPrefs(_fs); + #endif + // Scan the I2C bus before touching any sensor library. bool detected[128] = {}; scanI2CBus(TELEM_WIRE, detected); @@ -602,6 +639,9 @@ int EnvironmentSensorManager::getNumSettings() const { #if ENV_INCLUDE_GPS if (gps_detected) settings++; // only show GPS setting if GPS is detected #endif + #if ENV_INCLUDE_SEN0658 + settings += SEN0658.getNumSettings(); + #endif return settings; } @@ -612,17 +652,31 @@ const char* EnvironmentSensorManager::getSettingName(int i) const { return "gps"; } #endif + #if ENV_INCLUDE_SEN0658 + int local = i - settings; + if (local >= 0 && local < SEN0658.getNumSettings()) { + return SEN0658.getSettingName(local); + } + settings += SEN0658.getNumSettings(); + #endif return NULL; } -const char* EnvironmentSensorManager::getSettingValue(int i) const { +int EnvironmentSensorManager::getSettingValue(int i, char* buf, int bufLen) const { int settings = 0; #if ENV_INCLUDE_GPS if (gps_detected && i == settings++) { - return gps_active ? "1" : "0"; + return snprintf(buf, bufLen, "%s", gps_active ? "1" : "0"); } #endif - return NULL; + #if ENV_INCLUDE_SEN0658 + int local = i - settings; + if (local >= 0 && local < SEN0658.getNumSettings()) { + return SEN0658.getSettingValue(local, buf, bufLen); + } + settings += SEN0658.getNumSettings(); + #endif + return 0; } bool EnvironmentSensorManager::setSettingValue(const char* name, const char* value) { @@ -641,6 +695,12 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val return true; } #endif + #if ENV_INCLUDE_SEN0658 + if (SEN0658.setSettingValue(name, value)) { + if (_fs) SEN0658.savePrefs(_fs); + return true; + } + #endif return false; // not supported } @@ -798,6 +858,17 @@ void EnvironmentSensorManager::stop_gps() { MESH_DEBUG_PRINTLN("Stop GPS is N/A on this board. Actual GPS state unchanged"); #endif } +#endif + +bool EnvironmentSensorManager::hasPendingWork() { + #if ENV_INCLUDE_SEN0658 + if(SEN0658.hasPendingWork()) { + return true; + } + #endif + + return false; +} void EnvironmentSensorManager::loop() { static long next_gps_update = 0; @@ -830,5 +901,8 @@ void EnvironmentSensorManager::loop() { next_gps_update = millis() + (gps_update_interval_sec * 1000); } #endif -} -#endif + + #if ENV_INCLUDE_SEN0658 + SEN0658.loop(); + #endif +} \ No newline at end of file diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index bb3dded3f1..93583dd296 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -23,6 +23,8 @@ class EnvironmentSensorManager : public SensorManager { bool gps_active = false; uint32_t gps_update_interval_sec = 1; + FILESYSTEM* _fs = nullptr; // for sensors to save their prefs + #if ENV_INCLUDE_GPS LocationProvider* _location; void start_gps(); @@ -41,13 +43,12 @@ class EnvironmentSensorManager : public SensorManager { #else EnvironmentSensorManager(){}; #endif - bool begin() override; + bool begin(FILESYSTEM* fs = nullptr) override; + bool hasPendingWork() override; bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; - #if ENV_INCLUDE_GPS void loop() override; - #endif int getNumSettings() const override; const char* getSettingName(int i) const override; - const char* getSettingValue(int i) const override; + int getSettingValue(int i, char* buf, int bufLen) const override; bool setSettingValue(const char* name, const char* value) override; }; diff --git a/variants/heltec_mesh_solar/target.cpp b/variants/heltec_mesh_solar/target.cpp index d140864cd1..e15b71e64e 100644 --- a/variants/heltec_mesh_solar/target.cpp +++ b/variants/heltec_mesh_solar/target.cpp @@ -42,7 +42,7 @@ void SolarSensorManager::stop_gps() { } } -bool SolarSensorManager::begin() { +bool SolarSensorManager::begin(FILESYSTEM* fs) { Serial1.begin(9600); // We'll consider GPS detected if we see any data on Serial1 @@ -88,11 +88,11 @@ const char* SolarSensorManager::getSettingName(int i) const { return (gps_detected && i == 0) ? "gps" : NULL; } -const char* SolarSensorManager::getSettingValue(int i) const { +int SolarSensorManager::getSettingValue(int i, char* buf, int bufLen) const { if (gps_detected && i == 0) { - return gps_active ? "1" : "0"; + return snprintf(buf, bufLen, "%s", gps_active ? "1" : "0"); } - return NULL; + return 0; } bool SolarSensorManager::setSettingValue(const char* name, const char* value) { diff --git a/variants/heltec_mesh_solar/target.h b/variants/heltec_mesh_solar/target.h index 1956f50ebd..e84cb3518c 100644 --- a/variants/heltec_mesh_solar/target.h +++ b/variants/heltec_mesh_solar/target.h @@ -21,12 +21,12 @@ class SolarSensorManager : public SensorManager { void stop_gps(); public: SolarSensorManager(LocationProvider &location): _location(&location) { } - bool begin() override; + bool begin(FILESYSTEM* fs = nullptr) 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; + int getSettingValue(int i, char* buf, int bufLen) const override; bool setSettingValue(const char* name, const char* value) override; }; diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp index f32c41ff47..a1e1a4066d 100644 --- a/variants/heltec_tracker/target.cpp +++ b/variants/heltec_tracker/target.cpp @@ -57,7 +57,7 @@ void HWTSensorManager::stop_gps() { } } -bool HWTSensorManager::begin() { +bool HWTSensorManager::begin(FILESYSTEM* fs) { // init GPS port Serial1.begin(115200, SERIAL_8N1, PIN_GPS_RX, PIN_GPS_TX); return true; @@ -91,11 +91,11 @@ int HWTSensorManager::getNumSettings() const { return 1; } // just one supporte const char* HWTSensorManager::getSettingName(int i) const { return i == 0 ? "gps" : NULL; } -const char* HWTSensorManager::getSettingValue(int i) const { +int HWTSensorManager::getSettingValue(int i, char* buf, int bufLen) const { if (i == 0) { - return gps_active ? "1" : "0"; + return snprintf(buf, bufLen, "%s", gps_active ? "1" : "0"); } - return NULL; + return 0; } bool HWTSensorManager::setSettingValue(const char* name, const char* value) { if (strcmp(name, "gps") == 0) { diff --git a/variants/heltec_tracker/target.h b/variants/heltec_tracker/target.h index 2931eda816..aecb7a5350 100644 --- a/variants/heltec_tracker/target.h +++ b/variants/heltec_tracker/target.h @@ -21,12 +21,12 @@ class HWTSensorManager : public SensorManager { void stop_gps(); public: HWTSensorManager(LocationProvider &location): _location(&location) { } - bool begin() override; + bool begin(FILESYSTEM* fs = nullptr) 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; + int getSettingValue(int i, char* buf, int bufLen) const override; bool setSettingValue(const char* name, const char* value) override; LocationProvider* getLocationProvider() override { return _location; } }; diff --git a/variants/meshadventurer/target.cpp b/variants/meshadventurer/target.cpp index 8795a4cbe7..46d396a713 100644 --- a/variants/meshadventurer/target.cpp +++ b/variants/meshadventurer/target.cpp @@ -50,7 +50,7 @@ void MASensorManager::stop_gps() { } } -bool MASensorManager::begin() { +bool MASensorManager::begin(FILESYSTEM* fs) { Serial1.setPins(PIN_GPS_RX, PIN_GPS_TX); Serial1.begin(9600); delay(500); @@ -83,11 +83,11 @@ int MASensorManager::getNumSettings() const { return 1; } // just one supported const char* MASensorManager::getSettingName(int i) const { return i == 0 ? "gps" : NULL; } -const char* MASensorManager::getSettingValue(int i) const { +int MASensorManager::getSettingValue(int i, char* buf, int bufLen) const { if(i == 0) { - return gps_active ? "1" : "0"; + return snprintf(buf, bufLen, "%s", gps_active ? "1" : "0"); } - return NULL; + return 0; } bool MASensorManager::setSettingValue(const char* name, const char* value) { if(strcmp(name, "gps") == 0) { diff --git a/variants/meshadventurer/target.h b/variants/meshadventurer/target.h index d59b3aee8d..3964a72449 100644 --- a/variants/meshadventurer/target.h +++ b/variants/meshadventurer/target.h @@ -22,12 +22,12 @@ class MASensorManager : public SensorManager { void stop_gps(); public: MASensorManager(LocationProvider &location): _location(&location) { } - bool begin() override; + bool begin(FILESYSTEM* fs = nullptr) 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; + int getSettingValue(int i, char* buf, int bufLen) const override; bool setSettingValue(const char* name, const char* value) override; }; diff --git a/variants/nano_g2_ultra/target.cpp b/variants/nano_g2_ultra/target.cpp index 69a2772ccb..7ad0e7284a 100644 --- a/variants/nano_g2_ultra/target.cpp +++ b/variants/nano_g2_ultra/target.cpp @@ -47,7 +47,7 @@ void NanoG2UltraSensorManager::stop_gps() { _location->stop(); } -bool NanoG2UltraSensorManager::begin() { +bool NanoG2UltraSensorManager::begin(FILESYSTEM* fs) { digitalWrite(PIN_GPS_STANDBY, HIGH); // Wake GPS from standby Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX); Serial1.begin(9600); @@ -103,11 +103,11 @@ const char *NanoG2UltraSensorManager::getSettingName(int i) const { return i == 0 ? "gps" : NULL; } -const char *NanoG2UltraSensorManager::getSettingValue(int i) const { +int NanoG2UltraSensorManager::getSettingValue(int i, char* buf, int bufLen) const { if (i == 0) { - return gps_active ? "1" : "0"; + return snprintf(buf, bufLen, "%s", gps_active ? "1" : "0"); } - return NULL; + return 0; } bool NanoG2UltraSensorManager::setSettingValue(const char *name, const char *value) { diff --git a/variants/nano_g2_ultra/target.h b/variants/nano_g2_ultra/target.h index 5c6ebee11d..c451017ebb 100644 --- a/variants/nano_g2_ultra/target.h +++ b/variants/nano_g2_ultra/target.h @@ -23,12 +23,12 @@ class NanoG2UltraSensorManager : public SensorManager { public: NanoG2UltraSensorManager(LocationProvider &location) : _location(&location) {} - bool begin() override; + bool begin(FILESYSTEM* fs = nullptr) 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; + int getSettingValue(int i, char* buf, int bufLen) const override; bool setSettingValue(const char *name, const char *value) override; }; diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index ea7e49c355..b42990bcbd 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -48,6 +48,28 @@ build_src_filter = ${rak4631.build_src_filter} + +<../examples/simple_repeater> +[env:RAK_4631_repeater_SEN0658_serial2] +extends = rak4631 +build_flags = + ${rak4631.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RAK4631 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D ENV_INCLUDE_SEN0658=1 + -D WITH_SEN0658_SERIAL=Serial2 + -D WITH_SEN0658_RX=PIN_SERIAL2_RX + -D WITH_SEN0658_TX=PIN_SERIAL2_TX + -D WITH_SEN0658_EN=WB_IO3 + -UENV_INCLUDE_GPS +; -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 +build_src_filter = ${rak4631.build_src_filter} + + + +<../examples/simple_repeater> + [env:RAK_4631_repeater_bridge_rs232_serial1] extends = rak4631 build_flags = diff --git a/variants/rak4631/variant.cpp b/variants/rak4631/variant.cpp index bd85e97133..f5655c0cb4 100644 --- a/variants/rak4631/variant.cpp +++ b/variants/rak4631/variant.cpp @@ -45,5 +45,10 @@ void initVariant() pinMode(PIN_LED2, OUTPUT); ledOff(PIN_LED2);; + +#if defined(WITH_SEN0658_EN) + pinMode(WITH_SEN0658_EN, OUTPUT); + digitalWrite(WITH_SEN0658_EN, LOW); +#endif } diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index 4253282708..39d4c41903 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -128,7 +128,7 @@ void T1000SensorManager::stop_gps() { } -bool T1000SensorManager::begin() { +bool T1000SensorManager::begin(FILESYSTEM* fs) { // init GPS Serial1.begin(115200); return true; @@ -167,11 +167,11 @@ int T1000SensorManager::getNumSettings() const { return 1; } // just one suppor const char* T1000SensorManager::getSettingName(int i) const { return i == 0 ? "gps" : NULL; } -const char* T1000SensorManager::getSettingValue(int i) const { +int T1000SensorManager::getSettingValue(int i, char* buf, int bufLen) const { if (i == 0) { - return gps_active ? "1" : "0"; + return snprintf(buf, bufLen, "%s", gps_active ? "1" : "0"); } - return NULL; + return 0; } bool T1000SensorManager::setSettingValue(const char* name, const char* value) { if (strcmp(name, "gps") == 0) { diff --git a/variants/t1000-e/target.h b/variants/t1000-e/target.h index db003cc5fa..5d6ab52899 100644 --- a/variants/t1000-e/target.h +++ b/variants/t1000-e/target.h @@ -21,12 +21,12 @@ class T1000SensorManager: public SensorManager { void stop_gps(); public: T1000SensorManager(LocationProvider &nmea): _nmea(&nmea) { } - bool begin() override; + bool begin(FILESYSTEM* fs = nullptr) 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; + int getSettingValue(int i, char* buf, int bufLen) const override; bool setSettingValue(const char* name, const char* value) override; LocationProvider* getLocationProvider() { return _nmea; } }; diff --git a/variants/thinknode_m1/target.cpp b/variants/thinknode_m1/target.cpp index 69306fc0e1..3d0ca55268 100644 --- a/variants/thinknode_m1/target.cpp +++ b/variants/thinknode_m1/target.cpp @@ -43,7 +43,7 @@ void ThinkNodeM1SensorManager::stop_gps() { } } -bool ThinkNodeM1SensorManager::begin() { +bool ThinkNodeM1SensorManager::begin(FILESYSTEM* fs) { Serial1.begin(9600); // Initialize GPS switch pin @@ -117,11 +117,11 @@ const char* ThinkNodeM1SensorManager::getSettingName(int i) const { return (i == 0) ? "gps" : NULL; } -const char* ThinkNodeM1SensorManager::getSettingValue(int i) const { +int ThinkNodeM1SensorManager::getSettingValue(int i, char* buf, int bufLen) const { if (i == 0) { - return gps_active ? "1" : "0"; + return snprintf(buf, bufLen, "%s", gps_active ? "1" : "0"); } - return NULL; + return 0; } bool ThinkNodeM1SensorManager::setSettingValue(const char* name, const char* value) { diff --git a/variants/thinknode_m1/target.h b/variants/thinknode_m1/target.h index ec1297beff..f023eb07a6 100644 --- a/variants/thinknode_m1/target.h +++ b/variants/thinknode_m1/target.h @@ -23,12 +23,12 @@ class ThinkNodeM1SensorManager : public SensorManager { public: ThinkNodeM1SensorManager(LocationProvider &location): _location(&location) { } LocationProvider* getLocationProvider() override { return _location; } - bool begin() override; + bool begin(FILESYSTEM* fs = nullptr) 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; + int getSettingValue(int i, char* buf, int bufLen) const override; bool setSettingValue(const char* name, const char* value) override; }; diff --git a/variants/wio-e5-mini/target.cpp b/variants/wio-e5-mini/target.cpp index 92775d5511..331fdd0597 100644 --- a/variants/wio-e5-mini/target.cpp +++ b/variants/wio-e5-mini/target.cpp @@ -72,7 +72,7 @@ bool WIOE5SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& return true; } -bool WIOE5SensorManager::begin() { +bool WIOE5SensorManager::begin(FILESYSTEM* fs) { has_bme = bme.begin(); return has_bme; diff --git a/variants/wio-e5-mini/target.h b/variants/wio-e5-mini/target.h index 4807e0f791..21d93599ec 100644 --- a/variants/wio-e5-mini/target.h +++ b/variants/wio-e5-mini/target.h @@ -48,7 +48,7 @@ class WIOE5SensorManager : public SensorManager { public: WIOE5SensorManager() {} - bool begin() override; + bool begin(FILESYSTEM* fs = nullptr) override; bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; };