/*
* Fireflies UI: A custom UI for Fireflies-brand flashlights.
* (based on Anduril by ToyKeeper)
*
* Copyright (C) 2019 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 .
*/
/********* User-configurable options *********/
// UI config file name (set it here or define it at the gcc command line)
//#define CONFIGFILE cfg-ff-pl47.h
#define USE_LVP // FIXME: won't build when this option is turned off
// parameters for this defined below or per-driver
#define USE_THERMAL_REGULATION
#define DEFAULT_THERM_CEIL 45 // try not to get hotter than this
// short blip when crossing from "click" to "hold" from off
// (helps the user hit moon mode exactly, instead of holding too long
// or too short)
#define MOON_TIMING_HINT
// short blips while ramping
#define BLINK_AT_CHANNEL_BOUNDARIES
//#define BLINK_AT_RAMP_FLOOR
#define BLINK_AT_RAMP_CEILING
//#define BLINK_AT_STEPS // whenever a discrete ramp mode is passed in smooth mode
// ramp down via regular button hold if a ramp-up ended <1s ago
// ("hold, release, hold" ramps down instead of up)
#define USE_REVERSING
// battery readout style (pick one)
#define BATTCHECK_VpT
//#define BATTCHECK_8bars // FIXME: breaks build
//#define BATTCHECK_4bars // FIXME: breaks build
// enable/disable various strobe modes
#define USE_BIKE_FLASHER_MODE
#define USE_PARTY_STROBE_MODE
#define USE_TACTICAL_STROBE_MODE
#define USE_LIGHTNING_MODE
#define USE_CANDLE_MODE
// enable sunset (goodnight) mode
#define USE_GOODNIGHT_MODE
#define GOODNIGHT_TIME 60 // minutes (approximately)
#define GOODNIGHT_LEVEL 24 // ~11 lm
// enable beacon mode
#define USE_BEACON_MODE
//Muggle mode for easy UI
#define USE_MUGGLE_MODE
// make the ramps configurable by the user
#define USE_RAMP_CONFIG
// boring strobes nobody really likes, but sometimes flashlight companies want
// (these replace the fun strobe group,
// so don't enable them at the same time as any of the above strobes)
//#define USE_POLICE_STROBE_MODE
//#define USE_SOS_MODE
// dual-switch support (second switch is a tail clicky)
//#define START_AT_MEMORIZED_LEVEL
/***** specific settings for known driver types *****/
#include "tk.h"
#include incfile(CONFIGFILE)
///// Fireflies-specific configuration
// disable ramp config
#ifdef USE_RAMP_CONFIG
#undef USE_RAMP_CONFIG
#endif
// no muggle mode
#ifdef USE_MUGGLE_MODE
#undef USE_MUGGLE_MODE
#endif
// turn off strobe mode entirely; we're replacing it
#ifdef USE_BIKE_FLASHER_MODE
#undef USE_BIKE_FLASHER_MODE
#endif
#ifdef USE_PARTY_STROBE_MODE
#undef USE_PARTY_STROBE_MODE
#endif
#ifdef USE_TACTICAL_STROBE_MODE
#undef USE_TACTICAL_STROBE_MODE
#endif
#ifdef USE_LIGHTNING_MODE
#undef USE_LIGHTNING_MODE
#endif
#ifdef USE_CANDLE_MODE
#undef USE_CANDLE_MODE
#endif
// remove other blinkies too
#ifdef USE_GOODNIGHT_MODE
#undef USE_GOODNIGHT_MODE
#endif
#ifdef USE_BEACON_MODE
#undef USE_BEACON_MODE
#endif
// use these strobes instead
#define USE_POLICE_STROBE_MODE
#define USE_SOS_MODE
// thermal config mode on 10 clicks from off
#define USE_TENCLICK_THERMAL_CONFIG
///// end Fireflies-specific configuration
// thermal properties, if not defined per-driver
#ifndef MIN_THERM_STEPDOWN
#define MIN_THERM_STEPDOWN MAX_1x7135 // lowest value it'll step down to
#endif
#ifndef THERM_FASTER_LEVEL
#ifdef MAX_Nx7135
#define THERM_FASTER_LEVEL MAX_Nx7135 // throttle back faster when high
#else
#define THERM_FASTER_LEVEL (RAMP_SIZE*4/5) // throttle back faster when high
#endif
#endif
#ifdef USE_THERMAL_REGULATION
#define USE_SET_LEVEL_GRADUALLY // isn't used except for thermal adjustments
#endif
/********* Configure SpaghettiMonster *********/
#define USE_DELAY_ZERO
#define USE_RAMPING
#ifndef RAMP_LENGTH
#define RAMP_LENGTH 150 // default, if not overridden in a driver cfg file
#endif
#define MAX_BIKING_LEVEL 120 // should be 127 or less
#define USE_BATTCHECK
#if defined(USE_MUGGLE_MODE)
#ifndef MUGGLE_FLOOR
#define MUGGLE_FLOOR 22
#endif
#ifndef MUGGLE_CEILING
#define MUGGLE_CEILING (MAX_1x7135+20)
#endif
#endif
#define USE_IDLE_MODE // reduce power use while awake and no tasks are pending
#define USE_DYNAMIC_UNDERCLOCKING // cut clock speed at very low modes for better efficiency
// full FET strobe can be a bit much... use max regulated level instead,
// if there's a bright enough regulated level
#ifdef MAX_Nx7135
#define STROBE_BRIGHTNESS MAX_Nx7135
#else
#define STROBE_BRIGHTNESS MAX_LEVEL
#endif
#if defined(USE_CANDLE_MODE) || defined(USE_BIKE_FLASHER_MODE) || defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE) || defined(USE_LIGHTNING_MODE)
#define USE_STROBE_STATE
#endif
#if defined(USE_POLICE_STROBE_MODE) || defined(USE_SOS_MODE)
#define USE_BORING_STROBE_STATE
#endif
// auto-detect how many eeprom bytes
#define USE_EEPROM
typedef enum {
ramp_style_e,
ramp_smooth_floor_e,
ramp_smooth_ceil_e,
ramp_discrete_floor_e,
ramp_discrete_ceil_e,
ramp_discrete_steps_e,
#ifdef USE_STROBE_STATE
strobe_type_e,
#endif
#if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
strobe_delays_0_e,
strobe_delays_1_e,
#endif
#ifdef USE_BIKE_FLASHER_MODE
bike_flasher_brightness_e,
#endif
#ifdef USE_BEACON_MODE
beacon_seconds_e,
#endif
#ifdef USE_MUGGLE_MODE
muggle_mode_active_e,
#endif
#ifdef USE_THERMAL_REGULATION
therm_ceil_e,
therm_cal_offset_e,
#endif
#ifdef USE_INDICATOR_LED
indicator_led_mode_e,
#endif
eeprom_indexes_e_END
} eeprom_indexes_e;
#define EEPROM_BYTES eeprom_indexes_e_END
#ifdef START_AT_MEMORIZED_LEVEL
#define USE_EEPROM_WL
#define EEPROM_WL_BYTES 1
#endif
// auto-configure other stuff...
#if defined(USE_LIGHTNING_MODE) || defined(USE_CANDLE_MODE)
#define USE_PSEUDO_RAND
#endif
#include "spaghetti-monster.h"
// FSM states
uint8_t off_state(Event event, uint16_t arg);
// simple numeric entry config menu
uint8_t config_state_base(Event event, uint16_t arg,
uint8_t num_config_steps,
void (*savefunc)());
#define MAX_CONFIG_VALUES 3
uint8_t config_state_values[MAX_CONFIG_VALUES];
// ramping mode and its related config mode
uint8_t steady_state(Event event, uint16_t arg);
#ifdef USE_RAMP_CONFIG
uint8_t ramp_config_state(Event event, uint16_t arg);
#endif
// party and tactical strobes
#ifdef USE_STROBE_STATE
uint8_t strobe_state(Event event, uint16_t arg);
#endif
#ifdef USE_BORING_STROBE_STATE
uint8_t boring_strobe_state(Event event, uint16_t arg);
volatile uint8_t boring_strobe_type = 0;
void sos_blink(uint8_t num, uint8_t dah);
#define NUM_BORING_STROBES 2
#endif
#ifdef USE_BATTCHECK
uint8_t battcheck_state(Event event, uint16_t arg);
#endif
#ifdef USE_THERMAL_REGULATION
uint8_t tempcheck_state(Event event, uint16_t arg);
uint8_t thermal_config_state(Event event, uint16_t arg);
#endif
#ifdef USE_GOODNIGHT_MODE
// 1-hour ramp down from low, then automatic off
uint8_t goodnight_state(Event event, uint16_t arg);
#endif
#ifdef USE_BEACON_MODE
// beacon mode and its related config mode
uint8_t beacon_state(Event event, uint16_t arg);
uint8_t beacon_config_state(Event event, uint16_t arg);
#endif
// soft lockout
#define MOON_DURING_LOCKOUT_MODE
// if enabled, 2nd lockout click goes to the other ramp's floor level
//#define LOCKOUT_MOON_FANCY
uint8_t lockout_state(Event event, uint16_t arg);
// momentary / signalling mode
uint8_t momentary_state(Event event, uint16_t arg);
#ifdef USE_MUGGLE_MODE
// muggle mode, super-simple, hard to exit
uint8_t muggle_state(Event event, uint16_t arg);
uint8_t muggle_mode_active = 0;
#endif
// general helper function for config modes
uint8_t number_entry_state(Event event, uint16_t arg);
// return value from number_entry_state()
volatile uint8_t number_entry_value;
void blink_confirm(uint8_t num);
#if defined(USE_INDICATOR_LED) && defined(TICK_DURING_STANDBY)
void indicator_blink(uint8_t arg);
#endif
// remember stuff even after battery was changed
void load_config();
void save_config();
#ifdef START_AT_MEMORIZED_LEVEL
void save_config_wl();
#endif
// default ramp options if not overridden earlier per-driver
#ifndef RAMP_SMOOTH_FLOOR
#define RAMP_SMOOTH_FLOOR 1
#endif
#ifndef RAMP_SMOOTH_CEIL
#if PWM_CHANNELS == 3
#define RAMP_SMOOTH_CEIL MAX_Nx7135
#else
#define RAMP_SMOOTH_CEIL MAX_LEVEL - 30
#endif
#endif
#ifndef RAMP_DISCRETE_FLOOR
#define RAMP_DISCRETE_FLOOR 20
#endif
#ifndef RAMP_DISCRETE_CEIL
#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
#endif
#ifndef RAMP_DISCRETE_STEPS
#define RAMP_DISCRETE_STEPS 7
#endif
// brightness control
uint8_t memorized_level = MAX_1x7135;
// smooth vs discrete ramping
volatile uint8_t ramp_style = 0; // 0 = smooth, 1 = discrete
volatile uint8_t ramp_smooth_floor = RAMP_SMOOTH_FLOOR;
volatile uint8_t ramp_smooth_ceil = RAMP_SMOOTH_CEIL;
volatile uint8_t ramp_discrete_floor = RAMP_DISCRETE_FLOOR;
volatile uint8_t ramp_discrete_ceil = RAMP_DISCRETE_CEIL;
volatile uint8_t ramp_discrete_steps = RAMP_DISCRETE_STEPS;
uint8_t ramp_discrete_step_size; // don't set this
#ifdef USE_INDICATOR_LED
// bits 2-3 control lockout mode
// bits 0-1 control "off" mode
// modes are: 0=off, 1=low, 2=high, 3=blinking (if TICK_DURING_STANDBY enabled)
#ifdef INDICATOR_LED_DEFAULT_MODE
uint8_t indicator_led_mode = INDICATOR_LED_DEFAULT_MODE;
#else
#ifdef USE_INDICATOR_LED_WHILE_RAMPING
//uint8_t indicator_led_mode = (1<<2) + 2;
uint8_t indicator_led_mode = (2<<2) + 1;
#else
uint8_t indicator_led_mode = (3<<2) + 1;
#endif
#endif
#endif
// calculate the nearest ramp level which would be valid at the moment
// (is a no-op for smooth ramp, but limits discrete ramp to only the
// correct levels for the user's config)
uint8_t nearest_level(int16_t target);
#ifdef USE_THERMAL_REGULATION
// brightness before thermal step-down
uint8_t target_level = 0;
#endif
// internal numbering for strobe modes
#ifdef USE_STROBE_STATE
typedef enum {
#ifdef USE_PARTY_STROBE_MODE
party_strobe_e,
#endif
#ifdef USE_TACTICAL_STROBE_MODE
tactical_strobe_e,
#endif
#ifdef USE_LIGHTNING_MODE
lightning_storm_e,
#endif
#ifdef USE_CANDLE_MODE
candle_mode_e,
#endif
#ifdef USE_BIKE_FLASHER_MODE
bike_flasher_e,
#endif
strobe_mode_END
} strobe_mode_te;
const int NUM_STROBES = strobe_mode_END;
// which strobe mode is active?
#ifdef USE_CANDLE_MODE
volatile strobe_mode_te strobe_type = candle_mode_e;
#else
volatile strobe_mode_te strobe_type = 0;
#endif
#endif
#if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
// party / tactical strobe timing
volatile uint8_t strobe_delays[] = { 40, 67 }; // party strobe, tactical strobe
#endif
// bike mode config options
#ifdef USE_BIKE_FLASHER_MODE
volatile uint8_t bike_flasher_brightness = MAX_1x7135;
#endif
#ifdef USE_CANDLE_MODE
uint8_t triangle_wave(uint8_t phase);
#endif
#ifdef USE_BEACON_MODE
// beacon timing
volatile uint8_t beacon_seconds = 2;
#endif
uint8_t off_state(Event event, uint16_t arg) {
// turn emitter off when entering state
if (event == EV_enter_state) {
set_level(0);
#ifdef USE_INDICATOR_LED
indicator_led(indicator_led_mode & 0x03);
#endif
// sleep while off (lower power use)
go_to_standby = 1;
return MISCHIEF_MANAGED;
}
// go back to sleep eventually if we got bumped but didn't leave "off" state
else if (event == EV_tick) {
if (arg > TICKS_PER_SECOND*2) {
go_to_standby = 1;
#ifdef USE_INDICATOR_LED
indicator_led(indicator_led_mode & 0x03);
#endif
}
return MISCHIEF_MANAGED;
}
#if defined(TICK_DURING_STANDBY) && defined(USE_INDICATOR_LED)
// blink the indicator LED, maybe
else if (event == EV_sleep_tick) {
if ((indicator_led_mode & 0b00000011) == 0b00000011) {
indicator_blink(arg);
}
return MISCHIEF_MANAGED;
}
#endif
// hold (initially): go to lowest level (floor), but allow abort for regular click
else if (event == EV_click1_press) {
set_level(nearest_level(1));
return MISCHIEF_MANAGED;
}
// hold: go to lowest level
else if (event == EV_click1_hold) {
#ifdef MOON_TIMING_HINT
if (arg == 0) {
// let the user know they can let go now to stay at moon
uint8_t temp = actual_level;
set_level(0);
delay_4ms(2);
set_level(temp);
} else
#endif
// don't start ramping immediately;
// give the user time to release at moon level
//if (arg >= HOLD_TIMEOUT) { // smaller
if (arg >= (!ramp_style) * HOLD_TIMEOUT) { // more consistent
set_state(steady_state, 1);
}
return MISCHIEF_MANAGED;
}
// hold, release quickly: go to lowest level (floor)
else if (event == EV_click1_hold_release) {
set_state(steady_state, 1);
return MISCHIEF_MANAGED;
}
// 1 click (before timeout): go to memorized level, but allow abort for double click
else if (event == EV_click1_release) {
set_level(nearest_level(memorized_level));
return MISCHIEF_MANAGED;
}
// 1 click: regular mode
else if (event == EV_1click) {
set_state(steady_state, memorized_level);
return MISCHIEF_MANAGED;
}
// click, hold: go to highest level (ceiling) (for ramping down)
else if (event == EV_click2_hold) {
set_state(steady_state, MAX_LEVEL);
return MISCHIEF_MANAGED;
}
// 2 clicks: highest mode (ceiling)
else if (event == EV_2clicks) {
set_state(steady_state, MAX_LEVEL);
return MISCHIEF_MANAGED;
}
// 3 clicks (initial press): off, to prep for later events
else if (event == EV_click3_press) {
set_level(0);
return MISCHIEF_MANAGED;
}
#ifdef USE_BATTCHECK
// 3 clicks: battcheck mode / blinky mode group 1
else if (event == EV_3clicks) {
set_state(battcheck_state, 0);
return MISCHIEF_MANAGED;
}
#endif
// click, click, long-click: strobe mode
#ifdef USE_STROBE_STATE
else if (event == EV_click3_hold) {
set_state(strobe_state, 0);
return MISCHIEF_MANAGED;
}
#elif defined(USE_BORING_STROBE_STATE)
else if (event == EV_click3_hold) {
set_state(boring_strobe_state, 0);
return MISCHIEF_MANAGED;
}
#endif
// 4 clicks: soft lockout
else if (event == EV_4clicks) {
blink_confirm(2);
set_state(lockout_state, 0);
return MISCHIEF_MANAGED;
}
// 5 clicks: momentary mode
else if (event == EV_5clicks) {
blink_confirm(1);
set_state(momentary_state, 0);
return MISCHIEF_MANAGED;
}
#ifdef USE_MUGGLE_MODE
// 6 clicks: muggle mode
else if (event == EV_6clicks) {
blink_confirm(1);
set_state(muggle_state, 0);
return MISCHIEF_MANAGED;
}
#endif
// 7 clicks: temperature check
else if (event == EV_7clicks) {
set_state(tempcheck_state, 0);
return MISCHIEF_MANAGED;
}
#ifdef USE_TENCLICK_THERMAL_CONFIG
// 10 clicks: thermal config mode
else if (event == EV_10clicks) {
push_state(thermal_config_state, 0);
return MISCHIEF_MANAGED;
}
#endif
return EVENT_NOT_HANDLED;
}
uint8_t steady_state(Event event, uint16_t arg) {
uint8_t mode_min = ramp_smooth_floor;
uint8_t mode_max = ramp_smooth_ceil;
uint8_t ramp_step_size = 1;
#ifdef USE_REVERSING
static int8_t ramp_direction = 1;
#endif
if (ramp_style) {
mode_min = ramp_discrete_floor;
mode_max = ramp_discrete_ceil;
ramp_step_size = ramp_discrete_step_size;
}
// turn LED on when we first enter the mode
if ((event == EV_enter_state) || (event == EV_reenter_state)) {
// if we just got back from config mode, go back to memorized level
if (event == EV_reenter_state) {
arg = memorized_level;
}
// remember this level, unless it's moon or turbo
if ((arg > mode_min) && (arg < mode_max))
memorized_level = arg;
// use the requested level even if not memorized
arg = nearest_level(arg);
#ifdef USE_THERMAL_REGULATION
target_level = arg;
#endif
set_level(arg);
#ifdef USE_REVERSING
ramp_direction = 1;
#endif
return MISCHIEF_MANAGED;
}
// 1 click: off
else if (event == EV_1click) {
set_state(off_state, 0);
return MISCHIEF_MANAGED;
}
// 2 clicks: go to/from highest level
else if (event == EV_2clicks) {
if (actual_level < MAX_LEVEL) {
#ifdef USE_THERMAL_REGULATION
target_level = MAX_LEVEL;
#endif
// true turbo, not the mode-specific ceiling
set_level(MAX_LEVEL);
}
else {
#ifdef USE_THERMAL_REGULATION
target_level = memorized_level;
#endif
set_level(memorized_level);
}
return MISCHIEF_MANAGED;
}
// 3 clicks: toggle smooth vs discrete ramping
else if (event == EV_3clicks) {
ramp_style = !ramp_style;
memorized_level = nearest_level(actual_level);
#ifdef USE_THERMAL_REGULATION
target_level = memorized_level;
#ifdef USE_SET_LEVEL_GRADUALLY
//set_level_gradually(lvl);
#endif
#endif
save_config();
#ifdef START_AT_MEMORIZED_LEVEL
save_config_wl();
#endif
set_level(0);
delay_4ms(20/4);
set_level(memorized_level);
return MISCHIEF_MANAGED;
}
#ifdef USE_RAMP_CONFIG
// 4 clicks: configure this ramp mode
else if (event == EV_4clicks) {
push_state(ramp_config_state, 0);
return MISCHIEF_MANAGED;
}
#endif
// hold: change brightness (brighter)
else if (event == EV_click1_hold) {
// ramp slower in discrete mode
if (ramp_style && (arg % HOLD_TIMEOUT != 0)) {
return MISCHIEF_MANAGED;
}
#ifdef USE_REVERSING
// make it ramp down instead, if already at max
if ((arg <= 1) && (actual_level >= mode_max)) {
ramp_direction = -1;
}
memorized_level = nearest_level((int16_t)actual_level \
+ (ramp_step_size * ramp_direction));
#else
memorized_level = nearest_level((int16_t)actual_level + ramp_step_size);
#endif
#ifdef USE_THERMAL_REGULATION
target_level = memorized_level;
#endif
#if defined(BLINK_AT_RAMP_CEILING) || defined(BLINK_AT_CHANNEL_BOUNDARIES)
// only blink once for each threshold
if ((memorized_level != actual_level) && (
0 // for easier syntax below
#ifdef BLINK_AT_CHANNEL_BOUNDARIES
|| (memorized_level == MAX_1x7135)
#if PWM_CHANNELS >= 3
|| (memorized_level == MAX_Nx7135)
#endif
#endif
#ifdef BLINK_AT_RAMP_CEILING
|| (memorized_level == mode_max)
#endif
#if defined(USE_REVERSING) && defined(BLINK_AT_RAMP_FLOOR)
|| (memorized_level == mode_min)
#endif
)) {
set_level(0);
delay_4ms(8/4);
}
#endif
#if defined(BLINK_AT_STEPS)
uint8_t foo = ramp_style;
ramp_style = 1;
uint8_t nearest = nearest_level((int16_t)actual_level);
ramp_style = foo;
// only blink once for each threshold
if ((memorized_level != actual_level) &&
(ramp_style == 0) &&
(memorized_level == nearest)
)
{
set_level(0);
delay_4ms(8/4);
}
#endif
set_level(memorized_level);
return MISCHIEF_MANAGED;
}
#if defined(USE_REVERSING) || defined(START_AT_MEMORIZED_LEVEL)
// reverse ramp direction on hold release
else if (event == EV_click1_hold_release) {
#ifdef USE_REVERSING
ramp_direction = -ramp_direction;
#endif
#ifdef START_AT_MEMORIZED_LEVEL
save_config_wl();
#endif
return MISCHIEF_MANAGED;
}
#endif
// click, hold: change brightness (dimmer)
else if (event == EV_click2_hold) {
#ifdef USE_REVERSING
ramp_direction = 1;
#endif
// ramp slower in discrete mode
if (ramp_style && (arg % HOLD_TIMEOUT != 0)) {
return MISCHIEF_MANAGED;
}
// TODO? make it ramp up instead, if already at min?
memorized_level = nearest_level((int16_t)actual_level - ramp_step_size);
#ifdef USE_THERMAL_REGULATION
target_level = memorized_level;
#endif
#if defined(BLINK_AT_RAMP_FLOOR) || defined(BLINK_AT_CHANNEL_BOUNDARIES)
// only blink once for each threshold
if ((memorized_level != actual_level) && (
0 // for easier syntax below
#ifdef BLINK_AT_CHANNEL_BOUNDARIES
|| (memorized_level == MAX_1x7135)
#if PWM_CHANNELS >= 3
|| (memorized_level == MAX_Nx7135)
#endif
#endif
#ifdef BLINK_AT_RAMP_FLOOR
|| (memorized_level == mode_min)
#endif
)) {
set_level(0);
delay_4ms(8/4);
}
#endif
#if defined(BLINK_AT_STEPS)
uint8_t foo = ramp_style;
ramp_style = 1;
uint8_t nearest = nearest_level((int16_t)actual_level);
ramp_style = foo;
// only blink once for each threshold
if ((memorized_level != actual_level) &&
(ramp_style == 0) &&
(memorized_level == nearest)
)
{
set_level(0);
delay_4ms(8/4);
}
#endif
set_level(memorized_level);
return MISCHIEF_MANAGED;
}
#ifdef START_AT_MEMORIZED_LEVEL
// click, release, hold, release: save new ramp level (if necessary)
else if (event == EV_click2_hold_release) {
save_config_wl();
return MISCHIEF_MANAGED;
}
#endif
#if defined(USE_SET_LEVEL_GRADUALLY) || defined(USE_REVERSING)
else if (event == EV_tick) {
#ifdef USE_REVERSING
// un-reverse after 1 second
if (arg == TICKS_PER_SECOND) ramp_direction = 1;
#endif
#ifdef USE_SET_LEVEL_GRADUALLY
// make thermal adjustment speed scale with magnitude
if ((arg & 1) && (actual_level < THERM_FASTER_LEVEL)) {
return MISCHIEF_MANAGED; // adjust slower when not a high mode
}
#ifdef THERM_HARD_TURBO_DROP
else if ((! (actual_level < THERM_FASTER_LEVEL))
&& (actual_level > gradual_target)) {
gradual_tick();
}
else {
#endif
// [int(62*4 / (x**0.8)) for x in (1,2,4,8,16,32,64,128)]
//uint8_t intervals[] = {248, 142, 81, 46, 26, 15, 8, 5};
// [int(62*4 / (x**0.9)) for x in (1,2,4,8,16,32,64,128)]
//uint8_t intervals[] = {248, 132, 71, 38, 20, 10, 5, 3};
// [int(62*4 / (x**0.95)) for x in (1,2,4,8,16,32,64,128)]
uint8_t intervals[] = {248, 128, 66, 34, 17, 9, 4, 2};
uint8_t diff;
static uint8_t ticks_since_adjust = 0;
ticks_since_adjust ++;
if (gradual_target > actual_level) diff = gradual_target - actual_level;
else {
diff = actual_level - gradual_target;
}
uint8_t magnitude = 0;
#ifndef THERM_HARD_TURBO_DROP
// if we're on a really high mode, drop faster
if (actual_level >= THERM_FASTER_LEVEL) { magnitude ++; }
#endif
while (diff) {
magnitude ++;
diff >>= 1;
}
uint8_t ticks_per_adjust = intervals[magnitude];
if (ticks_since_adjust > ticks_per_adjust)
{
gradual_tick();
ticks_since_adjust = 0;
}
//if (!(arg % ticks_per_adjust)) gradual_tick();
#ifdef THERM_HARD_TURBO_DROP
}
#endif
#endif
return MISCHIEF_MANAGED;
}
#endif
#ifdef USE_THERMAL_REGULATION
// overheating: drop by an amount proportional to how far we are above the ceiling
else if (event == EV_temperature_high) {
#if 0
uint8_t foo = actual_level;
set_level(0);
delay_4ms(2);
set_level(foo);
#endif
#ifdef THERM_HARD_TURBO_DROP
if (actual_level > THERM_FASTER_LEVEL) {
#ifdef USE_SET_LEVEL_GRADUALLY
set_level_gradually(THERM_FASTER_LEVEL);
#else
set_level(THERM_FASTER_LEVEL);
#endif
target_level = THERM_FASTER_LEVEL;
} else
#endif
if (actual_level > MIN_THERM_STEPDOWN) {
int16_t stepdown = actual_level - arg;
if (stepdown < MIN_THERM_STEPDOWN) stepdown = MIN_THERM_STEPDOWN;
else if (stepdown > MAX_LEVEL) stepdown = MAX_LEVEL;
#ifdef USE_SET_LEVEL_GRADUALLY
set_level_gradually(stepdown);
#else
set_level(stepdown);
#endif
}
return MISCHIEF_MANAGED;
}
// underheating: increase slowly if we're lower than the target
// (proportional to how low we are)
else if (event == EV_temperature_low) {
#if 0
uint8_t foo = actual_level;
set_level(0);
delay_4ms(2);
set_level(foo);
#endif
if (actual_level < target_level) {
//int16_t stepup = actual_level + (arg>>1);
int16_t stepup = actual_level + arg;
if (stepup > target_level) stepup = target_level;
else if (stepup < MIN_THERM_STEPDOWN) stepup = MIN_THERM_STEPDOWN;
#ifdef USE_SET_LEVEL_GRADUALLY
set_level_gradually(stepup);
#else
set_level(stepup);
#endif
}
return MISCHIEF_MANAGED;
}
#endif
return EVENT_NOT_HANDLED;
}
#ifdef USE_STROBE_STATE
uint8_t strobe_state(Event event, uint16_t arg) {
// 'st' reduces ROM size by avoiding access to a volatile var
// (maybe I should just make it nonvolatile?)
strobe_mode_te st = strobe_type;
#ifdef USE_CANDLE_MODE
// FIXME: make candle variance magnitude a compile-time option,
// since 20 is sometimes too much or too little,
// depending on the driver type and ramp shape
//#define MAX_CANDLE_LEVEL (RAMP_SIZE-8-6-4)
#define MAX_CANDLE_LEVEL (RAMP_SIZE/2)
static uint8_t candle_wave1 = 0;
static uint8_t candle_wave2 = 0;
static uint8_t candle_wave3 = 0;
static uint8_t candle_wave2_speed = 0;
static uint8_t candle_wave2_depth = 7;
static uint8_t candle_wave3_depth = 4;
static uint8_t candle_mode_brightness = 24;
static uint8_t candle_mode_timer = 0;
#define TICKS_PER_CANDLE_MINUTE 4096 // about 65 seconds
#define MINUTES_PER_CANDLE_HALFHOUR 27 // ish
#endif
if (event == EV_enter_state) {
#ifdef USE_CANDLE_MODE
candle_mode_timer = 0; // in case any time was left over from earlier
#endif
return MISCHIEF_MANAGED;
}
// 1 click: off
else if (event == EV_1click) {
set_state(off_state, 0);
return MISCHIEF_MANAGED;
}
// 2 clicks: rotate through strobe/flasher modes
else if (event == EV_2clicks) {
strobe_type = (st + 1) % NUM_STROBES;
#ifdef USE_CANDLE_MODE
candle_mode_timer = 0; // in case any time was left over from earlier
#endif
//interrupt_nice_delays();
save_config();
return MISCHIEF_MANAGED;
}
// hold: change speed (go faster)
// or change brightness (brighter)
else if (event == EV_click1_hold) {
if (0) {} // placeholder
// party / tactical strobe faster
#if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
#ifdef USE_TACTICAL_STROBE_MODE
else if (st <= tactical_strobe_e) {
#else
else if (st == party_strobe_e) {
#endif
if ((arg & 1) == 0) {
if (strobe_delays[st] > 8) strobe_delays[st] --;
}
}
#endif
// lightning has no adjustments
//else if (st == lightning_storm_e) {}
// candle mode brighter
#ifdef USE_CANDLE_MODE
else if (st == candle_mode_e) {
if (candle_mode_brightness < MAX_CANDLE_LEVEL)
candle_mode_brightness ++;
}
#endif
// biking mode brighter
#ifdef USE_BIKE_FLASHER_MODE
else if (st == bike_flasher_e) {
if (bike_flasher_brightness < MAX_BIKING_LEVEL)
bike_flasher_brightness ++;
set_level(bike_flasher_brightness);
}
#endif
return MISCHIEF_MANAGED;
}
// click, hold: change speed (go slower)
// or change brightness (dimmer)
else if (event == EV_click2_hold) {
if (0) {} // placeholder
// party / tactical strobe slower
#if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
#ifdef USE_TACTICAL_STROBE_MODE
else if (st <= tactical_strobe_e) {
#else
else if (st == party_strobe_e) {
#endif
if ((arg & 1) == 0) {
if (strobe_delays[st] < 255) strobe_delays[st] ++;
}
}
#endif
// lightning has no adjustments
//else if (st == lightning_storm_e) {}
// candle mode dimmer
#ifdef USE_CANDLE_MODE
else if (st == candle_mode_e) {
if (candle_mode_brightness > 1)
candle_mode_brightness --;
}
#endif
// biking mode dimmer
#ifdef USE_BIKE_FLASHER_MODE
else if (st == bike_flasher_e) {
if (bike_flasher_brightness > 2)
bike_flasher_brightness --;
set_level(bike_flasher_brightness);
}
#endif
return MISCHIEF_MANAGED;
}
// release hold: save new strobe settings
else if ((event == EV_click1_hold_release)
|| (event == EV_click2_hold_release)) {
save_config();
return MISCHIEF_MANAGED;
}
#if defined(USE_CANDLE_MODE)
// 3 clicks: add 30m to candle timer
else if (event == EV_3clicks) {
// candle mode only
if (st == candle_mode_e) {
if (candle_mode_timer < (255 - MINUTES_PER_CANDLE_HALFHOUR)) {
// add 30m to the timer
candle_mode_timer += MINUTES_PER_CANDLE_HALFHOUR;
// blink to confirm
set_level(actual_level + 32);
delay_4ms(2);
}
}
return MISCHIEF_MANAGED;
}
#endif
#if defined(USE_LIGHTNING_MODE) || defined(USE_CANDLE_MODE)
// clock tick: bump the random seed, adjust candle brightness
else if (event == EV_tick) {
pseudo_rand_seed += arg;
#ifdef USE_CANDLE_MODE
if (st == candle_mode_e) {
// self-timer dims the light during the final minute
uint8_t subtract = 0;
if (candle_mode_timer == 1) {
subtract = ((candle_mode_brightness+20)
* ((arg & (TICKS_PER_CANDLE_MINUTE-1)) >> 4))
>> 8;
}
// we passed a minute mark, decrease timer if it's running
if ((arg & (TICKS_PER_CANDLE_MINUTE-1)) == (TICKS_PER_CANDLE_MINUTE - 1)) {
if (candle_mode_timer > 0) {
candle_mode_timer --;
//set_level(0); delay_4ms(2);
// if the timer ran out, shut off
if (! candle_mode_timer) {
set_state(off_state, 0);
}
}
}
// 3-oscillator synth for a relatively organic pattern
uint8_t add;
add = ((triangle_wave(candle_wave1) * 8) >> 8)
+ ((triangle_wave(candle_wave2) * candle_wave2_depth) >> 8)
+ ((triangle_wave(candle_wave3) * candle_wave3_depth) >> 8);
int8_t brightness = candle_mode_brightness + add - subtract;
if (brightness < 0) { brightness = 0; }
set_level(brightness);
// wave1: slow random LFO
if ((arg & 1) == 0) candle_wave1 += pseudo_rand() & 1;
// wave2: medium-speed erratic LFO
candle_wave2 += candle_wave2_speed;
// wave3: erratic fast wave
candle_wave3 += pseudo_rand() % 37;
// S&H on wave2 frequency to make it more erratic
if ((pseudo_rand() & 0b00111111) == 0)
candle_wave2_speed = pseudo_rand() % 13;
// downward sawtooth on wave2 depth to simulate stabilizing
if ((candle_wave2_depth > 0) && ((pseudo_rand() & 0b00111111) == 0))
candle_wave2_depth --;
// random sawtooth retrigger
if ((pseudo_rand()) == 0) {
candle_wave2_depth = 7;
//candle_wave3_depth = 5;
candle_wave2 = 0;
}
// downward sawtooth on wave3 depth to simulate stabilizing
if ((candle_wave3_depth > 2) && ((pseudo_rand() & 0b00011111) == 0))
candle_wave3_depth --;
if ((pseudo_rand() & 0b01111111) == 0)
candle_wave3_depth = 5;
}
#endif
return MISCHIEF_MANAGED;
}
#endif
return EVENT_NOT_HANDLED;
}
#endif // ifdef USE_STROBE_STATE
#ifdef USE_BORING_STROBE_STATE
uint8_t boring_strobe_state(Event event, uint16_t arg) {
// police strobe and SOS, meh
// 'st' reduces ROM size by avoiding access to a volatile var
// (maybe I should just make it nonvolatile?)
uint8_t st = boring_strobe_type;
if (event == EV_enter_state) {
return MISCHIEF_MANAGED;
}
// 1 click: off
else if (event == EV_1click) {
// reset to police strobe for next time
boring_strobe_type = 0;
set_state(off_state, 0);
return MISCHIEF_MANAGED;
}
// 2 clicks: rotate through strobe/flasher modes
else if (event == EV_2clicks) {
boring_strobe_type = (st + 1) % NUM_BORING_STROBES;
return MISCHIEF_MANAGED;
}
return EVENT_NOT_HANDLED;
}
void sos_blink(uint8_t num, uint8_t dah) {
#define DIT_LENGTH 200
for (; num > 0; num--) {
set_level(memorized_level);
nice_delay_ms(DIT_LENGTH);
if (dah) { // dah is 3X as long as a dit
nice_delay_ms(DIT_LENGTH*2);
}
set_level(0);
// one "off" dit between blinks
nice_delay_ms(DIT_LENGTH);
}
// three "off" dits (or one "dah") between letters
nice_delay_ms(DIT_LENGTH*2);
}
#endif // ifdef USE_BORING_STROBE_STATE
#ifdef USE_BATTCHECK
uint8_t battcheck_state(Event event, uint16_t arg) {
// 1 click: off
if (event == EV_1click) {
set_state(off_state, 0);
return MISCHIEF_MANAGED;
}
#if defined(USE_GOODNIGHT_MODE) || defined(USE_BEACON_MODE)
// 2 clicks: next mode
else if (event == EV_2clicks) {
#ifdef USE_GOODNIGHT_MODE
set_state(goodnight_state, 0);
#elif defined(USE_BEACON_MODE)
set_state(beacon_state, 0);
#endif
return MISCHIEF_MANAGED;
}
#endif
return EVENT_NOT_HANDLED;
}
#endif
#ifdef USE_THERMAL_REGULATION
uint8_t tempcheck_state(Event event, uint16_t arg) {
// 1 click: off
if (event == EV_1click) {
set_state(off_state, 0);
return MISCHIEF_MANAGED;
}
#if 0 // not part of a loop in this UI
// 2 clicks: battcheck mode
else if (event == EV_2clicks) {
set_state(battcheck_state, 0);
return MISCHIEF_MANAGED;
}
#endif
// 4 clicks: thermal config mode
else if (event == EV_4clicks) {
push_state(thermal_config_state, 0);
return MISCHIEF_MANAGED;
}
return EVENT_NOT_HANDLED;
}
#endif
#ifdef USE_BEACON_MODE
uint8_t beacon_state(Event event, uint16_t arg) {
// 1 click: off
if (event == EV_1click) {
set_state(off_state, 0);
return MISCHIEF_MANAGED;
}
// TODO: use sleep ticks to measure time between pulses,
// to save power
// 2 clicks: tempcheck mode
else if (event == EV_2clicks) {
#ifdef USE_THERMAL_REGULATION
set_state(tempcheck_state, 0);
#else
set_state(battcheck_state, 0);
#endif
return MISCHIEF_MANAGED;
}
// 4 clicks: beacon config mode
else if (event == EV_4clicks) {
push_state(beacon_config_state, 0);
return MISCHIEF_MANAGED;
}
return EVENT_NOT_HANDLED;
}
#endif
#ifdef USE_GOODNIGHT_MODE
#define GOODNIGHT_TICKS_PER_STEPDOWN (GOODNIGHT_TIME*TICKS_PER_SECOND*60L/GOODNIGHT_LEVEL)
uint8_t goodnight_state(Event event, uint16_t arg) {
static uint16_t ticks_since_stepdown = 0;
// blink on start
if (event == EV_enter_state) {
ticks_since_stepdown = 0;
blink_confirm(2);
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) {
#ifdef USE_BEACON_MODE
set_state(beacon_state, 0);
#elif defined(USE_TEMPCHECK_MODE)
set_state(tempcheck_state, 0);
#endif
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;
}
#endif
uint8_t lockout_state(Event event, uint16_t arg) {
#ifdef MOON_DURING_LOCKOUT_MODE
// momentary(ish) moon mode during lockout
// button is being held
if ((event & (B_CLICK | B_PRESS)) == (B_CLICK | B_PRESS)) {
#ifdef LOCKOUT_MOON_LOWEST
// Use lowest moon configured
uint8_t lvl = ramp_smooth_floor;
if (ramp_discrete_floor < lvl) lvl = ramp_discrete_floor;
set_level(lvl);
#elif defined(LOCKOUT_MOON_FANCY)
uint8_t levels[] = { ramp_smooth_floor, ramp_discrete_floor };
if ((event & 0x0f) == 2) {
set_level(levels[ramp_style^1]);
} else {
set_level(levels[ramp_style]);
}
#else
// Use moon from current ramp
set_level(nearest_level(1));
#endif
}
// button was released
else if ((event & (B_CLICK | B_PRESS)) == (B_CLICK)) {
set_level(0);
}
#endif
// regular event handling
// conserve power while locked out
// (allow staying awake long enough to exit, but otherwise
// be persistent about going back to sleep every few seconds
// even if the user keeps pressing the button)
#ifdef USE_INDICATOR_LED
if (event == EV_enter_state) {
indicator_led(indicator_led_mode >> 2);
} else
#endif
if (event == EV_tick) {
if (arg > TICKS_PER_SECOND*2) {
go_to_standby = 1;
#ifdef USE_INDICATOR_LED
indicator_led(indicator_led_mode >> 2);
#endif
}
return MISCHIEF_MANAGED;
}
#if defined(TICK_DURING_STANDBY) && defined(USE_INDICATOR_LED)
else if (event == EV_sleep_tick) {
if ((indicator_led_mode & 0b00001100) == 0b00001100) {
indicator_blink(arg);
}
return MISCHIEF_MANAGED;
}
#endif
#ifdef USE_INDICATOR_LED
// 3 clicks: rotate through indicator LED modes (lockout mode)
else if (event == EV_3clicks) {
uint8_t mode = indicator_led_mode >> 2;
#ifdef TICK_DURING_STANDBY
mode = (mode + 1) & 3;
#else
mode = (mode + 1) % 3;
#endif
#ifdef INDICATOR_LED_SKIP_LOW
if (mode == 1) { mode ++; }
#endif
indicator_led_mode = (mode << 2) + (indicator_led_mode & 0x03);
indicator_led(mode);
save_config();
return MISCHIEF_MANAGED;
}
#if 0 // old method, deprecated in favor of "7 clicks from off"
// click, click, hold: rotate through indicator LED modes (off mode)
else if (event == EV_click3_hold) {
#ifndef USE_INDICATOR_LED_WHILE_RAMPING
// if main LED obscures aux LEDs, turn it off
set_level(0);
#endif
#ifdef TICK_DURING_STANDBY
uint8_t mode = (arg >> 5) & 3;
#else
uint8_t mode = (arg >> 5) % 3;
#endif
#ifdef INDICATOR_LED_SKIP_LOW
if (mode == 1) { mode ++; }
#endif
indicator_led_mode = (indicator_led_mode & 0b11111100) | mode;
#ifdef TICK_DURING_STANDBY
if (mode == 3)
indicator_led(mode & (arg&3));
else
indicator_led(mode);
#else
indicator_led(mode);
#endif
//save_config();
return MISCHIEF_MANAGED;
}
// click, click, hold, release: save indicator LED mode (off mode)
else if (event == EV_click3_hold_release) {
save_config();
return MISCHIEF_MANAGED;
}
#endif
#endif
// 4 clicks: exit
else if (event == EV_4clicks) {
blink_confirm(1);
set_state(off_state, 0);
return MISCHIEF_MANAGED;
}
return EVENT_NOT_HANDLED;
}
uint8_t momentary_state(Event event, uint16_t arg) {
// TODO: momentary strobe here? (for light painting)
// light up when the button is pressed; go dark otherwise
// button is being held
if ((event & (B_CLICK | B_PRESS)) == (B_CLICK | B_PRESS)) {
set_level(memorized_level);
return MISCHIEF_MANAGED;
}
// button was released
else if ((event & (B_CLICK | B_PRESS)) == (B_CLICK)) {
set_level(0);
//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
// TODO: lighted button should use lockout config?
}
return MISCHIEF_MANAGED;
}
return EVENT_NOT_HANDLED;
}
#ifdef USE_MUGGLE_MODE
uint8_t muggle_state(Event event, uint16_t arg) {
static int8_t ramp_direction;
static int8_t muggle_off_mode;
// turn LED off when we first enter the mode
if (event == EV_enter_state) {
ramp_direction = 1;
#ifdef START_AT_MEMORIZED_LEVEL
memorized_level = arg;
muggle_off_mode = 0;
set_level(memorized_level);
if (! muggle_mode_active) { // don't write eeprom at every boot
muggle_mode_active = 1;
save_config();
}
#else
muggle_mode_active = 1;
save_config();
muggle_off_mode = 1;
//memorized_level = MAX_1x7135;
memorized_level = (MUGGLE_FLOOR + MUGGLE_CEILING) / 2;
#endif
return MISCHIEF_MANAGED;
}
// initial press: moon hint
else if (event == EV_click1_press) {
if (muggle_off_mode)
set_level(MUGGLE_FLOOR);
}
// initial release: direct to memorized level
else if (event == EV_click1_release) {
if (muggle_off_mode)
set_level(memorized_level);
}
// if the user keeps pressing, turn off
else if (event == EV_click2_press) {
muggle_off_mode = 1;
set_level(0);
}
// 1 click: on/off
else if (event == EV_1click) {
muggle_off_mode ^= 1;
if (muggle_off_mode) {
set_level(0);
}
/*
else {
set_level(memorized_level);
}
*/
return MISCHIEF_MANAGED;
}
// hold: change brightness
else if (event == EV_click1_hold) {
// ramp at half speed
if (arg & 1) return MISCHIEF_MANAGED;
// if off, start at bottom
if (muggle_off_mode) {
muggle_off_mode = 0;
ramp_direction = 1;
set_level(MUGGLE_FLOOR);
}
else {
uint8_t m;
m = actual_level;
// ramp down if already at ceiling
if ((arg <= 1) && (m >= MUGGLE_CEILING)) ramp_direction = -1;
// ramp
m += ramp_direction;
if (m < MUGGLE_FLOOR)
m = MUGGLE_FLOOR;
if (m > MUGGLE_CEILING)
m = MUGGLE_CEILING;
memorized_level = m;
set_level(m);
}
return MISCHIEF_MANAGED;
}
// reverse ramp direction on hold release
else if (event == EV_click1_hold_release) {
ramp_direction = -ramp_direction;
#ifdef START_AT_MEMORIZED_LEVEL
save_config_wl(); // momentary use should retain brightness level
#endif
return MISCHIEF_MANAGED;
}
/*
// click, hold: change brightness (dimmer)
else if (event == EV_click2_hold) {
ramp_direction = 1;
if (memorized_level > MUGGLE_FLOOR)
memorized_level = actual_level - 1;
set_level(memorized_level);
return MISCHIEF_MANAGED;
}
*/
// 6 clicks: exit muggle mode
else if (event == EV_6clicks) {
blink_confirm(1);
muggle_mode_active = 0;
save_config();
set_state(off_state, 0);
return MISCHIEF_MANAGED;
}
// tick: housekeeping
else if (event == EV_tick) {
// un-reverse after 1 second
if (arg == TICKS_PER_SECOND) ramp_direction = 1;
// turn off, but don't go to the main "off" state
if (muggle_off_mode) {
if (arg > TICKS_PER_SECOND*1) { // sleep after 1 second
go_to_standby = 1; // sleep while light is off
}
}
return MISCHIEF_MANAGED;
}
#ifdef USE_THERMAL_REGULATION
// overheating is handled specially in muggle mode
else if(event == EV_temperature_high) {
// don't even try...
// go immediately to the bottom, in case someone put the light on
// maximum while wrapped in dark-colored flammable insulation
// or something, because muggles are cool like that
// memorized_level = MUGGLE_FLOOR; // override memory? maybe not
set_level(MUGGLE_FLOOR);
return MISCHIEF_MANAGED;
}
#endif
// low voltage is handled specially in muggle mode
else if(event == EV_voltage_low) {
uint8_t lvl = (actual_level >> 1) + (actual_level >> 2);
if (lvl >= MUGGLE_FLOOR) {
set_level(lvl);
} else {
muggle_off_mode = 1;
}
return MISCHIEF_MANAGED;
}
return EVENT_NOT_HANDLED;
}
#endif
// ask the user for a sequence of numbers, then save them and return to caller
uint8_t config_state_base(Event event, uint16_t arg,
uint8_t num_config_steps,
void (*savefunc)()) {
static uint8_t config_step;
if (event == EV_enter_state) {
config_step = 0;
set_level(0);
return MISCHIEF_MANAGED;
}
// advance forward through config steps
else if (event == EV_tick) {
if (config_step < num_config_steps) {
push_state(number_entry_state, config_step + 1);
}
else {
// TODO: blink out some sort of success pattern
savefunc();
save_config();
//set_state(retstate, retval);
pop_state();
}
return MISCHIEF_MANAGED;
}
// an option was set (return from number_entry_state)
else if (event == EV_reenter_state) {
config_state_values[config_step] = number_entry_value;
config_step ++;
return MISCHIEF_MANAGED;
}
//return EVENT_NOT_HANDLED;
// eat all other events; don't pass any through to parent
return EVENT_HANDLED;
}
#ifdef USE_RAMP_CONFIG
void ramp_config_save() {
// parse values
uint8_t val;
if (ramp_style) { // discrete / stepped ramp
val = config_state_values[0];
if (val) { ramp_discrete_floor = val; }
val = config_state_values[1];
if (val) { ramp_discrete_ceil = MAX_LEVEL + 1 - val; }
val = config_state_values[2];
if (val) ramp_discrete_steps = val;
} else { // smooth ramp
val = config_state_values[0];
if (val) { ramp_smooth_floor = val; }
val = config_state_values[1];
if (val) { ramp_smooth_ceil = MAX_LEVEL + 1 - val; }
}
}
uint8_t ramp_config_state(Event event, uint16_t arg) {
uint8_t num_config_steps;
num_config_steps = 2 + ramp_style;
return config_state_base(event, arg,
num_config_steps, ramp_config_save);
}
#endif // #ifdef USE_RAMP_CONFIG
#ifdef USE_THERMAL_REGULATION
void thermal_config_save() {
// parse values
uint8_t val;
// calibrate room temperature
val = config_state_values[0];
if (val) {
int8_t rawtemp = (temperature >> 1) - therm_cal_offset;
therm_cal_offset = val - rawtemp;
}
val = config_state_values[1];
if (val) {
// set maximum heat limit
therm_ceil = 30 + val;
}
if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL;
}
uint8_t thermal_config_state(Event event, uint16_t arg) {
return config_state_base(event, arg,
2, thermal_config_save);
}
#endif // #ifdef USE_THERMAL_REGULATION
#ifdef USE_BEACON_MODE
void beacon_config_save() {
// parse values
uint8_t val = config_state_values[0];
if (val) {
beacon_seconds = val;
}
}
uint8_t beacon_config_state(Event event, uint16_t arg) {
return config_state_base(event, arg,
1, beacon_config_save);
}
#endif // #ifdef USE_BEACON_MODE
uint8_t number_entry_state(Event event, uint16_t arg) {
static uint8_t value;
static uint8_t blinks_left;
static uint8_t entry_step;
static uint16_t wait_ticks;
if (event == EV_enter_state) {
value = 0;
blinks_left = arg;
entry_step = 0;
wait_ticks = 0;
return MISCHIEF_MANAGED;
}
// advance through the process:
// 0: wait a moment
// 1: blink out the 'arg' value
// 2: wait a moment
// 3: "buzz" while counting clicks
// 4: save and exit
else if (event == EV_tick) {
// wait a moment
if ((entry_step == 0) || (entry_step == 2)) {
if (wait_ticks < TICKS_PER_SECOND/2)
wait_ticks ++;
else {
entry_step ++;
wait_ticks = 0;
}
}
// blink out the option number
else if (entry_step == 1) {
if (blinks_left) {
if ((wait_ticks & 31) == 10) {
set_level(RAMP_SIZE/4);
}
else if ((wait_ticks & 31) == 20) {
set_level(0);
}
else if ((wait_ticks & 31) == 31) {
blinks_left --;
}
wait_ticks ++;
}
else {
entry_step ++;
wait_ticks = 0;
}
}
else if (entry_step == 3) { // buzz while waiting for a number to be entered
wait_ticks ++;
// buzz for N seconds after last event
if ((wait_ticks & 3) == 0) {
set_level(RAMP_SIZE/6);
}
else if ((wait_ticks & 3) == 2) {
set_level(RAMP_SIZE/8);
}
// time out after 3 seconds
if (wait_ticks > TICKS_PER_SECOND*3) {
//number_entry_value = value;
set_level(0);
entry_step ++;
}
}
else if (entry_step == 4) {
number_entry_value = value;
pop_state();
}
return MISCHIEF_MANAGED;
}
// count clicks
else if (event == EV_click1_release) {
empty_event_sequence();
if (entry_step == 3) { // only count during the "buzz"
value ++;
wait_ticks = 0;
// flash briefly
set_level(RAMP_SIZE/2);
delay_4ms(8/2);
set_level(0);
}
return MISCHIEF_MANAGED;
}
return EVENT_NOT_HANDLED;
}
// find the ramp level closest to the target,
// using only the levels which are allowed in the current state
uint8_t nearest_level(int16_t target) {
// 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);
}
}
#if defined(USE_INDICATOR_LED) && defined(TICK_DURING_STANDBY)
// beacon-like mode for the indicator LED
void indicator_blink(uint8_t arg) {
#ifdef USE_FANCIER_BLINKING_INDICATOR
// fancy blink, set off/low/high levels here:
uint8_t seq[] = {0, 1, 2, 1, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0};
indicator_led(seq[arg & 15]);
#else // basic blink, 1/8th duty cycle
if (! (arg & 7)) {
indicator_led(2);
}
else {
indicator_led(0);
}
#endif
}
#endif
#ifdef USE_CANDLE_MODE
uint8_t triangle_wave(uint8_t phase) {
uint8_t result = phase << 1;
if (phase > 127) result = 255 - result;
return result;
}
#endif
void load_config() {
if (load_eeprom()) {
ramp_style = eeprom[ramp_style_e];
#ifdef USE_RAMP_CONFIG
ramp_smooth_floor = eeprom[ramp_smooth_floor_e];
ramp_smooth_ceil = eeprom[ramp_smooth_ceil_e];
ramp_discrete_floor = eeprom[ramp_discrete_floor_e];
ramp_discrete_ceil = eeprom[ramp_discrete_ceil_e];
ramp_discrete_steps = eeprom[ramp_discrete_steps_e];
#endif
#if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
strobe_type = eeprom[strobe_type_e]; // TODO: move this to eeprom_wl?
strobe_delays[0] = eeprom[strobe_delays_0_e];
strobe_delays[1] = eeprom[strobe_delays_1_e];
#endif
#ifdef USE_BIKE_FLASHER_MODE
bike_flasher_brightness = eeprom[bike_flasher_brightness_e];
#endif
#ifdef USE_BEACON_MODE
beacon_seconds = eeprom[beacon_seconds_e];
#endif
#ifdef USE_MUGGLE_MODE
muggle_mode_active = eeprom[muggle_mode_active_e];
#endif
#ifdef USE_THERMAL_REGULATION
therm_ceil = eeprom[therm_ceil_e];
therm_cal_offset = eeprom[therm_cal_offset_e];
#endif
#ifdef USE_INDICATOR_LED
indicator_led_mode = eeprom[indicator_led_mode_e];
#endif
}
#ifdef START_AT_MEMORIZED_LEVEL
if (load_eeprom_wl()) {
memorized_level = eeprom_wl[0];
}
#endif
}
void save_config() {
eeprom[ramp_style_e] = ramp_style;
#ifdef USE_RAMP_CONFIG
eeprom[ramp_smooth_floor_e] = ramp_smooth_floor;
eeprom[ramp_smooth_ceil_e] = ramp_smooth_ceil;
eeprom[ramp_discrete_floor_e] = ramp_discrete_floor;
eeprom[ramp_discrete_ceil_e] = ramp_discrete_ceil;
eeprom[ramp_discrete_steps_e] = ramp_discrete_steps;
#endif
#if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
eeprom[strobe_type_e] = strobe_type; // TODO: move this to eeprom_wl?
eeprom[strobe_delays_0_e] = strobe_delays[0];
eeprom[strobe_delays_1_e] = strobe_delays[1];
#endif
#ifdef USE_BIKE_FLASHER_MODE
eeprom[bike_flasher_brightness_e] = bike_flasher_brightness;
#endif
#ifdef USE_BEACON_MODE
eeprom[beacon_seconds_e] = beacon_seconds;
#endif
#ifdef USE_MUGGLE_MODE
eeprom[muggle_mode_active_e] = muggle_mode_active;
#endif
#ifdef USE_THERMAL_REGULATION
eeprom[therm_ceil_e] = therm_ceil;
eeprom[therm_cal_offset_e] = therm_cal_offset;
#endif
#ifdef USE_INDICATOR_LED
eeprom[indicator_led_mode_e] = indicator_led_mode;
#endif
save_eeprom();
}
#ifdef START_AT_MEMORIZED_LEVEL
void save_config_wl() {
eeprom_wl[0] = memorized_level;
save_eeprom_wl();
}
#endif
void low_voltage() {
StatePtr state = current_state;
// TODO: turn off aux LED(s) when power is really low
if (0) {} // placeholder
#ifdef USE_STROBE_STATE
// "step down" from strobe to something low
else if (state == strobe_state) {
set_state(steady_state, RAMP_SIZE/6);
}
#endif
// in normal or muggle mode, step down or turn off
//else if ((state == steady_state) || (state == muggle_state)) {
else if (state == steady_state) {
if (actual_level > 1) {
uint8_t lvl = (actual_level >> 1) + (actual_level >> 2);
set_level(lvl);
#ifdef USE_THERMAL_REGULATION
target_level = lvl;
#ifdef USE_SET_LEVEL_GRADUALLY
// not needed?
//set_level_gradually(lvl);
#endif
#endif
}
else {
set_state(off_state, 0);
}
}
// all other modes, just turn off when voltage is low
else {
set_state(off_state, 0);
}
}
void setup() {
#ifdef START_AT_MEMORIZED_LEVEL
// dual switch: e-switch + power clicky
// power clicky acts as a momentary mode
load_config();
#ifdef USE_MUGGLE_MODE
if (muggle_mode_active)
push_state(muggle_state, memorized_level);
else
#endif
if (button_is_pressed())
// hold button to go to moon
push_state(steady_state, 1);
else
// otherwise use memory
push_state(steady_state, memorized_level);
#else // if not START_AT_MEMORIZED_LEVEL
// blink at power-on to let user know power is connected
set_level(RAMP_SIZE/8);
delay_4ms(3);
set_level(0);
load_config();
#ifdef USE_MUGGLE_MODE
if (muggle_mode_active)
push_state(muggle_state, (MUGGLE_FLOOR+MUGGLE_CEILING)/2);
else
#endif
push_state(off_state, 0);
#endif
}
void loop() {
StatePtr state = current_state;
if (0) {}
#ifdef USE_STROBE_STATE
if (state == strobe_state) {
uint8_t st = strobe_type;
if (0) {} // placeholder
// party / tactial strobe
#if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
#ifdef USE_TACTICAL_STROBE_MODE
else if (st <= tactical_strobe_e) {
#else
else if (st == party_strobe_e) {
#endif
uint8_t del = strobe_delays[st];
// TODO: make tac strobe brightness configurable?
set_level(STROBE_BRIGHTNESS);
if (st == party_strobe_e) { // party strobe
if (del < 42) delay_zero();
else nice_delay_ms(1);
} else { //tactical strobe
nice_delay_ms(del >> 1);
}
set_level(0);
nice_delay_ms(del); // no return check necessary on final delay
}
#endif
// lightning storm
#ifdef USE_LIGHTNING_MODE
else if (st == lightning_storm_e) {
int16_t brightness;
uint16_t rand_time;
// turn the emitter on at a random level,
// for a random amount of time between 1ms and 32ms
//rand_time = 1 << (pseudo_rand() % 7);
rand_time = pseudo_rand() & 63;
brightness = 1 << (pseudo_rand() % 7); // 1, 2, 4, 8, 16, 32, 64
brightness += 1 << (pseudo_rand() & 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);
nice_delay_ms(rand_time);
// decrease the brightness somewhat more gradually, like lightning
uint8_t stepdown = brightness >> 3;
if (stepdown < 1) stepdown = 1;
while(brightness > 1) {
nice_delay_ms(rand_time);
brightness -= stepdown;
if (brightness < 0) brightness = 0;
set_level(brightness);
/*
if ((brightness < MAX_LEVEL/2) && (! (pseudo_rand() & 15))) {
brightness <<= 1;
set_level(brightness);
}
*/
if (! (pseudo_rand() & 3)) {
nice_delay_ms(rand_time);
set_level(brightness>>1);
}
}
// turn the emitter off,
// for a random amount of time between 1ms and 8192ms
// (with a low bias)
rand_time = 1 << (pseudo_rand() % 13);
rand_time += pseudo_rand() % rand_time;
set_level(0);
nice_delay_ms(rand_time); // no return check necessary on final delay
}
#endif
// candle mode
#ifdef USE_CANDLE_MODE
// this NOP should get compiled out
else if (st == candle_mode_e) {}
#endif
// bike flasher
#ifdef USE_BIKE_FLASHER_MODE
else if (st == bike_flasher_e) {
uint8_t burst = bike_flasher_brightness << 1;
if (burst > MAX_LEVEL) burst = MAX_LEVEL;
for(uint8_t i=0; i<4; i++) {
set_level(burst);
nice_delay_ms(5);
set_level(bike_flasher_brightness);
nice_delay_ms(65);
}
nice_delay_ms(720); // no return check necessary on final delay
}
#endif
}
#endif // #ifdef USE_STROBE_STATE
#ifdef USE_BORING_STROBE_STATE
if (state == boring_strobe_state) {
uint8_t st = boring_strobe_type;
// police strobe
if (st == 0) {
// flash at 16 Hz then 8 Hz, 8 times each
for (uint8_t del=41; del<100; del+=41) {
for (uint8_t f=0; f<8; f++) {
set_level(STROBE_BRIGHTNESS);
nice_delay_ms(del >> 1);
set_level(0);
nice_delay_ms(del);
}
}
}
// SOS
else if (st == 1) {
nice_delay_ms(1000);
sos_blink(3, 0);
sos_blink(3, 1);
sos_blink(3, 0);
nice_delay_ms(1000);
}
}
#endif // #ifdef USE_BORING_STROBE_STATE
#ifdef USE_BATTCHECK
else if (state == battcheck_state) {
battcheck();
}
#endif
#ifdef USE_THERMAL_REGULATION
// TODO: blink out therm_ceil during thermal_config_state
else if (state == tempcheck_state) {
blink_num(temperature>>1);
nice_delay_ms(1000);
}
#endif
#ifdef USE_BEACON_MODE
else if (state == beacon_state) {
set_level(memorized_level);
nice_delay_ms(500);
set_level(0);
nice_delay_ms(((beacon_seconds) * 1000) - 500);
}
#endif
#ifdef USE_IDLE_MODE
else {
// doze until next clock tick
idle_mode();
}
#endif
}