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. --- bin/build-85.sh | 2 +- 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 +++++++++++ 15 files changed, 2315 insertions(+), 2314 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 diff --git a/bin/build-85.sh b/bin/build-85.sh index 14a3da4..8b2937d 100755 --- a/bin/build-85.sh +++ b/bin/build-85.sh @@ -8,7 +8,7 @@ export ATTINY=85 export MCU=attiny$ATTINY export CC=avr-gcc export OBJCOPY=avr-objcopy -export CFLAGS="-Wall -g -Os -mmcu=$MCU -c -std=gnu99 -DATTINY=$ATTINY -I.." +export CFLAGS="-Wall -g -Os -mmcu=$MCU -c -std=gnu99 -DATTINY=$ATTINY -I.. -I../.. -I../../.." export OFLAGS="-Wall -g -Os -mmcu=$MCU" export LDFLAGS= export OBJCOPYFLAGS='--set-section-flags=.eeprom=alloc,load --change-section-lma .eeprom=0 --no-change-warnings -O ihex' 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