From aac892a56f3bbb24a26391bb838b824dfdc95241 Mon Sep 17 00:00:00 2001 From: cayossarian Date: Sat, 27 Jul 2024 02:18:46 -0700 Subject: [PATCH 01/16] Addresses issue #13: Other fluid types and air --- src/mopeka_iot_ble/mopeka_types.py | 25 ++ src/mopeka_iot_ble/parser.py | 50 ++- tests/test_parser.py | 491 ++++++++++++++++++++++++++++- 3 files changed, 554 insertions(+), 12 deletions(-) create mode 100644 src/mopeka_iot_ble/mopeka_types.py diff --git a/src/mopeka_iot_ble/mopeka_types.py b/src/mopeka_iot_ble/mopeka_types.py new file mode 100644 index 0000000..4aa9408 --- /dev/null +++ b/src/mopeka_iot_ble/mopeka_types.py @@ -0,0 +1,25 @@ +"""Types for Mopeka IOT BLE advertisements. + +Thanks to https://github.com/spbrogan/mopeka_pro_check for +help decoding the advertisements. + +MIT License applies. +""" + +from enum import Enum + +class MediumType(Enum): + """Enumeration of medium types for tank level measurements.""" + + PROPANE = "propane" + AIR = "air" + FRESH_WATER = "fresh_water" + WASTE_WATER = "waste_water" + LIVE_WELL = "live_well" + BLACK_WATER = "black_water" + RAW_WATER = "raw_water" + GASOLINE = "gasoline" + DIESEL = "diesel" + LNG = "lng" + OIL = "oil" + HYDRAULIC_OIL = "hydraulic_oil" diff --git a/src/mopeka_iot_ble/parser.py b/src/mopeka_iot_ble/parser.py index ad94b0d..57a9c2c 100644 --- a/src/mopeka_iot_ble/parser.py +++ b/src/mopeka_iot_ble/parser.py @@ -9,6 +9,7 @@ from __future__ import annotations import logging + from dataclasses import dataclass from bluetooth_data_tools import short_address @@ -24,13 +25,28 @@ _LOGGER = logging.getLogger(__name__) -# converting sensor value to height - contact Mopeka for other fluids/gases -MOPEKA_TANK_LEVEL_COEFFICIENTS_PROPANE = (0.573045, -0.002822, -0.00000535) +from .mopeka_types import MediumType + + +# converting sensor value to height +MOPEKA_TANK_LEVEL_COEFFICIENTS = { + MediumType.PROPANE: [0.573045, -0.002822, -0.00000535], + MediumType.AIR: [0.153096, 0.000327, -0.000000294], + MediumType.FRESH_WATER: [0.600592, 0.003124, -0.00001368], + MediumType.WASTE_WATER: [0.600592, 0.003124, -0.00001368], + MediumType.LIVE_WELL: [0.600592, 0.003124, -0.00001368], + MediumType.BLACK_WATER: [0.600592, 0.003124, -0.00001368], + MediumType.RAW_WATER: [0.600592, 0.003124, -0.00001368], + MediumType.GASOLINE: [0.7373417462, -0.001978229885, 0.00000202162], + MediumType.DIESEL: [0.7373417462, -0.001978229885, 0.00000202162], + MediumType.LNG: [0.7373417462, -0.001978229885, 0.00000202162], + MediumType.OIL: [0.7373417462, -0.001978229885, 0.00000202162], + MediumType.HYDRAULIC_OIL: [0.7373417462, -0.001978229885, 0.00000202162], +} MOPEKA_MANUFACTURER = 89 MOKPEKA_PRO_SERVICE_UUID = "0000fee5-0000-1000-8000-00805f9b34fb" - @dataclass class MopekaDevice: @@ -76,15 +92,15 @@ def tank_level_to_mm(tank_level: int) -> int: """Convert tank level value to mm.""" return tank_level * 10 - -def tank_level_and_temp_to_mm(tank_level: int, temp: int) -> int: - """Get the tank level in mm.""" +def tank_level_and_temp_to_mm(tank_level: int, temp: int, medium: MediumType = MediumType.PROPANE) -> int: + """Get the tank level in mm for a given fluid type.""" + coefs = MOPEKA_TANK_LEVEL_COEFFICIENTS.get(medium) return int( tank_level * ( - MOPEKA_TANK_LEVEL_COEFFICIENTS_PROPANE[0] - + (MOPEKA_TANK_LEVEL_COEFFICIENTS_PROPANE[1] * temp) - + (MOPEKA_TANK_LEVEL_COEFFICIENTS_PROPANE[2] * (temp**2)) + coefs[0] + + (coefs[1] * temp) + + (coefs[2] * (temp**2)) ) ) @@ -92,9 +108,21 @@ def tank_level_and_temp_to_mm(tank_level: int, temp: int) -> int: class MopekaIOTBluetoothDeviceData(BluetoothData): """Data for Mopeka IOT BLE sensors.""" + def __init__(self) -> None: + super().__init__() + self._medium_type = MediumType.PROPANE + + @property + def medium_type(self) -> MediumType: + return self._medium_type + + @medium_type.setter + def medium_type(self, value: MediumType) ->None: + self._medium_type = value + def _start_update(self, service_info: BluetoothServiceInfo) -> None: """Update from BLE advertisement data.""" - _LOGGER.debug("Parsing Mopeka IOT BLE advertisement data: %s", service_info) + _LOGGER.debug("Parsing Mopeka IOT BLE advertisement data: %s, MediumType is: %s", service_info, self._medium_type) manufacturer_data = service_info.manufacturer_data service_uuids = service_info.service_uuids address = service_info.address @@ -123,7 +151,7 @@ def _start_update(self, service_info: BluetoothServiceInfo) -> None: temp = data[2] & 0x7F temp_celsius = temp_to_celsius(temp) tank_level = ((int(data[4]) << 8) + data[3]) & 0x3FFF - tank_level_mm = tank_level_and_temp_to_mm(tank_level, temp) + tank_level_mm = tank_level_and_temp_to_mm(tank_level, temp, self._medium_type) reading_quality = data[4] >> 6 accelerometer_x = data[8] accelerometer_y = data[9] diff --git a/tests/test_parser.py b/tests/test_parser.py index 5609d9d..bc1e3d9 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -11,7 +11,18 @@ Units, ) -from mopeka_iot_ble.parser import MopekaIOTBluetoothDeviceData, hex +# Consider renaming the hex method to avoid the override complaint +from mopeka_iot_ble.parser import ( # pylint: disable=redefined-builtin + MopekaIOTBluetoothDeviceData, + hex, + battery_to_voltage, + battery_to_percentage, + temp_to_celsius, + tank_level_to_mm, + tank_level_and_temp_to_mm +) + +from mopeka_iot_ble.mopeka_types import MediumType PRO_SERVICE_BAD_QUALITY_INFO = BluetoothServiceInfo( name="", @@ -977,3 +988,481 @@ def test_lippert(): }, events={}, ) + +TDR40_AIR_BAD_QUALITY_INFO = BluetoothServiceInfo( + name="", + address="DA:D8:AC:6A:75:10", + rssi=-60, + manufacturer_data={89: b'\ns@NMju\x10\x7f\x80'}, + service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], + service_data={}, + source="local", +) + +TDR40_AIR_LOW_QUALITY_INFO = BluetoothServiceInfo( + name="", + address="DA:D8:AC:6A:75:10", + rssi=-44, + manufacturer_data={89: b'\x0c`8<\x83*\xea\x8c1\xf8'}, + service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], + service_data={}, + source="local", +) + +TDR40_AIR_GOOD_QUALITY_INFO = BluetoothServiceInfo( + name="", + address="DA:D8:AC:6A:75:10", + rssi=-50, + manufacturer_data={89: b'\nq@}\xd0ju\x10\x80 '}, + service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], + service_data={}, + source="local", +) + +# Test parser specifics +battery_raw = 89 # example battery raw value +temperature_raw = 77 # example temperature raw value +tank_level_raw = 3145 # example tank level raw value +medium_type = MediumType.AIR + +def test_battery_to_voltage(): + voltage = battery_to_voltage(battery_raw) + assert voltage == 2.78125 + +def test_battery_to_percentage(): + percentage = battery_to_percentage(battery_raw) + assert percentage == 89.4 + +def test_temp_to_celsius(): + celsius = temp_to_celsius(temperature_raw) + assert celsius == 37 + +def test_tank_level_to_mm(): + mm = tank_level_to_mm(tank_level_raw) + assert mm == 31450 # tank_level_raw * 10 + +def test_tank_level_and_temp_to_mm(): + tank_level_mm = tank_level_and_temp_to_mm(tank_level_raw, temperature_raw, medium_type) + expected_mm = int( + tank_level_raw + * ( + 0.153096 # coefs[0] for MediumType.AIR + + (0.000327 * temperature_raw) # coefs[1] * temp + + (-0.000000294 * (temperature_raw ** 2)) # coefs[2] * (temp ** 2) + ) + ) + assert tank_level_mm == expected_mm + +def test_parser_with_sample_data(): + parser = MopekaIOTBluetoothDeviceData() + parser.medium_type = medium_type + + sample_data = { + "battery_voltage": battery_to_voltage(battery_raw), + "battery_percentage": battery_to_percentage(battery_raw), + "temperature": temp_to_celsius(temperature_raw), + "tank_level_mm": tank_level_and_temp_to_mm(tank_level_raw, temperature_raw, medium_type), + } + + assert sample_data["battery_voltage"] == 2.78125 + assert sample_data["battery_percentage"] == 89.4 + assert sample_data["temperature"] == 37 + assert sample_data["tank_level_mm"] == int( + tank_level_raw + * ( + 0.153096 # coefs[0] for MediumType.AIR + + (0.000327 * temperature_raw) # coefs[1] * temp + + (-0.000000294 * (temperature_raw ** 2)) # coefs[2] * (temp ** 2) + ) + ) + +# Test entire parser chain +def test_tdr40_air_bad_quality(): + parser = MopekaIOTBluetoothDeviceData() + parser.medium_type = MediumType.AIR + service_info = TDR40_AIR_BAD_QUALITY_INFO + result = parser.update(service_info) + + assert result == SensorUpdate( + title=None, + devices={ + None: SensorDeviceInfo( + name="TD40/TD200 7510", # Corrected name + model="TD40/TD200", # Corrected model + manufacturer="Mopeka IOT", + sw_version=None, + hw_version=None, + ) + }, + entity_descriptions={ + DeviceKey(key="battery_voltage", device_id=None): SensorDescription( + device_key=DeviceKey(key="battery_voltage", device_id=None), + device_class=SensorDeviceClass.VOLTAGE, + native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT, + ), + DeviceKey(key="tank_level", device_id=None): SensorDescription( + device_key=DeviceKey(key="tank_level", device_id=None), + device_class=SensorDeviceClass.DISTANCE, + native_unit_of_measurement=Units.LENGTH_MILLIMETERS, + ), + DeviceKey(key="temperature", device_id=None): SensorDescription( + device_key=DeviceKey(key="temperature", device_id=None), + device_class=SensorDeviceClass.TEMPERATURE, + native_unit_of_measurement=Units.TEMP_CELSIUS, + ), + DeviceKey(key="battery", device_id=None): SensorDescription( + device_key=DeviceKey(key="battery", device_id=None), + device_class=SensorDeviceClass.BATTERY, + native_unit_of_measurement=Units.PERCENTAGE, + ), + DeviceKey(key="signal_strength", device_id=None): SensorDescription( + device_key=DeviceKey(key="signal_strength", device_id=None), + device_class=SensorDeviceClass.SIGNAL_STRENGTH, + native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, + ), + DeviceKey(key="reading_quality", device_id=None): SensorDescription( + device_key=DeviceKey(key="reading_quality", device_id=None), + device_class=None, + native_unit_of_measurement=Units.PERCENTAGE, + ), + DeviceKey(key="reading_quality_raw", device_id=None): SensorDescription( + device_key=DeviceKey(key="reading_quality_raw", device_id=None), + device_class=None, + native_unit_of_measurement=None, + ), + DeviceKey(key="accelerometer_y", device_id=None): SensorDescription( + device_key=DeviceKey(key="accelerometer_y", device_id=None), + device_class=None, + native_unit_of_measurement=None, + ), + DeviceKey(key="accelerometer_x", device_id=None): SensorDescription( + device_key=DeviceKey(key="accelerometer_x", device_id=None), + device_class=None, + native_unit_of_measurement=None, + ), + }, + entity_values={ + DeviceKey(key="battery_voltage", device_id=None): SensorValue( + device_key=DeviceKey(key="battery_voltage", device_id=None), + name="Battery Voltage", + native_value=3.59375, + ), + DeviceKey(key="tank_level", device_id=None): SensorValue( + device_key=DeviceKey(key="tank_level", device_id=None), + name="Tank Level", + native_value=588, + ), + DeviceKey(key="temperature", device_id=None): SensorValue( + device_key=DeviceKey(key="temperature", device_id=None), + name="Temperature", + native_value=24, + ), + DeviceKey(key="battery", device_id=None): SensorValue( + device_key=DeviceKey(key="battery", device_id=None), + name="Battery", + native_value=100, + ), + DeviceKey(key="signal_strength", device_id=None): SensorValue( + device_key=DeviceKey(key="signal_strength", device_id=None), + name="Signal Strength", + native_value=-60, + ), + DeviceKey(key="reading_quality", device_id=None): SensorValue( + device_key=DeviceKey(key="reading_quality", device_id=None), + name="Reading quality", + native_value=33, + ), + DeviceKey(key="reading_quality_raw", device_id=None): SensorValue( + device_key=DeviceKey(key="reading_quality_raw", device_id=None), + name="Reading quality raw", + native_value=1, + ), + DeviceKey(key="accelerometer_y", device_id=None): SensorValue( + device_key=DeviceKey(key="accelerometer_y", device_id=None), + name="Position Y", + native_value=128, + ), + DeviceKey(key="accelerometer_x", device_id=None): SensorValue( + device_key=DeviceKey(key="accelerometer_x", device_id=None), + name="Position X", + native_value=127, + ), + }, + binary_entity_descriptions={ + DeviceKey(key="button_pressed", device_id=None): BinarySensorDescription( + device_key=DeviceKey(key="button_pressed", device_id=None), + device_class=BinarySensorDeviceClass.OCCUPANCY, + ) + }, + binary_entity_values={ + DeviceKey(key="button_pressed", device_id=None): BinarySensorValue( + device_key=DeviceKey(key="button_pressed", device_id=None), + name="Button pressed", + native_value=False, + ) + }, + events={}, + ) + +def test_tdr40_air_low_quality(): + parser = MopekaIOTBluetoothDeviceData() + parser.medium_type = MediumType.AIR + service_info = BluetoothServiceInfo( + name="", + address="DA:D8:AC:6A:75:10", + rssi=-49, + manufacturer_data={89: b'\x0c`8<\x83*\xea\x8c1\xf8'}, + service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], + service_data={}, + source="local", + ) + result = parser.update(service_info) + + assert result == SensorUpdate( + title=None, + devices={ + None: SensorDeviceInfo( + name='Pro Check Universal 7510', # Updated name + model='M1017', # Updated model + manufacturer='Mopeka IOT', + sw_version=None, + hw_version=None, + ) + }, + entity_descriptions={ + DeviceKey(key="battery_voltage", device_id=None): SensorDescription( + device_key=DeviceKey(key="battery_voltage", device_id=None), + device_class=SensorDeviceClass.VOLTAGE, + native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT, + ), + DeviceKey(key="tank_level", device_id=None): SensorDescription( + device_key=DeviceKey(key="tank_level", device_id=None), + device_class=SensorDeviceClass.DISTANCE, + native_unit_of_measurement=Units.LENGTH_MILLIMETERS, + ), + DeviceKey(key="temperature", device_id=None): SensorDescription( + device_key=DeviceKey(key="temperature", device_id=None), + device_class=SensorDeviceClass.TEMPERATURE, + native_unit_of_measurement=Units.TEMP_CELSIUS, + ), + DeviceKey(key="battery", device_id=None): SensorDescription( + device_key=DeviceKey(key="battery", device_id=None), + device_class=SensorDeviceClass.BATTERY, + native_unit_of_measurement=Units.PERCENTAGE, + ), + DeviceKey(key="signal_strength", device_id=None): SensorDescription( + device_key=DeviceKey(key="signal_strength", device_id=None), + device_class=SensorDeviceClass.SIGNAL_STRENGTH, + native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, + ), + DeviceKey(key="reading_quality", device_id=None): SensorDescription( + device_key=DeviceKey(key="reading_quality", device_id=None), + device_class=None, + native_unit_of_measurement=Units.PERCENTAGE, + ), + DeviceKey(key="reading_quality_raw", device_id=None): SensorDescription( + device_key=DeviceKey(key="reading_quality_raw", device_id=None), + device_class=None, + native_unit_of_measurement=None, + ), + DeviceKey(key="accelerometer_y", device_id=None): SensorDescription( + device_key=DeviceKey(key="accelerometer_y", device_id=None), + device_class=None, + native_unit_of_measurement=None, + ), + DeviceKey(key="accelerometer_x", device_id=None): SensorDescription( + device_key=DeviceKey(key="accelerometer_x", device_id=None), + device_class=None, + native_unit_of_measurement=None, + ), + }, + entity_values={ + DeviceKey(key="battery_voltage", device_id=None): SensorValue( + device_key=DeviceKey(key="battery_voltage", device_id=None), + name="Battery Voltage", + native_value=3.0, + ), + DeviceKey(key="tank_level", device_id=None): SensorValue( + device_key=DeviceKey(key="tank_level", device_id=None), + name="Tank Level", + native_value=141, + ), + DeviceKey(key="temperature", device_id=None): SensorValue( + device_key=DeviceKey(key="temperature", device_id=None), + name="Temperature", + native_value=16, + ), + DeviceKey(key="battery", device_id=None): SensorValue( + device_key=DeviceKey(key="battery", device_id=None), + name="Battery", + native_value=100, + ), + DeviceKey(key="signal_strength", device_id=None): SensorValue( + device_key=DeviceKey(key="signal_strength", device_id=None), + name="Signal Strength", + native_value=-49, + ), + DeviceKey(key="reading_quality", device_id=None): SensorValue( + device_key=DeviceKey(key="reading_quality", device_id=None), + name="Reading quality", + native_value=67, + ), + DeviceKey(key="reading_quality_raw", device_id=None): SensorValue( + device_key=DeviceKey(key="reading_quality_raw", device_id=None), + name="Reading quality raw", + native_value=2, + ), + DeviceKey(key="accelerometer_y", device_id=None): SensorValue( + device_key=DeviceKey(key="accelerometer_y", device_id=None), + name="Position Y", + native_value=248, + ), + DeviceKey(key="accelerometer_x", device_id=None): SensorValue( + device_key=DeviceKey(key="accelerometer_x", device_id=None), + name="Position X", + native_value=49, + ), + }, + binary_entity_descriptions={ + DeviceKey(key="button_pressed", device_id=None): BinarySensorDescription( + device_key=DeviceKey(key="button_pressed", device_id=None), + device_class=BinarySensorDeviceClass.OCCUPANCY, + ) + }, + binary_entity_values={ + DeviceKey(key="button_pressed", device_id=None): BinarySensorValue( + device_key=DeviceKey(key="button_pressed", device_id=None), + name="Button pressed", + native_value=False, + ) + }, + events={}, + ) + + +def test_tdr40_air_good_quality(): + parser = MopekaIOTBluetoothDeviceData() + parser.medium_type = MediumType.AIR + service_info = TDR40_AIR_GOOD_QUALITY_INFO + result = parser.update(service_info) + + assert result == SensorUpdate( + title=None, + devices={ + None: SensorDeviceInfo( + name="TD40/TD200 7510", + model="TD40/TD200", + manufacturer="Mopeka IOT", + sw_version=None, + hw_version=None, + ) + }, + entity_descriptions={ + DeviceKey(key="battery_voltage", device_id=None): SensorDescription( + device_key=DeviceKey(key="battery_voltage", device_id=None), + device_class=SensorDeviceClass.VOLTAGE, + native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT, + ), + DeviceKey(key="tank_level", device_id=None): SensorDescription( + device_key=DeviceKey(key="tank_level", device_id=None), + device_class=SensorDeviceClass.DISTANCE, + native_unit_of_measurement=Units.LENGTH_MILLIMETERS, + ), + DeviceKey(key="temperature", device_id=None): SensorDescription( + device_key=DeviceKey(key="temperature", device_id=None), + device_class=SensorDeviceClass.TEMPERATURE, + native_unit_of_measurement=Units.TEMP_CELSIUS, + ), + DeviceKey(key="battery", device_id=None): SensorDescription( + device_key=DeviceKey(key="battery", device_id=None), + device_class=SensorDeviceClass.BATTERY, + native_unit_of_measurement=Units.PERCENTAGE, + ), + DeviceKey(key="signal_strength", device_id=None): SensorDescription( + device_key=DeviceKey(key="signal_strength", device_id=None), + device_class=SensorDeviceClass.SIGNAL_STRENGTH, + native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, + ), + DeviceKey(key="reading_quality", device_id=None): SensorDescription( + device_key=DeviceKey(key="reading_quality", device_id=None), + device_class=None, + native_unit_of_measurement=Units.PERCENTAGE, + ), + DeviceKey(key="reading_quality_raw", device_id=None): SensorDescription( + device_key=DeviceKey(key="reading_quality_raw", device_id=None), + device_class=None, + native_unit_of_measurement=None, + ), + DeviceKey(key="accelerometer_y", device_id=None): SensorDescription( + device_key=DeviceKey(key="accelerometer_y", device_id=None), + device_class=None, + native_unit_of_measurement=None, + ), + DeviceKey(key="accelerometer_x", device_id=None): SensorDescription( + device_key=DeviceKey(key="accelerometer_x", device_id=None), + device_class=None, + native_unit_of_measurement=None, + ), + }, + entity_values={ + DeviceKey(key="battery_voltage", device_id=None): SensorValue( + device_key=DeviceKey(key="battery_voltage", device_id=None), + name="Battery Voltage", + native_value=3.53125, + ), + DeviceKey(key="tank_level", device_id=None): SensorValue( + device_key=DeviceKey(key="tank_level", device_id=None), + name="Tank Level", + native_value=729, + ), + DeviceKey(key="temperature", device_id=None): SensorValue( + device_key=DeviceKey(key="temperature", device_id=None), + name="Temperature", + native_value=24, + ), + DeviceKey(key="battery", device_id=None): SensorValue( + device_key=DeviceKey(key="battery", device_id=None), + name="Battery", + native_value=100, + ), + DeviceKey(key="signal_strength", device_id=None): SensorValue( + device_key=DeviceKey(key="signal_strength", device_id=None), + name="Signal Strength", + native_value=-50, + ), + DeviceKey(key="reading_quality", device_id=None): SensorValue( + device_key=DeviceKey(key="reading_quality", device_id=None), + name="Reading quality", + native_value=100, + ), + DeviceKey(key="reading_quality_raw", device_id=None): SensorValue( + device_key=DeviceKey(key="reading_quality_raw", device_id=None), + name="Reading quality raw", + native_value=3, + ), + DeviceKey(key="accelerometer_y", device_id=None): SensorValue( + device_key=DeviceKey(key="accelerometer_y", device_id=None), + name="Position Y", + native_value=32, + ), + DeviceKey(key="accelerometer_x", device_id=None): SensorValue( + device_key=DeviceKey(key="accelerometer_x", device_id=None), + name="Position X", + native_value=128, + ), + }, + binary_entity_descriptions={ + DeviceKey(key="button_pressed", device_id=None): BinarySensorDescription( + device_key=DeviceKey(key="button_pressed", device_id=None), + device_class=BinarySensorDeviceClass.OCCUPANCY, + ) + }, + binary_entity_values={ + DeviceKey(key="button_pressed", device_id=None): BinarySensorValue( + device_key=DeviceKey(key="button_pressed", device_id=None), + name="Button pressed", + native_value=False, + ) + }, + events={}, + ) From d3b214f1ef98a9b0c608a4d8b67dca1912f662ac Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 27 Jul 2024 09:32:52 +0000 Subject: [PATCH 02/16] chore(pre-commit.ci): auto fixes --- src/mopeka_iot_ble/mopeka_types.py | 1 + src/mopeka_iot_ble/parser.py | 29 +++++++-------- tests/test_parser.py | 59 ++++++++++++++++++------------ 3 files changed, 51 insertions(+), 38 deletions(-) diff --git a/src/mopeka_iot_ble/mopeka_types.py b/src/mopeka_iot_ble/mopeka_types.py index 4aa9408..32c52ed 100644 --- a/src/mopeka_iot_ble/mopeka_types.py +++ b/src/mopeka_iot_ble/mopeka_types.py @@ -8,6 +8,7 @@ from enum import Enum + class MediumType(Enum): """Enumeration of medium types for tank level measurements.""" diff --git a/src/mopeka_iot_ble/parser.py b/src/mopeka_iot_ble/parser.py index 57a9c2c..cab980c 100644 --- a/src/mopeka_iot_ble/parser.py +++ b/src/mopeka_iot_ble/parser.py @@ -9,7 +9,6 @@ from __future__ import annotations import logging - from dataclasses import dataclass from bluetooth_data_tools import short_address @@ -27,7 +26,6 @@ from .mopeka_types import MediumType - # converting sensor value to height MOPEKA_TANK_LEVEL_COEFFICIENTS = { MediumType.PROPANE: [0.573045, -0.002822, -0.00000535], @@ -47,6 +45,7 @@ MOPEKA_MANUFACTURER = 89 MOKPEKA_PRO_SERVICE_UUID = "0000fee5-0000-1000-8000-00805f9b34fb" + @dataclass class MopekaDevice: @@ -92,37 +91,37 @@ def tank_level_to_mm(tank_level: int) -> int: """Convert tank level value to mm.""" return tank_level * 10 -def tank_level_and_temp_to_mm(tank_level: int, temp: int, medium: MediumType = MediumType.PROPANE) -> int: + +def tank_level_and_temp_to_mm( + tank_level: int, temp: int, medium: MediumType = MediumType.PROPANE +) -> int: """Get the tank level in mm for a given fluid type.""" coefs = MOPEKA_TANK_LEVEL_COEFFICIENTS.get(medium) - return int( - tank_level - * ( - coefs[0] - + (coefs[1] * temp) - + (coefs[2] * (temp**2)) - ) - ) + return int(tank_level * (coefs[0] + (coefs[1] * temp) + (coefs[2] * (temp**2)))) class MopekaIOTBluetoothDeviceData(BluetoothData): """Data for Mopeka IOT BLE sensors.""" def __init__(self) -> None: - super().__init__() - self._medium_type = MediumType.PROPANE + super().__init__() + self._medium_type = MediumType.PROPANE @property def medium_type(self) -> MediumType: return self._medium_type @medium_type.setter - def medium_type(self, value: MediumType) ->None: + def medium_type(self, value: MediumType) -> None: self._medium_type = value def _start_update(self, service_info: BluetoothServiceInfo) -> None: """Update from BLE advertisement data.""" - _LOGGER.debug("Parsing Mopeka IOT BLE advertisement data: %s, MediumType is: %s", service_info, self._medium_type) + _LOGGER.debug( + "Parsing Mopeka IOT BLE advertisement data: %s, MediumType is: %s", + service_info, + self._medium_type, + ) manufacturer_data = service_info.manufacturer_data service_uuids = service_info.service_uuids address = service_info.address diff --git a/tests/test_parser.py b/tests/test_parser.py index bc1e3d9..2afc1d8 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -11,19 +11,19 @@ Units, ) +from mopeka_iot_ble.mopeka_types import MediumType + # Consider renaming the hex method to avoid the override complaint -from mopeka_iot_ble.parser import ( # pylint: disable=redefined-builtin +from mopeka_iot_ble.parser import ( # pylint: disable=redefined-builtin MopekaIOTBluetoothDeviceData, - hex, - battery_to_voltage, battery_to_percentage, - temp_to_celsius, + battery_to_voltage, + hex, + tank_level_and_temp_to_mm, tank_level_to_mm, - tank_level_and_temp_to_mm + temp_to_celsius, ) -from mopeka_iot_ble.mopeka_types import MediumType - PRO_SERVICE_BAD_QUALITY_INFO = BluetoothServiceInfo( name="", address="C9:F3:32:E0:F5:09", @@ -989,11 +989,12 @@ def test_lippert(): events={}, ) + TDR40_AIR_BAD_QUALITY_INFO = BluetoothServiceInfo( name="", address="DA:D8:AC:6A:75:10", rssi=-60, - manufacturer_data={89: b'\ns@NMju\x10\x7f\x80'}, + manufacturer_data={89: b"\ns@NMju\x10\x7f\x80"}, service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], service_data={}, source="local", @@ -1003,7 +1004,7 @@ def test_lippert(): name="", address="DA:D8:AC:6A:75:10", rssi=-44, - manufacturer_data={89: b'\x0c`8<\x83*\xea\x8c1\xf8'}, + manufacturer_data={89: b"\x0c`8<\x83*\xea\x8c1\xf8"}, service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], service_data={}, source="local", @@ -1013,7 +1014,7 @@ def test_lippert(): name="", address="DA:D8:AC:6A:75:10", rssi=-50, - manufacturer_data={89: b'\nq@}\xd0ju\x10\x80 '}, + manufacturer_data={89: b"\nq@}\xd0ju\x10\x80 "}, service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], service_data={}, source="local", @@ -1025,34 +1026,42 @@ def test_lippert(): tank_level_raw = 3145 # example tank level raw value medium_type = MediumType.AIR + def test_battery_to_voltage(): voltage = battery_to_voltage(battery_raw) assert voltage == 2.78125 + def test_battery_to_percentage(): percentage = battery_to_percentage(battery_raw) assert percentage == 89.4 + def test_temp_to_celsius(): celsius = temp_to_celsius(temperature_raw) assert celsius == 37 + def test_tank_level_to_mm(): mm = tank_level_to_mm(tank_level_raw) assert mm == 31450 # tank_level_raw * 10 + def test_tank_level_and_temp_to_mm(): - tank_level_mm = tank_level_and_temp_to_mm(tank_level_raw, temperature_raw, medium_type) + tank_level_mm = tank_level_and_temp_to_mm( + tank_level_raw, temperature_raw, medium_type + ) expected_mm = int( tank_level_raw * ( 0.153096 # coefs[0] for MediumType.AIR + (0.000327 * temperature_raw) # coefs[1] * temp - + (-0.000000294 * (temperature_raw ** 2)) # coefs[2] * (temp ** 2) + + (-0.000000294 * (temperature_raw**2)) # coefs[2] * (temp ** 2) ) ) assert tank_level_mm == expected_mm + def test_parser_with_sample_data(): parser = MopekaIOTBluetoothDeviceData() parser.medium_type = medium_type @@ -1061,7 +1070,9 @@ def test_parser_with_sample_data(): "battery_voltage": battery_to_voltage(battery_raw), "battery_percentage": battery_to_percentage(battery_raw), "temperature": temp_to_celsius(temperature_raw), - "tank_level_mm": tank_level_and_temp_to_mm(tank_level_raw, temperature_raw, medium_type), + "tank_level_mm": tank_level_and_temp_to_mm( + tank_level_raw, temperature_raw, medium_type + ), } assert sample_data["battery_voltage"] == 2.78125 @@ -1072,10 +1083,11 @@ def test_parser_with_sample_data(): * ( 0.153096 # coefs[0] for MediumType.AIR + (0.000327 * temperature_raw) # coefs[1] * temp - + (-0.000000294 * (temperature_raw ** 2)) # coefs[2] * (temp ** 2) + + (-0.000000294 * (temperature_raw**2)) # coefs[2] * (temp ** 2) ) ) + # Test entire parser chain def test_tdr40_air_bad_quality(): parser = MopekaIOTBluetoothDeviceData() @@ -1204,6 +1216,7 @@ def test_tdr40_air_bad_quality(): events={}, ) + def test_tdr40_air_low_quality(): parser = MopekaIOTBluetoothDeviceData() parser.medium_type = MediumType.AIR @@ -1211,7 +1224,7 @@ def test_tdr40_air_low_quality(): name="", address="DA:D8:AC:6A:75:10", rssi=-49, - manufacturer_data={89: b'\x0c`8<\x83*\xea\x8c1\xf8'}, + manufacturer_data={89: b"\x0c`8<\x83*\xea\x8c1\xf8"}, service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], service_data={}, source="local", @@ -1222,9 +1235,9 @@ def test_tdr40_air_low_quality(): title=None, devices={ None: SensorDeviceInfo( - name='Pro Check Universal 7510', # Updated name - model='M1017', # Updated model - manufacturer='Mopeka IOT', + name="Pro Check Universal 7510", # Updated name + model="M1017", # Updated model + manufacturer="Mopeka IOT", sw_version=None, hw_version=None, ) @@ -1428,27 +1441,27 @@ def test_tdr40_air_good_quality(): DeviceKey(key="signal_strength", device_id=None): SensorValue( device_key=DeviceKey(key="signal_strength", device_id=None), name="Signal Strength", - native_value=-50, + native_value=-50, ), DeviceKey(key="reading_quality", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality", device_id=None), name="Reading quality", - native_value=100, + native_value=100, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality_raw", device_id=None), name="Reading quality raw", - native_value=3, + native_value=3, ), DeviceKey(key="accelerometer_y", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_y", device_id=None), name="Position Y", - native_value=32, + native_value=32, ), DeviceKey(key="accelerometer_x", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_x", device_id=None), name="Position X", - native_value=128, + native_value=128, ), }, binary_entity_descriptions={ From d804ee4d5e2b63d6f2650a0063f9dbd1d7d066ec Mon Sep 17 00:00:00 2001 From: cayossarian Date: Sat, 27 Jul 2024 03:01:28 -0700 Subject: [PATCH 03/16] flake8 adherence --- src/mopeka_iot_ble/parser.py | 17 +++++++++-------- tests/test_parser.py | 29 +++++++++++++++++++---------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/mopeka_iot_ble/parser.py b/src/mopeka_iot_ble/parser.py index 57a9c2c..6e8ee55 100644 --- a/src/mopeka_iot_ble/parser.py +++ b/src/mopeka_iot_ble/parser.py @@ -8,6 +8,8 @@ from __future__ import annotations +from .mopeka_types import MediumType + import logging from dataclasses import dataclass @@ -24,10 +26,6 @@ _LOGGER = logging.getLogger(__name__) - -from .mopeka_types import MediumType - - # converting sensor value to height MOPEKA_TANK_LEVEL_COEFFICIENTS = { MediumType.PROPANE: [0.573045, -0.002822, -0.00000535], @@ -47,6 +45,7 @@ MOPEKA_MANUFACTURER = 89 MOKPEKA_PRO_SERVICE_UUID = "0000fee5-0000-1000-8000-00805f9b34fb" + @dataclass class MopekaDevice: @@ -92,6 +91,7 @@ def tank_level_to_mm(tank_level: int) -> int: """Convert tank level value to mm.""" return tank_level * 10 + def tank_level_and_temp_to_mm(tank_level: int, temp: int, medium: MediumType = MediumType.PROPANE) -> int: """Get the tank level in mm for a given fluid type.""" coefs = MOPEKA_TANK_LEVEL_COEFFICIENTS.get(medium) @@ -109,20 +109,21 @@ class MopekaIOTBluetoothDeviceData(BluetoothData): """Data for Mopeka IOT BLE sensors.""" def __init__(self) -> None: - super().__init__() - self._medium_type = MediumType.PROPANE + super().__init__() + self._medium_type = MediumType.PROPANE @property def medium_type(self) -> MediumType: return self._medium_type @medium_type.setter - def medium_type(self, value: MediumType) ->None: + def medium_type(self, value: MediumType) -> None: self._medium_type = value def _start_update(self, service_info: BluetoothServiceInfo) -> None: """Update from BLE advertisement data.""" - _LOGGER.debug("Parsing Mopeka IOT BLE advertisement data: %s, MediumType is: %s", service_info, self._medium_type) + _LOGGER.debug("Parsing Mopeka IOT BLE advertisement data: %s, MediumType is: %s", + service_info, self._medium_type) manufacturer_data = service_info.manufacturer_data service_uuids = service_info.service_uuids address = service_info.address diff --git a/tests/test_parser.py b/tests/test_parser.py index bc1e3d9..5ccf956 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -12,7 +12,7 @@ ) # Consider renaming the hex method to avoid the override complaint -from mopeka_iot_ble.parser import ( # pylint: disable=redefined-builtin +from mopeka_iot_ble.parser import ( # pylint: disable=redefined-builtin MopekaIOTBluetoothDeviceData, hex, battery_to_voltage, @@ -989,6 +989,7 @@ def test_lippert(): events={}, ) + TDR40_AIR_BAD_QUALITY_INFO = BluetoothServiceInfo( name="", address="DA:D8:AC:6A:75:10", @@ -1025,22 +1026,27 @@ def test_lippert(): tank_level_raw = 3145 # example tank level raw value medium_type = MediumType.AIR + def test_battery_to_voltage(): voltage = battery_to_voltage(battery_raw) assert voltage == 2.78125 + def test_battery_to_percentage(): percentage = battery_to_percentage(battery_raw) assert percentage == 89.4 + def test_temp_to_celsius(): celsius = temp_to_celsius(temperature_raw) assert celsius == 37 + def test_tank_level_to_mm(): mm = tank_level_to_mm(tank_level_raw) assert mm == 31450 # tank_level_raw * 10 + def test_tank_level_and_temp_to_mm(): tank_level_mm = tank_level_and_temp_to_mm(tank_level_raw, temperature_raw, medium_type) expected_mm = int( @@ -1053,6 +1059,7 @@ def test_tank_level_and_temp_to_mm(): ) assert tank_level_mm == expected_mm + def test_parser_with_sample_data(): parser = MopekaIOTBluetoothDeviceData() parser.medium_type = medium_type @@ -1076,6 +1083,7 @@ def test_parser_with_sample_data(): ) ) + # Test entire parser chain def test_tdr40_air_bad_quality(): parser = MopekaIOTBluetoothDeviceData() @@ -1204,6 +1212,7 @@ def test_tdr40_air_bad_quality(): events={}, ) + def test_tdr40_air_low_quality(): parser = MopekaIOTBluetoothDeviceData() parser.medium_type = MediumType.AIR @@ -1408,47 +1417,47 @@ def test_tdr40_air_good_quality(): DeviceKey(key="battery_voltage", device_id=None): SensorValue( device_key=DeviceKey(key="battery_voltage", device_id=None), name="Battery Voltage", - native_value=3.53125, + native_value=3.53125 ), DeviceKey(key="tank_level", device_id=None): SensorValue( device_key=DeviceKey(key="tank_level", device_id=None), name="Tank Level", - native_value=729, + native_value=729 ), DeviceKey(key="temperature", device_id=None): SensorValue( device_key=DeviceKey(key="temperature", device_id=None), name="Temperature", - native_value=24, + native_value=24 ), DeviceKey(key="battery", device_id=None): SensorValue( device_key=DeviceKey(key="battery", device_id=None), name="Battery", - native_value=100, + native_value=100 ), DeviceKey(key="signal_strength", device_id=None): SensorValue( device_key=DeviceKey(key="signal_strength", device_id=None), name="Signal Strength", - native_value=-50, + native_value=-50 ), DeviceKey(key="reading_quality", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality", device_id=None), name="Reading quality", - native_value=100, + native_value=100 ), DeviceKey(key="reading_quality_raw", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality_raw", device_id=None), name="Reading quality raw", - native_value=3, + native_value=3 ), DeviceKey(key="accelerometer_y", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_y", device_id=None), name="Position Y", - native_value=32, + native_value=32 ), DeviceKey(key="accelerometer_x", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_x", device_id=None), name="Position X", - native_value=128, + native_value=128 ), }, binary_entity_descriptions={ From c5c707fcc64a8764455593b6c716cf0c2ecc1b31 Mon Sep 17 00:00:00 2001 From: cayossarian Date: Sat, 27 Jul 2024 03:08:47 -0700 Subject: [PATCH 04/16] flake8 --- src/mopeka_iot_ble/mopeka_types.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mopeka_iot_ble/mopeka_types.py b/src/mopeka_iot_ble/mopeka_types.py index 4aa9408..7b23418 100644 --- a/src/mopeka_iot_ble/mopeka_types.py +++ b/src/mopeka_iot_ble/mopeka_types.py @@ -1,5 +1,6 @@ """Types for Mopeka IOT BLE advertisements. + Thanks to https://github.com/spbrogan/mopeka_pro_check for help decoding the advertisements. @@ -8,6 +9,7 @@ from enum import Enum + class MediumType(Enum): """Enumeration of medium types for tank level measurements.""" From 5a95db1ee7ba43703daf14937c4305a5753a338a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 27 Jul 2024 10:27:50 +0000 Subject: [PATCH 05/16] chore(pre-commit.ci): auto fixes --- src/mopeka_iot_ble/parser.py | 4 ++-- tests/test_parser.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mopeka_iot_ble/parser.py b/src/mopeka_iot_ble/parser.py index 44efc91..0a9a7ff 100644 --- a/src/mopeka_iot_ble/parser.py +++ b/src/mopeka_iot_ble/parser.py @@ -8,8 +8,6 @@ from __future__ import annotations -from .mopeka_types import MediumType - import logging from dataclasses import dataclass @@ -23,6 +21,8 @@ Units, ) +from .mopeka_types import MediumType + _LOGGER = logging.getLogger(__name__) diff --git a/tests/test_parser.py b/tests/test_parser.py index 4449a3b..2afc1d8 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1421,22 +1421,22 @@ def test_tdr40_air_good_quality(): DeviceKey(key="battery_voltage", device_id=None): SensorValue( device_key=DeviceKey(key="battery_voltage", device_id=None), name="Battery Voltage", - native_value=3.53125 + native_value=3.53125, ), DeviceKey(key="tank_level", device_id=None): SensorValue( device_key=DeviceKey(key="tank_level", device_id=None), name="Tank Level", - native_value=729 + native_value=729, ), DeviceKey(key="temperature", device_id=None): SensorValue( device_key=DeviceKey(key="temperature", device_id=None), name="Temperature", - native_value=24 + native_value=24, ), DeviceKey(key="battery", device_id=None): SensorValue( device_key=DeviceKey(key="battery", device_id=None), name="Battery", - native_value=100 + native_value=100, ), DeviceKey(key="signal_strength", device_id=None): SensorValue( device_key=DeviceKey(key="signal_strength", device_id=None), From 8219bb79f33741cb375c79c495dd8bb6a3103ee2 Mon Sep 17 00:00:00 2001 From: cayossarian Date: Sat, 27 Jul 2024 03:32:12 -0700 Subject: [PATCH 06/16] flake8 pass 2 --- src/mopeka_iot_ble/parser.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/mopeka_iot_ble/parser.py b/src/mopeka_iot_ble/parser.py index 44efc91..0086b88 100644 --- a/src/mopeka_iot_ble/parser.py +++ b/src/mopeka_iot_ble/parser.py @@ -26,8 +26,6 @@ _LOGGER = logging.getLogger(__name__) -from .mopeka_types import MediumType - # converting sensor value to height MOPEKA_TANK_LEVEL_COEFFICIENTS = { MediumType.PROPANE: [0.573045, -0.002822, -0.00000535], From 9e8bd96dc2a072edf34455139315609828b30a6c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 27 Jul 2024 09:32:52 +0000 Subject: [PATCH 07/16] chore(pre-commit.ci): auto fixes --- tests/test_parser.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_parser.py b/tests/test_parser.py index 2afc1d8..e4b8cd2 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -14,6 +14,7 @@ from mopeka_iot_ble.mopeka_types import MediumType # Consider renaming the hex method to avoid the override complaint +from mopeka_iot_ble.parser import ( # pylint: disable=redefined-builtin from mopeka_iot_ble.parser import ( # pylint: disable=redefined-builtin MopekaIOTBluetoothDeviceData, battery_to_percentage, @@ -990,6 +991,7 @@ def test_lippert(): ) + TDR40_AIR_BAD_QUALITY_INFO = BluetoothServiceInfo( name="", address="DA:D8:AC:6A:75:10", @@ -1027,26 +1029,31 @@ def test_lippert(): medium_type = MediumType.AIR + def test_battery_to_voltage(): voltage = battery_to_voltage(battery_raw) assert voltage == 2.78125 + def test_battery_to_percentage(): percentage = battery_to_percentage(battery_raw) assert percentage == 89.4 + def test_temp_to_celsius(): celsius = temp_to_celsius(temperature_raw) assert celsius == 37 + def test_tank_level_to_mm(): mm = tank_level_to_mm(tank_level_raw) assert mm == 31450 # tank_level_raw * 10 + def test_tank_level_and_temp_to_mm(): tank_level_mm = tank_level_and_temp_to_mm( tank_level_raw, temperature_raw, medium_type @@ -1062,6 +1069,7 @@ def test_tank_level_and_temp_to_mm(): assert tank_level_mm == expected_mm + def test_parser_with_sample_data(): parser = MopekaIOTBluetoothDeviceData() parser.medium_type = medium_type @@ -1088,6 +1096,7 @@ def test_parser_with_sample_data(): ) + # Test entire parser chain def test_tdr40_air_bad_quality(): parser = MopekaIOTBluetoothDeviceData() @@ -1217,6 +1226,7 @@ def test_tdr40_air_bad_quality(): ) + def test_tdr40_air_low_quality(): parser = MopekaIOTBluetoothDeviceData() parser.medium_type = MediumType.AIR From 2e5b26aa2bd20ff07c3b03fbd4d84cea8e66a739 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 27 Jul 2024 11:25:49 +0000 Subject: [PATCH 08/16] chore(pre-commit.ci): auto fixes --- tests/test_parser.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_parser.py b/tests/test_parser.py index e4b8cd2..67695e7 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -14,12 +14,16 @@ from mopeka_iot_ble.mopeka_types import MediumType # Consider renaming the hex method to avoid the override complaint -from mopeka_iot_ble.parser import ( # pylint: disable=redefined-builtin +from mopeka_iot_ble.parser import ( + mopeka_iot_ble.parser, # pylint: disable=redefined-builtin +) from mopeka_iot_ble.parser import ( # pylint: disable=redefined-builtin MopekaIOTBluetoothDeviceData, battery_to_percentage, battery_to_voltage, + from, hex, + import, tank_level_and_temp_to_mm, tank_level_to_mm, temp_to_celsius, From 0cc706339af9b2a8e91c4af87eb4880890db7a42 Mon Sep 17 00:00:00 2001 From: cayossarian Date: Sat, 27 Jul 2024 04:57:09 -0700 Subject: [PATCH 09/16] Fixed erroneous changes made by upstream bot --- tests/test_parser.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/tests/test_parser.py b/tests/test_parser.py index 67695e7..45b215f 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -15,15 +15,10 @@ # Consider renaming the hex method to avoid the override complaint from mopeka_iot_ble.parser import ( - mopeka_iot_ble.parser, # pylint: disable=redefined-builtin -) -from mopeka_iot_ble.parser import ( # pylint: disable=redefined-builtin MopekaIOTBluetoothDeviceData, battery_to_percentage, battery_to_voltage, - from, hex, - import, tank_level_and_temp_to_mm, tank_level_to_mm, temp_to_celsius, @@ -995,7 +990,6 @@ def test_lippert(): ) - TDR40_AIR_BAD_QUALITY_INFO = BluetoothServiceInfo( name="", address="DA:D8:AC:6A:75:10", @@ -1033,31 +1027,26 @@ def test_lippert(): medium_type = MediumType.AIR - def test_battery_to_voltage(): voltage = battery_to_voltage(battery_raw) assert voltage == 2.78125 - def test_battery_to_percentage(): percentage = battery_to_percentage(battery_raw) assert percentage == 89.4 - def test_temp_to_celsius(): celsius = temp_to_celsius(temperature_raw) assert celsius == 37 - def test_tank_level_to_mm(): mm = tank_level_to_mm(tank_level_raw) assert mm == 31450 # tank_level_raw * 10 - def test_tank_level_and_temp_to_mm(): tank_level_mm = tank_level_and_temp_to_mm( tank_level_raw, temperature_raw, medium_type @@ -1073,7 +1062,6 @@ def test_tank_level_and_temp_to_mm(): assert tank_level_mm == expected_mm - def test_parser_with_sample_data(): parser = MopekaIOTBluetoothDeviceData() parser.medium_type = medium_type @@ -1100,7 +1088,6 @@ def test_parser_with_sample_data(): ) - # Test entire parser chain def test_tdr40_air_bad_quality(): parser = MopekaIOTBluetoothDeviceData() @@ -1230,7 +1217,6 @@ def test_tdr40_air_bad_quality(): ) - def test_tdr40_air_low_quality(): parser = MopekaIOTBluetoothDeviceData() parser.medium_type = MediumType.AIR From 3f06932a95d6be7a867bce874b0e5dc138043355 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 27 Jul 2024 07:29:20 -0500 Subject: [PATCH 10/16] fix: fail sooner if MediumType is unknown --- src/mopeka_iot_ble/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mopeka_iot_ble/parser.py b/src/mopeka_iot_ble/parser.py index b6e3437..22ae23f 100644 --- a/src/mopeka_iot_ble/parser.py +++ b/src/mopeka_iot_ble/parser.py @@ -95,7 +95,7 @@ def tank_level_and_temp_to_mm( tank_level: int, temp: int, medium: MediumType = MediumType.PROPANE ) -> int: """Get the tank level in mm for a given fluid type.""" - coefs = MOPEKA_TANK_LEVEL_COEFFICIENTS.get(medium) + coefs = MOPEKA_TANK_LEVEL_COEFFICIENTS[medium] return int(tank_level * (coefs[0] + (coefs[1] * temp) + (coefs[2] * (temp**2)))) From d7bc5ce0be5667858992774ffab922046bd60341 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 27 Jul 2024 07:31:17 -0500 Subject: [PATCH 11/16] chore: rename mopeka_types to models --- src/mopeka_iot_ble/__init__.py | 2 ++ src/mopeka_iot_ble/{mopeka_types.py => models.py} | 0 src/mopeka_iot_ble/parser.py | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) rename src/mopeka_iot_ble/{mopeka_types.py => models.py} (100%) diff --git a/src/mopeka_iot_ble/__init__.py b/src/mopeka_iot_ble/__init__.py index 0665115..356b5ed 100644 --- a/src/mopeka_iot_ble/__init__.py +++ b/src/mopeka_iot_ble/__init__.py @@ -23,6 +23,7 @@ ) from .parser import MopekaIOTBluetoothDeviceData +from .models import MediumType __version__ = "0.7.0" @@ -31,6 +32,7 @@ "BinarySensorDeviceClass", "BinarySensorDescription", "BinarySensorValue", + "MediumType", "SensorDescription", "SensorDeviceInfo", "DeviceClass", diff --git a/src/mopeka_iot_ble/mopeka_types.py b/src/mopeka_iot_ble/models.py similarity index 100% rename from src/mopeka_iot_ble/mopeka_types.py rename to src/mopeka_iot_ble/models.py diff --git a/src/mopeka_iot_ble/parser.py b/src/mopeka_iot_ble/parser.py index 22ae23f..51e2394 100644 --- a/src/mopeka_iot_ble/parser.py +++ b/src/mopeka_iot_ble/parser.py @@ -21,7 +21,7 @@ Units, ) -from .mopeka_types import MediumType +from .models import MediumType _LOGGER = logging.getLogger(__name__) From ecdfe4f97babecd0e7e385f0dbeeb6fc3d971e7a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 27 Jul 2024 07:33:04 -0500 Subject: [PATCH 12/16] chore: make coefficients immutable --- src/mopeka_iot_ble/parser.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/mopeka_iot_ble/parser.py b/src/mopeka_iot_ble/parser.py index 51e2394..94c88d2 100644 --- a/src/mopeka_iot_ble/parser.py +++ b/src/mopeka_iot_ble/parser.py @@ -28,18 +28,18 @@ # converting sensor value to height MOPEKA_TANK_LEVEL_COEFFICIENTS = { - MediumType.PROPANE: [0.573045, -0.002822, -0.00000535], - MediumType.AIR: [0.153096, 0.000327, -0.000000294], - MediumType.FRESH_WATER: [0.600592, 0.003124, -0.00001368], - MediumType.WASTE_WATER: [0.600592, 0.003124, -0.00001368], - MediumType.LIVE_WELL: [0.600592, 0.003124, -0.00001368], - MediumType.BLACK_WATER: [0.600592, 0.003124, -0.00001368], - MediumType.RAW_WATER: [0.600592, 0.003124, -0.00001368], - MediumType.GASOLINE: [0.7373417462, -0.001978229885, 0.00000202162], - MediumType.DIESEL: [0.7373417462, -0.001978229885, 0.00000202162], - MediumType.LNG: [0.7373417462, -0.001978229885, 0.00000202162], - MediumType.OIL: [0.7373417462, -0.001978229885, 0.00000202162], - MediumType.HYDRAULIC_OIL: [0.7373417462, -0.001978229885, 0.00000202162], + MediumType.PROPANE: (0.573045, -0.002822, -0.00000535), + MediumType.AIR: (0.153096, 0.000327, -0.000000294), + MediumType.FRESH_WATER: (0.600592, 0.003124, -0.00001368), + MediumType.WASTE_WATER: (0.600592, 0.003124, -0.00001368), + MediumType.LIVE_WELL: (0.600592, 0.003124, -0.00001368), + MediumType.BLACK_WATER: (0.600592, 0.003124, -0.00001368), + MediumType.RAW_WATER: (0.600592, 0.003124, -0.00001368), + MediumType.GASOLINE: (0.7373417462, -0.001978229885, 0.00000202162), + MediumType.DIESEL: (0.7373417462, -0.001978229885, 0.00000202162), + MediumType.LNG: (0.7373417462, -0.001978229885, 0.00000202162), + MediumType.OIL: (0.7373417462, -0.001978229885, 0.00000202162), + MediumType.HYDRAULIC_OIL: (0.7373417462, -0.001978229885, 0.00000202162), } MOPEKA_MANUFACTURER = 89 From e06bd9c1fb2837979d0a73c2058acb7c0d2c9a88 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 27 Jul 2024 07:35:33 -0500 Subject: [PATCH 13/16] chore: set type in constructor --- src/mopeka_iot_ble/parser.py | 13 ++----------- tests/test_parser.py | 14 +++++--------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/src/mopeka_iot_ble/parser.py b/src/mopeka_iot_ble/parser.py index 94c88d2..dc8f7e4 100644 --- a/src/mopeka_iot_ble/parser.py +++ b/src/mopeka_iot_ble/parser.py @@ -10,7 +10,6 @@ import logging from dataclasses import dataclass - from bluetooth_data_tools import short_address from bluetooth_sensor_state_data import BluetoothData from home_assistant_bluetooth import BluetoothServiceInfo @@ -102,17 +101,9 @@ def tank_level_and_temp_to_mm( class MopekaIOTBluetoothDeviceData(BluetoothData): """Data for Mopeka IOT BLE sensors.""" - def __init__(self) -> None: + def __init__(self, medium_type: MediumType = MediumType.PROPANE) -> None: super().__init__() - self._medium_type = MediumType.PROPANE - - @property - def medium_type(self) -> MediumType: - return self._medium_type - - @medium_type.setter - def medium_type(self, value: MediumType) -> None: - self._medium_type = value + self._medium_type = medium_type def _start_update(self, service_info: BluetoothServiceInfo) -> None: """Update from BLE advertisement data.""" diff --git a/tests/test_parser.py b/tests/test_parser.py index 990eeb8..c2e3df4 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -11,7 +11,7 @@ Units, ) -from mopeka_iot_ble.mopeka_types import MediumType +from mopeka_iot_ble import MediumType # Consider renaming the hex method to avoid the override complaint from mopeka_iot_ble.parser import ( @@ -1063,8 +1063,7 @@ def test_tank_level_and_temp_to_mm(): def test_parser_with_sample_data(): - parser = MopekaIOTBluetoothDeviceData() - parser.medium_type = medium_type + assert MopekaIOTBluetoothDeviceData(medium_type) sample_data = { "battery_voltage": battery_to_voltage(battery_raw), @@ -1090,8 +1089,7 @@ def test_parser_with_sample_data(): # Test entire parser chain def test_tdr40_air_bad_quality(): - parser = MopekaIOTBluetoothDeviceData() - parser.medium_type = MediumType.AIR + parser = MopekaIOTBluetoothDeviceData(MediumType.AIR) service_info = TDR40_AIR_BAD_QUALITY_INFO result = parser.update(service_info) @@ -1218,8 +1216,7 @@ def test_tdr40_air_bad_quality(): def test_tdr40_air_low_quality(): - parser = MopekaIOTBluetoothDeviceData() - parser.medium_type = MediumType.AIR + parser = MopekaIOTBluetoothDeviceData(MediumType.AIR) service_info = BluetoothServiceInfo( name="", address="DA:D8:AC:6A:75:10", @@ -1354,8 +1351,7 @@ def test_tdr40_air_low_quality(): def test_tdr40_air_good_quality(): - parser = MopekaIOTBluetoothDeviceData() - parser.medium_type = MediumType.AIR + parser = MopekaIOTBluetoothDeviceData(MediumType.AIR) service_info = TDR40_AIR_GOOD_QUALITY_INFO result = parser.update(service_info) From 9dc698931520490f6ae470d86cc28e2412a9cbfb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 27 Jul 2024 07:39:45 -0500 Subject: [PATCH 14/16] chore: test cleanup --- tests/test_parser.py | 102 ++++++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 40 deletions(-) diff --git a/tests/test_parser.py b/tests/test_parser.py index c2e3df4..7c8f1b8 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -10,7 +10,7 @@ SensorValue, Units, ) - +import pytest from mopeka_iot_ble import MediumType # Consider renaming the hex method to avoid the override complaint @@ -97,6 +97,37 @@ ) +TDR40_AIR_BAD_QUALITY_INFO = BluetoothServiceInfo( + name="", + address="DA:D8:AC:6A:75:10", + rssi=-60, + manufacturer_data={89: b"\ns@NMju\x10\x7f\x80"}, + service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], + service_data={}, + source="local", +) + +TDR40_AIR_LOW_QUALITY_INFO = BluetoothServiceInfo( + name="", + address="DA:D8:AC:6A:75:10", + rssi=-44, + manufacturer_data={89: b"\x0c`8<\x83*\xea\x8c1\xf8"}, + service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], + service_data={}, + source="local", +) + +TDR40_AIR_GOOD_QUALITY_INFO = BluetoothServiceInfo( + name="", + address="DA:D8:AC:6A:75:10", + rssi=-50, + manufacturer_data={89: b"\nq@}\xd0ju\x10\x80 "}, + service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], + service_data={}, + source="local", +) + + def test_hex(): assert ( hex(b"\x08rF\x000\xe0\xf5\t\xf0\xd8") @@ -990,66 +1021,53 @@ def test_lippert(): ) -TDR40_AIR_BAD_QUALITY_INFO = BluetoothServiceInfo( - name="", - address="DA:D8:AC:6A:75:10", - rssi=-60, - manufacturer_data={89: b"\ns@NMju\x10\x7f\x80"}, - service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], - service_data={}, - source="local", -) - -TDR40_AIR_LOW_QUALITY_INFO = BluetoothServiceInfo( - name="", - address="DA:D8:AC:6A:75:10", - rssi=-44, - manufacturer_data={89: b"\x0c`8<\x83*\xea\x8c1\xf8"}, - service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], - service_data={}, - source="local", -) - -TDR40_AIR_GOOD_QUALITY_INFO = BluetoothServiceInfo( - name="", - address="DA:D8:AC:6A:75:10", - rssi=-50, - manufacturer_data={89: b"\nq@}\xd0ju\x10\x80 "}, - service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], - service_data={}, - source="local", +@pytest.mark.parametrize( + "battery_raw, expected_voltage", + [ + (89, 2.78125), + (90, 2.8125), + (91, 2.84375), + (92, 2.875), + (93, 2.90625), + (94, 2.9375), + (95, 2.96875), + (96, 3.0), + (97, 3.03125), + (98, 3.0625), + (99, 3.09375), + (100, 3.125), + ], ) - -# Test parser specifics -battery_raw = 89 # example battery raw value -temperature_raw = 77 # example temperature raw value -tank_level_raw = 3145 # example tank level raw value -medium_type = MediumType.AIR - - -def test_battery_to_voltage(): +def test_battery_to_voltage(battery_raw: int, expected_voltage: float) -> None: voltage = battery_to_voltage(battery_raw) - assert voltage == 2.78125 + assert voltage == expected_voltage def test_battery_to_percentage(): + battery_raw = 89 # example battery raw value percentage = battery_to_percentage(battery_raw) assert percentage == 89.4 def test_temp_to_celsius(): + temperature_raw = 77 # example temperature raw value + celsius = temp_to_celsius(temperature_raw) assert celsius == 37 def test_tank_level_to_mm(): + tank_level_raw = 3145 # example tank level raw value mm = tank_level_to_mm(tank_level_raw) assert mm == 31450 # tank_level_raw * 10 def test_tank_level_and_temp_to_mm(): + temperature_raw = 77 # example temperature raw value + tank_level_raw = 3145 # example tank level raw value + tank_level_mm = tank_level_and_temp_to_mm( - tank_level_raw, temperature_raw, medium_type + tank_level_raw, temperature_raw, MediumType.AIR ) expected_mm = int( tank_level_raw @@ -1063,6 +1081,10 @@ def test_tank_level_and_temp_to_mm(): def test_parser_with_sample_data(): + medium_type = MediumType.AIR + battery_raw = 89 # example battery raw value + tank_level_raw = 3145 # example tank level raw value + temperature_raw = 77 # example temperature raw value assert MopekaIOTBluetoothDeviceData(medium_type) sample_data = { From 51a4b2f4f6a2b06781e40dcd3f5abf35e77881fa Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 27 Jul 2024 07:47:03 -0500 Subject: [PATCH 15/16] chore: remove unneeded dict --- tests/test_parser.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/tests/test_parser.py b/tests/test_parser.py index 7c8f1b8..963dad2 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1086,20 +1086,12 @@ def test_parser_with_sample_data(): tank_level_raw = 3145 # example tank level raw value temperature_raw = 77 # example temperature raw value assert MopekaIOTBluetoothDeviceData(medium_type) - - sample_data = { - "battery_voltage": battery_to_voltage(battery_raw), - "battery_percentage": battery_to_percentage(battery_raw), - "temperature": temp_to_celsius(temperature_raw), - "tank_level_mm": tank_level_and_temp_to_mm( - tank_level_raw, temperature_raw, medium_type - ), - } - - assert sample_data["battery_voltage"] == 2.78125 - assert sample_data["battery_percentage"] == 89.4 - assert sample_data["temperature"] == 37 - assert sample_data["tank_level_mm"] == int( + assert battery_to_voltage(battery_raw) == 2.78125 + assert battery_to_percentage(battery_raw) == 89.4 + assert temp_to_celsius(temperature_raw) == 37 + assert tank_level_and_temp_to_mm( + tank_level_raw, temperature_raw, medium_type + ) == int( tank_level_raw * ( 0.153096 # coefs[0] for MediumType.AIR From 490403bc3163e3200d2d850d20ca20e35a438ba0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 27 Jul 2024 07:48:33 -0500 Subject: [PATCH 16/16] chore: cleanups --- tests/test_parser.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/test_parser.py b/tests/test_parser.py index 963dad2..fe39be4 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -43,6 +43,16 @@ service_data={}, source="local", ) +TDR40_AIR_LOW_QUALITY_INFO_2 = BluetoothServiceInfo( + name="", + address="DA:D8:AC:6A:75:10", + rssi=-49, + manufacturer_data={89: b"\x0c`8<\x83*\xea\x8c1\xf8"}, + service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], + service_data={}, + source="local", +) + PRO_SERVICE_GOOD_QUALITY_INFO = BluetoothServiceInfo( name="", @@ -1231,16 +1241,8 @@ def test_tdr40_air_bad_quality(): def test_tdr40_air_low_quality(): parser = MopekaIOTBluetoothDeviceData(MediumType.AIR) - service_info = BluetoothServiceInfo( - name="", - address="DA:D8:AC:6A:75:10", - rssi=-49, - manufacturer_data={89: b"\x0c`8<\x83*\xea\x8c1\xf8"}, - service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], - service_data={}, - source="local", - ) - result = parser.update(service_info) + + result = parser.update(TDR40_AIR_LOW_QUALITY_INFO_2) assert result == SensorUpdate( title=None,