import bluetooth, time, struct

ble = bluetooth.BLE()
ble.active(True)

conn = None
services = []
chars = []
svc_done = False
chr_done = False

_IRQ_PERIPHERAL_CONNECT   = 7
_IRQ_PERIPHERAL_DISCONNECT= 8
_IRQ_GATTC_SERVICE_RESULT = 9
_IRQ_GATTC_SERVICE_DONE   = 10
_IRQ_GATTC_CHAR_RESULT    = 11
_IRQ_GATTC_CHAR_DONE      = 12
_IRQ_GATTC_WRITE_DONE     = 17
_IRQ_GATTC_NOTIFY         = 18

notify_data = []
write_done  = False

def irq(ev, data):
    global conn, svc_done, chr_done, write_done
    if ev == _IRQ_PERIPHERAL_CONNECT:
        c, at, addr = data; conn = c
        print("CONNECTED", conn)
    elif ev == _IRQ_PERIPHERAL_DISCONNECT:
        conn = None; print("DISCONNECTED")
    elif ev == _IRQ_GATTC_SERVICE_RESULT:
        conn_h, start, end, uuid = data
        services.append((start, end, bytes(uuid).hex()))
    elif ev == _IRQ_GATTC_SERVICE_DONE:
        svc_done = True
    elif ev == _IRQ_GATTC_CHAR_RESULT:
        conn_h, def_h, val_h, props, uuid = data
        chars.append((def_h, val_h, props, bytes(uuid).hex()))
    elif ev == _IRQ_GATTC_CHAR_DONE:
        chr_done = True
    elif ev == _IRQ_GATTC_WRITE_DONE:
        write_done = True
        print("WRITE DONE:", data)
    elif ev == _IRQ_GATTC_NOTIFY:
        ch, vh, d = data
        nd = bytes(d)
        notify_data.append((vh, nd.hex()))
        print("NOTIFY vh={} {}b: {}".format(vh, len(nd), nd.hex()[:40]))
    else:
        print("IRQ", ev)

ble.irq(irq)
ble.gap_connect(0, bytes([0x24,0x19,0x72,0x61,0x9D,0x62]))
t = time.ticks_ms()
while conn is None:
    if time.ticks_diff(time.ticks_ms(), t) > 10000: raise Exception("CONN TIMEOUT")
    time.sleep_ms(50)
print("Connected"); time.sleep_ms(500)

# Discover all services
ble.gattc_discover_services(conn)
t = time.ticks_ms()
while not svc_done:
    if time.ticks_diff(time.ticks_ms(), t) > 5000: break
    time.sleep_ms(50)
print("Services:")
for s in services: print(" ", s)

# Discover chars in each service
for start, end, uuid in services:
    chr_done = False
    ble.gattc_discover_characteristics(conn, start, end)
    t = time.ticks_ms()
    while not chr_done:
        if time.ticks_diff(time.ticks_ms(), t) > 5000: break
        time.sleep_ms(50)

print("Chars:")
for c in chars: print("  def={} val={} props={} uuid={}".format(*c))

# Enable all notify CCCDs and send command
for def_h, val_h, props, uuid in chars:
    if props & 0x10:  # NOTIFY property
        print("Enabling notify on val_h={}".format(val_h))
        write_done = False
        ble.gattc_write(conn, val_h + 1, struct.pack("<H", 0x0001), 1)
        t = time.ticks_ms()
        while not write_done:
            if time.ticks_diff(time.ticks_ms(), t) > 3000: break
            time.sleep_ms(50)

def crc16(data):
    crc = 0xFFFF
    for b in data:
        crc ^= b
        for _ in range(8):
            crc = (crc >> 1) ^ 0xA001 if crc & 1 else crc >> 1
    return crc & 0xFFFF

SN = b"30000251257777051"
payload = b"\x0a" + bytes([len(SN)]) + SN
frame = b"HM\xa3\x03" + struct.pack(">HHH", 1, crc16(payload), len(payload)+10) + payload
print("TX {}b: {}".format(len(frame), frame.hex()))

# Write to the write characteristic (find by WRITE property)
for def_h, val_h, props, uuid in chars:
    if props & 0x08:  # WRITE property
        print("Writing to val_h={}".format(val_h))
        write_done = False
        ble.gattc_write(conn, val_h, frame, 1)
        t = time.ticks_ms()
        while not write_done:
            if time.ticks_diff(time.ticks_ms(), t) > 5000: break
            time.sleep_ms(50)
        print("Write done")

# Wait for notify
t = time.ticks_ms()
while len(notify_data) == 0:
    if time.ticks_diff(time.ticks_ms(), t) > 15000:
        print("NO NOTIFY"); break
    time.sleep_ms(100)

print("Got {} notifies".format(len(notify_data)))
for vh, d in notify_data: print("vh={} {}".format(vh, d))
if conn: ble.gap_disconnect(conn)
print("DONE")
