aboutsummaryrefslogtreecommitdiff
path: root/ui/werner
diff options
context:
space:
mode:
Diffstat (limited to 'ui/werner')
-rw-r--r--ui/werner/Makefile7
-rw-r--r--ui/werner/README56
-rwxr-xr-xui/werner/build-all.sh15
-rw-r--r--ui/werner/werner.c715
4 files changed, 793 insertions, 0 deletions
diff --git a/ui/werner/Makefile b/ui/werner/Makefile
new file mode 100644
index 0000000..21d85f7
--- /dev/null
+++ b/ui/werner/Makefile
@@ -0,0 +1,7 @@
+all:
+ ./build-all.sh
+
+clean:
+ rm -f cfg-*.h *.hex *~ *.elf *.o
+
+.phony: clean
diff --git a/ui/werner/README b/ui/werner/README
new file mode 100644
index 0000000..5fe392d
--- /dev/null
+++ b/ui/werner/README
@@ -0,0 +1,56 @@
+This is a Werner-style interface for dual-switch lights
+(e-switch + clicky switch). What that means is:
+
+While the clicky switch is off:
+
+ - Click the clicky switch: Turn on, at the last-used level. The clicky
+ switch works as a momentary mode.
+
+ - Click the clicky switch while holding the e-switch: Go into sort of a
+ utility mode.
+
+While on, in a normal steady mode:
+
+ - Click the clicky switch: Turn off.
+
+ - Click the e-switch: Brighter. One step per click.
+
+ - Hold the e-switch: Dimmer. Keep holding to go down multiple steps.
+
+While in standby, in utility mode:
+
+ - Click the e-switch: Turn on.
+
+ - Hold the e-switch: Turn on at lowest level.
+
+ - Double-click the e-switch: Turn on at highest level.
+
+ - Triple-click the e-switch: Battery check mode.
+
+ - Quad-click the e-switch: Ramp config mode.
+
+While in battery check mode:
+
+ - Click either switch: Turn off.
+
+ - Double-click the e-switch: Go to temperature check mode.
+
+While in temperature check mode:
+
+ - Click either switch: Turn off.
+
+ - Double-click the e-switch: Go to battery check mode.
+
+ - Quad-click the e-switch: Go to thermal config mode.
+
+Ramp config mode and thermal config mode work the same as in Anduril or
+RampingIOS V3. The options are:
+
+ - Ramp config mode:
+ 1. Floor level.
+ 2. Ceiling level.
+ 3. Number of steps.
+
+ - Thermal config mode:
+ 1. Calibrate sensor by entering current temperature in C.
+ 2. Set temperature limit to 30 C + N clicks.
diff --git a/ui/werner/build-all.sh b/ui/werner/build-all.sh
new file mode 100755
index 0000000..b114101
--- /dev/null
+++ b/ui/werner/build-all.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+cp -av ../anduril/cfg*.h .
+
+UI=werner
+
+for TARGET in cfg-*.h ; do
+ NAME=$(echo "$TARGET" | perl -ne '/cfg-(.*).h/ && print "$1\n";')
+ echo "===== $NAME ====="
+ ATTINY=$(grep 'ATTINY:' $TARGET | awk '{ print $3 }')
+ if [ -z "$ATTINY" ]; then ATTINY=85 ; fi
+ echo ../../../bin/build.sh $ATTINY "$UI" "-DCONFIGFILE=${TARGET}"
+ ../../../bin/build.sh $ATTINY "$UI" "-DCONFIGFILE=${TARGET}"
+ mv -f "$UI".hex "$UI".$NAME.hex
+done
diff --git a/ui/werner/werner.c b/ui/werner/werner.c
new file mode 100644
index 0000000..f3241ee
--- /dev/null
+++ b/ui/werner/werner.c
@@ -0,0 +1,715 @@
+/*
+ * Werner: Werner-style dual-switch UI for SpaghettiMonster.
+ * Side click to go up, side hold to go down, tail click for on/off.
+ *
+ * Copyright (C) 2018 Selene ToyKeeper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/********* User-configurable options *********/
+// Physical driver type (uncomment one of the following or define it at the gcc command line)
+//#define CONFIGFILE cfg-emisar-d4.h
+
+#define USE_LVP // FIXME: won't build when this option is turned off
+
+// parameters for this defined below or per-driver
+#define USE_THERMAL_REGULATION
+#define DEFAULT_THERM_CEIL 45 // try not to get hotter than this
+
+// battery readout style (pick one)
+#define BATTCHECK_VpT
+//#define BATTCHECK_8bars // FIXME: breaks build
+//#define BATTCHECK_4bars // FIXME: breaks build
+
+// cut clock speed at very low modes for better efficiency
+// (defined here so config files can override it)
+#define USE_DYNAMIC_UNDERCLOCKING
+
+/***** specific settings for known driver types *****/
+#ifdef CONFIGFILE
+#include "tk.h"
+#include incfile(CONFIGFILE)
+#else
+#error You need to define CONFIGFILE
+#endif
+
+// thermal properties, if not defined per-driver
+#ifndef MIN_THERM_STEPDOWN
+#define MIN_THERM_STEPDOWN MAX_1x7135 // lowest value it'll step down to
+#endif
+#ifndef THERM_FASTER_LEVEL
+ #ifdef MAX_Nx7135
+ #define THERM_FASTER_LEVEL MAX_Nx7135 // throttle back faster when high
+ #else
+ #define THERM_FASTER_LEVEL (RAMP_SIZE*4/5) // throttle back faster when high
+ #endif
+#endif
+#ifdef USE_THERMAL_REGULATION
+#define USE_SET_LEVEL_GRADUALLY // isn't used except for thermal adjustments
+#endif
+
+
+/********* Configure SpaghettiMonster *********/
+#define USE_DELAY_ZERO
+#define USE_RAMPING
+#define RAMP_LENGTH 150 // default, if not overridden in a driver cfg file
+#define USE_BATTCHECK
+#define USE_IDLE_MODE // reduce power use while awake and no tasks are pending
+
+// auto-detect how many eeprom bytes
+#define USE_EEPROM
+#ifdef USE_THERMAL_REGULATION
+#define EEPROM_BYTES 5
+#else
+#define EEPROM_BYTES 3
+#endif
+// for mode memory on tail switch
+#define USE_EEPROM_WL
+#define EEPROM_WL_BYTES 1
+
+#include "spaghetti-monster.h"
+
+
+// FSM states
+uint8_t off_state(Event event, uint16_t arg);
+// simple numeric entry config menu
+uint8_t config_state_base(Event event, uint16_t arg,
+ uint8_t num_config_steps,
+ void (*savefunc)());
+#define MAX_CONFIG_VALUES 3
+uint8_t config_state_values[MAX_CONFIG_VALUES];
+// ramping mode and its related config mode
+uint8_t steady_state(Event event, uint16_t arg);
+uint8_t ramp_config_state(Event event, uint16_t arg);
+#ifdef USE_BATTCHECK
+uint8_t battcheck_state(Event event, uint16_t arg);
+#endif
+#ifdef USE_THERMAL_REGULATION
+uint8_t tempcheck_state(Event event, uint16_t arg);
+uint8_t thermal_config_state(Event event, uint16_t arg);
+#endif
+
+// general helper function for config modes
+uint8_t number_entry_state(Event event, uint16_t arg);
+// return value from number_entry_state()
+volatile uint8_t number_entry_value;
+
+void blink_confirm(uint8_t num);
+
+// remember stuff even after battery was changed
+void load_config();
+void save_config();
+void save_config_wl();
+
+// default ramp options if not overridden earlier per-driver
+#ifndef RAMP_DISCRETE_FLOOR
+ #define RAMP_DISCRETE_FLOOR 1
+#endif
+#ifndef RAMP_DISCRETE_CEIL
+ #define RAMP_DISCRETE_CEIL RAMP_SIZE
+#endif
+#ifndef RAMP_DISCRETE_STEPS
+ #define RAMP_DISCRETE_STEPS 7
+#endif
+
+// brightness control
+uint8_t memorized_level = MAX_1x7135;
+// smooth vs discrete ramping
+volatile uint8_t ramp_discrete_floor = RAMP_DISCRETE_FLOOR;
+volatile uint8_t ramp_discrete_ceil = RAMP_DISCRETE_CEIL;
+volatile uint8_t ramp_discrete_steps = RAMP_DISCRETE_STEPS;
+uint8_t ramp_discrete_step_size; // don't set this
+
+// calculate the nearest ramp level which would be valid at the moment
+// (is a no-op for smooth ramp, but limits discrete ramp to only the
+// correct levels for the user's config)
+uint8_t nearest_level(int16_t target);
+
+#ifdef USE_THERMAL_REGULATION
+// brightness before thermal step-down
+uint8_t target_level = 0;
+#endif
+
+
+uint8_t off_state(Event event, uint16_t arg) {
+ // turn emitter off when entering state
+ if ((event == EV_enter_state) || (event == EV_reenter_state)) {
+ // let the user know the power is connected
+ blink_confirm(1);
+ // but otherwise stay off
+ set_level(0);
+ // sleep while off (lower power use)
+ go_to_standby = 1;
+ return EVENT_HANDLED;
+ }
+ // go back to sleep eventually if we got bumped but didn't leave "off" state
+ else if (event == EV_tick) {
+ if (arg > TICKS_PER_SECOND*2) {
+ go_to_standby = 1;
+ }
+ return EVENT_HANDLED;
+ }
+ // hold (initially): go to lowest level, but allow abort for regular click
+ else if (event == EV_click1_press) {
+ set_level(nearest_level(1));
+ return EVENT_HANDLED;
+ }
+ // hold: go to lowest level
+ else if (event == EV_click1_hold) {
+ // don't start ramping immediately;
+ // give the user time to release at moon level
+ if (arg >= HOLD_TIMEOUT) {
+ set_state(steady_state, 1);
+ }
+ return EVENT_HANDLED;
+ }
+ // hold, release quickly: go to lowest level
+ else if (event == EV_click1_hold_release) {
+ set_state(steady_state, 1);
+ return EVENT_HANDLED;
+ }
+ // 1 click (before timeout): go to memorized level, but allow abort for double click
+ else if (event == EV_click1_release) {
+ set_level(nearest_level(memorized_level));
+ return EVENT_HANDLED;
+ }
+ // 1 click: regular mode
+ else if (event == EV_1click) {
+ set_state(steady_state, memorized_level);
+ return EVENT_HANDLED;
+ }
+ // 2 clicks (initial press): off, to prep for later events
+ else if (event == EV_click2_press) {
+ set_level(0);
+ return EVENT_HANDLED;
+ }
+ // click, hold: go to highest level (for ramping down)
+ else if (event == EV_click2_hold) {
+ set_state(steady_state, MAX_LEVEL);
+ return EVENT_HANDLED;
+ }
+ // 2 clicks: highest mode
+ else if (event == EV_2clicks) {
+ set_state(steady_state, nearest_level(MAX_LEVEL));
+ return EVENT_HANDLED;
+ }
+ #ifdef USE_BATTCHECK
+ // 3 clicks: battcheck mode / blinky mode group
+ else if (event == EV_3clicks) {
+ set_state(battcheck_state, 0);
+ return EVENT_HANDLED;
+ }
+ #endif
+ // 4 clicks: configure ramp
+ else if (event == EV_4clicks) {
+ push_state(ramp_config_state, 0);
+ return EVENT_HANDLED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+
+uint8_t steady_state(Event event, uint16_t arg) {
+ uint8_t mode_min = ramp_discrete_floor;
+ uint8_t mode_max = ramp_discrete_ceil;
+ uint8_t ramp_step_size = ramp_discrete_step_size;
+
+ // turn LED on when we first enter the mode
+ if ((event == EV_enter_state) || (event == EV_reenter_state)) {
+ // if we just got back from config mode, go back to memorized level
+ if (event == EV_reenter_state) {
+ arg = memorized_level;
+ }
+ // remember this level, unless it's moon or turbo
+ if ((arg > mode_min) && (arg < mode_max))
+ memorized_level = arg;
+ // use the requested level even if not memorized
+ #ifdef USE_THERMAL_REGULATION
+ target_level = arg;
+ #endif
+ set_level(nearest_level(arg));
+ return EVENT_HANDLED;
+ }
+ // click: brighter
+ else if (event == EV_click1_release) {
+ memorized_level = nearest_level((int16_t)actual_level + ramp_step_size);
+ #ifdef USE_THERMAL_REGULATION
+ target_level = memorized_level;
+ #endif
+ set_level(memorized_level);
+ // make sure next click will respond quickly
+ empty_event_sequence();
+ // remember mode for later
+ save_config_wl();
+ return EVENT_HANDLED;
+ }
+ // hold: dimmer
+ else if (event == EV_click1_hold) {
+ // ramp slower in discrete mode
+ if (arg % HOLD_TIMEOUT != 0) {
+ return EVENT_HANDLED;
+ }
+ memorized_level = nearest_level((int16_t)actual_level - ramp_step_size);
+ #ifdef USE_THERMAL_REGULATION
+ target_level = memorized_level;
+ #endif
+ set_level(memorized_level);
+ return EVENT_HANDLED;
+ }
+ // reverse ramp direction on hold release
+ else if (event == EV_click1_hold_release) {
+ save_config_wl();
+ return EVENT_HANDLED;
+ }
+ #if defined(USE_SET_LEVEL_GRADUALLY)
+ // gradual thermal regulation
+ else if (event == EV_tick) {
+ #ifdef USE_SET_LEVEL_GRADUALLY
+ // make thermal adjustment speed scale with magnitude
+ if ((arg & 1) && (actual_level < THERM_FASTER_LEVEL)) {
+ return EVENT_HANDLED; // adjust slower when not a high mode
+ }
+ #ifdef THERM_HARD_TURBO_DROP
+ else if ((! (actual_level < THERM_FASTER_LEVEL))
+ && (actual_level > gradual_target)) {
+ gradual_tick();
+ }
+ else {
+ #endif
+ // [int(62*4 / (x**0.95)) for x in (1,2,4,8,16,32,64,128)]
+ uint8_t intervals[] = {248, 128, 66, 34, 17, 9, 4, 2};
+ uint8_t diff;
+ static uint8_t ticks_since_adjust = 0;
+ ticks_since_adjust ++;
+ if (gradual_target > actual_level) diff = gradual_target - actual_level;
+ else {
+ diff = actual_level - gradual_target;
+ }
+ uint8_t magnitude = 0;
+ #ifndef THERM_HARD_TURBO_DROP
+ // if we're on a really high mode, drop faster
+ if (actual_level >= THERM_FASTER_LEVEL) { magnitude ++; }
+ #endif
+ while (diff) {
+ magnitude ++;
+ diff >>= 1;
+ }
+ uint8_t ticks_per_adjust = intervals[magnitude];
+ if (ticks_since_adjust > ticks_per_adjust)
+ {
+ gradual_tick();
+ ticks_since_adjust = 0;
+ }
+ //if (!(arg % ticks_per_adjust)) gradual_tick();
+ #ifdef THERM_HARD_TURBO_DROP
+ }
+ #endif
+ #endif
+ return EVENT_HANDLED;
+ }
+ #endif
+ #ifdef USE_THERMAL_REGULATION
+ // overheating: drop by an amount proportional to how far we are above the ceiling
+ else if (event == EV_temperature_high) {
+ #ifdef THERM_HARD_TURBO_DROP
+ if (actual_level > THERM_FASTER_LEVEL) {
+ #ifdef USE_SET_LEVEL_GRADUALLY
+ set_level_gradually(THERM_FASTER_LEVEL);
+ #else
+ set_level(THERM_FASTER_LEVEL);
+ #endif
+ } else
+ #endif
+ if (actual_level > MIN_THERM_STEPDOWN) {
+ int16_t stepdown = actual_level - arg;
+ if (stepdown < MIN_THERM_STEPDOWN) stepdown = MIN_THERM_STEPDOWN;
+ else if (stepdown > MAX_LEVEL) stepdown = MAX_LEVEL;
+ #ifdef USE_SET_LEVEL_GRADUALLY
+ set_level_gradually(stepdown);
+ #else
+ set_level(stepdown);
+ #endif
+ }
+ return EVENT_HANDLED;
+ }
+ // underheating: increase slowly if we're lower than the target
+ // (proportional to how low we are)
+ else if (event == EV_temperature_low) {
+ if (actual_level < target_level) {
+ //int16_t stepup = actual_level + (arg>>1);
+ int16_t stepup = actual_level + arg;
+ if (stepup > target_level) stepup = target_level;
+ else if (stepup < MIN_THERM_STEPDOWN) stepup = MIN_THERM_STEPDOWN;
+ #ifdef USE_SET_LEVEL_GRADUALLY
+ set_level_gradually(stepup);
+ #else
+ set_level(stepup);
+ #endif
+ }
+ return EVENT_HANDLED;
+ }
+ #endif
+ return EVENT_NOT_HANDLED;
+}
+
+
+#ifdef USE_BATTCHECK
+uint8_t battcheck_state(Event event, uint16_t arg) {
+ // 1 click: off
+ if (event == EV_1click) {
+ set_state(off_state, 0);
+ return EVENT_HANDLED;
+ }
+ #ifdef USE_THERMAL_REGULATION
+ // 2 clicks: tempcheck mode
+ else if (event == EV_2clicks) {
+ blink_confirm(2);
+ set_state(tempcheck_state, 0);
+ return EVENT_HANDLED;
+ }
+ #endif
+ return EVENT_NOT_HANDLED;
+}
+#endif
+
+#ifdef USE_THERMAL_REGULATION
+uint8_t tempcheck_state(Event event, uint16_t arg) {
+ // 1 click: off
+ if (event == EV_1click) {
+ set_state(off_state, 0);
+ return EVENT_HANDLED;
+ }
+ // 2 clicks: battcheck mode
+ else if (event == EV_2clicks) {
+ blink_confirm(1);
+ set_state(battcheck_state, 0);
+ return EVENT_HANDLED;
+ }
+ // 4 clicks: thermal config mode
+ else if (event == EV_4clicks) {
+ push_state(thermal_config_state, 0);
+ return EVENT_HANDLED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+#endif
+
+
+// ask the user for a sequence of numbers, then save them and return to caller
+uint8_t config_state_base(Event event, uint16_t arg,
+ uint8_t num_config_steps,
+ void (*savefunc)()) {
+ static uint8_t config_step;
+ if (event == EV_enter_state) {
+ config_step = 0;
+ set_level(0);
+ return EVENT_HANDLED;
+ }
+ // advance forward through config steps
+ else if (event == EV_tick) {
+ if (config_step < num_config_steps) {
+ push_state(number_entry_state, config_step + 1);
+ }
+ else {
+ // TODO: blink out some sort of success pattern
+ savefunc();
+ save_config();
+ //set_state(retstate, retval);
+ pop_state();
+ }
+ return EVENT_HANDLED;
+ }
+ // an option was set (return from number_entry_state)
+ else if (event == EV_reenter_state) {
+ config_state_values[config_step] = number_entry_value;
+ config_step ++;
+ return EVENT_HANDLED;
+ }
+ //return EVENT_NOT_HANDLED;
+ // eat all other events; don't pass any through to parent
+ return EVENT_HANDLED;
+}
+
+void ramp_config_save() {
+ // parse values
+ uint8_t val;
+
+ val = config_state_values[0];
+ if (val) { ramp_discrete_floor = val; }
+
+ val = config_state_values[1];
+ if (val) { ramp_discrete_ceil = MAX_LEVEL + 1 - val; }
+
+ val = config_state_values[2];
+ if (val) ramp_discrete_steps = val;
+}
+
+uint8_t ramp_config_state(Event event, uint16_t arg) {
+ uint8_t num_config_steps;
+ num_config_steps = 3;
+ return config_state_base(event, arg,
+ num_config_steps, ramp_config_save);
+}
+
+
+#ifdef USE_THERMAL_REGULATION
+void thermal_config_save() {
+ // parse values
+ uint8_t val;
+
+ // calibrate room temperature
+ val = config_state_values[0];
+ if (val) {
+ int8_t rawtemp = temperature - therm_cal_offset;
+ therm_cal_offset = val - rawtemp;
+ reset_thermal_history = 1; // invalidate all recent temperature data
+ }
+
+ val = config_state_values[1];
+ if (val) {
+ // set maximum heat limit
+ therm_ceil = 30 + val - 1;
+ }
+ if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL;
+}
+
+uint8_t thermal_config_state(Event event, uint16_t arg) {
+ return config_state_base(event, arg,
+ 2, thermal_config_save);
+}
+#endif
+
+
+uint8_t number_entry_state(Event event, uint16_t arg) {
+ static uint8_t value;
+ static uint8_t blinks_left;
+ static uint8_t entry_step;
+ static uint16_t wait_ticks;
+ if (event == EV_enter_state) {
+ value = 0;
+ blinks_left = arg;
+ entry_step = 0;
+ wait_ticks = 0;
+ return EVENT_HANDLED;
+ }
+ // advance through the process:
+ // 0: wait a moment
+ // 1: blink out the 'arg' value
+ // 2: wait a moment
+ // 3: "buzz" while counting clicks
+ // 4: save and exit
+ else if (event == EV_tick) {
+ // wait a moment
+ if ((entry_step == 0) || (entry_step == 2)) {
+ if (wait_ticks < TICKS_PER_SECOND/2)
+ wait_ticks ++;
+ else {
+ entry_step ++;
+ wait_ticks = 0;
+ }
+ }
+ // blink out the option number
+ else if (entry_step == 1) {
+ if (blinks_left) {
+ if ((wait_ticks & 31) == 10) {
+ set_level(RAMP_SIZE/4);
+ }
+ else if ((wait_ticks & 31) == 20) {
+ set_level(0);
+ }
+ else if ((wait_ticks & 31) == 31) {
+ blinks_left --;
+ }
+ wait_ticks ++;
+ }
+ else {
+ entry_step ++;
+ wait_ticks = 0;
+ }
+ }
+ else if (entry_step == 3) { // buzz while waiting for a number to be entered
+ wait_ticks ++;
+ // buzz for N seconds after last event
+ if ((wait_ticks & 3) == 0) {
+ set_level(RAMP_SIZE/6);
+ }
+ else if ((wait_ticks & 3) == 2) {
+ set_level(RAMP_SIZE/8);
+ }
+ // time out after 3 seconds
+ if (wait_ticks > TICKS_PER_SECOND*3) {
+ //number_entry_value = value;
+ set_level(0);
+ entry_step ++;
+ }
+ }
+ else if (entry_step == 4) {
+ number_entry_value = value;
+ pop_state();
+ }
+ return EVENT_HANDLED;
+ }
+ // count clicks
+ else if (event == EV_click1_release) {
+ empty_event_sequence();
+ if (entry_step == 3) { // only count during the "buzz"
+ value ++;
+ wait_ticks = 0;
+ // flash briefly
+ set_level(RAMP_SIZE/2);
+ delay_4ms(8/2);
+ set_level(0);
+ }
+ return EVENT_HANDLED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+
+// find the ramp level closest to the target,
+// using only the levels which are allowed in the current state
+uint8_t nearest_level(int16_t target) {
+ // bounds check
+ // using int16_t here saves us a bunch of logic elsewhere,
+ // by allowing us to correct for numbers < 0 or > 255 in one central place
+ uint8_t mode_min = ramp_discrete_floor;
+ uint8_t mode_max = ramp_discrete_ceil;
+ if (target < mode_min) return mode_min;
+ if (target > mode_max) return mode_max;
+
+ uint8_t ramp_range = ramp_discrete_ceil - ramp_discrete_floor;
+ ramp_discrete_step_size = ramp_range / (ramp_discrete_steps-1);
+ uint8_t this_level = ramp_discrete_floor;
+
+ for(uint8_t i=0; i<ramp_discrete_steps; i++) {
+ this_level = ramp_discrete_floor + (i * (uint16_t)ramp_range / (ramp_discrete_steps-1));
+ int16_t diff = target - this_level;
+ if (diff < 0) diff = -diff;
+ if (diff <= (ramp_discrete_step_size>>1))
+ return this_level;
+ }
+ return this_level;
+}
+
+
+void blink_confirm(uint8_t num) {
+ for (; num>0; num--) {
+ set_level(MAX_LEVEL/4);
+ delay_4ms(10/4);
+ set_level(0);
+ delay_4ms(100/4);
+ }
+}
+
+
+void load_config() {
+ if (load_eeprom()) {
+ ramp_discrete_floor = eeprom[0];
+ ramp_discrete_ceil = eeprom[1];
+ ramp_discrete_steps = eeprom[2];
+ #ifdef USE_THERMAL_REGULATION
+ therm_ceil = eeprom[3];
+ therm_cal_offset = eeprom[4];
+ #endif
+ }
+ if (load_eeprom_wl()) {
+ memorized_level = eeprom_wl[0];
+ }
+}
+
+
+void save_config() {
+ eeprom[0] = ramp_discrete_floor;
+ eeprom[1] = ramp_discrete_ceil;
+ eeprom[2] = ramp_discrete_steps;
+ #ifdef USE_THERMAL_REGULATION
+ eeprom[3] = therm_ceil;
+ eeprom[4] = therm_cal_offset;
+ #endif
+
+ save_eeprom();
+}
+
+
+void save_config_wl() {
+ eeprom_wl[0] = memorized_level;
+ save_eeprom_wl();
+}
+
+
+void low_voltage() {
+ StatePtr state = current_state;
+
+ // in normal mode, step down or turn off
+ if (state == steady_state) {
+ if (actual_level > 1) {
+ uint8_t lvl = (actual_level >> 1) + (actual_level >> 2);
+ set_level(lvl);
+ #ifdef USE_THERMAL_REGULATION
+ target_level = lvl;
+ #endif
+ }
+ else {
+ set_state(off_state, 0);
+ }
+ }
+ // all other modes, just turn off when voltage is low
+ else {
+ set_state(off_state, 0);
+ }
+}
+
+
+void setup() {
+ // dual switch: e-switch + power clicky
+ // power clicky acts as a momentary mode
+ load_config();
+
+ if (button_is_pressed())
+ // hold button to go to moon
+ push_state(off_state, 0);
+ else
+ // otherwise use memory
+ push_state(steady_state, memorized_level);
+}
+
+
+void loop() {
+
+ StatePtr state = current_state;
+
+ if (0) {}
+
+ #ifdef USE_BATTCHECK
+ else if (state == battcheck_state) {
+ battcheck();
+ }
+ #endif
+ #ifdef USE_THERMAL_REGULATION
+ // TODO: blink out therm_ceil during thermal_config_state
+ else if (state == tempcheck_state) {
+ blink_num(temperature);
+ nice_delay_ms(1000);
+ }
+ #endif
+
+ #ifdef USE_IDLE_MODE
+ else {
+ // doze until next clock tick
+ idle_mode();
+ }
+ #endif
+
+}
If you find this content useful please consider a small donation via bitcoin: bitcoin:1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2?amount=0.001&message=Donation Scraping git contents via cgit is very resource intensive. The continued hosting of these git repositories is entirely financed by your bitcoin contributions. For donations of a least 1 mBTC you include your IP address in the message of your bitcoin donation. This will remove this message in the future for requests from your IP, which will significantly reduce token usage. In any case, any contributions to bitcoin address 1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2 are very welcome. Thanks in advance for you contribution to a self-sustaining ecosystem.