Skip to content

Commit

Permalink
Merge branch 'Mi-Scale-update' into new-HA_BLE
Browse files Browse the repository at this point in the history
  • Loading branch information
Ernst79 committed Mar 15, 2022
2 parents 095c0d2 + db486b4 commit ca88318
Show file tree
Hide file tree
Showing 24 changed files with 112 additions and 43 deletions.
5 changes: 5 additions & 0 deletions custom_components/ble_monitor/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"mode",
"sector_timer",
"number_of_sectors",
"weight",
]


Expand Down Expand Up @@ -461,6 +462,10 @@ def collect(self, data, batt_attr=None):
self._extra_state_attributes["sector_timer"] = data["sector timer"]
if "number of sectors" in data:
self._extra_state_attributes["number_of_sectors"] = data["number of sectors"]
if self.entity_description.key == "weight removed":
if "stabilized" in data:
if data["stabilized"] and data["weight removed"]:
self._extra_state_attributes["weight"] = data["non-stabilized weight"]

async def async_update(self):
"""Update sensor state and attribute."""
Expand Down
5 changes: 4 additions & 1 deletion custom_components/ble_monitor/ble_parser/altbeacon.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

DEVICE_TYPE: Final = "AltBeacon"


def parse_altbeacon(self, data: str, comp_id: int, source_mac: str, rssi: float):
if len(data) >= 27:
uuid = data[6:22]
Expand Down Expand Up @@ -71,8 +72,10 @@ def parse_altbeacon(self, data: str, comp_id: int, source_mac: str, rssi: float)


def to_uuid(uuid: str) -> str:
"""Return formatted UUID"""
return str(UUID(''.join('{:02X}'.format(x) for x in uuid)))


def to_mac(addr: str) -> str:
return ':'.join('{:02x}'.format(x) for x in addr).upper()
"""Return formatted MAC address"""
return ':'.join(f'{i:02X}' for i in addr)
6 changes: 3 additions & 3 deletions custom_components/ble_monitor/ble_parser/atc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


def parse_atc(self, data, source_mac, rssi):
"""Check for adstruc length"""
"""Parse ATC BLE advertisements"""
device_type = "ATC"
msg_length = len(data)
if msg_length == 19:
Expand Down Expand Up @@ -178,5 +178,5 @@ def decrypt_atc(self, data, atc_mac):


def to_mac(addr: int):
"""Convert MAC address."""
return ':'.join('{:02x}'.format(x) for x in addr).upper()
"""Return formatted MAC address"""
return ':'.join(f'{i:02X}' for i in addr)
4 changes: 2 additions & 2 deletions custom_components/ble_monitor/ble_parser/bluemaestro.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,5 @@ def parse_bluemaestro(self, data, source_mac, rssi):


def to_mac(addr: int):
"""Convert MAC address."""
return ':'.join('{:02x}'.format(x) for x in addr).upper()
"""Return formatted MAC address"""
return ':'.join(f'{i:02X}' for i in addr)
2 changes: 1 addition & 1 deletion custom_components/ble_monitor/ble_parser/brifit.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,5 @@ def parse_brifit(self, data, source_mac, rssi):


def to_mac(addr: int):
"""Convert MAC address."""
"""Return formatted MAC address"""
return ':'.join(f'{i:02X}' for i in addr)
6 changes: 5 additions & 1 deletion custom_components/ble_monitor/ble_parser/ibeacon.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@

DEVICE_TYPE: Final = "iBeacon"


def parse_ibeacon(self, data: str, source_mac: str, rssi: float):
"""Parse iBeacon advertisements"""
if data[5] == 0x15 and len(data) >= 27:
uuid = data[6:22]
(major, minor, power) = unpack(">HHb", data[22:27])
Expand Down Expand Up @@ -68,8 +70,10 @@ def parse_ibeacon(self, data: str, source_mac: str, rssi: float):


def to_uuid(uuid: str) -> str:
"""Return formatted UUID"""
return str(UUID(''.join('{:02X}'.format(x) for x in uuid)))


def to_mac(addr: str) -> str:
return ':'.join('{:02x}'.format(x) for x in addr).upper()
"""Return formatted MAC address"""
return ':'.join(f'{i:02X}' for i in addr)
2 changes: 1 addition & 1 deletion custom_components/ble_monitor/ble_parser/inode.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,4 @@ def parse_inode(self, data, source_mac, rssi):

def to_mac(addr: int):
"""Return formatted MAC address"""
return ':'.join('{:02x}'.format(x) for x in addr).upper()
return ':'.join(f'{i:02X}' for i in addr)
4 changes: 2 additions & 2 deletions custom_components/ble_monitor/ble_parser/jinou.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,5 @@ def parse_jinou(self, data, source_mac, rssi):


