# Desktop over USB Example A browser-rendered windowed desktop served from an ESP32-S3: the firmware registers apps and describes their windows / widgets with `espp::Desktop`; the hosted [desktop web app](https://esp-cpp.github.io/espp/apps/desktop.html) (`components/desktop/web/desktop.html`) draws and operates them over the native USB port, on both the **vendor (WebUSB)** and **CDC (Web Serial)** interfaces (`espp.desktop` v2 on module 9 by default). The [Device Hub](https://esp-cpp.github.io/espp/apps/dispatcher_hub.html) lists it through discovery next to the standard services. Apps (`main/apps/*.hpp`, one `register_<name>_app()` each): - **Counter** — the API reference (~40 lines): a label, three buttons, the count kept in NVS, a confirmation message box. - **About** — chip / firmware / partition / hardware labels (`SystemInfo`). - **System Monitor** — uptime and per-region heap gauges (`HeapMonitor`), refreshed by a 1 s window timer. - **Task Manager** — the FreeRTOS task table (`TaskMonitor`: CPU %, stack high-water mark, priority, core) with a refresh-period selector. Filter by task name (substring, case-insensitive) and core; click a column header to sort (again for descending, again to clear). - **Log Viewer** — the captured console (`ConsoleCapture`) streamed live into a read-only, ANSI-aware console text area; pause and clear. - **Files** — browse the LittleFS partition (`FileSystem`), create / rename / delete through dialogs; open a file in the **Editor** (a text area saved with `std::ofstream`). - **Settings** — nickname, theme and accent (applied to the browser at once) and the log-capture tee (whether captured logs still go to the UART console; the capture itself is the compile-time `CONFIG_DESKTOP_EXAMPLE_LOG_CAPTURE`), all kept in NVS and restored at boot. Hardware apps, each behind a Kconfig option (see [Configuration](#configuration)). CANopen (on the simulated node), the I2C scanner and the Wi-Fi group are on by default, so the CI build compiles them; the Ethernet group defaults off and is only selectable on SoCs with an EMAC (ESP32 / ESP32-P4, not the S3): - **CANopen / DS402** — a CiA 301 NMT master + SDO client (`espp::CanopenClient`) and a CiA 402 drive panel (`espp::Ds402Drive`): node id, NMT Start / Stop / Pre-operational / Reset, NMT + drive state and statusword, mode of operation, Enable / Disable / Quick stop / Fault reset, a target-velocity slider, position / velocity, and a raw SDO read / write row. The bus is either the in-firmware **simulated DS402 node** (the CAN bridge example's `SimulatedCanBus`, no hardware needed, with an "Inject fault" button) or the **TWAI peripheral** wired to a CAN transceiver. Every bus transaction runs on the app's own task (SDO calls block); the window only queues commands and the task updates the widgets. - **I2C scanner** — probe every 7-bit address on the configured bus (`espp::I2c`) from a short task and list what answers; read / write a device register from the window. A bus that fails to initialize shows a hint instead. - **Network** — the Wi-Fi station (`espp::WifiSta`): status / SSID / IP / RSSI / MAC, Scan (on its own task; a scan disconnects first), the AP list, password field and Connect / Disconnect / Forget with the credentials kept in NVS (`desktop` namespace, `wifi_ssid` / `wifi_pass`); and, on SoCs with an EMAC, the RMII Ethernet link (`espp::Ethernet`): link / IP / MAC / speed. The interfaces come up on the first launch and stay up when the window is closed. ## How to use example ### Hardware Required An ESP32-S3 (or -S2 / -P4) board with the native USB port wired to a host. The console / logs go to UART0 (see `sdkconfig.defaults`); with `CONFIG_DESKTOP_EXAMPLE_LOG_CAPTURE` (default on) they are also captured for the Log Viewer. The hardware apps need nothing extra by default: the CANopen app talks to a simulated node, the I2C scanner just reports an empty bus and the Network app scans for Wi-Fi. For a real CAN bus select the TWAI peripheral and wire a transceiver (SN65HVD230 or similar) to the configured TX / RX GPIOs; for the Ethernet group an ESP32-Ethernet-Kit or an ESP32-P4-Function-EV-Board (the RMII wiring is selected by `DESKTOP_EXAMPLE_ETHERNET_BOARD`; other boards: edit `rmii_config()` in `main/apps/network_app.hpp`). ### Configuration `idf.py menuconfig` -> *Desktop Example Configuration*: | Option | Default | Meaning | |---|---|---| | `DESKTOP_EXAMPLE_LOG_CAPTURE` (+ `_BYTES`) | y (16384) | Tee the console into a ring for the Log Viewer | | `DESKTOP_EXAMPLE_ENABLE_CANOPEN` | y | Register the CANopen / DS402 app | | `DESKTOP_EXAMPLE_CANOPEN_BUS` | `SIMULATED` | `SIMULATED` (in-firmware DS402 node) or `TWAI` (the peripheral) | | `DESKTOP_EXAMPLE_CANOPEN_NODE_ID` | 1 | Server node id (1..127; also the simulated node's id) | | `DESKTOP_EXAMPLE_CAN_TX_GPIO` / `_RX_GPIO` / `_BAUDRATE` | 17 / 16 / 500000 | TWAI wiring and bit rate (TWAI bus only) | | `DESKTOP_EXAMPLE_ENABLE_I2C` | y | Register the I2C scanner app | | `DESKTOP_EXAMPLE_I2C_PORT` / `_SDA_GPIO` / `_SCL_GPIO` / `_FREQ_HZ` | 0 / 8 / 9 / 400000 | The I2C bus it scans | | `DESKTOP_EXAMPLE_ENABLE_WIFI` | y | The Network app's Wi-Fi station group (`SOC_WIFI_SUPPORTED`) | | `DESKTOP_EXAMPLE_ENABLE_ETHERNET` | n | The Network app's RMII Ethernet group (`SOC_EMAC_SUPPORTED`: ESP32 / -P4) | | `DESKTOP_EXAMPLE_ETHERNET_BOARD` | per target | RMII wiring: `ETHERNET_KIT` (ESP32-Ethernet-Kit) or `P4_FUNCTION_EV` (ESP32-P4-Function-EV-Board, with its routable data pins) | The hardware components (`i2c`, `wifi`, `ethernet`, `cli`) are always part of the build (`REQUIRES` cannot depend on Kconfig); the options only decide which apps are registered. **Minimum IDF.** The example itself builds on IDF 5.5 (the `desktop`, `usb_device`, `ethernet` (>= 5.4), `wifi`, `i2c` and `canopen` components all support it). The CANopen app is the exception: it is built on the `twai` component (the simulated bus is a `Twai` drop-in), which needs the IDF >= 6.0 `esp_driver_twai` node API. The CMakeLists therefore adds `canopen` + `twai` and compiles the app only on IDF >= 6.0, controlled by the CMake option `DESKTOP_EXAMPLE_CANOPEN` (default ON on IDF >= 6, OFF below; override with `idf.py -DDESKTOP_EXAMPLE_CANOPEN=OFF build`). On an older IDF `CONFIG_DESKTOP_EXAMPLE_ENABLE_CANOPEN` has no effect and `app_main` logs a warning. The simulated CAN bus / DS402 node headers are included from the CAN bridge example (`components/canopen/can_bridge_example/main`); promoting them into the `canopen` component is a follow-up. ### Build and Flash ``` idf.py set-target esp32s3 idf.py build flash monitor ``` CI builds it with the component manager off (`IDF_COMPONENT_MANAGER=0 idf.py build`), resolving every dependency from the repository (including the vendored `esp_tinyusb` / `tinyusb` submodules under `external/` and the `littlefs` submodule under `components/`). Then open the desktop web app and Connect (WebUSB or Web Serial): the app icons appear; double-click one (or use the start menu) to launch it. Windows can be dragged, resized, minimised, maximised and closed; the browser remembers where you put them. Reconnecting (or reloading the page) resyncs the whole desktop with one `GET_DESKTOP`. ## Standard USB services Like every espp USB example, this one serves the standard service set on its framed USB link(s) next to its own protocol, so the hosted consoles and the [Device Hub](https://esp-cpp.github.io/espp/apps/dispatcher_hub.html) (which finds each service through discovery, by protocol id) work against it: | Service | Module (default) | Protocol id | Console | |---|---|---|---| | `espp::DesktopService` -- this desktop | 9 | `espp.desktop` | [desktop](https://esp-cpp.github.io/espp/apps/desktop.html) | | `espp::SystemService` -- device info, reboot, reboot into the bootloader | 7 | `espp.system` | [system console](https://esp-cpp.github.io/espp/apps/system_console.html) | | `espp::MonitorService` -- heap regions + task table, on request or streamed | 8 | `espp.monitor` | system console | | `espp::OtaService` -- firmware update (host-driven rollback confirmation) | 0 | `espp.ota` | [OTA console](https://esp-cpp.github.io/espp/apps/ota_console.html) | | `espp::CoreDumpService` -- last-crash report, core dump download / erase | 4 | `espp.coredump` | [coredump console](https://esp-cpp.github.io/espp/apps/coredump_console.html) | `partitions.csv` therefore carries the OTA layout (`otadata`, `ota_0`, `ota_1`), a `coredump` partition and a `littlefs` partition for the Files app, and `sdkconfig.defaults` enables core dumps to flash, OTA rollback and the FreeRTOS run-time statistics the task monitor reads. Every device->host write on a transport goes through one mutex, so the services never interleave frames; `write_vendor` / `write_cdc` wait (bounded, 250 ms) for FIFO room for a whole frame and never queue a partial one, and when the host is not draining the FIFO the frame is dropped and the desktop flags that transport as needing a resync (the browser resyncs with GET_DESKTOP on its next connect). ## Example Output ``` I (327) Desktop Example: Starting desktop example I (337) Desktop Example: LittleFS at /littlefs: 8 / 256 KiB used I (347) Desktop Example: Clean boot history (reset reason: power-on) I (1077) Desktop Example: Ready. Connect the native USB port and open the desktop ... ```
To create a project from this example, run:
idf.py create-project-from-example "espp/desktop=1.3.7:example"