Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sensirion SHT4x and miscale update #777

Merged
merged 3 commits into from
Mar 18, 2022
Merged
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
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 @@ -65,6 +65,7 @@
"mode",
"sector_timer",
"number_of_sectors",
"weight",
]


Expand Down Expand Up @@ -437,6 +438,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 @@ -900,6 +900,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 @@ -915,7 +919,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"], [], []],
'HA BLE DIY' : [["temperature", "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"], [], []],
Expand Down Expand Up @@ -1003,6 +1006,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": "7.9.7",
"version": "7.9.8-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.