Skip to content

Instantly share code, notes, and snippets.

@taylorfinnell
Last active February 11, 2024 00:57
Show Gist options
  • Save taylorfinnell/87c79939a63ec2cb607ed2ebe28db5ce to your computer and use it in GitHub Desktop.
Save taylorfinnell/87c79939a63ec2cb607ed2ebe28db5ce to your computer and use it in GitHub Desktop.
hai-smart-showerhead.py
import asyncio
import struct
from bleak import BleakClient, BleakScanner
from dataclasses import dataclass
from datetime import datetime
XOR_DECRYPTION_KEY = [1, 2, 3, 4, 5, 6] # Yes, for real
@dataclass(eq=True, frozen=True)
class Characteristic:
type: str
desc: str
uuid: str
transform: callable
encrypted: bool
notifies: bool
def duration_transform(val):
from datetime import timedelta
delta = timedelta(seconds=val)
hours, remainder = divmod(delta.total_seconds(), 3600)
minutes, seconds = divmod(remainder, 60)
return f"{int(minutes)}:{int(seconds)}"
def temp_transform(val):
temp = val
temp /= 100
return (1.8 * temp) + 32
def flow_transform(val):
return float(val) * 0.000264172
def consumption_transform(val):
return float(val) * 0.000264172
def timestamp_transform(val):
return datetime.fromtimestamp(val)
def battery_transform(val):
return val / 1000.0
SESSION_ID_CHAR = Characteristic(
desc="Session ID",
notifies=True,
encrypted=False,
transform=None,
uuid="e6221401-e12f-40f2-b0f5-aaa011c0aa8d",
type="i",
)
WATER_TEMP_CHAR = Characteristic(
desc="Water Temperature (F)",
notifies=True,
encrypted=False,
transform=temp_transform,
uuid="e6221402-e12f-40f2-b0f5-aaa011c0aa8d",
type="h",
)
AVG_WATER_TEMP_CHAR = Characteristic(
desc="Avg Shower Water Temperature (F)",
notifies=True,
encrypted=False,
transform=temp_transform,
uuid="e6221403-e12f-40f2-b0f5-aaa011c0aa8d",
type="h",
)
WATER_CONSUMPTION_CHAR = Characteristic(
desc="Water Consumption (gal)",
notifies=True,
encrypted=True,
transform=consumption_transform,
uuid="e6221404-e12f-40f2-b0f5-aaa011c0aa8d",
type="i",
)
SHOWER_FLOW_CHAR = Characteristic(
desc="Shower Water Flow Unit (gal/sec)",
notifies=True,
encrypted=True,
transform=flow_transform,
uuid="e6221405-e12f-40f2-b0f5-aaa011c0aa8d",
type="i",
)
SHOWER_DURATION_CHAR = Characteristic(
desc="Shower Duration",
notifies=True,
encrypted=True,
transform=duration_transform,
uuid="e6221406-e12f-40f2-b0f5-aaa011c0aa8d",
type="h",
)
SHOWER_TIMESTAMP_CHAR = Characteristic(
desc="Start Time",
notifies=True,
transform=timestamp_transform,
encrypted=True,
uuid="e6221407-e12f-40f2-b0f5-aaa011c0aa8d",
type="i",
)
SHOWER_LIFETIME_CONSUMPTION_CHAR = Characteristic(
desc="Lifetime Consumption (gal)",
notifies=True,
transform=consumption_transform,
uuid="e6221408-e12f-40f2-b0f5-aaa011c0aa8d",
type="i",
encrypted=True,
)
SHOWER_LIFETIME_WATER_TEMP_CHAR = Characteristic(
transform=temp_transform,
notifies=True,
encrypted=False,
uuid="e6221409-e12f-40f2-b0f5-aaa011c0aa8d",
type="h",
desc="Lifetime Water Temperature Unit: F",
)
SHOWER_BATTERY_CHAR = Characteristic(
desc="Battery Voltage (V)",
notifies=True,
encrypted=False,
transform=battery_transform,
uuid="e622140c-e12f-40f2-b0f5-aaa011c0aa8d",
type="h",
)
ALL_CHARACTERISTICS = [
SESSION_ID_CHAR,
WATER_TEMP_CHAR,
AVG_WATER_TEMP_CHAR,
WATER_CONSUMPTION_CHAR,
SHOWER_FLOW_CHAR,
SHOWER_DURATION_CHAR,
SHOWER_TIMESTAMP_CHAR,
SHOWER_LIFETIME_CONSUMPTION_CHAR,
SHOWER_LIFETIME_WATER_TEMP_CHAR,
SHOWER_BATTERY_CHAR,
]
def decrypt(data, key=XOR_DECRYPTION_KEY):
return bytes([b ^ key[i % len(key)] for i, b in enumerate(data)])
def print_reading(char: Characteristic, val):
print(f"{char.desc}:", val)
def build_notify_callback(char):
def cb(sender, data):
data = data if not char.encrypted else decrypt(data)
val = struct.unpack(char.type, data)[0]
if char.transform:
val = char.transform(val)
print_reading(char, val)
return cb
disconnected_event = asyncio.Event()
def disconnected_callback(client):
disconnected_event.set()
async def main():
print("Connecting...")
device = await BleakScanner.find_device_by_name(
"haiSmartShower", cb={"use_bdaddr": False}
)
async with BleakClient(device) as client:
print("Connected...")
for c in filter(lambda c: c.notifies, ALL_CHARACTERISTICS):
print("Subscribing to changes for:", c.desc)
await client.start_notify(c.uuid, build_notify_callback(c))
await disconnected_event.wait()
print("Disconnected")
asyncio.run(main())
# 2024-02-09 20:37:27,881 __main__ INFO: starting scan...
# 2024-02-09 20:37:33,962 __main__ INFO: connecting to device...
# 2024-02-09 20:37:37,383 __main__ INFO: connected
# 2024-02-09 20:37:37,383 __main__ INFO: [Service] e6221400-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 14): Unknown
# 2024-02-09 20:37:37,414 __main__ INFO: [Characteristic] e6221401-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 15): Unknown (read,notify), Value: '00000000'
# 2024-02-09 20:37:37,443 __main__ INFO: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 17): Client Characteristic Configuration, Value: bytearray(b'')
# 2024-02-09 20:37:37,504 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 18): Characteristic User Description, Value: bytearray(b'Current Shower ID')
# 2024-02-09 20:37:37,533 __main__ INFO: [Characteristic] e6221402-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 19): Unknown (read,notify), Value: '0000'
# 2024-02-09 20:37:37,564 __main__ INFO: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 21): Client Characteristic Configuration, Value: bytearray(b'')
# 2024-02-09 20:37:37,624 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 22): Characteristic User Description, Value: bytearray(b'Real Time Water Temperature\nUnit: cCelsius')
# 2024-02-09 20:37:37,653 __main__ INFO: [Characteristic] e6221403-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 23): Unknown (read,notify), Value: '0000'
# 2024-02-09 20:37:37,683 __main__ INFO: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 25): Client Characteristic Configuration, Value: bytearray(b'')
# 2024-02-09 20:37:37,759 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 26): Characteristic User Description, Value: bytearray(b'Average Shower Water Temperature\nUnit: cCelsius')
# 2024-02-09 20:37:37,788 __main__ INFO: [Characteristic] e6221404-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 27): Unknown (read,notify), Value: '00000000'
# 2024-02-09 20:37:37,833 __main__ INFO: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 29): Client Characteristic Configuration, Value: bytearray(b'')
# 2024-02-09 20:37:37,908 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 30): Characteristic User Description, Value: bytearray(b'Total Water consumption\nUnit: mL')
# 2024-02-09 20:37:37,939 __main__ INFO: [Characteristic] e6221405-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 31): Unknown (read,notify), Value: '00000000'
# 2024-02-09 20:37:37,968 __main__ INFO: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 33): Client Characteristic Configuration, Value: bytearray(b'')
# 2024-02-09 20:37:38,059 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 34): Characteristic User Description, Value: bytearray(b'Current Shower Water Flow\nUnit: mL/sec')
# 2024-02-09 20:37:38,088 __main__ INFO: [Characteristic] e6221406-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 35): Unknown (read,notify), Value: '0000'
# 2024-02-09 20:37:38,118 __main__ INFO: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 37): Client Characteristic Configuration, Value: bytearray(b'')
# 2024-02-09 20:37:38,178 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 38): Characteristic User Description, Value: bytearray(b'Current Shower Duration\nUnit: sec')
# 2024-02-09 20:37:38,223 __main__ INFO: [Characteristic] e6221407-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 39): Unknown (read,notify), Value: '00000000'
# 2024-02-09 20:37:38,253 __main__ INFO: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 41): Client Characteristic Configuration, Value: bytearray(b'')
# 2024-02-09 20:37:38,314 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 42): Characteristic User Description, Value: bytearray(b'Current Shower Timestamp (epoch)\nUnit: sec')
# 2024-02-09 20:37:38,344 __main__ INFO: [Characteristic] e6221408-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 43): Unknown (read,notify), Value: '00000000'
# 2024-02-09 20:37:38,373 __main__ INFO: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 45): Client Characteristic Configuration, Value: bytearray(b'')
# 2024-02-09 20:37:38,448 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 46): Characteristic User Description, Value: bytearray(b"Device\'s Lifetime Water consumption\nUnit: mL")
# 2024-02-09 20:37:38,478 __main__ INFO: [Characteristic] e6221409-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 47): Unknown (read,notify), Value: '0000'
# 2024-02-09 20:37:38,508 __main__ INFO: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 49): Client Characteristic Configuration, Value: bytearray(b'')
# 2024-02-09 20:37:38,569 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 50): Characteristic User Description, Value: bytearray(b'Average Lifetime Water Temperature\nUnit: cCelsius')
# 2024-02-09 20:37:38,599 __main__ INFO: [Characteristic] e622140a-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 51): Unknown (read,notify), Value: '1f020304600af5006f9b0406a5cdc561c20d'
# 2024-02-09 20:37:38,628 __main__ INFO: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 53): Client Characteristic Configuration, Value: bytearray(b'')
# 2024-02-09 20:37:38,659 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 54): Characteristic User Description, Value: bytearray(b'Binary data of completed shower')
# 2024-02-09 20:37:38,689 __main__ INFO: [Characteristic] e622140b-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 55): Unknown (read), Value: 'a172203101f9'
# 2024-02-09 20:37:38,719 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 57): Characteristic User Description, Value: bytearray(b'Product ID')
# 2024-02-09 20:37:38,748 __main__ INFO: [Characteristic] e622140c-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 58): Unknown (read,notify), Value: 'd10d'
# 2024-02-09 20:37:38,793 __main__ INFO: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 60): Client Characteristic Configuration, Value: bytearray(b'')
# 2024-02-09 20:37:38,853 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 61): Characteristic User Description, Value: bytearray(b'Current battery voltage\nUnit: mV')
# 2024-02-09 20:37:38,853 __main__ INFO: [Service] e6221500-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 62): Unknown
# 2024-02-09 20:37:38,883 __main__ INFO: [Characteristic] e6221501-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 63): Unknown (read,write), Value: '00000000'
# 2024-02-09 20:37:38,943 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 65): Characteristic User Description, Value: bytearray(b'First level threshold value\nUnit: mL')
# 2024-02-09 20:37:38,973 __main__ INFO: [Characteristic] e6221502-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 66): Unknown (read,write), Value: '00000000'
# 2024-02-09 20:37:39,033 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 68): Characteristic User Description, Value: bytearray(b'Second level threshold value\nUnit: mL')
# 2024-02-09 20:37:39,064 __main__ INFO: [Characteristic] e6221503-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 69): Unknown (read,write), Value: 'bd250204'
# 2024-02-09 20:37:39,138 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 71): Characteristic User Description, Value: bytearray(b'Third level threshold value\nUnit: mL')
# 2024-02-09 20:37:39,138 __main__ INFO: [Characteristic] e6221504-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 72): Unknown (write)
# 2024-02-09 20:37:39,169 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 74): Characteristic User Description, Value: bytearray(b'Current device time (epoch)')
# 2024-02-09 20:37:39,198 __main__ INFO: [Characteristic] e6221505-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 75): Unknown (read,write), Value: '000000'
# 2024-02-09 20:37:39,229 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 77): Characteristic User Description, Value: bytearray(b'First level RGB LED color')
# 2024-02-09 20:37:39,258 __main__ INFO: [Characteristic] e6221506-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 78): Unknown (read,write), Value: '000000'
# 2024-02-09 20:37:39,289 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 80): Characteristic User Description, Value: bytearray(b'Second level RGB LED color')
# 2024-02-09 20:37:39,319 __main__ INFO: [Characteristic] e6221507-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 81): Unknown (read,write), Value: '000000'
# 2024-02-09 20:37:39,349 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 83): Characteristic User Description, Value: bytearray(b'Third level RGB LED color')
# 2024-02-09 20:37:39,379 __main__ INFO: [Characteristic] e6221508-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 84): Unknown (read,write), Value: 'fe2203'
# 2024-02-09 20:37:39,409 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 86): Characteristic User Description, Value: bytearray(b'Fourth level RGB LED color')
# 2024-02-09 20:37:39,439 __main__ INFO: [Characteristic] e6221509-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 87): Unknown (read,write), Value: '01fd03'
# 2024-02-09 20:37:39,469 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 89): Characteristic User Description, Value: bytearray(b'Temperature level RGB LED color')
# 2024-02-09 20:37:39,469 __main__ INFO: [Characteristic] e622150a-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 90): Unknown (write)
# 2024-02-09 20:37:39,499 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 92): Characteristic User Description, Value: bytearray(b'Shower history erase')
# 2024-02-09 20:37:39,528 __main__ INFO: [Characteristic] e622150b-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 93): Unknown (read), Value: '6302'
# 2024-02-09 20:37:39,559 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 95): Characteristic User Description, Value: bytearray(b'Application version')
# 2024-02-09 20:37:39,603 __main__ INFO: [Characteristic] e622150c-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 96): Unknown (read), Value: '01'
# 2024-02-09 20:37:39,634 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 98): Characteristic User Description, Value: bytearray(b'Bootloader version')
# 2024-02-09 20:37:39,679 __main__ INFO: [Characteristic] e622150d-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 99): Unknown (read,write), Value: '01020304050601024c410406970e0f04050601020304050601fd038005a4'
# 2024-02-09 20:37:39,739 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 101): Characteristic User Description, Value: bytearray(b'Level Configuration (thresholds and RGB LED colors)')
# 2024-02-09 20:37:39,739 __main__ INFO: [Characteristic] e622150e-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 102): Unknown (write)
# 2024-02-09 20:37:39,768 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 104): Characteristic User Description, Value: bytearray(b'Factory reset')
# 2024-02-09 20:37:39,768 __main__ INFO: [Service] e6221600-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 105): Unknown
# 2024-02-09 20:37:39,769 __main__ INFO: [Characteristic] e6221601-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 106): Unknown (write)
# 2024-02-09 20:37:40,012 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 108): Characteristic User Description, Value: bytearray(b'Write any value from 0 to 255 to this characteristic to trigger a shower history read-out')
# 2024-02-09 20:37:42,005 __main__ INFO: [Characteristic] e6221602-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 109): Unknown (read,notify), Value: '1a020304'
# 2024-02-09 20:37:46,993 __main__ INFO: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 111): Client Characteristic Configuration, Value: bytearray(b'')
# 2024-02-09 20:37:50,983 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 112): Characteristic User Description, Value: bytearray(b'Number of showers available in memory')
# 2024-02-09 20:37:50,983 __main__ INFO: [Characteristic] e6221603-e12f-40f2-b0f5-aaa011c0aa8d (Handle: 113): Unknown (notify)
# 2024-02-09 20:37:52,978 __main__ INFO: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 115): Client Characteristic Configuration, Value: bytearray(b'')
# 2024-02-09 20:37:56,969 __main__ INFO: [Descriptor] 00002901-0000-1000-8000-00805f9b34fb (Handle: 116): Characteristic User Description, Value: bytearray(b'Holds the data of a shower stored in the flash of the device')
# 2024-02-09 20:37:56,969 __main__ INFO: [Service] 0000fe59-0000-1000-8000-00805f9b34fb (Handle: 117): Nordic Semiconductor ASA
# 2024-02-09 20:37:56,969 __main__ INFO: [Characteristic] 8ec90003-f315-4f60-9fb8-838830daea50 (Handle: 118): Buttonless DFU (write,indicate)
# 2024-02-09 20:37:58,963 __main__ INFO: [Descriptor] 00002902-0000-1000-8000-00805f9b34fb (Handle: 120): Client Characteristic Configuration, Value: bytearray(b'')
# 2024-02-09 20:37:58,963 __main__ INFO: disconnecting...
# 2024-02-09 20:37:58,964 __main__ INFO: disconnected
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment