kodeOS internals
Dock console
Use the kodeOS console through the magnetic charging base as a second USB serial port, with no reset on open, and what it costs in RAM and image size.
What it is
The dock console is the console of a Kode Dot reached through the charging base instead of the USB-C cable. It has the same commands, the same grammar, the same answers and the same payloads as the cable. A computer connected to the base sees a USB serial port.
The base is passive. It wires the four lines of its own USB-C socket (VBUS, D+, D-, GND) to four magnetic pads, so it is a cable with magnets. The Kode Dot has a second USB circuit next to the one behind its USB-C socket, and the pads are wired to it. The computer therefore sees a second USB connection that offers a CDC-ACM serial port.
Opening this port does not reset the board. The USB-Serial/JTAG peripheral behind the USB-C socket resets the P4 on every attach. A CDC device that ignores DTR and RTS does not. A session over the base starts with the board as it is: its uptime, its radio link and its screen do not change.
How it is connected
flowchart LR subgraph board["On the Kode Dot"] usbc["USB-C socket<br/>GPIO24, GPIO25"] --> sj["USB-Serial/JTAG"] pads["magnetic pads<br/>GPIO26, GPIO27"] --> otg["OTG full-speed PHY<br/>TinyUSB CDC"] sj --> rd["khal reader task"] otg --> rd rd --> log["AO_Log"] log --> con["AO_Console"] end class usbc,pads,sj,otg chip class log,con ao
| Pad on the Kode Dot | What it carries |
|---|---|
| +5 V | VBUS of the base |
| GND | GND of the base |
| GPIO26 and GPIO27 | D+ and D- of the base, the data pair of the second full-speed USB PHY of the P4 |
The USB-C socket uses another pair, GPIO24 and GPIO25 (KBSP_PIN_USB_USJ_DM and _DP), which belongs to the USB-Serial/JTAG.
Above khal the two wired ports are one port. Both feed one reader task and one line editor, and lines from both carry the source KODE_CONSOLE_SRC_SERIAL. Every console write goes to both ports. The rules that make two ports into one console are short functions in khal_console_esp.c:
- Input.
console_read()checks the dock without waiting, then waits up to 5 ms on the cable (DOCK_POLL_MS), and repeats until a byte arrives or the 50 ms timeout of the reader task ends. The port that spoke is remembered. - Output.
khal_console_write()writes to stdout, which is the cable, and then callsdock_write(). That function sends only while the host has mounted the USB connection (tud_mounted()). - A full queue never stops the console. A write that finds the TX FIFO of the dock full waits at most
CONFIG_KHAL_CONSOLE_DOCK_TX_WAIT_MS(20 ms by default) and then drops the rest, and drops without waiting until a later write fits. A base plugged into a computer with no program reading the port does not slow the console. - Raw mode belongs to one port. While a payload arrives, the reader takes bytes only from the port its command arrived on. It captures that port when it notices the raw-mode request, before it drains pending input, and keeps it until
khal_console_raw_end(). - Flow control. TinyUSB sends NAK to the host while its receive FIFO is full, and
dock_read()takes bytes straight out of it, so a payload cannot overrun this side. The cable drops what its ring cannot hold. The stop-and-wait withackrecords works the same on both.
| File | What it holds |
|---|---|
khal/console/src/khal_console_esp.c | The dock port, the read from two ports, the raw-mode latch and the bounded write |
khal/Kconfig | KHAL_CONSOLE_DOCK and KHAL_CONSOLE_DOCK_TX_WAIT_MS |
khal/idf_component.yml | The esp_tinyusb dependency, fetched only when the option is on |
sdkconfig.defaults of kodeOS | Turns the option on and sets the size of the CDC FIFOs |
AO_Log owns the console device, and this port is part of it. The option exists on the ESP32-P4 only. Its default is off in the SDK and on in kodeOS, because the port takes the OTG controller, which an app might want for another job such as a USB drive.
Specifications
What the computer sees
| Field | Value |
|---|---|
| VID:PID | 303a:4001. The vendor id is Espressif's. The product id is the esp_tinyusb default for a device with only a CDC class |
| Manufacturer | Kode |
| Product | Dot. macOS shows "Kode Dot" |
| Serial | The printed serial of the board (khal_identity_pretty()), or empty when the board does not know it. The board does not invent one |
| Interface | Kode Dot console |
| Cable port, for comparison | 303a:1001, "USB JTAG/serial debug unit" |
Two boards on one computer are two ports, and each port names its board. Any reset of the P4 closes the port, since the controller stops. When kodeOS has started, the board enumerates again.
What it costs
Measured on a Kode Dot with the face on screen, Bluetooth advertising and the card mounted.
| What | With the option |
|---|---|
| Image | 33,696 bytes more |
| Static internal RAM | 10,862 bytes in the two TinyUSB libraries: 9,559 in .bss and 1,303 in .data. The two CDC FIFOs of 4 KB are part of it |
| Tasks | One more: the TinyUSB task, stack 4,096 bytes, priority 5, core 1 |
| Internal pool | 15,524 bytes less, at rest and under load. ps reads int_free=134651 int_max=131072 on the face |
| Reader wake-up | Every 5 ms while the console is idle, instead of every 50 ms |
A firmware with the option off neither downloads nor compiles the stack: idf_component.yml gives esp_tinyusb the rule $CONFIG{KHAL_CONSOLE_DOCK} == True.
Building it into a firmware
kodeOS sets four lines in sdkconfig.defaults:
CONFIG_KHAL_CONSOLE_DOCK=y
CONFIG_TINYUSB_CDC_ENABLED=y
CONFIG_TINYUSB_CDC_RX_BUFSIZE=4096
CONFIG_TINYUSB_CDC_TX_BUFSIZE=4096The FIFOs default to 512 bytes. 4 KB each holds a whole 4 KB ack window in each direction, so a chunk never waits for the FIFO.
An app built from the SDK has the option off and shows no port on the base unless it turns it on. An app needs only the first two lines: the 512-byte FIFOs carry its lines and logs, and the two 4 KB ones would cost it about 7 KB of internal RAM for installs and file transfers it does not take. To turn it on:
- Add
CONFIG_KHAL_CONSOLE_DOCK=yandCONFIG_TINYUSB_CDC_ENABLED=yto thesdkconfig.defaultsof the app. - Delete the generated
sdkconfig. - Build.
The first pass reports the TinyUSB options as unknown symbols, which is normal: they exist only after khal has made the component manager fetch esp_tinyusb, and the next pass applies them.
Gotchas
Example code
Use the console through the base.
- Unplug the USB-C cable.
- List the serial ports of the computer.
- Put the Kode Dot on the base and plug the base into the computer. List the ports again. The new one is the dock port.
- Run
python3 tools/kcli.py -p <dock port> cmd 'ping'. It printspongandok, and the screen of the Kode Dot does not restart. - Run
python3 tools/kcli.py -p <dock port> screenshot -o shot.png. The payload commands work as they do on the cable,install dst=c5included.
In the SDK
components/khal/console:khal_console_esp.cholds the dock port.components/khal/Kconfig: the two options.components/khal/idf_component.yml: the exactesp_tinyusbpin, 2.2.0.