; 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 ; 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