rt82display 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. rt82display-0.1.0/.github/workflows/publish.yml +41 -0
  2. rt82display-0.1.0/.gitignore +51 -0
  3. rt82display-0.1.0/AGENTS.md +183 -0
  4. rt82display-0.1.0/LICENSE +674 -0
  5. rt82display-0.1.0/PKG-INFO +161 -0
  6. rt82display-0.1.0/PROTOCOL.md +237 -0
  7. rt82display-0.1.0/QGIF.md +452 -0
  8. rt82display-0.1.0/README.md +133 -0
  9. rt82display-0.1.0/pyproject.toml +44 -0
  10. rt82display-0.1.0/qgif_native.py +202 -0
  11. rt82display-0.1.0/rt82display/__init__.py +3 -0
  12. rt82display-0.1.0/rt82display/cli.py +734 -0
  13. rt82display-0.1.0/rt82display/hid_device.py +268 -0
  14. rt82display-0.1.0/rt82display/image.py +214 -0
  15. rt82display-0.1.0/rt82display/protocol.py +280 -0
  16. rt82display-0.1.0/rt82display/qgif.py +273 -0
  17. rt82display-0.1.0/rt82display/theme.py +93 -0
  18. rt82display-0.1.0/udev/99-rt82.rules +16 -0
  19. rt82display-0.1.0/wasm2c_runtime/.gitignore +5 -0
  20. rt82display-0.1.0/wasm2c_runtime/build.sh +64 -0
  21. rt82display-0.1.0/wasm2c_runtime/qgif_generated.c +38062 -0
  22. rt82display-0.1.0/wasm2c_runtime/qgif_generated.h +92 -0
  23. rt82display-0.1.0/wasm2c_runtime/test_qgif.c +84 -0
  24. rt82display-0.1.0/wasm2c_runtime/wasm-rt-exceptions-impl.c +73 -0
  25. rt82display-0.1.0/wasm2c_runtime/wasm-rt-impl-tableops.inc +87 -0
  26. rt82display-0.1.0/wasm2c_runtime/wasm-rt-impl.c +401 -0
  27. rt82display-0.1.0/wasm2c_runtime/wasm-rt-impl.h +68 -0
  28. rt82display-0.1.0/wasm2c_runtime/wasm-rt-mem-impl-helper.inc +197 -0
  29. rt82display-0.1.0/wasm2c_runtime/wasm-rt-mem-impl.c +161 -0
  30. rt82display-0.1.0/wasm2c_runtime/wasm-rt.h +710 -0
  31. rt82display-0.1.0/wasm2c_runtime/wasm_syscalls.c +290 -0
