From f245a903db3ff4a693f2874957cd9a671c5e2331 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Sat, 26 Aug 2023 19:42:04 -0600 Subject: added "smooth steps" a.k.a. "soft start", to make brightness steps smoother (also made ramp extras config menu count its own steps) --- spaghetti-monster/anduril/anduril-manual.txt | 9 +++++ spaghetti-monster/anduril/anduril.c | 14 ++++++++ spaghetti-monster/anduril/config-default.h | 8 +++++ spaghetti-monster/anduril/load-save-config-fsm.h | 5 +++ spaghetti-monster/anduril/load-save-config.h | 5 +++ spaghetti-monster/anduril/off-mode.c | 30 +++++++++++++--- spaghetti-monster/anduril/ramp-mode.c | 44 +++++++++++++++++------ spaghetti-monster/anduril/ramp-mode.h | 21 ++++++++++- spaghetti-monster/anduril/smooth-steps.c | 45 ++++++++++++++++++++++++ spaghetti-monster/anduril/smooth-steps.h | 19 ++++++++++ 10 files changed, 184 insertions(+), 16 deletions(-) create mode 100644 spaghetti-monster/anduril/smooth-steps.c create mode 100644 spaghetti-monster/anduril/smooth-steps.h (limited to 'spaghetti-monster') diff --git a/spaghetti-monster/anduril/anduril-manual.txt b/spaghetti-monster/anduril/anduril-manual.txt index 1d5bc46..d47cc57 100644 --- a/spaghetti-monster/anduril/anduril-manual.txt +++ b/spaghetti-monster/anduril/anduril-manual.txt @@ -222,6 +222,9 @@ While the light is on, a few actions are available: 2: Anduril 2 style. Ramp -> 2C goes to ceiling, or goes to full power if user ramped up to ceiling first. This value also affects momentary turbo in Ramp and Off modes. + - Item 5: Configure "smooth steps". + 0: Disable smooth steps. + 1: Enable smooth steps. Memory determines which brightness level the light goes to with 1 click from off. There are three types of brightness memory to choose from: @@ -252,6 +255,11 @@ To choose a memory style, set the configuration accordingly: manual on zero hybrid on non-zero +If "smooth steps" is enabled, the stepped ramp uses a smooth animation +between steps, and turning the light on/off has the edges smoothed off +too. With "smooth steps" turned off, these brightness changes are +immediate. + Sunset Timer ------------ @@ -904,6 +912,7 @@ Ramp Full 10H Ramp Extras config menu: 2: set manual mem timeout 3: ramp after moon or not 4: advanced UI turbo style + 5: smooth steps Multi-channel lights only: Any Any 3C Next channel mode (i.e. next color mode) diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c index 6399ef6..e46eeaf 100644 --- a/spaghetti-monster/anduril/anduril.c +++ b/spaghetti-monster/anduril/anduril.c @@ -142,6 +142,10 @@ #include "sos-mode.h" #endif +#ifdef USE_SMOOTH_STEPS +#include "smooth-steps.h" +#endif + // this should be last, so other headers have a chance to declare values #include "load-save-config.h" @@ -205,6 +209,10 @@ #include "sos-mode.c" #endif +#ifdef USE_SMOOTH_STEPS +#include "smooth-steps.c" +#endif + // runs one time at boot, when power is connected void setup() { @@ -335,6 +343,12 @@ void loop() { } #endif + #ifdef USE_SMOOTH_STEPS + else if (cfg.smooth_steps_style && smooth_steps_in_progress) { + smooth_steps_iter(); + } + #endif + #ifdef USE_IDLE_MODE else { // doze until next clock tick diff --git a/spaghetti-monster/anduril/config-default.h b/spaghetti-monster/anduril/config-default.h index 8f07e47..b66a645 100644 --- a/spaghetti-monster/anduril/config-default.h +++ b/spaghetti-monster/anduril/config-default.h @@ -187,3 +187,11 @@ #define USE_STEPPED_TINT_RAMPING #define DEFAULT_TINT_RAMP_STYLE 0 // smooth +// Use "smooth steps" to soften on/off and step changes +// on MCUs with enough room for extra stuff like this +#if (ATTINY==1616) || (ATTINY==1634) +#define USE_SMOOTH_STEPS +#endif +// 0 = none, 1 = smooth, 2+ = undefined +#define DEFAULT_SMOOTH_STEPS_STYLE 1 + diff --git a/spaghetti-monster/anduril/load-save-config-fsm.h b/spaghetti-monster/anduril/load-save-config-fsm.h index 7bf87f4..0a9cabd 100644 --- a/spaghetti-monster/anduril/load-save-config-fsm.h +++ b/spaghetti-monster/anduril/load-save-config-fsm.h @@ -67,6 +67,11 @@ typedef struct Config { #endif #endif + ///// Smooth animation between steps, and for on/off + #ifdef USE_SMOOTH_STEPS + uint8_t smooth_steps_style; + #endif + ///// strobe / blinky mode settings #ifdef USE_STROBE_STATE uint8_t strobe_type; diff --git a/spaghetti-monster/anduril/load-save-config.h b/spaghetti-monster/anduril/load-save-config.h index 2dfa8c9..c70bb2b 100644 --- a/spaghetti-monster/anduril/load-save-config.h +++ b/spaghetti-monster/anduril/load-save-config.h @@ -94,6 +94,11 @@ Config cfg = { #endif #endif + ///// Smooth animation between steps, and for on/off + #ifdef USE_SMOOTH_STEPS + .smooth_steps_style = DEFAULT_SMOOTH_STEPS_STYLE, + #endif + ///// strobe / blinky mode settings #ifdef USE_STROBE_STATE diff --git a/spaghetti-monster/anduril/off-mode.c b/spaghetti-monster/anduril/off-mode.c index a484f06..af09d70 100644 --- a/spaghetti-monster/anduril/off-mode.c +++ b/spaghetti-monster/anduril/off-mode.c @@ -14,6 +14,12 @@ uint8_t off_state(Event event, uint16_t arg) { // turn emitter off when entering state if (event == EV_enter_state) { + #ifdef USE_SMOOTH_STEPS + if (cfg.smooth_steps_style && actual_level) { + set_level_smooth(0, 8); + arg = 1; // don't go to sleep immediately + } else + #endif set_level(0); ticks_since_on = 0; #if NUM_CHANNEL_MODES > 1 @@ -24,7 +30,8 @@ uint8_t off_state(Event event, uint16_t arg) { // redundant, sleep tick does the same thing //indicator_led_update(cfg.indicator_led_mode & 0x03, 0); #elif defined(USE_AUX_RGB_LEDS) - rgb_led_update(cfg.rgb_led_off_mode, 0); + // redundant, sleep tick does the same thing + //rgb_led_update(cfg.rgb_led_off_mode, 0); #endif #ifdef USE_SUNSET_TIMER sunset_timer = 0; // needs a reset in case previous timer was aborted @@ -37,13 +44,18 @@ uint8_t off_state(Event event, uint16_t arg) { // go back to sleep eventually if we got bumped but didn't leave "off" state else if (event == EV_tick) { - if (arg > HOLD_TIMEOUT) { + if (arg > HOLD_TIMEOUT + #ifdef USE_SMOOTH_STEPS + && (! smooth_steps_in_progress) + #endif + ) { go_to_standby = 1; #ifdef USE_INDICATOR_LED // redundant, sleep tick does the same thing //indicator_led_update(cfg.indicator_led_mode & 0x03, arg); #elif defined(USE_AUX_RGB_LEDS) - rgb_led_update(cfg.rgb_led_off_mode, arg); + // redundant, sleep tick does the same thing + //rgb_led_update(cfg.rgb_led_off_mode, arg); #endif } return EVENT_HANDLED; @@ -127,6 +139,11 @@ uint8_t off_state(Event event, uint16_t arg) { manual_memory_restore(); } #endif + #ifdef USE_SMOOTH_STEPS + if (cfg.smooth_steps_style) + set_level_smooth(nearest_level(memorized_level), 8); + else + #endif set_level(nearest_level(memorized_level)); return EVENT_HANDLED; } @@ -135,8 +152,7 @@ uint8_t off_state(Event event, uint16_t arg) { // 1 click: regular mode else if (event == EV_1click) { #if (B_TIMING_ON != B_TIMEOUT_T) - // brightness was already set; reuse previous value - set_state(steady_state, actual_level); + set_state(steady_state, memorized_level); #else // FIXME: B_TIMEOUT_T breaks manual_memory and manual_memory_timer // (need to duplicate manual mem logic here, probably) @@ -186,6 +202,10 @@ uint8_t off_state(Event event, uint16_t arg) { // 3 clicks (initial press): off, to prep for later events else if (event == EV_click3_press) { + #ifdef USE_SMOOTH_STEPS + // immediately cancel any animations in progress + smooth_steps_in_progress = 0; + #endif set_level(0); return EVENT_HANDLED; } diff --git a/spaghetti-monster/anduril/ramp-mode.c b/spaghetti-monster/anduril/ramp-mode.c index f21f599..c4c995f 100644 --- a/spaghetti-monster/anduril/ramp-mode.c +++ b/spaghetti-monster/anduril/ramp-mode.c @@ -10,6 +10,10 @@ #include "sunset-timer.h" #endif +#ifdef USE_SMOOTH_STEPS +#include "smooth-steps.h" +#endif + uint8_t steady_state(Event event, uint16_t arg) { static int8_t ramp_direction = 1; @@ -554,21 +558,25 @@ uint8_t simple_ui_config_state(Event event, uint16_t arg) { #ifdef USE_RAMP_EXTRAS_CONFIG void ramp_extras_config_save(uint8_t step, uint8_t value) { // item 1: disable manual memory, go back to automatic - if (1 == step) { cfg.manual_memory = 0; } + if (manual_memory_config_step == step) { + cfg.manual_memory = 0; + } #ifdef USE_MANUAL_MEMORY_TIMER // item 2: set manual memory timer duration // FIXME: should be limited to (65535 / SLEEP_TICKS_PER_MINUTE) // to avoid overflows or impossibly long timeouts // (by default, the effective limit is 145, but it allows up to 255) - else if (2 == step) { cfg.manual_memory_timer = value; } + else if (manual_memory_timer_config_step == step) { + cfg.manual_memory_timer = value; + } #endif #ifdef USE_RAMP_AFTER_MOON_CONFIG // item 3: ramp up after hold-from-off for moon? // 0 = yes, ramp after moon // 1+ = no, stay at moon - else if (3 == step) { + else if (dont_ramp_after_moon_config_step == step) { cfg.dont_ramp_after_moon = value; } #endif @@ -577,14 +585,22 @@ void ramp_extras_config_save(uint8_t step, uint8_t value) { // item 4: Anduril 1 2C turbo, or Anduril 2 2C ceiling? // 1 = Anduril 1, 2C turbo // 2+ = Anduril 2, 2C ceiling - else if (4 == step) { + else if (ramp_2c_style_config_step == step) { cfg.ramp_2c_style = value; } #endif + + #ifdef USE_SMOOTH_STEPS + else if (smooth_steps_style_config_step == step) { + cfg.smooth_steps_style = value; + } + #endif } uint8_t ramp_extras_config_state(Event event, uint16_t arg) { - return config_state_base(event, arg, 4, ramp_extras_config_save); + return config_state_base(event, arg, + ramp_extras_config_num_steps - 1, + ramp_extras_config_save); } #endif @@ -592,16 +608,17 @@ uint8_t ramp_extras_config_state(Event event, uint16_t arg) { void globals_config_save(uint8_t step, uint8_t value) { if (0) {} #if defined(USE_CHANNEL_MODE_ARGS) && defined(USE_STEPPED_TINT_RAMPING) - else if (step == 1+tint_style_config_step) { cfg.tint_ramp_style = value; } + else if (step == tint_style_config_step) { cfg.tint_ramp_style = value; } #endif #ifdef USE_JUMP_START - else if (step == 1+jump_start_config_step) { cfg.jump_start_level = value; } + else if (step == jump_start_config_step) { cfg.jump_start_level = value; } #endif } uint8_t globals_config_state(Event event, uint16_t arg) { - // TODO: set number of steps based on how many configurable options - return config_state_base(event, arg, globals_config_num_steps, globals_config_save); + return config_state_base(event, arg, + globals_config_num_steps - 1, + globals_config_save); } #endif @@ -657,9 +674,16 @@ void ramp_update_config() { ramp_ceil = cfg.ramp_ceils[which]; } -#ifdef USE_THERMAL_REGULATION +#if defined(USE_THERMAL_REGULATION) || defined(USE_SMOOTH_STEPS) void set_level_and_therm_target(uint8_t level) { + #ifdef USE_THERMAL_REGULATION target_level = level; + #endif + #ifdef USE_SMOOTH_STEPS + if (cfg.smooth_steps_style && cfg.ramp_style) + set_level_smooth(level, 4); + else + #endif set_level(level); } #else diff --git a/spaghetti-monster/anduril/ramp-mode.h b/spaghetti-monster/anduril/ramp-mode.h index 20986cc..21e2149 100644 --- a/spaghetti-monster/anduril/ramp-mode.h +++ b/spaghetti-monster/anduril/ramp-mode.h @@ -186,10 +186,29 @@ uint8_t sunset_timer_orig_level = 0; void reset_sunset_timer(); #endif +#ifdef USE_RAMP_EXTRAS_CONFIG +typedef enum { + manual_memory_config_step = 1, + #ifdef USE_MANUAL_MEMORY_TIMER + manual_memory_timer_config_step, + #endif + #ifdef USE_RAMP_AFTER_MOON_CONFIG + dont_ramp_after_moon_config_step, + #endif + #ifdef USE_2C_STYLE_CONFIG + ramp_2c_style_config_step, + #endif + #ifdef USE_SMOOTH_STEPS + smooth_steps_style_config_step, + #endif + ramp_extras_config_num_steps +} ramp_extras_config_steps_e; +#endif + #ifdef USE_GLOBALS_CONFIG typedef enum { #if defined(USE_CHANNEL_MODE_ARGS) && defined(USE_STEPPED_TINT_RAMPING) - tint_style_config_step, + tint_style_config_step = 1, #endif #ifdef USE_JUMP_START jump_start_config_step, diff --git a/spaghetti-monster/anduril/smooth-steps.c b/spaghetti-monster/anduril/smooth-steps.c new file mode 100644 index 0000000..9a09228 --- /dev/null +++ b/spaghetti-monster/anduril/smooth-steps.c @@ -0,0 +1,45 @@ +// smooth-steps.c: Smooth step adjustments for Anduril. +// Copyright (C) 2023 Selene ToyKeeper +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "smooth-steps.h" + +#ifdef USE_SMOOTH_STEPS + +// one iteration of main loop() +void smooth_steps_iter() { + if (actual_level == smooth_steps_target) { + smooth_steps_in_progress = 0; + // restore prev_level when animation ends + prev_level = smooth_steps_start; + return; + } + + if (actual_level < smooth_steps_target) { + uint8_t diff = smooth_steps_target - actual_level; + uint8_t this = diff / smooth_steps_speed; + if (!this) this = 1; + set_level(actual_level + this); + } else if (actual_level > smooth_steps_target) { + uint8_t diff = actual_level - smooth_steps_target; + uint8_t this = diff / smooth_steps_speed; + if (!this) this = 1; + set_level(actual_level - this); + } + // TODO: maybe change the delay based on the speed var? + nice_delay_ms(10); +} + +void set_level_smooth(uint8_t level, uint8_t speed) { + smooth_steps_target = level; + // TODO: maybe speed should be a desired total time for the animation? + smooth_steps_speed = speed; + smooth_steps_in_progress = 1; + // for setting prev_level after animation ends + smooth_steps_start = actual_level; +} + +#endif + diff --git a/spaghetti-monster/anduril/smooth-steps.h b/spaghetti-monster/anduril/smooth-steps.h new file mode 100644 index 0000000..a553af2 --- /dev/null +++ b/spaghetti-monster/anduril/smooth-steps.h @@ -0,0 +1,19 @@ +// smooth-steps.h: Smooth step adjustments for Anduril. +// Copyright (C) 2023 Selene ToyKeeper +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#ifdef USE_SMOOTH_STEPS + +uint8_t smooth_steps_start; +uint8_t smooth_steps_target; +uint8_t smooth_steps_in_progress; +uint8_t smooth_steps_speed; + +void smooth_steps_iter(); + +void set_level_smooth(uint8_t level, uint8_t speed); + +#endif + -- cgit v1.2.3