; keyrelay -- WiFi-controlled USB HID keyboard bridge for the M5Stack AtomS3 Lite. ; ; The board is an ESP32-S3FN8 (8 MB flash, no PSRAM) with NO USB-to-UART bridge: ; the USB-C connector is wired straight to the ESP32-S3 native USB (GPIO19/20), and ; those pads belong to ONE peripheral at a time: ; ; ARDUINO_USB_MODE=1 -> built-in USB-Serial/JTAG (console only, cannot type) ; ARDUINO_USB_MODE=0 -> USB-OTG + TinyUSB (HID keyboard + CDC console) ; ; That single-port fact is the whole reason the control path is WiFi: the board can ; be attached to exactly one machine over USB, so the controller on the laptop has to ; reach it over the network instead. See README "Why the control path is WiFi". ; ; Datasheet: https://docs.m5stack.com/en/core/AtomS3-Lite [platformio] default_envs = bridge [usb] ; The core USB library compiles its MSC/FS sources together with the HID ones, and ; those include FS.h / vfs_api.h, which LDF will not resolve by itself (it tries the ; registry and fails). Only the include path is needed: nothing here references the ; MSC/FS symbols, so the linker drops those objects again. Carried over from the ; jiggle firmware on the same board. fs_include = -I${platformio.packages_dir}/framework-arduinoespressif32/libraries/FS/src [env] platform = espressif32@8.0.0 board = esp32-s3-devkitc-1 framework = arduino ; Nominal for USB CDC (the USB link ignores baud); 921600 is what this board takes. upload_speed = 921600 monitor_speed = 115200 monitor_filters = esp32_exception_decoder, time ; WiFi + TinyUSB + the embedded controller page do not fit the default 1.3 MB app ; slot of the standard two-OTA layout. This board has no OTA path in this project -- ; it is reflashed over USB, always -- so one 3 MB app partition is the right trade. board_build.partitions = huge_app.csv lib_deps = adafruit/Adafruit NeoPixel@^1.15.0 ; --------------------------------------------------------------------------- ; Default: the full bridge. The board is a composite USB HID keyboard + USB serial ; on the target, a web server and WebSocket endpoint on the network. ; --------------------------------------------------------------------------- [env:bridge] build_flags = ${usb.fs_include} -DARDUINO_USB_MODE=0 ; TLS on: the page and the WebSocket are served over HTTPS/wss with an identity the ; board generated itself (src/tls.h). The rollback value is KR_TLS=0 -- plain http:// ; and ws:// only, exactly the pre-phase-02 behaviour, same standing as `mouse off`. ; It is a compile-time switch because a device that cannot be reached over TLS after a ; bad provisioning change needs a way back that is a reflash, not a repair. -DKR_TLS=1 ; CDC_ON_BOOT=0 is what makes the Logitech identity possible: with 1, ; cores/esp32/main.cpp calls USB.begin() from app_main() before setup() runs, the ; first tinyusb_init() fixes the VID/PID/strings as Espressif's, and every ; USB.VID()/productName() setter afterwards is a no-op. With 0 this environment ; starts the stack itself, after setting the identity (src/main.cpp: kUsb*). The ; console is unaffected: main.cpp owns a USBCDC instance, so /dev/ttyACM0 works. -DARDUINO_USB_CDC_ON_BOOT=0 ; Optional build-time station credentials, so a first bring-up can skip the serial ; console. Quoting is part of the macro: -D KR_STA_SSID='"MyNet"'. ; Prefer the `wifi ` console command: it stores into NVS and keeps ; the passphrase out of the image and out of shell history. ; -D KR_STA_SSID='" ssid "' ; -D KR_STA_PASS='" passphrase "' build_unflags = ; esp32-s3-devkitc-1 pins ARDUINO_USB_MODE=1 in its board JSON; drop it so the ; value above is the only one on the command line. -DARDUINO_USB_MODE=1 ; --------------------------------------------------------------------------- ; Diagnostic: no USB-OTG, so no HID -- the board cannot type, but the whole control ; plane (WiFi, page, passphrase, WebSocket, LED, button) is live. Use it to separate ; "the network side is broken" from "the HID side is broken" without rebuilding. ; Arming refuses with E5 here, on purpose. ; --------------------------------------------------------------------------- [env:console] build_flags = ${usb.fs_include} -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 ; Same TLS default as bridge: the control plane is what this environment exists to ; diagnose, so it must behave identically. Roll back with -DKR_TLS=0 (see env:rollback). -DKR_TLS=1 ; --------------------------------------------------------------------------- ; Rollback: the bridge with TLS compiled out, i.e. the pre-phase-02 build -- plain ; http:// page and ws:// socket on :80, no :443, no identity in NVS. PLAN.md locks this ; in as the way back from a provisioning change that left a board unreachable over TLS: ; the fix is a reflash, not a repair, and a way back only counts if it still builds. ; That is why it is an environment and not a remembered command line: `pio run` has no ; --build-flags option in this PlatformIO, and a second -DKR_TLS on the command line ; beside the -DKR_TLS=1 above is a macro redefinition whose winner depends on flag order. ; Unflagging the default and setting 0 is the only spelling with no ambiguity -- and ; .agents/validate.sh builds this env on every run, so the rollback cannot quietly rot. ; ; Nothing about arming moves here: KR_TLS changes which port carries bytes, never who may ; type (src/config.h, README "The rollback"). The page this image serves is the KR_TLS=0 ; branch of the marker in src/web_page.h; it is the only build where a plaintext ws:// ; session is the normal case, and the only one the README permits it in. ; --------------------------------------------------------------------------- [env:rollback] extends = env:bridge build_flags = ${env:bridge.build_flags} -DKR_TLS=0 build_unflags = ${env:bridge.build_unflags} -DKR_TLS=1