Oh my goodness. Whilst I was sitting at our campfire outside our RV, I was able to reverse engineer the android app and figure out exactly how to pull the data out of the subscribable characteristic. I’ve written a python script using the bleak
package which works on both Windows and macOS.
I’m not entirely sure what line
means- it’s either 2 or 1- but the rest of the values are just 4 byte integers that need to be divided by 10,000. This was as much as there was when I decompiled the apk. It’s so inefficient and confusing but hey, the data is correct!
{'volts': 120.2846, 'amps': 15.32, 'watts': 1738.4, 'energy': 702.37, 'line': 2}
Looking at it, I guess we only need the first packet starting with 01 03 20
. I’m gonna try to turn this into something I can use with home assistant and publish it on github, though I’m still trying to get a raspberry pi for my camper but I’m working on it in the meantime.
There’s an ascii diagram in the code but here it is anyway:
# 0 => 01 03 20 00 12 5b a4 00 01 43 07 00 93 c8 08 00 6a cd 68 00
# \______/ \_________/ \_________/ \_________/ \_________/
# header volts amps watts energy
#
# 1 => 00 03 d0 00 53 d7 01 ff df 10 00 00 00 17 6d c7 1c 00 00 00
# \______/
# line
Just make sure to run pip install bleak
first:
import asyncio
import platform
from bleak import BleakScanner, BleakError, BleakClient
address = (
"mac address here"
if platform.system() != "Darwin" else
"uuid here for macos"
)
async def main():
# devices = await BleakScanner.discover()
# for device in devices:
# print(device)
device = await BleakScanner.find_device_by_address(address, timeout=5)
if not device:
raise BleakError("Device not found")
async with BleakClient(device) as client:
if not client.is_connected:
await client.connect()
first_chunk = None
def on_data(sender, chunk):
chunk = bytes(chunk)
global first_chunk
if chunk[0] == 1 and chunk[1] == 3 and chunk[2] == 32:
first_chunk = chunk
else:
# we got the second chunk which includes the rest of the data
buffer = first_chunk + chunk
first_chunk = None
# 0 => 01 03 20 00 12 5b a4 00 01 43 07 00 93 c8 08 00 6a cd 68 00
# \______/ \_________/ \_________/ \_________/ \_________/
# header volts amps watts energy
#
# 1 => 00 03 d0 00 53 d7 01 ff df 10 00 00 00 17 6d c7 1c 00 00 00
# \______/
# line
volts = int.from_bytes(buffer[3:7], "big") / 10000
amps = int.from_bytes(buffer[7:11], "big") / 10000
watts = int.from_bytes(buffer[11:15], "big") / 10000
energy = int.from_bytes(buffer[15:19], "big") / 10000
line = None
if buffer[37] == 0 and buffer[38] == 0 and buffer[39] == 0:
line = 2
elif buffer[37] == 1 and buffer[38] == 1 and buffer[39] == 1:
line = 1
print({
"volts": volts,
"amps": amps,
"watts": watts,
"energy": energy,
"line": line,
})
# for service in client.services:
# print(service)
# for char in service.characteristics:
# print(char)
await client.start_notify("0000ffe2-0000-1000-8000-00805f9b34fb", on_data)
while True:
await asyncio.sleep(1)
asyncio.run(main())