FastLED 3.10.6
Loading...
Searching...
No Matches
AutoResearchRemoteRpSpiMethods.cpp
Go to the documentation of this file.
1// RP2040/RP2350 fixed-SPI byte-loopback AutoResearch RPC.
2
4#if FL_PLATFORM_HAS_LARGE_MEMORY
5
7
8#include <Arduino.h>
9
10#include "FastLED.h"
11#include "fl/channels/data.h"
12#include "fl/chipsets/spi.h"
13#include "fl/remote/remote.h"
14#include "fl/stl/json.h"
15#include "fl/stl/limits.h"
16#include "fl/stl/move.h"
17#include "fl/stl/singleton.h"
18#include "fl/stl/vector.h"
19#include "platforms/arm/rp/is_rp.h"
20
21#if defined(FL_IS_RP2040) || defined(FL_IS_RP2350)
22#include "platforms/arm/rp/rpcommon/rp_spi_bus_traits.h"
23#endif
24
25namespace {
26
27#if defined(FL_IS_RP2040) || defined(FL_IS_RP2350)
28
29bool parseInt(const fl::json& config, const char* key, int* value) {
30 if (!config.contains(key) || !config[key].is_int()) return false;
31 const fl::i64 parsed = config[key].as_int().value();
32 if (parsed < static_cast<fl::i64>((fl::numeric_limits<int>::min)()) ||
33 parsed > static_cast<fl::i64>((fl::numeric_limits<int>::max)())) {
34 return false;
35 }
36 *value = static_cast<int>(parsed);
37 return true;
38}
39
40template <fl::u8 Which>
41fl::json runLoopback(const fl::shared_ptr<AutoResearchState>& runtime_state,
42 int mosi_pin, int miso_pin, int sck_pin, int clock_hz) {
43 const fl::u8 loopback_pattern[] = {
44 0x00, 0xFF, 0x55, 0xAA, 0x12, 0x34, 0x56, 0x78,
45 0x87, 0x65, 0x43, 0x21, 0x0F, 0xF0, 0xCC, 0x33,
46 0x11, 0xEE, 0x22, 0xDD, 0x44, 0xBB, 0x88, 0x77,
47 0x66, 0x99, 0x3C, 0xC3, 0x5A, 0xA5, 0x7E, 0x81,
48 };
50 const int expected_mosi = Which == 0 ? 3 : 11;
51 const int expected_miso = Which == 0 ? 0 : 8;
52 const int expected_sck = Which == 0 ? 2 : 10;
53 if (mosi_pin != expected_mosi || miso_pin != expected_miso ||
54 sck_pin != expected_sck) {
55 response.set("success", false);
56 response.set("error", "UnsupportedPins");
57 response.set("message", "Pins do not select the requested RP fixed SPI instance.");
58 return response;
59 }
60 if (clock_hz < 100000 || clock_hz > 62500000) {
61 response.set("success", false);
62 response.set("error", "InvalidClock");
63 response.set("message", "clock_hz must be in [100000, 62500000].");
64 return response;
65 }
66
67 // The request is now fully validated. Release AutoResearch's diagnostic
68 // PIO reservation only when the fixed-SPI operation will actually run.
69 if (runtime_state) {
70 runtime_state->rx_channel.reset();
71 runtime_state->invalidateLedRxValidation();
72 }
73
75 tx.assign(loopback_pattern, loopback_pattern + sizeof(loopback_pattern));
76 fl::u8 captured[sizeof(loopback_pattern)] = {};
77 auto channel = fl::ChannelData::create(
78 fl::SpiChipsetConfig(mosi_pin, sck_pin,
79 fl::SpiEncoder::apa102(static_cast<fl::u32>(clock_hz))),
80 fl::move(tx));
82 if (!driver.captureNextRxBytes(captured, sizeof(captured))) {
83 response.set("success", false);
84 response.set("error", "DriverBusy");
85 response.set("message", "RP SPI driver was busy before the loopback transfer.");
86 return response;
87 }
88 driver.enqueue(channel);
89 driver.show();
90
92 const unsigned long start_ms = millis();
93 while (millis() - start_ms < 2000) {
94 state = driver.poll();
97 break;
98 }
99 delay(1);
100 }
101
102 bool exact_match = state == fl::IChannelDriver::DriverState::READY;
103 fl::json expected = fl::json::array();
104 fl::json received = fl::json::array();
105 for (size_t index = 0; index < sizeof(loopback_pattern); ++index) {
106 expected.push_back(static_cast<int64_t>(loopback_pattern[index]));
107 received.push_back(static_cast<int64_t>(captured[index]));
108 exact_match = exact_match && captured[index] == loopback_pattern[index];
109 }
110
111 fl::json data = fl::json::object();
112 data.set("driver", Which == 0 ? "SPI0" : "SPI1");
113 data.set("mosiPin", static_cast<int64_t>(mosi_pin));
114 data.set("misoPin", static_cast<int64_t>(miso_pin));
115 data.set("sckPin", static_cast<int64_t>(sck_pin));
116 data.set("requestedClockHz", static_cast<int64_t>(clock_hz));
117 data.set("actualClockHz", static_cast<int64_t>(driver.lastActualClockHz()));
119 data.set("expected", expected);
120 data.set("received", received);
121 response.set("success", exact_match);
122 response.set("message", exact_match ? "RP SPI loopback passed." : "RP SPI loopback failed.");
123 response.set("data", data);
124 return response;
125}
126
127template <bool Sk9822>
128struct PublicApiLoopbackState {
129 CRGB leds[257];
130 bool initialized;
131
132 PublicApiLoopbackState() : initialized(false) {}
133};
134
135template <bool Sk9822>
136PublicApiLoopbackState<Sk9822>& publicApiLoopbackState() {
138}
139
140template <bool Sk9822>
141fl::json runPublicApiLoopback(
142 const fl::shared_ptr<AutoResearchState>& runtime_state,
143 int pattern) {
144 constexpr fl::u8 kMosiPin = 11;
145 constexpr fl::u8 kSckPin = 10;
146 constexpr size_t kLedCount = 257;
147 constexpr size_t kFrameBytes = 4 + (kLedCount * 4) + 36;
148 PublicApiLoopbackState<Sk9822>& state = publicApiLoopbackState<Sk9822>();
149 CRGB* leds = state.leds;
151
152 if (pattern < 0 || pattern > 5) {
153 response.set("success", false);
154 response.set("error", "InvalidPattern");
155 return response;
156 }
157
158 if (runtime_state) {
159 runtime_state->rx_channel.reset();
160 runtime_state->invalidateLedRxValidation();
161 }
162
163 if (!state.initialized) {
164 if constexpr (Sk9822) {
165 FastLED.addLeds<SK9822, kMosiPin, kSckPin, RGB, 8000000,
166 fl::Bus::SPI, 1>(leds, kLedCount);
167 } else {
168 FastLED.addLeds<APA102, kMosiPin, kSckPin, RGB, 8000000,
169 fl::Bus::SPI, 1>(leds, kLedCount);
170 }
171 state.initialized = true;
172 }
173
174 for (size_t index = 0; index < kLedCount; ++index) leds[index] = CRGB::Black;
175 switch (pattern) {
176 case 1: for (size_t index = 0; index < kLedCount; ++index) leds[index] = CRGB::Red; break;
177 case 2: for (size_t index = 0; index < kLedCount; ++index) leds[index] = CRGB::Green; break;
178 case 3: for (size_t index = 0; index < kLedCount; ++index) leds[index] = CRGB::Blue; break;
179 case 4: for (size_t index = 0; index < kLedCount; ++index) leds[index] = CRGB::White; break;
180 case 5: leds[16] = CRGB::Red; break;
181 default: break;
182 }
183 fl::u8 captured[kFrameBytes] = {};
185 if (!driver.captureNextRxBytes(captured, sizeof(captured))) {
186 response.set("success", false);
187 response.set("error", "DriverBusy");
188 return response;
189 }
190 FastLED.show();
191 FastLED.wait(2000);
192
193 bool exact = driver.lastActualClockHz() > 0;
194 for (size_t index = 0; index < 4; ++index) exact = exact && captured[index] == 0x00;
195 for (size_t led = 0; led < kLedCount; ++led) {
196 const size_t offset = 4 + (led * 4);
197 exact = exact && captured[offset] == 0xFF;
198 exact = exact && captured[offset + 1] == leds[led].r;
199 exact = exact && captured[offset + 2] == leds[led].g;
200 exact = exact && captured[offset + 3] == leds[led].b;
201 }
202 for (size_t index = 4 + (kLedCount * 4); index < kFrameBytes; ++index) {
203 exact = exact && captured[index] == 0xFF;
204 }
205
206 fl::json data = fl::json::object();
207 data.set("chipset", Sk9822 ? "SK9822" : "APA102");
208 data.set("pattern", static_cast<int64_t>(pattern));
209 data.set("actualClockHz", static_cast<int64_t>(driver.lastActualClockHz()));
210 data.set("wireIdle", true);
211 data.set("frameBytes", static_cast<int64_t>(kFrameBytes));
212 response.set("success", exact);
213 response.set("message", exact ? "RP SPI public API loopback passed."
214 : "RP SPI public API loopback failed.");
215 response.set("data", data);
216 return response;
217}
218
219#endif
220
221} // namespace
222
224 remote.bind("rpSpiLoopback", [this](const fl::json& args) -> fl::json {
225#if !defined(FL_IS_RP2040) && !defined(FL_IS_RP2350)
226 (void)this;
227 (void)args;
228 fl::json response = fl::json::object();
229 response.set("success", false);
230 response.set("error", "PlatformNotSupported");
231 response.set("message", "rpSpiLoopback requires an RP2040 or RP2350 target.");
232 return response;
233#else
234 if (!args.is_object()) {
235 fl::json response = fl::json::object();
236 response.set("success", false);
237 response.set("error", "InvalidArgs");
238 response.set("message", "Expected an object with spi_index, mosi_pin, miso_pin, sck_pin, and clock_hz.");
239 return response;
240 }
241 const fl::json config = args;
242 int spi_index = -1;
243 int mosi_pin = -1;
244 int miso_pin = -1;
245 int sck_pin = -1;
246 int clock_hz = 0;
247 if (!parseInt(config, "spi_index", &spi_index) ||
248 !parseInt(config, "mosi_pin", &mosi_pin) ||
249 !parseInt(config, "miso_pin", &miso_pin) ||
250 !parseInt(config, "sck_pin", &sck_pin) ||
251 !parseInt(config, "clock_hz", &clock_hz)) {
252 fl::json response = fl::json::object();
253 response.set("success", false);
254 response.set("error", "InvalidArgs");
255 response.set("message", "spi_index, mosi_pin, miso_pin, sck_pin, and clock_hz must be integers.");
256 return response;
257 }
258 if (spi_index == 0) {
259 return runLoopback<0>(mState, mosi_pin, miso_pin, sck_pin,
260 clock_hz);
261 }
262 if (spi_index == 1) {
263 return runLoopback<1>(mState, mosi_pin, miso_pin, sck_pin,
264 clock_hz);
265 }
266 fl::json response = fl::json::object();
267 response.set("success", false);
268 response.set("error", "InvalidSpiIndex");
269 response.set("message", "spi_index must be 0 or 1.");
270 return response;
271#endif
272 });
273 remote.bind("rpSpiPublicApiLoopback", [this](const fl::json& args) -> fl::json {
274#if !defined(FL_IS_RP2040) && !defined(FL_IS_RP2350)
275 (void)this;
276 (void)args;
277 fl::json response = fl::json::object();
278 response.set("success", false);
279 response.set("error", "PlatformNotSupported");
280 return response;
281#else
282 if (!args.is_object() || !args.contains("sk9822") ||
283 !args["sk9822"].is_bool() || !args.contains("pattern") ||
284 !args["pattern"].is_int()) {
285 fl::json response = fl::json::object();
286 response.set("success", false);
287 response.set("error", "InvalidArgs");
288 return response;
289 }
290 const fl::i64 pattern_value = args["pattern"].as_int().value();
291 if (pattern_value < 0 || pattern_value > 5) {
292 fl::json response = fl::json::object();
293 response.set("success", false);
294 response.set("error", "InvalidPattern");
295 return response;
296 }
297 const bool sk9822 = args["sk9822"].as_bool().value();
298 const int pattern = static_cast<int>(pattern_value);
299 return sk9822 ? runPublicApiLoopback<true>(mState, pattern)
300 : runPublicApiLoopback<false>(mState, pattern);
301#endif
302 });
303}
304
305#endif // FL_PLATFORM_HAS_LARGE_MEMORY
CRGB leds[1]
TestState state
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
fl::unique_ptr< fl::Remote > remote
Definition RpcClient.ino:43
SPI encoder configuration for clocked LED chipsets.
void bindRpSpiMethods(fl::Remote &remote)
fl::shared_ptr< AutoResearchState > mState
static ChannelDataPtr create(const ChipsetVariant &chipset, fl::vector_psram< u8 > &&encodedData=fl::vector_psram< u8 >(), ChannelPixelFormat pixelFormat=ChannelPixelFormat::RGB) FL_NO_EXCEPT
Create channel transmission data (modern variant-based API)
Definition data.cpp.hpp:12
JSON-RPC server with scheduling support.
Definition remote.h:42
fl::optional< i64 > as_int() const FL_NO_EXCEPT
Definition json.h:398
void set(const fl::string &key, const json &value) FL_NO_EXCEPT
Definition json.h:866
void push_back(const json &value) FL_NO_EXCEPT
Definition json.h:918
bool contains(size_t idx) const FL_NO_EXCEPT
Definition json.h:790
static json object() FL_NO_EXCEPT
Definition json.h:857
static json array() FL_NO_EXCEPT
Definition json.h:853
void reset() FL_NO_EXCEPT
Definition shared_ptr.h:286
void assign(InputIt first, InputIt last) FL_NO_EXCEPT
Definition vector.h:638
Channel transmission data - lightweight DTO for driver transmission.
fl::UISlider offset("Offset", 0.0f, 0.0f, 1.0f, 0.01f)
constexpr EOrder RGB
Definition eorder.h:17
@ SK9822
SK9822 LED chipset.
Definition FastLED.h:305
@ APA102
APA102 LED chipset.
Definition FastLED.h:304
fl::CRGB CRGB
Definition crgb.h:25
FastLED's Elegant JSON Library: fl::json
bool parseInt(const fl::string &str, int &out)
unsigned char u8
Definition stdint.h:131
constexpr remove_reference< T >::type && move(T &&t) FL_NO_EXCEPT
Definition move.h:28
void delay(u32 ms, bool run_async=true) FL_NO_EXCEPT
Public delay wrapper that keeps bare Arduino delay() preferred after using fl::delay; while still all...
Definition delay.h:98
fl::u32 millis()
Universal millisecond timer - returns milliseconds since system startup.
@ SPI
Definition bus.h:31
Primary template — intentionally undefined.
Definition bus_traits.h:31
corkscrew_args args
Definition old.h:149
@ Green
<div style='background:#008000;width:4em;height:4em;'></div>
Definition crgb.h:565
@ Red
<div style='background:#FF0000;width:4em;height:4em;'></div>
Definition crgb.h:629
@ Blue
<div style='background:#0000FF;width:4em;height:4em;'></div>
Definition crgb.h:519
@ White
<div style='background:#FFFFFF;width:4em;height:4em;'></div>
Definition crgb.h:653
@ Black
<div style='background:#000000;width:4em;height:4em;'></div>
Definition crgb.h:517
@ READY
Hardware idle; ready to accept new transmissions.
Definition driver.h:170
@ ERROR
Driver encountered an error.
Definition driver.h:173
@ BUSY
Active: channels transmitting or queued.
Definition driver.h:171
Driver state with optional error message.
Definition driver.h:168
SPI chipset configuration (data + clock pins)
Definition config.h:103
static SpiEncoder apa102(u32 clock_hz=6000000) FL_NO_EXCEPT
Create APA102 encoder configuration.
Definition spi.h:44
static constexpr T min() FL_NO_EXCEPT
Definition limits.h:107
static constexpr T max() FL_NO_EXCEPT
Definition limits.h:108