diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index a75d378c8..6a7031049 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -331,6 +331,15 @@ bool EnvironmentSensorManager::begin() { } #endif + #ifdef ENV_INCLUDE_ONEWIRE + OneWire_initialized = _oneWireHub.begin(); + if (OneWire_initialized) { + MESH_DEBUG_PRINTLN("OneWire SensorHub initialized with %d probe(s)", _oneWireHub.getNumPids()); + } else { + MESH_DEBUG_PRINTLN("OneWire SensorHub: No probes found"); + } + #endif + return true; } @@ -483,6 +492,27 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen } #endif + #ifdef ENV_INCLUDE_ONEWIRE + if (OneWire_initialized) { + bool has_onewire_data = _oneWireHub.hasVoltage() || _oneWireHub.hasCurrent() || _oneWireHub.hasBatteryPercent() || _oneWireHub.hasTemperature(); + if (has_onewire_data) { + MESH_DEBUG_PRINTLN("OneWire Telemetry ch=%d: V=%d C=%d SOC=%d T=%d", + next_available_channel, + _oneWireHub.hasVoltage(), _oneWireHub.hasCurrent(), + _oneWireHub.hasBatteryPercent(), _oneWireHub.hasTemperature()); + if (_oneWireHub.hasVoltage()) + telemetry.addVoltage(next_available_channel, _oneWireHub.getVoltage()); + if (_oneWireHub.hasCurrent()) + telemetry.addCurrent(next_available_channel, _oneWireHub.getCurrent()); + if (_oneWireHub.hasBatteryPercent()) + telemetry.addPercentage(next_available_channel, _oneWireHub.getBatteryPercent()); + if (_oneWireHub.hasTemperature()) + telemetry.addTemperature(next_available_channel, _oneWireHub.getTemperature()); + next_available_channel++; + } + } + #endif + } return true; @@ -702,11 +732,13 @@ void EnvironmentSensorManager::stop_gps() { MESH_DEBUG_PRINTLN("Stop GPS is N/A on this board. Actual GPS state unchanged"); #endif } +#endif // ENV_INCLUDE_GPS +#if ENV_INCLUDE_GPS || ENV_INCLUDE_ONEWIRE void EnvironmentSensorManager::loop() { + #if ENV_INCLUDE_GPS static long next_gps_update = 0; - #if ENV_INCLUDE_GPS _location->loop(); if (millis() > next_gps_update) { @@ -732,5 +764,11 @@ void EnvironmentSensorManager::loop() { next_gps_update = millis() + (gps_update_interval_sec * 1000); } #endif + + #ifdef ENV_INCLUDE_ONEWIRE + if (OneWire_initialized) { + _oneWireHub.loop(); + } + #endif } #endif diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index f176a33f5..de7518eb8 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -4,6 +4,10 @@ #include #include +#ifdef ENV_INCLUDE_ONEWIRE +#include +#endif + class EnvironmentSensorManager : public SensorManager { protected: int next_available_channel = TELEM_CHANNEL_SELF + 1; @@ -23,6 +27,11 @@ class EnvironmentSensorManager : public SensorManager { bool BME680_initialized = false; bool BMP085_initialized = false; + #ifdef ENV_INCLUDE_ONEWIRE + OneWireSensorHub _oneWireHub; + bool OneWire_initialized = false; + #endif + bool gps_detected = false; bool gps_active = false; uint32_t gps_update_interval_sec = 1; // Default 1 second @@ -48,7 +57,7 @@ class EnvironmentSensorManager : public SensorManager { #endif bool begin() override; bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; - #if ENV_INCLUDE_GPS + #if ENV_INCLUDE_GPS || ENV_INCLUDE_ONEWIRE void loop() override; #endif int getNumSettings() const override; diff --git a/src/helpers/sensors/OneWireSensorHub.cpp b/src/helpers/sensors/OneWireSensorHub.cpp new file mode 100644 index 000000000..00412c02a --- /dev/null +++ b/src/helpers/sensors/OneWireSensorHub.cpp @@ -0,0 +1,267 @@ +#include "OneWireSensorHub.h" + +#ifdef ENV_INCLUDE_ONEWIRE + +#include + +static SoftwareHalfSerial oneWireSerial(ONEWIRE_PIN); + +OneWireSensorHub* OneWireSensorHub::_instance = nullptr; + +void OneWireSensorHub::_onewire_callback(const uint8_t pid, const uint8_t sid, + const SNHUBAPI_EVT_E eid, uint8_t* msg, uint16_t len) { + if (_instance) { + _instance->handleEvent(pid, sid, eid, msg, len); + } +} + +bool OneWireSensorHub::begin() { + _instance = this; + _found_pid_count = 0; + _has_voltage = false; + + for (int i = 0; i < ONEWIRE_MAX_PIDS; i++) { + _found_pids[i] = 0xFF; + } + + pinMode(WB_IO2, OUTPUT); + digitalWrite(WB_IO2, HIGH); + delay(100); + + oneWireSerial.begin(9600); + RakSNHub_Protocl_API.init(_onewire_callback); + + MESH_DEBUG_PRINTLN("OneWire: Scanning for sensor probes (%d ms)...", ONEWIRE_DISCOVERY_TIMEOUT_MS); + + unsigned long start = millis(); + while ((millis() - start) < ONEWIRE_DISCOVERY_TIMEOUT_MS) { + while (oneWireSerial.available()) { + if (_rxlen < sizeof(_rxbuf)) { + _rxbuf[_rxlen++] = oneWireSerial.read(); + } + delay(5); + } + + if (_rxlen > 0) { + RakSNHub_Protocl_API.process(_rxbuf, _rxlen); + _rxlen = 0; + } + + delay(100); + } + + MESH_DEBUG_PRINTLN("OneWire: Discovery complete. Found %d sensor probe(s)", _found_pid_count); + + if (_found_pid_count == 0) { + digitalWrite(WB_IO2, LOW); + return false; + } + + _next_poll_ms = millis() + 2000; + _current_poll_idx = 0; + + return true; +} + +void OneWireSensorHub::loop() { + if (_found_pid_count == 0) return; + + while (oneWireSerial.available()) { + if (_rxlen < sizeof(_rxbuf)) { + _rxbuf[_rxlen++] = oneWireSerial.read(); + } else { + break; + } + _last_rx_time = millis(); + } + + if (_rxlen > 0 && (millis() - _last_rx_time) >= 10) { + RakSNHub_Protocl_API.process(_rxbuf, _rxlen); + _rxlen = 0; + } + + if (millis() >= _next_poll_ms) { + if (_current_poll_idx < _found_pid_count) { + uint8_t pid = _found_pids[_current_poll_idx]; + if (pid != 0xFF) { + RakSNHub_Protocl_API.get.data(pid); + MESH_DEBUG_PRINTLN("OneWire: Requested data from PID %d", pid); + } + _current_poll_idx++; + } + + if (_current_poll_idx >= _found_pid_count) { + _current_poll_idx = 0; + _next_poll_ms = millis() + ONEWIRE_POLL_INTERVAL_MS; + } else { + // Stagger between PID requests + _next_poll_ms = millis() + 500; + } + } +} + +void OneWireSensorHub::handleEvent(uint8_t pid, uint8_t sid, SNHUBAPI_EVT_E eid, + uint8_t* msg, uint16_t len) { + switch (eid) { + case SNHUBAPI_EVT_QSEND: + oneWireSerial.write(msg, len); + break; + + case SNHUBAPI_EVT_ADD_PID: + registerPid(msg[0]); + break; + + case SNHUBAPI_EVT_ADD_SID: + MESH_DEBUG_PRINTLN("OneWire: Added SID 0x%02X for PID %d", msg[0], pid); + break; + + case SNHUBAPI_EVT_SDATA_REQ: { + uint8_t ipso_type = msg[0]; + uint16_t val_len = len - 1; + + uint8_t ordered[256]; + for (uint16_t i = 0; i < val_len; i += 2) { + if (i + 1 < val_len) { + ordered[i] = msg[1 + i + 1]; + ordered[i + 1] = msg[1 + i]; + } else { + ordered[i] = msg[1 + i]; + } + } + MESH_DEBUG_PRINTLN("OneWire: SDATA_REQ SID=0x%02X IPSO=%d len=%d", sid, ipso_type, val_len); + parseSensorData(sid, ipso_type, ordered, val_len); + break; + } + + case SNHUBAPI_EVT_REPORT: { + uint8_t ipso_type = msg[0]; + uint16_t val_len = len - 1; + MESH_DEBUG_PRINTLN("OneWire: REPORT SID=0x%02X IPSO=%d len=%d", sid, ipso_type, val_len); + parseSensorData(sid, ipso_type, &msg[1], val_len); + break; + } + + case SNHUBAPI_EVT_CHKSUM_ERR: + MESH_DEBUG_PRINTLN("OneWire: Checksum error"); + break; + + case SNHUBAPI_EVT_SEQ_ERR: + MESH_DEBUG_PRINTLN("OneWire: Sequence error"); + break; + + default: + break; + } +} + +void OneWireSensorHub::registerPid(uint8_t pid) { + for (int i = 0; i < ONEWIRE_MAX_PIDS; i++) { + if (_found_pids[i] == pid) { + MESH_DEBUG_PRINTLN("OneWire: PID %d already registered", pid); + return; + } + } + for (int i = 0; i < ONEWIRE_MAX_PIDS; i++) { + if (_found_pids[i] == 0xFF) { + _found_pids[i] = pid; + _found_pid_count++; + MESH_DEBUG_PRINTLN("OneWire: Registered PID %d (total: %d)", pid, _found_pid_count); + return; + } + } + MESH_DEBUG_PRINTLN("OneWire: No slots for PID %d", pid); +} + +void OneWireSensorHub::parseSensorData(uint8_t sid, uint8_t ipso_type, uint8_t* data, uint16_t data_len) { + switch (ipso_type) { + case RAK_IPSO_BATTERVALUE: // 116 (3316-3200): battery voltage, 2 bytes, /100 + case RAK_IPSO_DC_VOLTAGE: { // 186 (3386-3200): DC voltage, 2 bytes, /100 + if (data_len >= 2) { + int16_t raw = ((int16_t)data[0] << 8) | (int16_t)data[1]; + _cached_voltage = (float)raw / 100.0f; + _has_voltage = true; + MESH_DEBUG_PRINTLN("OneWire: Battery Voltage = %.2fV (IPSO %d, raw=%d)", _cached_voltage, ipso_type, raw); + } + break; + } + + case RAK_IPSO_DC_CURRENT: { // 185 (3385-3200): DC current, 2 bytes, mA + if (data_len >= 2) { + int16_t raw = ((int16_t)data[0] << 8) | (int16_t)data[1]; + _cached_current_ma = raw; + _has_current = true; + MESH_DEBUG_PRINTLN("OneWire: Battery Current = %dmA (IPSO %d, raw=%d)", _cached_current_ma, ipso_type, raw); + } + break; + } + + case RAK_IPSO_CAPACITY: { // 184 (3384-3200): battery percentage, 1 byte + if (data_len >= 1) { + _cached_battery_pct = data[0]; + if (_cached_battery_pct > 100) _cached_battery_pct = 100; + _has_battery_pct = true; + MESH_DEBUG_PRINTLN("OneWire: Battery SOC = %d%% (IPSO %d)", _cached_battery_pct, ipso_type); + } + break; + } + + case RAK_IPSO_TEMP_SENSOR: { // 103 (3303-3200): temperature, 2 bytes, /10 + if (data_len >= 2) { + int16_t raw = ((int16_t)data[0] << 8) | (int16_t)data[1]; + _cached_temperature = (float)raw / 10.0f; + _has_temperature = true; + MESH_DEBUG_PRINTLN("OneWire: Battery Temperature = %.1fC (IPSO %d, raw=%d)", _cached_temperature, ipso_type, raw); + } + break; + } + + case RAK_IPSO_SSN: { // 126 (3326-3200): serial number, 3 bytes + if (data_len >= 3) { + _cached_serial = ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | data[2]; + _has_serial = true; + MESH_DEBUG_PRINTLN("OneWire: Serial Number = %06lX (IPSO %d)", (unsigned long)_cached_serial, ipso_type); + } + break; + } + + case RAK_IPSO_BINARY2BYTE: { // 243 (0xF3): 2-byte binary, SID distinguishes error vs FW version + if (data_len >= 2) { + uint16_t val = ((uint16_t)data[0] << 8) | data[1]; + if (sid == 0x19) { + _cached_error = val; + _has_error = true; + MESH_DEBUG_PRINTLN("OneWire: Battery Error = 0x%04X (IPSO %d, SID 0x%02X)", val, ipso_type, sid); + } else if (sid == 0x1A) { + _cached_fw_version = val; + _has_fw_version = true; + MESH_DEBUG_PRINTLN("OneWire: Battery FW Version = v%02d.%02d (IPSO %d, SID 0x%02X)", val >> 8, val & 0xFF, ipso_type, sid); + } else { + MESH_DEBUG_PRINTLN("OneWire: BINARY2BYTE = 0x%04X (IPSO %d, SID 0x%02X)", val, ipso_type, sid); + } + } + break; + } + + default: + MESH_DEBUG_PRINTLN("OneWire: Unhandled IPSO %d (len=%d)", ipso_type, data_len); + break; + } +} + +bool OneWireSensorHub::hasVoltage() const { return _has_voltage; } +float OneWireSensorHub::getVoltage() const { return _cached_voltage; } +bool OneWireSensorHub::hasCurrent() const { return _has_current; } +float OneWireSensorHub::getCurrent() const { return (float)_cached_current_ma / 100.0f; } +bool OneWireSensorHub::hasBatteryPercent() const { return _has_battery_pct; } +uint8_t OneWireSensorHub::getBatteryPercent() const { return _cached_battery_pct; } +bool OneWireSensorHub::hasTemperature() const { return _has_temperature; } +float OneWireSensorHub::getTemperature() const { return _cached_temperature; } +bool OneWireSensorHub::hasSerialNumber() const { return _has_serial; } +uint32_t OneWireSensorHub::getSerialNumber() const { return _cached_serial; } +bool OneWireSensorHub::hasError() const { return _has_error; } +uint16_t OneWireSensorHub::getError() const { return _cached_error; } +bool OneWireSensorHub::hasFwVersion() const { return _has_fw_version; } +uint16_t OneWireSensorHub::getFwVersion() const { return _cached_fw_version; } +uint8_t OneWireSensorHub::getNumPids() const { return _found_pid_count; } + +#endif // ENV_INCLUDE_ONEWIRE diff --git a/src/helpers/sensors/OneWireSensorHub.h b/src/helpers/sensors/OneWireSensorHub.h new file mode 100644 index 000000000..8d46a7fb1 --- /dev/null +++ b/src/helpers/sensors/OneWireSensorHub.h @@ -0,0 +1,77 @@ +#pragma once + +#ifdef ENV_INCLUDE_ONEWIRE + +#include + +#ifndef ONEWIRE_DISCOVERY_TIMEOUT_MS +#define ONEWIRE_DISCOVERY_TIMEOUT_MS 8000 +#endif + +#ifndef ONEWIRE_POLL_INTERVAL_MS +#define ONEWIRE_POLL_INTERVAL_MS 30000 +#endif + +#ifndef ONEWIRE_PIN +#define ONEWIRE_PIN PIN_SERIAL2_RX +#endif + +#define ONEWIRE_MAX_PIDS 5 + +class OneWireSensorHub { +public: + bool begin(); + void loop(); + + bool hasVoltage() const; + float getVoltage() const; + bool hasCurrent() const; + float getCurrent() const; + bool hasBatteryPercent() const; + uint8_t getBatteryPercent() const; + bool hasTemperature() const; + float getTemperature() const; + bool hasSerialNumber() const; + uint32_t getSerialNumber() const; + bool hasError() const; + uint16_t getError() const; + bool hasFwVersion() const; + uint16_t getFwVersion() const; + uint8_t getNumPids() const; + +private: + uint8_t _found_pids[ONEWIRE_MAX_PIDS]; + uint8_t _found_pid_count = 0; + + volatile float _cached_voltage = 0.0f; + volatile bool _has_voltage = false; + volatile int16_t _cached_current_ma = 0; + volatile bool _has_current = false; + volatile uint8_t _cached_battery_pct = 0; + volatile bool _has_battery_pct = false; + volatile float _cached_temperature = 0.0f; + volatile bool _has_temperature = false; + volatile uint32_t _cached_serial = 0; + volatile bool _has_serial = false; + volatile uint16_t _cached_error = 0; + volatile bool _has_error = false; + volatile uint16_t _cached_fw_version = 0; + volatile bool _has_fw_version = false; + + unsigned long _next_poll_ms = 0; + unsigned long _last_rx_time = 0; + uint8_t _current_poll_idx = 0; + + uint8_t _rxbuf[256]; + uint16_t _rxlen = 0; + + static OneWireSensorHub* _instance; + static void _onewire_callback(const uint8_t pid, const uint8_t sid, + const SNHUBAPI_EVT_E eid, uint8_t* msg, uint16_t len); + + void handleEvent(uint8_t pid, uint8_t sid, SNHUBAPI_EVT_E eid, uint8_t* msg, uint16_t len); + void registerPid(uint8_t pid); + void parseSensorData(uint8_t sid, uint8_t ipso_type, uint8_t* data, uint16_t data_len); +}; + +#endif // ENV_INCLUDE_ONEWIRE diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 737ef5652..f8eda1a4e 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -46,6 +46,31 @@ build_src_filter = ${rak4631.build_src_filter} + +<../examples/simple_repeater> +[env:RAK_4631_repeater_onewire] +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_ONEWIRE=1 + -D ONEWIRE_PIN=PIN_SERIAL1_RX + -UENV_INCLUDE_GPS + -D ONEWIRE_DISCOVERY_TIMEOUT_MS=5000 + -D ONEWIRE_POLL_INTERVAL_MS=30000 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak4631.build_src_filter} + + + + + +<../examples/simple_repeater> +lib_deps = + ${rak4631.lib_deps} + beegee-tokyo/RAK-OneWireSerial + [env:RAK_4631_repeater_bridge_rs232_serial1] extends = rak4631 build_flags =