From af011b08919e42736f514ae6ffe045571dbefda9 Mon Sep 17 00:00:00 2001
From: Selene ToyKeeper
Date: Wed, 30 Aug 2017 23:15:39 -0600
Subject: Reorganized FSM files, one dir per UI.
---
spaghetti-monster/anduril.c | 961 ------------------------------
spaghetti-monster/anduril.txt | 100 ----
spaghetti-monster/anduril/anduril.c | 961 ++++++++++++++++++++++++++++++
spaghetti-monster/anduril/anduril.txt | 100 ++++
spaghetti-monster/baton-simpler.c | 202 -------
spaghetti-monster/baton.c | 230 -------
spaghetti-monster/baton/baton-simpler.c | 202 +++++++
spaghetti-monster/baton/baton.c | 231 +++++++
spaghetti-monster/darkhorse.c | 378 ------------
spaghetti-monster/darkhorse/darkhorse.c | 378 ++++++++++++
spaghetti-monster/momentary.c | 81 ---
spaghetti-monster/momentary/momentary.c | 81 +++
spaghetti-monster/ramping-ui.c | 361 -----------
spaghetti-monster/ramping-ui/ramping-ui.c | 361 +++++++++++
14 files changed, 2314 insertions(+), 2313 deletions(-)
delete mode 100644 spaghetti-monster/anduril.c
delete mode 100644 spaghetti-monster/anduril.txt
create mode 100644 spaghetti-monster/anduril/anduril.c
create mode 100644 spaghetti-monster/anduril/anduril.txt
delete mode 100644 spaghetti-monster/baton-simpler.c
delete mode 100644 spaghetti-monster/baton.c
create mode 100644 spaghetti-monster/baton/baton-simpler.c
create mode 100644 spaghetti-monster/baton/baton.c
delete mode 100644 spaghetti-monster/darkhorse.c
create mode 100644 spaghetti-monster/darkhorse/darkhorse.c
delete mode 100644 spaghetti-monster/momentary.c
create mode 100644 spaghetti-monster/momentary/momentary.c
delete mode 100644 spaghetti-monster/ramping-ui.c
create mode 100644 spaghetti-monster/ramping-ui/ramping-ui.c
(limited to 'spaghetti-monster')
diff --git a/spaghetti-monster/anduril.c b/spaghetti-monster/anduril.c
deleted file mode 100644
index 998c0c5..0000000
--- a/spaghetti-monster/anduril.c
+++ /dev/null
@@ -1,961 +0,0 @@
-/*
- * Anduril: Narsil-inspired UI for SpaghettiMonster.
- * (Anduril is Aragorn's sword, the blade Narsil reforged)
- *
- * 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 .
- */
-
-#define FSM_EMISAR_D4_DRIVER
-#define USE_LVP
-#define USE_THERMAL_REGULATION
-#define DEFAULT_THERM_CEIL 45
-#define USE_DELAY_MS
-#define USE_DELAY_4MS
-#define USE_DELAY_ZERO
-#define USE_RAMPING
-#define RAMP_LENGTH 150
-#define USE_BATTCHECK
-#define BATTCHECK_VpT
-#define MAX_CLICKS 6
-#define USE_EEPROM
-#define EEPROM_BYTES 12
-#include "spaghetti-monster.h"
-
-// Options specific to this UI (not inherited from SpaghettiMonster)
-#define USE_LIGHTNING_MODE
-// set this a bit high, since the bottom 2 ramp levels might not emit any light at all
-#define GOODNIGHT_TIME 65 // minutes (approximately)
-#define GOODNIGHT_LEVEL 24 // ~11 lm
-
-// FSM states
-uint8_t off_state(EventPtr event, uint16_t arg);
-// 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);
-// party and tactical strobes
-uint8_t strobe_state(EventPtr event, uint16_t arg);
-#ifdef USE_LIGHTNING_MODE
-#define NUM_STROBES 4
-#else
-#define NUM_STROBES 3
-#endif
-#ifdef USE_BATTCHECK
-uint8_t battcheck_state(EventPtr event, uint16_t arg);
-uint8_t tempcheck_state(EventPtr event, uint16_t arg);
-uint8_t thermal_config_state(EventPtr event, uint16_t arg);
-#endif
-// 1-hour ramp down from low, then automatic off
-uint8_t goodnight_state(EventPtr event, uint16_t arg);
-// 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
-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);
-
-// remember stuff even after battery was changed
-void load_config();
-void save_config();
-
-// 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 = 5;
-volatile uint8_t ramp_smooth_ceil = MAX_LEVEL - 30;
-volatile uint8_t ramp_discrete_floor = 20;
-volatile uint8_t ramp_discrete_ceil = MAX_LEVEL - 30;
-volatile uint8_t ramp_discrete_steps = 7;
-uint8_t ramp_discrete_step_size; // don't set this
-
-// calculate the nearest ramp level which would be valid at the moment
-// (is a no-op for smooth ramp, but limits discrete ramp to only the
-// correct levels for the user's config)
-uint8_t nearest_level(int16_t target);
-
-#ifdef USE_THERMAL_REGULATION
-// brightness before thermal step-down
-uint8_t target_level = 0;
-#endif
-
-// strobe timing
-volatile uint8_t strobe_delays[] = { 40, 67 }; // party strobe, tactical strobe
-volatile uint8_t strobe_type = 3; // 0 == party strobe, 1 == tactical strobe, 2 == lightning storm, 3 == bike flasher
-
-// bike mode config options
-volatile uint8_t bike_flasher_brightness = MAX_1x7135;
-
-#ifdef USE_LIGHTNING_MODE
-volatile uint8_t pseudo_rand_seed = 0;
-uint8_t pseudo_rand();
-#endif
-
-// beacon timing
-volatile uint8_t beacon_seconds = 2;
-
-// deferred "off" so we won't suspend in a weird state
-volatile uint8_t go_to_standby = 0;
-
-
-uint8_t off_state(EventPtr event, uint16_t arg) {
- // turn emitter off when entering state
- if (event == EV_enter_state) {
- set_level(0);
- // sleep while off (lower power use)
- go_to_standby = 1;
- return MISCHIEF_MANAGED;
- }
- // 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;
- }
- // 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;
- }
- // 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
- else if (event == EV_3clicks) {
- set_state(battcheck_state, 0);
- return MISCHIEF_MANAGED;
- }
- #endif
- // 4 clicks: soft lockout
- else if (event == EV_4clicks) {
- blink_confirm(5);
- set_state(lockout_state, 0);
- return MISCHIEF_MANAGED;
- }
- // 5 clicks: strobe mode
- else if (event == EV_5clicks) {
- set_state(strobe_state, 0);
- return MISCHIEF_MANAGED;
- }
- // 6 clicks: momentary mode
- else if (event == EV_6clicks) {
- blink_confirm(1);
- set_state(momentary_state, 0);
- 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;
- }
- // click, hold: go to highest level (for ramping down)
- else if (event == EV_click2_hold) {
- set_state(steady_state, MAX_LEVEL);
- return MISCHIEF_MANAGED;
- }
- 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;
- 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) {
- // remember this level, unless it's moon or turbo
- if ((arg > mode_min) && (arg < mode_max))
- memorized_level = arg;
- // use the requested level even if not memorized
- #ifdef USE_THERMAL_REGULATION
- target_level = arg;
- #endif
- set_level(nearest_level(arg));
- return 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) {
- memorized_level = actual_level; // in case we're on moon
- #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 ^= 1;
- memorized_level = nearest_level(memorized_level);
- 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) {
- set_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;
- }
- // TODO? make it ramp down instead, if already at max?
- memorized_level = nearest_level((int16_t)actual_level + ramp_step_size);
- #ifdef USE_THERMAL_REGULATION
- target_level = memorized_level;
- #endif
- // only blink once for each threshold
- if ((memorized_level != actual_level)
- && ((memorized_level == MAX_1x7135)
- || (memorized_level == mode_max))) {
- set_level(0);
- delay_4ms(8/4);
- }
- set_level(memorized_level);
- return MISCHIEF_MANAGED;
- }
- // click, hold: change brightness (dimmer)
- else if (event == EV_click2_hold) {
- // 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
- // only blink once for each threshold
- if ((memorized_level != actual_level)
- && ((memorized_level == MAX_1x7135)
- || (memorized_level == mode_min))) {
- set_level(0);
- delay_4ms(8/4);
- }
- set_level(memorized_level);
- return MISCHIEF_MANAGED;
- }
- #ifdef USE_THERMAL_REGULATION
- // TODO: test this on a real light
- // overheating: drop by an amount proportional to how far we are above the ceiling
- else if (event == EV_temperature_high) {
- if (actual_level > MAX_LEVEL/4) {
- uint8_t stepdown = actual_level - arg;
- if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4;
- set_level(stepdown);
- }
- 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 (actual_level < target_level) {
- uint8_t stepup = actual_level + (arg>>1);
- if (stepup > target_level) stepup = target_level;
- set_level(stepup);
- }
- return MISCHIEF_MANAGED;
- }
- #endif
- return EVENT_NOT_HANDLED;
-}
-
-
-uint8_t strobe_state(EventPtr event, uint16_t arg) {
- if (event == EV_enter_state) {
- 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 = (strobe_type + 1) % NUM_STROBES;
- interrupt_nice_delays();
- save_config();
- return MISCHIEF_MANAGED;
- }
- // hold: change speed (go faster)
- // or change brightness (brighter)
- else if (event == EV_click1_hold) {
- if (strobe_type < 2) {
- if ((arg & 1) == 0) {
- if (strobe_delays[strobe_type] > 8) strobe_delays[strobe_type] --;
- }
- }
- // biking mode brighter
- else if (strobe_type == 3) {
- if (bike_flasher_brightness < MAX_LEVEL/2)
- bike_flasher_brightness ++;
- set_level(bike_flasher_brightness);
- }
- return MISCHIEF_MANAGED;
- }
- // click, hold: change speed (go slower)
- // or change brightness (dimmer)
- else if (event == EV_click2_hold) {
- if (strobe_type < 2) {
- if ((arg & 1) == 0) {
- if (strobe_delays[strobe_type] < 255) strobe_delays[strobe_type] ++;
- }
- }
- // biking mode dimmer
- else if (strobe_type == 3) {
- if (bike_flasher_brightness > 1)
- bike_flasher_brightness --;
- set_level(bike_flasher_brightness);
- }
- return MISCHIEF_MANAGED;
- }
- // release hold: save new strobe settings
- else if ((event == EV_click1_hold_release)
- || (event == EV_click2_hold_release)) {
- save_config();
- return MISCHIEF_MANAGED;
- }
- #ifdef USE_LIGHTNING_MODE
- // clock tick: bump the random seed
- else if (event == EV_tick) {
- pseudo_rand_seed += arg;
- 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: goodnight mode
- else if (event == EV_2clicks) {
- set_state(goodnight_state, 0);
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
-
-uint8_t tempcheck_state(EventPtr event, uint16_t arg) {
- // 1 click: off
- if (event == EV_1click) {
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
- // 2 clicks: battcheck mode
- else if (event == EV_2clicks) {
- set_state(battcheck_state, 0);
- return MISCHIEF_MANAGED;
- }
- // 3 clicks: thermal config mode
- else if (event == EV_3clicks) {
- set_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;
- }
- // 2 clicks: tempcheck mode
- else if (event == EV_2clicks) {
- set_state(tempcheck_state, 0);
- return MISCHIEF_MANAGED;
- }
- // 3 clicks: beacon config mode
- else if (event == EV_3clicks) {
- set_state(beacon_config_state, 0);
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
-
-
-#define GOODNIGHT_TICKS_PER_STEPDOWN (GOODNIGHT_TIME*TICKS_PER_SECOND*60L/GOODNIGHT_LEVEL)
-uint8_t goodnight_state(EventPtr event, uint16_t arg) {
- static uint16_t ticks_since_stepdown = 0;
- // blink on start
- if (event == EV_enter_state) {
- blink_confirm(4);
- ticks_since_stepdown = 0;
- set_level(GOODNIGHT_LEVEL);
- return MISCHIEF_MANAGED;
- }
- // 1 click: off
- else if (event == EV_1click) {
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
- // 2 clicks: beacon mode
- else if (event == EV_2clicks) {
- set_state(beacon_state, 0);
- return MISCHIEF_MANAGED;
- }
- // tick: step down (maybe) or off (maybe)
- else if (event == EV_tick) {
- if (++ticks_since_stepdown > GOODNIGHT_TICKS_PER_STEPDOWN) {
- ticks_since_stepdown = 0;
- set_level(actual_level-1);
- if (! actual_level) {
- #if 0 // test blink, to help measure timing
- set_level(MAX_LEVEL>>2);
- delay_4ms(8/2);
- set_level(0);
- #endif
- set_state(off_state, 0);
- }
- }
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
-
-
-uint8_t lockout_state(EventPtr event, uint16_t arg) {
- // 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)
- if (event == EV_tick) {
- static uint8_t ticks_spent_awake = 0;
- ticks_spent_awake ++;
- if (ticks_spent_awake > 180) {
- ticks_spent_awake = 0;
- go_to_standby = 1;
- }
- return MISCHIEF_MANAGED;
- }
- // 4 clicks: exit
- else if (event == EV_4clicks) {
- blink_confirm(2);
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
-
-
-uint8_t momentary_state(EventPtr event, uint16_t arg) {
- 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
- 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;
-}
-
-
-uint8_t ramp_config_state(EventPtr event, uint16_t arg) {
- static uint8_t config_step;
- static uint8_t num_config_steps;
- if (event == EV_enter_state) {
- config_step = 0;
- if (ramp_style) {
- num_config_steps = 3;
- }
- else {
- num_config_steps = 2;
- }
- 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 {
- save_config();
- // TODO: blink out some sort of success pattern
- // return to steady mode
- set_state(steady_state, memorized_level);
- }
- return MISCHIEF_MANAGED;
- }
- // an option was set (return from number_entry_state)
- else if (event == EV_reenter_state) {
- #if 0
- // FIXME? this is a kludge which relies on the vars being consecutive
- // in RAM and in the same order as declared
- // ... and it doesn't work; it seems they're not consecutive :(
- volatile uint8_t *dest;
- if (ramp_style) dest = (&ramp_discrete_floor) + config_step;
- else dest = (&ramp_smooth_floor) + config_step;
- if (number_entry_value)
- *dest = number_entry_value;
- #else
- switch (config_step) {
- case 0:
- if (number_entry_value) {
- if (ramp_style) ramp_discrete_floor = number_entry_value;
- else ramp_smooth_floor = number_entry_value;
- }
- break;
- case 1:
- if (number_entry_value) {
- if (ramp_style) ramp_discrete_ceil = MAX_LEVEL + 1 - number_entry_value;
- else ramp_smooth_ceil = MAX_LEVEL + 1 - number_entry_value;
- }
- break;
- case 2:
- if (number_entry_value)
- ramp_discrete_steps = number_entry_value;
- break;
- }
- #endif
- config_step ++;
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
-
-
-uint8_t thermal_config_state(EventPtr event, uint16_t arg) {
- static uint8_t done = 0;
- if (event == EV_enter_state) {
- set_level(0);
- done = 0;
- return MISCHIEF_MANAGED;
- }
- // advance forward through config steps
- else if (event == EV_tick) {
- if (! done) push_state(number_entry_state, 1);
- else {
- save_config();
- // return to beacon mode
- set_state(tempcheck_state, 0);
- }
- return MISCHIEF_MANAGED;
- }
- // an option was set (return from number_entry_state)
- else if (event == EV_reenter_state) {
- if (number_entry_value) therm_ceil = 30 + number_entry_value;
- if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL;
- done = 1;
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
-
-
-uint8_t beacon_config_state(EventPtr event, uint16_t arg) {
- static uint8_t done = 0;
- if (event == EV_enter_state) {
- set_level(0);
- done = 0;
- return MISCHIEF_MANAGED;
- }
- // advance forward through config steps
- else if (event == EV_tick) {
- if (! done) push_state(number_entry_state, 1);
- else {
- save_config();
- // return to beacon mode
- set_state(beacon_state, 0);
- }
- return MISCHIEF_MANAGED;
- }
- // an option was set (return from number_entry_state)
- else if (event == EV_reenter_state) {
- if (number_entry_value) beacon_seconds = number_entry_value;
- done = 1;
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
-
-
-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;
- // TODO: blink out the 'arg' to show which option this is
- 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>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);
- }
-}
-
-
-#ifdef USE_LIGHTNING_MODE
-uint8_t pseudo_rand() {
- static uint16_t offset = 1024;
- // loop from 1024 to 4095
- offset = ((offset + 1) & 0x0fff) | 0x0400;
- pseudo_rand_seed += 0b01010101; // 85
- return pgm_read_byte(offset) + pseudo_rand_seed;
-}
-#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];
- strobe_type = eeprom[6]; // TODO: move this to eeprom_wl?
- strobe_delays[0] = eeprom[7];
- strobe_delays[1] = eeprom[8];
- bike_flasher_brightness = eeprom[9];
- beacon_seconds = eeprom[10];
- therm_ceil = eeprom[11];
- }
-}
-
-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] = strobe_type; // TODO: move this to eeprom_wl?
- eeprom[7] = strobe_delays[0];
- eeprom[8] = strobe_delays[1];
- eeprom[9] = bike_flasher_brightness;
- eeprom[10] = beacon_seconds;
- eeprom[11] = therm_ceil;
-
- save_eeprom();
-}
-
-
-void low_voltage() {
- // "step down" from strobe to something low
- if (current_state == strobe_state) {
- set_state(steady_state, RAMP_SIZE/6);
- }
- // in normal mode, step down by half or turn off
- else if (current_state == steady_state) {
- if (actual_level > 1) {
- set_level(actual_level >> 1);
- }
- 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() {
-
- // deferred "off" so we won't suspend in a weird state
- // (like... during the middle of a strobe pulse)
- if (go_to_standby) {
- go_to_standby = 0;
- set_level(0);
- standby_mode();
- }
-
- if (current_state == strobe_state) {
- // party / tactical strobe
- if (strobe_type < 2) {
- set_level(MAX_LEVEL);
- if (strobe_type == 0) { // party strobe
- if (strobe_delays[strobe_type] < 42) delay_zero();
- else delay_ms(1);
- } else { //tactical strobe
- nice_delay_ms(strobe_delays[strobe_type] >> 1);
- }
- set_level(0);
- nice_delay_ms(strobe_delays[strobe_type]);
- }
- #ifdef USE_LIGHTNING_MODE
- // lightning storm
- else if (strobe_type == 2) {
- 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() % 6);
- brightness = 1 << (pseudo_rand() % 7); // 1, 2, 4, 8, 16, 32, 64
- brightness += 1 << (pseudo_rand()&0x03); // 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);
- if (! nice_delay_ms(rand_time)) return;
-
- // decrease the brightness somewhat more gradually, like lightning
- uint8_t stepdown = brightness >> 3;
- if (stepdown < 1) stepdown = 1;
- while(brightness > 1) {
- if (! nice_delay_ms(rand_time)) return;
- brightness -= stepdown;
- if (brightness < 0) brightness = 0;
- set_level(brightness);
- }
-
- // 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);
-
- }
- #endif
- // bike flasher
- else if (strobe_type == 3) {
- uint8_t burst = bike_flasher_brightness << 1;
- for(uint8_t i=0; i<4; i++) {
- set_level(burst);
- if (! nice_delay_ms(5)) return;
- set_level(bike_flasher_brightness);
- if (! nice_delay_ms(65)) return;
- }
- if (! nice_delay_ms(720)) return;
- }
- }
-
- #ifdef USE_BATTCHECK
- else if (current_state == battcheck_state) {
- battcheck();
- }
- else if (current_state == tempcheck_state) {
- blink_num(temperature>>2);
- nice_delay_ms(1000);
- }
- // TODO: blink out therm_ceil during thermal_config_state
- #endif
-
- else if (current_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/anduril.txt b/spaghetti-monster/anduril.txt
deleted file mode 100644
index bb0f5e2..0000000
--- a/spaghetti-monster/anduril.txt
+++ /dev/null
@@ -1,100 +0,0 @@
-From off:
- * 1 click: memorized level
- * Hold: lowest level then ramp up
- * 2 clicks: highest ramp level
- * Click, hold: highest level then ramp down
- * 3 clicks: battcheck mode
- (battcheck, goodnight, beacon, tempcheck)
- * 4 clicks: lock-out
- * 5 clicks: strobe modes
- (bike flasher, party strobe, tactical strobe, lightning storm mode)
- (remembers which you last used)
- * 6 clicks: momentary mode (disconnect power to exit)
-
-In steady mode:
- * 1 click: off
- * Hold: ramp up
- * Click, hold: ramp down
- * 2 clicks: to/from turbo (actual turbo, not just highest ramp level)
- * 3 clicks: toggle smooth vs discrete ramping
- * 4 clicks: configure current ramp
-
-Smooth ramp config mode:
- - Setting 1: memory on/off (not implemented yet)
- * Setting 2: low end
- (click N times to set ramp floor to level N)
- * Setting 3: high end
- (click N times to set ramp ceiling to level "151 - N")
-
-Discrete ramp config mode:
- - Setting 1: memory on/off (not implemented yet)
- * Setting 2: low end
- (click N times to set ramp floor to level N)
- * Setting 3: high end
- (click N times to set ramp ceiling to level "151 - N")
- * Setting 4: number of levels in discrete ramp
- (click N times to make discrete mode have N stair-steps)
- (minimum 2, maximum 150)
-
-Bike flasher:
- * 1 click: off
- * 2 clicks: party strobe
- * Hold: brighter
- * Click, hold: dimmer
-
-Party / Tactical strobe modes:
- * 1 click: off
- * Hold: change speed (faster)
- * Click, hold: change speed (slower)
- * 2 clicks: next strobe mode
- (bike flasher, party strobe, tactical strobe, lightning storm mode)
- (TODO: random/police strobe?)
-
-Lightning storm mode:
- * 1 click: off
- * 2 clicks: bike flasher
-
-Battcheck mode:
- * 1 click: off
- * 2 clicks: goodnight mode
-
-Goodnight mode:
- * 1 click: off
- * 2 clicks: beacon mode
-
-Beacon mode:
- * 1 click: off
- * 2 clicks: tempcheck mode
- * 3 clicks: configure time between pulses
-
-Beacon config mode:
- * At buzz, click N times to set beacon frequency to N seconds.
-
-Tempcheck mode:
- * 1 click: off
- * 2 clicks: battcheck mode
- * 3 clicks: thermal config mode
- - Hold: thermal calibration mode
-
-Thermal config mode:
- * At buzz, click N times to set thermal limit to roughly 30 C + N.
-
-Thermal calibration mode:
- - Hold until hot: set new ceiling value
- - ... don't hold: blink out current ceiling value and exit
-
-Momentary mode:
- * Press button: Light on (at memorized level).
- * Release button: Light off.
- * To exit, disconnect power. (loosen/tighten the tailcap)
-
-TODO:
- * save settings in eeprom
- - decide on "hold until hot" or "click N times" for thermal config mode
- - test thermal regulation on an actual light
- - improve thermal regulation
- - a way to blink out the firmware version?
- - indicator LED support
- - a way to configure indicator LED behavior?
- * add goodnight mode?
- * add lightning mode?
diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c
new file mode 100644
index 0000000..998c0c5
--- /dev/null
+++ b/spaghetti-monster/anduril/anduril.c
@@ -0,0 +1,961 @@
+/*
+ * Anduril: Narsil-inspired UI for SpaghettiMonster.
+ * (Anduril is Aragorn's sword, the blade Narsil reforged)
+ *
+ * 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 .
+ */
+
+#define FSM_EMISAR_D4_DRIVER
+#define USE_LVP
+#define USE_THERMAL_REGULATION
+#define DEFAULT_THERM_CEIL 45
+#define USE_DELAY_MS
+#define USE_DELAY_4MS
+#define USE_DELAY_ZERO
+#define USE_RAMPING
+#define RAMP_LENGTH 150
+#define USE_BATTCHECK
+#define BATTCHECK_VpT
+#define MAX_CLICKS 6
+#define USE_EEPROM
+#define EEPROM_BYTES 12
+#include "spaghetti-monster.h"
+
+// Options specific to this UI (not inherited from SpaghettiMonster)
+#define USE_LIGHTNING_MODE
+// set this a bit high, since the bottom 2 ramp levels might not emit any light at all
+#define GOODNIGHT_TIME 65 // minutes (approximately)
+#define GOODNIGHT_LEVEL 24 // ~11 lm
+
+// FSM states
+uint8_t off_state(EventPtr event, uint16_t arg);
+// 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);
+// party and tactical strobes
+uint8_t strobe_state(EventPtr event, uint16_t arg);
+#ifdef USE_LIGHTNING_MODE
+#define NUM_STROBES 4
+#else
+#define NUM_STROBES 3
+#endif
+#ifdef USE_BATTCHECK
+uint8_t battcheck_state(EventPtr event, uint16_t arg);
+uint8_t tempcheck_state(EventPtr event, uint16_t arg);
+uint8_t thermal_config_state(EventPtr event, uint16_t arg);
+#endif
+// 1-hour ramp down from low, then automatic off
+uint8_t goodnight_state(EventPtr event, uint16_t arg);
+// 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
+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);
+
+// remember stuff even after battery was changed
+void load_config();
+void save_config();
+
+// 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 = 5;
+volatile uint8_t ramp_smooth_ceil = MAX_LEVEL - 30;
+volatile uint8_t ramp_discrete_floor = 20;
+volatile uint8_t ramp_discrete_ceil = MAX_LEVEL - 30;
+volatile uint8_t ramp_discrete_steps = 7;
+uint8_t ramp_discrete_step_size; // don't set this
+
+// calculate the nearest ramp level which would be valid at the moment
+// (is a no-op for smooth ramp, but limits discrete ramp to only the
+// correct levels for the user's config)
+uint8_t nearest_level(int16_t target);
+
+#ifdef USE_THERMAL_REGULATION
+// brightness before thermal step-down
+uint8_t target_level = 0;
+#endif
+
+// strobe timing
+volatile uint8_t strobe_delays[] = { 40, 67 }; // party strobe, tactical strobe
+volatile uint8_t strobe_type = 3; // 0 == party strobe, 1 == tactical strobe, 2 == lightning storm, 3 == bike flasher
+
+// bike mode config options
+volatile uint8_t bike_flasher_brightness = MAX_1x7135;
+
+#ifdef USE_LIGHTNING_MODE
+volatile uint8_t pseudo_rand_seed = 0;
+uint8_t pseudo_rand();
+#endif
+
+// beacon timing
+volatile uint8_t beacon_seconds = 2;
+
+// deferred "off" so we won't suspend in a weird state
+volatile uint8_t go_to_standby = 0;
+
+
+uint8_t off_state(EventPtr event, uint16_t arg) {
+ // turn emitter off when entering state
+ if (event == EV_enter_state) {
+ set_level(0);
+ // sleep while off (lower power use)
+ go_to_standby = 1;
+ return MISCHIEF_MANAGED;
+ }
+ // 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;
+ }
+ // 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;
+ }
+ // 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
+ else if (event == EV_3clicks) {
+ set_state(battcheck_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ // 4 clicks: soft lockout
+ else if (event == EV_4clicks) {
+ blink_confirm(5);
+ set_state(lockout_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 5 clicks: strobe mode
+ else if (event == EV_5clicks) {
+ set_state(strobe_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 6 clicks: momentary mode
+ else if (event == EV_6clicks) {
+ blink_confirm(1);
+ set_state(momentary_state, 0);
+ 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;
+ }
+ // click, hold: go to highest level (for ramping down)
+ else if (event == EV_click2_hold) {
+ set_state(steady_state, MAX_LEVEL);
+ return MISCHIEF_MANAGED;
+ }
+ 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;
+ 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) {
+ // remember this level, unless it's moon or turbo
+ if ((arg > mode_min) && (arg < mode_max))
+ memorized_level = arg;
+ // use the requested level even if not memorized
+ #ifdef USE_THERMAL_REGULATION
+ target_level = arg;
+ #endif
+ set_level(nearest_level(arg));
+ return 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) {
+ memorized_level = actual_level; // in case we're on moon
+ #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 ^= 1;
+ memorized_level = nearest_level(memorized_level);
+ 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) {
+ set_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;
+ }
+ // TODO? make it ramp down instead, if already at max?
+ memorized_level = nearest_level((int16_t)actual_level + ramp_step_size);
+ #ifdef USE_THERMAL_REGULATION
+ target_level = memorized_level;
+ #endif
+ // only blink once for each threshold
+ if ((memorized_level != actual_level)
+ && ((memorized_level == MAX_1x7135)
+ || (memorized_level == mode_max))) {
+ set_level(0);
+ delay_4ms(8/4);
+ }
+ set_level(memorized_level);
+ return MISCHIEF_MANAGED;
+ }
+ // click, hold: change brightness (dimmer)
+ else if (event == EV_click2_hold) {
+ // 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
+ // only blink once for each threshold
+ if ((memorized_level != actual_level)
+ && ((memorized_level == MAX_1x7135)
+ || (memorized_level == mode_min))) {
+ set_level(0);
+ delay_4ms(8/4);
+ }
+ set_level(memorized_level);
+ return MISCHIEF_MANAGED;
+ }
+ #ifdef USE_THERMAL_REGULATION
+ // TODO: test this on a real light
+ // overheating: drop by an amount proportional to how far we are above the ceiling
+ else if (event == EV_temperature_high) {
+ if (actual_level > MAX_LEVEL/4) {
+ uint8_t stepdown = actual_level - arg;
+ if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4;
+ set_level(stepdown);
+ }
+ 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 (actual_level < target_level) {
+ uint8_t stepup = actual_level + (arg>>1);
+ if (stepup > target_level) stepup = target_level;
+ set_level(stepup);
+ }
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ return EVENT_NOT_HANDLED;
+}
+
+
+uint8_t strobe_state(EventPtr event, uint16_t arg) {
+ if (event == EV_enter_state) {
+ 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 = (strobe_type + 1) % NUM_STROBES;
+ interrupt_nice_delays();
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ // hold: change speed (go faster)
+ // or change brightness (brighter)
+ else if (event == EV_click1_hold) {
+ if (strobe_type < 2) {
+ if ((arg & 1) == 0) {
+ if (strobe_delays[strobe_type] > 8) strobe_delays[strobe_type] --;
+ }
+ }
+ // biking mode brighter
+ else if (strobe_type == 3) {
+ if (bike_flasher_brightness < MAX_LEVEL/2)
+ bike_flasher_brightness ++;
+ set_level(bike_flasher_brightness);
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // click, hold: change speed (go slower)
+ // or change brightness (dimmer)
+ else if (event == EV_click2_hold) {
+ if (strobe_type < 2) {
+ if ((arg & 1) == 0) {
+ if (strobe_delays[strobe_type] < 255) strobe_delays[strobe_type] ++;
+ }
+ }
+ // biking mode dimmer
+ else if (strobe_type == 3) {
+ if (bike_flasher_brightness > 1)
+ bike_flasher_brightness --;
+ set_level(bike_flasher_brightness);
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // release hold: save new strobe settings
+ else if ((event == EV_click1_hold_release)
+ || (event == EV_click2_hold_release)) {
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ #ifdef USE_LIGHTNING_MODE
+ // clock tick: bump the random seed
+ else if (event == EV_tick) {
+ pseudo_rand_seed += arg;
+ 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: goodnight mode
+ else if (event == EV_2clicks) {
+ set_state(goodnight_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+uint8_t tempcheck_state(EventPtr event, uint16_t arg) {
+ // 1 click: off
+ if (event == EV_1click) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks: battcheck mode
+ else if (event == EV_2clicks) {
+ set_state(battcheck_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 3 clicks: thermal config mode
+ else if (event == EV_3clicks) {
+ set_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;
+ }
+ // 2 clicks: tempcheck mode
+ else if (event == EV_2clicks) {
+ set_state(tempcheck_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 3 clicks: beacon config mode
+ else if (event == EV_3clicks) {
+ set_state(beacon_config_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+
+#define GOODNIGHT_TICKS_PER_STEPDOWN (GOODNIGHT_TIME*TICKS_PER_SECOND*60L/GOODNIGHT_LEVEL)
+uint8_t goodnight_state(EventPtr event, uint16_t arg) {
+ static uint16_t ticks_since_stepdown = 0;
+ // blink on start
+ if (event == EV_enter_state) {
+ blink_confirm(4);
+ ticks_since_stepdown = 0;
+ set_level(GOODNIGHT_LEVEL);
+ return MISCHIEF_MANAGED;
+ }
+ // 1 click: off
+ else if (event == EV_1click) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks: beacon mode
+ else if (event == EV_2clicks) {
+ set_state(beacon_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // tick: step down (maybe) or off (maybe)
+ else if (event == EV_tick) {
+ if (++ticks_since_stepdown > GOODNIGHT_TICKS_PER_STEPDOWN) {
+ ticks_since_stepdown = 0;
+ set_level(actual_level-1);
+ if (! actual_level) {
+ #if 0 // test blink, to help measure timing
+ set_level(MAX_LEVEL>>2);
+ delay_4ms(8/2);
+ set_level(0);
+ #endif
+ set_state(off_state, 0);
+ }
+ }
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+
+uint8_t lockout_state(EventPtr event, uint16_t arg) {
+ // 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)
+ if (event == EV_tick) {
+ static uint8_t ticks_spent_awake = 0;
+ ticks_spent_awake ++;
+ if (ticks_spent_awake > 180) {
+ ticks_spent_awake = 0;
+ go_to_standby = 1;
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // 4 clicks: exit
+ else if (event == EV_4clicks) {
+ blink_confirm(2);
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+
+uint8_t momentary_state(EventPtr event, uint16_t arg) {
+ 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
+ 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;
+}
+
+
+uint8_t ramp_config_state(EventPtr event, uint16_t arg) {
+ static uint8_t config_step;
+ static uint8_t num_config_steps;
+ if (event == EV_enter_state) {
+ config_step = 0;
+ if (ramp_style) {
+ num_config_steps = 3;
+ }
+ else {
+ num_config_steps = 2;
+ }
+ 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 {
+ save_config();
+ // TODO: blink out some sort of success pattern
+ // return to steady mode
+ set_state(steady_state, memorized_level);
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // an option was set (return from number_entry_state)
+ else if (event == EV_reenter_state) {
+ #if 0
+ // FIXME? this is a kludge which relies on the vars being consecutive
+ // in RAM and in the same order as declared
+ // ... and it doesn't work; it seems they're not consecutive :(
+ volatile uint8_t *dest;
+ if (ramp_style) dest = (&ramp_discrete_floor) + config_step;
+ else dest = (&ramp_smooth_floor) + config_step;
+ if (number_entry_value)
+ *dest = number_entry_value;
+ #else
+ switch (config_step) {
+ case 0:
+ if (number_entry_value) {
+ if (ramp_style) ramp_discrete_floor = number_entry_value;
+ else ramp_smooth_floor = number_entry_value;
+ }
+ break;
+ case 1:
+ if (number_entry_value) {
+ if (ramp_style) ramp_discrete_ceil = MAX_LEVEL + 1 - number_entry_value;
+ else ramp_smooth_ceil = MAX_LEVEL + 1 - number_entry_value;
+ }
+ break;
+ case 2:
+ if (number_entry_value)
+ ramp_discrete_steps = number_entry_value;
+ break;
+ }
+ #endif
+ config_step ++;
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+
+uint8_t thermal_config_state(EventPtr event, uint16_t arg) {
+ static uint8_t done = 0;
+ if (event == EV_enter_state) {
+ set_level(0);
+ done = 0;
+ return MISCHIEF_MANAGED;
+ }
+ // advance forward through config steps
+ else if (event == EV_tick) {
+ if (! done) push_state(number_entry_state, 1);
+ else {
+ save_config();
+ // return to beacon mode
+ set_state(tempcheck_state, 0);
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // an option was set (return from number_entry_state)
+ else if (event == EV_reenter_state) {
+ if (number_entry_value) therm_ceil = 30 + number_entry_value;
+ if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL;
+ done = 1;
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+
+uint8_t beacon_config_state(EventPtr event, uint16_t arg) {
+ static uint8_t done = 0;
+ if (event == EV_enter_state) {
+ set_level(0);
+ done = 0;
+ return MISCHIEF_MANAGED;
+ }
+ // advance forward through config steps
+ else if (event == EV_tick) {
+ if (! done) push_state(number_entry_state, 1);
+ else {
+ save_config();
+ // return to beacon mode
+ set_state(beacon_state, 0);
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // an option was set (return from number_entry_state)
+ else if (event == EV_reenter_state) {
+ if (number_entry_value) beacon_seconds = number_entry_value;
+ done = 1;
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+
+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;
+ // TODO: blink out the 'arg' to show which option this is
+ 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>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);
+ }
+}
+
+
+#ifdef USE_LIGHTNING_MODE
+uint8_t pseudo_rand() {
+ static uint16_t offset = 1024;
+ // loop from 1024 to 4095
+ offset = ((offset + 1) & 0x0fff) | 0x0400;
+ pseudo_rand_seed += 0b01010101; // 85
+ return pgm_read_byte(offset) + pseudo_rand_seed;
+}
+#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];
+ strobe_type = eeprom[6]; // TODO: move this to eeprom_wl?
+ strobe_delays[0] = eeprom[7];
+ strobe_delays[1] = eeprom[8];
+ bike_flasher_brightness = eeprom[9];
+ beacon_seconds = eeprom[10];
+ therm_ceil = eeprom[11];
+ }
+}
+
+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] = strobe_type; // TODO: move this to eeprom_wl?
+ eeprom[7] = strobe_delays[0];
+ eeprom[8] = strobe_delays[1];
+ eeprom[9] = bike_flasher_brightness;
+ eeprom[10] = beacon_seconds;
+ eeprom[11] = therm_ceil;
+
+ save_eeprom();
+}
+
+
+void low_voltage() {
+ // "step down" from strobe to something low
+ if (current_state == strobe_state) {
+ set_state(steady_state, RAMP_SIZE/6);
+ }
+ // in normal mode, step down by half or turn off
+ else if (current_state == steady_state) {
+ if (actual_level > 1) {
+ set_level(actual_level >> 1);
+ }
+ 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() {
+
+ // deferred "off" so we won't suspend in a weird state
+ // (like... during the middle of a strobe pulse)
+ if (go_to_standby) {
+ go_to_standby = 0;
+ set_level(0);
+ standby_mode();
+ }
+
+ if (current_state == strobe_state) {
+ // party / tactical strobe
+ if (strobe_type < 2) {
+ set_level(MAX_LEVEL);
+ if (strobe_type == 0) { // party strobe
+ if (strobe_delays[strobe_type] < 42) delay_zero();
+ else delay_ms(1);
+ } else { //tactical strobe
+ nice_delay_ms(strobe_delays[strobe_type] >> 1);
+ }
+ set_level(0);
+ nice_delay_ms(strobe_delays[strobe_type]);
+ }
+ #ifdef USE_LIGHTNING_MODE
+ // lightning storm
+ else if (strobe_type == 2) {
+ 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() % 6);
+ brightness = 1 << (pseudo_rand() % 7); // 1, 2, 4, 8, 16, 32, 64
+ brightness += 1 << (pseudo_rand()&0x03); // 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);
+ if (! nice_delay_ms(rand_time)) return;
+
+ // decrease the brightness somewhat more gradually, like lightning
+ uint8_t stepdown = brightness >> 3;
+ if (stepdown < 1) stepdown = 1;
+ while(brightness > 1) {
+ if (! nice_delay_ms(rand_time)) return;
+ brightness -= stepdown;
+ if (brightness < 0) brightness = 0;
+ set_level(brightness);
+ }
+
+ // 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);
+
+ }
+ #endif
+ // bike flasher
+ else if (strobe_type == 3) {
+ uint8_t burst = bike_flasher_brightness << 1;
+ for(uint8_t i=0; i<4; i++) {
+ set_level(burst);
+ if (! nice_delay_ms(5)) return;
+ set_level(bike_flasher_brightness);
+ if (! nice_delay_ms(65)) return;
+ }
+ if (! nice_delay_ms(720)) return;
+ }
+ }
+
+ #ifdef USE_BATTCHECK
+ else if (current_state == battcheck_state) {
+ battcheck();
+ }
+ else if (current_state == tempcheck_state) {
+ blink_num(temperature>>2);
+ nice_delay_ms(1000);
+ }
+ // TODO: blink out therm_ceil during thermal_config_state
+ #endif
+
+ else if (current_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/anduril/anduril.txt b/spaghetti-monster/anduril/anduril.txt
new file mode 100644
index 0000000..bb0f5e2
--- /dev/null
+++ b/spaghetti-monster/anduril/anduril.txt
@@ -0,0 +1,100 @@
+From off:
+ * 1 click: memorized level
+ * Hold: lowest level then ramp up
+ * 2 clicks: highest ramp level
+ * Click, hold: highest level then ramp down
+ * 3 clicks: battcheck mode
+ (battcheck, goodnight, beacon, tempcheck)
+ * 4 clicks: lock-out
+ * 5 clicks: strobe modes
+ (bike flasher, party strobe, tactical strobe, lightning storm mode)
+ (remembers which you last used)
+ * 6 clicks: momentary mode (disconnect power to exit)
+
+In steady mode:
+ * 1 click: off
+ * Hold: ramp up
+ * Click, hold: ramp down
+ * 2 clicks: to/from turbo (actual turbo, not just highest ramp level)
+ * 3 clicks: toggle smooth vs discrete ramping
+ * 4 clicks: configure current ramp
+
+Smooth ramp config mode:
+ - Setting 1: memory on/off (not implemented yet)
+ * Setting 2: low end
+ (click N times to set ramp floor to level N)
+ * Setting 3: high end
+ (click N times to set ramp ceiling to level "151 - N")
+
+Discrete ramp config mode:
+ - Setting 1: memory on/off (not implemented yet)
+ * Setting 2: low end
+ (click N times to set ramp floor to level N)
+ * Setting 3: high end
+ (click N times to set ramp ceiling to level "151 - N")
+ * Setting 4: number of levels in discrete ramp
+ (click N times to make discrete mode have N stair-steps)
+ (minimum 2, maximum 150)
+
+Bike flasher:
+ * 1 click: off
+ * 2 clicks: party strobe
+ * Hold: brighter
+ * Click, hold: dimmer
+
+Party / Tactical strobe modes:
+ * 1 click: off
+ * Hold: change speed (faster)
+ * Click, hold: change speed (slower)
+ * 2 clicks: next strobe mode
+ (bike flasher, party strobe, tactical strobe, lightning storm mode)
+ (TODO: random/police strobe?)
+
+Lightning storm mode:
+ * 1 click: off
+ * 2 clicks: bike flasher
+
+Battcheck mode:
+ * 1 click: off
+ * 2 clicks: goodnight mode
+
+Goodnight mode:
+ * 1 click: off
+ * 2 clicks: beacon mode
+
+Beacon mode:
+ * 1 click: off
+ * 2 clicks: tempcheck mode
+ * 3 clicks: configure time between pulses
+
+Beacon config mode:
+ * At buzz, click N times to set beacon frequency to N seconds.
+
+Tempcheck mode:
+ * 1 click: off
+ * 2 clicks: battcheck mode
+ * 3 clicks: thermal config mode
+ - Hold: thermal calibration mode
+
+Thermal config mode:
+ * At buzz, click N times to set thermal limit to roughly 30 C + N.
+
+Thermal calibration mode:
+ - Hold until hot: set new ceiling value
+ - ... don't hold: blink out current ceiling value and exit
+
+Momentary mode:
+ * Press button: Light on (at memorized level).
+ * Release button: Light off.
+ * To exit, disconnect power. (loosen/tighten the tailcap)
+
+TODO:
+ * save settings in eeprom
+ - decide on "hold until hot" or "click N times" for thermal config mode
+ - test thermal regulation on an actual light
+ - improve thermal regulation
+ - a way to blink out the firmware version?
+ - indicator LED support
+ - a way to configure indicator LED behavior?
+ * add goodnight mode?
+ * add lightning mode?
diff --git a/spaghetti-monster/baton-simpler.c b/spaghetti-monster/baton-simpler.c
deleted file mode 100644
index a6ddf36..0000000
--- a/spaghetti-monster/baton-simpler.c
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Baton: Olight Baton-like UI for SpaghettiMonster.
- *
- * 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 .
- */
-
-#define FSM_EMISAR_D4_DRIVER
-#define USE_LVP
-#define USE_THERMAL_REGULATION
-#define DEFAULT_THERM_CEIL 45
-#define USE_DELAY_MS
-#include "spaghetti-monster.h"
-
-// FSM states
-uint8_t off_state(EventPtr event, uint16_t arg);
-uint8_t steady_state(EventPtr event, uint16_t arg);
-uint8_t lockout_state(EventPtr event, uint16_t arg);
-
-// brightness control
-uint8_t memorized_level = 1;
-uint8_t actual_level = 0;
-#ifdef USE_THERMAL_REGULATION
-uint8_t target_level = 0;
-#endif
-
-// deferred "off" so we won't suspend in a weird state
-volatile uint8_t go_to_standby = 0;
-
-// moon + ../../bin/level_calc.py 2 6 7135 18 10 150 FET 1 10 1500
-uint8_t pwm1_levels[] = { 3, 18, 110, 255, 255, 255, 0, };
-uint8_t pwm2_levels[] = { 0, 0, 0, 9, 58, 138, 255, };
-#define MAX_LEVEL (sizeof(pwm1_levels)-1)
-
-// set LED brightness
-void set_level(uint8_t lvl) {
- actual_level = lvl;
- PWM1_LVL = pwm1_levels[lvl];
- PWM2_LVL = pwm2_levels[lvl];
-}
-
-uint8_t off_state(EventPtr event, uint16_t arg) {
- // turn emitter off when entering state
- if (event == EV_enter_state) {
- go_to_standby = 1; // sleep while off (lower power use)
- return EVENT_HANDLED;
- }
- // hold (initially): go to lowest level, but allow abort for regular click
- else if (event == EV_click1_press) {
- set_level(0);
- return EVENT_HANDLED;
- }
- // 1 click (before timeout): go to memorized level, but allow abort for double click
- else if (event == EV_click1_release) {
- set_level(memorized_level);
- return EVENT_HANDLED;
- }
- // 1 click: regular mode
- else if (event == EV_1click) {
- set_state(steady_state, memorized_level);
- return EVENT_HANDLED;
- }
- // 2 clicks: highest mode
- else if (event == EV_2clicks) {
- set_state(steady_state, MAX_LEVEL);
- return EVENT_HANDLED;
- }
- // 4 clicks: soft lockout
- else if (event == EV_4clicks) {
- set_state(lockout_state, 0);
- return EVENT_HANDLED;
- }
- // hold: go to lowest level
- else if (event == EV_click1_hold) {
- set_state(steady_state, 0);
- return EVENT_HANDLED;
- }
- return EVENT_NOT_HANDLED;
-}
-
-uint8_t steady_state(EventPtr event, uint16_t arg) {
- // turn LED on when we first enter the mode
- if (event == EV_enter_state) {
- // remember this level, unless it's moon or turbo
- if ((arg > 0) && (arg < MAX_LEVEL))
- memorized_level = arg;
- #ifdef USE_THERMAL_REGULATION
- target_level = arg;
- #endif
- set_level(arg);
- return EVENT_HANDLED;
- }
- // 1 click: off
- else if (event == EV_1click) {
- set_state(off_state, 0);
- return EVENT_HANDLED;
- }
- // 2 clicks: go to/from highest level
- else if (event == EV_2clicks) {
- if (actual_level < MAX_LEVEL) { // go to turbo
- memorized_level = actual_level; // in case we're on moon
- #ifdef USE_THERMAL_REGULATION
- target_level = MAX_LEVEL;
- #endif
- set_level(MAX_LEVEL);
- }
- else { // return from turbo
- #ifdef USE_THERMAL_REGULATION
- target_level = memorized_level;
- #endif
- set_level(memorized_level);
- }
- return EVENT_HANDLED;
- }
- // hold: change brightness
- else if (event == EV_click1_hold) {
- if ((arg % HOLD_TIMEOUT) == 0) {
- memorized_level = (actual_level+1) % (MAX_LEVEL+1);
- #ifdef USE_THERMAL_REGULATION
- target_level = memorized_level;
- #endif
- set_level(memorized_level);
- }
- return EVENT_HANDLED;
- }
- #ifdef USE_THERMAL_REGULATION
- // overheating: drop by 1 level
- else if (event == EV_temperature_high) {
- if (actual_level > 1) {
- set_level(actual_level - 1);
- }
- return EVENT_HANDLED;
- }
- // underheating: increase by 1 level if we're lower than the target
- else if (event == EV_temperature_low) {
- if (actual_level < target_level) {
- set_level(actual_level + 1);
- }
- return EVENT_HANDLED;
- }
- #endif
- return EVENT_NOT_HANDLED;
-}
-
-uint8_t lockout_state(EventPtr event, uint16_t arg) {
- // stay asleep while locked, but allow waking long enough to click 4 times
- if (event == EV_tick) {
- static uint8_t ticks_spent_awake = 0;
- ticks_spent_awake ++;
- PWM1_LVL = 0; PWM2_LVL = 0;
- if (ticks_spent_awake > 3 * TICKS_PER_SECOND) {
- ticks_spent_awake = 0;
- go_to_standby = 1;
- }
- return MISCHIEF_MANAGED;
- }
- // 4 clicks: exit
- else if (event == EV_4clicks) {
- set_state(steady_state, 1);
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
-
-void low_voltage() {
- // step down by one level or turn off
- if (actual_level > 0) {
- set_level(actual_level - 1);
- }
- else {
- set_state(off_state, 0);
- }
-}
-
-void setup() {
- // blink when power is connected
- set_level(MAX_LEVEL/2);
- delay_ms(10);
- set_level(0);
-
- push_state(off_state, 0);
-}
-
-void loop() {
- if (go_to_standby) {
- go_to_standby = 0;
- PWM1_LVL = 0; PWM2_LVL = 0;
- standby_mode();
- }
-}
diff --git a/spaghetti-monster/baton.c b/spaghetti-monster/baton.c
deleted file mode 100644
index 4ba329e..0000000
--- a/spaghetti-monster/baton.c
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Baton: Olight Baton-like UI for SpaghettiMonster.
- *
- * 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 .
- */
-
-#define FSM_EMISAR_D4_DRIVER
-#define USE_LVP
-#define USE_THERMAL_REGULATION
-#define DEFAULT_THERM_CEIL 45
-#define USE_DEBUG_BLINK
-#define USE_DELAY_MS
-#define USE_DELAY_ZERO
-#include "spaghetti-monster.h"
-
-// moon + ../../bin/level_calc.py 2 6 7135 18 10 150 FET 1 10 1500
-uint8_t pwm1_modes[] = { 3, 18, 110, 255, 255, 255, 0, };
-uint8_t pwm2_modes[] = { 0, 0, 0, 9, 58, 138, 255, };
-#define MAX_LEVEL (sizeof(pwm1_modes)-1)
-
-// FSM states
-uint8_t off_state(EventPtr event, uint16_t arg);
-uint8_t steady_state(EventPtr event, uint16_t arg);
-uint8_t party_strobe_state(EventPtr event, uint16_t arg);
-
-// brightness control
-uint8_t memorized_level = 1;
-uint8_t actual_level = 0;
-#ifdef USE_THERMAL_REGULATION
-uint8_t target_level = 0;
-#endif
-
-void set_level(uint8_t lvl) {
- actual_level = lvl;
- PWM1_LVL = pwm1_modes[lvl];
- PWM2_LVL = pwm2_modes[lvl];
-}
-
-uint8_t off_state(EventPtr event, uint16_t arg) {
- // turn emitter off when entering state
- if (event == EV_enter_state) {
- PWM1_LVL = 0;
- PWM2_LVL = 0;
- // sleep while off (lower power use)
- //empty_event_sequence(); // just in case (but shouldn't be needed)
- standby_mode();
- return 0;
- }
- // hold (initially): go to lowest level, but allow abort for regular click
- else if (event == EV_click1_press) {
- set_level(0);
- return 0;
- }
- // 1 click (before timeout): go to memorized level, but allow abort for double click
- else if (event == EV_click1_release) {
- set_level(memorized_level);
- return 0;
- }
- // 1 click: regular mode
- else if (event == EV_1click) {
- set_state(steady_state, memorized_level);
- return 0;
- }
- // 2 clicks: highest mode
- else if (event == EV_2clicks) {
- set_state(steady_state, MAX_LEVEL);
- return 0;
- }
- // 3 clicks: strobe mode
- else if (event == EV_3clicks) {
- set_state(party_strobe_state, 255);
- return 0;
- }
- // hold: go to lowest level
- else if (event == EV_click1_hold) {
- set_state(steady_state, 0);
- return 0;
- }
- return 1;
-}
-
-uint8_t steady_state(EventPtr event, uint16_t arg) {
- // turn LED on when we first enter the mode
- if (event == EV_enter_state) {
- // remember this level, unless it's moon or turbo
- if ((arg > 0) && (arg < MAX_LEVEL))
- memorized_level = arg;
- // use the requested level even if not memorized
- #ifdef USE_THERMAL_REGULATION
- target_level = arg;
- #endif
- set_level(arg);
- return 0;
- }
- // 1 click: off
- else if (event == EV_1click) {
- set_state(off_state, 0);
- return 0;
- }
- // 2 clicks: go to/from highest level
- else if (event == EV_2clicks) {
- if (actual_level < MAX_LEVEL) {
- memorized_level = actual_level; // in case we're on moon
- #ifdef USE_THERMAL_REGULATION
- target_level = MAX_LEVEL;
- #endif
- set_level(MAX_LEVEL);
- }
- else {
- #ifdef USE_THERMAL_REGULATION
- target_level = memorized_level;
- #endif
- set_level(memorized_level);
- }
- return 0;
- }
- // 3 clicks: go to strobe modes
- else if (event == EV_3clicks) {
- set_state(party_strobe_state, 0xff);
- return 0;
- }
- // hold: change brightness
- else if (event == EV_click1_hold) {
- if ((arg % HOLD_TIMEOUT) == 0) {
- memorized_level = (actual_level+1) % (MAX_LEVEL+1);
- #ifdef USE_THERMAL_REGULATION
- target_level = memorized_level;
- #endif
- set_level(memorized_level);
- }
- return 0;
- }
- #ifdef USE_THERMAL_REGULATION
- // overheating: drop by 1 level
- else if (event == EV_temperature_high) {
- if (actual_level > 1) {
- set_level(actual_level - 1);
- }
- return 0;
- }
- // underheating: increase by 1 level if we're lower than the target
- else if (event == EV_temperature_low) {
- if (actual_level < target_level) {
- set_level(actual_level + 1);
- }
- return 0;
- }
- #endif
- return 1;
-}
-
-uint8_t party_strobe_state(EventPtr event, uint16_t arg) {
- static volatile uint8_t frames = 0;
- static volatile uint8_t between = 2;
- if (event == EV_enter_state) {
- if (arg < 64) between = arg;
- frames = 0;
- return 0;
- }
- // tick: strobe the emitter
- else if (event == EV_tick) {
- if (frames == 0) {
- PWM1_LVL = 0;
- PWM2_LVL = 255;
- if (between < 3) delay_zero();
- else delay_ms(1);
- PWM2_LVL = 0;
- }
- //frames = (frames + 1) % between;
- frames++;
- if (frames > between) frames = 0;
- return 0;
- }
- // 1 click: off
- else if (event == EV_1click) {
- set_state(off_state, 0);
- return 0;
- }
- // 2 clicks: go back to regular modes
- else if (event == EV_2clicks) {
- set_state(steady_state, memorized_level);
- return 0;
- }
- // hold: change speed
- else if (event == EV_click1_hold) {
- if ((arg % HOLD_TIMEOUT) == 0) {
- between = (between+1)%6;
- frames = 0;
- }
- return 0;
- }
- return 1;
-}
-
-void low_voltage() {
- // "step down" from strobe to level 2
- if (current_state == party_strobe_state) {
- set_state(steady_state, 1);
- }
- // in normal mode, step down by one level or turn off
- else if (current_state == steady_state) {
- if (actual_level > 0) {
- set_level(actual_level - 1);
- }
- else {
- set_state(off_state, 0);
- }
- }
-}
-
-void setup() {
- debug_blink(2);
-
- push_state(off_state, 0);
-}
-
-void loop() { }
diff --git a/spaghetti-monster/baton/baton-simpler.c b/spaghetti-monster/baton/baton-simpler.c
new file mode 100644
index 0000000..a6ddf36
--- /dev/null
+++ b/spaghetti-monster/baton/baton-simpler.c
@@ -0,0 +1,202 @@
+/*
+ * Baton: Olight Baton-like UI for SpaghettiMonster.
+ *
+ * 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 .
+ */
+
+#define FSM_EMISAR_D4_DRIVER
+#define USE_LVP
+#define USE_THERMAL_REGULATION
+#define DEFAULT_THERM_CEIL 45
+#define USE_DELAY_MS
+#include "spaghetti-monster.h"
+
+// FSM states
+uint8_t off_state(EventPtr event, uint16_t arg);
+uint8_t steady_state(EventPtr event, uint16_t arg);
+uint8_t lockout_state(EventPtr event, uint16_t arg);
+
+// brightness control
+uint8_t memorized_level = 1;
+uint8_t actual_level = 0;
+#ifdef USE_THERMAL_REGULATION
+uint8_t target_level = 0;
+#endif
+
+// deferred "off" so we won't suspend in a weird state
+volatile uint8_t go_to_standby = 0;
+
+// moon + ../../bin/level_calc.py 2 6 7135 18 10 150 FET 1 10 1500
+uint8_t pwm1_levels[] = { 3, 18, 110, 255, 255, 255, 0, };
+uint8_t pwm2_levels[] = { 0, 0, 0, 9, 58, 138, 255, };
+#define MAX_LEVEL (sizeof(pwm1_levels)-1)
+
+// set LED brightness
+void set_level(uint8_t lvl) {
+ actual_level = lvl;
+ PWM1_LVL = pwm1_levels[lvl];
+ PWM2_LVL = pwm2_levels[lvl];
+}
+
+uint8_t off_state(EventPtr event, uint16_t arg) {
+ // turn emitter off when entering state
+ if (event == EV_enter_state) {
+ go_to_standby = 1; // sleep while off (lower power use)
+ return EVENT_HANDLED;
+ }
+ // hold (initially): go to lowest level, but allow abort for regular click
+ else if (event == EV_click1_press) {
+ set_level(0);
+ return EVENT_HANDLED;
+ }
+ // 1 click (before timeout): go to memorized level, but allow abort for double click
+ else if (event == EV_click1_release) {
+ set_level(memorized_level);
+ return EVENT_HANDLED;
+ }
+ // 1 click: regular mode
+ else if (event == EV_1click) {
+ set_state(steady_state, memorized_level);
+ return EVENT_HANDLED;
+ }
+ // 2 clicks: highest mode
+ else if (event == EV_2clicks) {
+ set_state(steady_state, MAX_LEVEL);
+ return EVENT_HANDLED;
+ }
+ // 4 clicks: soft lockout
+ else if (event == EV_4clicks) {
+ set_state(lockout_state, 0);
+ return EVENT_HANDLED;
+ }
+ // hold: go to lowest level
+ else if (event == EV_click1_hold) {
+ set_state(steady_state, 0);
+ return EVENT_HANDLED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+uint8_t steady_state(EventPtr event, uint16_t arg) {
+ // turn LED on when we first enter the mode
+ if (event == EV_enter_state) {
+ // remember this level, unless it's moon or turbo
+ if ((arg > 0) && (arg < MAX_LEVEL))
+ memorized_level = arg;
+ #ifdef USE_THERMAL_REGULATION
+ target_level = arg;
+ #endif
+ set_level(arg);
+ return EVENT_HANDLED;
+ }
+ // 1 click: off
+ else if (event == EV_1click) {
+ set_state(off_state, 0);
+ return EVENT_HANDLED;
+ }
+ // 2 clicks: go to/from highest level
+ else if (event == EV_2clicks) {
+ if (actual_level < MAX_LEVEL) { // go to turbo
+ memorized_level = actual_level; // in case we're on moon
+ #ifdef USE_THERMAL_REGULATION
+ target_level = MAX_LEVEL;
+ #endif
+ set_level(MAX_LEVEL);
+ }
+ else { // return from turbo
+ #ifdef USE_THERMAL_REGULATION
+ target_level = memorized_level;
+ #endif
+ set_level(memorized_level);
+ }
+ return EVENT_HANDLED;
+ }
+ // hold: change brightness
+ else if (event == EV_click1_hold) {
+ if ((arg % HOLD_TIMEOUT) == 0) {
+ memorized_level = (actual_level+1) % (MAX_LEVEL+1);
+ #ifdef USE_THERMAL_REGULATION
+ target_level = memorized_level;
+ #endif
+ set_level(memorized_level);
+ }
+ return EVENT_HANDLED;
+ }
+ #ifdef USE_THERMAL_REGULATION
+ // overheating: drop by 1 level
+ else if (event == EV_temperature_high) {
+ if (actual_level > 1) {
+ set_level(actual_level - 1);
+ }
+ return EVENT_HANDLED;
+ }
+ // underheating: increase by 1 level if we're lower than the target
+ else if (event == EV_temperature_low) {
+ if (actual_level < target_level) {
+ set_level(actual_level + 1);
+ }
+ return EVENT_HANDLED;
+ }
+ #endif
+ return EVENT_NOT_HANDLED;
+}
+
+uint8_t lockout_state(EventPtr event, uint16_t arg) {
+ // stay asleep while locked, but allow waking long enough to click 4 times
+ if (event == EV_tick) {
+ static uint8_t ticks_spent_awake = 0;
+ ticks_spent_awake ++;
+ PWM1_LVL = 0; PWM2_LVL = 0;
+ if (ticks_spent_awake > 3 * TICKS_PER_SECOND) {
+ ticks_spent_awake = 0;
+ go_to_standby = 1;
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // 4 clicks: exit
+ else if (event == EV_4clicks) {
+ set_state(steady_state, 1);
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+void low_voltage() {
+ // step down by one level or turn off
+ if (actual_level > 0) {
+ set_level(actual_level - 1);
+ }
+ else {
+ set_state(off_state, 0);
+ }
+}
+
+void setup() {
+ // blink when power is connected
+ set_level(MAX_LEVEL/2);
+ delay_ms(10);
+ set_level(0);
+
+ push_state(off_state, 0);
+}
+
+void loop() {
+ if (go_to_standby) {
+ go_to_standby = 0;
+ PWM1_LVL = 0; PWM2_LVL = 0;
+ standby_mode();
+ }
+}
diff --git a/spaghetti-monster/baton/baton.c b/spaghetti-monster/baton/baton.c
new file mode 100644
index 0000000..515f2d0
--- /dev/null
+++ b/spaghetti-monster/baton/baton.c
@@ -0,0 +1,231 @@
+/*
+ * Baton: Olight Baton-like UI for SpaghettiMonster.
+ *
+ * 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 .
+ */
+
+#define FSM_EMISAR_D4_DRIVER
+#define USE_LVP
+#define USE_THERMAL_REGULATION
+#define DEFAULT_THERM_CEIL 45
+#define USE_DEBUG_BLINK
+#define USE_DELAY_MS
+#define USE_DELAY_4MS
+#define USE_DELAY_ZERO
+#include "spaghetti-monster.h"
+
+// moon + ../../bin/level_calc.py 2 6 7135 18 10 150 FET 1 10 1500
+uint8_t pwm1_modes[] = { 3, 18, 110, 255, 255, 255, 0, };
+uint8_t pwm2_modes[] = { 0, 0, 0, 9, 58, 138, 255, };
+#define MAX_LEVEL (sizeof(pwm1_modes)-1)
+
+// FSM states
+uint8_t off_state(EventPtr event, uint16_t arg);
+uint8_t steady_state(EventPtr event, uint16_t arg);
+uint8_t party_strobe_state(EventPtr event, uint16_t arg);
+
+// brightness control
+uint8_t memorized_level = 1;
+uint8_t actual_level = 0;
+#ifdef USE_THERMAL_REGULATION
+uint8_t target_level = 0;
+#endif
+
+void set_level(uint8_t lvl) {
+ actual_level = lvl;
+ PWM1_LVL = pwm1_modes[lvl];
+ PWM2_LVL = pwm2_modes[lvl];
+}
+
+uint8_t off_state(EventPtr event, uint16_t arg) {
+ // turn emitter off when entering state
+ if (event == EV_enter_state) {
+ PWM1_LVL = 0;
+ PWM2_LVL = 0;
+ // sleep while off (lower power use)
+ //empty_event_sequence(); // just in case (but shouldn't be needed)
+ standby_mode();
+ return 0;
+ }
+ // hold (initially): go to lowest level, but allow abort for regular click
+ else if (event == EV_click1_press) {
+ set_level(0);
+ return 0;
+ }
+ // 1 click (before timeout): go to memorized level, but allow abort for double click
+ else if (event == EV_click1_release) {
+ set_level(memorized_level);
+ return 0;
+ }
+ // 1 click: regular mode
+ else if (event == EV_1click) {
+ set_state(steady_state, memorized_level);
+ return 0;
+ }
+ // 2 clicks: highest mode
+ else if (event == EV_2clicks) {
+ set_state(steady_state, MAX_LEVEL);
+ return 0;
+ }
+ // 3 clicks: strobe mode
+ else if (event == EV_3clicks) {
+ set_state(party_strobe_state, 255);
+ return 0;
+ }
+ // hold: go to lowest level
+ else if (event == EV_click1_hold) {
+ set_state(steady_state, 0);
+ return 0;
+ }
+ return 1;
+}
+
+uint8_t steady_state(EventPtr event, uint16_t arg) {
+ // turn LED on when we first enter the mode
+ if (event == EV_enter_state) {
+ // remember this level, unless it's moon or turbo
+ if ((arg > 0) && (arg < MAX_LEVEL))
+ memorized_level = arg;
+ // use the requested level even if not memorized
+ #ifdef USE_THERMAL_REGULATION
+ target_level = arg;
+ #endif
+ set_level(arg);
+ return 0;
+ }
+ // 1 click: off
+ else if (event == EV_1click) {
+ set_state(off_state, 0);
+ return 0;
+ }
+ // 2 clicks: go to/from highest level
+ else if (event == EV_2clicks) {
+ if (actual_level < MAX_LEVEL) {
+ memorized_level = actual_level; // in case we're on moon
+ #ifdef USE_THERMAL_REGULATION
+ target_level = MAX_LEVEL;
+ #endif
+ set_level(MAX_LEVEL);
+ }
+ else {
+ #ifdef USE_THERMAL_REGULATION
+ target_level = memorized_level;
+ #endif
+ set_level(memorized_level);
+ }
+ return 0;
+ }
+ // 3 clicks: go to strobe modes
+ else if (event == EV_3clicks) {
+ set_state(party_strobe_state, 0xff);
+ return 0;
+ }
+ // hold: change brightness
+ else if (event == EV_click1_hold) {
+ if ((arg % HOLD_TIMEOUT) == 0) {
+ memorized_level = (actual_level+1) % (MAX_LEVEL+1);
+ #ifdef USE_THERMAL_REGULATION
+ target_level = memorized_level;
+ #endif
+ set_level(memorized_level);
+ }
+ return 0;
+ }
+ #ifdef USE_THERMAL_REGULATION
+ // overheating: drop by 1 level
+ else if (event == EV_temperature_high) {
+ if (actual_level > 1) {
+ set_level(actual_level - 1);
+ }
+ return 0;
+ }
+ // underheating: increase by 1 level if we're lower than the target
+ else if (event == EV_temperature_low) {
+ if (actual_level < target_level) {
+ set_level(actual_level + 1);
+ }
+ return 0;
+ }
+ #endif
+ return 1;
+}
+
+uint8_t party_strobe_state(EventPtr event, uint16_t arg) {
+ static volatile uint8_t frames = 0;
+ static volatile uint8_t between = 2;
+ if (event == EV_enter_state) {
+ if (arg < 64) between = arg;
+ frames = 0;
+ return 0;
+ }
+ // tick: strobe the emitter
+ else if (event == EV_tick) {
+ if (frames == 0) {
+ PWM1_LVL = 0;
+ PWM2_LVL = 255;
+ if (between < 3) delay_zero();
+ else delay_ms(1);
+ PWM2_LVL = 0;
+ }
+ //frames = (frames + 1) % between;
+ frames++;
+ if (frames > between) frames = 0;
+ return 0;
+ }
+ // 1 click: off
+ else if (event == EV_1click) {
+ set_state(off_state, 0);
+ return 0;
+ }
+ // 2 clicks: go back to regular modes
+ else if (event == EV_2clicks) {
+ set_state(steady_state, memorized_level);
+ return 0;
+ }
+ // hold: change speed
+ else if (event == EV_click1_hold) {
+ if ((arg % HOLD_TIMEOUT) == 0) {
+ between = (between+1)%6;
+ frames = 0;
+ }
+ return 0;
+ }
+ return 1;
+}
+
+void low_voltage() {
+ // "step down" from strobe to level 2
+ if (current_state == party_strobe_state) {
+ set_state(steady_state, 1);
+ }
+ // in normal mode, step down by one level or turn off
+ else if (current_state == steady_state) {
+ if (actual_level > 0) {
+ set_level(actual_level - 1);
+ }
+ else {
+ set_state(off_state, 0);
+ }
+ }
+}
+
+void setup() {
+ debug_blink(2);
+
+ push_state(off_state, 0);
+}
+
+void loop() { }
diff --git a/spaghetti-monster/darkhorse.c b/spaghetti-monster/darkhorse.c
deleted file mode 100644
index 2aa3e90..0000000
--- a/spaghetti-monster/darkhorse.c
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * DarkHorse: Improved ZebraLight clone UI for SpaghettiMonster.
- *
- * 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 .
- */
-
-#define FSM_EMISAR_D4_DRIVER
-#define USE_LVP
-#define USE_THERMAL_REGULATION
-#define DEFAULT_THERM_CEIL 45
-#define USE_DELAY_4MS
-#define USE_RAMPING
-#define RAMP_LENGTH 150
-#define USE_BATTCHECK
-#define BATTCHECK_4bars
-#define DONT_DELAY_AFTER_BATTCHECK
-#define USE_EEPROM
-#define EEPROM_BYTES 5
-#include "spaghetti-monster.h"
-
-// FSM states
-uint8_t off_state(EventPtr event, uint16_t arg);
-uint8_t low_mode_state(EventPtr event, uint16_t arg);
-uint8_t med_mode_state(EventPtr event, uint16_t arg);
-uint8_t hi_mode_state(EventPtr event, uint16_t arg);
-uint8_t strobe_beacon_state(EventPtr event, uint16_t arg);
-#ifdef USE_BATTCHECK
-uint8_t battcheck_state(EventPtr event, uint16_t arg);
-#endif
-// Not a FSM state, just handles stuff common to all low/med/hi states
-uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t *secondary, uint8_t *modes);
-
-void load_config();
-void save_config();
-
-// toggle between L1/L2, M1/M2, H1/H2
-uint8_t L1 = 1;
-uint8_t M1 = 1;
-uint8_t H1 = 1;
-// brightness for L2, M2, H2 (valid range 1 to 3 inclusive)
-uint8_t L2 = 1;
-uint8_t M2 = 1;
-uint8_t H2 = 1;
-// mode groups, ish
-uint8_t low_modes[] = {12, 1, 4, 9}; // 3.3 lm, 2.0 lm, 0.8 lm, 0.3 lm
-uint8_t med_modes[] = {56, 21, 29, 37}; // 101 lm, 35 lm, 20 lm, 10 lm
-uint8_t hi_modes[] = {MAX_LEVEL, 81, 96, 113}; // 1500 lm, 678 lm, 430 lm, 270 lm
-// strobe/beacon modes:
-// 0: 0.2 Hz beacon at L1
-// 1: 0.2 Hz beacon at H1
-// 2: 4 Hz strobe at H1
-// 3: 19 Hz strobe at H1
-uint8_t strobe_beacon_mode = 0;
-
-// deferred "off" so we won't suspend in a weird state
-volatile uint8_t go_to_standby = 0;
-
-#ifdef USE_THERMAL_REGULATION
-// brightness before thermal step-down
-uint8_t target_level = 0;
-#endif
-
-void set_any_mode(uint8_t primary, uint8_t secondary, uint8_t *modes) {
- // primary (H1/M1/L1)
- if (primary) {
- set_level(modes[0]);
- }
- // secondary (H2/M2/L2)
- else {
- set_level(modes[secondary]);
- }
- #ifdef USE_THERMAL_REGULATION
- target_level = actual_level;
- #endif
-}
-
-inline void set_low_mode() { set_any_mode(L1, L2, low_modes); }
-inline void set_med_mode() { set_any_mode(M1, M2, med_modes); }
-inline void set_hi_mode() { set_any_mode(H1, H2, hi_modes); }
-
-
-uint8_t off_state(EventPtr event, uint16_t arg) {
- // turn emitter off when entering state
- if (event == EV_enter_state) {
- set_level(0);
- // sleep while off (lower power use)
- go_to_standby = 1;
- return EVENT_HANDLED;
- }
- // hold (initially): go to lowest level, but allow abort for regular click
- else if (event == EV_click1_press) {
- set_low_mode();
- return EVENT_HANDLED;
- }
- // 1 click (before timeout): go to high level, but allow abort for double click
- else if (event == EV_click1_release) {
- set_hi_mode();
- return EVENT_HANDLED;
- }
- // 1 click: high mode
- else if (event == EV_1click) {
- set_state(hi_mode_state, 0);
- return EVENT_HANDLED;
- }
- // click, press (initially): go to medium mode, but allow abort
- else if (event == EV_click2_press) {
- set_med_mode();
- return EVENT_HANDLED;
- }
- // 2 clicks: medium mode
- else if (event == EV_2clicks) {
- set_state(med_mode_state, 0);
- return EVENT_HANDLED;
- }
- // click, click, press (initially): light off, prep for blinkies
- else if (event == EV_click3_press) {
- set_level(0);
- return EVENT_HANDLED;
- }
- // 3 clicks: strobe mode
- else if (event == EV_3clicks) {
- set_state(strobe_beacon_state, 0);
- return EVENT_HANDLED;
- }
- #ifdef USE_BATTCHECK
- // 4 clicks: battcheck mode
- else if (event == EV_4clicks) {
- set_state(battcheck_state, 0);
- return EVENT_HANDLED;
- }
- #endif
- // hold: go to low mode, but allow ramping up
- else if (event == EV_click1_hold) {
- // don't start ramping immediately;
- // give the user time to release at low mode
- if (arg >= HOLD_TIMEOUT)
- set_state(low_mode_state, 0);
- return EVENT_HANDLED;
- }
- // hold, release quickly: go to low mode
- else if (event == EV_click1_hold_release) {
- set_state(low_mode_state, 0);
- return EVENT_HANDLED;
- }
- /* TODO: implement
- // click-release-hold: discrete ramp through all levels
- else if (event == EV_click2_hold) {
- set_state(steady_state, MAX_LEVEL);
- return EVENT_HANDLED;
- }
- */
- return EVENT_NOT_HANDLED;
-}
-
-
-uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t *secondary, uint8_t *modes) {
- // turn on LED when entering the mode
- if (event == EV_enter_state) {
- set_any_mode(*primary, *secondary, modes);
- return MISCHIEF_MANAGED;
- }
- // 1 click: off
- else if (event == EV_1click) {
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
- // hold: change brightness (low, med, hi, always starting at low)
- else if (event == EV_click1_hold) {
- uint8_t which = arg % (HOLD_TIMEOUT * 3) / HOLD_TIMEOUT;
- switch(which) {
- case 0:
- set_state(low_mode_state, 0);
- break;
- case 1:
- set_state(med_mode_state, 0);
- break;
- case 2:
- set_state(hi_mode_state, 0);
- break;
- }
- return MISCHIEF_MANAGED;
- }
- // 2 clicks: toggle primary/secondary level
- else if (event == EV_2clicks) {
- *primary ^= 1;
- set_any_mode(*primary, *secondary, modes);
- save_config();
- return MISCHIEF_MANAGED;
- }
- // click-release-hold: change secondary level
- else if (event == EV_click2_hold) {
- if (arg % HOLD_TIMEOUT == 0) {
- *secondary = (*secondary + 1) & 3;
- if (! *secondary) *secondary = 1;
- *primary = 0;
- set_any_mode(*primary, *secondary, modes);
- }
- return MISCHIEF_MANAGED;
- }
- // click, hold, release: save secondary level
- else if (event == EV_click2_hold_release) {
- save_config();
- }
- #ifdef USE_THERMAL_REGULATION
- // TODO: test this on a real light
- // overheating: drop by an amount proportional to how far we are above the ceiling
- else if (event == EV_temperature_high) {
- if (actual_level > MAX_LEVEL/4) {
- uint8_t stepdown = actual_level - arg;
- if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4;
- set_level(stepdown);
- }
- return EVENT_HANDLED;
- }
- // underheating: increase slowly if we're lower than the target
- // (proportional to how low we are)
- else if (event == EV_temperature_low) {
- if (actual_level < target_level) {
- uint8_t stepup = actual_level + (arg>>1);
- if (stepup > target_level) stepup = target_level;
- set_level(stepup);
- }
- return EVENT_HANDLED;
- }
- #endif
- return EVENT_NOT_HANDLED;
-}
-
-uint8_t low_mode_state(EventPtr event, uint16_t arg) {
- return any_mode_state(event, arg, &L1, &L2, low_modes);
-}
-
-uint8_t med_mode_state(EventPtr event, uint16_t arg) {
- return any_mode_state(event, arg, &M1, &M2, med_modes);
-}
-
-uint8_t hi_mode_state(EventPtr event, uint16_t arg) {
- return any_mode_state(event, arg, &H1, &H2, hi_modes);
-}
-
-
-#ifdef USE_BATTCHECK
-uint8_t battcheck_state(EventPtr event, uint16_t arg) {
- return EVENT_NOT_HANDLED;
-}
-#endif
-
-
-uint8_t strobe_beacon_state(EventPtr event, uint16_t arg) {
- // 1 click: off
- if (event == EV_1click) {
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
- // 1 click (initially): cancel current blink
- if (event == EV_click1_release) {
- interrupt_nice_delays();
- return MISCHIEF_MANAGED;
- }
- // 2 clicks: rotate through blinky modes
- else if (event == EV_2clicks) {
- strobe_beacon_mode = (strobe_beacon_mode + 1) & 3;
- save_config();
- interrupt_nice_delays();
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
-
-
-void low_voltage() {
- if (current_state == hi_mode_state) {
- set_state(med_mode_state, 0);
- }
- else if (current_state == med_mode_state) {
- set_state(low_mode_state, 0);
- }
- else if (current_state == low_mode_state) {
- set_state(off_state, 0);
- }
- // "step down" from blinkies to low
- else if (current_state == strobe_beacon_state) {
- set_state(low_mode_state, 0);
- }
-}
-
-void strobe(uint8_t level, uint16_t ontime, uint16_t offtime) {
- set_level(level);
- if (! nice_delay_ms(ontime)) return;
- set_level(0);
- nice_delay_ms(offtime);
-}
-
-void load_config() {
- if (load_eeprom()) {
- H1 = !(!(eeprom[0] & 0b00000100));
- M1 = !(!(eeprom[0] & 0b00000010));
- L1 = !(!(eeprom[0] & 0b00000001));
- H2 = eeprom[1];
- M2 = eeprom[2];
- L2 = eeprom[3];
- strobe_beacon_mode = eeprom[4];
- }
-}
-
-void save_config() {
- eeprom[0] = (H1<<2) | (M1<<1) | (L1);
- eeprom[1] = H2;
- eeprom[2] = M2;
- eeprom[3] = L2;
- eeprom[4] = strobe_beacon_mode;
-
- save_eeprom();
-}
-
-void setup() {
- set_level(RAMP_SIZE/8);
- delay_4ms(3);
- set_level(0);
-
- load_config();
-
- push_state(off_state, 0);
-}
-
-void loop() {
- // deferred "off" so we won't suspend in a weird state
- // (like... during the middle of a strobe pulse)
- if (go_to_standby) {
- go_to_standby = 0;
- set_level(0);
- standby_mode();
- }
-
- if (current_state == strobe_beacon_state) {
- switch(strobe_beacon_mode) {
- // 0.2 Hz beacon at L1
- case 0:
- strobe(low_modes[0], 500, 4500);
- break;
- // 0.2 Hz beacon at H1
- case 1:
- strobe(hi_modes[0], 500, 4500);
- break;
- // 4 Hz tactical strobe at H1
- case 2:
- strobe(hi_modes[0], 83, 167);
- break;
- // 19 Hz tactical strobe at H1
- case 3:
- strobe(hi_modes[0], 17, 35);
- break;
- }
- }
-
- #ifdef USE_BATTCHECK
- else if (current_state == battcheck_state) {
- nice_delay_ms(500); // wait a moment to measure voltage
- battcheck();
- set_state(off_state, 0);
- }
- #endif
-}
-
-
diff --git a/spaghetti-monster/darkhorse/darkhorse.c b/spaghetti-monster/darkhorse/darkhorse.c
new file mode 100644
index 0000000..2aa3e90
--- /dev/null
+++ b/spaghetti-monster/darkhorse/darkhorse.c
@@ -0,0 +1,378 @@
+/*
+ * DarkHorse: Improved ZebraLight clone UI for SpaghettiMonster.
+ *
+ * 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 .
+ */
+
+#define FSM_EMISAR_D4_DRIVER
+#define USE_LVP
+#define USE_THERMAL_REGULATION
+#define DEFAULT_THERM_CEIL 45
+#define USE_DELAY_4MS
+#define USE_RAMPING
+#define RAMP_LENGTH 150
+#define USE_BATTCHECK
+#define BATTCHECK_4bars
+#define DONT_DELAY_AFTER_BATTCHECK
+#define USE_EEPROM
+#define EEPROM_BYTES 5
+#include "spaghetti-monster.h"
+
+// FSM states
+uint8_t off_state(EventPtr event, uint16_t arg);
+uint8_t low_mode_state(EventPtr event, uint16_t arg);
+uint8_t med_mode_state(EventPtr event, uint16_t arg);
+uint8_t hi_mode_state(EventPtr event, uint16_t arg);
+uint8_t strobe_beacon_state(EventPtr event, uint16_t arg);
+#ifdef USE_BATTCHECK
+uint8_t battcheck_state(EventPtr event, uint16_t arg);
+#endif
+// Not a FSM state, just handles stuff common to all low/med/hi states
+uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t *secondary, uint8_t *modes);
+
+void load_config();
+void save_config();
+
+// toggle between L1/L2, M1/M2, H1/H2
+uint8_t L1 = 1;
+uint8_t M1 = 1;
+uint8_t H1 = 1;
+// brightness for L2, M2, H2 (valid range 1 to 3 inclusive)
+uint8_t L2 = 1;
+uint8_t M2 = 1;
+uint8_t H2 = 1;
+// mode groups, ish
+uint8_t low_modes[] = {12, 1, 4, 9}; // 3.3 lm, 2.0 lm, 0.8 lm, 0.3 lm
+uint8_t med_modes[] = {56, 21, 29, 37}; // 101 lm, 35 lm, 20 lm, 10 lm
+uint8_t hi_modes[] = {MAX_LEVEL, 81, 96, 113}; // 1500 lm, 678 lm, 430 lm, 270 lm
+// strobe/beacon modes:
+// 0: 0.2 Hz beacon at L1
+// 1: 0.2 Hz beacon at H1
+// 2: 4 Hz strobe at H1
+// 3: 19 Hz strobe at H1
+uint8_t strobe_beacon_mode = 0;
+
+// deferred "off" so we won't suspend in a weird state
+volatile uint8_t go_to_standby = 0;
+
+#ifdef USE_THERMAL_REGULATION
+// brightness before thermal step-down
+uint8_t target_level = 0;
+#endif
+
+void set_any_mode(uint8_t primary, uint8_t secondary, uint8_t *modes) {
+ // primary (H1/M1/L1)
+ if (primary) {
+ set_level(modes[0]);
+ }
+ // secondary (H2/M2/L2)
+ else {
+ set_level(modes[secondary]);
+ }
+ #ifdef USE_THERMAL_REGULATION
+ target_level = actual_level;
+ #endif
+}
+
+inline void set_low_mode() { set_any_mode(L1, L2, low_modes); }
+inline void set_med_mode() { set_any_mode(M1, M2, med_modes); }
+inline void set_hi_mode() { set_any_mode(H1, H2, hi_modes); }
+
+
+uint8_t off_state(EventPtr event, uint16_t arg) {
+ // turn emitter off when entering state
+ if (event == EV_enter_state) {
+ set_level(0);
+ // sleep while off (lower power use)
+ go_to_standby = 1;
+ return EVENT_HANDLED;
+ }
+ // hold (initially): go to lowest level, but allow abort for regular click
+ else if (event == EV_click1_press) {
+ set_low_mode();
+ return EVENT_HANDLED;
+ }
+ // 1 click (before timeout): go to high level, but allow abort for double click
+ else if (event == EV_click1_release) {
+ set_hi_mode();
+ return EVENT_HANDLED;
+ }
+ // 1 click: high mode
+ else if (event == EV_1click) {
+ set_state(hi_mode_state, 0);
+ return EVENT_HANDLED;
+ }
+ // click, press (initially): go to medium mode, but allow abort
+ else if (event == EV_click2_press) {
+ set_med_mode();
+ return EVENT_HANDLED;
+ }
+ // 2 clicks: medium mode
+ else if (event == EV_2clicks) {
+ set_state(med_mode_state, 0);
+ return EVENT_HANDLED;
+ }
+ // click, click, press (initially): light off, prep for blinkies
+ else if (event == EV_click3_press) {
+ set_level(0);
+ return EVENT_HANDLED;
+ }
+ // 3 clicks: strobe mode
+ else if (event == EV_3clicks) {
+ set_state(strobe_beacon_state, 0);
+ return EVENT_HANDLED;
+ }
+ #ifdef USE_BATTCHECK
+ // 4 clicks: battcheck mode
+ else if (event == EV_4clicks) {
+ set_state(battcheck_state, 0);
+ return EVENT_HANDLED;
+ }
+ #endif
+ // hold: go to low mode, but allow ramping up
+ else if (event == EV_click1_hold) {
+ // don't start ramping immediately;
+ // give the user time to release at low mode
+ if (arg >= HOLD_TIMEOUT)
+ set_state(low_mode_state, 0);
+ return EVENT_HANDLED;
+ }
+ // hold, release quickly: go to low mode
+ else if (event == EV_click1_hold_release) {
+ set_state(low_mode_state, 0);
+ return EVENT_HANDLED;
+ }
+ /* TODO: implement
+ // click-release-hold: discrete ramp through all levels
+ else if (event == EV_click2_hold) {
+ set_state(steady_state, MAX_LEVEL);
+ return EVENT_HANDLED;
+ }
+ */
+ return EVENT_NOT_HANDLED;
+}
+
+
+uint8_t any_mode_state(EventPtr event, uint16_t arg, uint8_t *primary, uint8_t *secondary, uint8_t *modes) {
+ // turn on LED when entering the mode
+ if (event == EV_enter_state) {
+ set_any_mode(*primary, *secondary, modes);
+ return MISCHIEF_MANAGED;
+ }
+ // 1 click: off
+ else if (event == EV_1click) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // hold: change brightness (low, med, hi, always starting at low)
+ else if (event == EV_click1_hold) {
+ uint8_t which = arg % (HOLD_TIMEOUT * 3) / HOLD_TIMEOUT;
+ switch(which) {
+ case 0:
+ set_state(low_mode_state, 0);
+ break;
+ case 1:
+ set_state(med_mode_state, 0);
+ break;
+ case 2:
+ set_state(hi_mode_state, 0);
+ break;
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks: toggle primary/secondary level
+ else if (event == EV_2clicks) {
+ *primary ^= 1;
+ set_any_mode(*primary, *secondary, modes);
+ save_config();
+ return MISCHIEF_MANAGED;
+ }
+ // click-release-hold: change secondary level
+ else if (event == EV_click2_hold) {
+ if (arg % HOLD_TIMEOUT == 0) {
+ *secondary = (*secondary + 1) & 3;
+ if (! *secondary) *secondary = 1;
+ *primary = 0;
+ set_any_mode(*primary, *secondary, modes);
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // click, hold, release: save secondary level
+ else if (event == EV_click2_hold_release) {
+ save_config();
+ }
+ #ifdef USE_THERMAL_REGULATION
+ // TODO: test this on a real light
+ // overheating: drop by an amount proportional to how far we are above the ceiling
+ else if (event == EV_temperature_high) {
+ if (actual_level > MAX_LEVEL/4) {
+ uint8_t stepdown = actual_level - arg;
+ if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4;
+ set_level(stepdown);
+ }
+ return EVENT_HANDLED;
+ }
+ // underheating: increase slowly if we're lower than the target
+ // (proportional to how low we are)
+ else if (event == EV_temperature_low) {
+ if (actual_level < target_level) {
+ uint8_t stepup = actual_level + (arg>>1);
+ if (stepup > target_level) stepup = target_level;
+ set_level(stepup);
+ }
+ return EVENT_HANDLED;
+ }
+ #endif
+ return EVENT_NOT_HANDLED;
+}
+
+uint8_t low_mode_state(EventPtr event, uint16_t arg) {
+ return any_mode_state(event, arg, &L1, &L2, low_modes);
+}
+
+uint8_t med_mode_state(EventPtr event, uint16_t arg) {
+ return any_mode_state(event, arg, &M1, &M2, med_modes);
+}
+
+uint8_t hi_mode_state(EventPtr event, uint16_t arg) {
+ return any_mode_state(event, arg, &H1, &H2, hi_modes);
+}
+
+
+#ifdef USE_BATTCHECK
+uint8_t battcheck_state(EventPtr event, uint16_t arg) {
+ return EVENT_NOT_HANDLED;
+}
+#endif
+
+
+uint8_t strobe_beacon_state(EventPtr event, uint16_t arg) {
+ // 1 click: off
+ if (event == EV_1click) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 1 click (initially): cancel current blink
+ if (event == EV_click1_release) {
+ interrupt_nice_delays();
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks: rotate through blinky modes
+ else if (event == EV_2clicks) {
+ strobe_beacon_mode = (strobe_beacon_mode + 1) & 3;
+ save_config();
+ interrupt_nice_delays();
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+
+void low_voltage() {
+ if (current_state == hi_mode_state) {
+ set_state(med_mode_state, 0);
+ }
+ else if (current_state == med_mode_state) {
+ set_state(low_mode_state, 0);
+ }
+ else if (current_state == low_mode_state) {
+ set_state(off_state, 0);
+ }
+ // "step down" from blinkies to low
+ else if (current_state == strobe_beacon_state) {
+ set_state(low_mode_state, 0);
+ }
+}
+
+void strobe(uint8_t level, uint16_t ontime, uint16_t offtime) {
+ set_level(level);
+ if (! nice_delay_ms(ontime)) return;
+ set_level(0);
+ nice_delay_ms(offtime);
+}
+
+void load_config() {
+ if (load_eeprom()) {
+ H1 = !(!(eeprom[0] & 0b00000100));
+ M1 = !(!(eeprom[0] & 0b00000010));
+ L1 = !(!(eeprom[0] & 0b00000001));
+ H2 = eeprom[1];
+ M2 = eeprom[2];
+ L2 = eeprom[3];
+ strobe_beacon_mode = eeprom[4];
+ }
+}
+
+void save_config() {
+ eeprom[0] = (H1<<2) | (M1<<1) | (L1);
+ eeprom[1] = H2;
+ eeprom[2] = M2;
+ eeprom[3] = L2;
+ eeprom[4] = strobe_beacon_mode;
+
+ save_eeprom();
+}
+
+void setup() {
+ set_level(RAMP_SIZE/8);
+ delay_4ms(3);
+ set_level(0);
+
+ load_config();
+
+ push_state(off_state, 0);
+}
+
+void loop() {
+ // deferred "off" so we won't suspend in a weird state
+ // (like... during the middle of a strobe pulse)
+ if (go_to_standby) {
+ go_to_standby = 0;
+ set_level(0);
+ standby_mode();
+ }
+
+ if (current_state == strobe_beacon_state) {
+ switch(strobe_beacon_mode) {
+ // 0.2 Hz beacon at L1
+ case 0:
+ strobe(low_modes[0], 500, 4500);
+ break;
+ // 0.2 Hz beacon at H1
+ case 1:
+ strobe(hi_modes[0], 500, 4500);
+ break;
+ // 4 Hz tactical strobe at H1
+ case 2:
+ strobe(hi_modes[0], 83, 167);
+ break;
+ // 19 Hz tactical strobe at H1
+ case 3:
+ strobe(hi_modes[0], 17, 35);
+ break;
+ }
+ }
+
+ #ifdef USE_BATTCHECK
+ else if (current_state == battcheck_state) {
+ nice_delay_ms(500); // wait a moment to measure voltage
+ battcheck();
+ set_state(off_state, 0);
+ }
+ #endif
+}
+
+
diff --git a/spaghetti-monster/momentary.c b/spaghetti-monster/momentary.c
deleted file mode 100644
index 6b049f4..0000000
--- a/spaghetti-monster/momentary.c
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Momentary: Very simple example UI for SpaghettiMonster.
- * Is intended to be the simplest possible FSM e-switch UI.
- * The light is on while the button is held; off otherwise.
- *
- * 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 .
- */
-
-#define FSM_EMISAR_D4_DRIVER
-#define USE_LVP
-#define USE_DEBUG_BLINK
-#define USE_DELAY_4MS
-#include "spaghetti-monster.h"
-
-volatile uint8_t brightness;
-volatile uint8_t on_now;
-
-void light_on() {
- on_now = 1;
- PWM1_LVL = brightness;
- PWM2_LVL = 0;
-}
-
-void light_off() {
- on_now = 0;
- PWM1_LVL = 0;
- PWM2_LVL = 0;
-}
-
-uint8_t momentary_state(EventPtr event, uint16_t arg) {
-
- if (event == EV_click1_press) {
- brightness = 255;
- light_on();
- empty_event_sequence(); // don't attempt to parse multiple clicks
- return 0;
- }
-
- else if (event == EV_release) {
- light_off();
- empty_event_sequence(); // don't attempt to parse multiple clicks
- standby_mode(); // sleep while light is off
- return 0;
- }
-
- return 1; // event not handled
-}
-
-// LVP / low-voltage protection
-void low_voltage() {
- if (brightness > 0) {
- debug_blink(3);
- brightness >>= 1;
- if (on_now) light_on();
- } else {
- debug_blink(8);
- light_off();
- standby_mode();
- }
-}
-
-void setup() {
- debug_blink(2);
- push_state(momentary_state, 0);
-}
-
-void loop() { }
-
diff --git a/spaghetti-monster/momentary/momentary.c b/spaghetti-monster/momentary/momentary.c
new file mode 100644
index 0000000..6b049f4
--- /dev/null
+++ b/spaghetti-monster/momentary/momentary.c
@@ -0,0 +1,81 @@
+/*
+ * Momentary: Very simple example UI for SpaghettiMonster.
+ * Is intended to be the simplest possible FSM e-switch UI.
+ * The light is on while the button is held; off otherwise.
+ *
+ * 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 .
+ */
+
+#define FSM_EMISAR_D4_DRIVER
+#define USE_LVP
+#define USE_DEBUG_BLINK
+#define USE_DELAY_4MS
+#include "spaghetti-monster.h"
+
+volatile uint8_t brightness;
+volatile uint8_t on_now;
+
+void light_on() {
+ on_now = 1;
+ PWM1_LVL = brightness;
+ PWM2_LVL = 0;
+}
+
+void light_off() {
+ on_now = 0;
+ PWM1_LVL = 0;
+ PWM2_LVL = 0;
+}
+
+uint8_t momentary_state(EventPtr event, uint16_t arg) {
+
+ if (event == EV_click1_press) {
+ brightness = 255;
+ light_on();
+ empty_event_sequence(); // don't attempt to parse multiple clicks
+ return 0;
+ }
+
+ else if (event == EV_release) {
+ light_off();
+ empty_event_sequence(); // don't attempt to parse multiple clicks
+ standby_mode(); // sleep while light is off
+ return 0;
+ }
+
+ return 1; // event not handled
+}
+
+// LVP / low-voltage protection
+void low_voltage() {
+ if (brightness > 0) {
+ debug_blink(3);
+ brightness >>= 1;
+ if (on_now) light_on();
+ } else {
+ debug_blink(8);
+ light_off();
+ standby_mode();
+ }
+}
+
+void setup() {
+ debug_blink(2);
+ push_state(momentary_state, 0);
+}
+
+void loop() { }
+
diff --git a/spaghetti-monster/ramping-ui.c b/spaghetti-monster/ramping-ui.c
deleted file mode 100644
index 527a824..0000000
--- a/spaghetti-monster/ramping-ui.c
+++ /dev/null
@@ -1,361 +0,0 @@
-/*
- * Ramping-UI: Ramping UI for SpaghettiMonster.
- *
- * 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 .
- */
-
-#define FSM_EMISAR_D4_DRIVER
-#define USE_LVP
-#define USE_THERMAL_REGULATION
-#define DEFAULT_THERM_CEIL 32
-#define USE_DELAY_MS
-#define USE_DELAY_4MS
-#define USE_DELAY_ZERO
-#define USE_RAMPING
-#define USE_BATTCHECK
-#define BATTCHECK_VpT
-#define RAMP_LENGTH 150
-#include "spaghetti-monster.h"
-
-// FSM states
-uint8_t off_state(EventPtr event, uint16_t arg);
-uint8_t steady_state(EventPtr event, uint16_t arg);
-uint8_t strobe_state(EventPtr event, uint16_t arg);
-#ifdef USE_BATTCHECK
-uint8_t battcheck_state(EventPtr event, uint16_t arg);
-uint8_t tempcheck_state(EventPtr event, uint16_t arg);
-#endif
-
-// brightness control
-uint8_t memorized_level = MAX_1x7135;
-// smooth vs discrete ramping
-uint8_t ramp_step_size = 1;
-
-#ifdef USE_THERMAL_REGULATION
-// brightness before thermal step-down
-uint8_t target_level = 0;
-#endif
-
-// strobe timing
-volatile uint8_t strobe_delay = 67;
-volatile uint8_t strobe_type = 0; // 0 == party strobe, 1 == tactical strobe
-
-
-uint8_t off_state(EventPtr event, uint16_t arg) {
- // turn emitter off when entering state
- if (event == EV_enter_state) {
- set_level(0);
- // sleep while off (lower power use)
- //empty_event_sequence(); // just in case (but shouldn't be needed)
- standby_mode();
- return MISCHIEF_MANAGED;
- }
- // hold (initially): go to lowest level, but allow abort for regular click
- else if (event == EV_click1_press) {
- set_level(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(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;
- }
- // 2 clicks: highest mode
- else if (event == EV_2clicks) {
- set_state(steady_state, MAX_LEVEL);
- return MISCHIEF_MANAGED;
- }
- // 3 clicks: strobe mode
- else if (event == EV_3clicks) {
- set_state(strobe_state, 0);
- return MISCHIEF_MANAGED;
- }
- #ifdef USE_BATTCHECK
- // 4 clicks: battcheck mode
- else if (event == EV_4clicks) {
- set_state(battcheck_state, 0);
- return MISCHIEF_MANAGED;
- }
- #endif
- // 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;
- }
- // click, hold: go to highest level (for ramping down)
- else if (event == EV_click2_hold) {
- set_state(steady_state, MAX_LEVEL);
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
-
-
-uint8_t steady_state(EventPtr event, uint16_t arg) {
- // turn LED on when we first enter the mode
- if (event == EV_enter_state) {
- // remember this level, unless it's moon or turbo
- if ((arg > 1) && (arg < MAX_LEVEL))
- memorized_level = arg;
- // use the requested level even if not memorized
- #ifdef USE_THERMAL_REGULATION
- target_level = arg;
- #endif
- set_level(arg);
- 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) {
- memorized_level = actual_level; // in case we're on moon
- #ifdef USE_THERMAL_REGULATION
- target_level = MAX_LEVEL;
- #endif
- set_level(MAX_LEVEL);
- }
- else {
- #ifdef USE_THERMAL_REGULATION
- target_level = memorized_level;
- #endif
- set_level(memorized_level);
- }
- return MISCHIEF_MANAGED;
- }
- // 3 clicks: go to strobe modes
- else if (event == EV_3clicks) {
- set_state(strobe_state, 0);
- return MISCHIEF_MANAGED;
- }
- // 4 clicks: toggle smooth vs discrete ramping
- else if (event == EV_4clicks) {
- if (ramp_step_size == 1) ramp_step_size = MAX_LEVEL/6;
- else ramp_step_size = 1;
- set_level(0);
- delay_4ms(20/4);
- set_level(memorized_level);
- return MISCHIEF_MANAGED;
- }
- // hold: change brightness (brighter)
- else if (event == EV_click1_hold) {
- // ramp slower in discrete mode
- if (arg % ramp_step_size != 0) {
- return MISCHIEF_MANAGED;
- }
- // FIXME: make it ramp down instead, if already at max
- if (actual_level + ramp_step_size < MAX_LEVEL)
- memorized_level = actual_level + ramp_step_size;
- else memorized_level = MAX_LEVEL;
- #ifdef USE_THERMAL_REGULATION
- target_level = memorized_level;
- #endif
- // only blink once for each threshold
- if ((memorized_level != actual_level)
- && ((memorized_level == MAX_1x7135)
- || (memorized_level == MAX_LEVEL))) {
- set_level(0);
- delay_4ms(8/4);
- }
- set_level(memorized_level);
- return MISCHIEF_MANAGED;
- }
- // click, hold: change brightness (dimmer)
- else if (event == EV_click2_hold) {
- // ramp slower in discrete mode
- if (arg % ramp_step_size != 0) {
- return MISCHIEF_MANAGED;
- }
- // FIXME: make it ramp up instead, if already at min
- if (actual_level > ramp_step_size)
- memorized_level = (actual_level-ramp_step_size);
- else
- memorized_level = 1;
- #ifdef USE_THERMAL_REGULATION
- target_level = memorized_level;
- #endif
- // only blink once for each threshold
- if ((memorized_level != actual_level)
- && ((memorized_level == MAX_1x7135)
- || (memorized_level == 1))) {
- set_level(0);
- delay_4ms(8/4);
- }
- set_level(memorized_level);
- return MISCHIEF_MANAGED;
- }
- #ifdef USE_THERMAL_REGULATION
- // TODO: test this on a real light
- // overheating: drop by an amount proportional to how far we are above the ceiling
- else if (event == EV_temperature_high) {
- if (actual_level > MAX_LEVEL/4) {
- uint8_t stepdown = actual_level - arg;
- if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4;
- set_level(stepdown);
- }
- 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 (actual_level < target_level) {
- uint8_t stepup = actual_level + (arg>>1);
- if (stepup > target_level) stepup = target_level;
- set_level(stepup);
- }
- return MISCHIEF_MANAGED;
- }
- #endif
- return EVENT_NOT_HANDLED;
-}
-
-
-uint8_t strobe_state(EventPtr event, uint16_t arg) {
- if (event == EV_enter_state) {
- return MISCHIEF_MANAGED;
- }
- // 1 click: off
- else if (event == EV_1click) {
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
- // 2 clicks: toggle party strobe vs tactical strobe
- else if (event == EV_2clicks) {
- strobe_type ^= 1;
- return MISCHIEF_MANAGED;
- }
- // 3 clicks: go back to regular modes
- else if (event == EV_3clicks) {
- set_state(steady_state, memorized_level);
- return MISCHIEF_MANAGED;
- }
- // hold: change speed (go faster)
- else if (event == EV_click1_hold) {
- if ((arg & 1) == 0) {
- if (strobe_delay > 8) strobe_delay --;
- }
- return MISCHIEF_MANAGED;
- }
- // click, hold: change speed (go slower)
- else if (event == EV_click2_hold) {
- if ((arg & 1) == 0) {
- if (strobe_delay < 255) strobe_delay ++;
- }
- return MISCHIEF_MANAGED;
- }
- 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;
-}
-
-uint8_t tempcheck_state(EventPtr event, uint16_t arg) {
- // 1 click: off
- if (event == EV_1click) {
- set_state(off_state, 0);
- return MISCHIEF_MANAGED;
- }
- return EVENT_NOT_HANDLED;
-}
-#endif
-
-
-void low_voltage() {
- // "step down" from strobe to something low
- if (current_state == strobe_state) {
- set_state(steady_state, RAMP_SIZE/6);
- }
- // in normal mode, step down by half or turn off
- else if (current_state == steady_state) {
- if (actual_level > 1) {
- set_level(actual_level >> 1);
- }
- else {
- set_state(off_state, 0);
- }
- }
- // all other modes, just turn off when voltage is low
- else {
- set_state(off_state, 0);
- }
-}
-
-
-void setup() {
- set_level(RAMP_SIZE/8);
- delay_4ms(3);
- set_level(0);
-
- push_state(off_state, 0);
-}
-
-
-void loop() {
- if (current_state == strobe_state) {
- set_level(MAX_LEVEL);
- if (strobe_type == 0) { // party strobe
- if (strobe_delay < 30) delay_zero();
- else delay_ms(1);
- } else { //tactical strobe
- nice_delay_ms(strobe_delay >> 1);
- }
- set_level(0);
- nice_delay_ms(strobe_delay);
- }
- #ifdef USE_BATTCHECK
- else if (current_state == battcheck_state) {
- battcheck();
- }
- else if (current_state == tempcheck_state) {
- blink_num(projected_temperature>>2);
- nice_delay_ms(1000);
- }
- #endif
-}
diff --git a/spaghetti-monster/ramping-ui/ramping-ui.c b/spaghetti-monster/ramping-ui/ramping-ui.c
new file mode 100644
index 0000000..527a824
--- /dev/null
+++ b/spaghetti-monster/ramping-ui/ramping-ui.c
@@ -0,0 +1,361 @@
+/*
+ * Ramping-UI: Ramping UI for SpaghettiMonster.
+ *
+ * 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 .
+ */
+
+#define FSM_EMISAR_D4_DRIVER
+#define USE_LVP
+#define USE_THERMAL_REGULATION
+#define DEFAULT_THERM_CEIL 32
+#define USE_DELAY_MS
+#define USE_DELAY_4MS
+#define USE_DELAY_ZERO
+#define USE_RAMPING
+#define USE_BATTCHECK
+#define BATTCHECK_VpT
+#define RAMP_LENGTH 150
+#include "spaghetti-monster.h"
+
+// FSM states
+uint8_t off_state(EventPtr event, uint16_t arg);
+uint8_t steady_state(EventPtr event, uint16_t arg);
+uint8_t strobe_state(EventPtr event, uint16_t arg);
+#ifdef USE_BATTCHECK
+uint8_t battcheck_state(EventPtr event, uint16_t arg);
+uint8_t tempcheck_state(EventPtr event, uint16_t arg);
+#endif
+
+// brightness control
+uint8_t memorized_level = MAX_1x7135;
+// smooth vs discrete ramping
+uint8_t ramp_step_size = 1;
+
+#ifdef USE_THERMAL_REGULATION
+// brightness before thermal step-down
+uint8_t target_level = 0;
+#endif
+
+// strobe timing
+volatile uint8_t strobe_delay = 67;
+volatile uint8_t strobe_type = 0; // 0 == party strobe, 1 == tactical strobe
+
+
+uint8_t off_state(EventPtr event, uint16_t arg) {
+ // turn emitter off when entering state
+ if (event == EV_enter_state) {
+ set_level(0);
+ // sleep while off (lower power use)
+ //empty_event_sequence(); // just in case (but shouldn't be needed)
+ standby_mode();
+ return MISCHIEF_MANAGED;
+ }
+ // hold (initially): go to lowest level, but allow abort for regular click
+ else if (event == EV_click1_press) {
+ set_level(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(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;
+ }
+ // 2 clicks: highest mode
+ else if (event == EV_2clicks) {
+ set_state(steady_state, MAX_LEVEL);
+ return MISCHIEF_MANAGED;
+ }
+ // 3 clicks: strobe mode
+ else if (event == EV_3clicks) {
+ set_state(strobe_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #ifdef USE_BATTCHECK
+ // 4 clicks: battcheck mode
+ else if (event == EV_4clicks) {
+ set_state(battcheck_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ // 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;
+ }
+ // click, hold: go to highest level (for ramping down)
+ else if (event == EV_click2_hold) {
+ set_state(steady_state, MAX_LEVEL);
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+
+
+uint8_t steady_state(EventPtr event, uint16_t arg) {
+ // turn LED on when we first enter the mode
+ if (event == EV_enter_state) {
+ // remember this level, unless it's moon or turbo
+ if ((arg > 1) && (arg < MAX_LEVEL))
+ memorized_level = arg;
+ // use the requested level even if not memorized
+ #ifdef USE_THERMAL_REGULATION
+ target_level = arg;
+ #endif
+ set_level(arg);
+ 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) {
+ memorized_level = actual_level; // in case we're on moon
+ #ifdef USE_THERMAL_REGULATION
+ target_level = MAX_LEVEL;
+ #endif
+ set_level(MAX_LEVEL);
+ }
+ else {
+ #ifdef USE_THERMAL_REGULATION
+ target_level = memorized_level;
+ #endif
+ set_level(memorized_level);
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // 3 clicks: go to strobe modes
+ else if (event == EV_3clicks) {
+ set_state(strobe_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 4 clicks: toggle smooth vs discrete ramping
+ else if (event == EV_4clicks) {
+ if (ramp_step_size == 1) ramp_step_size = MAX_LEVEL/6;
+ else ramp_step_size = 1;
+ set_level(0);
+ delay_4ms(20/4);
+ set_level(memorized_level);
+ return MISCHIEF_MANAGED;
+ }
+ // hold: change brightness (brighter)
+ else if (event == EV_click1_hold) {
+ // ramp slower in discrete mode
+ if (arg % ramp_step_size != 0) {
+ return MISCHIEF_MANAGED;
+ }
+ // FIXME: make it ramp down instead, if already at max
+ if (actual_level + ramp_step_size < MAX_LEVEL)
+ memorized_level = actual_level + ramp_step_size;
+ else memorized_level = MAX_LEVEL;
+ #ifdef USE_THERMAL_REGULATION
+ target_level = memorized_level;
+ #endif
+ // only blink once for each threshold
+ if ((memorized_level != actual_level)
+ && ((memorized_level == MAX_1x7135)
+ || (memorized_level == MAX_LEVEL))) {
+ set_level(0);
+ delay_4ms(8/4);
+ }
+ set_level(memorized_level);
+ return MISCHIEF_MANAGED;
+ }
+ // click, hold: change brightness (dimmer)
+ else if (event == EV_click2_hold) {
+ // ramp slower in discrete mode
+ if (arg % ramp_step_size != 0) {
+ return MISCHIEF_MANAGED;
+ }
+ // FIXME: make it ramp up instead, if already at min
+ if (actual_level > ramp_step_size)
+ memorized_level = (actual_level-ramp_step_size);
+ else
+ memorized_level = 1;
+ #ifdef USE_THERMAL_REGULATION
+ target_level = memorized_level;
+ #endif
+ // only blink once for each threshold
+ if ((memorized_level != actual_level)
+ && ((memorized_level == MAX_1x7135)
+ || (memorized_level == 1))) {
+ set_level(0);
+ delay_4ms(8/4);
+ }
+ set_level(memorized_level);
+ return MISCHIEF_MANAGED;
+ }
+ #ifdef USE_THERMAL_REGULATION
+ // TODO: test this on a real light
+ // overheating: drop by an amount proportional to how far we are above the ceiling
+ else if (event == EV_temperature_high) {
+ if (actual_level > MAX_LEVEL/4) {
+ uint8_t stepdown = actual_level - arg;
+ if (stepdown < MAX_LEVEL/4) stepdown = MAX_LEVEL/4;
+ set_level(stepdown);
+ }
+ 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 (actual_level < target_level) {
+ uint8_t stepup = actual_level + (arg>>1);
+ if (stepup > target_level) stepup = target_level;
+ set_level(stepup);
+ }
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+ return EVENT_NOT_HANDLED;
+}
+
+
+uint8_t strobe_state(EventPtr event, uint16_t arg) {
+ if (event == EV_enter_state) {
+ return MISCHIEF_MANAGED;
+ }
+ // 1 click: off
+ else if (event == EV_1click) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ // 2 clicks: toggle party strobe vs tactical strobe
+ else if (event == EV_2clicks) {
+ strobe_type ^= 1;
+ return MISCHIEF_MANAGED;
+ }
+ // 3 clicks: go back to regular modes
+ else if (event == EV_3clicks) {
+ set_state(steady_state, memorized_level);
+ return MISCHIEF_MANAGED;
+ }
+ // hold: change speed (go faster)
+ else if (event == EV_click1_hold) {
+ if ((arg & 1) == 0) {
+ if (strobe_delay > 8) strobe_delay --;
+ }
+ return MISCHIEF_MANAGED;
+ }
+ // click, hold: change speed (go slower)
+ else if (event == EV_click2_hold) {
+ if ((arg & 1) == 0) {
+ if (strobe_delay < 255) strobe_delay ++;
+ }
+ return MISCHIEF_MANAGED;
+ }
+ 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;
+}
+
+uint8_t tempcheck_state(EventPtr event, uint16_t arg) {
+ // 1 click: off
+ if (event == EV_1click) {
+ set_state(off_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ return EVENT_NOT_HANDLED;
+}
+#endif
+
+
+void low_voltage() {
+ // "step down" from strobe to something low
+ if (current_state == strobe_state) {
+ set_state(steady_state, RAMP_SIZE/6);
+ }
+ // in normal mode, step down by half or turn off
+ else if (current_state == steady_state) {
+ if (actual_level > 1) {
+ set_level(actual_level >> 1);
+ }
+ else {
+ set_state(off_state, 0);
+ }
+ }
+ // all other modes, just turn off when voltage is low
+ else {
+ set_state(off_state, 0);
+ }
+}
+
+
+void setup() {
+ set_level(RAMP_SIZE/8);
+ delay_4ms(3);
+ set_level(0);
+
+ push_state(off_state, 0);
+}
+
+
+void loop() {
+ if (current_state == strobe_state) {
+ set_level(MAX_LEVEL);
+ if (strobe_type == 0) { // party strobe
+ if (strobe_delay < 30) delay_zero();
+ else delay_ms(1);
+ } else { //tactical strobe
+ nice_delay_ms(strobe_delay >> 1);
+ }
+ set_level(0);
+ nice_delay_ms(strobe_delay);
+ }
+ #ifdef USE_BATTCHECK
+ else if (current_state == battcheck_state) {
+ battcheck();
+ }
+ else if (current_state == tempcheck_state) {
+ blink_num(projected_temperature>>2);
+ nice_delay_ms(1000);
+ }
+ #endif
+}
--
cgit v1.2.3