From b955f1d0a3eda03e4377b7c65fb1971cb64f513a Mon Sep 17 00:00:00 2001 From: lbibass <27320050+lbibass@users.noreply.github.com> Date: Sat, 25 Jul 2026 22:41:01 -0400 Subject: [PATCH] added current and power to telemetry page --- repeater/handler_helpers/protocol_request.py | 38 +++++++++++++--- ...test_handler_helpers_path_protocol_text.py | 44 ++++++++++++++++--- 2 files changed, 70 insertions(+), 12 deletions(-) diff --git a/repeater/handler_helpers/protocol_request.py b/repeater/handler_helpers/protocol_request.py index 296e773..12aa6e5 100644 --- a/repeater/handler_helpers/protocol_request.py +++ b/repeater/handler_helpers/protocol_request.py @@ -287,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: @@ -329,8 +338,21 @@ class ProtocolRequestHelper: except (TypeError, ValueError): 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, else 0.000A.""" + """First configured sensor's current in amps, else 0.000A.""" for reading in readings: if not reading.get("ok"): continue @@ -338,20 +360,22 @@ class ProtocolRequestHelper: current = data.get("current_ma") if current is not None: try: - return float(current) + return float(current) / 1000.0 except (TypeError, ValueError): continue return 0.000 + + @staticmethod def _battery_power(readings) -> int: - """First configured sensor's power, else 0W.""" + """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")/1000 + power = data.get("power_mw") if power is not None: try: - return int(power) + return int(float(power) / 1000.0) except (TypeError, ValueError): continue return 0 diff --git a/tests/test_handler_helpers_path_protocol_text.py b/tests/test_handler_helpers_path_protocol_text.py index 8da7643..cc77ba7 100644 --- a/tests/test_handler_helpers_path_protocol_text.py +++ b/tests/test_handler_helpers_path_protocol_text.py @@ -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.