Skip to content

Commit c37161d

Browse files
author
Frank Zosso
committed
Initial RAK One-Wire implementation for WisMesh Solar Repeater boundle (RAK9154 Solar Battery + RAK2560_HZB + RAK4630 Module) to report battery pack telemetry in channel 2
1 parent eeae32b commit c37161d

File tree

5 files changed

+418
-2
lines changed

5 files changed

+418
-2
lines changed

src/helpers/sensors/EnvironmentSensorManager.cpp

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,15 @@ bool EnvironmentSensorManager::begin() {
331331
}
332332
#endif
333333

334+
#ifdef ENV_INCLUDE_ONEWIRE
335+
OneWire_initialized = _oneWireHub.begin();
336+
if (OneWire_initialized) {
337+
MESH_DEBUG_PRINTLN("OneWire SensorHub initialized with %d probe(s)", _oneWireHub.getNumPids());
338+
} else {
339+
MESH_DEBUG_PRINTLN("OneWire SensorHub: No probes found");
340+
}
341+
#endif
342+
334343
return true;
335344
}
336345

@@ -483,6 +492,27 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen
483492
}
484493
#endif
485494

495+
#ifdef ENV_INCLUDE_ONEWIRE
496+
if (OneWire_initialized) {
497+
bool has_onewire_data = _oneWireHub.hasVoltage() || _oneWireHub.hasCurrent() || _oneWireHub.hasBatteryPercent() || _oneWireHub.hasTemperature();
498+
if (has_onewire_data) {
499+
MESH_DEBUG_PRINTLN("OneWire Telemetry ch=%d: V=%d C=%d SOC=%d T=%d",
500+
next_available_channel,
501+
_oneWireHub.hasVoltage(), _oneWireHub.hasCurrent(),
502+
_oneWireHub.hasBatteryPercent(), _oneWireHub.hasTemperature());
503+
if (_oneWireHub.hasVoltage())
504+
telemetry.addVoltage(next_available_channel, _oneWireHub.getVoltage());
505+
if (_oneWireHub.hasCurrent())
506+
telemetry.addCurrent(next_available_channel, _oneWireHub.getCurrent());
507+
if (_oneWireHub.hasBatteryPercent())
508+
telemetry.addPercentage(next_available_channel, _oneWireHub.getBatteryPercent());
509+
if (_oneWireHub.hasTemperature())
510+
telemetry.addTemperature(next_available_channel, _oneWireHub.getTemperature());
511+
next_available_channel++;
512+
}
513+
}
514+
#endif
515+
486516
}
487517

488518
return true;
@@ -702,11 +732,13 @@ void EnvironmentSensorManager::stop_gps() {
702732
MESH_DEBUG_PRINTLN("Stop GPS is N/A on this board. Actual GPS state unchanged");
703733
#endif
704734
}
735+
#endif // ENV_INCLUDE_GPS
705736

