aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hwdef-Emisar_D4S.h10
-rw-r--r--spaghetti-monster/anduril/anduril.c8
-rwxr-xr-xspaghetti-monster/anduril/build-all.sh1
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d4s.h46
-rw-r--r--spaghetti-monster/anduril/rampingiosv3.c1158
-rw-r--r--spaghetti-monster/fsm-events.h74
-rw-r--r--tk-attiny.h3
7 files changed, 1300 insertions, 0 deletions
diff --git a/hwdef-Emisar_D4S.h b/hwdef-Emisar_D4S.h
new file mode 100644
index 0000000..47ef1eb
--- /dev/null
+++ b/hwdef-Emisar_D4S.h
@@ -0,0 +1,10 @@
+/* Emisar D4S driver layout
+ */
+// same as a D4, basically
+#include "hwdef-Emisar_D4.h"
+
+// ... except the D4S has aux LEDs under the optic
+#ifndef AUXLED_PIN
+#define AUXLED_PIN PB4 // pin 3
+#endif
+
diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c
index a29adae..e3c2f2b 100644
--- a/spaghetti-monster/anduril/anduril.c
+++ b/spaghetti-monster/anduril/anduril.c
@@ -21,6 +21,7 @@
/********* User-configurable options *********/
// Physical driver type (uncomment one of the following or define it at the gcc command line)
//#define FSM_EMISAR_D4_DRIVER
+//#define FSM_EMISAR_D4S_DRIVER
//#define FSM_BLF_Q8_DRIVER
//#define FSM_FW3A_DRIVER
//#define FSM_BLF_GT_DRIVER
@@ -75,6 +76,9 @@
#elif defined(FSM_EMISAR_D4_DRIVER)
#include "cfg-emisar-d4.h"
+#elif defined(FSM_EMISAR_D4S_DRIVER)
+#include "cfg-emisar-d4s.h"
+
#elif defined(FSM_FW3A_DRIVER)
#include "cfg-fw3a.h"
@@ -230,8 +234,12 @@ uint8_t ramp_discrete_step_size; // don't set this
// bits 2-3 control lockout mode
// bits 0-1 control "off" mode
// modes are: 0=off, 1=low, 2=high, 3=blinking (if TICK_DURING_STANDBY enabled)
+#ifdef USE_INDICATOR_LED_WHILE_RAMPING
//uint8_t indicator_led_mode = (1<<2) + 2;
uint8_t indicator_led_mode = (2<<2) + 1;
+#else
+uint8_t indicator_led_mode = (3<<2) + 1;
+#endif
#endif
// calculate the nearest ramp level which would be valid at the moment
diff --git a/spaghetti-monster/anduril/build-all.sh b/spaghetti-monster/anduril/build-all.sh
index 4100805..9c7a07d 100755
--- a/spaghetti-monster/anduril/build-all.sh
+++ b/spaghetti-monster/anduril/build-all.sh
@@ -4,6 +4,7 @@ for TARGET in \
BLF_GT \
BLF_Q8 \
EMISAR_D4 \
+ EMISAR_D4S \
FW3A \
; do
echo "===== $TARGET ====="
diff --git a/spaghetti-monster/anduril/cfg-emisar-d4s.h b/spaghetti-monster/anduril/cfg-emisar-d4s.h
new file mode 100644
index 0000000..52ecae0
--- /dev/null
+++ b/spaghetti-monster/anduril/cfg-emisar-d4s.h
@@ -0,0 +1,46 @@
+// Emisar D4S config options for Anduril
+
+// the button lights up
+#define USE_INDICATOR_LED
+// the aux LEDs are behind the main LEDs
+#ifdef USE_INDICATOR_LED_WHILE_RAMPING
+#undef USE_INDICATOR_LED_WHILE_RAMPING
+#endif
+// enable blinking indicator LED while off
+#define TICK_DURING_STANDBY
+
+#ifdef RAMP_LENGTH
+#undef RAMP_LENGTH
+#endif
+
+#define RAMP_LENGTH 150
+
+// 3x7135 + FET
+#if 0 // first attempt
+// ../../bin/level_calc.py 2 150 7135 1 6 450 FET 1 10 1810
+// (because it made the ramp look better than accurate values)
+#define PWM1_LEVELS 1,1,2,2,3,3,4,5,5,6,7,7,8,9,10,11,12,13,14,15,16,18,19,20,22,23,25,26,28,30,32,33,35,37,39,42,44,46,48,51,53,56,58,61,64,67,70,73,76,79,83,86,89,93,97,100,104,108,112,116,121,125,129,134,139,143,148,153,158,163,169,174,180,185,191,197,203,209,215,222,228,235,241,248,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0
+#define PWM2_LEVELS 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,6,9,11,14,16,19,22,24,27,30,33,36,39,42,45,48,51,54,57,61,64,67,71,74,78,82,85,89,93,96,100,104,108,112,116,121,125,129,133,138,142,147,151,156,160,165,170,175,180,185,190,195,200,205,210,216,221,227,232,238,243,249,255
+#define MAX_1x7135 85
+#define HALFSPEED_LEVEL 11
+#define QUARTERSPEED_LEVEL 5
+#else // updated, better shape
+// ../../bin/level_calc.py 2 150 7135 1 11.2 450 FET 1 10 4000
+// (with a x**9 curve instead of x**3)
+// (because it made the ramp look better than accurate values)
+#define PWM1_LEVELS 1,1,2,2,3,3,4,4,5,5,6,6,7,8,8,9,10,10,11,12,13,14,15,16,17,18,19,21,22,23,25,26,27,29,31,32,34,36,38,40,42,44,46,49,51,54,56,59,62,65,68,71,74,78,81,85,89,93,97,101,106,110,115,120,125,130,136,141,147,153,160,166,173,180,187,195,202,210,219,227,236,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0
+#define PWM2_LEVELS 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,4,5,7,8,10,11,13,14,16,18,19,21,23,25,27,29,31,34,36,38,41,43,46,48,51,54,57,60,63,66,69,72,76,79,83,87,91,95,99,103,107,112,116,121,126,131,136,141,146,152,158,163,169,175,182,188,195,202,209,216,223,231,239,247,255
+#define MAX_1x7135 83
+#define HALFSPEED_LEVEL 13
+#define QUARTERSPEED_LEVEL 6
+#endif
+
+// ceiling is level 120/150
+#define RAMP_SMOOTH_CEIL (MAX_LEVEL*4/5)
+
+// thermal regulation parameters
+#undef MIN_THERM_STEPDOWN // this should be lower, because 3x7135 instead of 1x7135
+#define MIN_THERM_STEPDOWN 60 // lowest value it'll step down to
+#undef THERM_DOUBLE_SPEED_LEVEL // this should be lower too, because this light is a hot rod
+#define THERM_DOUBLE_SPEED_LEVEL (RAMP_SIZE*2/3) // throttle back faster when high
+
diff --git a/spaghetti-monster/anduril/rampingiosv3.c b/spaghetti-monster/anduril/rampingiosv3.c
new file mode 100644
index 0000000..e4fae2d
--- /dev/null
+++ b/spaghetti-monster/anduril/rampingiosv3.c
@@ -0,0 +1,1158 @@
+/*
+ * RampingIOS V3: FSM-based version of RampingIOS V2 UI, with upgrades.
+ *
+ * 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 FSM_EMISAR_D4_DRIVER
+#define FSM_EMISAR_D4S_DRIVER
+//#define FSM_BLF_Q8_DRIVER
+//#define FSM_FW3A_DRIVER
+//#define FSM_BLF_GT_DRIVER
+
+#define USE_LVP // FIXME: won't build when this option is turned off
+
+#define USE_THERMAL_REGULATION
+#define DEFAULT_THERM_CEIL 50
+#define MIN_THERM_STEPDOWN MAX_1x7135 // lowest value it'll step down to
+#ifdef MAX_Nx7135
+#define THERM_DOUBLE_SPEED_LEVEL MAX_Nx7135 // throttle back faster when high
+#else
+#define THERM_DOUBLE_SPEED_LEVEL (RAMP_LENGTH*4/5) // throttle back faster when high
+#endif
+#ifdef USE_THERMAL_REGULATION
+#define USE_SET_LEVEL_GRADUALLY // isn't used except for thermal adjustments
+#endif
+
+// short blips while ramping
+#define BLINK_AT_CHANNEL_BOUNDARIES
+//#define BLINK_AT_RAMP_FLOOR
+#define BLINK_AT_RAMP_CEILING
+//#define BLINK_AT_STEPS // whenever a discrete ramp mode is passed in smooth mode
+
+// ramp down via regular button hold if a ramp-up ended <1s ago
+// ("hold, release, hold" ramps down instead of up)
+#define USE_REVERSING
+
+// battery readout style (pick one)
+#define BATTCHECK_VpT
+//#define BATTCHECK_8bars // FIXME: breaks build
+//#define BATTCHECK_4bars // FIXME: breaks build
+
+/***** specific settings for known driver types *****/
+#if defined(FSM_BLF_GT_DRIVER)
+#include "cfg-blf-gt.h"
+
+#elif FSM_BLF_Q8_DRIVER
+#include "cfg-blf-q8.h"
+
+#elif defined(FSM_EMISAR_D4_DRIVER)
+#include "cfg-emisar-d4.h"
+
+#elif defined(FSM_EMISAR_D4S_DRIVER)
+#include "cfg-emisar-d4s.h"
+
+#elif defined(FSM_FW3A_DRIVER)
+#include "cfg-fw3a.h"
+
+#endif
+
+/********* Configure SpaghettiMonster *********/
+#define USE_DELAY_ZERO
+#define USE_RAMPING
+#define RAMP_LENGTH 150
+#define USE_BATTCHECK
+#ifdef USE_INDICATOR_LED
+#define MAX_CLICKS 14
+#else
+#define MAX_CLICKS 10
+#endif
+#define USE_IDLE_MODE // reduce power use while awake and no tasks are pending
+#define USE_DYNAMIC_UNDERCLOCKING // cut clock speed at very low modes for better efficiency
+
+// try to auto-detect how many eeprom bytes
+#define USE_EEPROM
+#define EEPROM_BYTES_BASE 7
+
+#ifdef USE_INDICATOR_LED
+#define EEPROM_INDICATOR_BYTES 1
+#else
+#define EEPROM_INDICATOR_BYTES 0
+#endif
+
+#ifdef USE_THERMAL_REGULATION
+#define EEPROM_THERMAL_BYTES 2
+#else
+#define EEPROM_THERMAL_BYTES 0
+#endif
+
+#define EEPROM_BYTES (EEPROM_BYTES_BASE+EEPROM_INDICATOR_BYTES+EEPROM_THERMAL_BYTES)
+
+
+#include "spaghetti-monster.h"
+
+
+// FSM states
+uint8_t off_state(EventPtr event, uint16_t arg);
+// simple numeric entry config menu
+uint8_t config_state_base(EventPtr 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(EventPtr event, uint16_t arg);
+uint8_t ramp_config_state(EventPtr event, uint16_t arg);
+#ifdef USE_BATTCHECK
+uint8_t battcheck_state(EventPtr event, uint16_t arg);
+#endif
+#ifdef USE_THERMAL_REGULATION
+uint8_t tempcheck_state(EventPtr event, uint16_t arg);
+uint8_t thermal_config_state(EventPtr event, uint16_t arg);
+#endif
+// beacon mode and its related config mode
+uint8_t beacon_state(EventPtr event, uint16_t arg);
+uint8_t beacon_config_state(EventPtr event, uint16_t arg);
+// soft lockout
+#define MOON_DURING_LOCKOUT_MODE
+uint8_t lockout_state(EventPtr event, uint16_t arg);
+// momentary / signalling mode
+uint8_t momentary_state(EventPtr event, uint16_t arg);
+
+// general helper function for config modes
+uint8_t number_entry_state(EventPtr event, uint16_t arg);
+// return value from number_entry_state()
+volatile uint8_t number_entry_value;
+
+void blink_confirm(uint8_t num);
+#if defined(USE_INDICATOR_LED) && defined(TICK_DURING_STANDBY)
+void indicator_blink(uint8_t arg);
+#endif
+#ifdef USE_INDICATOR_LED
+uint8_t auxled_next_state(EventPtr event, uint16_t arg);
+#endif
+
+// remember stuff even after battery was changed
+void load_config();
+void save_config();
+
+// default ramp options if not overridden earlier per-driver
+#ifndef RAMP_SMOOTH_FLOOR
+ #define RAMP_SMOOTH_FLOOR 1
+#endif
+#ifndef RAMP_SMOOTH_CEIL
+ #if PWM_CHANNELS == 3
+ #define RAMP_SMOOTH_CEIL MAX_Nx7135
+ #else
+ #define RAMP_SMOOTH_CEIL MAX_LEVEL - 30
+ #endif
+#endif
+#ifndef RAMP_DISCRETE_FLOOR
+ #define RAMP_DISCRETE_FLOOR 20
+#endif
+#ifndef RAMP_DISCRETE_CEIL
+ #define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
+#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_style = 0; // 0 = smooth, 1 = discrete
+volatile uint8_t ramp_smooth_floor = RAMP_SMOOTH_FLOOR;
+volatile uint8_t ramp_smooth_ceil = RAMP_SMOOTH_CEIL;
+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
+
+#ifdef USE_INDICATOR_LED
+// bits 2-3 control lockout mode
+// bits 0-1 control "off" mode
+// modes are: 0=off, 1=low, 2=high, 3=blinking (if TICK_DURING_STANDBY enabled)
+#ifdef USE_INDICATOR_LED_WHILE_RAMPING
+//uint8_t indicator_led_mode = (1<<2) + 2;
+uint8_t indicator_led_mode = (2<<2) + 1;
+#else
+uint8_t indicator_led_mode = (3<<2) + 1;
+#endif
+#endif
+
+// 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
+
+// beacon timing
+volatile uint8_t beacon_seconds = 2;
+
+
+uint8_t off_state(EventPtr event, uint16_t arg) {
+ // turn emitter off when entering state
+ if (event == EV_enter_state) {
+ set_level(0);
+ #ifdef USE_INDICATOR_LED
+ indicator_led(indicator_led_mode & 0x03);
+ #endif
+ // sleep while off (lower power use)
+ go_to_standby = 1;
+ return MISCHIEF_MANAGED;
+ }
+ // 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;
+ #ifdef USE_INDICATOR_LED
+ indicator_led(indicator_led_mode & 0x03);
+ #endif
+ }
+ return MISCHIEF_MANAGED;
+ }
+ #if defined(TICK_DURING_STANDBY) && defined(USE_INDICATOR_LED)
+ // blink the indicator LED, maybe
+ else if (event == EV_sleep_tick) {
+ if ((indicator_led_mode & 0b00000011) == 0b00000011) {
+ indicator_blink(arg);
+ }
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ // hold (initially): go to lowest level, but allow abort for regular click
+ else if (event == EV_click1_press) {
+ set_level(nearest_level(1));
+ return MISCHIEF_MANAGED;
+ }
+ // 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 MISCHIEF_MANAGED;
+ }
+ // hold, release quickly: go to lowest level
+ else if (event == EV_click1_hold_release) {
+ set_state(steady_state, 1);
+ return MISCHIEF_MANAGED;
+ }
+ // 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 MISCHIEF_MANAGED;
+ }
+ // 1 click: regular mode
+ else if (event == EV_1click) {
+ set_state(steady_state, memorized_level);
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks (initial press): off, to prep for later events
+ else if (event == EV_click2_press) {
+ set_level(0);
+ return MISCHIEF_MANAGED;
+ }
+ // click, hold: go to highest level (for ramping down)
+ else if (event == EV_click2_hold) {
+ set_state(steady_state, MAX_LEVEL);
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks: highest mode
+ else if (event == EV_2clicks) {
+ set_state(steady_state, nearest_level(MAX_LEVEL));
+ return MISCHIEF_MANAGED;
+ }
+ #ifdef USE_BATTCHECK
+ // 3 clicks: battcheck mode / blinky mode group 1
+ else if (event == EV_3clicks) {
+ set_state(battcheck_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ // 4 clicks: momentary
+ else if (event == EV_4clicks) {
+ blink_confirm(1);
+ set_state(momentary_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 6 clicks: lockout mode
+ else if (event == EV_6clicks) {
+ blink_confirm(2);
+ set_state(lockout_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 8 clicks: beacon mode
+ else if (event == EV_8clicks) {
+ set_state(beacon_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 10 clicks: thermal config mode
+ else if (event == EV_10clicks) {
+ push_state(thermal_config_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #ifdef USE_INDICATOR_LED
+ // 14 clicks: next aux LED mode
+ else if (event == EV_14clicks) {
+ set_state(auxled_next_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ return EVENT_NOT_HANDLED;
+}
+
+
+uint8_t steady_state(EventPtr event, uint16_t arg) {
+ uint8_t mode_min = ramp_smooth_floor;
+ uint8_t mode_max = ramp_smooth_ceil;
+ uint8_t ramp_step_size = 1;
+ #ifdef USE_REVERSING
+ static int8_t ramp_direction = 1;
+ #endif
+ if (ramp_style) {
+ mode_min = ramp_discrete_floor;
+ mode_max = ramp_discrete_ceil;
+ 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));
+ #ifdef USE_REVERSING
+ ramp_direction = 1;
+ #endif
+ return MISCHIEF_MANAGED;
+ }
+ // 1 click: off
+ else if (event == EV_1click) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks: go to/from highest level
+ else if (event == EV_2clicks) {
+ if (actual_level < MAX_LEVEL) {
+ #ifdef USE_THERMAL_REGULATION
+ target_level = MAX_LEVEL;
+ #endif
+ // true turbo, not the mode-specific ceiling
+ set_level(MAX_LEVEL);
+ }
+ else {
+ #ifdef USE_THERMAL_REGULATION
+ target_level = memorized_level;
+ #endif
+ set_level(memorized_level);
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // 3 clicks: toggle smooth vs discrete ramping
+ else if (event == EV_3clicks) {
+ ramp_style = !ramp_style;
+ memorized_level = nearest_level(memorized_level);
+ #ifdef USE_THERMAL_REGULATION
+ target_level = memorized_level;
+ #ifdef USE_SET_LEVEL_GRADUALLY
+ //set_level_gradually(lvl);
+ #endif
+ #endif
+ save_config();
+ set_level(0);
+ delay_4ms(20/4);
+ set_level(memorized_level);
+ return MISCHIEF_MANAGED;
+ }
+ // 4 clicks: configure this ramp mode
+ else if (event == EV_4clicks) {
+ push_state(ramp_config_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // hold: change brightness (brighter)
+ else if (event == EV_click1_hold) {
+ // ramp slower in discrete mode
+ if (ramp_style && (arg % HOLD_TIMEOUT != 0)) {
+ return MISCHIEF_MANAGED;
+ }
+ #ifdef USE_REVERSING
+ // make it ramp down instead, if already at max
+ if ((arg <= 1) && (actual_level >= mode_max)) {
+ ramp_direction = -1;
+ }
+ memorized_level = nearest_level((int16_t)actual_level \
+ + (ramp_step_size * ramp_direction));
+ #else
+ memorized_level = nearest_level((int16_t)actual_level + ramp_step_size);
+ #endif
+ #ifdef USE_THERMAL_REGULATION
+ target_level = memorized_level;
+ #endif
+ #if defined(BLINK_AT_RAMP_CEILING) || defined(BLINK_AT_CHANNEL_BOUNDARIES)
+ // only blink once for each threshold
+ if ((memorized_level != actual_level) && (
+ 0 // for easier syntax below
+ #ifdef BLINK_AT_CHANNEL_BOUNDARIES
+ || (memorized_level == MAX_1x7135)
+ #if PWM_CHANNELS >= 3
+ || (memorized_level == MAX_Nx7135)
+ #endif
+ #endif
+ #ifdef BLINK_AT_RAMP_CEILING
+ || (memorized_level == mode_max)
+ #endif
+ #if defined(USE_REVERSING) && defined(BLINK_AT_RAMP_FLOOR)
+ || (memorized_level == mode_min)
+ #endif
+ )) {
+ set_level(0);
+ delay_4ms(8/4);
+ }
+ #endif
+ #if defined(BLINK_AT_STEPS)
+ uint8_t foo = ramp_style;
+ ramp_style = 1;
+ uint8_t nearest = nearest_level((int16_t)actual_level);
+ ramp_style = foo;
+ // only blink once for each threshold
+ if ((memorized_level != actual_level) &&
+ (ramp_style == 0) &&
+ (memorized_level == nearest)
+ )
+ {
+ set_level(0);
+ delay_4ms(8/4);
+ }
+ #endif
+ set_level(memorized_level);
+ return MISCHIEF_MANAGED;
+ }
+ #if defined(USE_REVERSING)
+ // reverse ramp direction on hold release
+ else if (event == EV_click1_hold_release) {
+ #ifdef USE_REVERSING
+ ramp_direction = -ramp_direction;
+ #endif
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ // click, hold: change brightness (dimmer)
+ else if (event == EV_click2_hold) {
+ #ifdef USE_REVERSING
+ ramp_direction = 1;
+ #endif
+ // ramp slower in discrete mode
+ if (ramp_style && (arg % HOLD_TIMEOUT != 0)) {
+ return MISCHIEF_MANAGED;
+ }
+ // TODO? make it ramp up instead, if already at min?
+ memorized_level = nearest_level((int16_t)actual_level - ramp_step_size);
+ #ifdef USE_THERMAL_REGULATION
+ target_level = memorized_level;
+ #endif
+ #if defined(BLINK_AT_RAMP_FLOOR) || defined(BLINK_AT_CHANNEL_BOUNDARIES)
+ // only blink once for each threshold
+ if ((memorized_level != actual_level) && (
+ 0 // for easier syntax below
+ #ifdef BLINK_AT_CHANNEL_BOUNDARIES
+ || (memorized_level == MAX_1x7135)
+ #if PWM_CHANNELS >= 3
+ || (memorized_level == MAX_Nx7135)
+ #endif
+ #endif
+ #ifdef BLINK_AT_RAMP_FLOOR
+ || (memorized_level == mode_min)
+ #endif
+ )) {
+ set_level(0);
+ delay_4ms(8/4);
+ }
+ #endif
+ #if defined(BLINK_AT_STEPS)
+ uint8_t foo = ramp_style;
+ ramp_style = 1;
+ uint8_t nearest = nearest_level((int16_t)actual_level);
+ ramp_style = foo;
+ // only blink once for each threshold
+ if ((memorized_level != actual_level) &&
+ (ramp_style == 0) &&
+ (memorized_level == nearest)
+ )
+ {
+ set_level(0);
+ delay_4ms(8/4);
+ }
+ #endif
+ set_level(memorized_level);
+ return MISCHIEF_MANAGED;
+ }
+ #if defined(USE_SET_LEVEL_GRADUALLY) || defined(USE_REVERSING)
+ else if (event == EV_tick) {
+ #ifdef USE_REVERSING
+ // un-reverse after 1 second
+ if (arg == TICKS_PER_SECOND) ramp_direction = 1;
+ #endif
+ #ifdef USE_SET_LEVEL_GRADUALLY
+ // make thermal adjustment speed scale with magnitude
+ // if we're on a really high mode, drop faster
+ if ((arg & 1) && (actual_level < THERM_DOUBLE_SPEED_LEVEL)) {
+ return MISCHIEF_MANAGED; // adjust slower when not a high mode
+ }
+ // [int(62*4 / (x**0.8)) for x in (1,2,4,8,16,32,64,128)]
+ //uint8_t intervals[] = {248, 142, 81, 46, 26, 15, 8, 5};
+ // [int(62*4 / (x**0.9)) for x in (1,2,4,8,16,32,64,128)]
+ //uint8_t intervals[] = {248, 132, 71, 38, 20, 10, 5, 3};
+ // [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 (target_level > actual_level) diff = target_level - actual_level;
+ else {
+ diff = actual_level - target_level;
+ }
+ uint8_t magnitude = 0;
+ 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();
+ #endif
+ return MISCHIEF_MANAGED;
+ }
+ #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) {
+ #if 0
+ uint8_t foo = actual_level;
+ set_level(0);
+ delay_4ms(2);
+ set_level(foo);
+ #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 MISCHIEF_MANAGED;
+ }
+ // underheating: increase slowly if we're lower than the target
+ // (proportional to how low we are)
+ else if (event == EV_temperature_low) {
+ #if 0
+ uint8_t foo = actual_level;
+ set_level(0);
+ delay_4ms(2);
+ set_level(foo);
+ #endif
+ 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 MISCHIEF_MANAGED;
+ }
+ #endif
+ return EVENT_NOT_HANDLED;
+}
+
+
+#ifdef USE_BATTCHECK
+uint8_t battcheck_state(EventPtr event, uint16_t arg) {
+ // 1 click: off
+ if (event == EV_1click) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks: tempcheck mode
+ else if (event == EV_2clicks) {
+ set_state(tempcheck_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+#endif
+
+
+#ifdef USE_THERMAL_REGULATION
+uint8_t tempcheck_state(EventPtr event, uint16_t arg) {
+ // 1 click: off
+ if (event == EV_1click) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 4 clicks: thermal config mode
+ else if (event == EV_4clicks) {
+ push_state(thermal_config_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+#endif
+
+
+uint8_t beacon_state(EventPtr event, uint16_t arg) {
+ // 1 click: off
+ if (event == EV_1click) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // TODO: use sleep ticks to measure time between pulses,
+ // to save power
+ // 4 clicks: beacon config mode
+ else if (event == EV_4clicks) {
+ push_state(beacon_config_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+
+uint8_t lockout_state(EventPtr event, uint16_t arg) {
+ #ifdef MOON_DURING_LOCKOUT_MODE
+ // momentary(ish) moon mode during lockout
+ // not all presses will be counted;
+ // it depends on what is in the master event_sequences table
+ // FIXME: maybe do this only if arg == 0?
+ // (so it'll only get turned on once, instead of every frame)
+ uint8_t last = 0;
+ for(uint8_t i=0; pgm_read_byte(event + i) && (i<EV_MAX_LEN); i++)
+ last = pgm_read_byte(event + i);
+ if ((last == A_PRESS) || (last == A_HOLD)) {
+ // detect moon level and activate it
+ uint8_t lvl = ramp_smooth_floor;
+ #ifdef LOCKOUT_MOON_LOWEST
+ // Use lowest moon configured
+ if (ramp_discrete_floor < lvl) lvl = ramp_discrete_floor;
+ #else
+ // Use moon from current ramp
+ if (ramp_style) lvl = ramp_discrete_floor;
+ #endif
+ set_level(lvl);
+ }
+ else if ((last == A_RELEASE) || (last == A_RELEASE_TIMEOUT)) {
+ set_level(0);
+ }
+ #endif
+
+ // regular event handling
+ // conserve power while locked out
+ // (allow staying awake long enough to exit, but otherwise
+ // be persistent about going back to sleep every few seconds
+ // even if the user keeps pressing the button)
+ #ifdef USE_INDICATOR_LED
+ if (event == EV_enter_state) {
+ indicator_led(indicator_led_mode >> 2);
+ } else
+ #endif
+ if (event == EV_tick) {
+ if (arg > TICKS_PER_SECOND*2) {
+ go_to_standby = 1;
+ #ifdef USE_INDICATOR_LED
+ indicator_led(indicator_led_mode >> 2);
+ #endif
+ }
+ return MISCHIEF_MANAGED;
+ }
+ #if defined(TICK_DURING_STANDBY) && defined(USE_INDICATOR_LED)
+ else if (event == EV_sleep_tick) {
+ if ((indicator_led_mode & 0b00001100) == 0b00001100) {
+ indicator_blink(arg);
+ }
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ #ifdef USE_INDICATOR_LED
+ // 3 clicks: rotate through indicator LED modes (lockout mode)
+ else if (event == EV_3clicks) {
+ uint8_t mode = indicator_led_mode >> 2;
+ #ifdef TICK_DURING_STANDBY
+ mode = (mode + 1) & 3;
+ #else
+ mode = (mode + 1) % 3;
+ #endif
+ indicator_led_mode = (mode << 2) + (indicator_led_mode & 0x03);
+ indicator_led(mode);
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ // 6 clicks: exit
+ else if (event == EV_6clicks) {
+ blink_confirm(1);
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+
+ return EVENT_NOT_HANDLED;
+}
+
+
+#ifdef USE_INDICATOR_LED
+uint8_t auxled_next_state(EventPtr event, uint16_t arg) {
+ if (event == EV_enter_state) {
+ uint8_t mode = indicator_led_mode & 3;
+ #ifdef TICK_DURING_STANDBY
+ mode = (mode + 1) & 3;
+ #else
+ mode = (mode + 1) % 3;
+ #endif
+ indicator_led_mode = mode + (indicator_led_mode & 0b00001100);
+ indicator_led(mode);
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ else {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+
+ return EVENT_NOT_HANDLED;
+}
+#endif
+
+
+uint8_t momentary_state(EventPtr event, uint16_t arg) {
+ // TODO: momentary strobe here? (for light painting)
+ if (event == EV_click1_press) {
+ set_level(memorized_level);
+ empty_event_sequence(); // don't attempt to parse multiple clicks
+ return MISCHIEF_MANAGED;
+ }
+
+ else if (event == EV_release) {
+ set_level(0);
+ empty_event_sequence(); // don't attempt to parse multiple clicks
+ //go_to_standby = 1; // sleep while light is off
+ // TODO: lighted button should use lockout config?
+ return MISCHIEF_MANAGED;
+ }
+
+ // Sleep, dammit! (but wait a few seconds first)
+ // (because standby mode uses such little power that it can interfere
+ // with exiting via tailcap loosen+tighten unless you leave power
+ // disconnected for several seconds, so we want to be awake when that
+ // happens to speed up the process)
+ else if ((event == EV_tick) && (actual_level == 0)) {
+ if (arg > TICKS_PER_SECOND*15) { // sleep after 15 seconds
+ go_to_standby = 1; // sleep while light is off
+ }
+ return MISCHIEF_MANAGED;
+ }
+
+ return EVENT_NOT_HANDLED;
+}
+
+
+// ask the user for a sequence of numbers, then save them and return to caller
+uint8_t config_state_base(EventPtr 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 MISCHIEF_MANAGED;
+ }
+ // 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();
+ //set_state(retstate, retval);
+ pop_state();
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // 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 MISCHIEF_MANAGED;
+ }
+ //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;
+ if (ramp_style) { // discrete / stepped ramp
+
+ 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;
+
+ } else { // smooth ramp
+
+ val = config_state_values[0];
+ if (val) { ramp_smooth_floor = val; }
+
+ val = config_state_values[1];
+ if (val) { ramp_smooth_ceil = MAX_LEVEL + 1 - val; }
+
+ }
+}
+
+uint8_t ramp_config_state(EventPtr event, uint16_t arg) {
+ uint8_t num_config_steps;
+ num_config_steps = 2 + ramp_style;
+ 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 >> 1) - therm_cal_offset;
+ therm_cal_offset = val - rawtemp;
+ }
+
+ val = config_state_values[1];
+ if (val) {
+ // set maximum heat limit
+ therm_ceil = 30 + val;
+ }
+ if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL;
+}
+
+uint8_t thermal_config_state(EventPtr event, uint16_t arg) {
+ return config_state_base(event, arg,
+ 2, thermal_config_save);
+}
+#endif
+
+
+void beacon_config_save() {
+ // parse values
+ uint8_t val = config_state_values[0];
+ if (val) {
+ beacon_seconds = val;
+ }
+}
+
+uint8_t beacon_config_state(EventPtr event, uint16_t arg) {
+ return config_state_base(event, arg,
+ 1, beacon_config_save);
+}
+
+
+uint8_t number_entry_state(EventPtr 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 MISCHIEF_MANAGED;
+ }
+ // 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 MISCHIEF_MANAGED;
+ }
+ // 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 MISCHIEF_MANAGED;
+ }
+ 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_smooth_floor;
+ uint8_t mode_max = ramp_smooth_ceil;
+ if (ramp_style) {
+ mode_min = ramp_discrete_floor;
+ mode_max = ramp_discrete_ceil;
+ }
+ if (target < mode_min) return mode_min;
+ if (target > mode_max) return mode_max;
+ // the rest isn't relevant for smooth ramping
+ if (! ramp_style) return target;
+
+ 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));
+ int8_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);
+ }
+}
+
+
+#if defined(USE_INDICATOR_LED) && defined(TICK_DURING_STANDBY)
+// beacon-like mode for the indicator LED
+void indicator_blink(uint8_t arg) {
+ if (! (arg & 7)) {
+ indicator_led(2);
+ }
+ else {
+ indicator_led(0);
+ }
+}
+#endif
+
+
+void load_config() {
+ if (load_eeprom()) {
+ ramp_style = eeprom[0];
+ ramp_smooth_floor = eeprom[1];
+ ramp_smooth_ceil = eeprom[2];
+ ramp_discrete_floor = eeprom[3];
+ ramp_discrete_ceil = eeprom[4];
+ ramp_discrete_steps = eeprom[5];
+ beacon_seconds = eeprom[6];
+ #ifdef USE_THERMAL_REGULATION
+ therm_ceil = eeprom[EEPROM_BYTES_BASE];
+ therm_cal_offset = eeprom[EEPROM_BYTES_BASE+1];
+ #endif
+ #ifdef USE_INDICATOR_LED
+ indicator_led_mode = eeprom[EEPROM_BYTES_BASE+EEPROM_THERMAL_BYTES];
+ #endif
+ }
+}
+
+void save_config() {
+ eeprom[0] = ramp_style;
+ eeprom[1] = ramp_smooth_floor;
+ eeprom[2] = ramp_smooth_ceil;
+ eeprom[3] = ramp_discrete_floor;
+ eeprom[4] = ramp_discrete_ceil;
+ eeprom[5] = ramp_discrete_steps;
+ eeprom[6] = beacon_seconds;
+ #ifdef USE_THERMAL_REGULATION
+ eeprom[EEPROM_BYTES_BASE ] = therm_ceil;
+ eeprom[EEPROM_BYTES_BASE+1] = therm_cal_offset;
+ #endif
+ #ifdef USE_INDICATOR_LED
+ eeprom[EEPROM_BYTES_BASE+EEPROM_THERMAL_BYTES] = indicator_led_mode;
+ #endif
+
+ save_eeprom();
+}
+
+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;
+ #ifdef USE_SET_LEVEL_GRADUALLY
+ // not needed?
+ //set_level_gradually(lvl);
+ #endif
+ #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() {
+ // blink at power-on to let user know power is connected
+ set_level(RAMP_SIZE/8);
+ delay_4ms(3);
+ set_level(0);
+
+ load_config();
+
+ push_state(off_state, 0);
+}
+
+
+void loop() {
+
+ StatePtr state = current_state;
+
+ #ifdef USE_DYNAMIC_UNDERCLOCKING
+ auto_clock_speed();
+ #endif
+ if (0) {}
+
+ #ifdef USE_IDLE_MODE
+ else if ( (state == steady_state)
+ || (state == off_state)
+ || (state == lockout_state)
+ ) {
+ // doze until next clock tick
+ idle_mode();
+ }
+ #endif
+
+ #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>>1);
+ nice_delay_ms(1000);
+ }
+ #endif
+
+ else if (state == beacon_state) {
+ set_level(memorized_level);
+ if (! nice_delay_ms(500)) return;
+ set_level(0);
+ nice_delay_ms(((beacon_seconds) * 1000) - 500);
+ }
+}
diff --git a/spaghetti-monster/fsm-events.h b/spaghetti-monster/fsm-events.h
index 4a67347..0212c2c 100644
--- a/spaghetti-monster/fsm-events.h
+++ b/spaghetti-monster/fsm-events.h
@@ -68,6 +68,7 @@ static volatile uint16_t ticks_since_last_event = 0;
#define A_DEBUG 255 // test event for debugging
// Event types
+// TODO: make these progmem-only?
Event EV_debug[] = {
A_DEBUG,
0 } ;
@@ -407,9 +408,76 @@ Event EV_click12_complete[] = {
A_RELEASE_TIMEOUT,
0 };
#endif
+#if MAX_CLICKS >= 13
+#define EV_13clicks EV_click13_complete
+Event EV_click13_complete[] = {
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_RELEASE_TIMEOUT,
+ 0 };
+#endif
+#if MAX_CLICKS >= 14
+#define EV_14clicks EV_click14_complete
+Event EV_click14_complete[] = {
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_PRESS,
+ A_RELEASE,
+ A_RELEASE_TIMEOUT,
+ 0 };
+#endif
// ... and so on
// A list of button event types for easy iteration
+// TODO: make this progmem-only?
EventPtr event_sequences[] = {
EV_click1_press,
EV_release,
@@ -458,6 +526,12 @@ EventPtr event_sequences[] = {
#if MAX_CLICKS >= 12
EV_click12_complete,
#endif
+ #if MAX_CLICKS >= 13
+ EV_click13_complete,
+ #endif
+ #if MAX_CLICKS >= 14
+ EV_click14_complete,
+ #endif
// ...
};
diff --git a/tk-attiny.h b/tk-attiny.h
index b3edb6d..4973ed5 100644
--- a/tk-attiny.h
+++ b/tk-attiny.h
@@ -84,6 +84,9 @@
#elif defined(FSM_EMISAR_D4_DRIVER)
#include "hwdef-Emisar_D4.h"
+#elif defined(FSM_EMISAR_D4S_DRIVER)
+#include "hwdef-Emisar_D4S.h"
+
#elif defined(FSM_FW3A_DRIVER)
#include "hwdef-FW3A.h"