@@ -0,0 +1,41 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.12"
16
+
17
+ - name: Install build tools
18
+ run: pip install build
19
+
20
+ - name: Build sdist and wheel
21
+ run: python -m build
22
+
23
+ - uses: actions/upload-artifact@v4
24
+ with:
25
+ name: dist
26
+ path: dist/
27
+
28
+ publish:
29
+ needs: build
30
+ runs-on: ubuntu-latest
31
+ environment: pypi
32
+ permissions:
33
+ id-token: write
34
+ steps:
35
+ - uses: actions/download-artifact@v4
36
+ with:
37
+ name: dist
38
+ path: dist/
39
+
40
+ - name: Publish to PyPI
41
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,51 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.so
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .eggs/
9
+
10
+ # Virtual environments
11
+ .venv/
12
+ venv/
13
+ env/
14
+
15
+ # IDE
16
+ .idea/
17
+ .vscode/
18
+ *.swp
19
+ *.swo
20
+
21
+ # OS
22
+ .DS_Store
23
+ Thumbs.db
24
+
25
+ # Testing
26
+ .pytest_cache/
27
+ .coverage
28
+ htmlcov/
29
+ .tox/
30
+ .mypy_cache/
31
+
32
+ # Environment
33
+ .env
34
+ .envrc
35
+
36
+ # WASM decode (not working yet)
37
+ qgif.js
38
+ qgif.wasm
39
+ qgif_encoder.js
40
+ encode_qgif.mjs
41
+ node_modules/
42
+ package.json
43
+ package-lock.json
44
+
45
+ # Test files and captures
46
+ *.qgif
47
+ *.gif
48
+ *.png
49
+ capture_log.json
50
+ CAPTURE_*.js
51
+ make_test_gif.py
@@ -0,0 +1,183 @@
1
+ # RT82 Display - Agent Instructions
2
+
3
+ Instructions for AI agents working on this project.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ cd rt82display
9
+ source ../.venv/bin/activate # or use uv
10
+ rt82display upload <file.qgif>
11
+ ```
12
+
13
+ ## Troubleshooting
14
+
15
+ ### Device Not Detected
16
+
17
+ **Symptom**: `Could not find keyboard interface (0x36B0)` or `LCD interface (0x1919) did not appear after init`
18
+
19
+ **Cause**: The RT82 has TWO USB devices:
20
+ - `0x36B0:0x30A3` - Always visible (keyboard)
21
+ - `0x1919:0x1919` - Only appears AFTER init commands sent to 0x36B0
22
+
23
+ **Cross-OS note**: On **macOS**, `hid.enumerate()` reports the `usage_page` field correctly (e.g. `0xFF60` for the keyboard interface). On **Linux** with the `libusb` backend (common default), `usage_page` is returned as `0` for all interfaces. The `0x36B0` device exposes three interfaces:
24
+ - IF=0: Keyboard (usage page 0x0001)
25
+ - IF=1: **Raw HID** (usage page 0xFF60) -- **this is the one needed for init**
26
+ - IF=2: Mouse/consumer (usage page 0x0001)
27
+
28
+ The CLI handles this by: (1) preferring the correct `usage_page` when available, (2) falling back to `interface_number == 1` (the standard QMK raw HID interface), (3) falling back to the first match as last resort.
29
+
30
+ Additionally, USB re-enumeration is slower on Linux (~1-2 seconds) than macOS (~300ms). The CLI polls for the `0x1919` device with retries rather than a single fixed sleep.
31
+
32
+ **Solution**: The CLI should automatically handle this, but if it fails:
33
+
34
+ 1. **Check devices are visible**:
35
+ ```bash
36
+ rt82display list
37
+ ```
38
+
39
+ 2. **(Linux only) Install udev rules** so non-root users can access the HID device:
40
+ ```bash
41
+ sudo cp udev/99-rt82.rules /etc/udev/rules.d/
42
+ sudo udevadm control --reload-rules
43
+ sudo udevadm trigger
44
+ ```
45
+ Then unplug and replug the keyboard.
46
+
47
+ 3. **Manually activate LCD interface**:
48
+ ```python
49
+ import hid
50
+ import time
51
+
52
+ # Send init to 0x36B0 — prefer usage_page 0xFF60, then IF=1, then first
53
+ devices = list(hid.enumerate(0x36B0, 0x30A3))
54
+ target = None
55
+ for d in devices:
56
+ if d.get('usage_page') == 0xFF60:
57
+ target = d
58
+ break
59
+ if target is None:
60
+ for d in devices:
61
+ if d.get('interface_number') == 1:
62
+ target = d
63
+ break
64
+ if target is None and devices:
65
+ target = devices[0]
66
+
67
+ if target:
68
+ dev = hid.device()
69
+ dev.open_path(target['path'])
70
+ dev.set_nonblocking(True)
71
+ dev.write(bytes([0xAA, 0xE2] + [0]*62))
72
+ time.sleep(0.05)
73
+ for _ in range(5):
74
+ dev.write(bytes([0xAA, 0xE0] + [0]*62))
75
+ time.sleep(0.05)
76
+ dev.close()
77
+
78
+ # Poll for 0x1919 (may take 1-2s on Linux)
79
+ for _ in range(10):
80
+ time.sleep(0.3)
81
+ found = list(hid.enumerate(0x1919, 0x1919))
82
+ if found:
83
+ for d in found:
84
+ print(f"Found: IF={d.get('interface_number')} UP=0x{d.get('usage_page', 0):04X}")
85
+ break
86
+ else:
87
+ print("0x1919 did not appear")
88
+ ```
89
+
90
+ 4. **If still not working**: Unplug and replug the keyboard
91
+
92
+ ### Screen Stuck in "Downloading"
93
+
94
+ **Cause**: Transfer started but didn't complete properly.
95
+
96
+ **Solution**:
97
+ 1. Unplug keyboard
98
+ 2. Wait 5 seconds
99
+ 3. Replug keyboard
100
+ 4. Try upload again
101
+
102
+ ### Garbled/Wrong Colors
103
+
104
+ **Cause**: Data format issue
105
+
106
+ **Possible fixes**:
107
+ - Ensure using QGIF format (not raw RGB565)
108
+ - Check byte order (should be Little Endian)
109
+ - Verify dimensions are 240×135
110
+
111
+ ### Upload Succeeds But No Image
112
+
113
+ **Cause**: QGIF format might not match what firmware expects
114
+
115
+ **Debug steps**:
116
+ 1. Capture a working QGIF from web tool using browser console
117
+ 2. Compare header bytes with generated QGIF
118
+ 3. Try the captured QGIF to verify protocol works
119
+
120
+ ## Device IDs
121
+
122
+ | Device | VID | PID | Usage Page | Interface | Purpose |
123
+ |--------|-----|-----|------------|-----------|---------|
124
+ | Keyboard HID | 0x36B0 | 0x30A3 | 0x0001 | 0 | Standard keyboard |
125
+ | **Raw HID** | 0x36B0 | 0x30A3 | **0xFF60** | **1** | **Init commands** |
126
+ | Mouse/Consumer | 0x36B0 | 0x30A3 | 0x0001 | 2 | Media keys etc. |
127
+ | LCD | 0x1919 | 0x1919 | 0xFF | 0,1 | Data transfer |
128
+
129
+ **Note**: On Linux (libusb backend), `usage_page` is reported as `0x0000` for all interfaces. Use `interface_number` to distinguish them.
130
+
131
+ ## Protocol Summary
132
+
133
+ 1. **Init** (on 0x36B0:0x30A3, IF=1, UP=0xFF60):
134
+ - Send `AA E2` + `AA E0` ×5
135
+ - Poll for 0x1919 device (appears in ~300ms on macOS, ~1-2s on Linux)
136
+ - On Linux, `usage_page` may be 0; select interface by `interface_number == 1`
137
+
138
+ 2. **Download Mode** (on 0x1919:0x1919, UP=0xFF):
139
+ - Query commands (`AA 10`, `AA 17`, etc.)
140
+ - Trigger download: `AA E3 00 00 00 01 00 00 01`
141
+ - Screen shows "Downloading"
142
+
143
+ 3. **Data Transfer**:
144
+ - Setup packets (`AA 15`, `AA 16`, `AA 18`)
145
+ - Data packets: `AA 19 [offset_L] [offset_H] 00 38 00 00 [56 bytes]`
146
+
147
+ 4. **Finalize**:
148
+ - Status: `AA 1C`
149
+ - End: `AA 1A`
150
+
151
+ ## File Formats
152
+
153
+ ### QGIF (Recommended)
154
+ - Compressed RLE format
155
+ - 32-byte header + frame blocks
156
+ - Generated by `rt82display.qgif.encode_qgif()`
157
+
158
+ ### Raw RGB565 (Experimental)
159
+ - Uncompressed 16-bit pixels
160
+ - May not work on all firmware versions
161
+ - Use `--raw` flag
162
+
163
+ ## Key Files
164
+
165
+ | File | Purpose |
166
+ |------|---------|
167
+ | `cli.py` | Main CLI application |
168
+ | `qgif.py` | QGIF encoder |
169
+ | `hid_device.py` | USB HID communication |
170
+ | `protocol.py` | Packet builders |
171
+ | `udev/99-rt82.rules` | Linux udev rules for non-root HID access |
172
+ | `PROTOCOL.md` | Full protocol documentation |
173
+ | `QGIF.md` | QGIF format specification |
174
+
175
+ ## Common Issues for AI Agents
176
+
177
+ 1. **Don't prepend Report ID** - Just send 64-byte packets directly
178
+ 2. **Two-step connection** - Must init 0x36B0 before 0x1919 appears
179
+ 3. **Correct interface on 0x36B0** - Init must go to IF=1 (raw HID, UP=0xFF60), not IF=0 (keyboard) or IF=2 (mouse). On Linux, `usage_page` is 0 so select by `interface_number == 1`
180
+ 4. **Poll for 0x1919** - USB re-enumeration takes ~1-2s on Linux; use a retry loop, not a fixed sleep
181
+ 5. **Little Endian** - All multi-byte values are LE
182
+ 6. **56-byte chunks** - Data packets have 8-byte header + 56 data
183
+ 7. **QGIF required** - Raw RGB565 usually doesn't display correctly