737+
#if ENV_INCLUDE_GPS || ENV_INCLUDE_ONEWIRE
706738
void EnvironmentSensorManager::loop() {
739+
#if ENV_INCLUDE_GPS
707740
static long next_gps_update = 0;
708741

709-
#if ENV_INCLUDE_GPS
710742
_location->loop();
711743
if (millis() > next_gps_update) {
712744

@@ -732,5 +764,11 @@ void EnvironmentSensorManager::loop() {
732764
next_gps_update = millis() + (gps_update_interval_sec * 1000);
733765
}
734766
#endif
767+
768+
#ifdef ENV_INCLUDE_ONEWIRE
769+
if (OneWire_initialized) {
770+
_oneWireHub.loop();
771+
}
772+
#endif
735773
}
736774
#endif

src/helpers/sensors/EnvironmentSensorManager.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
#include <helpers/SensorManager.h>
55
#include <helpers/sensors/LocationProvider.h>
66

7+
#ifdef ENV_INCLUDE_ONEWIRE
8+
#include <helpers/sensors/OneWireSensorHub.h>
9+
#endif
10+
711
class EnvironmentSensorManager : public SensorManager {
812
protected:
913
int next_available_channel = TELEM_CHANNEL_SELF + 1;
@@ -23,6 +27,11 @@ class EnvironmentSensorManager : public SensorManager {
2327
bool BME680_initialized = false;
2428
bool BMP085_initialized = false;
2529

30+
#ifdef ENV_INCLUDE_ONEWIRE
31+
OneWireSensorHub _oneWireHub;
32+
bool OneWire_initialized = false;
33+
#endif
34+
2635
bool gps_detected = false;
2736
bool gps_active = false;
2837
uint32_t gps_update_interval_sec = 1; // Default 1 second
@@ -48,7 +57,7 @@ class EnvironmentSensorManager : public SensorManager {
4857
#endif
4958
bool begin() override;
5059
bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override;
51-
#if ENV_INCLUDE_GPS
60+
#if ENV_INCLUDE_GPS || ENV_INCLUDE_ONEWIRE
5261
void loop() override;
5362
#endif
5463
int getNumSettings() const override;
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
#include "OneWireSensorHub.h"
2+
3+
#ifdef ENV_INCLUDE_ONEWIRE
4+
5+
#include <Mesh.h>
6+
7+
static SoftwareHalfSerial oneWireSerial(ONEWIRE_PIN);
8+
9+
OneWireSensorHub* OneWireSensorHub::_instance = nullptr;
10+
11+
void OneWireSensorHub::_onewire_callback(const uint8_t pid, const uint8_t sid,
12+
const SNHUBAPI_EVT_E eid, uint8_t* msg, uint16_t len) {
13+
if (_instance) {
14+
_instance->handleEvent(pid, sid, eid, msg, len);
15+
}
16+
}
17+
18+
bool OneWireSensorHub::begin() {
19+
_instance = this;
20+
_found_pid_count = 0;
21+
_has_voltage = false;
22+
23+
for (int i = 0; i < ONEWIRE_MAX_PIDS; i++) {
24+
_found_pids[i] = 0xFF;
25+
}
26+
27+
pinMode(WB_IO2, OUTPUT);
28+
digitalWrite(WB_IO2, HIGH);
29+
delay(100);
30+
31+
oneWireSerial.begin(9600);
32+
RakSNHub_Protocl_API.init(_onewire_callback);
33+
34+
MESH_DEBUG_PRINTLN("OneWire: Scanning for sensor probes (%d ms)...", ONEWIRE_DISCOVERY_TIMEOUT_MS);
35+
36+
unsigned long start = millis();
37+
while ((millis() - start) < ONEWIRE_DISCOVERY_TIMEOUT_MS) {
38+
while (oneWireSerial.available()) {
39+
if (_rxlen < sizeof(_rxbuf)) {
40+
_rxbuf[_rxlen++] = oneWireSerial.read();
41+
}
42+
delay(5);
43+
}
44+
45+
if (_rxlen > 0) {
46+
RakSNHub_Protocl_API.process(_rxbuf, _rxlen);
47+
_rxlen = 0;
48+
}
49+
50+
delay(100);
51+
}
52+
53+
MESH_DEBUG_PRINTLN("OneWire: Discovery complete. Found %d sensor probe(s)", _found_pid_count);
54+
55+
if (_found_pid_count == 0) {
56+
digitalWrite(WB_IO2, LOW);
57+
return false;
58+
}
59+
60+
_next_poll_ms = millis() + 2000;
61+
_current_poll_idx = 0;
62+
63+
return true;
64+
}
65+
66+
void OneWireSensorHub::loop() {
67+
if (_found_pid_count == 0) return;
68+
69+
while (oneWireSerial.available()) {
70+
if (_rxlen < sizeof(_rxbuf)) {
71+
_rxbuf[_rxlen++] = oneWireSerial.read();
72+
} else {
73+
break;
74+
}
75+
_last_rx_time = millis();
76+
}
77+
78+
if (_rxlen > 0 && (millis() - _last_rx_time) >= 10) {
79+
RakSNHub_Protocl_API.process(_rxbuf, _rxlen);
80+
_rxlen = 0;
81+
}
82+
83+
if (millis() >= _next_poll_ms) {
84+
if (_current_poll_idx < _found_pid_count) {
85+
uint8_t pid = _found_pids[_current_poll_idx];
86+
if (pid != 0xFF) {
87+
RakSNHub_Protocl_API.get.data(pid);
88+
MESH_DEBUG_PRINTLN("OneWire: Requested data from PID %d", pid);
89+
}
90+
_current_poll_idx++;
91+
}
92+
93+
if (_current_poll_idx >= _found_pid_count) {
94+
_current_poll_idx = 0;
95+
_next_poll_ms = millis() + ONEWIRE_POLL_INTERVAL_MS;
96+
} else {
97+
// Stagger between PID requests
98+
_next_poll_ms = millis() + 500;
99+
}
100+
}
101+
}
102+
103+
void OneWireSensorHub::handleEvent(uint8_t pid, uint8_t sid, SNHUBAPI_EVT_E eid,
104+
uint8_t* msg, uint16_t len) {
105+
switch (eid) {
106+
case SNHUBAPI_EVT_QSEND:
107+
oneWireSerial.write(msg, len);
108+
break;
109+
110+
case SNHUBAPI_EVT_ADD_PID:
111+
registerPid(msg[0]);
112+
break;
113+
114+
case SNHUBAPI_EVT_ADD_SID:
115+
MESH_DEBUG_PRINTLN("OneWire: Added SID 0x%02X for PID %d", msg[0], pid);
116+
break;
117+
118+
case SNHUBAPI_EVT_SDATA_REQ: {
119+
uint8_t ipso_type = msg[0];
120+
uint16_t val_len = len - 1;
121+
122+
uint8_t ordered[256];
123+
for (uint16_t i = 0; i < val_len; i += 2) {
124+
if (i + 1 < val_len) {
125+
ordered[i] = msg[1 + i + 1];
126+
ordered[i + 1] = msg[1 + i];
127+
} else {
128+
ordered[i] = msg[1 + i];
129+
}
130+
}
131+
MESH_DEBUG_PRINTLN("OneWire: SDATA_REQ SID=0x%02X IPSO=%d len=%d", sid, ipso_type, val_len);
132+
parseSensorData(sid, ipso_type, ordered, val_len);
133+
break;
134+
}
135+
136+
case SNHUBAPI_EVT_REPORT: {
137+
uint8_t ipso_type = msg[0];
138+
uint16_t val_len = len - 1;
139+
MESH_DEBUG_PRINTLN("OneWire: REPORT SID=0x%02X IPSO=%d len=%d", sid, ipso_type, val_len);
140+
parseSensorData(sid, ipso_type, &msg[1], val_len);
141+
break;
142+
}
143+
144+
case SNHUBAPI_EVT_CHKSUM_ERR:
145+
MESH_DEBUG_PRINTLN("OneWire: Checksum error");
146+
break;
147+
148+
case SNHUBAPI_EVT_SEQ_ERR:
149+
MESH_DEBUG_PRINTLN("OneWire: Sequence error");
150+
break;
151+
152+
default:
153+
break;
154+
}
155+
}
156+
157+
void OneWireSensorHub::registerPid(uint8_t pid) {
158+
for (int i = 0; i < ONEWIRE_MAX_PIDS; i++) {
159+
if (_found_pids[i] == pid) {
160+
MESH_DEBUG_PRINTLN("OneWire: PID %d already registered", pid);
161+
return;
162+
}
163+
}
164+
for (int i = 0; i < ONEWIRE_MAX_PIDS; i++) {
165+
if (_found_pids[i] == 0xFF) {
166+
_found_pids[i] = pid;
167+
_found_pid_count++;
168+
MESH_DEBUG_PRINTLN("OneWire: Registered PID %d (total: %d)", pid, _found_pid_count);
169+
return;
170+
}
171+
}
172+
MESH_DEBUG_PRINTLN("OneWire: No slots for PID %d", pid);
173+
}
174+
175+
void OneWireSensorHub::parseSensorData(uint8_t sid, uint8_t ipso_type, uint8_t* data, uint16_t data_len) {
176+
switch (ipso_type) {
177+
case RAK_IPSO_BATTERVALUE: // 116 (3316-3200): battery voltage, 2 bytes, /100
178+
case RAK_IPSO_DC_VOLTAGE: { // 186 (3386-3200): DC voltage, 2 bytes, /100
179+
if (data_len >= 2) {
180+
int16_t raw = ((int16_t)data[0] << 8) | (int16_t)data[1];
181+
_cached_voltage = (float)raw / 100.0f;
182+
_has_voltage = true;
183+
MESH_DEBUG_PRINTLN("OneWire: Battery Voltage = %.2fV (IPSO %d, raw=%d)", _cached_voltage, ipso_type, raw);
184+
}
185+
break;
186+
}
187+
188+
case RAK_IPSO_DC_CURRENT: { // 185 (3385-3200): DC current, 2 bytes, mA
189+
if (data_len >= 2) {
190+
int16_t raw = ((int16_t)data[0] << 8) | (int16_t)data[1];
191+
_cached_current_ma = raw;
192+
_has_current = true;
193+
MESH_DEBUG_PRINTLN("OneWire: Battery Current = %dmA (IPSO %d, raw=%d)", _cached_current_ma, ipso_type, raw);
194+
}
195+
break;
196+
}
197+
198+
case RAK_IPSO_CAPACITY: { // 184 (3384-3200): battery percentage, 1 byte
199+
if (data_len >= 1) {
200+
_cached_battery_pct = data[0];
201+
if (_cached_battery_pct > 100) _cached_battery_pct = 100;
202+
_has_battery_pct = true;
203+
MESH_DEBUG_PRINTLN("OneWire: Battery SOC = %d%% (IPSO %d)", _cached_battery_pct, ipso_type);
204+
}
205+
break;
206+
}
207+
208+
case RAK_IPSO_TEMP_SENSOR: { // 103 (3303-3200): temperature, 2 bytes, /10
209+
if (data_len >= 2) {
210+
int16_t raw = ((int16_t)data[0] << 8) | (int16_t)data[1];
211+
_cached_temperature = (float)raw / 10.0f;
212+
_has_temperature = true;
213+
MESH_DEBUG_PRINTLN("OneWire: Battery Temperature = %.1fC (IPSO %d, raw=%d)", _cached_temperature, ipso_type, raw);
214+
}
215+
break;
216+
}
217+
218+
case RAK_IPSO_SSN: { // 126 (3326-3200): serial number, 3 bytes
219+
if (data_len >= 3) {
220+
_cached_serial = ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | data[2];
221+
_has_serial = true;
222+
MESH_DEBUG_PRINTLN("OneWire: Serial Number = %06lX (IPSO %d)", (unsigned long)_cached_serial, ipso_type);
223+
}
224+
break;
225+
}
226+
227+
case RAK_IPSO_BINARY2BYTE: { // 243 (0xF3): 2-byte binary, SID distinguishes error vs FW version
228+
if (data_len >= 2) {
229+
uint16_t val = ((uint16_t)data[0] << 8) | data[1];
230+
if (sid == 0x19) {
231+
_cached_error = val;
232+
_has_error = true;
233+
MESH_DEBUG_PRINTLN("OneWire: Battery Error = 0x%04X (IPSO %d, SID 0x%02X)", val, ipso_type, sid);
234+
} else if (sid == 0x1A) {
235+
_cached_fw_version = val;
236+
_has_fw_version = true;
237+
MESH_DEBUG_PRINTLN("OneWire: Battery FW Version = v%02d.%02d (IPSO %d, SID 0x%02X)", val >> 8, val & 0xFF, ipso_type, sid);
238+
} else {
239+
MESH_DEBUG_PRINTLN("OneWire: BINARY2BYTE = 0x%04X (IPSO %d, SID 0x%02X)", val, ipso_type, sid);
240+
}
241+
}
242+
break;
243+
}
244+
245+
default:
246+
MESH_DEBUG_PRINTLN("OneWire: Unhandled IPSO %d (len=%d)", ipso_type, data_len);
247+
break;
248+
}
249+
}
250+
251+
bool OneWireSensorHub::hasVoltage() const { return _has_voltage; }
252+
float OneWireSensorHub::getVoltage() const { return _cached_voltage; }
253+
bool OneWireSensorHub::hasCurrent() const { return _has_current; }
254+
float OneWireSensorHub::getCurrent() const { return (float)_cached_current_ma / 100.0f; }
255+
bool OneWireSensorHub::hasBatteryPercent() const { return _has_battery_pct; }
256+
uint8_t OneWireSensorHub::getBatteryPercent() const { return _cached_battery_pct; }
257+
bool OneWireSensorHub::hasTemperature() const { return _has_temperature; }
258+
float OneWireSensorHub::getTemperature() const { return _cached_temperature; }
259+
bool OneWireSensorHub::hasSerialNumber() const { return _has_serial; }
260+
uint32_t OneWireSensorHub::getSerialNumber() const { return _cached_serial; }
261+
bool OneWireSensorHub::hasError() const { return _has_error; }
262+
uint16_t OneWireSensorHub::getError() const { return _cached_error; }
263+
bool OneWireSensorHub::hasFwVersion() const { return _has_fw_version; }
264+
uint16_t OneWireSensorHub::getFwVersion() const { return _cached_fw_version; }
265+
uint8_t OneWireSensorHub::getNumPids() const { return _found_pid_count; }
266+
267+
#endif // ENV_INCLUDE_ONEWIRE

0 commit comments

Comments
 (0)