def to_mac(addr: int):
"""Convert MAC address."""
return ':'.join('{:02x}'.format(x) for x in addr).upper()
"""Return formatted MAC address"""
return ':'.join(f'{i:02X}' for i in addr)
3 changes: 2 additions & 1 deletion custom_components/ble_monitor/ble_parser/kegtron.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,5 @@ def parse_kegtron(self, data, source_mac, rssi):


def to_mac(addr: int):
return ':'.join('{:02x}'.format(x) for x in addr).upper()
"""Return formatted MAC address"""
return ':'.join(f'{i:02X}' for i in addr)
14 changes: 9 additions & 5 deletions custom_components/ble_monitor/ble_parser/miscale.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,15 @@ def parse_miscale(self, data, source_mac, rssi):
"stabilized": 0 if is_stabilized == 0 else 1
}

if is_stabilized and not weight_removed:
result.update({"weight": weight})

if has_impedance:
result.update({"impedance": impedance})
if device_type == "Mi Scale V1":
if is_stabilized and not weight_removed:
result.update({"weight": weight})
elif device_type == "Mi Scale V2":
if is_stabilized and (weight_removed == 0) and has_impedance:
result.update({"weight": weight})
result.update({"impedance": impedance})
else:
pass

firmware = device_type
miscale_mac = source_mac
Expand Down
2 changes: 1 addition & 1 deletion custom_components/ble_monitor/ble_parser/moat.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,4 @@ def parse_moat(self, data, source_mac, rssi):

def to_mac(addr: int):
"""Return formatted MAC address"""
return ':'.join('{:02x}'.format(x) for x in addr).upper()
return ':'.join(f'{i:02X}' for i in addr)
2 changes: 1 addition & 1 deletion custom_components/ble_monitor/ble_parser/qingping.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,4 @@ def parse_qingping(self, data, source_mac, rssi):

def to_mac(addr: int):
"""Return formatted MAC address"""
return ':'.join('{:02x}'.format(x) for x in addr).upper()
return ':'.join(f'{i:02X}' for i in addr)
3 changes: 2 additions & 1 deletion custom_components/ble_monitor/ble_parser/ruuvitag.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,5 @@ def parse_ruuvitag(self, data, source_mac, rssi):


def to_mac(addr: int):
return ":".join("{:02x}".format(x) for x in addr).upper()
"""Return formatted MAC address"""
return ':'.join(f'{i:02X}' for i in addr)
31 changes: 20 additions & 11 deletions custom_components/ble_monitor/ble_parser/sensirion.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
# Parser for Sensirion BLE advertisements
"""Parser for Sensirion BLE advertisements"""
import logging

_LOGGER = logging.getLogger(__name__)

SENSIRION_DEVICES = [
"MyCO2",
"SHT40 Gadget",
"SHT41 Gadget",
"SHT45 Gadget",
]


def parse_sensirion(self, data, complete_local_name, source_mac, rssi):
"""Sensirion parser"""
result = {"firmware": "Sensirion"}
sensirion_mac = source_mac
device_type = complete_local_name

if device_type != "MyCO2":
if device_type not in SENSIRION_DEVICES:
if self.report_unknown == "Sensirion":
_LOGGER.info(
"BLE ADV from UNKNOWN Sensirion DEVICE: RSSI: %s, MAC: %s, ADV: %s",
Expand All @@ -19,32 +26,31 @@ def parse_sensirion(self, data, complete_local_name, source_mac, rssi):
data.hex()
)
return None

# check for MAC presence in sensor whitelist, if needed
if self.discovery is False and source_mac not in self.sensor_whitelist:
_LOGGER.debug(
"Discovery is disabled. MAC: %s is not whitelisted!", to_mac(source_mac))
return None

# not all of the following values are used yet, but this explains the full protocol
# bytes 1+2 (length and type) are part of the header
advertisementLength = data[0] # redundant
advertisementType0 = data[1] # redundant (also encoded in body - see below)
advertisementType0 = data[1] # redundant (also encoded in body - see below)
companyId = data[2:3] # redundant (already part of the metadata)
advertisementType = int(data[4])
advSampleType = int(data[5])
deviceName = f'{data[6]:x}:{data[7]:x}' # as shown in Sensirion MyAmbience app (last 4 bytes of MAC address)

if(advertisementType == 0):
if advertisementType == 0:
samples = _parse_dataType(advSampleType, data[8:])

