# 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

```mermaid
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
```

*Two wired ports, one console*

| 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 calls `dock_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 with `ack` records 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`:

```text
CONFIG_KHAL_CONSOLE_DOCK=y
CONFIG_TINYUSB_CDC_ENABLED=y
CONFIG_TINYUSB_CDC_RX_BUFSIZE=4096
CONFIG_TINYUSB_CDC_TX_BUFSIZE=4096
```

The 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:

1. Add `CONFIG_KHAL_CONSOLE_DOCK=y` and `CONFIG_TINYUSB_CDC_ENABLED=y` to the `sdkconfig.defaults` of the app.
2. Delete the generated `sdkconfig`.
3. 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

> **DANGER**
>
> **Never connect the USB-C cable and the base at the same time.** The pads have no reverse blocking. If the cable feeds the board and the base is plugged into a computer, the 5 V of the board reaches that computer through the base. Use one or the other.

> **WARNING**
>
> **The base cannot flash the board.** The pads have no BOOT line and no EN line, so download mode, `idf.py flash` and the factory flash need the USB-C cable. If the kodeOS on a board does not open the dock, give it one that does over the cable, from a medium with `install src="..." dst=os`, or over Bluetooth. After that `kcli.py install --dst os` works through the base.

> **WARNING**
>
> **A base on a charger is not a port.** With no computer behind it there is no USB host. The board never enumerates, `tud_mounted()` stays false and nothing is written to the port.

> **NOTE**
>
> **The option does nothing until the generated `sdkconfig` is deleted.** A stored value beats a new default. Also, `kcli.py` retries by opening the port again, which resets the board on the cable and nothing on the dock. To restart a board on the base, hold the bottom and left buttons together for about 1 s. Two `!W tusb_desc` lines at every boot are normal: TinyUSB says it uses its default device and configuration descriptors, and khal supplies only the strings.

## Example code

Use the console through the base.

1. Unplug the USB-C cable.
2. List the serial ports of the computer.
3. 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.
4. Run `python3 tools/kcli.py -p <dock port> cmd 'ping'`. It prints `pong` and `ok`, and the screen of the Kode Dot does not restart.
5. Run `python3 tools/kcli.py -p <dock port> screenshot -o shot.png`. The payload commands work as they do on the cable, `install dst=c5` included.

## In the SDK

- [`components/khal/console`](https://github.com/kodediy/kode-sdk/tree/main/components/khal/console): `khal_console_esp.c` holds the dock port.
- [`components/khal/Kconfig`](https://github.com/kodediy/kode-sdk/blob/main/components/khal/Kconfig): the two options.
- [`components/khal/idf_component.yml`](https://github.com/kodediy/kode-sdk/blob/main/components/khal/idf_component.yml): the exact `esp_tinyusb` pin, 2.2.0.
