mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 23:27:49 +01:00
90 lines
5.5 KiB
Markdown
90 lines
5.5 KiB
Markdown
# Cardboy Time Sync Companion
|
||
|
||
This SwiftUI app connects to the Cardboy device over Bluetooth Low Energy and updates its wall clock using the custom time sync service exposed by the firmware. The sources live inside the existing `cardboy-companion/cardboy-companion.xcodeproj` so you can open and run them directly in Xcode.
|
||
|
||
## Requirements
|
||
|
||
- Xcode 15 or newer
|
||
- iOS 16 or newer deployment target (can be lowered to 15 with minor API tweaks)
|
||
- A Cardboy running firmware that includes the BLE time sync service
|
||
|
||
## How it works
|
||
|
||
1. The app scans for peripherals exposing service UUID `00000001-CA7B-4EFD-B5A6-10C3F4D3F230`.
|
||
2. Once connected it discovers characteristic `00000002-CA7B-4EFD-B5A6-10C3F4D3F231`.
|
||
3. Tapping **Sync Now** writes a 12‑byte payload containing:
|
||
- 8 bytes Unix epoch seconds (little endian)
|
||
- 2 bytes time zone offset in minutes from UTC (little endian)
|
||
- 1 byte DST flag (`1` if daylight saving is active)
|
||
- 1 reserved byte (`0`)
|
||
4. The firmware applies the timestamp with `settimeofday()` and updates the TZ environment variable so the clock app renders local time.
|
||
|
||
## Usage
|
||
|
||
1. Open `cardboy-companion/cardboy-companion.xcodeproj` in Xcode.
|
||
2. Ensure the `CoreBluetooth` capability is enabled for the `cardboy-companion` target and keep the *Uses Bluetooth LE accessories* background mode on (preconfigured in this project).
|
||
3. Build & run on a real device (BLE is not available in the simulator).
|
||
4. Allow Bluetooth permissions when prompted. The app keeps scanning in the background, so the Cardboy can request a sync even while the companion is not foregrounded. Tap **Sync Now** any time you want to trigger a manual refresh.
|
||
5. Switch to the **Files** tab to browse the LittleFS volume on the Cardboy: you can upload ROMs from the Files picker, create/remove folders, rename entries, delete files, and download items back to the phone for sharing.
|
||
|
||
## BLE File Service Protocol
|
||
|
||
The ESP firmware exposes a custom GATT service (UUID `00000010-CA7B-4EFD-B5A6-10C3F4D3F230`) with two characteristics:
|
||
|
||
| Characteristic | UUID | Properties | Direction | Description |
|
||
| --- | --- | --- | --- | --- |
|
||
| File Command | `00000011-CA7B-4EFD-B5A6-10C3F4D3F231` | Write / Write Without Response | iOS → ESP | Sends file management requests |
|
||
| File Response | `00000012-CA7B-4EFD-B5A6-10C3F4D3F232` | Notify | ESP → iOS | Streams command results (responses or data) |
|
||
|
||
All payloads share the same framing. Commands written to the File Command characteristic use:
|
||
|
||
```
|
||
Offset | Size | Description
|
||
-------+------+------------
|
||
0 | 1 | Opcode (see table below)
|
||
1 | 1 | Reserved (set to 0)
|
||
2 | 2 | Little-endian payload length in bytes (N)
|
||
4 | N | Command payload
|
||
```
|
||
|
||
Notifications from the File Response characteristic use:
|
||
|
||
```
|
||
Offset | Size | Description
|
||
-------+------+------------
|
||
0 | 1 | Opcode (echoed from command)
|
||
1 | 1 | Status byte (bit 7 = completion flag; lower 7 bits = error code)
|
||
2 | 2 | Little-endian payload length (N)
|
||
4 | N | Response payload (command-specific)
|
||
```
|
||
|
||
Status byte semantics:
|
||
- Bit 7 (0x80) set → final packet for the current command (no further fragments).
|
||
- Lower 7 bits = error code (`0` = success, otherwise `errno`-style code echoed back).
|
||
- On error the response payload may contain a UTF-8 message.
|
||
|
||
### Opcodes and Payloads
|
||
|
||
| Opcode | Name | Command Payload | Response Payload |
|
||
| --- | --- | --- | --- |
|
||
| `0x01` | List Directory | `uint16 path_len` + UTF-8 path | One or more fragments, each entry encoded as:<br> - `uint8 type` (0=file, 1=dir)<br> - `uint8 reserved`<br> - `uint16 name_len`<br> - `uint32 size` (0 for dirs)<br> - `name_len` bytes UTF-8 name<br> Final notification has completion bit set. |
|
||
| `0x02` | Upload Begin | `uint16 path_len` + UTF-8 path + `uint32 file_size` | Empty payload on success. Starts upload session (expects `UploadChunk` packets). |
|
||
| `0x03` | Upload Chunk | Raw file bytes | Empty payload ack for each chunk. |
|
||
| `0x04` | Upload End | No payload | Empty payload confirming completion. |
|
||
| `0x05` | Download Request | `uint16 path_len` + UTF-8 path | First notification: 4-byte little-endian total file size; subsequent notifications stream raw file data fragments. Completion bit marks the final chunk. |
|
||
| `0x06` | Delete File | `uint16 path_len` + UTF-8 path | Empty payload on success. |
|
||
| `0x07` | Create Directory | `uint16 path_len` + UTF-8 path | Empty payload on success. |
|
||
| `0x08` | Delete Directory | `uint16 path_len` + UTF-8 path | Empty payload on success. |
|
||
| `0x09` | Rename Path | `uint16 src_len` + UTF-8 source path + `uint16 dst_len` + UTF-8 destination path | Empty payload on success. |
|
||
|
||
### Notes
|
||
|
||
- Paths are absolute within the LittleFS volume; the firmware normalizes them and rejects entries containing `..`.
|
||
- Large responses (directory lists, downloads) may arrive in multiple notifications; the iOS client aggregates fragments until it sees the completion flag.
|
||
- Uploads are initiated with `Upload Begin` (including total size), followed by one or more `Upload Chunk` writes, and `Upload End` when done.
|
||
- Errors from the firmware propagate via the status byte; when `status & 0x7F != 0`, the notification payload typically includes a UTF-8 error message (e.g., `"stat failed"`).
|
||
|
||
This protocol mirrors the implementation in `components/backend-esp/src/time_sync_service.cpp` and the Swift client in `TimeSyncManager.swift`. Update both sides if new commands are added.
|
||
|
||
Optionally bundle this code into your existing app—`TimeSyncManager` is self‑contained and can be reused.
|