Skip to content

Commit

Permalink
Reimplement LED blinker to support RGB LEDs
Browse files Browse the repository at this point in the history
  • Loading branch information
mairas committed Dec 11, 2024
1 parent f2380bc commit 2b06f1d
Show file tree
Hide file tree
Showing 7 changed files with 452 additions and 306 deletions.
181 changes: 45 additions & 136 deletions src/sensesp/system/led_blinker.cpp
Original file line number Diff line number Diff line change
@@ -1,154 +1,63 @@

#include "sensesp.h"

#include "led_blinker.h"

#include "sensesp_app.h"

namespace sensesp {

#define max(a, b) ((a) > (b) ? (a) : (b))

BaseBlinker::BaseBlinker(int pin) : pin_{pin} {
pinMode(pin, OUTPUT);
event_loop()->onDelay(1, [this]() { this->tick(); });
}
LEDPatternFragment frag_solid_color(uint32_t duration_ms, const CRGB& color) {
return LEDPatternFragment(duration_ms,
[color](uint32_t, CRGB& crgb) { crgb = color; });
}

LEDPatternFragment frag_linear_fade(uint32_t duration_ms,
uint32_t fade_duration_ms,
const CRGB& target_color) {
LEDPatternFragment fragment(duration_ms, [target_color, fade_duration_ms](
uint32_t elapsed_ms,
CRGB& crgb) {
static CRGB from_color;
static unsigned long last_elapsed_ms = 0;
if (elapsed_ms < last_elapsed_ms) {
from_color = crgb;
}
last_elapsed_ms = elapsed_ms;
if (elapsed_ms >= fade_duration_ms) {
crgb = target_color;
return;
}
crgb = blend(from_color, target_color, elapsed_ms * 256 / fade_duration_ms);
});

/**
* Turn the LED on or off.
*/
void BaseBlinker::set_state(bool state) {
this->state_ = state;
digitalWrite(pin_, state);
update_counter_++;
return fragment;
}

/**
* Invert the current LED state.
*/
void BaseBlinker::flip_state() { this->set_state(!this->state_); }

/**
* Flip the LED off and on for `duration` milliseconds.
*/
void BaseBlinker::blip(int duration) {
// indicator for a blip being in progress
static bool blipping = false;

// only allow one blip at a time
if (blipping) {
return;
}
blipping = true;

bool const orig_state = this->state_;
this->set_state(false);
int const current_counter = this->update_counter_;
event_loop()->onDelay(
duration, [this, duration, orig_state, current_counter]() {
// only update if no-one has touched the LED in the meanwhile
if (this->update_counter_ == current_counter) {
this->set_state(true);
int const new_counter = this->update_counter_;
event_loop()->onDelay(
duration, [this, orig_state, new_counter]() {
// again, only update if no-one has touched the LED
if (this->update_counter_ == new_counter) {
this->set_state(orig_state);
}
blipping = false;
});
LEDPatternFragment frag_linear_invert(uint32_t duration_ms, bool reverse) {
return LEDPatternFragment(
duration_ms, [duration_ms, reverse](uint32_t elapsed_ms, CRGB& crgb) {
// Blend the color from current color to inverted
if (reverse) {
crgb = blend(CRGB(255, 255, 255) - crgb, crgb,
elapsed_ms * 256 / duration_ms);
} else {
blipping = false;
crgb = blend(crgb, CRGB(255, 255, 255) - crgb,
elapsed_ms * 256 / duration_ms);
}
});
}

/**
* Enable or disable the blinker.
*/
void BaseBlinker::set_enabled(bool state) {
bool const was_enabled = this->enabled_;
this->enabled_ = state;
if (this->enabled_) {
this->tick();
} else {
this->set_state(false);
if (was_enabled) {
event_->remove(event_loop());
LEDPatternFragment frag_blend(uint32_t duration_ms, const CRGB& target_color,
bool reverse) {
return LEDPatternFragment(duration_ms, [duration_ms, reverse, target_color](
uint32_t elapsed_ms, CRGB& crgb) {
if (reverse) {
crgb = blend(target_color, crgb, elapsed_ms * 255 / duration_ms);
} else {
crgb = blend(crgb, target_color, elapsed_ms * 255 / duration_ms);
}
}
}

PeriodicBlinker::PeriodicBlinker(int pin, unsigned int period)
: BaseBlinker(pin), period_{period} {}

EvenBlinker::EvenBlinker(int pin, unsigned int period)
: PeriodicBlinker(pin, period) {}

void EvenBlinker::tick() {
if (!enabled_) {
return;
}
this->flip_state();
event_ = event_loop()->onDelay(
period_, [this]() { this->tick(); });
}

RatioBlinker::RatioBlinker(int pin, unsigned int period, float ratio)
: PeriodicBlinker(pin, period), ratio_{ratio} {}

void RatioBlinker::tick() {
if (!enabled_) {
return;
}
this->flip_state();
int const on_duration = ratio_ * period_;
int const off_duration = max(0, period_ - on_duration);
unsigned int const ref_duration =
state_ == false ? off_duration : on_duration;
event_ = event_loop()->onDelay(
ref_duration, [this]() { this->tick(); });
}

PatternBlinker::PatternBlinker(int pin, int pattern[])
: BaseBlinker(pin), pattern_{pattern} {}

/**
* Set a new blink pattern. Patterns are arrays of ints, with
* PATTERN_END as the last value. Each number in the pattern indicates
* the length of that segment, in milliseconds. The first number indicates
* an ON duration, the second an OFF duration, and so on.
*/
void PatternBlinker::set_pattern(int pattern[]) {
this->pattern_ = pattern;
this->restart();
}

void PatternBlinker::tick() {
if (!enabled_) {
return;
}
// When pattern[pattern_ptr] == PATTERN_END, that's the end of the pattern,
// so start over at the beginning.
if (pattern_[pattern_ptr_] == PATTERN_END) {
pattern_ptr_ = 0;
}
// odd indices indicate times when LED should be OFF, even when ON
bool const new_state = (pattern_ptr_ % 2) == 0;
this->set_state(new_state);
event_ = event_loop()->onDelay(
pattern_[pattern_ptr_++], [this]() { this->tick(); });
});
}

void PatternBlinker::restart() {
state_ = false;
pattern_ptr_ = 0;
if (event_ != NULL) {
event_->remove(event_loop());
event_ = NULL;
this->tick();
}
LEDPatternFragment frag_nop(uint32_t duration_ms) {
// No operation
return LEDPatternFragment(duration_ms, [](uint32_t, CRGB&) {});
}

} // namespace sensesp
Loading

0 comments on commit 2b06f1d

Please sign in to comment.