diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c43c1b1..1f9d3e7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ Look through the GitHub issues for features. Anything tagged with "enhancement" ### Write Documentation -BThome BLE could always use more documentation, whether as part of the official BThome BLE docs, in docstrings, or even on the web in blog posts, articles, and such. +BTHome BLE could always use more documentation, whether as part of the official BTHome BLE docs, in docstrings, or even on the web in blog posts, articles, and such. ### Submit Feedback diff --git a/README.md b/README.md index 1a47fce..cb3fffa 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# BThome BLE +# BTHome BLE
-BLE parser for sensors that support the BThome BLE format. +BLE parser for sensors that support the BTHome BLE format. ## Installation @@ -40,7 +40,7 @@ Install this via pip (or your favourite package manager): ## BThome BLE format -More detailed information about the BThome BLE format can be found on the [usage page](https://github.com/Bluetooth-Devices/bthome-ble/blob/main/docs/source/usage.md) +More detailed information about the BTHome BLE format can be found on the [usage page](https://github.com/Bluetooth-Devices/bthome-ble/blob/main/docs/source/usage.md) ## Contributors ✨ diff --git a/docs/source/conf.py b/docs/source/conf.py index 21d0eab..8494eab 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -17,7 +17,7 @@ # -- Project information ----------------------------------------------------- -project = "BThome BLE" +project = "BTHome BLE" copyright = "2022, E. Klamer" author = "E. Klamer" diff --git a/docs/source/index.md b/docs/source/index.md index 2c6bf10..9d6d417 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -1,4 +1,4 @@ -# Welcome to BThome BLE documentation! +# Welcome to BTHome BLE documentation! ```{toctree} :caption: Installation & Usage diff --git a/docs/source/usage.md b/docs/source/usage.md index bc52650..bcfc096 100644 --- a/docs/source/usage.md +++ b/docs/source/usage.md @@ -1,160 +1,3 @@ -# Usage BThome for DIY sensors +# Usage BTHome for DIY sensors -## Introduction - -The BThome BLE format was originally developed as HA BLE for the custom Home Assistant integration "BLE monitor", and is soon also available for the official Home Assistant Bluetooth integration. -The BThome BLE format allows you to create for your own DIY BLE sensors, which can be read by the Home Assistant Bluetooth integration. -This format tries to provide an energy effective, but still flexible BLE format that can be customized to your own needs. -A proof of concept is the latest [ATC firmware](https://github.com/pvvx/ATC_MiThermometer), which is supporting the BThome BLE format (firmware version 3.7 and up). Note that in firmware 3.7 it is called `HA BLE` format. - -To use this package, import it: - -```python -import bthome_ble -``` - -## The BThome BLE format - -The `BThome BLE` format can best be explained with an example. A BLE advertisement is a long message with bytes (bytestring). - -``` -043E1B02010000A5808FE648540F0201060B161C182302C4090303BF13CC -``` - -This message is split up in three parts, the **header**, the **advertising payload** and an **RSSI value** - -- Header: `043E1B02010000A5808FE648540F` -- Adverting payload `0201060B161C182302C4090303BF13` -- RSSI value `CC` - -## Header - -The first part `043E1B02010000A5808FE648540F` is the header of the message and contains, amongst others - -- the length of the message in the 3rd byte (`0x1B` in hex is 27 in decimals, meaning 27 bytes after the third byte = 30 bytes in total) -- the MAC address in reversed order in byte 8-13 (`A5808FE64854`, in reversed order, this corresponds to a MAC address `54:48:E6:8F:80:A5`) -- the length of the advertising payload in byte 14 (`0x0F` = 15) - -## Advertising payload - -The second part `0201060B161C182302C4090303BF13` contains the advertising payload, and can consist of one or more **Advertising Data (AD) elements**. Each element contains the following: - -- 1st byte: length of the element (excluding the length byte itself) -- 2nd byte: AD type – specifies what data is included in the element -- AD data – one or more bytes - the meaning is defined by the AD type - -In the `BThome BLE` format, the advertising payload should contain the following two AD elements: - -- Flags (`0x01`) -- Service Data - 16-bit UUID (`0x16`) - -In the example, we have: - -- First AD element: `020106` - - - `0x02` = length (2 bytes) - `0x01` = Flags - `0x06` = in bits, this is `00000110`. Bit 1 and bit 2 are 1, meaning: - Bit 1: “LE General Discoverable Mode” - Bit 2: “BR/EDR Not Supported” - - This part always has to be added, and is always the same (`0x020106`) - -- Second AD element: `0B161C182302C4090303BF13` (BThome BLE data) - - - `0x0B` = length (11 bytes) - `0x16` = Service Data - 16-bit UUID - `0x1C182302C4090303BF13` = BThome BLE data - -### BThome BLE data format (non-encrypted) - -The BThome BLE data is the part that contains the data. The data can contain multiple measurements. We continue with the example from above. - -- BThome BLE data = `0x1C182302C4090303BF13` - - `0x1C18` = The first two byte are the UUID16, which are assigned numbers that can be found in [this official document]https://btprodspecificationrefs.blob.core.windows.net/assigned-values/16-bit%20UUID%20Numbers%20Document.pdf by the Bluetooth organization. For BThome BLE we use the so called `GATT Service` = `User Data` (`0x1C18`) for non-encrypted messages. For encrypted messages, we use `GATT Service` = `Bond Management` (`0x1E18`). More information about encryption can be found further down this page. This part should always be `0x1C18` (non-encrypted) or `0x1E18` (encrypted), as it is used to recognize a BThome BLE message. - - `0x2302C409` = Temperature packet - - `0x0303BF13` = Humidity packet - -Let's explain how the last two data packets work. The temperature packet is used as example. - -- The first byte `0x23` (in bits `00100011`) is giving information about: - - The object length (bit 0-4): `00011` = 3 bytes (excluding the length byte itself) - - The object format (bit 5-7) `001` = 1 = Signed Integer (see table below) - -| type | bit 5-7 | format | Data type | -| ---- | ------- | ------ | ---------------------- | -| `0` | `000` | uint | unsingned integer | -| `1` | `001` | int | signed integer | -| `2` | `010` | float | float | -| `3` | `011` | string | string | -| `4` | `100` | MAC | MAC address (reversed) | - -- The second byte `0x02` is defining the type of measurement (temperature, see table below) -- The remaining bytes `0xC409` is the object value (little endian), which will be multiplied with the factor in the table below to get a sufficient number of digits. - - The object length is telling us that the value is 2 bytes long (object length = 3 bytes minus the second byte) and the object format is telling us that the value is a Signed Integer (positive or negative integer). - - `0xC409` as unsigned integer in little endian is equal to 2500. - - The factor for a temperature measurement is 0.01, resulting in a temperature of 25.00°C - -At the moment, the following sensors are supported. A preferred data type is given for your convenience, which should give you a short data message and at the same time a sufficient number of digits to display your data with high accuracy in Home Assistant. But you are free to use a different data type. If you want another sensor, let us know by creating a new issue on Github. - -| Object id | Property | Preferred data type | Factor | example | result | Unit in HA | Notes | -| --------- | ----------- | ------------------- | ------ | ---------------- | ------------ | ---------- | ------- | -| `0x00` | packet id | uint8 (1 byte) | 1 | `020009` | 9 | | [1] [2] | -| `0x01` | battery | uint8 (1 byte) | 1 | `020161` | 97 | `%` | | -| `0x02` | temperature | sint16 (2 bytes) | 0.01 | `2302CA09` | 25.06 | `°C` | | -| `0x03` | humidity | uint16 (2 bytes) | 0.01 | `0303BF13` | 50.55 | `%` | | -| `0x04` | pressure | uint24 (3 bytes) | 0.01 | `0404138A01` | 1008.83 | `mbar` | | -| `0x05` | illuminance | uint24 (3 bytes) | 0.01 | `0405138A14` | 13460.67 | `lux` | | -| `0x06` | weight | uint16 (2 byte) | 0.01 | `03065E1F` | 80.3 | `kg` | [1] [3] | -| `0x07` | weight unit | string (2 bytes) | None | `63076B67` | "kg" | | [1] [3] | -| `0x08` | dewpoint | sint16 (2 bytes) | 0.01 | `2308CA06` | 17.386 | `°C` | [1] | -| `0x09` | count | uint | 1 | `020960` | 96 | | [1] | -| `0X0A` | energy | uint24 (3 bytes) | 0.001 | `040A138A14` | 1346.067 | `kWh` | | -| `0x0B` | power | uint24 (3 bytes) | 0.01 | `040B021B00` | 69.14 | `W` | | -| `0x0C` | voltage | uint16 (2 bytes) | 0.001 | `030C020C` | 3.074 | `V` | | -| `0x0D` | pm2.5 | uint16 (2 bytes) | 1 | `030D120C` | 3090 | `ug/m3` | | -| `0x0E` | pm10 | uint16 (2 bytes) | 1 | `030E021C` | 7170 | `ug/m3` | | -| `0x0F` | boolean | uint8 (1 byte) | 1 | `020F01` | 1 (True) | `True` | [1] | -| `0x10` | switch | uint8 (1 byte) | 1 | `021001` | 1 (True) | `on` | [1] | -| `0x11` | opening | uint8 (1 byte) | 1 | `021100` | 0 (false) | `closed` | [1] | -| `0x12` | co2 | uint16 (2 bytes) | 1 | `0312E204` | 1250 | `ppm` | | -| `0x13` | voc | uint16 (2 bytes) | 1 | `03133301` | 307 | `ug/m3` | | -| | mac | 6 bytes (reversed) | | `86A6808FE64854` | 5448E68F80A6 | | [1] [4] | - -**Notes** - -Full example payloads are given in the test tests. - -**_1. Not supported measurement type_** - -The sensors with [1] is the last column are not yet supported in the official HA integration and will be added soon. -For these measurement types, use [BLE monitor](https://github.com/custom_components/ble_monitor) for the time being. - -**_2. packet id (not supported in HA integration yet)_** - -The `packet id` is optional and was used to filter duplicate data in BLE monitor. This functionality is not supported in the official Home Assistant integration. - -**_3. weight (unit) (not supported in HA integration yet)_** - -The `weight unit` is in `kg` by default, but can be set with the weight unit property. Examples of `weight unit` packets are: - -- kg (`63076B67`) -- lbs (`64076C6273`) -- jin (`64076A696E`) - -**_4. mac (not supported in HA integration yet)_** - -The `mac` functionality is not yet supported in the official Home Assistant integration, but was developed for BLE monitor. -We will most likely add this in a future update. - -You don't have to specify the `mac` address in the advertising payload, as it is already included in the [header](#header). -However, you can overwrite the `mac` by specifying it in the advertising payload. -To do this, set the first byte to `0x86` (meaning: object type = 4 (`mac`) and object length = 6), followed by the MAC in reversed order. No Object id is needed. - -### BThome BLE data format (encrypted) - -You can also choose to send the data in an encrypted way, which gives you extra security. -Unencrypted BLE advertisements can be read by everyone, even your neighbour with Home Assistant and BLE Monitor should in theory be able to receive your BLE data. -However, when you use encryption, it will be useless for anyone else, as long as he or she doesn't have the encryption key. -The encryption key should be 16 bytes long key (32 characters). -More information on how to encrypt your messages is demonstrated in [this script](https://github.com/custom-components/ble_monitor/blob/master/custom_components/ble_monitor/ble_parser/ha_ble_encryption.py). -BThome is using an AES encryption (CCM mode). Don't forget to set the encryption key in your Home Assistant device settings. +Detailed information about the use of BTHome BLE can be found on our [website](https://bthome.io). diff --git a/src/bthome_ble/__init__.py b/src/bthome_ble/__init__.py index c6b7554..b6589c5 100644 --- a/src/bthome_ble/__init__.py +++ b/src/bthome_ble/__init__.py @@ -1,4 +1,4 @@ -"""Parser for BLE advertisements in BThome format.""" +"""Parser for BLE advertisements in BTHome format.""" from __future__ import annotations from sensor_state_data import ( @@ -11,12 +11,12 @@ Units, ) -from .parser import BThomeBluetoothDeviceData +from .parser import BTHomeBluetoothDeviceData __version__ = "0.5.2" __all__ = [ - "BThomeBluetoothDeviceData", + "BTHomeBluetoothDeviceData", "SensorDescription", "SensorDeviceInfo", "DeviceClass", diff --git a/src/bthome_ble/const.py b/src/bthome_ble/const.py index e613afe..9325822 100644 --- a/src/bthome_ble/const.py +++ b/src/bthome_ble/const.py @@ -1,4 +1,4 @@ -"""Constants for BThome measurements.""" +"""Constants for BTHome measurements.""" import dataclasses from sensor_state_data import SensorLibrary, description diff --git a/src/bthome_ble/parser.py b/src/bthome_ble/parser.py index 66b3a57..7dd8ca9 100644 --- a/src/bthome_ble/parser.py +++ b/src/bthome_ble/parser.py @@ -1,10 +1,10 @@ -"""Parser for BLE advertisements in BThome format. +"""Parser for BLE advertisements in BTHome format. This file is shamelessly copied from the following repository: https://github.com/Ernst79/bleparser/blob/ac8757ad64f1fc17674dcd22111e547cdf2f205b/package/bleparser/ha_ble.py -BThome was originally developed as HA BLE for ble_monitor and been renamed to -BThome for Home Assistant Bluetooth integrations. +BTHome was originally developed as HA BLE for ble_monitor and has been renamed +to BTHome for the Home Assistant Bluetooth integration. MIT License applies. """ @@ -66,7 +66,7 @@ def parse_float(data_obj: bytes, factor: float = 1.0) -> float | None: elif len(data_obj) == 8: [val] = struct.unpack("d", data_obj) else: - _LOGGER.error("only 2, 4 or 8 byte long floats are supported in BThome BLE") + _LOGGER.error("only 2, 4 or 8 byte long floats are supported in BTHome BLE") return None return round(val * factor, decimal_places) @@ -85,8 +85,8 @@ def parse_mac(data_obj: bytes) -> bytes | None: return None -class BThomeBluetoothDeviceData(BluetoothData): - """Date for BThome Bluetooth devices.""" +class BTHomeBluetoothDeviceData(BluetoothData): + """Date for BTHome Bluetooth devices.""" def __init__(self, bindkey: bytes | None = None) -> None: super().__init__() @@ -138,7 +138,7 @@ def supported(self, data: BluetoothServiceInfo) -> bool: def _start_update(self, service_info: BluetoothServiceInfo) -> None: """Update from BLE advertisement data.""" - _LOGGER.debug("Parsing BThome BLE advertisement data: %s", service_info) + _LOGGER.debug("Parsing BTHome BLE advertisement data: %s", service_info) for uuid, data in service_info.service_data.items(): if self._parse_bthome(service_info, service_info.name, data): self.last_service_info = service_info @@ -146,7 +146,7 @@ def _start_update(self, service_info: BluetoothServiceInfo) -> None: def _parse_bthome( self, service_info: BluetoothServiceInfo, name: str, data: bytes ) -> bool: - """Parser for BThome sensors""" + """Parser for BTHome sensors""" identifier = short_address(service_info.address) # Remove identifier from ATC sensors. @@ -172,23 +172,23 @@ def _parse_bthome( self.set_device_name(f"{name} {identifier}") self.set_title(f"{name} {identifier}") - self.set_device_type("BThome sensor") + self.set_device_type("BTHome sensor") uuid16 = list(service_info.service_data.keys()) if uuid16 == ["0000181c-0000-1000-8000-00805f9b34fb"]: - # Non-encrypted BThome BLE format + # Non-encrypted BTHome BLE format self.encryption_scheme = EncryptionScheme.NONE - self.set_device_sw_version("BThome BLE") + self.set_device_sw_version("BTHome BLE") payload = data packet_id = None # noqa: F841 elif uuid16 == ["0000181e-0000-1000-8000-00805f9b34fb"]: - # Encrypted BThome BLE format + # Encrypted BTHome BLE format self.encryption_scheme = EncryptionScheme.BTHOME_BINDKEY - self.set_device_sw_version("BThome BLE (encrypted)") + self.set_device_sw_version("BTHome BLE (encrypted)") mac_readable = service_info.address if len(mac_readable) != 17 and mac_readable[2] != ":": - # On macOS, we get a UUID, which is useless for BThome sensors + # On macOS, we get a UUID, which is useless for BTHome sensors self.mac_known = False return False else: @@ -235,14 +235,14 @@ def _parse_bthome( elif obj_data_format > 4: _LOGGER.error( - "UNKNOWN dataobject in BThome BLE payload! Adv: %s", + "UNKNOWN dataobject in BTHome BLE payload! Adv: %s", data.hex(), ) continue if obj_meas_type not in MEAS_TYPES: _LOGGER.debug( - "UNKNOWN measurement type in BThome BLE payload! Adv: %s", + "UNKNOWN measurement type in BTHome BLE payload! Adv: %s", data.hex(), ) continue @@ -267,13 +267,18 @@ def _parse_bthome( if meas_float is not None: if meas_format.device_class in HA_SUPPORTED_DEVICE_CLASSES: self.update_predefined_sensor(meas_format, meas_float) - else: + elif meas_format.device_class: self.update_sensor( key=meas_format.device_class, native_unit_of_measurement=meas_format.native_unit_of_measurement, native_value=meas_float, device_class=None, ) + else: + _LOGGER.debug( + "UNKNOWN dataobject in BTHome BLE payload! Adv: %s", + data.hex(), + ) result = True elif meas_str is not None: _LOGGER.debug( @@ -282,7 +287,7 @@ def _parse_bthome( ) else: _LOGGER.debug( - "UNKNOWN dataobject in BThome BLE payload! Adv: %s", + "UNKNOWN dataobject in BTHome BLE payload! Adv: %s", data.hex(), ) @@ -292,7 +297,7 @@ def _parse_bthome( return True def _decrypt_bthome(self, data: bytes, bthome_mac: bytes) -> bytes: - """Decrypt encrypted BThome BLE advertisements""" + """Decrypt encrypted BTHome BLE advertisements""" if not self.bindkey: self.bindkey_verified = False _LOGGER.debug("Encryption key not set and adv is encrypted") diff --git a/tests/test_parser.py b/tests/test_parser.py index 6eba3fa..c98fe90 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,4 +1,4 @@ -"""Tests for the parser of BLE advertisements in BThome format.""" +"""Tests for the parser of BLE advertisements in BTHome format.""" import logging from unittest.mock import patch @@ -12,7 +12,7 @@ Units, ) -from bthome_ble.parser import BThomeBluetoothDeviceData, EncryptionScheme +from bthome_ble.parser import BTHomeBluetoothDeviceData, EncryptionScheme KEY_BATTERY = DeviceKey(key="battery", device_id=None) KEY_CO2 = DeviceKey(key="carbon_dioxide", device_id=None) @@ -76,7 +76,7 @@ def bytes_to_encrypted_service_info( def test_can_create(): - BThomeBluetoothDeviceData() + BTHomeBluetoothDeviceData() def test_encryption_key_needed(): @@ -88,7 +88,7 @@ def test_encryption_key_needed(): address="A4:C1:38:8D:18:B2", ) - device = BThomeBluetoothDeviceData() + device = BTHomeBluetoothDeviceData() assert device.supported(advertisement) assert device.encryption_scheme == EncryptionScheme.BTHOME_BINDKEY @@ -104,7 +104,7 @@ def test_encryption_no_key_needed(): address="A4:C1:38:8D:18:B2", ) - device = BThomeBluetoothDeviceData() + device = BTHomeBluetoothDeviceData() assert device.supported(advertisement) assert device.encryption_scheme == EncryptionScheme.NONE @@ -112,7 +112,7 @@ def test_encryption_no_key_needed(): def test_bindkey_wrong(): - """Test BThome parser with wrong encryption key.""" + """Test BTHome parser with wrong encryption key.""" bindkey = "814aac74c4f17b6c1581e1ab87816b99" data_string = b'\xfb\xa45\xe4\xd3\xc3\x12\xfb\x00\x11"3W\xd9\n\x99' advertisement = bytes_to_encrypted_service_info( @@ -121,7 +121,7 @@ def test_bindkey_wrong(): address="A4:C1:38:8D:18:B2", ) - device = BThomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) + device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert not device.bindkey_verified assert device.update(advertisement) == SensorUpdate( @@ -130,8 +130,8 @@ def test_bindkey_wrong(): None: SensorDeviceInfo( name="ATC 18B2", manufacturer="Xiaomi", - model="BThome sensor", - sw_version="BThome BLE (encrypted)", + model="BTHome sensor", + sw_version="BTHome BLE (encrypted)", hw_version=None, ) }, @@ -151,7 +151,7 @@ def test_bindkey_wrong(): def test_bindkey_correct(): - """Test BThome parser with correct encryption key.""" + """Test BTHome parser with correct encryption key.""" bindkey = "231d39c1d7cc1ab1aee224cd096db932" data_string = b'\xfb\xa45\xe4\xd3\xc3\x12\xfb\x00\x11"3W\xd9\n\x99' advertisement = bytes_to_encrypted_service_info( @@ -160,7 +160,7 @@ def test_bindkey_correct(): address="54:48:E6:8F:80:A5", ) - device = BThomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) + device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified assert device.update(advertisement) == SensorUpdate( @@ -169,8 +169,8 @@ def test_bindkey_correct(): None: SensorDeviceInfo( name="TEST DEVICE 80A5", manufacturer=None, - model="BThome sensor", - sw_version="BThome BLE (encrypted)", + model="BTHome sensor", + sw_version="BTHome BLE (encrypted)", hw_version=None, ) }, @@ -206,7 +206,7 @@ def test_bindkey_correct(): def test_bindkey_verified_can_be_unset(): - """Test BThome parser with wrong encryption key.""" + """Test BTHome parser with wrong encryption key.""" bindkey = "814aac74c4f17b6c1581e1ab87816b99" data_string = b'\xfb\xa45\xe4\xd3\xc3\x12\xfb\x00\x11"3W\xd9\n\x99' advertisement = bytes_to_encrypted_service_info( @@ -215,7 +215,7 @@ def test_bindkey_verified_can_be_unset(): address="A4:C1:38:8D:18:B2", ) - device = BThomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) + device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) device.bindkey_verified = True assert device.supported(advertisement) @@ -223,21 +223,21 @@ def test_bindkey_verified_can_be_unset(): def test_bthome_temperature_humidity(caplog): - """Test BThome parser for temperature humidity reading without encryption.""" + """Test BTHome parser for temperature humidity reading without encryption.""" data_string = b"#\x02\xca\t\x03\x03\xbf\x13" advertisement = bytes_to_service_info( data_string, local_name="ATC_8D18B2", address="A4:C1:38:8D:18:B2" ) - device = BThomeBluetoothDeviceData() + device = BTHomeBluetoothDeviceData() assert device.update(advertisement) == SensorUpdate( title="ATC 18B2", devices={ None: SensorDeviceInfo( name="ATC 18B2", manufacturer="Xiaomi", - model="BThome sensor", - sw_version="BThome BLE", + model="BTHome sensor", + sw_version="BTHome BLE", hw_version=None, ) }, @@ -273,21 +273,21 @@ def test_bthome_temperature_humidity(caplog): def test_bthome_temperature_humidity_battery(caplog): - """Test BThome parser for temperature humidity battery reading.""" + """Test BTHome parser for temperature humidity battery reading.""" data_string = b"\x02\x00\xa8#\x02]\t\x03\x03\xb7\x18\x02\x01]" advertisement = bytes_to_service_info( data_string, local_name="ATC_8D18B2", address="A4:C1:38:8D:18:B2" ) - device = BThomeBluetoothDeviceData() + device = BTHomeBluetoothDeviceData() assert device.update(advertisement) == SensorUpdate( title="ATC 18B2", devices={ None: SensorDeviceInfo( name="ATC 18B2", manufacturer="Xiaomi", - model="BThome sensor", - sw_version="BThome BLE", + model="BTHome sensor", + sw_version="BTHome BLE", hw_version=None, ) }, @@ -331,21 +331,21 @@ def test_bthome_temperature_humidity_battery(caplog): def test_bthome_pressure(caplog): - """Test BThome parser for pressure reading without encryption.""" + """Test BTHome parser for pressure reading without encryption.""" data_string = b"\x02\x00\x0c\x04\x04\x13\x8a\x01" advertisement = bytes_to_service_info( data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2" ) - device = BThomeBluetoothDeviceData() + 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", + model="BTHome sensor", + sw_version="BTHome BLE", hw_version=None, ) }, @@ -373,21 +373,21 @@ def test_bthome_pressure(caplog): def test_bthome_illuminance(caplog): - """Test BThome parser for illuminance reading without encryption.""" + """Test BTHome parser for illuminance reading without encryption.""" data_string = b"\x04\x05\x13\x8a\x14" advertisement = bytes_to_service_info( data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2" ) - device = BThomeBluetoothDeviceData() + 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", + model="BTHome sensor", + sw_version="BTHome BLE", hw_version=None, ) }, @@ -415,21 +415,21 @@ def test_bthome_illuminance(caplog): def test_bthome_mass_kilograms(caplog): - """Test BThome parser for mass reading in kilograms without encryption.""" + """Test BTHome parser for mass reading in kilograms without encryption.""" data_string = b"\x03\x06\x5E\x1F" advertisement = bytes_to_service_info( data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2" ) - device = BThomeBluetoothDeviceData() + 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", + model="BTHome sensor", + sw_version="BTHome BLE", hw_version=None, ) }, @@ -455,21 +455,21 @@ def test_bthome_mass_kilograms(caplog): def test_bthome_mass_pounds(caplog): - """Test BThome parser for mass reading in pounds without encryption.""" + """Test BTHome parser for mass reading in pounds without encryption.""" data_string = b"\x03\x07\x3E\x1d" advertisement = bytes_to_service_info( data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2" ) - device = BThomeBluetoothDeviceData() + 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", + model="BTHome sensor", + sw_version="BTHome BLE", hw_version=None, ) }, @@ -495,21 +495,21 @@ def test_bthome_mass_pounds(caplog): def test_bthome_dew_point(caplog): - """Test BThome parser for dew point reading without encryption.""" + """Test BTHome parser for dew point reading without encryption.""" data_string = b"\x23\x08\xCA\x06" advertisement = bytes_to_service_info( data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2" ) - device = BThomeBluetoothDeviceData() + 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", + model="BTHome sensor", + sw_version="BTHome BLE", hw_version=None, ) }, @@ -537,21 +537,21 @@ def test_bthome_dew_point(caplog): def test_bthome_count(caplog): - """Test BThome parser for counter reading without encryption.""" + """Test BTHome parser for counter reading without encryption.""" data_string = b"\x02\x09\x60" advertisement = bytes_to_service_info( data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2" ) - device = BThomeBluetoothDeviceData() + 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", + model="BTHome sensor", + sw_version="BTHome BLE", hw_version=None, ) }, @@ -577,21 +577,21 @@ def test_bthome_count(caplog): def test_bthome_energy(caplog): - """Test BThome parser for energy reading without encryption.""" + """Test BTHome parser for energy reading without encryption.""" data_string = b"\x04\n\x13\x8a\x14" advertisement = bytes_to_service_info( data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2" ) - device = BThomeBluetoothDeviceData() + 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", + model="BTHome sensor", + sw_version="BTHome BLE", hw_version=None, ) }, @@ -619,21 +619,21 @@ def test_bthome_energy(caplog): def test_bthome_power(caplog): - """Test BThome parser for power reading without encryption.""" + """Test BTHome parser for power reading without encryption.""" data_string = b"\x04\x0b\x02\x1b\x00" advertisement = bytes_to_service_info( data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2" ) - device = BThomeBluetoothDeviceData() + 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", + model="BTHome sensor", + sw_version="BTHome BLE", hw_version=None, ) }, @@ -667,15 +667,15 @@ def test_bthome_voltage(caplog): data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2" ) - device = BThomeBluetoothDeviceData() + 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", + model="BTHome sensor", + sw_version="BTHome BLE", hw_version=None, ) }, @@ -703,21 +703,21 @@ def test_bthome_voltage(caplog): def test_bthome_pm(caplog): - """Test BThome parser for PM2.5 and PM10 reading without encryption.""" + """Test BTHome parser for PM2.5 and PM10 reading without encryption.""" data_string = b"\x03\r\x12\x0c\x03\x0e\x02\x1c" advertisement = bytes_to_service_info( data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2" ) - device = BThomeBluetoothDeviceData() + 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", + model="BTHome sensor", + sw_version="BTHome BLE", hw_version=None, ) }, @@ -753,21 +753,21 @@ def test_bthome_pm(caplog): def test_bthome_co2(caplog): - """Test BThome parser for CO2 reading without encryption.""" + """Test BTHome parser for CO2 reading without encryption.""" data_string = b"\x03\x12\xe2\x04" advertisement = bytes_to_service_info( data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2" ) - device = BThomeBluetoothDeviceData() + 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", + model="BTHome sensor", + sw_version="BTHome BLE", hw_version=None, ) }, @@ -795,21 +795,21 @@ def test_bthome_co2(caplog): def test_bthome_voc(caplog): - """Test BThome parser for VOC reading without encryption.""" + """Test BTHome parser for VOC reading without encryption.""" data_string = b"\x03\x133\x01" advertisement = bytes_to_service_info( data_string, local_name="TEST DEVICE", address="A4:C1:38:8D:18:B2" ) - device = BThomeBluetoothDeviceData() + 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", + model="BTHome sensor", + sw_version="BTHome BLE", hw_version=None, ) }, @@ -837,21 +837,21 @@ def test_bthome_voc(caplog): def test_bthome_moisture(caplog): - """Test BThome parser for moisture reading from b-parasite sensor.""" + """Test BTHome parser for moisture reading from b-parasite sensor.""" data_string = b"\x03\x14\x02\x0c" advertisement = bytes_to_service_info( data_string, local_name="prst", address="A4:C1:38:8D:18:B2" ) - device = BThomeBluetoothDeviceData() + device = BTHomeBluetoothDeviceData() assert device.update(advertisement) == SensorUpdate( title="b-parasite 18B2", devices={ None: SensorDeviceInfo( name="b-parasite 18B2", manufacturer="b-parasite", - model="BThome sensor", - sw_version="BThome BLE", + model="BTHome sensor", + sw_version="BTHome BLE", hw_version=None, ) },