From 1ea18a4d1d43ab654b5294c3324a535b8f0b21e7 Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Thu, 5 Oct 2023 20:52:03 +0200 Subject: [PATCH 1/4] Add parser for tetonika_eye --- .../ble_monitor/ble_parser/__init__.py | 11 +++++ .../ble_monitor/ble_parser/teltonika.py | 48 ++++++++++++++++++- .../ble_monitor/test/test_teltonika.py | 23 +++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/custom_components/ble_monitor/ble_parser/__init__.py b/custom_components/ble_monitor/ble_parser/__init__.py index b5b89b6a1..58735af9f 100644 --- a/custom_components/ble_monitor/ble_parser/__init__.py +++ b/custom_components/ble_monitor/ble_parser/__init__.py @@ -289,6 +289,13 @@ def parse_advertisement( # Jaalee sensor_data = parse_jaalee(self, man_spec_data, mac, rssi) break + elif len(man_spec_data_list) == 2: + # Teltonika Eye (can send both iBeacon and Teltonika data in one message) + second_man_spec_data = man_spec_data_list[1] + second_comp_id = (second_man_spec_data[3] << 8) | second_man_spec_data[2] + if second_comp_id == 0x089A: + sensor_data = parse_teltonika(self, second_man_spec_data, local_name, mac, rssi) + break else: # iBeacon sensor_data, tracker_data = parse_ibeacon(self, man_spec_data, mac, rssi) @@ -325,6 +332,10 @@ def parse_advertisement( man_spec_data = b"".join(man_spec_data_list) sensor_data = parse_hormann(self, man_spec_data, mac, rssi) break + elif comp_id == 0x089A: + # Teltonika + sensor_data = parse_teltonika(self, man_spec_data, local_name, mac, rssi) + break elif comp_id == 0x094F and data_len == 0x15: # Mikrotik sensor_data = parse_mikrotik(self, man_spec_data, mac, rssi) diff --git a/custom_components/ble_monitor/ble_parser/teltonika.py b/custom_components/ble_monitor/ble_parser/teltonika.py index a990214bc..24808545b 100644 --- a/custom_components/ble_monitor/ble_parser/teltonika.py +++ b/custom_components/ble_monitor/ble_parser/teltonika.py @@ -11,8 +11,11 @@ def parse_teltonika(self, data, complete_local_name, source_mac, rssi): """Teltonika parser""" result = {"firmware": "Teltonika"} teltonika_mac = source_mac + device_id = (data[3] << 8) | data[2] - if complete_local_name == "PUCK_T1": + if device_id == 0x089A: + device_type = "EYE sensor" + elif complete_local_name == "PUCK_T1": device_type = "Blue Puck T" elif complete_local_name == "PUCK_TH": device_type = "Blue Puck RHT" @@ -62,6 +65,49 @@ def parse_teltonika(self, data, complete_local_name, source_mac, rssi): # Battery (batt,) = unpack("h", sensor_data[0:2]) + result.update({"temperature": temp / 100}) + sensor_data = sensor_data[2:] + if flags & (1 << 1): # bit 1 + # Humidity + humi = sensor_data[0] + result.update({"humidity": humi}) + sensor_data = sensor_data[1:] + if flags & (1 << 2): # bit 2 + # Magnetic sensor presence + if flags & (1 << 3): # bit 3 + # magnetic field is detected + result.update({"magnetic field": 1}) + else: + # magnetic field is not detected + result.update({"magnetic field": 0}) + if flags & (1 << 4): # bit 4 + # Movement sensor counter + # Most significant bit indicates movement state + # 15 least significant bits represent count of movement events. + movement = sensor_data[0] & (1 << 7) + count = ((sensor_data[0] & 0b01111111) << 8) + sensor_data[1] + result.update({"movement": movement, "count": count}) + sensor_data = sensor_data[2:] + if flags & (1 << 5): # bit 5 + # Movement sensor angle + # Most significant byte – pitch (-90/+90) + # Two least significant bytes – roll (-180/+180) + (pitch, roll,) = unpack(">bh", sensor_data[0:3]) + result.update({"roll": roll, "pitch": pitch}) + sensor_data = sensor_data[3:] + if flags & (1 << 6): # bit 6 + # Low battery indication sensor presence + result.update({"low battery": 1}) + if flags & (1 << 7): # bit 7 + # Battery voltage value presence + volt = round(2.0 + sensor_data[0] * 0.01, 3) + result.update({"voltage": volt}) data_size -= packet_size packet_start += packet_size diff --git a/custom_components/ble_monitor/test/test_teltonika.py b/custom_components/ble_monitor/test/test_teltonika.py index 405ff33eb..2009f68c7 100644 --- a/custom_components/ble_monitor/test/test_teltonika.py +++ b/custom_components/ble_monitor/test/test_teltonika.py @@ -102,3 +102,26 @@ def test_ela_blue_puck_T_with_batt(self): assert sensor_msg["temperature"] == 27.12 assert sensor_msg["battery"] == 13 assert sensor_msg["rssi"] == -35 + + def test_teltonika_eye(self): + """Test Teltonika parser for Teltonika Eye sensor.""" + data_string = "043E46020103001897035ECFD03a0201061AFF4C000215FFFFFFFF0B8C404510C655AAB636EBEFBB700055020C094D50315F313233343536370EFF9A0801B708B4120CCB0BFFC767DD" + data = bytes(bytearray.fromhex(data_string)) + # pylint: disable=unused-variable + ble_parser = BleParser() + sensor_msg, tracker_msg = ble_parser.parse_raw_data(data) + + assert sensor_msg["firmware"] == "Teltonika" + assert sensor_msg["type"] == "EYE sensor" + assert sensor_msg["mac"] == "D0CF5E039718" + assert sensor_msg["packet"] == "no packet id" + assert sensor_msg["data"] + assert sensor_msg["temperature"] == 22.28 + assert sensor_msg["humidity"] == 18 + assert sensor_msg["magnetic field"] == 0 + assert sensor_msg["movement"] == 0 + assert sensor_msg["count"] == 3275 + assert sensor_msg["roll"] == -57 + assert sensor_msg["pitch"] == 11 + assert sensor_msg["voltage"] == 3.03 + assert sensor_msg["rssi"] == -35 From f6a92d5c1ebd7582327f9a67fc967595d3cc8ae3 Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Fri, 6 Oct 2023 16:45:33 +0200 Subject: [PATCH 2/4] Add new sensors for teltonika eye --- .../ble_monitor/ble_parser/bthome_const.py | 2 +- .../ble_monitor/ble_parser/teltonika.py | 10 ++-- custom_components/ble_monitor/const.py | 56 +++++++++++++++++-- custom_components/ble_monitor/sensor.py | 2 + .../ble_monitor/test/test_bthome_v2.py | 17 ++++++ .../ble_monitor/test/test_teltonika.py | 4 +- 6 files changed, 79 insertions(+), 12 deletions(-) diff --git a/custom_components/ble_monitor/ble_parser/bthome_const.py b/custom_components/ble_monitor/ble_parser/bthome_const.py index a2fac78ad..cb8431e55 100644 --- a/custom_components/ble_monitor/ble_parser/bthome_const.py +++ b/custom_components/ble_monitor/ble_parser/bthome_const.py @@ -123,7 +123,7 @@ class MeasTypes: factor=0.01, ), 0x15: MeasTypes( - meas_format="battery", + meas_format="battery low", ), 0x16: MeasTypes( meas_format="battery charging", diff --git a/custom_components/ble_monitor/ble_parser/teltonika.py b/custom_components/ble_monitor/ble_parser/teltonika.py index 24808545b..c027c23d0 100644 --- a/custom_components/ble_monitor/ble_parser/teltonika.py +++ b/custom_components/ble_monitor/ble_parser/teltonika.py @@ -82,17 +82,17 @@ def parse_teltonika(self, data, complete_local_name, source_mac, rssi): # Magnetic sensor presence if flags & (1 << 3): # bit 3 # magnetic field is detected - result.update({"magnetic field": 1}) + result.update({"magnetic field detected": 1}) else: # magnetic field is not detected - result.update({"magnetic field": 0}) + result.update({"magnetic field detected": 0}) if flags & (1 << 4): # bit 4 # Movement sensor counter # Most significant bit indicates movement state # 15 least significant bits represent count of movement events. - movement = sensor_data[0] & (1 << 7) + moving = sensor_data[0] & (1 << 7) count = ((sensor_data[0] & 0b01111111) << 8) + sensor_data[1] - result.update({"movement": movement, "count": count}) + result.update({"moving": moving, "count": count}) sensor_data = sensor_data[2:] if flags & (1 << 5): # bit 5 # Movement sensor angle @@ -103,7 +103,7 @@ def parse_teltonika(self, data, complete_local_name, source_mac, rssi): sensor_data = sensor_data[3:] if flags & (1 << 6): # bit 6 # Low battery indication sensor presence - result.update({"low battery": 1}) + result.update({"battery low": 1}) if flags & (1 << 7): # bit 7 # Battery voltage value presence volt = round(2.0 + sensor_data[0] * 0.01, 3) diff --git a/custom_components/ble_monitor/const.py b/custom_components/ble_monitor/const.py index 5b7b44c37..f11b78e49 100755 --- a/custom_components/ble_monitor/const.py +++ b/custom_components/ble_monitor/const.py @@ -134,6 +134,15 @@ class BLEMonitorBinarySensorEntityDescription( device_class=BinarySensorDeviceClass.BATTERY_CHARGING, force_update=True, ), + BLEMonitorBinarySensorEntityDescription( + key="battery low", + sensor_class="BaseBinarySensor", + update_behavior="Instantly", + name="battery low", + unique_id="bl_", + device_class=BinarySensorDeviceClass.BATTERY, + force_update=True, + ), BLEMonitorBinarySensorEntityDescription( key="carbon monoxide", sensor_class="BaseBinarySensor", @@ -465,6 +474,16 @@ class BLEMonitorBinarySensorEntityDescription( device_class=None, force_update=False, ), + BLEMonitorBinarySensorEntityDescription( + key="magnetic field detected", + sensor_class="BaseBinarySensor", + update_behavior="Instantly", + name="magnetic field", + unique_id="magnetic_field_", + icon="mdi:magnet", + device_class=None, + force_update=False, + ), BLEMonitorBinarySensorEntityDescription( key="bed occupancy", sensor_class="BaseBinarySensor", @@ -1035,6 +1054,30 @@ class BLEMonitorBinarySensorEntityDescription( suggested_display_precision=1, state_class=SensorStateClass.MEASUREMENT, ), + BLEMonitorSensorEntityDescription( + key="roll", + sensor_class="InstantUpdateSensor", + update_behavior="Instantly", + name="roll", + unique_id="roll_", + icon="mdi:horizontal-rotate-clockwise", + native_unit_of_measurement="°", + device_class=None, + suggested_display_precision=0, + state_class=SensorStateClass.MEASUREMENT, + ), + BLEMonitorSensorEntityDescription( + key="pitch", + sensor_class="InstantUpdateSensor", + update_behavior="Instantly", + name="pitch", + unique_id="pitch_", + icon="mdi:rotate-right-variant", + native_unit_of_measurement="°", + device_class=None, + suggested_display_precision=0, + state_class=SensorStateClass.MEASUREMENT, + ), BLEMonitorSensorEntityDescription( key="distance", sensor_class="InstantUpdateSensor", @@ -1796,18 +1839,23 @@ class BLEMonitorBinarySensorEntityDescription( # Sensors that support automatic adding of sensors and binary sensors AUTO_MANUFACTURER_DICT = { 'Amazfit Smart Scale' : 'Amazfit', + 'Blustream' : 'Blustream', 'BTHome' : 'BTHome', 'HHCCJCY10' : 'HHCC', + 'HolyIOT BLE tracker' : 'HolyIOT', + 'Supramatic E4 BS' : 'Hörmann', 'IBS-TH' : 'Inkbird', 'IBS-TH2/P01B' : 'Inkbird', 'JHT' : 'Jaalee', 'TG-BT5-IN' : 'Mikrotik', 'TG-BT5-OUT' : 'Mikrotik', + 'Electra Washbasin Faucet': 'Oras', 'EClerk Eco' : 'Relsib', 'WT51' : 'Relsib', 'Blue Puck T' : 'Teltonika', 'Blue Coin T' : 'Teltonika', 'Blue Puck RHT' : 'Teltonika', + 'EYE sensor' : 'Teltonika', 'TP357' : 'Thermopro', 'TP359' : 'Thermopro', 'Tilt Red' : 'Tilt', @@ -1819,16 +1867,13 @@ class BLEMonitorBinarySensorEntityDescription( 'Tilt Yellow' : 'Tilt', 'Tilt Pink' : 'Tilt', 'MMC-W505' : 'Xiaomi', - 'Electra Washbasin Faucet': 'Oras', - 'Supramatic E4 BS' : 'Hörmann', - 'Blustream' : 'Blustream', - 'HolyIOT BLE tracker' : 'HolyIOT', } # Binary Sensors that are automatically added if device is in AUTO_MANUFACTURER_DICT AUTO_BINARY_SENSOR_LIST = [ "battery charging", + "battery low", "carbon monoxide", "cold", "connectivity", @@ -1841,6 +1886,7 @@ class BLEMonitorBinarySensorEntityDescription( "impact", "light", "lock", + "magnetic field detected", "motion", "moisture detected", "moving", @@ -1886,11 +1932,13 @@ class BLEMonitorBinarySensorEntityDescription( "moisture", "non-stabilized weight", "opening percentage", + "pitch", "pm2.5", "pm10", "power", "pressure", "pulse", + "roll", "rotation", "speed", "steps", diff --git a/custom_components/ble_monitor/sensor.py b/custom_components/ble_monitor/sensor.py index 239136560..d5a16e2b0 100644 --- a/custom_components/ble_monitor/sensor.py +++ b/custom_components/ble_monitor/sensor.py @@ -339,6 +339,8 @@ class BaseSensor(RestoreSensor, SensorEntity): # | |**pulse # | |**shake # | |**rotation + # | |**roll + # | |**pitch # | |**distance # | |**distance mm # | |**duration diff --git a/custom_components/ble_monitor/test/test_bthome_v2.py b/custom_components/ble_monitor/test/test_bthome_v2.py index 5e07f0975..606f58e7e 100644 --- a/custom_components/ble_monitor/test/test_bthome_v2.py +++ b/custom_components/ble_monitor/test/test_bthome_v2.py @@ -305,6 +305,23 @@ def test_bthome_v2_moisture(self): assert sensor_msg["moisture"] == 3.07 assert sensor_msg["rssi"] == -52 + def test_bthome_v2_battery_low(self): + """Test BTHome parser for battery low measurement""" + data_string = "043E1602010000A5808FE648540A0201060616D2FC401501CC" + data = bytes(bytearray.fromhex(data_string)) + + # pylint: disable=unused-variable + ble_parser = BleParser() + sensor_msg, tracker_msg = ble_parser.parse_raw_data(data) + + assert sensor_msg["firmware"] == "BTHome V2" + assert sensor_msg["type"] == "BTHome" + assert sensor_msg["mac"] == "5448E68F80A5" + assert sensor_msg["packet"] == "no packet id" + assert sensor_msg["data"] + assert sensor_msg["battery low"] == 1 + assert sensor_msg["rssi"] == -52 + def test_bthome_v2_battery_charging(self): """Test BTHome parser for battery charging measurement""" data_string = "043E1602010000A5808FE648540A0201060616D2FC401601CC" diff --git a/custom_components/ble_monitor/test/test_teltonika.py b/custom_components/ble_monitor/test/test_teltonika.py index 2009f68c7..47850e9c8 100644 --- a/custom_components/ble_monitor/test/test_teltonika.py +++ b/custom_components/ble_monitor/test/test_teltonika.py @@ -118,8 +118,8 @@ def test_teltonika_eye(self): assert sensor_msg["data"] assert sensor_msg["temperature"] == 22.28 assert sensor_msg["humidity"] == 18 - assert sensor_msg["magnetic field"] == 0 - assert sensor_msg["movement"] == 0 + assert sensor_msg["magnetic field detected"] == 0 + assert sensor_msg["moving"] == 0 assert sensor_msg["count"] == 3275 assert sensor_msg["roll"] == -57 assert sensor_msg["pitch"] == 11 From 0a24b6bd35a2cb2c7ac54408da2c0ba23fb023d6 Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Fri, 6 Oct 2023 17:20:37 +0200 Subject: [PATCH 3/4] Add docs for Teltonika Eye --- ...lue_coin_t.md => Teltonika_blue_coin_t.md} | 0 ...uck_rht.md => Teltonika_blue_pluck_rht.md} | 0 ...e_pluck_t.md => Teltonika_blue_pluck_t.md} | 0 docs/_devices/Teltonika_eye_beacon.md | 31 ++++++++++++++++++ docs/assets/images/Teltonika_eye.png | Bin 0 -> 30721 bytes 5 files changed, 31 insertions(+) rename docs/_devices/{blue_coin_t.md => Teltonika_blue_coin_t.md} (100%) rename docs/_devices/{blue_pluck_rht.md => Teltonika_blue_pluck_rht.md} (100%) rename docs/_devices/{blue_pluck_t.md => Teltonika_blue_pluck_t.md} (100%) create mode 100644 docs/_devices/Teltonika_eye_beacon.md create mode 100644 docs/assets/images/Teltonika_eye.png diff --git a/docs/_devices/blue_coin_t.md b/docs/_devices/Teltonika_blue_coin_t.md similarity index 100% rename from docs/_devices/blue_coin_t.md rename to docs/_devices/Teltonika_blue_coin_t.md diff --git a/docs/_devices/blue_pluck_rht.md b/docs/_devices/Teltonika_blue_pluck_rht.md similarity index 100% rename from docs/_devices/blue_pluck_rht.md rename to docs/_devices/Teltonika_blue_pluck_rht.md diff --git a/docs/_devices/blue_pluck_t.md b/docs/_devices/Teltonika_blue_pluck_t.md similarity index 100% rename from docs/_devices/blue_pluck_t.md rename to docs/_devices/Teltonika_blue_pluck_t.md diff --git a/docs/_devices/Teltonika_eye_beacon.md b/docs/_devices/Teltonika_eye_beacon.md new file mode 100644 index 000000000..ba5d318f3 --- /dev/null +++ b/docs/_devices/Teltonika_eye_beacon.md @@ -0,0 +1,31 @@ +--- +manufacturer: Teltonika +name: Eye Beacon +model: Eye Beacon +image: Teltonika_eye.png +physical_description: Rounded beacon, no screen +broadcasted_properties: + - temperature + - humidity + - roll + - pitch + - magnetic field detected + - moving + - count + - battery low + - rssi +broadcasted_property_notes: +broadcast_rate: +active_scan: see notes +encryption_key: +custom_firmware: +notes: + - Teltonika EYE sensors can send BLE data in 3 formats +1. iBeacon + EYE Sensors +2. Eddystone + EYE Sensors +3. EYE Sensors + +For iBeacon + EYE Sensors and Eddystone + EYE Sensors protocols only iBeacon/Eddystone packet is broadcasted and will be seen by both active and passive scans, to see the EYE Sensors packet you need to use active scan. In other words in an environment where no BLE devices are scanning with an active scan or in case when there are no scanning devices at all, only the iBeacon/Eddystone packet will be sent by the BTS device to conserve energy. + +This means that if you use the iBeacon (1) or Eddystone format (2), you will need to enable active scan. If you are only using EYE sensors format (3), you can use passive scanning in BLE monitor. +--- diff --git a/docs/assets/images/Teltonika_eye.png b/docs/assets/images/Teltonika_eye.png new file mode 100644 index 0000000000000000000000000000000000000000..3a90483a977d35b6a20777ad8dc1b81babd86a76 GIT binary patch literal 30721 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 Ef(eO!5&!@I literal 0 HcmV?d00001 From 8caa137b2f1b55e034e2f2177a98d9d752a4a095 Mon Sep 17 00:00:00 2001 From: Ernst Klamer Date: Sun, 8 Oct 2023 12:25:22 +0200 Subject: [PATCH 4/4] Add movement counter --- .../ble_monitor/ble_parser/teltonika.py | 2 +- custom_components/ble_monitor/const.py | 13 +++++++++++++ custom_components/ble_monitor/sensor.py | 1 + .../ble_monitor/test/test_teltonika.py | 2 +- docs/_devices/Teltonika_eye_beacon.md | 2 +- 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/custom_components/ble_monitor/ble_parser/teltonika.py b/custom_components/ble_monitor/ble_parser/teltonika.py index c027c23d0..3f290cdea 100644 --- a/custom_components/ble_monitor/ble_parser/teltonika.py +++ b/custom_components/ble_monitor/ble_parser/teltonika.py @@ -92,7 +92,7 @@ def parse_teltonika(self, data, complete_local_name, source_mac, rssi): # 15 least significant bits represent count of movement events. moving = sensor_data[0] & (1 << 7) count = ((sensor_data[0] & 0b01111111) << 8) + sensor_data[1] - result.update({"moving": moving, "count": count}) + result.update({"moving": moving, "movement counter": count}) sensor_data = sensor_data[2:] if flags & (1 << 5): # bit 5 # Movement sensor angle diff --git a/custom_components/ble_monitor/const.py b/custom_components/ble_monitor/const.py index f11b78e49..c2f686270 100755 --- a/custom_components/ble_monitor/const.py +++ b/custom_components/ble_monitor/const.py @@ -1269,6 +1269,18 @@ class BLEMonitorBinarySensorEntityDescription( suggested_display_precision=0, state_class=SensorStateClass.MEASUREMENT, ), + BLEMonitorSensorEntityDescription( + key="movement counter", + sensor_class="StateChangedSensor", + update_behavior="StateChange", + name="movement counter", + unique_id="mv_cnt_", + icon="mdi:counter", + native_unit_of_measurement=None, + device_class=None, + suggested_display_precision=0, + state_class=SensorStateClass.MEASUREMENT, + ), BLEMonitorSensorEntityDescription( key="score", sensor_class="StateChangedSensor", @@ -1930,6 +1942,7 @@ class BLEMonitorBinarySensorEntityDescription( "illuminance", "impedance", "moisture", + "movement counter", "non-stabilized weight", "opening percentage", "pitch", diff --git a/custom_components/ble_monitor/sensor.py b/custom_components/ble_monitor/sensor.py index d5a16e2b0..3fb2e52cf 100644 --- a/custom_components/ble_monitor/sensor.py +++ b/custom_components/ble_monitor/sensor.py @@ -356,6 +356,7 @@ class BaseSensor(RestoreSensor, SensorEntity): # | | |**major # | | |**minor # | | |**count + # | | |**movement counter # | | |**score # | | |**air quality # | | |**text diff --git a/custom_components/ble_monitor/test/test_teltonika.py b/custom_components/ble_monitor/test/test_teltonika.py index 47850e9c8..9875d09f1 100644 --- a/custom_components/ble_monitor/test/test_teltonika.py +++ b/custom_components/ble_monitor/test/test_teltonika.py @@ -120,7 +120,7 @@ def test_teltonika_eye(self): assert sensor_msg["humidity"] == 18 assert sensor_msg["magnetic field detected"] == 0 assert sensor_msg["moving"] == 0 - assert sensor_msg["count"] == 3275 + assert sensor_msg["movement counter"] == 3275 assert sensor_msg["roll"] == -57 assert sensor_msg["pitch"] == 11 assert sensor_msg["voltage"] == 3.03 diff --git a/docs/_devices/Teltonika_eye_beacon.md b/docs/_devices/Teltonika_eye_beacon.md index ba5d318f3..b7e571734 100644 --- a/docs/_devices/Teltonika_eye_beacon.md +++ b/docs/_devices/Teltonika_eye_beacon.md @@ -11,7 +11,7 @@ broadcasted_properties: - pitch - magnetic field detected - moving - - count + - movement counter - battery low - rssi broadcasted_property_notes: