Skip to content

Commit 307fc02

Browse files
hhvrcclaude
andcommitted
feat(visual): add ledtest serial command and fix cooperative task shutdown
Add serial command `ledtest` that cycles through: - Mono LED PWM brightness sweep (up/down) - RGB LED color test (R/G/B/white, rainbow cycle, brightness sweep) - All visual state patterns (WiFi, WS, E-Stop states, critical error) Fix cooperative task shutdown in both LED drivers by chunking long vTaskDelay calls into 50ms intervals, allowing m_stopRequested to be checked promptly instead of force-killing tasks after timeout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 12baa02 commit 307fc02

6 files changed

Lines changed: 178 additions & 2 deletions

File tree

include/serial/command_handlers/index.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ namespace OpenShock::Serial::CommandHandlers {
2121
OpenShock::Serial::CommandGroup RawConfigHandler();
2222
OpenShock::Serial::CommandGroup RfTransmitHandler();
2323
OpenShock::Serial::CommandGroup FactoryResetHandler();
24+
OpenShock::Serial::CommandGroup LedTestHandler();
2425

2526
inline std::vector<OpenShock::Serial::CommandGroup> AllCommandHandlers()
2627
{
@@ -41,6 +42,7 @@ namespace OpenShock::Serial::CommandHandlers {
4142
RawConfigHandler(),
4243
RfTransmitHandler(),
4344
FactoryResetHandler(),
45+
LedTestHandler(),
4446
};
4547
}
4648
} // namespace OpenShock::Serial::CommandHandlers

include/visual/VisualStateManager.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@ namespace OpenShock::VisualStateManager {
88

99
void SetCriticalError();
1010
void SetScanningStarted();
11+
12+
/// Cycles through all LED patterns for visual verification. Blocks for ~20s.
13+
void RunLedTest();
1114
} // namespace OpenShock::VisualStateManager
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include "serial/command_handlers/common.h"
2+
3+
#include "visual/VisualStateManager.h"
4+
5+
void _handleSerialLedTestCommand(std::string_view arg, bool isAutomated)
6+
{
7+
(void)arg;
8+
(void)isAutomated;
9+
10+
SERPR_RESPONSE("LedTest|Starting LED test (~24s)...");
11+
12+
OpenShock::VisualStateManager::RunLedTest();
13+
14+
SERPR_SUCCESS("LedTest|Complete");
15+
}
16+
17+
OpenShock::Serial::CommandGroup OpenShock::Serial::CommandHandlers::LedTestHandler()
18+
{
19+
auto group = OpenShock::Serial::CommandGroup("ledtest"sv);
20+
21+
group.addCommand("Cycle through all LED patterns for visual verification"sv, _handleSerialLedTestCommand);
22+
23+
return group;
24+
}

src/visual/MonoLedDriver.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,14 @@ void MonoLedDriver::RunPattern()
130130
if (m_stopRequested.load(std::memory_order_relaxed)) break;
131131
ledc_set_duty(OS_LEDC_SPEED, OS_LEDC_CHANNEL, state.level ? m_brightness : 0);
132132
ledc_update_duty(OS_LEDC_SPEED, OS_LEDC_CHANNEL);
133-
vTaskDelay(pdMS_TO_TICKS(state.duration));
133+
134+
// Chunked delay so cooperative shutdown can interrupt long waits
135+
uint32_t remaining = state.duration;
136+
while (remaining > 0 && !m_stopRequested.load(std::memory_order_relaxed)) {
137+
uint32_t chunk = remaining > 50 ? 50 : remaining;
138+
vTaskDelay(pdMS_TO_TICKS(chunk));
139+
remaining -= chunk;
140+
}
134141
}
135142
}
136143

src/visual/RgbLedDriver.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,14 @@ void RgbLedDriver::RunPattern()
141141

142142
// Send the data
143143
rmtWriteBlocking(m_rmtHandle, led_data.data(), led_data.size());
144-
vTaskDelay(pdMS_TO_TICKS(state.duration));
144+
145+
// Chunked delay so cooperative shutdown can interrupt long waits
146+
uint32_t remaining = state.duration;
147+
while (remaining > 0 && !m_stopRequested.load(std::memory_order_relaxed)) {
148+
uint32_t chunk = remaining > 50 ? 50 : remaining;
149+
vTaskDelay(pdMS_TO_TICKS(chunk));
150+
remaining -= chunk;
151+
}
145152
}
146153
}
147154

