-Preferring Python? I just released jled-circuitpython,
+
+Preferring Python? I just released jled-circuitpython,
a JLed implementation for CircuitPython and MicroPython.
|
@@ -91,8 +91,8 @@ void loop() {
* [Arduino framework](#arduino-framework)
* [Raspberry Pi Pico](#raspberry-pi-pico)
* [Example sketches](#example-sketches)
- * [PlatformIO](#platformio-1)
- * [Arduino IDE](#arduino-ide-1)
+ * [Building examples with PlatformIO](#building-examples-with-platformio)
+ * [Building examples with the Arduino IDE](#building-examples-with-the-arduino-ide)
* [Extending](#extending)
* [Support new hardware](#support-new-hardware)
* [Unit tests](#unit-tests)
@@ -194,9 +194,9 @@ Use the `Set(uint8_t brightness, uint16_t period=1)` method to set the
brightness to the given value, i.e., `Set(255)` is equivalent to calling `On()`
and `Set(0)` is equivalent to calling `Off()`.
-Technically, `Set`, `On` and `Off` are effects with a default period of 1ms, that
+Technically, `Set`, `On` and `Off` are effects with a default period of 1ms, that
set the brightness to a constant value. Specifying a different period has an
-effect on when the `Update()` method will be done updating the effect and
+effect on when the `Update()` method will be done updating the effect and
return false (like for any other effects). This is important when for example
in a `JLedSequence` the LED should stay on for a given amount of time.
@@ -268,25 +268,25 @@ auto led = JLed(13).Breathe(500, 1000, 500).DelayAfter(1000).Forever();
#### Candle
-In candle mode, the random flickering of a candle or fire is simulated.
+In candle mode, the random flickering of a candle or fire is simulated.
The builder method has the following signature:
`Candle(uint8_t speed, uint8_t jitter, uin16_t period)`
-* `speed` - controls the speed of the effect. 0 for fastest, increasing speed
+* `speed` - controls the speed of the effect. 0 for fastest, increasing speed
divides into halve per increment. The default value is 7.
* `jitter` - the amount of jittering. 0 none (constant on), 255 maximum. Default
value is 15.
* `period` - Period of effect in ms. The default value is 65535 ms.
The default settings simulate a candle. For a fire effect for example use
-call the method with `Candle(5 /*speed*/, 100 /* jitter*/)`.
+call the method with `Candle(5 /*speed*/, 100 /* jitter*/)`.
##### Candle example
```c++
#include
-// Candle on LED pin 13 (PWM capable).
+// Candle on LED pin 13 (PWM capable).
auto led = JLed(13).Candle();
void setup() { }
@@ -363,7 +363,7 @@ two methods:
as an unsigned byte, where 0 means LED off and 255 means full brightness.
* `uint16_t Period() const` - period of the effect.
-All time values are specified in milliseconds.
+All time values are specified in milliseconds.
The [user_func](examples/user_func) example demonstrates a simple user provided
brightness function, while the [morse](examples/morse) example shows how a more
@@ -410,10 +410,29 @@ specified by `DelayAfter()` method.
##### Update
-Call `Update()` or `Update(uint32_t t)` periodically to update the state of the
-LED. `Update` returns `true` if the effect is active, and `false` when it
-finished. `Update()` is a shortcut to call `Update(uint32_t t)` with the
-current time.
+Call `Update(int16_t *pLast=nullptr)` or `Update(uint32_t t, int16_t *pLast=nullptr)`
+to periodically update the state of the LED.
+
+`Update` returns `true`, if the effect is active, or `false` when it finished.
+`Update()` is a shortcut to call `Update(uint32_t t)` with the current time in
+milliseconds.
+
+To obtain the value of the last written brightness value (after applying min-
+and max-brightness transformations), pass an additional optional pointer
+`*pLast` , where this value will be stored, when it was written. Example:
+
+```c++
+int16_t lastVal = -1;
+led.Update(&lastVal);
+if (lastVal != -1) {
+ // the LED was updated with the brightness value now stored in lastVal
+ ...
+}
+```
+
+Most of the time just calling `Update()` without any parameters is what you want.
+
+See [last_brightness](examples/last_brightness) example for a working example.
##### IsRunning
@@ -453,8 +472,8 @@ will be inverted by JLed (i.e., instead of x, the value of 255-x will be set).
##### Minimum- and Maximum brightness level
-The `MaxBrightness(uint8_t level)` method is used to set the maximum brightness
-level of the LED. A level of 255 (the default) is full brightness, while 0
+The `MaxBrightness(uint8_t level)` method is used to set the maximum brightness
+level of the LED. A level of 255 (the default) is full brightness, while 0
effectively turns the LED off. In the same way, the `MinBrightness(uint8_t level)`
method sets the minimum brightness level. The default minimum level is 0. If
minimum or maximum brightness levels are set, the output value is scaled to be
@@ -462,7 +481,7 @@ within the interval defined by `[minimum brightness, maximum brightness]`: a
value of 0 will be mapped to the minimum brightness level, a value of 255 will
be mapped to the maximum brightness level.
-The `uint_8 MaxBrightness() const` method returns the current maximum
+The `uint_8 MaxBrightness() const` method returns the current maximum
brightness level. `uint8_t MinBrightness() const` returns the current minimum
brightness level.
@@ -500,10 +519,10 @@ The `JLedSequence` provides the following methods:
else `false`.
* Use the `Repeat(n)` method to specify the number of repetitions. The default
value is 1 repetition. The `Forever()` methods sets to repeat the sequence
- forever.
-* `Stop()` - turns off all `JLed` objects controlled by the sequence and
+ forever.
+* `Stop()` - turns off all `JLed` objects controlled by the sequence and
stops the sequence. Further calls to `Update()` will have no effect.
-* `Reset()` - Resets all `JLed` objects controlled by the sequence and
+* `Reset()` - Resets all `JLed` objects controlled by the sequence and
the sequence, resulting in a start-over.
## Framework notes
@@ -518,7 +537,7 @@ framework:
platform=ststm32
board = nucleo_f401re
framework = mbed
-build_flags = -Isrc
+build_flags = -Isrc
src_filter = +<../../src/> +<./>
upload_protocol=stlink
```
@@ -570,8 +589,8 @@ so it should be avoided and is normally not necessary.
For completeness, the full signature of the Esp32Hal constructor is
```
-Esp32Hal(PinType pin,
- int chan = kAutoSelectChan,
+Esp32Hal(PinType pin,
+ int chan = kAutoSelectChan,
uint16_t freq = 5000,
ledc_timer_t timer = LEDC_TIMER_0)
```
@@ -600,9 +619,14 @@ necessary to upload sketches to the microcontroller.
### Raspberry Pi Pico
-When using JLed on a Raspberry Pi Pico, the Pico-SDK and tools must be
-installed. The Pico supports up to 16 PWM channels in parallel. See
-the [pico-demo](examples/raspi_pico) for an example and build instructions.
+When using JLed on a Raspberry Pi Pico, the Pico-SDK and tools can be
+used. The Pico supports up to 16 PWM channels in parallel. See
+the [pico-demo](examples/raspi_pico) for an example and build instructions when
+the Pico-SDK is used.
+
+A probably easier approach is to use the Arduino platform. See
+[platformio.ini](platformio.ini) for details (look for
+`env:raspberrypi_pico_w`, which targets the Raspberry Pi Pico W.
## Example sketches
@@ -621,6 +645,7 @@ Example sketches are provided in the [examples](examples/) directory.
* [Controlling multiple LEDs sequentially](examples/sequence)
* [Simple User provided effect](examples/user_func)
* [Morsecode example](examples/morse)
+* [Last brightness value example](examples/last_brightness)
* [Custom HAL example](examples/custom_hal)
* [Custom PCA9685 HAL](https://github.com/jandelgado/jled-pca9685-hal)
* [Dynamically switch sequences](https://github.com/jandelgado/jled-example-switch-sequence)
@@ -629,9 +654,9 @@ Example sketches are provided in the [examples](examples/) directory.
* [ESP32 ESP-IDF example](https://github.com/jandelgado/jled-esp-idf-example)
* [ESP32 ESP-IDF PlatformIO example](https://github.com/jandelgado/jled-esp-idf-platformio-example)
-### PlatformIO
+### Building examples with PlatformIO
-To build an example using [the PlatformIO ide](http://platformio.org/),
+To build an example using [the PlatformIO ide](http://platformio.org/),
uncomment the example to be built in the [platformio.ini](platformio.ini)
project file, e.g.:
@@ -642,7 +667,7 @@ src_dir = examples/hello
;src_dir = examples/breathe
```
-### Arduino IDE
+### Building examples with the Arduino IDE
To build an example sketch in the Arduino IDE, select an example from
the `File` > `Examples` > `JLed` menu.
@@ -670,8 +695,8 @@ the host-based provided unit tests [is provided here](test/README.md).
* add code
* add [unit test(s)](test/)
* add [documentation](README.md)
-* make sure the cpp [linter](https://github.com/cpplint/cpplint) does not
- report any problems (run `make lint`). Hint: use `clang-format` with the
+* make sure the cpp [linter](https://github.com/cpplint/cpplint) does not
+ report any problems (run `make lint`). Hint: use `clang-format` with the
provided [settings](.clang-format)
* commit changes
* submit a PR
diff --git a/devbox.json b/devbox.json
index 5dfc81c..e24a8ed 100644
--- a/devbox.json
+++ b/devbox.json
@@ -1,13 +1,13 @@
{
"packages": [
- "python@3.11",
+ "python@3.13",
"lcov@1.16",
"pipx",
"cpplint@2.0.0"
],
"shell": {
- "init_hook": [
- "echo 'Welcome to devbox!' > /dev/null"
+ "init_hook": [
+ ". $VENV_DIR/bin/activate"
],
"scripts": {
"test": [
diff --git a/devbox.lock b/devbox.lock
index 14600c5..5e96396 100644
--- a/devbox.lock
+++ b/devbox.lock
@@ -89,24 +89,60 @@
"resolved": "github:NixOS/nixpkgs/75a52265bda7fd25e06e3a67dee3f0354e73243c#pipx",
"source": "nixpkg"
},
- "python@3.11": {
- "last_modified": "2024-03-22T11:26:23Z",
- "plugin_version": "0.0.3",
- "resolved": "github:NixOS/nixpkgs/a3ed7406349a9335cb4c2a71369b697cecd9d351#python3",
+ "python@3.13": {
+ "last_modified": "2024-11-28T07:51:56Z",
+ "plugin_version": "0.0.4",
+ "resolved": "github:NixOS/nixpkgs/226216574ada4c3ecefcbbec41f39ce4655f78ef#python313",
"source": "devbox-search",
- "version": "3.11.8",
+ "version": "3.13.0",
"systems": {
"aarch64-darwin": {
- "store_path": "/nix/store/c05vbvkjxarxkws9zkwrcwrzlsx9nd68-python3-3.11.8"
+ "outputs": [
+ {
+ "name": "out",
+ "path": "/nix/store/fbyrkq5n04a9hn5zs26vrmqjzdx73d4g-python3-3.13.0",
+ "default": true
+ }
+ ],
+ "store_path": "/nix/store/fbyrkq5n04a9hn5zs26vrmqjzdx73d4g-python3-3.13.0"
},
"aarch64-linux": {
- "store_path": "/nix/store/pxzzyri1wbq7kc7pain665g94afkl4ww-python3-3.11.8"
+ "outputs": [
+ {
+ "name": "out",
+ "path": "/nix/store/jbz9fj3sp5c8bf0s6d0bkjjj9mslxsrc-python3-3.13.0",
+ "default": true
+ },
+ {
+ "name": "debug",
+ "path": "/nix/store/60jgy93wj50wwimmhm2p53pzaiap8ypm-python3-3.13.0-debug"
+ }
+ ],
+ "store_path": "/nix/store/jbz9fj3sp5c8bf0s6d0bkjjj9mslxsrc-python3-3.13.0"
},
"x86_64-darwin": {
- "store_path": "/nix/store/1zaap1xxxvw2ypsgh1mfxb3wzdd49873-python3-3.11.8"
+ "outputs": [
+ {
+ "name": "out",
+ "path": "/nix/store/c7j1vxcdcqswsddm5m1n2n4z5zfhmbq2-python3-3.13.0",
+ "default": true
+ }
+ ],
+ "store_path": "/nix/store/c7j1vxcdcqswsddm5m1n2n4z5zfhmbq2-python3-3.13.0"
},
"x86_64-linux": {
- "store_path": "/nix/store/7wz6hm9i8wljz0hgwz1wqmn2zlbgavrq-python3-3.11.8"
+ "outputs": [
+ {
+ "name": "out",
+ "path": "/nix/store/0b83hlniyfbpha92k2j0w93mxdalv8kb-python3-3.13.0",
+ "default": true
+ },
+ {
+ "name": "debug",
+ "path": "/nix/store/xzhxhqs8my0yvfi09aj1s9i1s9nrmpvg-python3-3.13.0-debug"
+ }
+ ],
+ "store_path": "/nix/store/0b83hlniyfbpha92k2j0w93mxdalv8kb-python3-3.13.0"
}
}
}
diff --git a/examples/last_brightness/last_brightness.ino b/examples/last_brightness/last_brightness.ino
new file mode 100644
index 0000000..b5cb71a
--- /dev/null
+++ b/examples/last_brightness/last_brightness.ino
@@ -0,0 +1,38 @@
+// Stops an effect when a button is pressed (and hold). When the button is
+// released, the LED will fade to off with starting the brightness value it had
+// when the effect was stopped.
+//
+// dependency: arduinogetstarted/ezButton@1.0.6 to control the button
+//
+// Copyright 2024 by Jan Delgado. All rights reserved.
+// https://github.com/jandelgado/jled
+//
+#include // arduinogetstarted/ezButton@1.0.6
+#include
+
+constexpr auto LED_PIN = 16;
+constexpr auto BUTTON_PIN = 18;
+
+auto button = ezButton(BUTTON_PIN);
+
+// start with a pulse effect
+auto led =
+ JLed(LED_PIN).DelayBefore(1000).Breathe(2000).Forever().MinBrightness(25);
+
+void setup() {}
+
+void loop() {
+ static int16_t lastBrightness = 0;
+
+ button.loop();
+ led.Update(&lastBrightness);
+
+ if (button.isPressed()) {
+ // when the button is pressed, stop the effect on led, but keep the LED
+ // on with it's current brightness ...
+ led.Stop(JLed::KEEP_CURRENT);
+ } else if (button.isReleased()) {
+ // when the button is released, fade from the last brightness to 0
+ led = JLed(LED_PIN).Fade(lastBrightness, 0, 1000);
+ }
+}
diff --git a/library.json b/library.json
index 52dc8a0..448ad55 100644
--- a/library.json
+++ b/library.json
@@ -1,6 +1,6 @@
{
"name": "JLed",
- "version": "4.14",
+ "version": "4.15.0",
"description": "An embedded library to control LEDs",
"license": "MIT",
"frameworks": ["espidf", "arduino", "mbed"],
diff --git a/library.properties b/library.properties
index a11d63b..f71cc0d 100644
--- a/library.properties
+++ b/library.properties
@@ -1,5 +1,5 @@
name=JLed
-version=4.14
+version=4.15.0
author=Jan Delgado
maintainer=Jan Delgado
sentence=An Arduino library to control LEDs
diff --git a/platformio.ini b/platformio.ini
index eb98c62..86f7718 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -18,7 +18,7 @@
;default_envs = esp8266
default_envs = esp32
;default_envs = sparkfun_samd21_dev_usb
-;default_envs = sparkfun_samd21_dev_usb
+;default_envs = raspberrypi_pico_w
; uncomment example to build
src_dir = examples/hello
@@ -28,6 +28,7 @@ src_dir = examples/hello
;src_dir = examples/fade_on
;src_dir = examples/fade_off
;src_dir = examples/simple_on
+;src_dir = examples/last_brightness
;src_dir = examples/multiled
;src_dir = examples/multiled_mbed
;src_dir = examples/user_func
@@ -36,29 +37,26 @@ src_dir = examples/hello
;src_dir = examples/pulse
;src_dir = examples/fade_from_to
+[env]
+build_flags = -Isrc
+build_src_filter = +<../../src/> +<./>
+;lib_deps = arduinogetstarted/ezButton@1.0.6
+
[env:nanoatmega328]
platform = atmelavr
board = nanoatmega328
framework = arduino
-build_flags = -Isrc
-build_src_filter = +<../../src/> +<./>
[env:nucleo_f401re_mbed]
platform=ststm32
board = nucleo_f401re
framework = mbed
-build_flags = -Isrc
-build_src_filter = +<../../src/> +<./>
upload_protocol=stlink
[env:nucleo_f401re]
-# nucleo f401re arduino framework support only on master at the moment
platform=ststm32
-;platform=https://github.com/platformio/platform-ststm32.git
board = nucleo_f401re
framework = arduino
-build_flags = -Isrc
-build_src_filter = +<../../src/> +<./>
upload_protocol=stlink
debug_speed=auto
@@ -66,29 +64,29 @@ debug_speed=auto
platform = espressif8266
board = nodemcuv2
framework = arduino
-build_flags = -Isrc
-build_src_filter = +<../../src/> +<./>
[env:esp32]
lib_ldf_mode = off
platform = espressif32
board = esp32dev
framework = arduino
-build_flags = -Isrc
-build_src_filter = +<../../src/> +<./>
[env:sparkfun_samd21_dev_usb]
platform = atmelsam
framework = arduino
board = sparkfun_samd21_dev_usb
-build_flags = -Isrc
-build_src_filter = +<../../src/> +<./>
[env:nano33ble]
platform=https://github.com/platformio/platform-nordicnrf52.git
board = nano33ble
framework = arduino
-build_flags = -Isrc
-build_src_filter = +<../../src/> +<./>
upload_protocol=stlink
+[env:raspberrypi_pico_w]
+build_flags = ${env.build_flags} -D ARDUINO_RASPBERRY_PI_PICO_W
+platform = https://github.com/maxgerhardt/platform-raspberrypi.git
+board = rpipicow
+framework = arduino
+board_build.filesystem_size = 0.5m
+board_build.core = earlephilhower
+upload_protocol = picotool
diff --git a/src/jled_base.cpp b/src/jled_base.cpp
index 6e59531..62d0358 100644
--- a/src/jled_base.cpp
+++ b/src/jled_base.cpp
@@ -26,8 +26,7 @@ namespace jled {
// pre-calculated fade-on function. This table samples the function
// y(x) = exp(sin((t - period / 2.) * PI / period)) - 0.36787944)
// * 108.
-// at x={0,32,...,256}. In FadeOnFunc() we us linear interpolation
-// to
+// at x={0,32,...,256}. In FadeOnFunc() we us linear interpolation to
// approximate the original function (so we do not need fp-ops).
// fade-off and breath functions are all derived from fade-on, see
// below.
@@ -70,14 +69,21 @@ uint8_t rand8() {
// scale8(0, f) == 0 for all f
// scale8(x, 255) == x for all x
uint8_t scale8(uint8_t val, uint8_t factor) {
- return (static_cast(val)*static_cast(1 + factor)) >> 8;
+ return (static_cast(val)*static_cast(factor))/255;
}
// interpolate a byte (val) to the interval [a,b].
uint8_t lerp8by8(uint8_t val, uint8_t a, uint8_t b) {
if (a == 0 && b == 255) return val; // optimize for most common case
- uint8_t delta = b - a;
+ const uint8_t delta = b - a;
return a + scale8(val, delta);
}
+// the inverse of lerp8by8: invlerp8by8(lerp8by8(x, a, b,), a, b,) = x
+uint8_t invlerp8by8(uint8_t val, uint8_t a, uint8_t b) {
+ const uint16_t delta = b - a;
+ if (delta == 0) return 0;
+ return (static_cast(val-a)*255)/(delta);
+}
+
}; // namespace jled
diff --git a/src/jled_base.h b/src/jled_base.h
index 60c492e..1575dc1 100644
--- a/src/jled_base.h
+++ b/src/jled_base.h
@@ -46,6 +46,12 @@ uint8_t rand8();
void rand_seed(uint32_t s);
uint8_t scale8(uint8_t val, uint8_t f);
uint8_t lerp8by8(uint8_t val, uint8_t a, uint8_t b);
+uint8_t invlerp8by8(uint8_t val, uint8_t a, uint8_t b);
+
+template
+static constexpr T __max(T a, T b) {
+ return (a > b) ? a : b;
+}
// a function f(t,period,param) that calculates the LEDs brightness for a given
// point in time and the given period. param is an optionally user provided
@@ -107,15 +113,21 @@ class BreatheBrightnessEvaluator : public CloneableBrightnessEvaluator {
uint16_t duration_fade_on_;
uint16_t duration_on_;
uint16_t duration_fade_off_;
+ uint8_t from_;
+ uint8_t to_;
public:
BreatheBrightnessEvaluator() = delete;
explicit BreatheBrightnessEvaluator(uint16_t duration_fade_on,
uint16_t duration_on,
- uint16_t duration_fade_off)
+ uint16_t duration_fade_off,
+ uint8_t from = 0,
+ uint8_t to = kFullBrightness)
: duration_fade_on_(duration_fade_on),
duration_on_(duration_on),
- duration_fade_off_(duration_fade_off) {}
+ duration_fade_off_(duration_fade_off),
+ from_(from),
+ to_(to) {}
BrightnessEvaluator* clone(void* ptr) const override {
return new (ptr) BreatheBrightnessEvaluator(*this);
}
@@ -123,17 +135,21 @@ class BreatheBrightnessEvaluator : public CloneableBrightnessEvaluator {
return duration_fade_on_ + duration_on_ + duration_fade_off_;
}
uint8_t Eval(uint32_t t) const override {
+ uint8_t val = 0;
if (t < duration_fade_on_)
- return fadeon_func(t, duration_fade_on_);
+ val = fadeon_func(t, duration_fade_on_);
else if (t < duration_fade_on_ + duration_on_)
- return kFullBrightness;
+ val = kFullBrightness;
else
- return fadeon_func(Period() - t, duration_fade_off_);
+ val = fadeon_func(Period() - t, duration_fade_off_);
+ return lerp8by8(val, from_, to_);
}
uint16_t DurationFadeOn() const { return duration_fade_on_; }
uint16_t DurationFadeOff() const { return duration_fade_off_; }
uint16_t DurationOn() const { return duration_on_; }
+ uint8_t From() const { return from_; }
+ uint8_t To() const { return to_; }
};
class CandleBrightnessEvaluator : public CloneableBrightnessEvaluator {
@@ -182,12 +198,9 @@ class TJLed {
// Evaluate effect(t) and scale to be within [minBrightness, maxBrightness]
// assumes brigthness_eval_ is set as it is not checked here.
- uint8_t Eval(uint32_t t) const {
- const auto val = brightness_eval_->Eval(t);
- return lerp8by8(val, minBrightness_, maxBrightness_);
- }
+ uint8_t Eval(uint32_t t) const { return brightness_eval_->Eval(t); }
- // Write val out to "hardware", reverting signal when active-low is set.
+ // Write val out to the "hardware", inverting signal when active-low is set.
void Write(uint8_t val) {
hal_.analogWrite(IsLowActive() ? kFullBrightness - val : val);
}
@@ -259,15 +272,19 @@ class TJLed {
}
// Fade LED on
- B& FadeOn(uint16_t duration) {
- return SetBrightnessEval(new (
- brightness_eval_buf_) BreatheBrightnessEvaluator(duration, 0, 0));
+ B& FadeOn(uint16_t duration, uint8_t from = 0,
+ uint8_t to = kFullBrightness) {
+ return SetBrightnessEval(
+ new (brightness_eval_buf_)
+ BreatheBrightnessEvaluator(duration, 0, 0, from, to));
}
// Fade LED off - acutally is just inverted version of FadeOn()
- B& FadeOff(uint16_t duration) {
- return SetBrightnessEval(new (
- brightness_eval_buf_) BreatheBrightnessEvaluator(0, 0, duration));
+ B& FadeOff(uint16_t duration, uint8_t from = kFullBrightness,
+ uint8_t to = 0) {
+ return SetBrightnessEval(
+ new (brightness_eval_buf_)
+ BreatheBrightnessEvaluator(0, 0, duration, to, from));
}
// Fade from "from" to "to" with period "duration". Sets up the breathe
@@ -275,9 +292,9 @@ class TJLed {
// levels specified by "from" and "to".
B& Fade(uint8_t from, uint8_t to, uint16_t duration) {
if (from < to) {
- return FadeOn(duration).MinBrightness(from).MaxBrightness(to);
+ return FadeOn(duration, from, to);
} else {
- return FadeOff(duration).MinBrightness(to).MaxBrightness(from);
+ return FadeOff(duration, from, to);
}
}
@@ -376,7 +393,13 @@ class TJLed {
// Returns current maximum brightness level.
uint8_t MaxBrightness() const { return maxBrightness_; }
- // update brightness of LED using the given brightness evaluator
+ // update brightness of LED using the given brightness evaluator and the
+ // current time. If the optional pLast pointer is set, then the actual
+ // brightness value (if an update happened), will be returned through
+ // the pointer. The value returned will be the calculated value after
+ // min- and max-brightness scaling was applied, which is the value that
+ // is written to the output.
+ //
// (brightness) ________________
// on 255 | ¸-'
// | ¸-'
@@ -385,50 +408,60 @@ class TJLed {
// |<-delay before->|<--period-->|<-delay after-> (time)
// | func(t) |
// |<- num_repetitions times ->
- bool Update() { return Update(hal_.millis()); }
+ bool Update(int16_t* pLast = nullptr) {
+ return Update(hal_.millis(), pLast);
+ }
- bool Update(uint32_t now) {
+ bool Update(uint32_t t, int16_t* pLast = nullptr) {
if (state_ == ST_STOPPED || !brightness_eval_) return false;
if (state_ == ST_INIT) {
- time_start_ = now + delay_before_;
+ time_start_ = t + delay_before_;
state_ = ST_RUNNING;
} else {
// no need to process updates twice during one time tick.
- if (!timeChangedSinceLastUpdate(now)) return true;
+ if (!timeChangedSinceLastUpdate(t)) return true;
}
- trackLastUpdateTime(now);
+ trackLastUpdateTime(t);
- if (static_cast(now - time_start_) < 0) return true;
+ if (static_cast(t - time_start_) < 0) return true;
+
+ auto writeCur = [this](uint32_t t, int16_t* p) {
+ const auto val = lerp8by8(Eval(t), minBrightness_, maxBrightness_);
+ if (p) {
+ *p = val;
+ }
+ Write(val);
+ };
// t cycles in range [0..period+delay_after-1]
const auto period = brightness_eval_->Period();
- const auto t = (now - time_start_) % (period + delay_after_);
if (!IsForever()) {
const auto time_end = time_start_ +
static_cast(period + delay_after_) *
- num_repetitions_ - 1;
+ num_repetitions_ -
+ 1;
- if (static_cast(now - time_end) >= 0) {
+ if (static_cast(t - time_end) >= 0) {
// make sure final value of t = (period-1) is set
state_ = ST_STOPPED;
- const auto val = Eval(period - 1);
- Write(val);
+ writeCur(period - 1, pLast);
return false;
}
}
+ t = (t - time_start_) % (period + delay_after_);
if (t < period) {
state_ = ST_RUNNING;
- Write(Eval(t));
+ writeCur(t, pLast);
} else {
if (state_ == ST_RUNNING) {
// when in delay after phase, just call Write()
// once at the beginning.
state_ = ST_IN_DELAY_AFTER_PHASE;
- Write(Eval(period - 1));
+ writeCur(period - 1, pLast);
}
}
return true;
@@ -466,7 +499,11 @@ class TJLed {
// this is where the BrightnessEvaluator object will be stored using
// placment new. Set MAX_SIZE to class occupying most memory
- static constexpr auto MAX_SIZE = sizeof(CandleBrightnessEvaluator);
+ static constexpr auto MAX_SIZE =
+ __max(sizeof(CandleBrightnessEvaluator),
+ __max(sizeof(BreatheBrightnessEvaluator),
+ __max(sizeof(ConstantBrightnessEvaluator), // NOLINT
+ sizeof(BlinkBrightnessEvaluator))));
alignas(alignof(
CloneableBrightnessEvaluator)) char brightness_eval_buf_[MAX_SIZE];
diff --git a/test/test_jled.cpp b/test/test_jled.cpp
index 855b57d..b8bea35 100644
--- a/test/test_jled.cpp
+++ b/test/test_jled.cpp
@@ -43,8 +43,8 @@ class MockBrightnessEvaluator : public BrightnessEvaluator {
// expected result when a JLed object is updated: return value
// of Update() and the current brightness
-typedef std::pair UpdateResult;
-typedef std::vector UpdateResults;
+using UpdateResult = std::pair;
+using UpdateResults = std::vector;
// helper to check if a led evaluates to given sequence. TODO use a catch
// matcher
@@ -190,7 +190,7 @@ TEST_CASE("using Fade() configures BreatheBrightnessEvaluator", "[jled]") {
static void test() {
SECTION("fade with from < to") {
TestableJLed jled(1);
- jled.Fade(100, 200, 300);
+ jled.Fade(100, 200, 300); // from, to, duration
REQUIRE(dynamic_cast(
jled.brightness_eval_) != nullptr);
auto eval = dynamic_cast(
@@ -198,8 +198,8 @@ TEST_CASE("using Fade() configures BreatheBrightnessEvaluator", "[jled]") {
CHECK(300 == eval->DurationFadeOn());
CHECK(0 == eval->DurationOn());
CHECK(0 == eval->DurationFadeOff());
- CHECK(100 == jled.MinBrightness());
- CHECK(200 == jled.MaxBrightness());
+ CHECK(100 == static_cast(eval->From()));
+ CHECK(200 == static_cast(eval->To()));
}
SECTION("fade with from >= to") {
TestableJLed jled(1);
@@ -211,8 +211,8 @@ TEST_CASE("using Fade() configures BreatheBrightnessEvaluator", "[jled]") {
CHECK(0 == eval->DurationFadeOn());
CHECK(0 == eval->DurationOn());
CHECK(300 == eval->DurationFadeOff());
- CHECK(100 == jled.MinBrightness());
- CHECK(200 == jled.MaxBrightness());
+ CHECK(100 == static_cast(eval->From()));
+ CHECK(200 == static_cast(eval->To()));
}
}
};
@@ -261,7 +261,6 @@ TEST_CASE(
TEST_CASE("CandleBrightnessEvaluator simulated candle flickering", "[jled]") {
auto eval = CandleBrightnessEvaluator(7, 15, 1000);
CHECK(1000 == eval.Period());
- // TODO(jd) do further and better tests
CHECK(eval.Eval(0) > 0);
CHECK(eval.Eval(999) > 0);
}
@@ -293,13 +292,13 @@ TEST_CASE("Forever flag is set by call to Forever()", "[jled]") {
CHECK(jled.IsForever());
}
-TEST_CASE("dont evalute twice during one time tick", "[jled]") {
+TEST_CASE("dont evaluate twice during one time tick", "[jled]") {
auto eval = MockBrightnessEvaluator(ByteVec{0, 1, 2});
TestJLed jled = TestJLed(1).UserFunc(&eval);
- jled.Update(0);
+ jled.Update(0, nullptr);
CHECK(eval.Count() == 1);
- jled.Update(0);
+ jled.Update(0, nullptr);
CHECK(eval.Count() == 1);
jled.Update(1);
@@ -317,15 +316,43 @@ TEST_CASE("Handles millis overflow during effect", "[jled]") {
CHECK(jled.IsRunning());
CHECK(jled.Hal().Value() > 0);
// Set time after overflow, before effect ends
- CHECK(jled.Update(time+50));
+ CHECK(jled.Update(time + 50));
CHECK(jled.IsRunning());
CHECK(jled.Hal().Value() > 0);
// Set time after effect ends
- CHECK_FALSE(jled.Update(time+150));
+ CHECK_FALSE(jled.Update(time + 150));
CHECK_FALSE(jled.IsRunning());
CHECK(0 == jled.Hal().Value());
}
+TEST_CASE("Update returns last written value if requested", "[jled]") {
+ auto eval = MockBrightnessEvaluator(ByteVec{0, 10});
+ int16_t lastVal = -1;
+ TestJLed jled = TestJLed(1).UserFunc(&eval);
+
+ jled.Update(0, &lastVal);
+ CHECK(lastVal == 0);
+
+ jled.Update(1, &lastVal);
+ CHECK(lastVal == 10);
+}
+
+TEST_CASE("Update doesn't change last value ptr if not updated", "[jled]") {
+ auto eval = MockBrightnessEvaluator(ByteVec{0, 10});
+ int16_t lastVal = -1;
+ TestJLed jled = TestJLed(1).UserFunc(&eval).DelayBefore(1);
+
+ jled.Update(0, &lastVal);
+ CHECK(lastVal == -1);
+
+ jled.Update(5, &lastVal);
+ CHECK(lastVal == 10);
+
+ lastVal = -1;
+ jled.Update(5, &lastVal);
+ CHECK(lastVal == -1);
+}
+
TEST_CASE("Stop() stops the effect", "[jled]") {
auto eval = MockBrightnessEvaluator(ByteVec{255, 255, 255, 0});
TestJLed jled = TestJLed(10).UserFunc(&eval);
@@ -342,10 +369,11 @@ TEST_CASE("default Stop() sets the brightness to minBrightness", "[jled]") {
TestJLed jled = TestJLed(10).UserFunc(&eval).MinBrightness(50);
jled.Update();
- REQUIRE(130 == jled.Hal().Value()); // 100 scaled to [50,255]
- jled.Stop();
+ REQUIRE(130 ==
+ static_cast(jled.Hal().Value())); // 100 scaled to [50,255]
- CHECK(50 == jled.Hal().Value());
+ jled.Stop();
+ CHECK(50 == static_cast(jled.Hal().Value()));
}
TEST_CASE("Stop(FULL_OFF) sets the brightness to 0", "[jled]") {
@@ -353,10 +381,11 @@ TEST_CASE("Stop(FULL_OFF) sets the brightness to 0", "[jled]") {
TestJLed jled = TestJLed(10).UserFunc(&eval).MinBrightness(50);
jled.Update();
- REQUIRE(130 == jled.Hal().Value()); // 100 scaled to [50,255]
- jled.Stop(TestJLed::eStopMode::FULL_OFF);
+ REQUIRE(130 ==
+ static_cast(jled.Hal().Value())); // 100 scaled to [50,255]
- CHECK(0 == jled.Hal().Value());
+ jled.Stop(TestJLed::eStopMode::FULL_OFF);
+ CHECK(0 == static_cast(jled.Hal().Value()));
}
TEST_CASE("Stop(KEEP_CURRENT) keeps the last brightness level", "[jled]") {
@@ -364,10 +393,11 @@ TEST_CASE("Stop(KEEP_CURRENT) keeps the last brightness level", "[jled]") {
TestJLed jled = TestJLed(10).UserFunc(&eval).MinBrightness(50);
jled.Update();
- REQUIRE(130 == jled.Hal().Value()); // 100 scaled to [50,255]
- jled.Stop(TestJLed::eStopMode::KEEP_CURRENT);
+ REQUIRE(130 ==
+ static_cast(jled.Hal().Value())); // 100 scaled to [50,255]
- CHECK(130 == jled.Hal().Value());
+ jled.Stop(TestJLed::eStopMode::KEEP_CURRENT);
+ CHECK(130 == static_cast(jled.Hal().Value()));
}
TEST_CASE("LowActive() inverts signal", "[jled]") {
@@ -376,7 +406,7 @@ TEST_CASE("LowActive() inverts signal", "[jled]") {
CHECK(jled.IsLowActive());
- jled.Update(0);
+ jled.Update(0, nullptr);
CHECK(255 == jled.Hal().Value());
jled.Update(1);
@@ -442,7 +472,7 @@ TEST_CASE("Update returns true while updating, else false", "[jled]") {
TestJLed jled = TestJLed(10).UserFunc(&eval);
// Update returns FALSE on last step and beyond, else TRUE
- CHECK(jled.Update(0));
+ CHECK(jled.Update(0, nullptr));
// when effect is done, we expect still false to be returned
CHECK_FALSE(jled.Update(1));
@@ -511,13 +541,14 @@ TEST_CASE(
using TestJLed::TestJLed;
static void test() {
TestableJLed jled(1);
-
auto eval = MockBrightnessEvaluator(ByteVec{0, 128, 255});
jled.UserFunc(&eval).MinBrightness(100).MaxBrightness(200);
- CHECK(100 == jled.Eval(0));
- CHECK(150 == jled.Eval(1));
- CHECK(200 == jled.Eval(2));
+ jled.Update(0, nullptr);
+ CHECK(100 == jled.Hal().Value());
+
+ jled.Update(2, nullptr);
+ CHECK(200 == jled.Hal().Value());
}
};
TestableJLed::test();
@@ -574,3 +605,11 @@ TEST_CASE("lerp8by8 interpolates a byte into the given interval",
CHECK(255 == (int)(jled::lerp8by8(255, 100, 255)));
CHECK(200 == (int)(jled::lerp8by8(255, 100, 200)));
}
+
+TEST_CASE("invlerp8by8 is the inverse of lerp8by8", "[invlerp8by8]") {
+ CHECK(0 == (int)(jled::invlerp8by8(0, 0, 255)));
+ CHECK(255 == (int)(jled::invlerp8by8(255, 0, 255)));
+
+ CHECK(0 == (int)(jled::invlerp8by8(100, 100, 200)));
+ CHECK(255 == (int)(jled::invlerp8by8(200, 100, 200)));
+}
|