Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ This custom component for [Home Assistant](https://www.home-assistant.io) passiv
- BTHome
- b-parasite
- Chef iQ
- Eddystone (TLM)
- Ela
- Govee
- Grundfos
Expand Down
7 changes: 6 additions & 1 deletion custom_components/ble_monitor/ble_parser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .bthome import parse_bthome
from .chefiq import parse_chefiq
from .const import JAALEE_TYPES, TILT_TYPES
from .eddystone_tlm import parse_eddystone_tlm
from .govee import parse_govee
from .grundfos import parse_grundfos
from .helpers import to_mac, to_unformatted_mac, to_uuid
Expand Down Expand Up @@ -319,7 +320,11 @@ def parse_advertisement(
sensor_data = parse_xiaomi(self, service_data, mac)
break
elif uuid16 == 0xFEAA:
if len(service_data) == 19:
if len(service_data) == 18 and service_data[4] == 0x20:
# UUID16 = Google (Eddystone-TLM)
sensor_data = parse_eddystone_tlm(self, service_data, mac)
break
elif len(service_data) == 19:
# UUID16 = Google (used by KKM)
sensor_data = parse_kkm(self, service_data, mac)
break
Expand Down
64 changes: 64 additions & 0 deletions custom_components/ble_monitor/ble_parser/eddystone_tlm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""Parser for Eddystone-TLM BLE advertisements"""
import logging
from struct import unpack

from .helpers import to_mac, to_unformatted_mac

_LOGGER = logging.getLogger(__name__)

TLM_FRAME_TYPE = 0x20
TLM_VERSION = 0x00
TLM_TEMPERATURE_UNSUPPORTED = -128.0


def parse_eddystone_tlm(self, data: bytes, mac: bytes):
"""Parser for Eddystone-TLM beacons"""
device_type = "Eddystone-TLM"
result = {
"mac": to_unformatted_mac(mac),
"type": device_type,
"data": False,
}
(frame_type, version, volt, temp_int, temp_frac, adv_cnt) = unpack(
">BBHbBI", data[4:14]
)
if frame_type != TLM_FRAME_TYPE or version != TLM_VERSION:
result = None
else:
result.update(
{
"packet": adv_cnt,
"data": True,
}
)
# a voltage of 0 means the beacon does not report its battery level
if volt != 0:
result["voltage"] = volt / 1000
temperature = temp_int + temp_frac / 256
if temperature != TLM_TEMPERATURE_UNSUPPORTED:
result["temperature"] = round(temperature, 2)
if result is None:
if self.report_unknown == "Eddystone":
_LOGGER.info(
"BLE ADV from UNKNOWN Eddystone DEVICE: MAC: %s, ADV: %s",
to_mac(mac),
data.hex(),
)
return None
# reformat battery info to match BLE monitor format
if "voltage" in result:
voltage = result["voltage"]
# calculate battery in %
if voltage >= 3.00:
batt = 100
elif voltage >= 2.60:
batt = 60 + (voltage - 2.60) * 100
elif voltage >= 2.50:
batt = 40 + (voltage - 2.50) * 200
elif voltage >= 2.45:
batt = 20 + (voltage - 2.45) * 400
else:
batt = 0
result["battery"] = round(batt, 1)

return result
3 changes: 3 additions & 0 deletions custom_components/ble_monitor/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2229,6 +2229,7 @@ class BLEMonitorBinarySensorEntityDescription(
'BEC07-5' : [["temperature", "humidity", "rssi"], [], []],
'iBeacon' : [["rssi", "measured power", "cypress temperature", "cypress humidity"], ["uuid", "mac", "major", "minor"], []], # mac can be dynamic
'AltBeacon' : [["rssi", "measured power"], ["uuid", "mac", "major", "minor"], []], # mac can be dynamic
'Eddystone-TLM' : [["temperature", "voltage", "battery", "rssi"], [], []],
'Air Mentor Pro 2' : [["temperature", "temperature calibrated", "humidity", "co2", "tvoc", "aqi", "air quality", "pm2.5", "pm10", "rssi"], [], []],
'Air Mentor 2S' : [["temperature", "temperature calibrated", "humidity", "co2", "tvoc", "aqi", "formaldehyde", "air quality", "pm2.5", "pm10", "rssi"], [], []],
'bluSensor Mini' : [["temperature", "humidity", "co2", "tvoc", "aqi", "rssi"], [], []],
Expand Down Expand Up @@ -2389,6 +2390,7 @@ class BLEMonitorBinarySensorEntityDescription(
'BEC07-5' : 'Jinou',
'iBeacon' : 'Apple',
'AltBeacon' : 'Radius Networks',
'Eddystone-TLM' : 'Google',
'Air Mentor Pro 2' : 'Air Mentor',
'Air Mentor 2S' : 'Air Mentor',
'bluSensor Mini' : 'Almendo',
Expand Down Expand Up @@ -2629,6 +2631,7 @@ class BLEMonitorBinarySensorEntityDescription(
"Blustream",
"BTHome",
'Chef iQ',
"Eddystone",
"Garnet",
"Govee",
"Grundfos",
Expand Down
39 changes: 39 additions & 0 deletions custom_components/ble_monitor/test/test_eddystone_tlm_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""The tests for the Eddystone-TLM ble_parser."""
from ble_monitor.ble_parser import BleParser


class TestEddystoneTlm:
"""Tests for the Eddystone-TLM parser"""
def test_eddystone_tlm(self):
"""Test Eddystone-TLM parser."""
data_string = "043E2502010301494BEFBEADDE190201060303AAFE1116AAFE20000C6F1E000002006D41DB9AB6D7"
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["type"] == "Eddystone-TLM"
assert sensor_msg["mac"] == "DEADBEEF4B49"
assert sensor_msg["packet"] == 131181
assert sensor_msg["data"]
assert sensor_msg["temperature"] == 30.0
assert sensor_msg["voltage"] == 3.183
assert sensor_msg["battery"] == 100
assert sensor_msg["rssi"] == -41

def test_eddystone_tlm_unsupported_readings(self):
"""Test Eddystone-TLM parser with an unsupported battery voltage and temperature."""
data_string = "043E2502010301494BEFBEADDE190201060303AAFE1116AAFE2000000080000002006D41DB9AB6D7"
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["type"] == "Eddystone-TLM"
assert sensor_msg["mac"] == "DEADBEEF4B49"
assert sensor_msg["packet"] == 131181
assert sensor_msg["data"]
assert "temperature" not in sensor_msg
assert "voltage" not in sensor_msg
assert "battery" not in sensor_msg
assert sensor_msg["rssi"] == -41
24 changes: 24 additions & 0 deletions docs/_devices/Eddystone_TLM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
manufacturer: Google
name: Eddystone-TLM
model: Eddystone-TLM
image:
physical_description:
broadcasted_properties:
- temperature
- voltage
- battery
- rssi
broadcasted_property_notes:
- property: temperature
note: The beacon's chip temperature, not that of a separate sensor. Not reported by beacons that send -128 °C.
- property: voltage
note: Not reported by beacons that send 0 mV.
broadcast_rate:
active_scan:
encryption_key:
custom_firmware:
notes:
- Eddystone-TLM is not a device, but Google's open telemetry frame, sent by beacons from e.g. Minew and BlueUp.
- Only unencrypted TLM frames (version `0x00`) are supported.
---