#pragma once #include "led_transport.h" #include #include #include #include #include // WLED-over-USB-CDC serial transport. Implements Adalight binary framing for // per-LED RGB streaming and JSON lines ({"bri":N}, {"on":bool}) for non-streaming // control on the same Win32 COM handle. Pattern 3 from 14-RESEARCH.md: // // CreateFileA("\\\\.\\COMn", GENERIC_READ|GENERIC_WRITE, 0, OPEN_EXISTING, // FILE_FLAG_OVERLAPPED) // DCB: CBR_115200 / 8N1 / fBinary / DTR_CONTROL_DISABLE / RTS_CONTROL_DISABLE // COMMTIMEOUTS: WriteTotalTimeoutConstant=50 / MAXDWORD read-interval-poll // Per-write: WaitForSingleObject(ev, 100 ms) + CancelIoEx on timeout // // The spike (Plan 14-01) validated this pattern end-to-end against MagWLED-1; // this class is production-grade lift from src/spike/backglow_spike/serial_spike.cpp. class WledSerialTransport : public ILedTransport { public: WledSerialTransport(); ~WledSerialTransport() override; WledSerialTransport(const WledSerialTransport&) = delete; WledSerialTransport& operator=(const WledSerialTransport&) = delete; // portName must match ^COM[0-9]{1,3}$ (T-14.2-01). Internally prefixed with // \\.\ for the CreateFileA call (D-10 / Pitfall 3). Returns true on success. bool Open(const std::string& portName) override; void Close() override; bool IsOpen() const override; bool SendRgbFrame(const uint8_t* rgb, int numLeds) override; bool SendBrightness(uint8_t value) override; bool SendPower(bool on) override; unsigned long LastErrorCode() const override; private: // Internal: write `len` bytes with OVERLAPPED + 100 ms bounded wait + // CancelIoEx on timeout. Returns true only if exactly `len` bytes were // reported written. Sets m_lastError and triggers Close() on any failure so // the owning LedController's writer thread will attempt reopen on its next // iteration. bool WriteAllWithTimeout(const uint8_t* data, size_t len); HANDLE m_hPort; std::atomic m_bOpen; std::atomic m_lastError; };