aboutsummaryrefslogtreecommitdiff
path: root/spaghetti-monster
diff options
context:
space:
mode:
authorSelene ToyKeeper2020-07-21 20:56:34 -0600
committerSelene ToyKeeper2020-07-21 20:56:34 -0600
commit9c9135640db4a71528765cd04ee0fb0fb7cd7938 (patch)
tree2673060fe7cc0f46b07d86dc772c9a51d37b8962 /spaghetti-monster
parentmerged fsm branch (mostly because D4v2.5 build targets were merged into KR4, (diff)
downloadanduril-9c9135640db4a71528765cd04ee0fb0fb7cd7938.tar.gz
anduril-9c9135640db4a71528765cd04ee0fb0fb7cd7938.tar.bz2
anduril-9c9135640db4a71528765cd04ee0fb0fb7cd7938.zip
started refactoring anduril into individual files...
... messy and very incomplete, but it's a start, and at least it still compiles. (the overall structure is ... weird. mostly because of compiling the entire program plus libraries in a single step, as a single file, to make -fwhole-program work)
Diffstat (limited to 'spaghetti-monster')
-rw-r--r--spaghetti-monster/anduril/anduril.c1105
-rw-r--r--spaghetti-monster/anduril/beacon.c72
-rw-r--r--spaghetti-monster/anduril/beacon.h31
-rw-r--r--spaghetti-monster/anduril/cfg-default.h103
-rw-r--r--spaghetti-monster/anduril/config-mode.c153
-rw-r--r--spaghetti-monster/anduril/config-mode.h34
-rw-r--r--spaghetti-monster/anduril/factory-reset-fsm.h28
-rw-r--r--spaghetti-monster/anduril/factory-reset.c73
-rw-r--r--spaghetti-monster/anduril/factory-reset.h26
-rw-r--r--spaghetti-monster/anduril/ff-strobes.c66
-rw-r--r--spaghetti-monster/anduril/ff-strobes.h32
-rw-r--r--spaghetti-monster/anduril/lockout.c169
-rw-r--r--spaghetti-monster/anduril/lockout.h31
-rw-r--r--spaghetti-monster/anduril/strobes-fsm.h35
-rw-r--r--spaghetti-monster/anduril/strobes.c403
-rw-r--r--spaghetti-monster/anduril/strobes.h100
-rw-r--r--spaghetti-monster/anduril/tint-ramping.c87
-rw-r--r--spaghetti-monster/anduril/tint-ramping.h27
-rw-r--r--spaghetti-monster/fsm-misc.c1
19 files changed, 1548 insertions, 1028 deletions
diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c
index 7ab7aee..d69be54 100644
--- a/spaghetti-monster/anduril/anduril.c
+++ b/spaghetti-monster/anduril/anduril.c
@@ -18,93 +18,51 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+// Usually a program would be structured like this...
+// - Library headers
+// - App headers
+// - App code
+//
+// ... in each source file.
+// ... and each library and part of the program would be linked together.
+//
+// But this doesn't follow that pattern, because it's using the
+// -fwhole-program
+// flag to reduce the compiled size. It lets us fit more features
+// in a tiny MCU chip's ROM.
+//
+// So the structure is like this instead...
+// - App-level configuration headers
+// - Default config
+// - Per build target config
+// - Library-level configuration headers
+// - Library code (FSM itself)
+// - App headers
+// - App code (all of it)
+//
+// Don't do this in regular programs. It's weird and kind of gross.
+// But in this case it gives us a bunch of much-needed space, so...
+
/********* User-configurable options *********/
+#include "cfg-default.h"
+
+/***** specific settings for known driver types *****/
// Anduril config file name (set it here or define it at the gcc command line)
//#define CONFIGFILE cfg-blf-q8.h
-#define USE_LVP
-
-// parameters for this defined below or per-driver
-#define USE_THERMAL_REGULATION
-#define DEFAULT_THERM_CEIL 45 // try not to get hotter than this
-
-#define USE_FACTORY_RESET
-//#define USE_SOFT_FACTORY_RESET // only needed on models which can't use hold-button-at-boot
-
-// dual-switch support (second switch is a tail clicky)
-// (currently incompatible with factory reset)
-//#define START_AT_MEMORIZED_LEVEL
-
-// include a function to blink out the firmware version
-#define USE_VERSION_CHECK
-
-// short blip when crossing from "click" to "hold" from off
-// (helps the user hit moon mode exactly, instead of holding too long
-// or too short)
-#define MOON_TIMING_HINT // only applies if B_TIMING_ON == B_PRESS_T
-// short blips while ramping
-#define BLINK_AT_RAMP_MIDDLE
-//#define BLINK_AT_RAMP_FLOOR
-#define BLINK_AT_RAMP_CEIL
-//#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
-
-// add a runtime option to switch between automatic memory (default)
-// and manual memory (only available if compiled in)
-// (manual memory makes 1-click-from-off start at the same level each time)
-// (the level can be set explicitly with 5 clicks from on,
-// or the user can go back to automatic with click-click-click-click-hold)
-#define USE_MANUAL_MEMORY
-
-// battery readout style (pick one)
-#define BATTCHECK_VpT
-//#define BATTCHECK_8bars // FIXME: breaks build
-//#define BATTCHECK_4bars // FIXME: breaks build
-
-// enable/disable various strobe modes
-#define USE_BIKE_FLASHER_MODE
-#define USE_PARTY_STROBE_MODE
-#define USE_TACTICAL_STROBE_MODE
-#define USE_LIGHTNING_MODE
-#define USE_CANDLE_MODE
-
-// enable sunset (goodnight) mode
-#define USE_GOODNIGHT_MODE
-#define GOODNIGHT_TIME 60 // minutes (approximately)
-#define GOODNIGHT_LEVEL 24 // ~11 lm
-
-// enable beacon mode
-#define USE_BEACON_MODE
-
-// enable momentary mode
-#define USE_MOMENTARY_MODE
-
-// Include a simplified UI for non-enthusiasts?
-#define USE_SIMPLE_UI
-
-// make the ramps configurable by the user
-#define USE_RAMP_CONFIG
-
-// boring strobes nobody really likes, but sometimes flashlight companies want
-// (these replace the fun strobe group,
-// so don't enable them at the same time as any of the above strobes)
-//#define USE_POLICE_STROBE_MODE
-//#define USE_SOS_MODE
-//#define USE_SOS_MODE_IN_FF_GROUP // put SOS in the "boring strobes" mode
-//#define USE_SOS_MODE_IN_BLINKY_GROUP // put SOS in the blinkies mode group
-
-// 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 *****/
#include "tk.h"
#include incfile(CONFIGFILE)
+////////// Include headers which need to be before FSM //////////
+#ifdef USE_FACTORY_RESET
+#include "factory-reset-fsm.h"
+#endif
+
+// enable FSM features needed by strobe modes
+#include "strobes-fsm.h"
+
+
// brightness to use when no memory is set
#ifndef DEFAULT_LEVEL
#define DEFAULT_LEVEL MAX_1x7135
@@ -153,24 +111,6 @@
#define USE_IDLE_MODE // reduce power use while awake and no tasks are pending
-// full FET strobe can be a bit much... use max regulated level instead,
-// if there's a bright enough regulated level
-#ifndef STROBE_BRIGHTNESS
-#ifdef MAX_Nx7135
-#define STROBE_BRIGHTNESS MAX_Nx7135
-#else
-#define STROBE_BRIGHTNESS MAX_LEVEL
-#endif
-#endif
-
-#if defined(USE_CANDLE_MODE) || defined(USE_BIKE_FLASHER_MODE) || defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE) || defined(USE_LIGHTNING_MODE)
-#define USE_STROBE_STATE
-#endif
-
-#if defined(USE_POLICE_STROBE_MODE) || defined(USE_SOS_MODE_IN_FF_GROUP)
-#define USE_BORING_STROBE_STATE
-#endif
-
// auto-detect how many eeprom bytes
#define USE_EEPROM
typedef enum {
@@ -227,22 +167,28 @@ typedef enum {
#define EEPROM_WL_BYTES 1
#endif
-// auto-configure other stuff...
-#if defined(USE_LIGHTNING_MODE) || defined(USE_CANDLE_MODE)
-#define USE_PSEUDO_RAND
-#endif
+#include "spaghetti-monster.h"
+
-#if defined(USE_CANDLE_MODE)
-#ifndef USE_TRIANGLE_WAVE
-#define USE_TRIANGLE_WAVE
+////////// Include all the extra headers //////////
+
+#include "config-mode.h"
+#include "lockout.h"
+
+#ifdef USE_TINT_RAMPING
+#include "tint-ramping.h"
#endif
+
+#ifdef USE_FACTORY_RESET
+#include "factory-reset.h"
#endif
-#ifdef USE_SOFT_FACTORY_RESET
-#define USE_REBOOT
+#ifdef USE_BEACON_MODE
+#include "beacon.h"
#endif
-#include "spaghetti-monster.h"
+// this one detects its own enable/disable settings
+#include "strobes.h"
// configure the timing of turning on/off in regular ramp mode
@@ -265,31 +211,11 @@ typedef enum {
// 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);
#ifdef USE_RAMP_CONFIG
uint8_t ramp_config_state(Event event, uint16_t arg);
#endif
-#ifdef USE_TINT_RAMPING
-// not actually a mode, more of a fallback under other modes
-uint8_t tint_ramping_state(Event event, uint16_t arg);
-#endif
-// party and tactical strobes
-#ifdef USE_STROBE_STATE
-uint8_t strobe_state(Event event, uint16_t arg);
-#endif
-#ifdef USE_BORING_STROBE_STATE
-uint8_t boring_strobe_state(Event event, uint16_t arg);
-volatile uint8_t boring_strobe_type = 0;
-void sos_blink(uint8_t num, uint8_t dah);
-#define NUM_BORING_STROBES 2
-#endif
#ifdef USE_BATTCHECK
uint8_t battcheck_state(Event event, uint16_t arg);
#endif
@@ -302,19 +228,10 @@ uint8_t thermal_config_state(Event event, uint16_t arg);
// 1-hour ramp down from low, then automatic off
uint8_t goodnight_state(Event event, uint16_t arg);
#endif
-#ifdef USE_BEACON_MODE
-// beacon mode and its related config mode
-uint8_t beacon_state(Event event, uint16_t arg);
-#endif
#ifdef USE_SOS_MODE_IN_BLINKY_GROUP
// automatic SOS emergency signal
uint8_t sos_state(Event event, uint16_t arg);
#endif
-// soft lockout
-#define MOON_DURING_LOCKOUT_MODE
-// if enabled, 2nd lockout click goes to the other ramp's floor level
-#define LOCKOUT_MOON_FANCY
-uint8_t lockout_state(Event event, uint16_t arg);
#ifdef USE_MOMENTARY_MODE
// momentary / signalling mode
uint8_t momentary_state(Event event, uint16_t arg);
@@ -322,11 +239,6 @@ uint8_t momentary_mode = 0; // 0 = ramping, 1 = strobe
uint8_t momentary_active = 0; // boolean, true if active *right now*
#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);
void blip();
#if defined(USE_INDICATOR_LED) && defined(TICK_DURING_STANDBY)
@@ -372,10 +284,6 @@ uint8_t rgb_led_off_mode = RGB_LED_OFF_DEFAULT;
uint8_t rgb_led_lockout_mode = RGB_LED_LOCKOUT_DEFAULT;
#endif
-#ifdef USE_FACTORY_RESET
-void factory_reset();
-#endif
-
// remember stuff even after battery was changed
void load_config();
void save_config();
@@ -493,67 +401,44 @@ void set_level_and_therm_target(uint8_t level);
#define set_level_and_therm_target(level) set_level(level)
#endif
-// internal numbering for strobe modes
-#ifdef USE_STROBE_STATE
-typedef enum {
- #ifdef USE_PARTY_STROBE_MODE
- party_strobe_e,
- #endif
- #ifdef USE_TACTICAL_STROBE_MODE
- tactical_strobe_e,
- #endif
- #ifdef USE_LIGHTNING_MODE
- lightning_storm_e,
- #endif
- #ifdef USE_CANDLE_MODE
- candle_mode_e,
- #endif
- #ifdef USE_BIKE_FLASHER_MODE
- bike_flasher_e,
- #endif
- strobe_mode_END
-} strobe_mode_te;
-
-const int NUM_STROBES = strobe_mode_END;
+#ifdef USE_VERSION_CHECK
+#define USE_BLINK_DIGIT
+#include "version.h"
+const PROGMEM uint8_t version_number[] = VERSION_NUMBER;
+uint8_t version_check_state(Event event, uint16_t arg);
+#endif
-// which strobe mode is active?
-#ifdef USE_CANDLE_MODE
-volatile strobe_mode_te strobe_type = candle_mode_e;
-#else
-volatile strobe_mode_te strobe_type = 0;
+#ifdef USE_RAMP_CONFIG
+void ramp_config_save();
#endif
+#ifdef USE_THERMAL_REGULATION
+void thermal_config_save();
#endif
-#if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
-// party / tactical strobe timing
-volatile uint8_t strobe_delays[] = { 41, 67 }; // party strobe 24 Hz, tactical strobe 10 Hz
-#endif
+////////// Include all the extra sources //////////
+// (is a bit weird to do things this way,
+// but it saves a lot of space by letting use use the -fwhole-program flag)
-// bike mode config options
-#ifdef USE_BIKE_FLASHER_MODE
-volatile uint8_t bike_flasher_brightness = MAX_1x7135;
-#endif
+#include "config-mode.c"
+#include "lockout.c"
-#ifdef USE_CANDLE_MODE
-uint8_t candle_mode_state(Event event, uint16_t arg);
-uint8_t triangle_wave(uint8_t phase);
-#ifndef CANDLE_AMPLITUDE
-#define CANDLE_AMPLITUDE 25
+#ifdef USE_TINT_RAMPING
+#include "tint-ramping.c"
#endif
+
+#ifdef USE_FACTORY_RESET
+#include "factory-reset.c"
#endif
#ifdef USE_BEACON_MODE
-// beacon timing
-volatile uint8_t beacon_seconds = 2;
+#include "beacon.c"
#endif
-#ifdef USE_VERSION_CHECK
-#define USE_BLINK_DIGIT
-#include "version.h"
-const PROGMEM uint8_t version_number[] = VERSION_NUMBER;
-uint8_t version_check_state(Event event, uint16_t arg);
+#ifdef USE_STROBE_STATE
+#include "strobes.c"
#endif
+
uint8_t off_state(Event event, uint16_t arg) {
// turn emitter off when entering state
if (event == EV_enter_state) {
@@ -1144,491 +1029,6 @@ uint8_t steady_state(Event event, uint16_t arg) {
}
-#ifdef USE_TINT_RAMPING
-uint8_t tint_ramping_state(Event event, uint16_t arg) {
- static int8_t tint_ramp_direction = 1;
- static uint8_t prev_tint = 0;
- // don't activate auto-tint modes unless the user hits the edge
- // and keeps pressing for a while
- static uint8_t past_edge_counter = 0;
- // bugfix: click-click-hold from off to strobes would invoke tint ramping
- // in addition to changing state... so ignore any tint-ramp events which
- // don't look like they were meant to be here
- static uint8_t active = 0;
-
- // click, click, hold: change the tint
- if (event == EV_click3_hold) {
- // reset at beginning of movement
- if (! arg) {
- active = 1; // first frame means this is for us
- past_edge_counter = 0; // doesn't start until user hits the edge
- }
- // ignore event if we weren't the ones who handled the first frame
- if (! active) return EVENT_HANDLED;
-
- // change normal tints
- if ((tint_ramp_direction > 0) && (tint < 254)) {
- tint += 1;
- }
- else if ((tint_ramp_direction < 0) && (tint > 1)) {
- tint -= 1;
- }
- // if the user kept pressing long enough, go the final step
- if (past_edge_counter == 64) {
- past_edge_counter ++;
- tint ^= 1; // 0 -> 1, 254 -> 255
- blip();
- }
- // if tint change stalled, let user know we hit the edge
- else if (prev_tint == tint) {
- if (past_edge_counter == 0) blip();
- // count up but don't wrap back to zero
- if (past_edge_counter < 255) past_edge_counter ++;
- }
- prev_tint = tint;
- set_level(actual_level);
- return EVENT_HANDLED;
- }
-
- // click, click, hold, release: reverse direction for next ramp
- else if (event == EV_click3_hold_release) {
- active = 0; // ignore next hold if it wasn't meant for us
- // reverse
- tint_ramp_direction = -tint_ramp_direction;
- if (tint == 0) tint_ramp_direction = 1;
- else if (tint == 255) tint_ramp_direction = -1;
- // remember tint after battery change
- save_config();
- return EVENT_HANDLED;
- }
-
- return EVENT_NOT_HANDLED;
-}
-#endif // ifdef USE_TINT_RAMPING
-
-
-#ifdef USE_STROBE_STATE
-uint8_t strobe_state(Event event, uint16_t arg) {
- static int8_t ramp_direction = 1;
-
- // 'st' reduces ROM size by avoiding access to a volatile var
- // (maybe I should just make it nonvolatile?)
- strobe_mode_te st = strobe_type;
-
- #ifdef USE_MOMENTARY_MODE
- momentary_mode = 1; // 0 = ramping, 1 = strobes
- #endif
-
- #ifdef USE_CANDLE_MODE
- // pass all events to candle mode, when it's active
- // (the code is in its own pseudo-state to keep things cleaner)
- if (st == candle_mode_e) {
- candle_mode_state(event, arg);
- }
- #endif
-
- if (0) {} // placeholder
- // init anything which needs to be initialized
- else if (event == EV_enter_state) {
- ramp_direction = 1;
- return MISCHIEF_MANAGED;
- }
- // 1 click: off
- else if (event == EV_1click) {
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
- // 2 clicks: rotate through strobe/flasher modes
- else if (event == EV_2clicks) {
- strobe_type = (st + 1) % NUM_STROBES;
- save_config();
- return MISCHIEF_MANAGED;
- }
- // hold: change speed (go faster)
- // or change brightness (brighter)
- else if (event == EV_click1_hold) {
- if (0) {} // placeholder
-
- // party / tactical strobe faster
- #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
- #ifdef USE_TACTICAL_STROBE_MODE
- else if (st <= tactical_strobe_e) {
- #else
- else if (st == party_strobe_e) {
- #endif
- if ((arg & 1) == 0) {
- uint8_t d = strobe_delays[st];
- d -= ramp_direction;
- if (d < 8) d = 8;
- else if (d > 254) d = 254;
- strobe_delays[st] = d;
- }
- }
- #endif
-
- // lightning has no adjustments
- //else if (st == lightning_storm_e) {}
-
- // biking mode brighter
- #ifdef USE_BIKE_FLASHER_MODE
- else if (st == bike_flasher_e) {
- bike_flasher_brightness += ramp_direction;
- if (bike_flasher_brightness < 2) bike_flasher_brightness = 2;
- else if (bike_flasher_brightness > MAX_BIKING_LEVEL) bike_flasher_brightness = MAX_BIKING_LEVEL;
- set_level(bike_flasher_brightness);
- }
- #endif
-
- return MISCHIEF_MANAGED;
- }
- // reverse ramp direction on hold release
- // ... and save new strobe settings
- else if (event == EV_click1_hold_release) {
- ramp_direction = -ramp_direction;
- save_config();
- return MISCHIEF_MANAGED;
- }
- // click, hold: change speed (go slower)
- // or change brightness (dimmer)
- else if (event == EV_click2_hold) {
- ramp_direction = 1;
-
- if (0) {} // placeholder
-
- // party / tactical strobe slower
- #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
- #ifdef USE_TACTICAL_STROBE_MODE
- else if (st <= tactical_strobe_e) {
- #else
- else if (st == party_strobe_e) {
- #endif
- if ((arg & 1) == 0) {
- if (strobe_delays[st] < 255) strobe_delays[st] ++;
- }
- }
- #endif
-
- // lightning has no adjustments
- //else if (st == lightning_storm_e) {}
-
- // biking mode dimmer
- #ifdef USE_BIKE_FLASHER_MODE
- else if (st == bike_flasher_e) {
- if (bike_flasher_brightness > 2)
- bike_flasher_brightness --;
- set_level(bike_flasher_brightness);
- }
- #endif
-
- return MISCHIEF_MANAGED;
- }
- // release hold: save new strobe settings
- else if (event == EV_click2_hold_release) {
- save_config();
- return MISCHIEF_MANAGED;
- }
- #ifdef USE_MOMENTARY_MODE
- // 5 clicks: go to momentary mode (momentary strobe)
- else if (event == EV_5clicks) {
- set_state(momentary_state, 0);
- set_level(0);
- return MISCHIEF_MANAGED;
- }
- #endif
- #if defined(USE_LIGHTNING_MODE) || defined(USE_CANDLE_MODE)
- // clock tick: bump the random seed
- else if (event == EV_tick) {
- // un-reverse after 1 second
- if (arg == TICKS_PER_SECOND) ramp_direction = 1;
-
- pseudo_rand_seed += arg;
- return MISCHIEF_MANAGED;
- }
- #endif
- return EVENT_NOT_HANDLED;
-}
-
-
-#ifdef USE_BEACON_MODE
-inline void beacon_mode_iter() {
- // one iteration of main loop()
- if (! button_last_state) {
- set_level(memorized_level);
- nice_delay_ms(100);
- set_level(0);
- nice_delay_ms(((beacon_seconds) * 1000) - 100);
- }
-}
-#endif // #ifdef USE_BEACON_MODE
-
-
-#if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
-inline void party_tactical_strobe_mode_iter(uint8_t st) {
- // one iteration of main loop()
- uint8_t del = strobe_delays[st];
- // TODO: make tac strobe brightness configurable?
- set_level(STROBE_BRIGHTNESS);
- if (0) {} // placeholde0
- #ifdef USE_PARTY_STROBE_MODE
- else if (st == party_strobe_e) { // party strobe
- #ifdef PARTY_STROBE_ONTIME
- nice_delay_ms(PARTY_STROBE_ONTIME);
- #else
- if (del < 42) delay_zero();
- else nice_delay_ms(1);
- #endif
- }
- #endif
- #ifdef USE_TACTICAL_STROBE_MODE
- else { //tactical strobe
- nice_delay_ms(del >> 1);
- }
- #endif
- set_level(0);
- nice_delay_ms(del); // no return check necessary on final delay
-}
-#endif
-
-#ifdef USE_LIGHTNING_MODE
-inline void lightning_storm_iter() {
- // one iteration of main loop()
- int16_t brightness;
- uint16_t rand_time;
-
- // turn the emitter on at a random level,
- // for a random amount of time between 1ms and 32ms
- //rand_time = 1 << (pseudo_rand() % 7);
- rand_time = pseudo_rand() & 63;
- brightness = 1 << (pseudo_rand() % 7); // 1, 2, 4, 8, 16, 32, 64
- brightness += 1 << (pseudo_rand() % 5); // 2 to 80 now
- brightness += pseudo_rand() % brightness; // 2 to 159 now (w/ low bias)
- if (brightness > MAX_LEVEL) brightness = MAX_LEVEL;
- set_level(brightness);
- nice_delay_ms(rand_time);
-
- // decrease the brightness somewhat more gradually, like lightning
- uint8_t stepdown = brightness >> 3;
- if (stepdown < 1) stepdown = 1;
- while(brightness > 1) {
- nice_delay_ms(rand_time);
- brightness -= stepdown;
- if (brightness < 0) brightness = 0;
- set_level(brightness);
- /*
- if ((brightness < MAX_LEVEL/2) && (! (pseudo_rand() & 15))) {
- brightness <<= 1;
- set_level(brightness);
- }
- */
- if (! (pseudo_rand() & 3)) {
- nice_delay_ms(rand_time);
- set_level(brightness>>1);
- }
- }
-
- // turn the emitter off,
- // for a random amount of time between 1ms and 8192ms
- // (with a low bias)
- rand_time = 1 << (pseudo_rand() % 13);
- rand_time += pseudo_rand() % rand_time;
- set_level(0);
- nice_delay_ms(rand_time); // no return check necessary on final delay
-}
-#endif
-
-#ifdef USE_BIKE_FLASHER_MODE
-inline void bike_flasher_iter() {
- // one iteration of main loop()
- uint8_t burst = bike_flasher_brightness << 1;
- if (burst > MAX_LEVEL) burst = MAX_LEVEL;
- for(uint8_t i=0; i<4; i++) {
- set_level(burst);
- nice_delay_ms(5);
- set_level(bike_flasher_brightness);
- nice_delay_ms(65);
- }
- nice_delay_ms(720); // no return check necessary on final delay
-}
-#endif
-
-#endif // ifdef USE_STROBE_STATE
-
-#ifdef USE_CANDLE_MODE
-uint8_t candle_mode_state(Event event, uint16_t arg) {
- static int8_t ramp_direction = 1;
- #define MAX_CANDLE_LEVEL (RAMP_LENGTH-CANDLE_AMPLITUDE-15)
- static uint8_t candle_wave1 = 0;
- static uint8_t candle_wave2 = 0;
- static uint8_t candle_wave3 = 0;
- static uint8_t candle_wave2_speed = 0;
- // these should add up to 100
- #define CANDLE_WAVE1_MAXDEPTH 30
- #define CANDLE_WAVE2_MAXDEPTH 45
- #define CANDLE_WAVE3_MAXDEPTH 25
- static const uint8_t candle_wave1_depth = CANDLE_WAVE1_MAXDEPTH * CANDLE_AMPLITUDE / 100;
- static uint8_t candle_wave2_depth = CANDLE_WAVE2_MAXDEPTH * CANDLE_AMPLITUDE / 100;
- static uint8_t candle_wave3_depth = CANDLE_WAVE3_MAXDEPTH * CANDLE_AMPLITUDE / 100;
- static uint8_t candle_mode_brightness = 24;
- static uint8_t candle_mode_timer = 0;
- #define TICKS_PER_CANDLE_MINUTE 4096 // about 65 seconds
- #define MINUTES_PER_CANDLE_HALFHOUR 27 // ish
-
- if (event == EV_enter_state) {
- candle_mode_timer = 0; // in case any time was left over from earlier
- ramp_direction = 1;
- return MISCHIEF_MANAGED;
- }
- // 2 clicks: cancel timer
- else if (event == EV_2clicks) {
- // parent state just rotated through strobe/flasher modes,
- // so cancel timer... in case any time was left over from earlier
- candle_mode_timer = 0;
- return MISCHIEF_MANAGED;
- }
- // hold: change brightness (brighter)
- else if (event == EV_click1_hold) {
- // ramp away from extremes
- if (! arg) {
- if (candle_mode_brightness >= MAX_CANDLE_LEVEL) { ramp_direction = -1; }
- else if (candle_mode_brightness <= 1) { ramp_direction = 1; }
- }
- // change brightness, but not too far
- candle_mode_brightness += ramp_direction;
- if (candle_mode_brightness < 1) candle_mode_brightness = 1;
- else if (candle_mode_brightness > MAX_CANDLE_LEVEL) candle_mode_brightness = MAX_CANDLE_LEVEL;
- return MISCHIEF_MANAGED;
- }
- // reverse ramp direction on hold release
- else if (event == EV_click1_hold_release) {
- ramp_direction = -ramp_direction;
- return MISCHIEF_MANAGED;
- }
- // click, hold: change brightness (dimmer)
- else if (event == EV_click2_hold) {
- ramp_direction = 1;
- if (candle_mode_brightness > 1)
- candle_mode_brightness --;
- return MISCHIEF_MANAGED;
- }
- // 3 clicks: add 30m to candle timer
- else if (event == EV_3clicks) {
- if (candle_mode_timer < (255 - MINUTES_PER_CANDLE_HALFHOUR)) {
- // add 30m to the timer
- candle_mode_timer += MINUTES_PER_CANDLE_HALFHOUR;
- // blink to confirm
- set_level(actual_level + 32);
- delay_4ms(2);
- }
- return MISCHIEF_MANAGED;
- }
- // clock tick: animate candle brightness
- else if (event == EV_tick) {
- // un-reverse after 1 second
- if (arg == TICKS_PER_SECOND) ramp_direction = 1;
-
- // self-timer dims the light during the final minute
- uint8_t subtract = 0;
- if (candle_mode_timer == 1) {
- subtract = ((candle_mode_brightness+CANDLE_AMPLITUDE)
- * ((arg & (TICKS_PER_CANDLE_MINUTE-1)) >> 4))
- >> 8;
- }
- // we passed a minute mark, decrease timer if it's running
- if ((arg & (TICKS_PER_CANDLE_MINUTE-1)) == (TICKS_PER_CANDLE_MINUTE - 1)) {
- if (candle_mode_timer > 0) {
- candle_mode_timer --;
- //set_level(0); delay_4ms(2);
- // if the timer ran out, shut off
- if (! candle_mode_timer) {
- set_state(off_state, 0);
- }
- }
- }
- // 3-oscillator synth for a relatively organic pattern
- uint8_t add;
- add = ((triangle_wave(candle_wave1) * candle_wave1_depth) >> 8)
- + ((triangle_wave(candle_wave2) * candle_wave2_depth) >> 8)
- + ((triangle_wave(candle_wave3) * candle_wave3_depth) >> 8);
- int8_t brightness = candle_mode_brightness + add - subtract;
- if (brightness < 0) { brightness = 0; }
- set_level(brightness);
-
- // wave1: slow random LFO
- // TODO: make wave slower and more erratic?
- if ((arg & 1) == 0) candle_wave1 += pseudo_rand() & 1;
- // wave2: medium-speed erratic LFO
- candle_wave2 += candle_wave2_speed;
- // wave3: erratic fast wave
- candle_wave3 += pseudo_rand() % 37;
- // S&H on wave2 frequency to make it more erratic
- if ((pseudo_rand() & 0b00111111) == 0)
- candle_wave2_speed = pseudo_rand() % 13;
- // downward sawtooth on wave2 depth to simulate stabilizing
- if ((candle_wave2_depth > 0) && ((pseudo_rand() & 0b00111111) == 0))
- candle_wave2_depth --;
- // random sawtooth retrigger
- if (pseudo_rand() == 0) {
- // random amplitude
- //candle_wave2_depth = 2 + (pseudo_rand() % ((CANDLE_WAVE2_MAXDEPTH * CANDLE_AMPLITUDE / 100) - 2));
- candle_wave2_depth = pseudo_rand() % (CANDLE_WAVE2_MAXDEPTH * CANDLE_AMPLITUDE / 100);
- //candle_wave3_depth = 5;
- candle_wave2 = 0;
- }
- // downward sawtooth on wave3 depth to simulate stabilizing
- if ((candle_wave3_depth > 2) && ((pseudo_rand() & 0b00011111) == 0))
- candle_wave3_depth --;
- if ((pseudo_rand() & 0b01111111) == 0)
- // random amplitude
- //candle_wave3_depth = 2 + (pseudo_rand() % ((CANDLE_WAVE3_MAXDEPTH * CANDLE_AMPLITUDE / 100) - 2));
- candle_wave3_depth = pseudo_rand() % (CANDLE_WAVE3_MAXDEPTH * CANDLE_AMPLITUDE / 100);
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
-#endif // #ifdef USE_CANDLE_MODE
-
-
-#ifdef USE_BORING_STROBE_STATE
-uint8_t boring_strobe_state(Event event, uint16_t arg) {
- // police strobe and SOS, meh
- // 'st' reduces ROM size by avoiding access to a volatile var
- // (maybe I should just make it nonvolatile?)
- uint8_t st = boring_strobe_type;
-
- if (event == EV_enter_state) {
- return MISCHIEF_MANAGED;
- }
- // 1 click: off
- else if (event == EV_1click) {
- // reset to police strobe for next time
- boring_strobe_type = 0;
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
- // 2 clicks: rotate through strobe/flasher modes
- else if (event == EV_2clicks) {
- boring_strobe_type = (st + 1) % NUM_BORING_STROBES;
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
-
-#ifdef USE_POLICE_STROBE_MODE
-inline void police_strobe_iter() {
- // one iteration of main loop()
- // flash at 16 Hz then 8 Hz, 8 times each
- for (uint8_t del=41; del<100; del+=41) {
- for (uint8_t f=0; f<8; f++) {
- set_level(STROBE_BRIGHTNESS);
- nice_delay_ms(del >> 1);
- set_level(0);
- nice_delay_ms(del);
- }
- }
-}
-#endif
-#endif // #ifdef USE_BORING_STROBE_STATE
-
#ifdef USE_SOS_MODE
#ifdef USE_SOS_MODE_IN_BLINKY_GROUP
uint8_t sos_state(Event event, uint16_t arg) {
@@ -1740,44 +1140,6 @@ uint8_t tempcheck_state(Event event, uint16_t arg) {
#endif
-#ifdef USE_BEACON_MODE
-uint8_t beacon_state(Event 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
- // 2 clicks: next mode
- else if (event == EV_2clicks) {
- #ifdef USE_SOS_MODE_IN_BLINKY_GROUP
- set_state(sos_state, 0);
- #elif defined(USE_THERMAL_REGULATION)
- set_state(tempcheck_state, 0);
- #elif defined(USE_BATTCHECK)
- set_state(battcheck_state, 0);
- #endif
- return MISCHIEF_MANAGED;
- }
- // hold: configure beacon timing
- else if (event == EV_click1_hold) {
- if (0 == (arg % TICKS_PER_SECOND)) {
- blink_confirm(1);
- }
- return MISCHIEF_MANAGED;
- }
- // release hold: save new timing
- else if (event == EV_click1_hold_release) {
- beacon_seconds = 1 + (arg / TICKS_PER_SECOND);
- save_config();
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
-#endif // #ifdef USE_BEACON_MODE
-
-
#ifdef USE_GOODNIGHT_MODE
#define GOODNIGHT_TICKS_PER_STEPDOWN (GOODNIGHT_TIME*TICKS_PER_SECOND*60L/GOODNIGHT_LEVEL)
uint8_t goodnight_state(Event event, uint16_t arg) {
@@ -1826,149 +1188,6 @@ uint8_t goodnight_state(Event event, uint16_t arg) {
#endif
-uint8_t lockout_state(Event event, uint16_t arg) {
- #ifdef MOON_DURING_LOCKOUT_MODE
- // momentary(ish) moon mode during lockout
- // button is being held
- #ifdef USE_AUX_RGB_LEDS
- // don't turn on during RGB aux LED configuration
- if (event == EV_click3_hold) { set_level(0); } else
- #endif
- if ((event & (B_CLICK | B_PRESS)) == (B_CLICK | B_PRESS)) {
- #ifdef LOCKOUT_MOON_LOWEST
- // Use lowest moon configured
- uint8_t lvl = ramp_floors[0];
- if (ramp_floors[1] < lvl) lvl = ramp_floors[1];
- set_level(lvl);
- #elif defined(LOCKOUT_MOON_FANCY)
- if ((event & 0x0f) == 2) {
- set_level(ramp_floors[ramp_style^1]);
- } else {
- set_level(ramp_floors[ramp_style]);
- }
- #else
- // Use moon from current ramp
- set_level(nearest_level(1));
- #endif
- }
- // button was released
- else if ((event & (B_CLICK | B_PRESS)) == (B_CLICK)) {
- 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
- #elif defined(USE_AUX_RGB_LEDS)
- if (event == EV_enter_state) {
- rgb_led_update(rgb_led_lockout_mode, 0);
- } else
- #endif
- if (event == EV_tick) {
- if (arg > HOLD_TIMEOUT) {
- go_to_standby = 1;
- #ifdef USE_INDICATOR_LED
- indicator_led(indicator_led_mode >> 2);
- #elif defined(USE_AUX_RGB_LEDS)
- rgb_led_update(rgb_led_lockout_mode, arg);
- #endif
- }
- return MISCHIEF_MANAGED;
- }
- #if defined(TICK_DURING_STANDBY) && (defined(USE_INDICATOR_LED) || defined(USE_AUX_RGB_LEDS))
- else if (event == EV_sleep_tick) {
- #if defined(USE_INDICATOR_LED)
- if ((indicator_led_mode & 0b00001100) == 0b00001100) {
- indicator_blink(arg);
- }
- #elif defined(USE_AUX_RGB_LEDS)
- rgb_led_update(rgb_led_lockout_mode, arg);
- #endif
- return MISCHIEF_MANAGED;
- }
- #endif
- // 4 clicks: exit and turn on
- else if (event == EV_4clicks) {
- blink_confirm(1);
- set_state(steady_state, memorized_level);
- return MISCHIEF_MANAGED;
- }
- // 4 clicks, but hold last: exit and start at floor
- else if (event == EV_click4_hold) {
- blink_confirm(1);
- set_state(steady_state, 1);
- return MISCHIEF_MANAGED;
- }
-
- ////////// Every action below here is blocked in the simple UI //////////
- #ifdef USE_SIMPLE_UI
- if (simple_ui_active) {
- return EVENT_NOT_HANDLED;
- }
- #endif
-
- #if defined(USE_INDICATOR_LED)
- // 7 clicks: rotate through indicator LED modes (lockout mode)
- else if (event == EV_7clicks) {
- #if defined(USE_INDICATOR_LED)
- uint8_t mode = indicator_led_mode >> 2;
- #ifdef TICK_DURING_STANDBY
- mode = (mode + 1) & 3;
- #else
- mode = (mode + 1) % 3;
- #endif
- #ifdef INDICATOR_LED_SKIP_LOW
- if (mode == 1) { mode ++; }
- #endif
- indicator_led_mode = (mode << 2) + (indicator_led_mode & 0x03);
- indicator_led(mode);
- #elif defined(USE_AUX_RGB_LEDS)
- #endif
- save_config();
- return MISCHIEF_MANAGED;
- }
- #elif defined(USE_AUX_RGB_LEDS)
- // 7 clicks: change RGB aux LED pattern
- else if (event == EV_7clicks) {
- uint8_t mode = (rgb_led_lockout_mode >> 4) + 1;
- mode = mode % RGB_LED_NUM_PATTERNS;
- rgb_led_lockout_mode = (mode << 4) | (rgb_led_lockout_mode & 0x0f);
- rgb_led_update(rgb_led_lockout_mode, 0);
- save_config();
- blink_confirm(1);
- return MISCHIEF_MANAGED;
- }
- // 7H: change RGB aux LED color
- else if (event == EV_click7_hold) {
- setting_rgb_mode_now = 1;
- if (0 == (arg & 0x3f)) {
- uint8_t mode = (rgb_led_lockout_mode & 0x0f) + 1;
- mode = mode % RGB_LED_NUM_COLORS;
- rgb_led_lockout_mode = mode | (rgb_led_lockout_mode & 0xf0);
- //save_config();
- }
- rgb_led_update(rgb_led_lockout_mode, arg);
- return MISCHIEF_MANAGED;
- }
- // 7H, release: save new color
- else if (event == EV_click7_hold_release) {
- setting_rgb_mode_now = 0;
- save_config();
- return MISCHIEF_MANAGED;
- }
- #endif
-
- return EVENT_NOT_HANDLED;
-}
-
-
#ifdef USE_MOMENTARY_MODE
uint8_t momentary_state(Event event, uint16_t arg) {
// TODO: momentary strobe here? (for light painting)
@@ -2040,41 +1259,6 @@ uint8_t version_check_state(Event event, uint16_t arg) {
#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 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();
- save_config();
- //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;
-}
-
#ifdef USE_RAMP_CONFIG
void ramp_config_save() {
// parse values
@@ -2131,92 +1315,6 @@ uint8_t thermal_config_state(Event event, uint16_t arg) {
#endif // #ifdef USE_THERMAL_REGULATION
-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 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) {
@@ -2429,55 +1527,6 @@ void rgb_led_voltage_readout(uint8_t bright) {
#endif
-#ifdef USE_FACTORY_RESET
-void factory_reset() {
- // display a warning for a few seconds before doing the actual reset,
- // so the user has time to abort if they want
- #define SPLODEY_TIME 2500
- #define SPLODEY_STEPS 64
- #define SPLODEY_TIME_PER_STEP (SPLODEY_TIME/SPLODEY_STEPS)
- uint8_t bright;
- uint8_t reset = 1;
- // wind up to an explosion
- for (bright=0; bright<SPLODEY_STEPS; bright++) {
- set_level(bright);
- nice_delay_ms(SPLODEY_TIME_PER_STEP/2);
- set_level(bright>>1);
- nice_delay_ms(SPLODEY_TIME_PER_STEP/2);
- if (! button_is_pressed()) {
- reset = 0;
- break;
- }
- }
- // explode, if button pressed long enough
- if (reset) {
- #ifdef USE_THERMAL_REGULATION
- // auto-calibrate temperature... assume current temperature is 21 C
- config_state_values[0] = 21;
- config_state_values[1] = 0;
- thermal_config_save();
- #endif
- // save all settings to eeprom
- // (assuming they're all at default because we haven't loaded them yet)
- save_config();
-
- bright = MAX_LEVEL;
- for (; bright > 0; bright--) {
- set_level(bright);
- nice_delay_ms(SPLODEY_TIME_PER_STEP/8);
- }
- }
- // explosion cancelled, fade away
- else {
- for (; bright > 0; bright--) {
- set_level(bright);
- nice_delay_ms(SPLODEY_TIME_PER_STEP/3);
- }
- }
-}
-#endif
-
-
void load_config() {
if (load_eeprom()) {
ramp_style = eeprom[ramp_style_e];
diff --git a/spaghetti-monster/anduril/beacon.c b/spaghetti-monster/anduril/beacon.c
new file mode 100644
index 0000000..ac56632
--- /dev/null
+++ b/spaghetti-monster/anduril/beacon.c
@@ -0,0 +1,72 @@
+/*
+ * beacon.c: Beacon mode for Anduril.
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef BEACON_C
+#define BEACON_C
+
+#include "beacon.h"
+
+inline void beacon_mode_iter() {
+ // one iteration of main loop()
+ if (! button_last_state) {
+ set_level(memorized_level);
+ nice_delay_ms(100);
+ set_level(0);
+ nice_delay_ms(((beacon_seconds) * 1000) - 100);
+ }
+}
+
+uint8_t beacon_state(Event 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
+ // 2 clicks: next mode
+ else if (event == EV_2clicks) {
+ #ifdef USE_SOS_MODE_IN_BLINKY_GROUP
+ set_state(sos_state, 0);
+ #elif defined(USE_THERMAL_REGULATION)
+ set_state(tempcheck_state, 0);
+ #elif defined(USE_BATTCHECK)
+ set_state(battcheck_state, 0);
+ #endif
+ return MISCHIEF_MANAGED;
+ }
+ // hold: configure beacon timing
+ else if (event == EV_click1_hold) {
+ if (0 == (arg % TICKS_PER_SECOND)) {
+ blink_confirm(1);
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // release hold: save new timing
+ else if (event == EV_click1_hold_release) {
+ beacon_seconds = 1 + (arg / TICKS_PER_SECOND);
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/beacon.h b/spaghetti-monster/anduril/beacon.h
new file mode 100644
index 0000000..ba885c7
--- /dev/null
+++ b/spaghetti-monster/anduril/beacon.h
@@ -0,0 +1,31 @@
+/*
+ * beacon.h: Beacon mode for Anduril.
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef BEACON_H
+#define BEACON_H
+
+// beacon timing
+volatile uint8_t beacon_seconds = 2;
+
+// beacon mode
+uint8_t beacon_state(Event event, uint16_t arg);
+inline void beacon_mode_iter();
+
+
+#endif
diff --git a/spaghetti-monster/anduril/cfg-default.h b/spaghetti-monster/anduril/cfg-default.h
new file mode 100644
index 0000000..1405422
--- /dev/null
+++ b/spaghetti-monster/anduril/cfg-default.h
@@ -0,0 +1,103 @@
+/*
+ * cfg-default.h: Default configuration for Anduril.
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef CFG_DEFAULT_H
+#define CFG_DEFAULT_H
+
+/********* User-configurable options *********/
+#define USE_LVP
+
+// parameters for this defined below or per-driver
+#define USE_THERMAL_REGULATION
+#define DEFAULT_THERM_CEIL 45 // try not to get hotter than this
+
+#define USE_FACTORY_RESET
+//#define USE_SOFT_FACTORY_RESET // only needed on models which can't use hold-button-at-boot
+
+// dual-switch support (second switch is a tail clicky)
+// (currently incompatible with factory reset)
+//#define START_AT_MEMORIZED_LEVEL
+
+// include a function to blink out the firmware version
+#define USE_VERSION_CHECK
+
+// short blip when crossing from "click" to "hold" from off
+// (helps the user hit moon mode exactly, instead of holding too long
+// or too short)
+#define MOON_TIMING_HINT // only applies if B_TIMING_ON == B_PRESS_T
+// short blips while ramping
+#define BLINK_AT_RAMP_MIDDLE
+//#define BLINK_AT_RAMP_FLOOR
+#define BLINK_AT_RAMP_CEIL
+//#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
+
+// add a runtime option to switch between automatic memory (default)
+// and manual memory (only available if compiled in)
+// (manual memory makes 1-click-from-off start at the same level each time)
+// (the level can be set explicitly with 5 clicks from on,
+// or the user can go back to automatic with click-click-click-click-hold)
+#define USE_MANUAL_MEMORY
+
+// battery readout style (pick one)
+#define BATTCHECK_VpT
+//#define BATTCHECK_8bars // FIXME: breaks build
+//#define BATTCHECK_4bars // FIXME: breaks build
+
+// enable/disable various strobe modes
+#define USE_BIKE_FLASHER_MODE
+#define USE_PARTY_STROBE_MODE
+#define USE_TACTICAL_STROBE_MODE
+#define USE_LIGHTNING_MODE
+#define USE_CANDLE_MODE
+
+// enable sunset (goodnight) mode
+#define USE_GOODNIGHT_MODE
+#define GOODNIGHT_TIME 60 // minutes (approximately)
+#define GOODNIGHT_LEVEL 24 // ~11 lm
+
+// enable beacon mode
+#define USE_BEACON_MODE
+
+// enable momentary mode
+#define USE_MOMENTARY_MODE
+
+// Include a simplified UI for non-enthusiasts?
+#define USE_SIMPLE_UI
+
+// make the ramps configurable by the user
+#define USE_RAMP_CONFIG
+
+// boring strobes nobody really likes, but sometimes flashlight companies want
+// (these replace the fun strobe group,
+// so don't enable them at the same time as any of the above strobes)
+//#define USE_POLICE_STROBE_MODE
+//#define USE_SOS_MODE
+//#define USE_SOS_MODE_IN_FF_GROUP // put SOS in the "boring strobes" mode
+//#define USE_SOS_MODE_IN_BLINKY_GROUP // put SOS in the blinkies mode group
+
+// cut clock speed at very low modes for better efficiency
+// (defined here so config files can override it)
+#define USE_DYNAMIC_UNDERCLOCKING
+
+
+#endif
diff --git a/spaghetti-monster/anduril/config-mode.c b/spaghetti-monster/anduril/config-mode.c
new file mode 100644
index 0000000..a62cec0
--- /dev/null
+++ b/spaghetti-monster/anduril/config-mode.c
@@ -0,0 +1,153 @@
+/*
+ * config-mode.c: Config mode base functions for Anduril.
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef CONFIG_MODE_C
+#define CONFIG_MODE_C
+
+#include "config-mode.h"
+
+// 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;
+
+
+// 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 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();
+ save_config();
+ //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;
+}
+
+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 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;
+}
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/config-mode.h b/spaghetti-monster/anduril/config-mode.h
new file mode 100644
index 0000000..7bf51fc
--- /dev/null
+++ b/spaghetti-monster/anduril/config-mode.h
@@ -0,0 +1,34 @@
+/*
+ * config-mode.h: Config mode base functions for Anduril.
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef CONFIG_MODE_H
+#define CONFIG_MODE_H
+
+#include "fsm-events.h"
+
+#define MAX_CONFIG_VALUES 3
+uint8_t config_state_values[MAX_CONFIG_VALUES];
+
+// simple numeric entry config menu
+uint8_t config_state_base(Event event, uint16_t arg,
+ uint8_t num_config_steps,
+ void (*savefunc)());
+
+
+#endif
diff --git a/spaghetti-monster/anduril/factory-reset-fsm.h b/spaghetti-monster/anduril/factory-reset-fsm.h
new file mode 100644
index 0000000..a8fb0d9
--- /dev/null
+++ b/spaghetti-monster/anduril/factory-reset-fsm.h
@@ -0,0 +1,28 @@
+/*
+ * factory-reset-fsm.h: FSM config options to enable factory reset in Anduril.
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef FACTORY_RESET_FSM_H
+#define FACTORY_RESET_FSM_H
+
+#ifdef USE_SOFT_FACTORY_RESET
+#define USE_REBOOT
+#endif
+
+
+#endif
diff --git a/spaghetti-monster/anduril/factory-reset.c b/spaghetti-monster/anduril/factory-reset.c
new file mode 100644
index 0000000..fe961b1
--- /dev/null
+++ b/spaghetti-monster/anduril/factory-reset.c
@@ -0,0 +1,73 @@
+/*
+ * factory-reset.c: Factory reset functions for Anduril.
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef FACTORY_RESET_C
+#define FACTORY_RESET_C
+
+#include "factory-reset.h"
+
+void factory_reset() {
+ // display a warning for a few seconds before doing the actual reset,
+ // so the user has time to abort if they want
+ #define SPLODEY_TIME 2500
+ #define SPLODEY_STEPS 64
+ #define SPLODEY_TIME_PER_STEP (SPLODEY_TIME/SPLODEY_STEPS)
+ uint8_t bright;
+ uint8_t reset = 1;
+ // wind up to an explosion
+ for (bright=0; bright<SPLODEY_STEPS; bright++) {
+ set_level(bright);
+ nice_delay_ms(SPLODEY_TIME_PER_STEP/2);
+ set_level(bright>>1);
+ nice_delay_ms(SPLODEY_TIME_PER_STEP/2);
+ if (! button_is_pressed()) {
+ reset = 0;
+ break;
+ }
+ }
+ // explode, if button pressed long enough
+ if (reset) {
+ #ifdef USE_THERMAL_REGULATION
+ // auto-calibrate temperature... assume current temperature is 21 C
+ config_state_values[0] = 21;
+ config_state_values[1] = 0;
+ thermal_config_save();
+ #endif
+ // save all settings to eeprom
+ // (assuming they're all at default because we haven't loaded them yet)
+ save_config();
+
+ bright = MAX_LEVEL;
+ for (; bright > 0; bright--) {
+ set_level(bright);
+ nice_delay_ms(SPLODEY_TIME_PER_STEP/8);
+ }
+ }
+ // explosion cancelled, fade away
+ else {
+ for (; bright > 0; bright--) {
+ set_level(bright);
+ nice_delay_ms(SPLODEY_TIME_PER_STEP/3);
+ }
+ }
+}
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/factory-reset.h b/spaghetti-monster/anduril/factory-reset.h
new file mode 100644
index 0000000..9f0af38
--- /dev/null
+++ b/spaghetti-monster/anduril/factory-reset.h
@@ -0,0 +1,26 @@
+/*
+ * factory-reset.h: Factory reset functions for Anduril.
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef FACTORY_RESET_H
+#define FACTORY_RESET_H
+
+void factory_reset();
+
+
+#endif
diff --git a/spaghetti-monster/anduril/ff-strobes.c b/spaghetti-monster/anduril/ff-strobes.c
new file mode 100644
index 0000000..c0b655e
--- /dev/null
+++ b/spaghetti-monster/anduril/ff-strobes.c
@@ -0,0 +1,66 @@
+/*
+ * ff-strobes.c: Fireflies Flashlights strobe modes for Anduril.
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef FF_STROBES_C
+#define FF_STROBES_C
+
+#include "ff-strobes.h"
+
+uint8_t boring_strobe_state(Event event, uint16_t arg) {
+ // police strobe and SOS, meh
+ // 'st' reduces ROM size by avoiding access to a volatile var
+ // (maybe I should just make it nonvolatile?)
+ uint8_t st = boring_strobe_type;
+
+ if (event == EV_enter_state) {
+ return MISCHIEF_MANAGED;
+ }
+ // 1 click: off
+ else if (event == EV_1click) {
+ // reset to police strobe for next time
+ boring_strobe_type = 0;
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks: rotate through strobe/flasher modes
+ else if (event == EV_2clicks) {
+ boring_strobe_type = (st + 1) % NUM_BORING_STROBES;
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+#ifdef USE_POLICE_STROBE_MODE
+inline void police_strobe_iter() {
+ // one iteration of main loop()
+ // flash at 16 Hz then 8 Hz, 8 times each
+ for (uint8_t del=41; del<100; del+=41) {
+ for (uint8_t f=0; f<8; f++) {
+ set_level(STROBE_BRIGHTNESS);
+ nice_delay_ms(del >> 1);
+ set_level(0);
+ nice_delay_ms(del);
+ }
+ }
+}
+#endif
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/ff-strobes.h b/spaghetti-monster/anduril/ff-strobes.h
new file mode 100644
index 0000000..0c57e2c
--- /dev/null
+++ b/spaghetti-monster/anduril/ff-strobes.h
@@ -0,0 +1,32 @@
+/*
+ * ff-strobes.h: Fireflies Flashlights strobe modes for Anduril.
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef FF_STROBES_H
+#define FF_STROBES_H
+
+uint8_t boring_strobe_state(Event event, uint16_t arg);
+volatile uint8_t boring_strobe_type = 0;
+void sos_blink(uint8_t num, uint8_t dah);
+#ifdef USE_POLICE_STROBE_MODE
+inline void police_strobe_iter();
+#endif
+#define NUM_BORING_STROBES 2
+
+
+#endif
diff --git a/spaghetti-monster/anduril/lockout.c b/spaghetti-monster/anduril/lockout.c
new file mode 100644
index 0000000..56b1051
--- /dev/null
+++ b/spaghetti-monster/anduril/lockout.c
@@ -0,0 +1,169 @@
+/*
+ * lockout.c: Lockout mode for Anduril.
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef LOCKOUT_C
+#define LOCKOUT_C
+
+#include "lockout.h"
+
+uint8_t lockout_state(Event event, uint16_t arg) {
+ #ifdef MOON_DURING_LOCKOUT_MODE
+ // momentary(ish) moon mode during lockout
+ // button is being held
+ #ifdef USE_AUX_RGB_LEDS
+ // don't turn on during RGB aux LED configuration
+ if (event == EV_click3_hold) { set_level(0); } else
+ #endif
+ if ((event & (B_CLICK | B_PRESS)) == (B_CLICK | B_PRESS)) {
+ #ifdef LOCKOUT_MOON_LOWEST
+ // Use lowest moon configured
+ uint8_t lvl = ramp_floors[0];
+ if (ramp_floors[1] < lvl) lvl = ramp_floors[1];
+ set_level(lvl);
+ #elif defined(LOCKOUT_MOON_FANCY)
+ if ((event & 0x0f) == 2) {
+ set_level(ramp_floors[ramp_style^1]);
+ } else {
+ set_level(ramp_floors[ramp_style]);
+ }
+ #else
+ // Use moon from current ramp
+ set_level(nearest_level(1));
+ #endif
+ }
+ // button was released
+ else if ((event & (B_CLICK | B_PRESS)) == (B_CLICK)) {
+ 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
+ #elif defined(USE_AUX_RGB_LEDS)
+ if (event == EV_enter_state) {
+ rgb_led_update(rgb_led_lockout_mode, 0);
+ } else
+ #endif
+ if (event == EV_tick) {
+ if (arg > HOLD_TIMEOUT) {
+ go_to_standby = 1;
+ #ifdef USE_INDICATOR_LED
+ indicator_led(indicator_led_mode >> 2);
+ #elif defined(USE_AUX_RGB_LEDS)
+ rgb_led_update(rgb_led_lockout_mode, arg);
+ #endif
+ }
+ return MISCHIEF_MANAGED;
+ }
+ #if defined(TICK_DURING_STANDBY) && (defined(USE_INDICATOR_LED) || defined(USE_AUX_RGB_LEDS))
+ else if (event == EV_sleep_tick) {
+ #if defined(USE_INDICATOR_LED)
+ if ((indicator_led_mode & 0b00001100) == 0b00001100) {
+ indicator_blink(arg);
+ }
+ #elif defined(USE_AUX_RGB_LEDS)
+ rgb_led_update(rgb_led_lockout_mode, arg);
+ #endif
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ // 4 clicks: exit and turn on
+ else if (event == EV_4clicks) {
+ blink_confirm(1);
+ set_state(steady_state, memorized_level);
+ return MISCHIEF_MANAGED;
+ }
+ // 4 clicks, but hold last: exit and start at floor
+ else if (event == EV_click4_hold) {
+ blink_confirm(1);
+ set_state(steady_state, 1);
+ return MISCHIEF_MANAGED;
+ }
+
+ ////////// Every action below here is blocked in the simple UI //////////
+ #ifdef USE_SIMPLE_UI
+ if (simple_ui_active) {
+ return EVENT_NOT_HANDLED;
+ }
+ #endif
+
+ #if defined(USE_INDICATOR_LED)
+ // 7 clicks: rotate through indicator LED modes (lockout mode)
+ else if (event == EV_7clicks) {
+ #if defined(USE_INDICATOR_LED)
+ uint8_t mode = indicator_led_mode >> 2;
+ #ifdef TICK_DURING_STANDBY
+ mode = (mode + 1) & 3;
+ #else
+ mode = (mode + 1) % 3;
+ #endif
+ #ifdef INDICATOR_LED_SKIP_LOW
+ if (mode == 1) { mode ++; }
+ #endif
+ indicator_led_mode = (mode << 2) + (indicator_led_mode & 0x03);
+ indicator_led(mode);
+ #elif defined(USE_AUX_RGB_LEDS)
+ #endif
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ #elif defined(USE_AUX_RGB_LEDS)
+ // 7 clicks: change RGB aux LED pattern
+ else if (event == EV_7clicks) {
+ uint8_t mode = (rgb_led_lockout_mode >> 4) + 1;
+ mode = mode % RGB_LED_NUM_PATTERNS;
+ rgb_led_lockout_mode = (mode << 4) | (rgb_led_lockout_mode & 0x0f);
+ rgb_led_update(rgb_led_lockout_mode, 0);
+ save_config();
+ blink_confirm(1);
+ return MISCHIEF_MANAGED;
+ }
+ // 7H: change RGB aux LED color
+ else if (event == EV_click7_hold) {
+ setting_rgb_mode_now = 1;
+ if (0 == (arg & 0x3f)) {
+ uint8_t mode = (rgb_led_lockout_mode & 0x0f) + 1;
+ mode = mode % RGB_LED_NUM_COLORS;
+ rgb_led_lockout_mode = mode | (rgb_led_lockout_mode & 0xf0);
+ //save_config();
+ }
+ rgb_led_update(rgb_led_lockout_mode, arg);
+ return MISCHIEF_MANAGED;
+ }
+ // 7H, release: save new color
+ else if (event == EV_click7_hold_release) {
+ setting_rgb_mode_now = 0;
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+
+ return EVENT_NOT_HANDLED;
+}
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/lockout.h b/spaghetti-monster/anduril/lockout.h
new file mode 100644
index 0000000..4268595
--- /dev/null
+++ b/spaghetti-monster/anduril/lockout.h
@@ -0,0 +1,31 @@
+/*
+ * lockout.h: Lockout mode for Anduril.
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef LOCKOUT_H
+#define LOCKOUT_H
+
+// should lockout mode function as a momentary moon mode?
+#define MOON_DURING_LOCKOUT_MODE
+// if enabled, 2nd lockout click goes to the other ramp's floor level
+#define LOCKOUT_MOON_FANCY
+// soft lockout
+uint8_t lockout_state(Event event, uint16_t arg);
+
+
+#endif
diff --git a/spaghetti-monster/anduril/strobes-fsm.h b/spaghetti-monster/anduril/strobes-fsm.h
new file mode 100644
index 0000000..6514d5f
--- /dev/null
+++ b/spaghetti-monster/anduril/strobes-fsm.h
@@ -0,0 +1,35 @@
+/*
+ * strobes-fsm.h: FSM config for strobe modes in Anduril.
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef STROBES_FSM_H
+#define STROBES_FSM_H
+
+// enable the random number generator if we need it
+#if defined(USE_LIGHTNING_MODE) || defined(USE_CANDLE_MODE)
+#define USE_PSEUDO_RAND
+#endif
+
+// the presence of strobe mode(s) affects how many eeprom bytes we need,
+// so it's relevant for FSM configuration
+#if defined(USE_CANDLE_MODE) || defined(USE_BIKE_FLASHER_MODE) || defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE) || defined(USE_LIGHTNING_MODE)
+#define USE_STROBE_STATE
+#endif
+
+
+#endif
diff --git a/spaghetti-monster/anduril/strobes.c b/spaghetti-monster/anduril/strobes.c
new file mode 100644
index 0000000..42d085d
--- /dev/null
+++ b/spaghetti-monster/anduril/strobes.c
@@ -0,0 +1,403 @@
+/*
+ * strobes.c: Strobe modes for Anduril.
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef STROBES_C
+#define STROBES_C
+
+#include "strobes.h"
+
+#ifdef USE_STROBE_STATE
+uint8_t strobe_state(Event event, uint16_t arg) {
+ static int8_t ramp_direction = 1;
+
+ // 'st' reduces ROM size by avoiding access to a volatile var
+ // (maybe I should just make it nonvolatile?)
+ strobe_mode_te st = strobe_type;
+
+ #ifdef USE_MOMENTARY_MODE
+ momentary_mode = 1; // 0 = ramping, 1 = strobes
+ #endif
+
+ #ifdef USE_CANDLE_MODE
+ // pass all events to candle mode, when it's active
+ // (the code is in its own pseudo-state to keep things cleaner)
+ if (st == candle_mode_e) {
+ candle_mode_state(event, arg);
+ }
+ #endif
+
+ if (0) {} // placeholder
+ // init anything which needs to be initialized
+ else if (event == EV_enter_state) {
+ ramp_direction = 1;
+ return MISCHIEF_MANAGED;
+ }
+ // 1 click: off
+ else if (event == EV_1click) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks: rotate through strobe/flasher modes
+ else if (event == EV_2clicks) {
+ strobe_type = (st + 1) % NUM_STROBES;
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ // hold: change speed (go faster)
+ // or change brightness (brighter)
+ else if (event == EV_click1_hold) {
+ if (0) {} // placeholder
+
+ // party / tactical strobe faster
+ #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
+ #ifdef USE_TACTICAL_STROBE_MODE
+ else if (st <= tactical_strobe_e) {
+ #else
+ else if (st == party_strobe_e) {
+ #endif
+ if ((arg & 1) == 0) {
+ uint8_t d = strobe_delays[st];
+ d -= ramp_direction;
+ if (d < 8) d = 8;
+ else if (d > 254) d = 254;
+ strobe_delays[st] = d;
+ }
+ }
+ #endif
+
+ // lightning has no adjustments
+ //else if (st == lightning_storm_e) {}
+
+ // biking mode brighter
+ #ifdef USE_BIKE_FLASHER_MODE
+ else if (st == bike_flasher_e) {
+ bike_flasher_brightness += ramp_direction;
+ if (bike_flasher_brightness < 2) bike_flasher_brightness = 2;
+ else if (bike_flasher_brightness > MAX_BIKING_LEVEL) bike_flasher_brightness = MAX_BIKING_LEVEL;
+ set_level(bike_flasher_brightness);
+ }
+ #endif
+
+ return MISCHIEF_MANAGED;
+ }
+ // reverse ramp direction on hold release
+ // ... and save new strobe settings
+ else if (event == EV_click1_hold_release) {
+ ramp_direction = -ramp_direction;
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ // click, hold: change speed (go slower)
+ // or change brightness (dimmer)
+ else if (event == EV_click2_hold) {
+ ramp_direction = 1;
+
+ if (0) {} // placeholder
+
+ // party / tactical strobe slower
+ #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
+ #ifdef USE_TACTICAL_STROBE_MODE
+ else if (st <= tactical_strobe_e) {
+ #else
+ else if (st == party_strobe_e) {
+ #endif
+ if ((arg & 1) == 0) {
+ if (strobe_delays[st] < 255) strobe_delays[st] ++;
+ }
+ }
+ #endif
+
+ // lightning has no adjustments
+ //else if (st == lightning_storm_e) {}
+
+ // biking mode dimmer
+ #ifdef USE_BIKE_FLASHER_MODE
+ else if (st == bike_flasher_e) {
+ if (bike_flasher_brightness > 2)
+ bike_flasher_brightness --;
+ set_level(bike_flasher_brightness);
+ }
+ #endif
+
+ return MISCHIEF_MANAGED;
+ }
+ // release hold: save new strobe settings
+ else if (event == EV_click2_hold_release) {
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ #ifdef USE_MOMENTARY_MODE
+ // 5 clicks: go to momentary mode (momentary strobe)
+ else if (event == EV_5clicks) {
+ set_state(momentary_state, 0);
+ set_level(0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ #if defined(USE_LIGHTNING_MODE) || defined(USE_CANDLE_MODE)
+ // clock tick: bump the random seed
+ else if (event == EV_tick) {
+ // un-reverse after 1 second
+ if (arg == TICKS_PER_SECOND) ramp_direction = 1;
+
+ pseudo_rand_seed += arg;
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ return EVENT_NOT_HANDLED;
+}
+#endif // ifdef USE_STROBE_STATE
+
+#if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
+inline void party_tactical_strobe_mode_iter(uint8_t st) {
+ // one iteration of main loop()
+ uint8_t del = strobe_delays[st];
+ // TODO: make tac strobe brightness configurable?
+ set_level(STROBE_BRIGHTNESS);
+ if (0) {} // placeholde0
+ #ifdef USE_PARTY_STROBE_MODE
+ else if (st == party_strobe_e) { // party strobe
+ #ifdef PARTY_STROBE_ONTIME
+ nice_delay_ms(PARTY_STROBE_ONTIME);
+ #else
+ if (del < 42) delay_zero();
+ else nice_delay_ms(1);
+ #endif
+ }
+ #endif
+ #ifdef USE_TACTICAL_STROBE_MODE
+ else { //tactical strobe
+ nice_delay_ms(del >> 1);
+ }
+ #endif
+ set_level(0);
+ nice_delay_ms(del); // no return check necessary on final delay
+}
+#endif
+
+#ifdef USE_LIGHTNING_MODE
+inline void lightning_storm_iter() {
+ // one iteration of main loop()
+ int16_t brightness;
+ uint16_t rand_time;
+
+ // turn the emitter on at a random level,
+ // for a random amount of time between 1ms and 32ms
+ //rand_time = 1 << (pseudo_rand() % 7);
+ rand_time = pseudo_rand() & 63;
+ brightness = 1 << (pseudo_rand() % 7); // 1, 2, 4, 8, 16, 32, 64
+ brightness += 1 << (pseudo_rand() % 5); // 2 to 80 now
+ brightness += pseudo_rand() % brightness; // 2 to 159 now (w/ low bias)
+ if (brightness > MAX_LEVEL) brightness = MAX_LEVEL;
+ set_level(brightness);
+ nice_delay_ms(rand_time);
+
+ // decrease the brightness somewhat more gradually, like lightning
+ uint8_t stepdown = brightness >> 3;
+ if (stepdown < 1) stepdown = 1;
+ while(brightness > 1) {
+ nice_delay_ms(rand_time);
+ brightness -= stepdown;
+ if (brightness < 0) brightness = 0;
+ set_level(brightness);
+ /*
+ if ((brightness < MAX_LEVEL/2) && (! (pseudo_rand() & 15))) {
+ brightness <<= 1;
+ set_level(brightness);
+ }
+ */
+ if (! (pseudo_rand() & 3)) {
+ nice_delay_ms(rand_time);
+ set_level(brightness>>1);
+ }
+ }
+
+ // turn the emitter off,
+ // for a random amount of time between 1ms and 8192ms
+ // (with a low bias)
+ rand_time = 1 << (pseudo_rand() % 13);
+ rand_time += pseudo_rand() % rand_time;
+ set_level(0);
+ nice_delay_ms(rand_time); // no return check necessary on final delay
+}
+#endif
+
+#ifdef USE_BIKE_FLASHER_MODE
+inline void bike_flasher_iter() {
+ // one iteration of main loop()
+ uint8_t burst = bike_flasher_brightness << 1;
+ if (burst > MAX_LEVEL) burst = MAX_LEVEL;
+ for(uint8_t i=0; i<4; i++) {
+ set_level(burst);
+ nice_delay_ms(5);
+ set_level(bike_flasher_brightness);
+ nice_delay_ms(65);
+ }
+ nice_delay_ms(720); // no return check necessary on final delay
+}
+#endif
+
+#ifdef USE_CANDLE_MODE
+uint8_t candle_mode_state(Event event, uint16_t arg) {
+ static int8_t ramp_direction = 1;
+ #define MAX_CANDLE_LEVEL (RAMP_LENGTH-CANDLE_AMPLITUDE-15)
+ static uint8_t candle_wave1 = 0;
+ static uint8_t candle_wave2 = 0;
+ static uint8_t candle_wave3 = 0;
+ static uint8_t candle_wave2_speed = 0;
+ // these should add up to 100
+ #define CANDLE_WAVE1_MAXDEPTH 30
+ #define CANDLE_WAVE2_MAXDEPTH 45
+ #define CANDLE_WAVE3_MAXDEPTH 25
+ static const uint8_t candle_wave1_depth = CANDLE_WAVE1_MAXDEPTH * CANDLE_AMPLITUDE / 100;
+ static uint8_t candle_wave2_depth = CANDLE_WAVE2_MAXDEPTH * CANDLE_AMPLITUDE / 100;
+ static uint8_t candle_wave3_depth = CANDLE_WAVE3_MAXDEPTH * CANDLE_AMPLITUDE / 100;
+ static uint8_t candle_mode_brightness = 24;
+ static uint8_t candle_mode_timer = 0;
+ #define TICKS_PER_CANDLE_MINUTE 4096 // about 65 seconds
+ #define MINUTES_PER_CANDLE_HALFHOUR 27 // ish
+
+ if (event == EV_enter_state) {
+ candle_mode_timer = 0; // in case any time was left over from earlier
+ ramp_direction = 1;
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks: cancel timer
+ else if (event == EV_2clicks) {
+ // parent state just rotated through strobe/flasher modes,
+ // so cancel timer... in case any time was left over from earlier
+ candle_mode_timer = 0;
+ return MISCHIEF_MANAGED;
+ }
+ // hold: change brightness (brighter)
+ else if (event == EV_click1_hold) {
+ // ramp away from extremes
+ if (! arg) {
+ if (candle_mode_brightness >= MAX_CANDLE_LEVEL) { ramp_direction = -1; }
+ else if (candle_mode_brightness <= 1) { ramp_direction = 1; }
+ }
+ // change brightness, but not too far
+ candle_mode_brightness += ramp_direction;
+ if (candle_mode_brightness < 1) candle_mode_brightness = 1;
+ else if (candle_mode_brightness > MAX_CANDLE_LEVEL) candle_mode_brightness = MAX_CANDLE_LEVEL;
+ return MISCHIEF_MANAGED;
+ }
+ // reverse ramp direction on hold release
+ else if (event == EV_click1_hold_release) {
+ ramp_direction = -ramp_direction;
+ return MISCHIEF_MANAGED;
+ }
+ // click, hold: change brightness (dimmer)
+ else if (event == EV_click2_hold) {
+ ramp_direction = 1;
+ if (candle_mode_brightness > 1)
+ candle_mode_brightness --;
+ return MISCHIEF_MANAGED;
+ }
+ // 3 clicks: add 30m to candle timer
+ else if (event == EV_3clicks) {
+ if (candle_mode_timer < (255 - MINUTES_PER_CANDLE_HALFHOUR)) {
+ // add 30m to the timer
+ candle_mode_timer += MINUTES_PER_CANDLE_HALFHOUR;
+ // blink to confirm
+ set_level(actual_level + 32);
+ delay_4ms(2);
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // clock tick: animate candle brightness
+ else if (event == EV_tick) {
+ // un-reverse after 1 second
+ if (arg == TICKS_PER_SECOND) ramp_direction = 1;
+
+ // self-timer dims the light during the final minute
+ uint8_t subtract = 0;
+ if (candle_mode_timer == 1) {
+ subtract = ((candle_mode_brightness+CANDLE_AMPLITUDE)
+ * ((arg & (TICKS_PER_CANDLE_MINUTE-1)) >> 4))
+ >> 8;
+ }
+ // we passed a minute mark, decrease timer if it's running
+ if ((arg & (TICKS_PER_CANDLE_MINUTE-1)) == (TICKS_PER_CANDLE_MINUTE - 1)) {
+ if (candle_mode_timer > 0) {
+ candle_mode_timer --;
+ //set_level(0); delay_4ms(2);
+ // if the timer ran out, shut off
+ if (! candle_mode_timer) {
+ set_state(off_state, 0);
+ }
+ }
+ }
+ // 3-oscillator synth for a relatively organic pattern
+ uint8_t add;
+ add = ((triangle_wave(candle_wave1) * candle_wave1_depth) >> 8)
+ + ((triangle_wave(candle_wave2) * candle_wave2_depth) >> 8)
+ + ((triangle_wave(candle_wave3) * candle_wave3_depth) >> 8);
+ int8_t brightness = candle_mode_brightness + add - subtract;
+ if (brightness < 0) { brightness = 0; }
+ set_level(brightness);
+
+ // wave1: slow random LFO
+ // TODO: make wave slower and more erratic?
+ if ((arg & 1) == 0) candle_wave1 += pseudo_rand() & 1;
+ // wave2: medium-speed erratic LFO
+ candle_wave2 += candle_wave2_speed;
+ // wave3: erratic fast wave
+ candle_wave3 += pseudo_rand() % 37;
+ // S&H on wave2 frequency to make it more erratic
+ if ((pseudo_rand() & 0b00111111) == 0)
+ candle_wave2_speed = pseudo_rand() % 13;
+ // downward sawtooth on wave2 depth to simulate stabilizing
+ if ((candle_wave2_depth > 0) && ((pseudo_rand() & 0b00111111) == 0))
+ candle_wave2_depth --;
+ // random sawtooth retrigger
+ if (pseudo_rand() == 0) {
+ // random amplitude
+ //candle_wave2_depth = 2 + (pseudo_rand() % ((CANDLE_WAVE2_MAXDEPTH * CANDLE_AMPLITUDE / 100) - 2));
+ candle_wave2_depth = pseudo_rand() % (CANDLE_WAVE2_MAXDEPTH * CANDLE_AMPLITUDE / 100);
+ //candle_wave3_depth = 5;
+ candle_wave2 = 0;
+ }
+ // downward sawtooth on wave3 depth to simulate stabilizing
+ if ((candle_wave3_depth > 2) && ((pseudo_rand() & 0b00011111) == 0))
+ candle_wave3_depth --;
+ if ((pseudo_rand() & 0b01111111) == 0)
+ // random amplitude
+ //candle_wave3_depth = 2 + (pseudo_rand() % ((CANDLE_WAVE3_MAXDEPTH * CANDLE_AMPLITUDE / 100) - 2));
+ candle_wave3_depth = pseudo_rand() % (CANDLE_WAVE3_MAXDEPTH * CANDLE_AMPLITUDE / 100);
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+uint8_t triangle_wave(uint8_t phase) {
+ uint8_t result = phase << 1;
+ if (phase > 127) result = 255 - result;
+ return result;
+}
+#endif // #ifdef USE_CANDLE_MODE
+
+
+#ifdef USE_BORING_STROBE_STATE
+#include "ff-strobes.c"
+#endif
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/strobes.h b/spaghetti-monster/anduril/strobes.h
new file mode 100644
index 0000000..8cfc4a7
--- /dev/null
+++ b/spaghetti-monster/anduril/strobes.h
@@ -0,0 +1,100 @@
+/*
+ * strobes.h: Strobe modes for Anduril.
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef STROBES_H
+#define STROBES_H
+
+// internal numbering for strobe modes
+#ifdef USE_STROBE_STATE
+typedef enum {
+ #ifdef USE_PARTY_STROBE_MODE
+ party_strobe_e,
+ #endif
+ #ifdef USE_TACTICAL_STROBE_MODE
+ tactical_strobe_e,
+ #endif
+ #ifdef USE_LIGHTNING_MODE
+ lightning_storm_e,
+ #endif
+ #ifdef USE_CANDLE_MODE
+ candle_mode_e,
+ #endif
+ #ifdef USE_BIKE_FLASHER_MODE
+ bike_flasher_e,
+ #endif
+ strobe_mode_END
+} strobe_mode_te;
+
+const int NUM_STROBES = strobe_mode_END;
+
+// which strobe mode is active?
+#ifdef USE_CANDLE_MODE
+volatile strobe_mode_te strobe_type = candle_mode_e;
+#else
+volatile strobe_mode_te strobe_type = 0;
+#endif
+#endif
+
+
+// full FET strobe can be a bit much... use max regulated level instead,
+// if there's a bright enough regulated level
+#ifndef STROBE_BRIGHTNESS
+#ifdef MAX_Nx7135
+#define STROBE_BRIGHTNESS MAX_Nx7135
+#else
+#define STROBE_BRIGHTNESS MAX_LEVEL
+#endif
+#endif
+
+// party and tactical strobes
+#ifdef USE_STROBE_STATE
+uint8_t strobe_state(Event event, uint16_t arg);
+#endif
+
+#if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
+// party / tactical strobe timing
+volatile uint8_t strobe_delays[] = { 41, 67 }; // party strobe 24 Hz, tactical strobe 10 Hz
+inline void party_tactical_strobe_mode_iter(uint8_t st);
+#endif
+
+#ifdef USE_LIGHTNING_MODE
+inline void lightning_storm_iter();
+#endif
+
+// bike mode config options
+#ifdef USE_BIKE_FLASHER_MODE
+volatile uint8_t bike_flasher_brightness = MAX_1x7135;
+inline void bike_flasher_iter();
+#endif
+
+#ifdef USE_CANDLE_MODE
+uint8_t candle_mode_state(Event event, uint16_t arg);
+uint8_t triangle_wave(uint8_t phase);
+#ifndef CANDLE_AMPLITUDE
+#define CANDLE_AMPLITUDE 25
+#endif
+#endif
+
+
+#if defined(USE_POLICE_STROBE_MODE) || defined(USE_SOS_MODE_IN_FF_GROUP)
+#define USE_BORING_STROBE_STATE
+#include "ff-strobes.h"
+#endif
+
+#endif
diff --git a/spaghetti-monster/anduril/tint-ramping.c b/spaghetti-monster/anduril/tint-ramping.c
new file mode 100644
index 0000000..0196bc7
--- /dev/null
+++ b/spaghetti-monster/anduril/tint-ramping.c
@@ -0,0 +1,87 @@
+/*
+ * tint-ramping.c: Tint ramping functions for Anduril.
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef TINT_RAMPING_C
+#define TINT_RAMPING_C
+
+#include "tint-ramping.h"
+
+uint8_t tint_ramping_state(Event event, uint16_t arg) {
+ static int8_t tint_ramp_direction = 1;
+ static uint8_t prev_tint = 0;
+ // don't activate auto-tint modes unless the user hits the edge
+ // and keeps pressing for a while
+ static uint8_t past_edge_counter = 0;
+ // bugfix: click-click-hold from off to strobes would invoke tint ramping
+ // in addition to changing state... so ignore any tint-ramp events which
+ // don't look like they were meant to be here
+ static uint8_t active = 0;
+
+ // click, click, hold: change the tint
+ if (event == EV_click3_hold) {
+ // reset at beginning of movement
+ if (! arg) {
+ active = 1; // first frame means this is for us
+ past_edge_counter = 0; // doesn't start until user hits the edge
+ }
+ // ignore event if we weren't the ones who handled the first frame
+ if (! active) return EVENT_HANDLED;
+
+ // change normal tints
+ if ((tint_ramp_direction > 0) && (tint < 254)) {
+ tint += 1;
+ }
+ else if ((tint_ramp_direction < 0) && (tint > 1)) {
+ tint -= 1;
+ }
+ // if the user kept pressing long enough, go the final step
+ if (past_edge_counter == 64) {
+ past_edge_counter ++;
+ tint ^= 1; // 0 -> 1, 254 -> 255
+ blip();
+ }
+ // if tint change stalled, let user know we hit the edge
+ else if (prev_tint == tint) {
+ if (past_edge_counter == 0) blip();
+ // count up but don't wrap back to zero
+ if (past_edge_counter < 255) past_edge_counter ++;
+ }
+ prev_tint = tint;
+ set_level(actual_level);
+ return EVENT_HANDLED;
+ }
+
+ // click, click, hold, release: reverse direction for next ramp
+ else if (event == EV_click3_hold_release) {
+ active = 0; // ignore next hold if it wasn't meant for us
+ // reverse
+ tint_ramp_direction = -tint_ramp_direction;
+ if (tint == 0) tint_ramp_direction = 1;
+ else if (tint == 255) tint_ramp_direction = -1;
+ // remember tint after battery change
+ save_config();
+ return EVENT_HANDLED;
+ }
+
+ return EVENT_NOT_HANDLED;
+}
+
+
+#endif
+
diff --git a/spaghetti-monster/anduril/tint-ramping.h b/spaghetti-monster/anduril/tint-ramping.h
new file mode 100644
index 0000000..bd86659
--- /dev/null
+++ b/spaghetti-monster/anduril/tint-ramping.h
@@ -0,0 +1,27 @@
+/*
+ * tint-ramping.h: Tint ramping functions for Anduril.
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef TINT_RAMPING_H
+#define TINT_RAMPING_H
+
+// not actually a mode, more of a fallback under other modes
+uint8_t tint_ramping_state(Event event, uint16_t arg);
+
+
+#endif
diff --git a/spaghetti-monster/fsm-misc.c b/spaghetti-monster/fsm-misc.c
index 82be745..eaa6fb3 100644
--- a/spaghetti-monster/fsm-misc.c
+++ b/spaghetti-monster/fsm-misc.c
@@ -222,6 +222,7 @@ void rgb_led_set(uint8_t value) {
#endif // ifdef USE_AUX_RGB_LEDS
#ifdef USE_TRIANGLE_WAVE
+// TODO: remove this, it's in the wrong file
uint8_t triangle_wave(uint8_t phase) {
uint8_t result = phase << 1;
if (phase > 127) result = 255 - result;