mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-07-26 19:42:46 +02:00
Merge pull request #375 from lbibass/ina219-telemetry-fix
This commit is contained in:
@@ -23,6 +23,8 @@ from openhop_core.protocol.cayenne_lpp import (
|
||||
encode_relative_humidity,
|
||||
encode_temperature,
|
||||
encode_voltage,
|
||||
encode_current,
|
||||
encode_power
|
||||
)
|
||||
from openhop_core.protocol.constants import TELEM_PERM_ENVIRONMENT
|
||||
|
||||
@@ -166,6 +168,10 @@ class ProtocolRequestHelper:
|
||||
# n_sent_flood, n_sent_direct, n_recv_flood, n_recv_direct,
|
||||
# uint16 err_events, int16 last_snr (×4), uint16 n_direct_dups, n_flood_dups,
|
||||
# uint32 total_rx_air_time_secs, n_recv_errors → 56 bytes
|
||||
|
||||
# Battery Readings: uses first configured sensor that reports bus/pack voltage.
|
||||
readings = self._get_sensor_readings()
|
||||
batt = int(min(max(self._battery_voltage(readings) * 1000, 0), 0xFFFF))
|
||||
|
||||
# Uptime: use engine start_time when available (fixes wrong "20521 days" from time.time())
|
||||
if self.engine and hasattr(self.engine, "start_time"):
|
||||
@@ -223,7 +229,7 @@ class ProtocolRequestHelper:
|
||||
# Pack 56-byte RepeaterStats (layout matches firmware)
|
||||
stats = struct.pack(
|
||||
"<HHhhIIIIIIIIHhHHII",
|
||||
0, # batt_milli_volts (not available on Pi)
|
||||
batt, # battery in mV
|
||||
0, # curr_tx_queue_len (TODO)
|
||||
noise_floor,
|
||||
last_rssi,
|
||||
@@ -281,10 +287,19 @@ class ProtocolRequestHelper:
|
||||
# else 0.0 V (voltage-only floor, mirroring the companion self-telemetry).
|
||||
lpp = bytearray(encode_voltage(TELEM_CHANNEL_SELF, self._battery_voltage(readings)))
|
||||
|
||||
# Environment sensors: each gets the next channel starting at 2, in the
|
||||
# order they are configured (firmware querySensors channel assignment).
|
||||
# INA219 power telemetry and environment sensors: the power telemetry
|
||||
# channels are emitted first, then environment sensors continue on the
|
||||
# next available channel.
|
||||
if perm_mask & TELEM_PERM_ENVIRONMENT:
|
||||
channel = TELEM_CHANNEL_SELF + 1
|
||||
if self._has_sensor_data(readings, "bus_voltage_v"):
|
||||
lpp.extend(encode_voltage(channel, self._battery_voltage(readings)))
|
||||
if self._has_sensor_data(readings, "current_ma"):
|
||||
lpp.extend(encode_current(channel, self._battery_current(readings)))
|
||||
if self._has_sensor_data(readings, "power_mw"):
|
||||
lpp.extend(encode_power(channel, self._battery_power(readings)))
|
||||
channel += 1
|
||||
|
||||
for reading in readings:
|
||||
entry = self._encode_environment_reading(channel, reading)
|
||||
if entry:
|
||||
@@ -324,6 +339,47 @@ class ProtocolRequestHelper:
|
||||
continue
|
||||
return 0.0
|
||||
|
||||
@staticmethod
|
||||
def _has_sensor_data(readings, key: str) -> bool:
|
||||
"""Return True when any reading exposes the requested sensor field."""
|
||||
for reading in readings:
|
||||
if not reading.get("ok"):
|
||||
continue
|
||||
data = reading.get("data") or {}
|
||||
if data.get(key) is not None:
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _battery_current(readings) -> float:
|
||||
"""First configured sensor's current in amps, else 0.000A."""
|
||||
for reading in readings:
|
||||
if not reading.get("ok"):
|
||||
continue
|
||||
data = reading.get("data") or {}
|
||||
current = data.get("current_ma")
|
||||
if current is not None:
|
||||
try:
|
||||
return float(current) / 1000.0
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
return 0.000
|
||||
|
||||
@staticmethod
|
||||
def _battery_power(readings) -> int:
|
||||
"""First configured sensor's power in watts, else 0W."""
|
||||
for reading in readings:
|
||||
if not reading.get("ok"):
|
||||
continue
|
||||
data = reading.get("data") or {}
|
||||
power = data.get("power_mw")
|
||||
if power is not None:
|
||||
try:
|
||||
return int(float(power) / 1000.0)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def _encode_environment_reading(channel: int, reading) -> bytes:
|
||||
"""Encode a temperature/humidity reading as CayenneLPP, or b"" if none.
|
||||
|
||||
@@ -682,6 +682,8 @@ from openhop_core.node.handlers.protocol_request import ( # noqa: E402
|
||||
)
|
||||
from openhop_core.protocol.cayenne_lpp import ( # noqa: E402
|
||||
TELEM_CHANNEL_SELF,
|
||||
encode_current,
|
||||
encode_power,
|
||||
encode_relative_humidity,
|
||||
encode_temperature,
|
||||
encode_voltage,
|
||||
@@ -722,8 +724,11 @@ def test_telemetry_base_voltage_from_ups_sensor():
|
||||
|
||||
lpp = helper._handle_get_telemetry(admin, 0, b"\x00")
|
||||
|
||||
# No environment values on the UPS reading -> voltage entry only.
|
||||
assert lpp == encode_voltage(TELEM_CHANNEL_SELF, 12.6)
|
||||
# Base voltage stays on channel 1, and the additional INA219 voltage view
|
||||
# is emitted on the next channel when environment telemetry is allowed.
|
||||
assert lpp == encode_voltage(TELEM_CHANNEL_SELF, 12.6) + encode_voltage(
|
||||
TELEM_CHANNEL_SELF + 1, 12.6
|
||||
)
|
||||
|
||||
|
||||
def test_telemetry_admin_full_mask_includes_environment_sensors():
|
||||
@@ -734,7 +739,7 @@ def test_telemetry_admin_full_mask_includes_environment_sensors():
|
||||
"""
|
||||
sm = _FakeSensorManager(
|
||||
[
|
||||
_reading(bus_voltage_v=12.6),
|
||||
_reading(bus_voltage_v=12.6, current_ma=500.0, power_mw=6000.0),
|
||||
_reading(temperature_c=21.5, humidity_pct=55.0),
|
||||
]
|
||||
)
|
||||
@@ -748,8 +753,11 @@ def test_telemetry_admin_full_mask_includes_environment_sensors():
|
||||
|
||||
expected = (
|
||||
encode_voltage(TELEM_CHANNEL_SELF, 12.6)
|
||||
+ encode_temperature(TELEM_CHANNEL_SELF + 1, 21.5)
|
||||
+ encode_relative_humidity(TELEM_CHANNEL_SELF + 1, 55.0)
|
||||
+ encode_voltage(TELEM_CHANNEL_SELF + 1, 12.6)
|
||||
+ encode_current(TELEM_CHANNEL_SELF + 2, 0.5)
|
||||
+ encode_power(TELEM_CHANNEL_SELF + 3, 6)
|
||||
+ encode_temperature(TELEM_CHANNEL_SELF + 4, 21.5)
|
||||
+ encode_relative_humidity(TELEM_CHANNEL_SELF + 4, 55.0)
|
||||
)
|
||||
assert lpp == expected
|
||||
|
||||
@@ -767,6 +775,32 @@ def test_telemetry_guest_forced_to_base_only():
|
||||
assert lpp == encode_voltage(TELEM_CHANNEL_SELF, 0.0)
|
||||
|
||||
|
||||
def test_telemetry_includes_ina219_power_channels_before_environment_sensors():
|
||||
"""INA219 voltage/current/power values are emitted before env sensors."""
|
||||
sm = _FakeSensorManager(
|
||||
[
|
||||
_reading(bus_voltage_v=12.6, current_ma=500.0, power_mw=6000.0),
|
||||
_reading(temperature_c=21.5, humidity_pct=55.0),
|
||||
]
|
||||
)
|
||||
helper = ProtocolRequestHelper(
|
||||
identity_manager=MagicMock(), packet_injector=AsyncMock(), sensor_manager=sm
|
||||
)
|
||||
admin = SimpleNamespace(is_guest=lambda: False)
|
||||
|
||||
lpp = helper._handle_get_telemetry(admin, 0, b"\x00")
|
||||
|
||||
expected = (
|
||||
encode_voltage(TELEM_CHANNEL_SELF, 12.6)
|
||||
+ encode_voltage(TELEM_CHANNEL_SELF + 1, 12.6)
|
||||
+ encode_current(TELEM_CHANNEL_SELF + 2, 0.5)
|
||||
+ encode_power(TELEM_CHANNEL_SELF + 3, 6)
|
||||
+ encode_temperature(TELEM_CHANNEL_SELF + 4, 21.5)
|
||||
+ encode_relative_humidity(TELEM_CHANNEL_SELF + 4, 55.0)
|
||||
)
|
||||
assert lpp == expected
|
||||
|
||||
|
||||
def test_telemetry_inverse_mask_gates_environment():
|
||||
"""An inverse mask that clears the environment bit strips env sensors.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user