Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion src/helpers/sensors/EnvironmentSensorManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {

Expand All @@ -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
11 changes: 10 additions & 1 deletion src/helpers/sensors/EnvironmentSensorManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
#include <helpers/SensorManager.h>
#include <helpers/sensors/LocationProvider.h>

#ifdef ENV_INCLUDE_ONEWIRE
#include <helpers/sensors/OneWireSensorHub.h>
#endif

class EnvironmentSensorManager : public SensorManager {
protected:
int next_available_channel = TELEM_CHANNEL_SELF + 1;
Expand All @@ -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
Expand All @@ -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;
Expand Down
267 changes: 267 additions & 0 deletions src/helpers/sensors/OneWireSensorHub.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
#include "OneWireSensorHub.h"

#ifdef ENV_INCLUDE_ONEWIRE

#include <Mesh.h>

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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the return value of write() be checked to handle failed or partial writes?

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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can len be passed in as 0? would the resulting value for val_len (65536) for the subsequent loop be a problem?


uint8_t ordered[256];
for (uint16_t i = 0; i < val_len; i += 2) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if val_len is >= 256, the writes to ordered would be beyond the end of its allocated size. can val_len be that value?

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; }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be dividing by 1000 to go from mA to A?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To my surprise it is indeed 100

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the value is 1/100 of an amp, then the variable should not be named "ma" (milli means 1/1000). I would suggest keeping the variable representing ma and divide by 10 when fetching from the hardware, and fix this line to divide by 1000. I prefer consistency, and most other variants use ma, ma is a more common unit, etc

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
Loading