Skip to content

Commit

Permalink
feat: add passive support for GV5121/GV5122/GV5123/GV5125/GV5126 (#89)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco authored Jul 16, 2024
1 parent 0043f59 commit 83ac0d5
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 31 deletions.
67 changes: 43 additions & 24 deletions src/govee_ble/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,42 +171,61 @@ def _process_mfr_data(
data = data[:-25]
msg_length = len(data)
if debug_logging:
_LOGGER.debug("Cleaned up packet: %s %s", mgr_id, hex(data))

if msg_length == 24 and (
(is_5121 := "GV5121" in local_name)
or (is_5122 := "GV5122" in local_name)
or (is_5123 := "GV5123" in local_name)
or (is_5125 := "GV5125" in local_name)
or (is_5126 := "GV5126" in local_name)
):
sensor_type = SensorType.BUTTON
if is_5121:
self.set_device_type("H5121")
sensor_type = SensorType.MOTION
elif is_5122:
self.set_device_type("H5122")
elif is_5123:
self.set_device_type("H5123")
sensor_type = SensorType.WINDOW
elif is_5125:
self.set_device_type("H5125")
elif is_5126:
self.set_device_type("H5126")
b_front_of_device_id = data[:2]
assert b_front_of_device_id
_LOGGER.debug("Cleaned up packet: %s %s", mgr_id, data.hex())

if msg_length == 24:
front_of_device_id = data[:2]
assert front_of_device_id
time_ms = data[2:6]
enc_data = data[6:22]
enc_crc = data[22:24]
if not calculate_crc(enc_data) == int.from_bytes(enc_crc, "big"):
_LOGGER.warning("CRC check failed for H512x: %s", hex(data))
return

key = time_ms + bytes(12)
try:
decrypted = decrypt_data(key, enc_data)
except ValueError:
_LOGGER.warning("Failed to decrypt H512x: %s", hex(data))
return
model_id = decrypted[2]
# GV5121
# 01040302640100000000000000000000

# GV5122
# 01050802640000000000000000000000
# 01050802640000000000000000000000

# GV5123
# 01050202640200000000000000000000

# GV5125
# 01010a02640000000000000000000000

# GV5126
# 01010b02640100000000000000000000
sensor_type = SensorType.BUTTON
if "GV5121" in local_name or model_id == 3:
self.set_device_type("H5121")
self.set_device_name(f"5121{short_address(address)}")
sensor_type = SensorType.MOTION
elif "GV5122" in local_name or model_id == 8:
self.set_device_type("H5122")
self.set_device_name(f"5122{short_address(address)}")
elif "GV5123" in local_name or model_id == 2:
self.set_device_type("H5123")
self.set_device_name(f"5123{short_address(address)}")
sensor_type = SensorType.WINDOW
elif "GV5125" in local_name or model_id == 10:
self.set_device_type("H5125")
self.set_device_name(f"5125{short_address(address)}")
elif "GV5126" in local_name or model_id == 11:
self.set_device_type("H5126")
self.set_device_name(f"5126{short_address(address)}")
else:
return

battery_percentage = decrypted[4]
button_number_pressed = decrypted[5]
self.update_predefined_sensor(
Expand Down
140 changes: 133 additions & 7 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,28 @@
service_uuids=[],
source="24:4C:AB:03:E6:B8",
)
GV5122_PASSIVE_SERVICE_INFO = BluetoothServiceInfo(
name="D2:32:39:37:56:34",
address="D2:32:39:37:56:34",
rssi=-68,
manufacturer_data={
61320: b'\xf3\n\x00$\xaa\xea\xa5c\x1b\x81\x08\x99\xe1\xc4\xe1@\x98\x83\xfe"Y5\xc4d'
},
service_data={},
service_uuids=[],
source="08:3A:F2:7B:50:9C",
)
GV5122_PASSIVE_2_SERVICE_INFO = BluetoothServiceInfo(
name="D2:32:39:37:56:34",
address="D2:32:39:37:56:34",
rssi=-68,
manufacturer_data={
61320: b"\xfe~\x00\x00\tL\xa8j\x1a\xf0\xd2\xbcD&\x0b\xd5\xaf4L\x0b\xe5\xc7\xf1\n"
},
service_data={},
service_uuids=[],
source="08:3A:F2:7B:50:9C",
)


def test_can_create():
Expand Down Expand Up @@ -1648,7 +1670,7 @@ def test_gvh5125_button_0():
title=None,
devices={
None: SensorDeviceInfo(
name="51255367",
name="51250F45",
model="H5125",
manufacturer="Govee",
sw_version=None,
Expand Down Expand Up @@ -1700,7 +1722,7 @@ def test_gvh5125_button_1():
title=None,
devices={
None: SensorDeviceInfo(
name="51255367",
name="51250F45",
model="H5125",
manufacturer="Govee",
sw_version=None,
Expand Down Expand Up @@ -1752,7 +1774,7 @@ def test_gvh5122_button_0():
title=None,
devices={
None: SensorDeviceInfo(
name="51225634",
name="51220F45",
model="H5122",
manufacturer="Govee",
sw_version=None,
Expand Down Expand Up @@ -1796,6 +1818,110 @@ def test_gvh5122_button_0():
)


def test_gvh5122_passive_button_0():
parser = GoveeBluetoothDeviceData()
service_info = GV5122_PASSIVE_SERVICE_INFO
result = parser.update(service_info)
assert result == SensorUpdate(
title=None,
devices={
None: SensorDeviceInfo(
name="51225634",
model="H5122",
manufacturer="Govee",
sw_version=None,
hw_version=None,
)
},
entity_descriptions={
DeviceKey(key="battery", device_id=None): SensorDescription(
device_key=DeviceKey(key="battery", device_id=None),
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=Units.PERCENTAGE,
),
DeviceKey(key="signal_strength", device_id=None): SensorDescription(
device_key=DeviceKey(key="signal_strength", device_id=None),
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
DeviceKey(key="battery", device_id=None): SensorValue(
device_key=DeviceKey(key="battery", device_id=None),
name="Battery",
native_value=100,
),
DeviceKey(key="signal_strength", device_id=None): SensorValue(
device_key=DeviceKey(key="signal_strength", device_id=None),
name="Signal " "Strength",
native_value=-68,
),
},
binary_entity_descriptions={},
binary_entity_values={},
events={
DeviceKey(key="button_0", device_id=None): Event(
device_key=DeviceKey(key="button_0", device_id=None),
name="Button " "0",
event_type="press",
event_properties=None,
)
},
)


def test_gvh5122_passive_2_button_0():
parser = GoveeBluetoothDeviceData()
service_info = GV5122_PASSIVE_2_SERVICE_INFO
result = parser.update(service_info)
assert result == SensorUpdate(
title=None,
devices={
None: SensorDeviceInfo(
name="51225634",
model="H5122",
manufacturer="Govee",
sw_version=None,
hw_version=None,
)
},
entity_descriptions={
DeviceKey(key="battery", device_id=None): SensorDescription(
device_key=DeviceKey(key="battery", device_id=None),
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=Units.PERCENTAGE,
),
DeviceKey(key="signal_strength", device_id=None): SensorDescription(
device_key=DeviceKey(key="signal_strength", device_id=None),
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
),
},
entity_values={
DeviceKey(key="battery", device_id=None): SensorValue(
device_key=DeviceKey(key="battery", device_id=None),
name="Battery",
native_value=100,
),
DeviceKey(key="signal_strength", device_id=None): SensorValue(
device_key=DeviceKey(key="signal_strength", device_id=None),
name="Signal " "Strength",
native_value=-68,
),
},
binary_entity_descriptions={},
binary_entity_values={},
events={
DeviceKey(key="button_0", device_id=None): Event(
device_key=DeviceKey(key="button_0", device_id=None),
name="Button " "0",
event_type="press",
event_properties=None,
)
},
)


def test_gvh5123_open():
parser = GoveeBluetoothDeviceData()
service_info = GV5123_OPEN_SERVICE_INFO
Expand All @@ -1804,7 +1930,7 @@ def test_gvh5123_open():
title=None,
devices={
None: SensorDeviceInfo(
name="51230B3D",
name="51230F45",
model="H5123",
manufacturer="Govee",
sw_version=None,
Expand Down Expand Up @@ -1860,7 +1986,7 @@ def test_gvh5123_closed():
title=None,
devices={
None: SensorDeviceInfo(
name="51230B3D",
name="51230F45",
model="H5123",
manufacturer="Govee",
sw_version=None,
Expand Down Expand Up @@ -1917,7 +2043,7 @@ def test_gvh5121_motion():
title=None,
devices={
None: SensorDeviceInfo(
name="5121195A",
name="51210F45",
model="H5121",
manufacturer="Govee",
sw_version=None,
Expand Down Expand Up @@ -1970,7 +2096,7 @@ def test_gvh5121_motion_2():
title=None,
devices={
None: SensorDeviceInfo(
name="5121195A",
name="51210F45",
model="H5121",
manufacturer="Govee",
sw_version=None,
Expand Down

0 comments on commit 83ac0d5

Please sign in to comment.