Skip to content

Commit

Permalink
document write characteristic MTU size limit and implement frame spli…
Browse files Browse the repository at this point in the history
…tting on write
  • Loading branch information
nkraetzschmar committed Feb 6, 2024
1 parent 97b8c04 commit 7f3d043
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 7 deletions.
12 changes: 9 additions & 3 deletions ikawa_protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,23 @@ Communication between the app and the roaster is facilitated over Bluetooth Low
1. Write characteristic `851A4582-19C1-4E6C-AB37-E7A03766BA16` for sending commands to the roaster.
2. Notify characteristic `948C5059-7F00-46D9-AC55-BF090AE066E3` for receiving responses to commands sent over the write characteristic.

To complete a request-response interaction, a request must be written to the write characteristic, and a listener on the notify characteristic must await the result. Each request and response is tagged with a sequence number to ensure they are correctly matched up.

> [!IMPORTANT]
> To complete a request-response interaction, a request must be written to the write characteristic, and a listener on the notify characteristic must await the result. Each request and response is tagged with a sequence number to ensure they are correctly matched up.
> The write characteristic only accepts 20 bytes[^1] at a time, so longer data frames need to be split into multiple writes.
> Similarly the notify characteristic provides at most 20 bytes at a time, so a notify may not contain a complete frame and the frame may need to be assembled from multiple notify events.
[^1]: MTU size of 23 - 3 bytes for header

## Frames

Data sent and received are encapsulated in frames, structured as follows:

- Start and end with FRAME_BYTE: `0x7E`
- Encapsulated payload:
- Start FRAME_BYTE: `0x7E`
- Escaped payload:
- Message data
- Custom CRC16 checksum
- End FRAME_BYTE: `0x7E`

Payload escaping is performed by replacing `0x7E` with `0x7D 0x5E` and `0x7D` with `0x7D 0x5D`. This applies to both the message data and the CRC checksum.

Expand Down
12 changes: 8 additions & 4 deletions libikawa.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ class Ikawa:
ESCAPE_MAPPING = {0x7D: 0x5D, 0x7E: 0x5E}
UNESCAPE_MAPPING = {0x5D: 0x7D, 0x5E: 0x7E}

def __init__(self, reconnect=True, retry_timeout=10):
def __init__(self, reconnect=True, reconnect_timeout=60, retry_timeout=10):
self.seq = 1 # start with 1 because seq=0 makes Cmd(cmd_type=BOOTLOADER_GET_VERSION) an empty message which the firmware does not seem to handle
self.resp_queue = asyncio.Queue()
self.recv_buf = bytearray()
self.reconnect = True
self.reconnect_timeout = reconnect_timeout
self.retry_timeout = retry_timeout

async def __aenter__(self):
Expand All @@ -45,7 +46,7 @@ def filter_device(device, advertisement_data):
async def connect(self):
self.client = BleakClient(self.target_device, disconnected_callback=self.on_disconnect)
t = time.time()
while time.time() - t < self.retry_timeout:
while time.time() - t < self.reconnect_timeout:
print("Trying to connect")
try:
await self.client.connect()
Expand Down Expand Up @@ -86,7 +87,7 @@ def on_disconnect(self, client):
asyncio.get_running_loop().create_task(self.connect())

async def on_notify(self, sender, data):
# print(f"notify recieved: {data}")
# print(f"notify recieved: (len={len(data)}) {data}")
self.recv_buf += data
while True:
try:
Expand Down Expand Up @@ -119,7 +120,10 @@ async def send_cmd(self, cmd):
data = cmd.SerializeToString()
cmd.seq = 0
frame = self.encode_frame(data)
await self.send_frame(frame)
for i in range(0, len(frame), 20):
frame_part = frame[i:i+20]
# print(f"sending (len={len(frame_part)}) {frame_part}")
await self.send_frame(frame_part)
# print("waiting for response")
resp = await asyncio.wait_for(self.resp_queue.get(), self.retry_timeout)
return resp
Expand Down

0 comments on commit 7f3d043

Please sign in to comment.