if not samples:
return None
else:
result.update(samples)
result.update({
"rssi": rssi,
"mac": to_mac(source_mac),
"mac": ''.join('{:02X}'.format(x) for x in sensirion_mac[:]),
"type": device_type,
"packet": "no packet id",
"data": True
Expand All @@ -58,7 +64,7 @@ def parse_sensirion(self, data, complete_local_name, source_mac, rssi):

def to_mac(addr: int):
"""Return formatted MAC address"""
return ':'.join('{:02x}'.format(x) for x in addr).upper()
return ':'.join(f'{i:02X}' for i in addr)


'''
Expand All @@ -68,13 +74,16 @@ def to_mac(addr: int):


def _parse_dataType(advSampleType, byte_data):
if(advSampleType == 8):
if (advSampleType == 6):
return {'temperature': _decodeTemperatureV1(byte_data[0:2]),
'humidity': _decodeHumidityV1(byte_data[2:4])}
elif (advSampleType == 8):
return {'temperature': _decodeTemperatureV1(byte_data[0:2]),
'humidity': _decodeHumidityV1(byte_data[2:4]),
'co2': _decodeSimple(byte_data[4:6])}
else:
_LOGGER.debug("Advertisement SampleType %s not supported", advSampleType)


def _decodeSimple(byte_data):
# GadgetBle::_convertSimple - return static_cast<uint16_t>(value + 0.5f);
Expand Down
2 changes: 1 addition & 1 deletion custom_components/ble_monitor/ble_parser/sensorpush.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,4 @@ def parse_sensorpush(self, data, source_mac, rssi):

def to_mac(addr: int):
"""Return formatted MAC address"""
return ':'.join('{:02x}'.format(x) for x in addr).upper()
return ':'.join(f'{i:02X}' for i in addr)
2 changes: 1 addition & 1 deletion custom_components/ble_monitor/ble_parser/teltonika.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,4 @@ def parse_teltonika(self, data, complete_local_name, source_mac, rssi):

def to_mac(addr: int):
"""Return formatted MAC address"""
return ':'.join('{:02x}'.format(x) for x in addr).upper()
return ':'.join(f'{i:02X}' for i in addr)
2 changes: 1 addition & 1 deletion custom_components/ble_monitor/ble_parser/thermoplus.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,4 @@ def parse_thermoplus(self, data, source_mac, rssi):

def to_mac(addr: int):
"""Return formatted MAC address"""
return ':'.join('{:02x}'.format(x) for x in addr).upper()
return ':'.join(f'{i:02X}' for i in addr)
2 changes: 1 addition & 1 deletion custom_components/ble_monitor/ble_parser/xiaogui.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,4 @@ def parse_xiaogui(self, data, source_mac, rssi):

def to_mac(addr: int):
"""Return formatted MAC address"""
return ':'.join('{:02x}'.format(x) for x in addr).upper()
return ':'.join(f'{i:02X}' for i in addr)
8 changes: 7 additions & 1 deletion custom_components/ble_monitor/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,10 @@ class BLEMonitorBinarySensorEntityDescription(
'Blue Puck RHT' : [["temperature", "humidity", "rssi"], [], []],
'HTP.xw' : [["temperature", "humidity", "pressure", "rssi"], [], []],
'HT.w' : [["temperature", "humidity", "pressure", "rssi"], [], []],
'MyCO2' : [["temperature", "humidity", "co2", "rssi"], [], []],
'SHT40 Gadget' : [["temperature", "humidity", "rssi"], [], []],
'SHT41 Gadget' : [["temperature", "humidity", "rssi"], [], []],
'SHT45 Gadget' : [["temperature", "humidity", "rssi"], [], []],
'Moat S2' : [["temperature", "humidity", "battery", "rssi"], [], []],
'Tempo Disc THD' : [["temperature", "humidity", "dewpoint", "battery", "rssi"], [], []],
'Tempo Disc THPD' : [["temperature", "humidity", "pressure", "battery", "rssi"], [], []],
Expand All @@ -987,7 +991,6 @@ 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
'MyCO2' : [["temperature", "humidity", "co2", "rssi"], [], []],
'EClerk Eco' : [["temperature", "humidity", "co2", "battery", "rssi"], [], []],
'Air Mentor Pro 2' : [["temperature", "temperature calibrated", "humidity", "co2", "tvoc", "aqi", "air quality", "pm2.5", "pm10", "rssi"], [], []],
'Meter TH S1' : [["temperature", "humidity", "battery", "rssi"], [], []],
Expand Down Expand Up @@ -1074,6 +1077,9 @@ class BLEMonitorBinarySensorEntityDescription(
'HTP.xw' : 'SensorPush',
'HT.w' : 'SensorPush',
'MyCO2' : 'Sensirion',
'SHT40 Gadget' : 'Sensirion',
'SHT41 Gadget' : 'Sensirion',
'SHT45 Gadget' : 'Sensirion',
'Moat S2' : 'Moat',
'Tempo Disc THD' : 'BlueMaestro',
'Tempo Disc THPD' : 'BlueMaestro',
Expand Down
2 changes: 1 addition & 1 deletion custom_components/ble_monitor/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
],
"dependencies": [],
"codeowners": ["@Ernst79", "@Magalex2x14", "@Thrilleratplay"],
"version": "8.0.3-beta",
"version": "8.0.4-beta",
"iot_class": "local_polling"
}
10 changes: 5 additions & 5 deletions custom_components/ble_monitor/test/test_miscale_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def test_miscale_v2(self):

def test_miscale_v2_impedance(self):
"""Test Mi Scale v2 parser."""
data_string = "043e2402010001ef148244dedf1802010603021b1810161b1802a6b20701011201128c01a852be"
data_string = "043e2402010001ef148244dedf1802010603021b1810161b180226b20705040f0201ac018642be"
data = bytes(bytearray.fromhex(data_string))

# pylint: disable=unused-variable
Expand All @@ -97,11 +97,11 @@ def test_miscale_v2_impedance(self):
assert sensor_msg["firmware"] == "Mi Scale V2"
assert sensor_msg["type"] == "Mi Scale V2"
assert sensor_msg["mac"] == "DFDE448214EF"
assert sensor_msg["packet"] == "02a6b20701011201128c01a852"
assert sensor_msg["packet"] == "0226b20705040f0201ac018642"
assert sensor_msg["data"]
assert sensor_msg["non-stabilized weight"] == 105.8
assert sensor_msg["non-stabilized weight"] == 85.15
assert sensor_msg["weight unit"] == "kg"
assert sensor_msg["weight removed"] == 1
assert sensor_msg["weight removed"] == 0
assert sensor_msg["stabilized"] == 1
assert sensor_msg["impedance"] == 396
assert sensor_msg["impedance"] == 428
assert sensor_msg["rssi"] == -66
18 changes: 17 additions & 1 deletion custom_components/ble_monitor/test/test_sensirion_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,27 @@ def test_Sensorion_MyCO2(self):

assert sensor_msg["firmware"] == "Sensirion"
assert sensor_msg["type"] == "MyCO2"
assert sensor_msg["mac"] == "F8:EA:DC:3C:67:35"
assert sensor_msg["mac"] == "F8EADC3C6735"
assert sensor_msg["packet"] == "no packet id"
assert sensor_msg["data"] == True
assert sensor_msg["temperature"] == 25.63
assert sensor_msg["humidity"] == 36.16
assert sensor_msg["co2"] == 1035
assert sensor_msg["rssi"] == -80

def test_Sensorion_SHT4x(self):
"""Test Sensirion SHT4x parser."""
data_string = "043e2902010001e7e2c3c067ff1d0201060bffd5060006e2e7036a1c650d09534854343020476164676574b9"
data = bytes(bytearray.fromhex(data_string))
# pylint: disable=unused-variable
ble_parser = BleParser()
sensor_msg, tracker_msg = ble_parser.parse_data(data)

assert sensor_msg["firmware"] == "Sensirion"
assert sensor_msg["type"] == "SHT40 Gadget"
assert sensor_msg["mac"] == "FF67C0C3E2E7"
assert sensor_msg["packet"] == "no packet id"
assert sensor_msg["data"] == True
assert sensor_msg["temperature"] == 27.47
assert sensor_msg["humidity"] == 39.5
assert sensor_msg["rssi"] == -71
20 changes: 20 additions & 0 deletions docs/_devices/Sensirion SHT4x.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
manufacturer: Sensirion
name: Sensirion SHT4x gadget
model: SHT4x gadget
image: sensirion_SHT4x.png
physical_description:
broadcasted_properties:
- temperature
- humidity
- rssi
broadcasted_property_notes:
broadcast_rate:
active_scan:
encryption_key:
custom_firmware:
notes:
- BLE monitor doesn't support any of the other Bluetooth features (LED control, download of past data etc.), due to the passive way of getting the data.
- The protocol is publically available at Sensirion/arduino-ble-gadget and used to feed data into the Sensirion MyAmbience App (Android + iOS)
- The same protocol is used by other Sensirion BLE devices as well, but these have not been implemented yet. If you want support for other Sensirion devices, create a new issue.
---
Binary file added docs/assets/images/sensirion_SHT4x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit ca88318

Please sign in to comment.