Open EdwardBetts opened 3 weeks ago
I think the first byte is wrong. b"\x43
means that BTHome should expect an encrypted message, but the actual message doesn't look encrypted at all. More info on encryption of the bthome format can be found here.
Anyway if you change the first byte to b"\x40
, you should be fine.
b"\x40\x06\xFE\x70"
should result in 289,26 kg (70FE in hex = 28926 in dec, x 0,01 = 289,26 kg).
If I look at the original test set, there is a different byte string and value in the test set b"\x40\x06\x5E\x1F"
. (1F5E in hex = 8030 in dec, x 0,01 = 80,3 kg).
Just to be clear, the test passes on little-endian architectures.
I'm a bit surprised that the test passes. But if you change the b"\x43
to b"\x40
, does it still pass the test? (as it shouldn't to my opinion).
I tried changing b"\x43
to b"\x40
and the test still fails, but with different output. Here's the test run.
============================= test session starts ==============================
platform linux -- Python 3.12.5, pytest-8.3.2, pluggy-1.5.0 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /home/edward/src/bthome-ble/bthome-ble-3.10.0
configfile: pyproject.toml
plugins: cov-5.0.0, typeguard-4.3.0
collecting ... collected 113 items / 111 deselected / 2 selected
tests/test_parser_v1.py::test_bthome_mass_kilograms FAILED [ 50%]
tests/test_parser_v2.py::test_bthome_mass_kilograms PASSED [100%]
=================================== FAILURES ===================================
__________________________ test_bthome_mass_kilograms __________________________
caplog = <_pytest.logging.LogCaptureFixture object at 0x3ffb09b55b0>
def test_bthome_mass_kilograms(caplog):
"""Test BTHome parser for mass reading in kilograms without encryption."""
data_string = b"\x40\x06\xFE\x70"
advertisement = bytes_to_service_info(
data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2"
)
device = BTHomeBluetoothDeviceData()
> assert device.update(advertisement) == SensorUpdate(
title="TEST DEVICE 18B2",
devices={
None: SensorDeviceInfo(
name="TEST DEVICE 18B2",
manufacturer=None,
model="BTHome sensor",
sw_version="BTHome BLE v1",
hw_version=None,
)
},
entity_descriptions={
KEY_MASS: SensorDescription(
device_key=KEY_MASS,
device_class=SensorDeviceClass.MASS,
native_unit_of_measurement=Units.MASS_KILOGRAMS,
),
KEY_SIGNAL_STRENGTH: SensorDescription(
device_key=KEY_SIGNAL_STRENGTH,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
KEY_MASS: SensorValue(
device_key=KEY_MASS, name="Mass", native_value=102.24
),
KEY_SIGNAL_STRENGTH: SensorValue(
device_key=KEY_SIGNAL_STRENGTH, name="Signal Strength", native_value=-60
),
},
)
E AssertionError: assert SensorUpdate(title='TEST DEVICE 18B2', devices={None: SensorDeviceInfo(name='TEST DEVICE 18B2', model='BTHome sensor', manufacturer=None, sw_version='BTHome BLE v1', hw_version=None)}, entity_descriptions={DeviceKey(key='signal_strength', device_id=None): SensorDescription(device_key=DeviceKey(key='signal_strength', device_id=None), device_class=<SensorDeviceClass.SIGNAL_STRENGTH: 'signal_strength'>, native_unit_of_measurement=<Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT: 'dBm'>)}, entity_values={DeviceKey(key='signal_strength', device_id=None): SensorValue(device_key=DeviceKey(key='signal_strength', device_id=None), name='Signal Strength', native_value=-60)}, binary_entity_descriptions={}, binary_entity_values={}, events={}) == SensorUpdate(title='TEST DEVICE 18B2', devices={None: SensorDeviceInfo(name='TEST DEVICE 18B2', model='BTHome sensor', manufacturer=None, sw_version='BTHome BLE v1', hw_version=None)}, entity_descriptions={DeviceKey(key='mass', device_id=None): SensorDescription(device_key=DeviceKey(key='mass', device_id=None), device_class=<SensorDeviceClass.MASS: 'mass'>, native_unit_of_measurement=<Units.MASS_KILOGRAMS: 'kg'>), DeviceKey(key='signal_strength', device_id=None): SensorDescription(device_key=DeviceKey(key='signal_strength', device_id=None), device_class=<SensorDeviceClass.SIGNAL_STRENGTH: 'signal_strength'>, native_unit_of_measurement=<Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT: 'dBm'>)}, entity_values={DeviceKey(key='mass', device_id=None): SensorValue(device_key=DeviceKey(key='mass', device_id=None), name='Mass', native_value=102.24), DeviceKey(key='signal_strength', device_id=None): SensorValue(device_key=DeviceKey(key='signal_strength', device_id=None), name='Signal Strength', native_value=-60)}, binary_entity_descriptions={}, binary_entity_values={}, events={})
E
E Matching attributes:
E ['title',
E 'devices',
E 'binary_entity_descriptions',
E 'binary_entity_values',
E 'events']
E Differing attributes:
E ['entity_descriptions', 'entity_values']
E
E Drill down into differing attribute entity_descriptions:
E entity_descriptions: {DeviceKey(key='signal_strength', device_id=None): SensorDescription(device_key=DeviceKey(key='signal_strength', device_id=None), device_class=<SensorDeviceClass.SIGNAL_STRENGTH: 'signal_strength'>, native_unit_of_measurement=<Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT: 'dBm'>)} != {DeviceKey(key='mass', device_id=None): SensorDescription(device_key=DeviceKey(key='mass', device_id=None), device_class=<SensorDeviceClass.MASS: 'mass'>, native_unit_of_measurement=<Units.MASS_KILOGRAMS: 'kg'>), DeviceKey(key='signal_strength', device_id=None): SensorDescription(device_key=DeviceKey(key='signal_strength', device_id=None), device_class=<SensorDeviceClass.SIGNAL_STRENGTH: 'signal_strength'>, native_unit_of_measurement=<Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT: 'dBm'>)}
E Common items:
E {DeviceKey(key='signal_strength', device_id=None): SensorDescription(device_key=DeviceKey(key='signal_strength',
E device_id=None),
E device_class=<SensorDeviceClass.SIGNAL_STRENGTH: 'signal_strength'>,
E native_unit_of_measurement=<Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT: 'dBm'>)}
E Right contains 1 more item:
E {DeviceKey(key='mass', device_id=None): SensorDescription(device_key=DeviceKey(key='mass',
E device_id=None),
E device_class=<SensorDeviceClass.MASS: 'mass'>,
E native_unit_of_measurement=<Units.MASS_KILOGRAMS: 'kg'>)}
E
E Full diff:
E {
E - DeviceKey(key='mass', device_id=None): SensorDescription(
E - device_key=DeviceKey(
E - key='mass',
E - device_id=None,
E - ),
E - device_class=<SensorDeviceClass.MASS: 'mass'>,
E - native_unit_of_measurement=<Units.MASS_KILOGRAMS: 'kg'>,
E - ),
E DeviceKey(key='signal_strength', device_id=None): SensorDescription(
E device_key=DeviceKey(
E key='signal_strength',
E device_id=None,
E ),
E device_class=<SensorDeviceClass.SIGNAL_STRENGTH: 'signal_strength'>,
E native_unit_of_measurement=<Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT: 'dBm'>,
E ),
E }
E
E Drill down into differing attribute entity_values:
E entity_values: {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='mass', device_id=None): SensorValue(device_key=DeviceKey(key='mass', device_id=None), name='Mass', native_value=102.24), DeviceKey(key='signal_strength', device_id=None): SensorValue(device_key=DeviceKey(key='signal_strength', device_id=None), name='Signal Strength', native_value=-60)}
E Common items:
E {DeviceKey(key='signal_strength', device_id=None): SensorValue(device_key=DeviceKey(key='signal_strength',
E device_id=None),
E name='Signal '
E 'Strength',
E native_value=-60)}
E Right contains 1 more item:
E {DeviceKey(key='mass', device_id=None): SensorValue(device_key=DeviceKey(key='mass',
E device_id=None),
E name='Mass',
E native_value=102.24)}
E
E Full diff:
E {
E - DeviceKey(key='mass', device_id=None): SensorValue(
E - device_key=DeviceKey(
E - key='mass',
E - device_id=None,
E - ),
E - name='Mass',
E - native_value=102.24,
E - ),
E DeviceKey(key='signal_strength', device_id=None): SensorValue(
E device_key=DeviceKey(
E key='signal_strength',
E device_id=None,
E ),
E name='Signal Strength',
E native_value=-60,
E ),
E }
tests/test_parser_v1.py:453: AssertionError
------------------------------ Captured log call -------------------------------
DEBUG bthome_ble.parser:parser.py:185 Parsing BTHome BLE advertisement data: <BluetoothServiceInfoBleak name=TEST DEVICE address=A4:C1:38:8D:18:B2 rssi=-60 manufacturer_data={} service_data={'0000181c-0000-1000-8000-00805f9b34fb': b'@\x06\xfep'} service_uuids=['0000181c-0000-1000-8000-00805f9b34fb'] source= connectable=False time=1709331995.5181565 tx_power=None>
DEBUG bthome_ble.parser:parser.py:450 TEST DEVICE 18B2: Invalid payload data length found with length 0, payload: 4006fe70
DEBUG bthome_ble.parser:parser.py:458 TEST DEVICE 18B2: Invalid payload data length, payload: 4006fe70
=========================== short test summary info ============================
FAILED tests/test_parser_v1.py::test_bthome_mass_kilograms - AssertionError: assert SensorUpdate(title='TEST DEVICE 18B2', devices={None: SensorDeviceInfo(name='TEST DEVICE 18B2', model='BTHome sensor', manufacturer=None, sw_version='BTHome BLE v1', hw_version=None)}, entity_descriptions={DeviceKey(key='signal_strength', device_id=None): SensorDescription(device_key=DeviceKey(key='signal_strength', device_id=None), device_class=<SensorDeviceClass.SIGNAL_STRENGTH: 'signal_strength'>, native_unit_of_measurement=<Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT: 'dBm'>)}, entity_values={DeviceKey(key='signal_strength', device_id=None): SensorValue(device_key=DeviceKey(key='signal_strength', device_id=None), name='Signal Strength', native_value=-60)}, binary_entity_descriptions={}, binary_entity_values={}, events={}) == SensorUpdate(title='TEST DEVICE 18B2', devices={None: SensorDeviceInfo(name='TEST DEVICE 18B2', model='BTHome sensor', manufacturer=None, sw_version='BTHome BLE v1', hw_version=None)}, entity_descriptions={DeviceKey(key='mass', device_id=None): SensorDescription(device_key=DeviceKey(key='mass', device_id=None), device_class=<SensorDeviceClass.MASS: 'mass'>, native_unit_of_measurement=<Units.MASS_KILOGRAMS: 'kg'>), DeviceKey(key='signal_strength', device_id=None): SensorDescription(device_key=DeviceKey(key='signal_strength', device_id=None), device_class=<SensorDeviceClass.SIGNAL_STRENGTH: 'signal_strength'>, native_unit_of_measurement=<Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT: 'dBm'>)}, entity_values={DeviceKey(key='mass', device_id=None): SensorValue(device_key=DeviceKey(key='mass', device_id=None), name='Mass', native_value=102.24), DeviceKey(key='signal_strength', device_id=None): SensorValue(device_key=DeviceKey(key='signal_strength', device_id=None), name='Signal Strength', native_value=-60)}, binary_entity_descriptions={}, binary_entity_values={}, events={})
Matching attributes:
['title',
'devices',
'binary_entity_descriptions',
'binary_entity_values',
'events']
Differing attributes:
['entity_descriptions', 'entity_values']
Drill down into differing attribute entity_descriptions:
entity_descriptions: {DeviceKey(key='signal_strength', device_id=None): SensorDescription(device_key=DeviceKey(key='signal_strength', device_id=None), device_class=<SensorDeviceClass.SIGNAL_STRENGTH: 'signal_strength'>, native_unit_of_measurement=<Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT: 'dBm'>)} != {DeviceKey(key='mass', device_id=None): SensorDescription(device_key=DeviceKey(key='mass', device_id=None), device_class=<SensorDeviceClass.MASS: 'mass'>, native_unit_of_measurement=<Units.MASS_KILOGRAMS: 'kg'>), DeviceKey(key='signal_strength', device_id=None): SensorDescription(device_key=DeviceKey(key='signal_strength', device_id=None), device_class=<SensorDeviceClass.SIGNAL_STRENGTH: 'signal_strength'>, native_unit_of_measurement=<Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT: 'dBm'>)}
Common items:
{DeviceKey(key='signal_strength', device_id=None): SensorDescription(device_key=DeviceKey(key='signal_strength',
device_id=None),
device_class=<SensorDeviceClass.SIGNAL_STRENGTH: 'signal_strength'>,
native_unit_of_measurement=<Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT: 'dBm'>)}
Right contains 1 more item:
{DeviceKey(key='mass', device_id=None): SensorDescription(device_key=DeviceKey(key='mass',
device_id=None),
device_class=<SensorDeviceClass.MASS: 'mass'>,
native_unit_of_measurement=<Units.MASS_KILOGRAMS: 'kg'>)}
Full diff:
{
- DeviceKey(key='mass', device_id=None): SensorDescription(
- device_key=DeviceKey(
- key='mass',
- device_id=None,
- ),
- device_class=<SensorDeviceClass.MASS: 'mass'>,
- native_unit_of_measurement=<Units.MASS_KILOGRAMS: 'kg'>,
- ),
DeviceKey(key='signal_strength', device_id=None): SensorDescription(
device_key=DeviceKey(
key='signal_strength',
device_id=None,
),
device_class=<SensorDeviceClass.SIGNAL_STRENGTH: 'signal_strength'>,
native_unit_of_measurement=<Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT: 'dBm'>,
),
}
Drill down into differing attribute entity_values:
entity_values: {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='mass', device_id=None): SensorValue(device_key=DeviceKey(key='mass', device_id=None), name='Mass', native_value=102.24), DeviceKey(key='signal_strength', device_id=None): SensorValue(device_key=DeviceKey(key='signal_strength', device_id=None), name='Signal Strength', native_value=-60)}
Common items:
{DeviceKey(key='signal_strength', device_id=None): SensorValue(device_key=DeviceKey(key='signal_strength',
device_id=None),
name='Signal '
'Strength',
native_value=-60)}
Right contains 1 more item:
{DeviceKey(key='mass', device_id=None): SensorValue(device_key=DeviceKey(key='mass',
device_id=None),
name='Mass',
native_value=102.24)}
Full diff:
{
- DeviceKey(key='mass', device_id=None): SensorValue(
- device_key=DeviceKey(
- key='mass',
- device_id=None,
- ),
- name='Mass',
- native_value=102.24,
- ),
DeviceKey(key='signal_strength', device_id=None): SensorValue(
device_key=DeviceKey(
key='signal_strength',
device_id=None,
),
name='Signal Strength',
native_value=-60,
),
}
================= 1 failed, 1 passed, 111 deselected in 0.15s ==================
And this is the unmodified test passing on amd64.
$ python3 -mpytest -k test_bthome_mass_kilograms --no-cov
============================= test session starts ==============================
platform linux -- Python 3.12.5, pytest-8.3.2, pluggy-1.5.0 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /home/edward/src/debian/ha/bthome-ble/bthome-ble
configfile: pyproject.toml
plugins: cov-5.0.0, respx-0.21.1, time-machine-2.13.0, requests_mock-1.12.1, anyio-4.4.0, aresponses-3.0.0, typeguard-4.3.0, asyncio-0.20.3, Faker-26.0.0
asyncio: mode=Mode.STRICT
collecting ... collected 112 items / 110 deselected / 2 selected
tests/test_parser_v1.py::test_bthome_mass_kilograms PASSED [ 50%]
tests/test_parser_v2.py::test_bthome_mass_kilograms PASSED [100%]
====================== 2 passed, 110 deselected in 0.10s =======================
$
I see that it fails on the V1 format, not on the V2 format. The V1 format is deprecated but is explained here. https://bthome.io/v1/ The first byte is defining the length and data format, I think it goes wrong in that part.
When I run the test suite on the s390x architecture one of the tests fails.
I'm guessing this bug is caused by s390x being big-endian.
I'm testing version 3.10.0 of bthome-ble.
Here's the test failure: