#pragma once #include "led_transport.h" #include // SOCKET, sockaddr_in — MUST precede any windows.h #include // inet_pton, inet_ntop #include #include #include // ---------------------------------------------------------------------------- // WLED DDP-over-UDP transport (TRNS-02). Implements ILedTransport. // // Reference: 15-RESEARCH.md §Pattern 1 (class shape) + §Pattern 2 (DDP encoder) // + §Pattern 3 (one-shot HTTP probe). File layout per D-16. // // Lifecycle: // Open(ipv4String): // 1. Validate ipv4String via inet_pton(AF_INET) — D-05 / T-15-01. // Hostnames, paths, garbage all rejected. // 2. ProbeJsonInfo() — raw-socket HTTP/1.0 GET /json/info to ipv4:80, // parse "count":N — D-07/D-08. Bounded ~500 ms. // 3. PostJsonState() — best-effort POST {"seg":[...col[[0,0,0]]]} to // /json/state — Pitfall 9 (segment-baseline override). // 4. socket(AF_INET, SOCK_DGRAM, 0); cache sockaddr_in for ipv4:4048 // — D-06 (DDP port hard-coded, not configurable). // Close(): // closesocket(); m_bOpen.store(false); idempotent. // SendRgbFrame: // 10-byte DDP header + numLeds*3 RGB bytes; one sendto; double-send // with 20 ms gap (Pitfall 8 — first-frame quirk parity with USB); // on failure -> Close() + return false. // SendBrightness/SendPower: // Raw-socket HTTP POST /json/state with {"bri":N} / {"on":bool}. // // IMPORTANT: WSAStartup / WSACleanup are NOT owned by this class. They are // owned by DeviceProvider::Init / Cleanup as a single driver-level pair // (15-RESEARCH.md §Code Examples §3). This class assumes Winsock is already // initialized when Open() is called. // // Threat-model notes (15-02 PLAN ): // T-15-01: inet_pton strict IPv4 validation. // T-15-03: bounded HTTP request/response buffers (snprintf with sizeof()). // T-15-06: NEVER log the raw /json/info body — only the parsed count. // ---------------------------------------------------------------------------- class WledDdpTransport : public ILedTransport { public: WledDdpTransport(); ~WledDdpTransport() override; WledDdpTransport(const WledDdpTransport&) = delete; WledDdpTransport& operator=(const WledDdpTransport&) = delete; // portName must be a valid IPv4 dotted quad (e.g. "192.168.1.42"). D-05. // Returns true if probe succeeded AND UDP socket was bound for host:4048. 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; // Diagnostic: WLED-reported LED count from /json/info (0 if not yet probed). // Plan 15-03 reads this to log a WARN if mismatch with kBackglowMaxLeds (D-08). int ReportedLedCount() const; private: SOCKET m_sock; // INVALID_SOCKET when closed sockaddr_in m_dest; // host:4048 cached after Open std::atomic m_bOpen; std::atomic m_lastError; // last WSAGetLastError() or 0 int m_reportedLedCount; // from /json/info; 0 until first Open uint8_t m_seq; // DDP sequence 1..15, wraps; reset in Open };