src/visual/VisualStateManager.cpp

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,3 +429,136 @@ void VisualStateManager::SetScanningStarted()
429429
_updateVisualState();
430430
}
431431
}
432+
433+
void VisualStateManager::RunLedTest()
434+
{
435+
// Save current state
436+
uint64_t savedFlags = s_stateFlags.load(std::memory_order_relaxed);
437+
438+
OS_LOGI(TAG, "=== LED Test Started ===");
439+
440+
// --- Phase 1: Mono LED brightness sweep (tests LEDC PWM) ---
441+
if (s_monoLedDriver != nullptr) {
442+
OS_LOGI(TAG, " [Mono] Brightness sweep up");
443+
s_monoLedDriver->ClearPattern();
444+
445+
static const MonoLedDriver::State solidOn[] = {
446+
{true, 100'000}
447+
};
448+
for (uint16_t b = 0; b <= 255; b += 5) {
449+
s_monoLedDriver->SetBrightness(static_cast<uint8_t>(b));
450+
s_monoLedDriver->SetPattern(solidOn);
451+
vTaskDelay(pdMS_TO_TICKS(20));
452+
}
453+
454+
OS_LOGI(TAG, " [Mono] Brightness sweep down");
455+
for (int16_t b = 255; b >= 0; b -= 5) {
456+
s_monoLedDriver->SetBrightness(static_cast<uint8_t>(b));
457+
s_monoLedDriver->SetPattern(solidOn);
458+
vTaskDelay(pdMS_TO_TICKS(20));
459+
}
460+
461+
// Restore default brightness
462+
s_monoLedDriver->SetBrightness(255);
463+
s_monoLedDriver->ClearPattern();
464+
vTaskDelay(pdMS_TO_TICKS(500));
465+
}
466+
467+
// --- Phase 2: RGB LED color test ---
468+
if (s_rgbLedDriver != nullptr) {
469+
uint8_t savedBrightness = 20; // default from Init()
470+
471+
s_rgbLedDriver->SetBrightness(255);
472+
473+
OS_LOGI(TAG, " [RGB] Red");
474+
static const RgbLedDriver::RGBState red[] = {
475+
{255, 0, 0, 100'000}
476+
};
477+
s_rgbLedDriver->SetPattern(red);
478+
vTaskDelay(pdMS_TO_TICKS(1500));
479+
480+
OS_LOGI(TAG, " [RGB] Green");
481+
static const RgbLedDriver::RGBState green[] = {
482+
{0, 255, 0, 100'000}
483+
};
484+
s_rgbLedDriver->SetPattern(green);
485+
vTaskDelay(pdMS_TO_TICKS(1500));
486+
487+
OS_LOGI(TAG, " [RGB] Blue");
488+
static const RgbLedDriver::RGBState blue[] = {
489+
{0, 0, 255, 100'000}
490+
};
491+
s_rgbLedDriver->SetPattern(blue);
492+
vTaskDelay(pdMS_TO_TICKS(1500));
493+
494+
OS_LOGI(TAG, " [RGB] White");
495+
static const RgbLedDriver::RGBState white[] = {
496+
{255, 255, 255, 100'000}
497+
};
498+
s_rgbLedDriver->SetPattern(white);
499+
vTaskDelay(pdMS_TO_TICKS(1500));
500+
501+
OS_LOGI(TAG, " [RGB] Color cycle");
502+
static const RgbLedDriver::RGBState cycle[] = {
503+
{255, 0, 0, 500},
504+
{255, 165, 0, 500},
505+
{255, 255, 0, 500},
506+
{ 0, 255, 0, 500},
507+
{ 0, 255, 255, 500},
508+
{ 0, 0, 255, 500},
509+
{128, 0, 255, 500},
510+
{255, 0, 255, 500},
511+
};
512+
s_rgbLedDriver->SetPattern(cycle);
513+
vTaskDelay(pdMS_TO_TICKS(4000));
514+
515+
OS_LOGI(TAG, " [RGB] Brightness sweep");
516+
static const RgbLedDriver::RGBState solidWhite[] = {
517+
{255, 255, 255, 100'000}
518+
};
519+
for (uint16_t b = 0; b <= 255; b += 5) {
520+
s_rgbLedDriver->SetBrightness(static_cast<uint8_t>(b));
521+
s_rgbLedDriver->SetPattern(solidWhite);
522+
vTaskDelay(pdMS_TO_TICKS(20));
523+
}
524+
for (int16_t b = 255; b >= 0; b -= 5) {
525+
s_rgbLedDriver->SetBrightness(static_cast<uint8_t>(b));
526+
s_rgbLedDriver->SetPattern(solidWhite);
527+
vTaskDelay(pdMS_TO_TICKS(20));
528+
}
529+
530+
s_rgbLedDriver->SetBrightness(savedBrightness);
531+
s_rgbLedDriver->ClearPattern();
532+
vTaskDelay(pdMS_TO_TICKS(500));
533+
}
534+
535+
// --- Phase 3: State pattern test (both LEDs together) ---
536+
struct TestStep {
537+
const char* name;
538+
uint64_t flags;
539+
uint32_t durationMs;
540+
};
541+
542+
static const TestStep steps[] = {
543+
{ "WiFi Disconnected", 0, 2000},
544+
{ "WiFi Connected (no WS)", kHasIpAddressFlag, 2000},
545+
{ "WebSocket Connected", kWebSocketConnectedFlag | kHasIpAddressFlag | kWiFiConnectedFlag, 2000},
546+
{ "E-Stop Active", kEmergencyStoppedFlag, 2000},
547+
{ "E-Stop Active Clearing", kEmergencyStoppedFlag | kEmergencyStopActiveClearingFlag, 2000},
548+
{"E-Stop Awaiting Release", kEmergencyStoppedFlag | kEmergencyStopAwaitingReleaseFlag, 2000},
549+
{ "Critical Error", kCriticalErrorFlag, 2000},
550+
};
551+
552+
OS_LOGI(TAG, " State patterns:");
553+
for (const auto& step : steps) {
554+
OS_LOGI(TAG, " %s", step.name);
555+
s_stateFlags.store(step.flags, std::memory_order_relaxed);
556+
_updateVisualState();
557+
vTaskDelay(pdMS_TO_TICKS(step.durationMs));
558+
}
559+
560+
// Restore original state
561+
OS_LOGI(TAG, "=== LED Test Complete, restoring state ===");
562+
s_stateFlags.store(savedFlags, std::memory_order_relaxed);
563+
_updateVisualState();
564+
}

0 commit comments

Comments
 (0)