~toykeeper/flashlight-firmware/fsm

« back to all changes in this revision

Viewing changes to ToyKeeper/spaghetti-monster/anduril/anduril.c

  • Committer: Selene ToyKeeper
  • Date: 2023-11-04 15:09:10 UTC
  • mfrom: (483.1.175 anduril2)
  • Revision ID: bzr@toykeeper.net-20231104150910-ddd3afw4nhfvof2l
merged anduril2 branch -> fsm, with *years* of changes
(this also means this code is now Anduril 2 instead of Anduril 1)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Anduril: Narsil-inspired UI for SpaghettiMonster.
 
2
// (Anduril is Aragorn's sword, the blade Narsil reforged)
 
3
// Copyright (C) 2017-2023 Selene ToyKeeper
 
4
// SPDX-License-Identifier: GPL-3.0-or-later
 
5
 
1
6
/*
2
 
 * Anduril: Narsil-inspired UI for SpaghettiMonster.
3
 
 * (Anduril is Aragorn's sword, the blade Narsil reforged)
4
 
 *
5
 
 * Copyright (C) 2017-2019 Selene Scriven
6
 
 *
7
 
 * This program is free software: you can redistribute it and/or modify
8
 
 * it under the terms of the GNU General Public License as published by
9
 
 * the Free Software Foundation, either version 3 of the License, or
10
 
 * (at your option) any later version.
11
 
 *
12
 
 * This program is distributed in the hope that it will be useful,
13
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 
 * GNU General Public License for more details.
16
 
 *
17
 
 * You should have received a copy of the GNU General Public License
18
 
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
7
 * Usually a program would be structured like this...
 
8
 * - Library headers
 
9
 * - App headers
 
10
 * - App code
 
11
 *
 
12
 * ... in each source file.
 
13
 * ... and each library and part of the program would be linked together.
 
14
 *
 
15
 * But this doesn't follow that pattern, because it's using the
 
16
 *   -fwhole-program
 
17
 * flag to reduce the compiled size.  It lets us fit more features
 
18
 * in a tiny MCU chip's ROM.
 
19
 *
 
20
 * So the structure is like this instead...
 
21
 * - App-level configuration headers
 
22
 *   - Default config
 
23
 *   - Per build target config
 
24
 * - Library-level configuration headers
 
25
 * - Library code (FSM itself)
 
26
 * - App headers
 
27
 * - App code (all of it, inline)
 
28
 *
 
29
 * Don't do this in regular programs.  It's weird and kind of gross.
 
30
 * But in this case it gives us a bunch of much-needed space, so... woot.
 
31
 *
 
32
 * Also, there are a ton of compile-time options because it needs to build
 
33
 * a bunch of different versions and each one needs to be trimmed as small
 
34
 * as possible.  These are mostly "USE" flags.
19
35
 */
20
36
 
21
37
/********* User-configurable options *********/
 
38
#include "config-default.h"
 
39
 
 
40
/********* specific settings for known driver types *********/
22
41
// Anduril config file name (set it here or define it at the gcc command line)
23
 
//#define CONFIGFILE cfg-blf-q8.h
24
 
 
25
 
#define USE_LVP
26
 
 
27
 
// parameters for this defined below or per-driver
28
 
#define USE_THERMAL_REGULATION
29
 
#define DEFAULT_THERM_CEIL 45  // try not to get hotter than this
30
 
 
31
 
#define USE_FACTORY_RESET
32
 
//#define USE_SOFT_FACTORY_RESET  // only needed on models which can't use hold-button-at-boot
33
 
 
34
 
// dual-switch support (second switch is a tail clicky)
35
 
// (currently incompatible with factory reset)
36
 
//#define START_AT_MEMORIZED_LEVEL
37
 
 
38
 
// include a function to blink out the firmware version
39
 
#define USE_VERSION_CHECK
40
 
 
41
 
// short blip when crossing from "click" to "hold" from off
42
 
// (helps the user hit moon mode exactly, instead of holding too long
43
 
//  or too short)
44
 
#define MOON_TIMING_HINT  // only applies if B_TIMING_ON == B_PRESS_T
45
 
// short blips while ramping
46
 
#define BLINK_AT_RAMP_MIDDLE
47
 
//#define BLINK_AT_RAMP_FLOOR
48
 
#define BLINK_AT_RAMP_CEILING
49
 
//#define BLINK_AT_STEPS  // whenever a discrete ramp mode is passed in smooth mode
50
 
 
51
 
// ramp down via regular button hold if a ramp-up ended <1s ago
52
 
// ("hold, release, hold" ramps down instead of up)
53
 
#define USE_REVERSING
54
 
 
55
 
// add a runtime option to switch between automatic memory (default)
56
 
// and manual memory (only available if compiled in)
57
 
// (manual memory makes 1-click-from-off start at the same level each time)
58
 
// (the level can be set explicitly with 5 clicks from on,
59
 
// or the user can go back to automatic with click-click-click-click-hold)
60
 
#define USE_MANUAL_MEMORY
61
 
 
62
 
// battery readout style (pick one)
63
 
#define BATTCHECK_VpT
64
 
//#define BATTCHECK_8bars  // FIXME: breaks build
65
 
//#define BATTCHECK_4bars  // FIXME: breaks build
66
 
 
67
 
// enable/disable various strobe modes
68
 
#define USE_BIKE_FLASHER_MODE
69
 
#define USE_PARTY_STROBE_MODE
70
 
#define USE_TACTICAL_STROBE_MODE
71
 
#define USE_LIGHTNING_MODE
72
 
#define USE_CANDLE_MODE
73
 
 
74
 
// enable sunset (goodnight) mode
75
 
#define USE_GOODNIGHT_MODE
76
 
#define GOODNIGHT_TIME  60  // minutes (approximately)
77
 
#define GOODNIGHT_LEVEL 24  // ~11 lm
78
 
 
79
 
// enable beacon mode
80
 
#define USE_BEACON_MODE
81
 
 
82
 
// enable momentary mode
83
 
#define USE_MOMENTARY_MODE
84
 
 
85
 
//Muggle mode for easy UI
86
 
#define USE_MUGGLE_MODE
87
 
 
88
 
// make the ramps configurable by the user
89
 
#define USE_RAMP_CONFIG
90
 
 
91
 
// boring strobes nobody really likes, but sometimes flashlight companies want
92
 
// (these replace the fun strobe group,
93
 
//  so don't enable them at the same time as any of the above strobes)
94
 
//#define USE_POLICE_STROBE_MODE
95
 
//#define USE_SOS_MODE
96
 
//#define USE_SOS_MODE_IN_FF_GROUP  // put SOS in the "boring strobes" mode
97
 
//#define USE_SOS_MODE_IN_BLINKY_GROUP  // put SOS in the blinkies mode group
98
 
 
99
 
// cut clock speed at very low modes for better efficiency
100
 
// (defined here so config files can override it)
101
 
#define USE_DYNAMIC_UNDERCLOCKING
102
 
 
103
 
/***** specific settings for known driver types *****/
 
42
//#define CFG_H cfg-blf-q8.h
 
43
 
104
44
#include "tk.h"
105
 
#include incfile(CONFIGFILE)
106
 
 
107
 
 
108
 
// brightness to use when no memory is set
109
 
#ifndef DEFAULT_LEVEL
110
 
#define DEFAULT_LEVEL MAX_1x7135
111
 
#endif
112
 
 
113
 
// thermal properties, if not defined per-driver
114
 
#ifndef MIN_THERM_STEPDOWN
115
 
#define MIN_THERM_STEPDOWN MAX_1x7135  // lowest value it'll step down to
116
 
#endif
117
 
#ifndef THERM_FASTER_LEVEL
118
 
    #ifdef MAX_Nx7135
119
 
    #define THERM_FASTER_LEVEL MAX_Nx7135  // throttle back faster when high
120
 
    #else
121
 
    #define THERM_FASTER_LEVEL (RAMP_SIZE*4/5)  // throttle back faster when high
122
 
    #endif
123
 
#endif
124
 
#ifdef USE_THERMAL_REGULATION
125
 
#define USE_SET_LEVEL_GRADUALLY  // isn't used except for thermal adjustments
126
 
#endif
127
 
 
128
 
 
129
 
/********* Configure SpaghettiMonster *********/
130
 
#define USE_DELAY_ZERO
131
 
#define USE_RAMPING
132
 
#ifndef RAMP_LENGTH
133
 
#define RAMP_LENGTH 150  // default, if not overridden in a driver cfg file
134
 
#endif
135
 
#define MAX_BIKING_LEVEL 120  // should be 127 or less
136
 
#define USE_BATTCHECK
137
 
 
138
 
#if defined(USE_MUGGLE_MODE)
139
 
#ifndef MUGGLE_FLOOR
140
 
#define MUGGLE_FLOOR 22
141
 
#endif
142
 
#ifndef MUGGLE_CEILING
143
 
#define MUGGLE_CEILING (MAX_1x7135+20)
144
 
#endif
145
 
#endif
 
45
#include incfile(CFG_H)
 
46
 
 
47
 
 
48
/********* Include headers which need to be before FSM *********/
 
49
 
 
50
// enable FSM features needed by basic ramping functions
 
51
#include "ramp-mode-fsm.h"
 
52
 
 
53
#ifdef USE_FACTORY_RESET
 
54
#include "factory-reset-fsm.h"
 
55
#endif
 
56
 
 
57
#ifdef USE_BATTCHECK_MODE
 
58
#include "battcheck-mode-fsm.h"
 
59
#endif
 
60
 
 
61
#ifdef USE_LOCKOUT_MODE
 
62
#include "lockout-mode-fsm.h"
 
63
#endif
 
64
 
 
65
// enable FSM features needed by strobe modes
 
66
#include "strobe-modes-fsm.h"
 
67
 
 
68
// figure out how many bytes of eeprom are needed,
 
69
// based on which UI features are enabled
 
70
// (include this one last)
 
71
#include "load-save-config-fsm.h"
 
72
 
 
73
 
 
74
/********* bring in FSM / SpaghettiMonster *********/
146
75
#define USE_IDLE_MODE  // reduce power use while awake and no tasks are pending
147
76
 
148
 
// full FET strobe can be a bit much...  use max regulated level instead,
149
 
// if there's a bright enough regulated level
150
 
#ifndef STROBE_BRIGHTNESS
151
 
#ifdef MAX_Nx7135
152
 
#define STROBE_BRIGHTNESS MAX_Nx7135
153
 
#else
154
 
#define STROBE_BRIGHTNESS MAX_LEVEL
155
 
#endif
156
 
#endif
157
 
 
158
 
#if defined(USE_CANDLE_MODE) || defined(USE_BIKE_FLASHER_MODE) || defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE) || defined(USE_LIGHTNING_MODE)
159
 
#define USE_STROBE_STATE
160
 
#endif
161
 
 
162
 
#if defined(USE_POLICE_STROBE_MODE) || defined(USE_SOS_MODE_IN_FF_GROUP)
163
 
#define USE_BORING_STROBE_STATE
164
 
#endif
165
 
 
166
 
// auto-detect how many eeprom bytes
167
 
#define USE_EEPROM
168
 
typedef enum {
169
 
    ramp_style_e,
170
 
    #ifdef USE_RAMP_CONFIG
171
 
    ramp_smooth_floor_e,
172
 
    ramp_smooth_ceil_e,
173
 
    ramp_discrete_floor_e,
174
 
    ramp_discrete_ceil_e,
175
 
    ramp_discrete_steps_e,
176
 
    #endif
177
 
    #ifdef USE_MANUAL_MEMORY
178
 
    manual_memory_e,
179
 
    #endif
180
 
    #ifdef USE_TINT_RAMPING
181
 
    tint_e,
182
 
    #endif
183
 
    #ifdef USE_STROBE_STATE
184
 
    strobe_type_e,
185
 
    #endif
186
 
    #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
187
 
    strobe_delays_0_e,
188
 
    strobe_delays_1_e,
189
 
    #endif
190
 
    #ifdef USE_BIKE_FLASHER_MODE
191
 
    bike_flasher_brightness_e,
192
 
    #endif
193
 
    #ifdef USE_BEACON_MODE
194
 
    beacon_seconds_e,
195
 
    #endif
196
 
    #ifdef USE_MUGGLE_MODE
197
 
    muggle_mode_active_e,
198
 
    #endif
199
 
    #ifdef USE_THERMAL_REGULATION
200
 
    therm_ceil_e,
201
 
    therm_cal_offset_e,
202
 
    #endif
203
 
    #ifdef USE_INDICATOR_LED
204
 
    indicator_led_mode_e,
205
 
    #endif
206
 
    #ifdef USE_AUX_RGB_LEDS
207
 
    rgb_led_off_mode_e,
208
 
    rgb_led_lockout_mode_e,
209
 
    #endif
210
 
    eeprom_indexes_e_END
211
 
} eeprom_indexes_e;
212
 
#define EEPROM_BYTES eeprom_indexes_e_END
213
 
 
214
 
#ifdef START_AT_MEMORIZED_LEVEL
215
 
#define USE_EEPROM_WL
216
 
#define EEPROM_WL_BYTES 1
217
 
#endif
218
 
 
219
 
// auto-configure other stuff...
220
 
#if defined(USE_LIGHTNING_MODE) || defined(USE_CANDLE_MODE)
221
 
#define USE_PSEUDO_RAND
222
 
#endif
223
 
 
224
 
#if defined(USE_CANDLE_MODE)
225
 
#ifndef USE_TRIANGLE_WAVE
226
 
#define USE_TRIANGLE_WAVE
227
 
#endif
228
 
#endif
229
 
 
230
 
#ifdef USE_SOFT_FACTORY_RESET
231
 
#define USE_REBOOT
232
 
#endif
233
 
 
234
77
#include "spaghetti-monster.h"
235
78
 
236
 
 
237
 
// configure the timing of turning on/off in regular ramp mode
238
 
// press: react as soon as the button is pressed
239
 
#define B_PRESS_T 0
240
 
// release: react as soon as the button is released
241
 
#define B_RELEASE_T 1
242
 
// timeout: react as soon as we're sure the user isn't doing a double-click
243
 
#define B_TIMEOUT_T 2
244
 
// defaults are release on, timeout off
245
 
#ifndef B_TIMING_ON
246
 
//#define B_TIMING_ON B_PRESS_T
247
 
#define B_TIMING_ON B_RELEASE_T
248
 
#endif
249
 
#ifndef B_TIMING_OFF
250
 
//#define B_TIMING_OFF B_RELEASE_T
251
 
#define B_TIMING_OFF B_TIMEOUT_T
252
 
#endif
253
 
 
254
 
 
255
 
// FSM states
256
 
uint8_t off_state(Event event, uint16_t arg);
257
 
// simple numeric entry config menu
258
 
uint8_t config_state_base(Event event, uint16_t arg,
259
 
                          uint8_t num_config_steps,
260
 
                          void (*savefunc)());
261
 
#define MAX_CONFIG_VALUES 3
262
 
uint8_t config_state_values[MAX_CONFIG_VALUES];
263
 
// ramping mode and its related config mode
264
 
uint8_t steady_state(Event event, uint16_t arg);
265
 
#ifdef USE_RAMP_CONFIG
266
 
uint8_t ramp_config_state(Event event, uint16_t arg);
267
 
#endif
268
 
#ifdef USE_TINT_RAMPING
269
 
// not actually a mode, more of a fallback under other modes
270
 
uint8_t tint_ramping_state(Event event, uint16_t arg);
271
 
#endif
272
 
// party and tactical strobes
273
 
#ifdef USE_STROBE_STATE
274
 
uint8_t strobe_state(Event event, uint16_t arg);
275
 
#endif
276
 
#ifdef USE_BORING_STROBE_STATE
277
 
uint8_t boring_strobe_state(Event event, uint16_t arg);
278
 
volatile uint8_t boring_strobe_type = 0;
279
 
void sos_blink(uint8_t num, uint8_t dah);
280
 
#define NUM_BORING_STROBES 2
281
 
#endif
282
 
#ifdef USE_BATTCHECK
283
 
uint8_t battcheck_state(Event event, uint16_t arg);
284
 
#endif
285
 
#ifdef USE_THERMAL_REGULATION
286
 
#define USE_BLINK_NUM
287
 
uint8_t tempcheck_state(Event event, uint16_t arg);
288
 
uint8_t thermal_config_state(Event event, uint16_t arg);
289
 
#endif
290
 
#ifdef USE_GOODNIGHT_MODE
291
 
// 1-hour ramp down from low, then automatic off
292
 
uint8_t goodnight_state(Event event, uint16_t arg);
293
 
#endif
294
 
#ifdef USE_BEACON_MODE
295
 
// beacon mode and its related config mode
296
 
uint8_t beacon_state(Event event, uint16_t arg);
297
 
uint8_t beacon_config_state(Event event, uint16_t arg);
298
 
#endif
299
 
#ifdef USE_SOS_MODE_IN_BLINKY_GROUP
300
 
// automatic SOS emergency signal
301
 
uint8_t sos_state(Event event, uint16_t arg);
302
 
#endif
303
 
// soft lockout
304
 
#define MOON_DURING_LOCKOUT_MODE
305
 
// if enabled, 2nd lockout click goes to the other ramp's floor level
306
 
#define LOCKOUT_MOON_FANCY
307
 
uint8_t lockout_state(Event event, uint16_t arg);
308
 
#ifdef USE_MOMENTARY_MODE
309
 
// momentary / signalling mode
310
 
uint8_t momentary_state(Event event, uint16_t arg);
311
 
uint8_t momentary_mode = 0;  // 0 = ramping, 1 = strobe
312
 
uint8_t momentary_active = 0;  // boolean, true if active *right now*
313
 
#endif
314
 
#ifdef USE_MUGGLE_MODE
315
 
// muggle mode, super-simple, hard to exit
316
 
uint8_t muggle_state(Event event, uint16_t arg);
317
 
uint8_t muggle_mode_active = 0;
318
 
#endif
319
 
 
320
 
// general helper function for config modes
321
 
uint8_t number_entry_state(Event event, uint16_t arg);
322
 
// return value from number_entry_state()
323
 
volatile uint8_t number_entry_value;
324
 
 
325
 
void blink_confirm(uint8_t num);
326
 
void blip();
327
 
#if defined(USE_INDICATOR_LED) && defined(TICK_DURING_STANDBY)
328
 
void indicator_blink(uint8_t arg);
329
 
#endif
330
 
#if defined(USE_AUX_RGB_LEDS) && defined(TICK_DURING_STANDBY)
331
 
uint8_t setting_rgb_mode_now = 0;
332
 
void rgb_led_update(uint8_t mode, uint8_t arg);
333
 
void rgb_led_voltage_readout(uint8_t bright);
334
 
/*
335
 
 * 0: R
336
 
 * 1: RG
337
 
 * 2:  G
338
 
 * 3:  GB
339
 
 * 4:   B
340
 
 * 5: R B
341
 
 * 6: RGB
342
 
 * 7: rainbow
343
 
 * 8: voltage
344
 
 */
345
 
const PROGMEM uint8_t rgb_led_colors[] = {
346
 
    0b00000001,  // 0: red
347
 
    0b00000101,  // 1: yellow
348
 
    0b00000100,  // 2: green
349
 
    0b00010100,  // 3: cyan
350
 
    0b00010000,  // 4: blue
351
 
    0b00010001,  // 5: purple
352
 
    0b00010101,  // 6: white
353
 
};
354
 
#define RGB_LED_NUM_COLORS 10
355
 
#define RGB_LED_NUM_PATTERNS 4
356
 
#ifndef RGB_LED_OFF_DEFAULT
357
 
//#define RGB_LED_OFF_DEFAULT 0x18  // low, voltage
358
 
#define RGB_LED_OFF_DEFAULT 0x17  // low, rainbow
359
 
#endif
360
 
#ifndef RGB_LED_LOCKOUT_DEFAULT
361
 
#define RGB_LED_LOCKOUT_DEFAULT 0x37  // blinking, rainbow
362
 
#endif
363
 
#ifndef RGB_RAINBOW_SPEED
364
 
#define RGB_RAINBOW_SPEED 0x0f  // change color every 16 frames
365
 
#endif
366
 
uint8_t rgb_led_off_mode = RGB_LED_OFF_DEFAULT;
367
 
uint8_t rgb_led_lockout_mode = RGB_LED_LOCKOUT_DEFAULT;
368
 
#endif
369
 
 
370
 
#ifdef USE_FACTORY_RESET
371
 
void factory_reset();
372
 
#endif
373
 
 
374
 
// remember stuff even after battery was changed
375
 
void load_config();
376
 
void save_config();
377
 
#ifdef START_AT_MEMORIZED_LEVEL
378
 
void save_config_wl();
379
 
#endif
380
 
 
381
 
// default ramp options if not overridden earlier per-driver
382
 
#ifndef RAMP_STYLE
383
 
#define RAMP_STYLE 0  // smooth default
384
 
#endif
385
 
#ifndef RAMP_SMOOTH_FLOOR
386
 
  #define RAMP_SMOOTH_FLOOR 1
387
 
#endif
388
 
#ifndef RAMP_SMOOTH_CEIL
389
 
  #if PWM_CHANNELS == 3
390
 
    #define RAMP_SMOOTH_CEIL MAX_Nx7135
391
 
  #else
392
 
    #define RAMP_SMOOTH_CEIL MAX_LEVEL - 30
393
 
  #endif
394
 
#endif
395
 
#ifndef RAMP_DISCRETE_FLOOR
396
 
  #define RAMP_DISCRETE_FLOOR 20
397
 
#endif
398
 
#ifndef RAMP_DISCRETE_CEIL
399
 
  #define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
400
 
#endif
401
 
#ifndef RAMP_DISCRETE_STEPS
402
 
  #define RAMP_DISCRETE_STEPS 7
403
 
#endif
404
 
 
405
 
// mile marker(s) partway up the ramp
406
 
// default: blink only at border between regulated and FET
407
 
#ifdef BLINK_AT_RAMP_MIDDLE
408
 
  #if PWM_CHANNELS >= 3
409
 
    #ifndef BLINK_AT_RAMP_MIDDLE_1
410
 
      #define BLINK_AT_RAMP_MIDDLE_1 MAX_Nx7135
411
 
      #ifndef BLINK_AT_RAMP_MIDDLE_2
412
 
      #define BLINK_AT_RAMP_MIDDLE_2 MAX_1x7135
413
 
      #endif
414
 
    #endif
415
 
  #else
416
 
    #ifndef BLINK_AT_RAMP_MIDDLE_1
417
 
    #define BLINK_AT_RAMP_MIDDLE_1 MAX_1x7135
418
 
    #endif
419
 
  #endif
420
 
#endif
421
 
 
422
 
// brightness control
423
 
uint8_t memorized_level = DEFAULT_LEVEL;
424
 
#ifdef USE_MANUAL_MEMORY
425
 
uint8_t manual_memory = 0;
426
 
#endif
427
 
// smooth vs discrete ramping
428
 
volatile uint8_t ramp_style = RAMP_STYLE;  // 0 = smooth, 1 = discrete
429
 
volatile uint8_t ramp_smooth_floor = RAMP_SMOOTH_FLOOR;
430
 
volatile uint8_t ramp_smooth_ceil = RAMP_SMOOTH_CEIL;
431
 
volatile uint8_t ramp_discrete_floor = RAMP_DISCRETE_FLOOR;
432
 
volatile uint8_t ramp_discrete_ceil = RAMP_DISCRETE_CEIL;
433
 
volatile uint8_t ramp_discrete_steps = RAMP_DISCRETE_STEPS;
434
 
uint8_t ramp_discrete_step_size;  // don't set this
435
 
 
436
 
#ifdef USE_INDICATOR_LED
437
 
    // bits 2-3 control lockout mode
438
 
    // bits 0-1 control "off" mode
439
 
    // modes are: 0=off, 1=low, 2=high, 3=blinking (if TICK_DURING_STANDBY enabled)
440
 
    #ifdef INDICATOR_LED_DEFAULT_MODE
441
 
    uint8_t indicator_led_mode = INDICATOR_LED_DEFAULT_MODE;
442
 
    #else
443
 
        #ifdef USE_INDICATOR_LED_WHILE_RAMPING
444
 
        //uint8_t indicator_led_mode = (1<<2) + 2;
445
 
        uint8_t indicator_led_mode = (2<<2) + 1;
446
 
        #else
447
 
        uint8_t indicator_led_mode = (3<<2) + 1;
448
 
        #endif
449
 
    #endif
450
 
#endif
451
 
 
452
 
// calculate the nearest ramp level which would be valid at the moment
453
 
// (is a no-op for smooth ramp, but limits discrete ramp to only the
454
 
// correct levels for the user's config)
455
 
uint8_t nearest_level(int16_t target);
456
 
 
457
 
#ifdef USE_THERMAL_REGULATION
458
 
// brightness before thermal step-down
459
 
uint8_t target_level = 0;
460
 
void set_level_and_therm_target(uint8_t level);
461
 
#else
462
 
#define set_level_and_therm_target(level) set_level(level)
463
 
#endif
464
 
 
465
 
// internal numbering for strobe modes
466
 
#ifdef USE_STROBE_STATE
467
 
typedef enum {
468
 
    #ifdef USE_PARTY_STROBE_MODE
469
 
    party_strobe_e,
470
 
    #endif
471
 
    #ifdef USE_TACTICAL_STROBE_MODE
472
 
    tactical_strobe_e,
473
 
    #endif
474
 
    #ifdef USE_LIGHTNING_MODE
475
 
    lightning_storm_e,
476
 
    #endif
477
 
    #ifdef USE_CANDLE_MODE
478
 
    candle_mode_e,
479
 
    #endif
480
 
    #ifdef USE_BIKE_FLASHER_MODE
481
 
    bike_flasher_e,
482
 
    #endif
483
 
    strobe_mode_END
484
 
} strobe_mode_te;
485
 
 
486
 
const int NUM_STROBES = strobe_mode_END;
487
 
 
488
 
// which strobe mode is active?
489
 
#ifdef USE_CANDLE_MODE
490
 
volatile strobe_mode_te strobe_type = candle_mode_e;
491
 
#else
492
 
volatile strobe_mode_te strobe_type = 0;
493
 
#endif
494
 
#endif
495
 
 
496
 
#if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
497
 
// party / tactical strobe timing
498
 
volatile uint8_t strobe_delays[] = { 41, 67 };  // party strobe 24 Hz, tactical strobe 10 Hz
499
 
#endif
500
 
 
501
 
// bike mode config options
502
 
#ifdef USE_BIKE_FLASHER_MODE
503
 
volatile uint8_t bike_flasher_brightness = MAX_1x7135;
504
 
#endif
505
 
 
506
 
#ifdef USE_CANDLE_MODE
507
 
uint8_t candle_mode_state(Event event, uint16_t arg);
508
 
uint8_t triangle_wave(uint8_t phase);
509
 
#ifndef CANDLE_AMPLITUDE
510
 
#define CANDLE_AMPLITUDE 25
511
 
#endif
512
 
#endif
513
 
 
514
 
#ifdef USE_BEACON_MODE
515
 
// beacon timing
516
 
volatile uint8_t beacon_seconds = 2;
517
 
#endif
518
 
 
519
 
#ifdef USE_VERSION_CHECK
520
 
#define USE_BLINK_DIGIT
521
 
#include "version.h"
522
 
const PROGMEM uint8_t version_number[] = VERSION_NUMBER;
523
 
uint8_t version_check_state(Event event, uint16_t arg);
524
 
#endif
525
 
 
526
 
uint8_t off_state(Event event, uint16_t arg) {
527
 
    // turn emitter off when entering state
528
 
    if (event == EV_enter_state) {
529
 
        set_level(0);
530
 
        #ifdef USE_INDICATOR_LED
531
 
        indicator_led(indicator_led_mode & 0x03);
532
 
        #elif defined(USE_AUX_RGB_LEDS)
533
 
        rgb_led_update(rgb_led_off_mode, 0);
534
 
        #endif
535
 
        // sleep while off  (lower power use)
536
 
        // (unless delay requested; give the ADC some time to catch up)
537
 
        if (! arg) { go_to_standby = 1; }
538
 
        return MISCHIEF_MANAGED;
539
 
    }
540
 
    // go back to sleep eventually if we got bumped but didn't leave "off" state
541
 
    else if (event == EV_tick) {
542
 
        if (arg > HOLD_TIMEOUT) {
543
 
            go_to_standby = 1;
544
 
            #ifdef USE_INDICATOR_LED
545
 
            indicator_led(indicator_led_mode & 0x03);
546
 
            #elif defined(USE_AUX_RGB_LEDS)
547
 
            rgb_led_update(rgb_led_off_mode, arg);
548
 
            #endif
549
 
        }
550
 
        return MISCHIEF_MANAGED;
551
 
    }
552
 
    #if defined(TICK_DURING_STANDBY) && (defined(USE_INDICATOR_LED) || defined(USE_AUX_RGB_LEDS))
553
 
    // blink the indicator LED, maybe
554
 
    else if (event == EV_sleep_tick) {
555
 
        #ifdef USE_INDICATOR_LED
556
 
        if ((indicator_led_mode & 0b00000011) == 0b00000011) {
557
 
            indicator_blink(arg);
558
 
        }
559
 
        #elif defined(USE_AUX_RGB_LEDS)
560
 
        rgb_led_update(rgb_led_off_mode, arg);
561
 
        #endif
562
 
        return MISCHIEF_MANAGED;
563
 
    }
564
 
    #endif
565
 
    #if (B_TIMING_ON == B_PRESS_T)
566
 
    // hold (initially): go to lowest level (floor), but allow abort for regular click
567
 
    else if (event == EV_click1_press) {
568
 
        set_level(nearest_level(1));
569
 
        return MISCHIEF_MANAGED;
570
 
    }
571
 
    #endif  // B_TIMING_ON == B_PRESS_T
572
 
    // hold: go to lowest level
573
 
    else if (event == EV_click1_hold) {
574
 
        #if (B_TIMING_ON == B_PRESS_T)
575
 
        #ifdef MOON_TIMING_HINT
576
 
        if (arg == 0) {
577
 
            // let the user know they can let go now to stay at moon
578
 
            blip();
579
 
        } else
580
 
        #endif
581
 
        #else  // B_RELEASE_T or B_TIMEOUT_T
582
 
        set_level(nearest_level(1));
583
 
        #endif
584
 
        // don't start ramping immediately;
585
 
        // give the user time to release at moon level
586
 
        //if (arg >= HOLD_TIMEOUT) {  // smaller
587
 
        if (arg >= (!ramp_style) * HOLD_TIMEOUT) {  // more consistent
588
 
            set_state(steady_state, 1);
589
 
        }
590
 
        return MISCHIEF_MANAGED;
591
 
    }
592
 
    // hold, release quickly: go to lowest level (floor)
593
 
    else if (event == EV_click1_hold_release) {
594
 
        set_state(steady_state, 1);
595
 
        return MISCHIEF_MANAGED;
596
 
    }
597
 
    #if (B_TIMING_ON != B_TIMEOUT_T)
598
 
    // 1 click (before timeout): go to memorized level, but allow abort for double click
599
 
    else if (event == EV_click1_release) {
600
 
        #ifdef USE_MANUAL_MEMORY
601
 
        if (manual_memory)
602
 
            set_level(nearest_level(manual_memory));
603
 
        else
604
 
        #endif
605
 
        set_level(nearest_level(memorized_level));
606
 
        return MISCHIEF_MANAGED;
607
 
    }
608
 
    #endif  // if (B_TIMING_ON != B_TIMEOUT_T)
609
 
    // 1 click: regular mode
610
 
    else if (event == EV_1click) {
611
 
        #ifdef USE_MANUAL_MEMORY
612
 
        if (manual_memory)
613
 
            set_state(steady_state, manual_memory);
614
 
        else
615
 
        #endif
616
 
        set_state(steady_state, memorized_level);
617
 
        return MISCHIEF_MANAGED;
618
 
    }
619
 
    // click, hold: go to highest level (ceiling) (for ramping down)
620
 
    else if (event == EV_click2_hold) {
621
 
        set_state(steady_state, MAX_LEVEL);
622
 
        return MISCHIEF_MANAGED;
623
 
    }
624
 
    // 2 clicks: highest mode (ceiling)
625
 
    else if (event == EV_2clicks) {
626
 
        set_state(steady_state, MAX_LEVEL);
627
 
        return MISCHIEF_MANAGED;
628
 
    }
629
 
    // 3 clicks (initial press): off, to prep for later events
630
 
    else if (event == EV_click3_press) {
631
 
        set_level(0);
632
 
        return MISCHIEF_MANAGED;
633
 
    }
634
 
    #ifdef USE_BATTCHECK
635
 
    // 3 clicks: battcheck mode / blinky mode group 1
636
 
    else if (event == EV_3clicks) {
637
 
        set_state(battcheck_state, 0);
638
 
        return MISCHIEF_MANAGED;
639
 
    }
640
 
    #endif
641
 
    // click, click, long-click: strobe mode
642
 
    #ifdef USE_STROBE_STATE
643
 
    else if (event == EV_click3_hold) {
644
 
        set_state(strobe_state, 0);
645
 
        return MISCHIEF_MANAGED;
646
 
    }
647
 
    #elif defined(USE_BORING_STROBE_STATE)
648
 
    else if (event == EV_click3_hold) {
649
 
        set_state(boring_strobe_state, 0);
650
 
        return MISCHIEF_MANAGED;
651
 
    }
652
 
    #endif
653
 
    // 4 clicks: soft lockout
654
 
    else if (event == EV_4clicks) {
655
 
        blink_confirm(2);
656
 
        set_state(lockout_state, 0);
657
 
        return MISCHIEF_MANAGED;
658
 
    }
659
 
    #ifdef USE_MOMENTARY_MODE
660
 
    // 5 clicks: momentary mode
661
 
    else if (event == EV_5clicks) {
662
 
        blink_confirm(1);
663
 
        set_state(momentary_state, 0);
664
 
        return MISCHIEF_MANAGED;
665
 
    }
666
 
    #endif
667
 
    #ifdef USE_MUGGLE_MODE
668
 
    // 6 clicks: muggle mode
669
 
    else if (event == EV_6clicks) {
670
 
        blink_confirm(1);
671
 
        set_state(muggle_state, 0);
672
 
        return MISCHIEF_MANAGED;
673
 
    }
674
 
    #endif
675
 
    #ifdef USE_INDICATOR_LED
676
 
    // 7 clicks: change indicator LED mode
677
 
    else if (event == EV_7clicks) {
678
 
        uint8_t mode = (indicator_led_mode & 3) + 1;
679
 
        #ifdef TICK_DURING_STANDBY
680
 
        mode = mode & 3;
681
 
        #else
682
 
        mode = mode % 3;
683
 
        #endif
684
 
        #ifdef INDICATOR_LED_SKIP_LOW
685
 
        if (mode == 1) { mode ++; }
686
 
        #endif
687
 
        indicator_led_mode = (indicator_led_mode & 0b11111100) | mode;
688
 
        indicator_led(mode);
689
 
        save_config();
690
 
        return MISCHIEF_MANAGED;
691
 
    }
692
 
    #elif defined(USE_AUX_RGB_LEDS)
693
 
    // 7 clicks: change RGB aux LED pattern
694
 
    else if (event == EV_7clicks) {
695
 
        uint8_t mode = (rgb_led_off_mode >> 4) + 1;
696
 
        mode = mode % RGB_LED_NUM_PATTERNS;
697
 
        rgb_led_off_mode = (mode << 4) | (rgb_led_off_mode & 0x0f);
698
 
        rgb_led_update(rgb_led_off_mode, 0);
699
 
        save_config();
700
 
        blink_confirm(1);
701
 
        return MISCHIEF_MANAGED;
702
 
    }
703
 
    // 7 clicks (hold last): change RGB aux LED color
704
 
    else if (event == EV_click7_hold) {
705
 
        setting_rgb_mode_now = 1;
706
 
        if (0 == (arg & 0x3f)) {
707
 
            uint8_t mode = (rgb_led_off_mode & 0x0f) + 1;
708
 
            mode = mode % RGB_LED_NUM_COLORS;
709
 
            rgb_led_off_mode = mode | (rgb_led_off_mode & 0xf0);
710
 
            //save_config();
711
 
        }
712
 
        rgb_led_update(rgb_led_off_mode, arg);
713
 
        return MISCHIEF_MANAGED;
714
 
    }
715
 
    else if (event == EV_click7_hold_release) {
716
 
        setting_rgb_mode_now = 0;
717
 
        save_config();
718
 
        return MISCHIEF_MANAGED;
719
 
    }
720
 
    #endif  // end 7 clicks
721
 
    #if defined(USE_TENCLICK_THERMAL_CONFIG) && defined(USE_THERMAL_REGULATION)
722
 
    // 10 clicks: thermal config mode
723
 
    else if (event == EV_10clicks) {
724
 
        push_state(thermal_config_state, 0);
725
 
        return MISCHIEF_MANAGED;
726
 
    }
727
 
    #endif
728
 
    #ifdef USE_VERSION_CHECK
729
 
    // 15+ clicks: show the version number
730
 
    else if (event == EV_15clicks) {
731
 
        set_state(version_check_state, 0);
732
 
        return MISCHIEF_MANAGED;
733
 
    }
734
 
    #endif
735
 
    #if defined(USE_FACTORY_RESET) && defined(USE_SOFT_FACTORY_RESET)
736
 
    // 13 clicks and hold the last click: invoke factory reset (reboot)
737
 
    else if (event == EV_click13_hold) {
738
 
        reboot();
739
 
        return MISCHIEF_MANAGED;
740
 
    }
741
 
    #endif
742
 
    return EVENT_NOT_HANDLED;
743
 
}
744
 
 
745
 
 
746
 
uint8_t steady_state(Event event, uint16_t arg) {
747
 
    uint8_t mode_min = ramp_smooth_floor;
748
 
    uint8_t mode_max = ramp_smooth_ceil;
749
 
    uint8_t ramp_step_size = 1;
750
 
    #ifdef USE_REVERSING
751
 
    static int8_t ramp_direction = 1;
752
 
    #endif
753
 
    #if (B_TIMING_OFF == B_RELEASE_T)
754
 
    // if the user double clicks, we need to abort turning off,
755
 
    // and this stores the level to return to
756
 
    static uint8_t level_before_off = 0;
757
 
    #endif
758
 
    if (ramp_style) {
759
 
        mode_min = ramp_discrete_floor;
760
 
        mode_max = ramp_discrete_ceil;
761
 
        ramp_step_size = ramp_discrete_step_size;
762
 
    }
763
 
 
764
 
    // turn LED on when we first enter the mode
765
 
    if ((event == EV_enter_state) || (event == EV_reenter_state)) {
766
 
        #if defined(USE_MOMENTARY_MODE) && defined(USE_STROBE_STATE)
767
 
        momentary_mode = 0;  // 0 = ramping, 1 = strobes
768
 
        #endif
769
 
        // if we just got back from config mode, go back to memorized level
770
 
        if (event == EV_reenter_state) {
771
 
            arg = memorized_level;
772
 
        }
773
 
        // remember this level, unless it's moon or turbo
774
 
        if ((arg > mode_min) && (arg < mode_max))
775
 
            memorized_level = arg;
776
 
        // use the requested level even if not memorized
777
 
        arg = nearest_level(arg);
778
 
        set_level_and_therm_target(arg);
779
 
        #ifdef USE_REVERSING
780
 
        ramp_direction = 1;
781
 
        #endif
782
 
        return MISCHIEF_MANAGED;
783
 
    }
784
 
    #if (B_TIMING_OFF == B_RELEASE_T)
785
 
    // 1 click (early): off, if configured for early response
786
 
    else if (event == EV_click1_release) {
787
 
        level_before_off = actual_level;
788
 
        set_level_and_therm_target(0);
789
 
        return MISCHIEF_MANAGED;
790
 
    }
791
 
    // 2 clicks (early): abort turning off, if configured for early response
792
 
    else if (event == EV_click2_press) {
793
 
        set_level_and_therm_target(level_before_off);
794
 
        return MISCHIEF_MANAGED;
795
 
    }
796
 
    #endif  // if (B_TIMING_OFF == B_RELEASE_T)
797
 
    // 1 click: off
798
 
    else if (event == EV_1click) {
799
 
        set_state(off_state, 0);
800
 
        return MISCHIEF_MANAGED;
801
 
    }
802
 
    // 2 clicks: go to/from highest level
803
 
    else if (event == EV_2clicks) {
804
 
        if (actual_level < MAX_LEVEL) {
805
 
            // true turbo, not the mode-specific ceiling
806
 
            set_level_and_therm_target(MAX_LEVEL);
807
 
        }
808
 
        else {
809
 
            set_level_and_therm_target(memorized_level);
810
 
        }
811
 
        return MISCHIEF_MANAGED;
812
 
    }
813
 
    // 3 clicks: toggle smooth vs discrete ramping
814
 
    else if (event == EV_3clicks) {
815
 
        ramp_style = !ramp_style;
816
 
        save_config();
817
 
        #ifdef START_AT_MEMORIZED_LEVEL
818
 
        save_config_wl();
819
 
        #endif
820
 
        blip();
821
 
        memorized_level = nearest_level(actual_level);
822
 
        set_level_and_therm_target(memorized_level);
823
 
        return MISCHIEF_MANAGED;
824
 
    }
825
 
    #ifdef USE_RAMP_CONFIG
826
 
    // 4 clicks: configure this ramp mode
827
 
    else if (event == EV_4clicks) {
828
 
        push_state(ramp_config_state, 0);
829
 
        return MISCHIEF_MANAGED;
830
 
    }
831
 
    #endif
832
 
    // hold: change brightness (brighter)
833
 
    else if (event == EV_click1_hold) {
834
 
        // ramp slower in discrete mode
835
 
        if (ramp_style  &&  (arg % HOLD_TIMEOUT != 0)) {
836
 
            return MISCHIEF_MANAGED;
837
 
        }
838
 
        #ifdef USE_REVERSING
839
 
        // fix ramp direction on first frame if necessary
840
 
        if (!arg) {
841
 
            // make it ramp down instead, if already at max
842
 
            if (actual_level >= mode_max) { ramp_direction = -1; }
843
 
            // make it ramp up if already at min
844
 
            // (off->hold->stepped_min->release causes this state)
845
 
            else if (actual_level <= mode_min) { ramp_direction = 1; }
846
 
        }
847
 
        // if the button is stuck, err on the side of safety and ramp down
848
 
        else if ((arg > TICKS_PER_SECOND * 5) && (actual_level >= mode_max)) {
849
 
            ramp_direction = -1;
850
 
        }
851
 
        // if the button is still stuck, lock the light
852
 
        else if ((arg > TICKS_PER_SECOND * 10) && (actual_level <= mode_min)) {
853
 
            blip();
854
 
            set_state(lockout_state, 0);
855
 
        }
856
 
        memorized_level = nearest_level((int16_t)actual_level \
857
 
                          + (ramp_step_size * ramp_direction));
858
 
        #else
859
 
        memorized_level = nearest_level((int16_t)actual_level + ramp_step_size);
860
 
        #endif
861
 
        #if defined(BLINK_AT_RAMP_CEILING) || defined(BLINK_AT_RAMP_MIDDLE)
862
 
        // only blink once for each threshold
863
 
        if ((memorized_level != actual_level) && (
864
 
                0  // for easier syntax below
865
 
                #ifdef BLINK_AT_RAMP_MIDDLE_1
866
 
                || (memorized_level == BLINK_AT_RAMP_MIDDLE_1)
867
 
                #endif
868
 
                #ifdef BLINK_AT_RAMP_MIDDLE_2
869
 
                || (memorized_level == BLINK_AT_RAMP_MIDDLE_2)
870
 
                #endif
871
 
                #ifdef BLINK_AT_RAMP_CEILING
872
 
                || (memorized_level == mode_max)
873
 
                #endif
874
 
                #if defined(USE_REVERSING) && defined(BLINK_AT_RAMP_FLOOR)
875
 
                || (memorized_level == mode_min)
876
 
                #endif
877
 
                )) {
878
 
            blip();
879
 
        }
880
 
        #endif
881
 
        #if defined(BLINK_AT_STEPS)
882
 
        uint8_t foo = ramp_style;
883
 
        ramp_style = 1;
884
 
        uint8_t nearest = nearest_level((int16_t)actual_level);
885
 
        ramp_style = foo;
886
 
        // only blink once for each threshold
887
 
        if ((memorized_level != actual_level) &&
888
 
                    (ramp_style == 0) &&
889
 
                    (memorized_level == nearest)
890
 
                    )
891
 
        {
892
 
            blip();
893
 
        }
894
 
        #endif
895
 
        set_level_and_therm_target(memorized_level);
896
 
        return MISCHIEF_MANAGED;
897
 
    }
898
 
    #if defined(USE_REVERSING) || defined(START_AT_MEMORIZED_LEVEL)
899
 
    // reverse ramp direction on hold release
900
 
    else if (event == EV_click1_hold_release) {
901
 
        #ifdef USE_REVERSING
902
 
        ramp_direction = -ramp_direction;
903
 
        #endif
904
 
        #ifdef START_AT_MEMORIZED_LEVEL
905
 
        save_config_wl();
906
 
        #endif
907
 
        return MISCHIEF_MANAGED;
908
 
    }
909
 
    #endif
910
 
    // click, hold: change brightness (dimmer)
911
 
    else if (event == EV_click2_hold) {
912
 
        #ifdef USE_REVERSING
913
 
        ramp_direction = 1;
914
 
        #endif
915
 
        // ramp slower in discrete mode
916
 
        if (ramp_style  &&  (arg % HOLD_TIMEOUT != 0)) {
917
 
            return MISCHIEF_MANAGED;
918
 
        }
919
 
        // TODO? make it ramp up instead, if already at min?
920
 
        memorized_level = nearest_level((int16_t)actual_level - ramp_step_size);
921
 
        #if defined(BLINK_AT_RAMP_FLOOR) || defined(BLINK_AT_RAMP_MIDDLE)
922
 
        // only blink once for each threshold
923
 
        if ((memorized_level != actual_level) && (
924
 
                0  // for easier syntax below
925
 
                #ifdef BLINK_AT_RAMP_MIDDLE_1
926
 
                || (memorized_level == BLINK_AT_RAMP_MIDDLE_1)
927
 
                #endif
928
 
                #ifdef BLINK_AT_RAMP_MIDDLE_2
929
 
                || (memorized_level == BLINK_AT_RAMP_MIDDLE_2)
930
 
                #endif
931
 
                #ifdef BLINK_AT_RAMP_FLOOR
932
 
                || (memorized_level == mode_min)
933
 
                #endif
934
 
                )) {
935
 
            blip();
936
 
        }
937
 
        #endif
938
 
        #if defined(BLINK_AT_STEPS)
939
 
        uint8_t foo = ramp_style;
940
 
        ramp_style = 1;
941
 
        uint8_t nearest = nearest_level((int16_t)actual_level);
942
 
        ramp_style = foo;
943
 
        // only blink once for each threshold
944
 
        if ((memorized_level != actual_level) &&
945
 
                    (ramp_style == 0) &&
946
 
                    (memorized_level == nearest)
947
 
                    )
948
 
        {
949
 
            blip();
950
 
        }
951
 
        #endif
952
 
        set_level_and_therm_target(memorized_level);
953
 
        return MISCHIEF_MANAGED;
954
 
    }
955
 
    #ifdef START_AT_MEMORIZED_LEVEL
956
 
    // click, release, hold, release: save new ramp level (if necessary)
957
 
    else if (event == EV_click2_hold_release) {
958
 
        save_config_wl();
959
 
        return MISCHIEF_MANAGED;
960
 
    }
961
 
    #endif
962
 
    #ifdef USE_MANUAL_MEMORY
963
 
    else if (event == EV_5clicks) {
964
 
        manual_memory = actual_level;
965
 
        save_config();
966
 
        blip();
967
 
    }
968
 
    else if (event == EV_click5_hold) {
969
 
        if (0 == arg) {
970
 
            manual_memory = 0;
971
 
            save_config();
972
 
            blip();
973
 
        }
974
 
    }
975
 
    #endif
976
 
    #if defined(USE_SET_LEVEL_GRADUALLY) || defined(USE_REVERSING)
977
 
    else if (event == EV_tick) {
978
 
        #ifdef USE_REVERSING
979
 
        // un-reverse after 1 second
980
 
        if (arg == TICKS_PER_SECOND) ramp_direction = 1;
981
 
        #endif
982
 
        #ifdef USE_SET_LEVEL_GRADUALLY
983
 
        int16_t diff = gradual_target - actual_level;
984
 
        static uint16_t ticks_since_adjust = 0;
985
 
        ticks_since_adjust++;
986
 
        if (diff) {
987
 
            uint16_t ticks_per_adjust = 256;
988
 
            if (diff < 0) {
989
 
                //diff = -diff;
990
 
                if (actual_level > THERM_FASTER_LEVEL) {
991
 
                    #ifdef THERM_HARD_TURBO_DROP
992
 
                    ticks_per_adjust >>= 2;
993
 
                    #endif
994
 
                    ticks_per_adjust >>= 2;
995
 
                }
996
 
            } else {
997
 
                // rise at half speed
998
 
                ticks_per_adjust <<= 1;
999
 
            }
1000
 
            while (diff) {
1001
 
                ticks_per_adjust >>= 1;
1002
 
                //diff >>= 1;
1003
 
                diff /= 2;  // because shifting produces weird behavior
1004
 
            }
1005
 
            if (ticks_since_adjust > ticks_per_adjust)
1006
 
            {
1007
 
                gradual_tick();
1008
 
                ticks_since_adjust = 0;
1009
 
            }
1010
 
        }
1011
 
        #endif  // ifdef USE_SET_LEVEL_GRADUALLY
1012
 
        return MISCHIEF_MANAGED;
1013
 
    }
1014
 
    #endif
1015
 
    #ifdef USE_THERMAL_REGULATION
1016
 
    // overheating: drop by an amount proportional to how far we are above the ceiling
1017
 
    else if (event == EV_temperature_high) {
1018
 
        #if 0
1019
 
        blip();
1020
 
        #endif
1021
 
        #ifdef THERM_HARD_TURBO_DROP
1022
 
        //if (actual_level > THERM_FASTER_LEVEL) {
1023
 
        if (actual_level == MAX_LEVEL) {
1024
 
            #ifdef USE_SET_LEVEL_GRADUALLY
1025
 
            set_level_gradually(THERM_FASTER_LEVEL);
1026
 
            target_level = THERM_FASTER_LEVEL;
1027
 
            #else
1028
 
            set_level_and_therm_target(THERM_FASTER_LEVEL);
1029
 
            #endif
1030
 
        } else
1031
 
        #endif
1032
 
        if (actual_level > MIN_THERM_STEPDOWN) {
1033
 
            int16_t stepdown = actual_level - arg;
1034
 
            if (stepdown < MIN_THERM_STEPDOWN) stepdown = MIN_THERM_STEPDOWN;
1035
 
            else if (stepdown > MAX_LEVEL) stepdown = MAX_LEVEL;
1036
 
            #ifdef USE_SET_LEVEL_GRADUALLY
1037
 
            set_level_gradually(stepdown);
1038
 
            #else
1039
 
            set_level(stepdown);
1040
 
            #endif
1041
 
        }
1042
 
        return MISCHIEF_MANAGED;
1043
 
    }
1044
 
    // underheating: increase slowly if we're lower than the target
1045
 
    //               (proportional to how low we are)
1046
 
    else if (event == EV_temperature_low) {
1047
 
        #if 0
1048
 
        blip();
1049
 
        #endif
1050
 
        if (actual_level < target_level) {
1051
 
            //int16_t stepup = actual_level + (arg>>1);
1052
 
            int16_t stepup = actual_level + arg;
1053
 
            if (stepup > target_level) stepup = target_level;
1054
 
            else if (stepup < MIN_THERM_STEPDOWN) stepup = MIN_THERM_STEPDOWN;
1055
 
            #ifdef USE_SET_LEVEL_GRADUALLY
1056
 
            set_level_gradually(stepup);
1057
 
            #else
1058
 
            set_level(stepup);
1059
 
            #endif
1060
 
        }
1061
 
        return MISCHIEF_MANAGED;
1062
 
    }
1063
 
    #ifdef USE_SET_LEVEL_GRADUALLY
1064
 
    // temperature is within target window
1065
 
    // (so stop trying to adjust output)
1066
 
    else if (event == EV_temperature_okay) {
1067
 
        // if we're still adjusting output...  stop after the current step
1068
 
        if (gradual_target > actual_level)
1069
 
            gradual_target = actual_level + 1;
1070
 
        else if (gradual_target < actual_level)
1071
 
            gradual_target = actual_level - 1;
1072
 
        return MISCHIEF_MANAGED;
1073
 
    }
1074
 
    #endif  // ifdef USE_SET_LEVEL_GRADUALLY
1075
 
    #endif  // ifdef USE_THERMAL_REGULATION
1076
 
    return EVENT_NOT_HANDLED;
1077
 
}
1078
 
 
1079
 
 
1080
 
#ifdef USE_TINT_RAMPING
1081
 
uint8_t tint_ramping_state(Event event, uint16_t arg) {
1082
 
    static int8_t tint_ramp_direction = 1;
1083
 
    static uint8_t prev_tint = 0;
1084
 
    // don't activate auto-tint modes unless the user hits the edge
1085
 
    // and keeps pressing for a while
1086
 
    static uint8_t past_edge_counter = 0;
1087
 
    // bugfix: click-click-hold from off to strobes would invoke tint ramping
1088
 
    // in addition to changing state...  so ignore any tint-ramp events which
1089
 
    // don't look like they were meant to be here
1090
 
    static uint8_t active = 0;
1091
 
 
1092
 
    // click, click, hold: change the tint
1093
 
    if (event == EV_click3_hold) {
1094
 
        // reset at beginning of movement
1095
 
        if (! arg) {
1096
 
            active = 1;  // first frame means this is for us
1097
 
            past_edge_counter = 0;  // doesn't start until user hits the edge
1098
 
        }
1099
 
        // ignore event if we weren't the ones who handled the first frame
1100
 
        if (! active) return EVENT_HANDLED;
1101
 
 
1102
 
        // change normal tints
1103
 
        if ((tint_ramp_direction > 0) && (tint < 254)) {
1104
 
            tint += 1;
1105
 
        }
1106
 
        else if ((tint_ramp_direction < 0) && (tint > 1)) {
1107
 
            tint -= 1;
1108
 
        }
1109
 
        // if the user kept pressing long enough, go the final step
1110
 
        if (past_edge_counter == 64) {
1111
 
            past_edge_counter ++;
1112
 
            tint ^= 1;  // 0 -> 1, 254 -> 255
1113
 
            blip();
1114
 
        }
1115
 
        // if tint change stalled, let user know we hit the edge
1116
 
        else if (prev_tint == tint) {
1117
 
            if (past_edge_counter == 0) blip();
1118
 
            // count up but don't wrap back to zero
1119
 
            if (past_edge_counter < 255) past_edge_counter ++;
1120
 
        }
1121
 
        prev_tint = tint;
1122
 
        set_level(actual_level);
1123
 
        return EVENT_HANDLED;
1124
 
    }
1125
 
 
1126
 
    // click, click, hold, release: reverse direction for next ramp
1127
 
    else if (event == EV_click3_hold_release) {
1128
 
        active = 0;  // ignore next hold if it wasn't meant for us
1129
 
        // reverse
1130
 
        tint_ramp_direction = -tint_ramp_direction;
1131
 
        if (tint == 0) tint_ramp_direction = 1;
1132
 
        else if (tint == 255) tint_ramp_direction = -1;
1133
 
        // remember tint after battery change
1134
 
        save_config();
1135
 
        return EVENT_HANDLED;
1136
 
    }
1137
 
 
1138
 
    return EVENT_NOT_HANDLED;
1139
 
}
1140
 
#endif  // ifdef USE_TINT_RAMPING
1141
 
 
1142
 
 
1143
 
#ifdef USE_STROBE_STATE
1144
 
uint8_t strobe_state(Event event, uint16_t arg) {
1145
 
    static int8_t ramp_direction = 1;
1146
 
 
1147
 
    // 'st' reduces ROM size by avoiding access to a volatile var
1148
 
    // (maybe I should just make it nonvolatile?)
1149
 
    strobe_mode_te st = strobe_type;
1150
 
 
1151
 
    #ifdef USE_MOMENTARY_MODE
1152
 
    momentary_mode = 1;  // 0 = ramping, 1 = strobes
1153
 
    #endif
1154
 
 
1155
 
    #ifdef USE_CANDLE_MODE
1156
 
    // pass all events to candle mode, when it's active
1157
 
    // (the code is in its own pseudo-state to keep things cleaner)
1158
 
    if (st == candle_mode_e) {
1159
 
        candle_mode_state(event, arg);
1160
 
    }
1161
 
    #endif
1162
 
 
1163
 
    if (0) {}  // placeholder
1164
 
    // init anything which needs to be initialized
1165
 
    else if (event == EV_enter_state) {
1166
 
        ramp_direction = 1;
1167
 
        return MISCHIEF_MANAGED;
1168
 
    }
1169
 
    // 1 click: off
1170
 
    else if (event == EV_1click) {
1171
 
        set_state(off_state, 0);
1172
 
        return MISCHIEF_MANAGED;
1173
 
    }
1174
 
    // 2 clicks: rotate through strobe/flasher modes
1175
 
    else if (event == EV_2clicks) {
1176
 
        strobe_type = (st + 1) % NUM_STROBES;
1177
 
        save_config();
1178
 
        return MISCHIEF_MANAGED;
1179
 
    }
1180
 
    // hold: change speed (go faster)
1181
 
    //       or change brightness (brighter)
1182
 
    else if (event == EV_click1_hold) {
1183
 
        if (0) {}  // placeholder
1184
 
 
1185
 
        // party / tactical strobe faster
1186
 
        #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
1187
 
        #ifdef USE_TACTICAL_STROBE_MODE
1188
 
        else if (st <= tactical_strobe_e) {
1189
 
        #else
1190
 
        else if (st == party_strobe_e) {
1191
 
        #endif
1192
 
            if ((arg & 1) == 0) {
1193
 
                uint8_t d = strobe_delays[st];
1194
 
                d -= ramp_direction;
1195
 
                if (d < 8) d = 8;
1196
 
                else if (d > 254) d = 254;
1197
 
                strobe_delays[st] = d;
1198
 
            }
1199
 
        }
1200
 
        #endif
1201
 
 
1202
 
        // lightning has no adjustments
1203
 
        //else if (st == lightning_storm_e) {}
1204
 
 
1205
 
        // biking mode brighter
1206
 
        #ifdef USE_BIKE_FLASHER_MODE
1207
 
        else if (st == bike_flasher_e) {
1208
 
            bike_flasher_brightness += ramp_direction;
1209
 
            if (bike_flasher_brightness < 2) bike_flasher_brightness = 2;
1210
 
            else if (bike_flasher_brightness > MAX_BIKING_LEVEL) bike_flasher_brightness = MAX_BIKING_LEVEL;
1211
 
            set_level(bike_flasher_brightness);
1212
 
        }
1213
 
        #endif
1214
 
 
1215
 
        return MISCHIEF_MANAGED;
1216
 
    }
1217
 
    // reverse ramp direction on hold release
1218
 
    // ... and save new strobe settings
1219
 
    else if (event == EV_click1_hold_release) {
1220
 
        ramp_direction = -ramp_direction;
1221
 
        save_config();
1222
 
        return MISCHIEF_MANAGED;
1223
 
    }
1224
 
    // click, hold: change speed (go slower)
1225
 
    //       or change brightness (dimmer)
1226
 
    else if (event == EV_click2_hold) {
1227
 
        ramp_direction = 1;
1228
 
 
1229
 
        if (0) {}  // placeholder
1230
 
 
1231
 
        // party / tactical strobe slower
1232
 
        #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
1233
 
        #ifdef USE_TACTICAL_STROBE_MODE
1234
 
        else if (st <= tactical_strobe_e) {
1235
 
        #else
1236
 
        else if (st == party_strobe_e) {
1237
 
        #endif
1238
 
            if ((arg & 1) == 0) {
1239
 
                if (strobe_delays[st] < 255) strobe_delays[st] ++;
1240
 
            }
1241
 
        }
1242
 
        #endif
1243
 
 
1244
 
        // lightning has no adjustments
1245
 
        //else if (st == lightning_storm_e) {}
1246
 
 
1247
 
        // biking mode dimmer
1248
 
        #ifdef USE_BIKE_FLASHER_MODE
1249
 
        else if (st == bike_flasher_e) {
1250
 
            if (bike_flasher_brightness > 2)
1251
 
                bike_flasher_brightness --;
1252
 
            set_level(bike_flasher_brightness);
1253
 
        }
1254
 
        #endif
1255
 
 
1256
 
        return MISCHIEF_MANAGED;
1257
 
    }
1258
 
    // release hold: save new strobe settings
1259
 
    else if (event == EV_click2_hold_release) {
1260
 
        save_config();
1261
 
        return MISCHIEF_MANAGED;
1262
 
    }
1263
 
    #if defined(USE_LIGHTNING_MODE) || defined(USE_CANDLE_MODE)
1264
 
    // clock tick: bump the random seed
1265
 
    else if (event == EV_tick) {
1266
 
        // un-reverse after 1 second
1267
 
        if (arg == TICKS_PER_SECOND) ramp_direction = 1;
1268
 
 
1269
 
        pseudo_rand_seed += arg;
1270
 
        return MISCHIEF_MANAGED;
1271
 
    }
1272
 
    #endif
1273
 
    return EVENT_NOT_HANDLED;
1274
 
}
1275
 
 
1276
 
#if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
1277
 
inline void party_tactical_strobe_mode_iter(uint8_t st) {
1278
 
    // one iteration of main loop()
1279
 
    uint8_t del = strobe_delays[st];
1280
 
    // TODO: make tac strobe brightness configurable?
1281
 
    set_level(STROBE_BRIGHTNESS);
1282
 
    if (0) {}  // placeholde0
1283
 
    #ifdef USE_PARTY_STROBE_MODE
1284
 
    else if (st == party_strobe_e) {  // party strobe
1285
 
        #ifdef PARTY_STROBE_ONTIME
1286
 
        nice_delay_ms(PARTY_STROBE_ONTIME);
1287
 
        #else
1288
 
        if (del < 42) delay_zero();
1289
 
        else nice_delay_ms(1);
1290
 
        #endif
1291
 
    }
1292
 
    #endif
1293
 
    #ifdef USE_TACTICAL_STROBE_MODE
1294
 
    else {  //tactical strobe
1295
 
        nice_delay_ms(del >> 1);
1296
 
    }
1297
 
    #endif
1298
 
    set_level(0);
1299
 
    nice_delay_ms(del);  // no return check necessary on final delay
1300
 
}
1301
 
#endif
1302
 
 
1303
 
#ifdef USE_LIGHTNING_MODE
1304
 
inline void lightning_storm_iter() {
1305
 
    // one iteration of main loop()
1306
 
    int16_t brightness;
1307
 
    uint16_t rand_time;
1308
 
 
1309
 
    // turn the emitter on at a random level,
1310
 
    // for a random amount of time between 1ms and 32ms
1311
 
    //rand_time = 1 << (pseudo_rand() % 7);
1312
 
    rand_time = pseudo_rand() & 63;
1313
 
    brightness = 1 << (pseudo_rand() % 7);  // 1, 2, 4, 8, 16, 32, 64
1314
 
    brightness += 1 << (pseudo_rand() % 5);  // 2 to 80 now
1315
 
    brightness += pseudo_rand() % brightness;  // 2 to 159 now (w/ low bias)
1316
 
    if (brightness > MAX_LEVEL) brightness = MAX_LEVEL;
1317
 
    set_level(brightness);
1318
 
    nice_delay_ms(rand_time);
1319
 
 
1320
 
    // decrease the brightness somewhat more gradually, like lightning
1321
 
    uint8_t stepdown = brightness >> 3;
1322
 
    if (stepdown < 1) stepdown = 1;
1323
 
    while(brightness > 1) {
1324
 
        nice_delay_ms(rand_time);
1325
 
        brightness -= stepdown;
1326
 
        if (brightness < 0) brightness = 0;
1327
 
        set_level(brightness);
1328
 
        /*
1329
 
           if ((brightness < MAX_LEVEL/2) && (! (pseudo_rand() & 15))) {
1330
 
           brightness <<= 1;
1331
 
           set_level(brightness);
1332
 
           }
1333
 
           */
1334
 
        if (! (pseudo_rand() & 3)) {
1335
 
            nice_delay_ms(rand_time);
1336
 
            set_level(brightness>>1);
1337
 
        }
1338
 
    }
1339
 
 
1340
 
    // turn the emitter off,
1341
 
    // for a random amount of time between 1ms and 8192ms
1342
 
    // (with a low bias)
1343
 
    rand_time = 1 << (pseudo_rand() % 13);
1344
 
    rand_time += pseudo_rand() % rand_time;
1345
 
    set_level(0);
1346
 
    nice_delay_ms(rand_time);  // no return check necessary on final delay
1347
 
}
1348
 
#endif
1349
 
 
1350
 
#ifdef USE_BIKE_FLASHER_MODE
1351
 
inline void bike_flasher_iter() {
1352
 
    // one iteration of main loop()
1353
 
    uint8_t burst = bike_flasher_brightness << 1;
1354
 
    if (burst > MAX_LEVEL) burst = MAX_LEVEL;
1355
 
    for(uint8_t i=0; i<4; i++) {
1356
 
        set_level(burst);
1357
 
        nice_delay_ms(5);
1358
 
        set_level(bike_flasher_brightness);
1359
 
        nice_delay_ms(65);
1360
 
    }
1361
 
    nice_delay_ms(720);  // no return check necessary on final delay
1362
 
}
1363
 
#endif
1364
 
 
1365
 
#endif  // ifdef USE_STROBE_STATE
1366
 
 
1367
 
#ifdef USE_CANDLE_MODE
1368
 
uint8_t candle_mode_state(Event event, uint16_t arg) {
1369
 
    static int8_t ramp_direction = 1;
1370
 
    #define MAX_CANDLE_LEVEL (RAMP_LENGTH-CANDLE_AMPLITUDE-15)
1371
 
    static uint8_t candle_wave1 = 0;
1372
 
    static uint8_t candle_wave2 = 0;
1373
 
    static uint8_t candle_wave3 = 0;
1374
 
    static uint8_t candle_wave2_speed = 0;
1375
 
    // these should add up to 100
1376
 
    #define CANDLE_WAVE1_MAXDEPTH 30
1377
 
    #define CANDLE_WAVE2_MAXDEPTH 45
1378
 
    #define CANDLE_WAVE3_MAXDEPTH 25
1379
 
    static const uint8_t candle_wave1_depth = CANDLE_WAVE1_MAXDEPTH * CANDLE_AMPLITUDE / 100;
1380
 
    static uint8_t candle_wave2_depth       = CANDLE_WAVE2_MAXDEPTH * CANDLE_AMPLITUDE / 100;
1381
 
    static uint8_t candle_wave3_depth       = CANDLE_WAVE3_MAXDEPTH * CANDLE_AMPLITUDE / 100;
1382
 
    static uint8_t candle_mode_brightness = 24;
1383
 
    static uint8_t candle_mode_timer = 0;
1384
 
    #define TICKS_PER_CANDLE_MINUTE 4096 // about 65 seconds
1385
 
    #define MINUTES_PER_CANDLE_HALFHOUR 27 // ish
1386
 
 
1387
 
    if (event == EV_enter_state) {
1388
 
        candle_mode_timer = 0;  // in case any time was left over from earlier
1389
 
        ramp_direction = 1;
1390
 
        return MISCHIEF_MANAGED;
1391
 
    }
1392
 
    // 2 clicks: cancel timer
1393
 
    else if (event == EV_2clicks) {
1394
 
        // parent state just rotated through strobe/flasher modes,
1395
 
        // so cancel timer...  in case any time was left over from earlier
1396
 
        candle_mode_timer = 0;
1397
 
        return MISCHIEF_MANAGED;
1398
 
    }
1399
 
    // hold: change brightness (brighter)
1400
 
    else if (event == EV_click1_hold) {
1401
 
        // ramp away from extremes
1402
 
        if (! arg) {
1403
 
            if (candle_mode_brightness >= MAX_CANDLE_LEVEL) { ramp_direction = -1; }
1404
 
            else if (candle_mode_brightness <= 1) { ramp_direction = 1; }
1405
 
        }
1406
 
        // change brightness, but not too far
1407
 
        candle_mode_brightness += ramp_direction;
1408
 
        if (candle_mode_brightness < 1) candle_mode_brightness = 1;
1409
 
        else if (candle_mode_brightness > MAX_CANDLE_LEVEL) candle_mode_brightness = MAX_CANDLE_LEVEL;
1410
 
        return MISCHIEF_MANAGED;
1411
 
    }
1412
 
    // reverse ramp direction on hold release
1413
 
    else if (event == EV_click1_hold_release) {
1414
 
        ramp_direction = -ramp_direction;
1415
 
        return MISCHIEF_MANAGED;
1416
 
    }
1417
 
    // click, hold: change brightness (dimmer)
1418
 
    else if (event == EV_click2_hold) {
1419
 
        ramp_direction = 1;
1420
 
        if (candle_mode_brightness > 1)
1421
 
            candle_mode_brightness --;
1422
 
        return MISCHIEF_MANAGED;
1423
 
    }
1424
 
    // 3 clicks: add 30m to candle timer
1425
 
    else if (event == EV_3clicks) {
1426
 
        if (candle_mode_timer < (255 - MINUTES_PER_CANDLE_HALFHOUR)) {
1427
 
            // add 30m to the timer
1428
 
            candle_mode_timer += MINUTES_PER_CANDLE_HALFHOUR;
1429
 
            // blink to confirm
1430
 
            set_level(actual_level + 32);
1431
 
            delay_4ms(2);
1432
 
        }
1433
 
        return MISCHIEF_MANAGED;
1434
 
    }
1435
 
    // clock tick: animate candle brightness
1436
 
    else if (event == EV_tick) {
1437
 
        // un-reverse after 1 second
1438
 
        if (arg == TICKS_PER_SECOND) ramp_direction = 1;
1439
 
 
1440
 
        // self-timer dims the light during the final minute
1441
 
        uint8_t subtract = 0;
1442
 
        if (candle_mode_timer == 1) {
1443
 
            subtract = ((candle_mode_brightness+CANDLE_AMPLITUDE)
1444
 
                     * ((arg & (TICKS_PER_CANDLE_MINUTE-1)) >> 4))
1445
 
                     >> 8;
1446
 
        }
1447
 
        // we passed a minute mark, decrease timer if it's running
1448
 
        if ((arg & (TICKS_PER_CANDLE_MINUTE-1)) == (TICKS_PER_CANDLE_MINUTE - 1)) {
1449
 
            if (candle_mode_timer > 0) {
1450
 
                candle_mode_timer --;
1451
 
                //set_level(0);  delay_4ms(2);
1452
 
                // if the timer ran out, shut off
1453
 
                if (! candle_mode_timer) {
1454
 
                    set_state(off_state, 0);
1455
 
                }
1456
 
            }
1457
 
        }
1458
 
        // 3-oscillator synth for a relatively organic pattern
1459
 
        uint8_t add;
1460
 
        add = ((triangle_wave(candle_wave1) * candle_wave1_depth) >> 8)
1461
 
            + ((triangle_wave(candle_wave2) * candle_wave2_depth) >> 8)
1462
 
            + ((triangle_wave(candle_wave3) * candle_wave3_depth) >> 8);
1463
 
        int8_t brightness = candle_mode_brightness + add - subtract;
1464
 
        if (brightness < 0) { brightness = 0; }
1465
 
        set_level(brightness);
1466
 
 
1467
 
        // wave1: slow random LFO
1468
 
        // TODO: make wave slower and more erratic?
1469
 
        if ((arg & 1) == 0) candle_wave1 += pseudo_rand() & 1;
1470
 
        // wave2: medium-speed erratic LFO
1471
 
        candle_wave2 += candle_wave2_speed;
1472
 
        // wave3: erratic fast wave
1473
 
        candle_wave3 += pseudo_rand() % 37;
1474
 
        // S&H on wave2 frequency to make it more erratic
1475
 
        if ((pseudo_rand() & 0b00111111) == 0)
1476
 
            candle_wave2_speed = pseudo_rand() % 13;
1477
 
        // downward sawtooth on wave2 depth to simulate stabilizing
1478
 
        if ((candle_wave2_depth > 0) && ((pseudo_rand() & 0b00111111) == 0))
1479
 
            candle_wave2_depth --;
1480
 
        // random sawtooth retrigger
1481
 
        if (pseudo_rand() == 0) {
1482
 
            // random amplitude
1483
 
            //candle_wave2_depth = 2 + (pseudo_rand() % ((CANDLE_WAVE2_MAXDEPTH * CANDLE_AMPLITUDE / 100) - 2));
1484
 
            candle_wave2_depth = pseudo_rand() % (CANDLE_WAVE2_MAXDEPTH * CANDLE_AMPLITUDE / 100);
1485
 
            //candle_wave3_depth = 5;
1486
 
            candle_wave2 = 0;
1487
 
        }
1488
 
        // downward sawtooth on wave3 depth to simulate stabilizing
1489
 
        if ((candle_wave3_depth > 2) && ((pseudo_rand() & 0b00011111) == 0))
1490
 
            candle_wave3_depth --;
1491
 
        if ((pseudo_rand() & 0b01111111) == 0)
1492
 
            // random amplitude
1493
 
            //candle_wave3_depth = 2 + (pseudo_rand() % ((CANDLE_WAVE3_MAXDEPTH * CANDLE_AMPLITUDE / 100) - 2));
1494
 
            candle_wave3_depth = pseudo_rand() % (CANDLE_WAVE3_MAXDEPTH * CANDLE_AMPLITUDE / 100);
1495
 
        return MISCHIEF_MANAGED;
1496
 
    }
1497
 
    return EVENT_NOT_HANDLED;
1498
 
}
1499
 
#endif  // #ifdef USE_CANDLE_MODE
1500
 
 
1501
 
 
1502
 
#ifdef USE_BORING_STROBE_STATE
1503
 
uint8_t boring_strobe_state(Event event, uint16_t arg) {
1504
 
    // police strobe and SOS, meh
1505
 
    // 'st' reduces ROM size by avoiding access to a volatile var
1506
 
    // (maybe I should just make it nonvolatile?)
1507
 
    uint8_t st = boring_strobe_type;
1508
 
 
1509
 
    if (event == EV_enter_state) {
1510
 
        return MISCHIEF_MANAGED;
1511
 
    }
1512
 
    // 1 click: off
1513
 
    else if (event == EV_1click) {
1514
 
        // reset to police strobe for next time
1515
 
        boring_strobe_type = 0;
1516
 
        set_state(off_state, 0);
1517
 
        return MISCHIEF_MANAGED;
1518
 
    }
1519
 
    // 2 clicks: rotate through strobe/flasher modes
1520
 
    else if (event == EV_2clicks) {
1521
 
        boring_strobe_type = (st + 1) % NUM_BORING_STROBES;
1522
 
        return MISCHIEF_MANAGED;
1523
 
    }
1524
 
    return EVENT_NOT_HANDLED;
1525
 
}
1526
 
 
1527
 
#ifdef USE_POLICE_STROBE_MODE
1528
 
inline void police_strobe_iter() {
1529
 
    // one iteration of main loop()
1530
 
    // flash at 16 Hz then 8 Hz, 8 times each
1531
 
    for (uint8_t del=41; del<100; del+=41) {
1532
 
        for (uint8_t f=0; f<8; f++) {
1533
 
            set_level(STROBE_BRIGHTNESS);
1534
 
            nice_delay_ms(del >> 1);
1535
 
            set_level(0);
1536
 
            nice_delay_ms(del);
1537
 
        }
1538
 
    }
1539
 
}
1540
 
#endif
1541
 
#endif  // #ifdef USE_BORING_STROBE_STATE
1542
 
 
1543
 
#ifdef USE_SOS_MODE
1544
 
#ifdef USE_SOS_MODE_IN_BLINKY_GROUP
1545
 
uint8_t sos_state(Event event, uint16_t arg) {
1546
 
    // 1 click: off
1547
 
    if (event == EV_1click) {
1548
 
        set_state(off_state, 0);
1549
 
        return MISCHIEF_MANAGED;
1550
 
    }
1551
 
    // 2 clicks: next mode
1552
 
    else if (event == EV_2clicks) {
1553
 
        #ifdef USE_THERMAL_REGULATION
1554
 
        set_state(tempcheck_state, 0);
1555
 
        #else
1556
 
        set_state(battcheck_state, 0);
1557
 
        #endif
1558
 
        return MISCHIEF_MANAGED;
1559
 
    }
1560
 
    return EVENT_NOT_HANDLED;
1561
 
}
1562
 
#endif
1563
 
 
1564
 
void sos_blink(uint8_t num, uint8_t dah) {
1565
 
    #define DIT_LENGTH 200
1566
 
    for (; num > 0; num--) {
1567
 
        set_level(memorized_level);
1568
 
        nice_delay_ms(DIT_LENGTH);
1569
 
        if (dah) {  // dah is 3X as long as a dit
1570
 
            nice_delay_ms(DIT_LENGTH*2);
1571
 
        }
1572
 
        set_level(0);
1573
 
        // one "off" dit between blinks
1574
 
        nice_delay_ms(DIT_LENGTH);
1575
 
    }
1576
 
    // three "off" dits (or one "dah") between letters
1577
 
    // (except for SOS, which is collectively treated as a single "letter")
1578
 
    //nice_delay_ms(DIT_LENGTH*2);
1579
 
}
1580
 
 
1581
 
inline void sos_mode_iter() {
1582
 
    // one iteration of main loop()
1583
 
    //nice_delay_ms(1000);
1584
 
    sos_blink(3, 0);  // S
1585
 
    sos_blink(3, 1);  // O
1586
 
    sos_blink(3, 0);  // S
1587
 
    nice_delay_ms(2000);
1588
 
}
1589
 
#endif  // #ifdef USE_SOS_MODE
1590
 
 
1591
 
 
1592
 
#ifdef USE_BATTCHECK
1593
 
uint8_t battcheck_state(Event event, uint16_t arg) {
1594
 
    // 1 click: off
1595
 
    if (event == EV_1click) {
1596
 
        set_state(off_state, 0);
1597
 
        return MISCHIEF_MANAGED;
1598
 
    }
1599
 
    #ifdef USE_GOODNIGHT_MODE
1600
 
    // 2 clicks: goodnight mode
1601
 
    else if (event == EV_2clicks) {
1602
 
        set_state(goodnight_state, 0);
1603
 
        return MISCHIEF_MANAGED;
1604
 
    }
1605
 
    #elif defined(USE_BEACON_MODE)
1606
 
    // 2 clicks: beacon mode
1607
 
    else if (event == EV_2clicks) {
1608
 
        set_state(beacon_state, 0);
1609
 
        return MISCHIEF_MANAGED;
1610
 
    }
1611
 
    #elif defined(USE_THERMAL_REGULATION)
1612
 
    // 2 clicks: tempcheck mode
1613
 
    else if (event == EV_2clicks) {
1614
 
        set_state(tempcheck_state, 0);
1615
 
        return MISCHIEF_MANAGED;
1616
 
    }
1617
 
    #endif
1618
 
    return EVENT_NOT_HANDLED;
1619
 
}
1620
 
#endif
1621
 
 
1622
 
 
1623
 
#ifdef USE_THERMAL_REGULATION
1624
 
uint8_t tempcheck_state(Event event, uint16_t arg) {
1625
 
    // 1 click: off
1626
 
    if (event == EV_1click) {
1627
 
        set_state(off_state, 0);
1628
 
        return MISCHIEF_MANAGED;
1629
 
    }
1630
 
    #ifdef USE_BATTCHECK
1631
 
    // 2 clicks: battcheck mode
1632
 
    else if (event == EV_2clicks) {
1633
 
        set_state(battcheck_state, 0);
1634
 
        return MISCHIEF_MANAGED;
1635
 
    }
1636
 
    #endif
1637
 
    // 4 clicks: thermal config mode
1638
 
    else if (event == EV_4clicks) {
1639
 
        push_state(thermal_config_state, 0);
1640
 
        return MISCHIEF_MANAGED;
1641
 
    }
1642
 
    return EVENT_NOT_HANDLED;
1643
 
}
1644
 
#endif
1645
 
 
1646
 
 
1647
 
#ifdef USE_BEACON_MODE
1648
 
uint8_t beacon_state(Event event, uint16_t arg) {
1649
 
    // 1 click: off
1650
 
    if (event == EV_1click) {
1651
 
        set_state(off_state, 0);
1652
 
        return MISCHIEF_MANAGED;
1653
 
    }
1654
 
    // TODO: use sleep ticks to measure time between pulses,
1655
 
    //       to save power
1656
 
    // 2 clicks: next mode
1657
 
    else if (event == EV_2clicks) {
1658
 
        #ifdef USE_SOS_MODE_IN_BLINKY_GROUP
1659
 
        set_state(sos_state, 0);
1660
 
        #elif defined(USE_THERMAL_REGULATION)
1661
 
        set_state(tempcheck_state, 0);
1662
 
        #elif defined(USE_BATTCHECK)
1663
 
        set_state(battcheck_state, 0);
1664
 
        #endif
1665
 
        return MISCHIEF_MANAGED;
1666
 
    }
1667
 
    // 4 clicks: beacon config mode
1668
 
    else if (event == EV_4clicks) {
1669
 
        push_state(beacon_config_state, 0);
1670
 
        return MISCHIEF_MANAGED;
1671
 
    }
1672
 
    return EVENT_NOT_HANDLED;
1673
 
}
1674
 
#endif  // #ifdef USE_BEACON_MODE
1675
 
 
1676
 
 
1677
 
#ifdef USE_GOODNIGHT_MODE
1678
 
#define GOODNIGHT_TICKS_PER_STEPDOWN (GOODNIGHT_TIME*TICKS_PER_SECOND*60L/GOODNIGHT_LEVEL)
1679
 
uint8_t goodnight_state(Event event, uint16_t arg) {
1680
 
    static uint16_t ticks_since_stepdown = 0;
1681
 
    // blink on start
1682
 
    if (event == EV_enter_state) {
1683
 
        ticks_since_stepdown = 0;
1684
 
        blink_confirm(2);
1685
 
        set_level(GOODNIGHT_LEVEL);
1686
 
        return MISCHIEF_MANAGED;
1687
 
    }
1688
 
    // 1 click: off
1689
 
    else if (event == EV_1click) {
1690
 
        set_state(off_state, 0);
1691
 
        return MISCHIEF_MANAGED;
1692
 
    }
1693
 
    // 2 clicks: next mode
1694
 
    else if (event == EV_2clicks) {
1695
 
        #ifdef USE_BEACON_MODE
1696
 
        set_state(beacon_state, 0);
1697
 
        #elif defined(USE_SOS_MODE_IN_BLINKY_GROUP)
1698
 
        set_state(sos_state, 0);
1699
 
        #elif defined(USE_THERMAL_REGULATION)
1700
 
        set_state(tempcheck_state, 0);
1701
 
        #endif
1702
 
        return MISCHIEF_MANAGED;
1703
 
    }
1704
 
    // tick: step down (maybe) or off (maybe)
1705
 
    else if (event == EV_tick) {
1706
 
        if (++ticks_since_stepdown > GOODNIGHT_TICKS_PER_STEPDOWN) {
1707
 
            ticks_since_stepdown = 0;
1708
 
            set_level(actual_level-1);
1709
 
            if (! actual_level) {
1710
 
                #if 0  // test blink, to help measure timing
1711
 
                set_level(MAX_LEVEL>>2);
1712
 
                delay_4ms(8/2);
1713
 
                set_level(0);
1714
 
                #endif
1715
 
                set_state(off_state, 0);
1716
 
            }
1717
 
        }
1718
 
        return MISCHIEF_MANAGED;
1719
 
    }
1720
 
    return EVENT_NOT_HANDLED;
1721
 
}
1722
 
#endif
1723
 
 
1724
 
 
1725
 
uint8_t lockout_state(Event event, uint16_t arg) {
1726
 
    #ifdef MOON_DURING_LOCKOUT_MODE
1727
 
    // momentary(ish) moon mode during lockout
1728
 
    // button is being held
1729
 
    #ifdef USE_AUX_RGB_LEDS
1730
 
    // don't turn on during RGB aux LED configuration
1731
 
    if (event == EV_click3_hold) { set_level(0); } else
1732
 
    #endif
1733
 
    if ((event & (B_CLICK | B_PRESS)) == (B_CLICK | B_PRESS)) {
1734
 
        #ifdef LOCKOUT_MOON_LOWEST
1735
 
        // Use lowest moon configured
1736
 
        uint8_t lvl = ramp_smooth_floor;
1737
 
        if (ramp_discrete_floor < lvl) lvl = ramp_discrete_floor;
1738
 
        set_level(lvl);
1739
 
        #elif defined(LOCKOUT_MOON_FANCY)
1740
 
        uint8_t levels[] = { ramp_smooth_floor, ramp_discrete_floor };
1741
 
        if ((event & 0x0f) == 2) {
1742
 
            set_level(levels[ramp_style^1]);
1743
 
        } else {
1744
 
            set_level(levels[ramp_style]);
1745
 
        }
1746
 
        #else
1747
 
        // Use moon from current ramp
1748
 
        set_level(nearest_level(1));
1749
 
        #endif
1750
 
    }
1751
 
    // button was released
1752
 
    else if ((event & (B_CLICK | B_PRESS)) == (B_CLICK)) {
1753
 
        set_level(0);
1754
 
    }
1755
 
    #endif
1756
 
 
1757
 
    // regular event handling
1758
 
    // conserve power while locked out
1759
 
    // (allow staying awake long enough to exit, but otherwise
1760
 
    //  be persistent about going back to sleep every few seconds
1761
 
    //  even if the user keeps pressing the button)
1762
 
    #ifdef USE_INDICATOR_LED
1763
 
    if (event == EV_enter_state) {
1764
 
        indicator_led(indicator_led_mode >> 2);
1765
 
    } else
1766
 
    #elif defined(USE_AUX_RGB_LEDS)
1767
 
    if (event == EV_enter_state) {
1768
 
        rgb_led_update(rgb_led_lockout_mode, 0);
1769
 
    } else
1770
 
    #endif
1771
 
    if (event == EV_tick) {
1772
 
        if (arg > HOLD_TIMEOUT) {
1773
 
            go_to_standby = 1;
1774
 
            #ifdef USE_INDICATOR_LED
1775
 
            indicator_led(indicator_led_mode >> 2);
1776
 
            #elif defined(USE_AUX_RGB_LEDS)
1777
 
            rgb_led_update(rgb_led_lockout_mode, arg);
1778
 
            #endif
1779
 
        }
1780
 
        return MISCHIEF_MANAGED;
1781
 
    }
1782
 
    #if defined(TICK_DURING_STANDBY) && (defined(USE_INDICATOR_LED) || defined(USE_AUX_RGB_LEDS))
1783
 
    else if (event == EV_sleep_tick) {
1784
 
        #if defined(USE_INDICATOR_LED)
1785
 
        if ((indicator_led_mode & 0b00001100) == 0b00001100) {
1786
 
            indicator_blink(arg);
1787
 
        }
1788
 
        #elif defined(USE_AUX_RGB_LEDS)
1789
 
        rgb_led_update(rgb_led_lockout_mode, arg);
1790
 
        #endif
1791
 
        return MISCHIEF_MANAGED;
1792
 
    }
1793
 
    #endif
1794
 
    #if defined(USE_INDICATOR_LED)
1795
 
    // 3 clicks: rotate through indicator LED modes (lockout mode)
1796
 
    else if (event == EV_3clicks) {
1797
 
        #if defined(USE_INDICATOR_LED)
1798
 
            uint8_t mode = indicator_led_mode >> 2;
1799
 
            #ifdef TICK_DURING_STANDBY
1800
 
            mode = (mode + 1) & 3;
1801
 
            #else
1802
 
            mode = (mode + 1) % 3;
1803
 
            #endif
1804
 
            #ifdef INDICATOR_LED_SKIP_LOW
1805
 
            if (mode == 1) { mode ++; }
1806
 
            #endif
1807
 
            indicator_led_mode = (mode << 2) + (indicator_led_mode & 0x03);
1808
 
            indicator_led(mode);
1809
 
        #elif defined(USE_AUX_RGB_LEDS)
1810
 
        #endif
1811
 
        save_config();
1812
 
        return MISCHIEF_MANAGED;
1813
 
    }
1814
 
    #elif defined(USE_AUX_RGB_LEDS)
1815
 
    // 3 clicks: change RGB aux LED pattern
1816
 
    else if (event == EV_3clicks) {
1817
 
        uint8_t mode = (rgb_led_lockout_mode >> 4) + 1;
1818
 
        mode = mode % RGB_LED_NUM_PATTERNS;
1819
 
        rgb_led_lockout_mode = (mode << 4) | (rgb_led_lockout_mode & 0x0f);
1820
 
        rgb_led_update(rgb_led_lockout_mode, 0);
1821
 
        save_config();
1822
 
        blink_confirm(1);
1823
 
        return MISCHIEF_MANAGED;
1824
 
    }
1825
 
    // click, click, hold: change RGB aux LED color
1826
 
    else if (event == EV_click3_hold) {
1827
 
        setting_rgb_mode_now = 1;
1828
 
        if (0 == (arg & 0x3f)) {
1829
 
            uint8_t mode = (rgb_led_lockout_mode & 0x0f) + 1;
1830
 
            mode = mode % RGB_LED_NUM_COLORS;
1831
 
            rgb_led_lockout_mode = mode | (rgb_led_lockout_mode & 0xf0);
1832
 
            //save_config();
1833
 
        }
1834
 
        rgb_led_update(rgb_led_lockout_mode, arg);
1835
 
        return MISCHIEF_MANAGED;
1836
 
    }
1837
 
    // click, click, hold, release: save new color
1838
 
    else if (event == EV_click3_hold_release) {
1839
 
        setting_rgb_mode_now = 0;
1840
 
        save_config();
1841
 
        return MISCHIEF_MANAGED;
1842
 
    }
1843
 
    #endif
1844
 
    // 4 clicks: exit
1845
 
    else if (event == EV_4clicks) {
1846
 
        blink_confirm(1);
1847
 
        set_state(off_state, 0);
1848
 
        return MISCHIEF_MANAGED;
1849
 
    }
1850
 
 
1851
 
    return EVENT_NOT_HANDLED;
1852
 
}
1853
 
 
1854
 
 
1855
 
#ifdef USE_MOMENTARY_MODE
1856
 
uint8_t momentary_state(Event event, uint16_t arg) {
1857
 
    // TODO: momentary strobe here?  (for light painting)
1858
 
 
1859
 
    // init strobe mode, if relevant
1860
 
    #ifdef USE_STROBE_STATE
1861
 
    if ((event == EV_enter_state) && (momentary_mode == 1)) {
1862
 
        strobe_state(event, arg);
1863
 
    }
1864
 
    #endif
1865
 
 
1866
 
    // light up when the button is pressed; go dark otherwise
1867
 
    // button is being held
1868
 
    if ((event & (B_CLICK | B_PRESS)) == (B_CLICK | B_PRESS)) {
1869
 
        momentary_active = 1;
1870
 
        // 0 = ramping, 1 = strobes
1871
 
        if (momentary_mode == 0) {
1872
 
            set_level(memorized_level);
1873
 
        }
1874
 
        return MISCHIEF_MANAGED;
1875
 
    }
1876
 
    // button was released
1877
 
    else if ((event & (B_CLICK | B_PRESS)) == (B_CLICK)) {
1878
 
        momentary_active = 0;
1879
 
        set_level(0);
1880
 
        //go_to_standby = 1;  // sleep while light is off
1881
 
        return MISCHIEF_MANAGED;
1882
 
    }
1883
 
 
1884
 
    // Sleep, dammit!  (but wait a few seconds first)
1885
 
    // (because standby mode uses such little power that it can interfere
1886
 
    //  with exiting via tailcap loosen+tighten unless you leave power
1887
 
    //  disconnected for several seconds, so we want to be awake when that
1888
 
    //  happens to speed up the process)
1889
 
    else if (event == EV_tick) {
1890
 
        #ifdef USE_STROBE_STATE
1891
 
        if (momentary_active) {
1892
 
            // 0 = ramping, 1 = strobes
1893
 
            if (momentary_mode == 1) {
1894
 
                return strobe_state(event, arg);
1895
 
            }
1896
 
        }
1897
 
        else {
1898
 
        #endif
1899
 
            if (arg > TICKS_PER_SECOND*5) {  // sleep after 5 seconds
1900
 
                go_to_standby = 1;  // sleep while light is off
1901
 
                // turn off lighted button
1902
 
                #ifdef USE_INDICATOR_LED
1903
 
                indicator_led(0);
1904
 
                #elif defined(USE_AUX_RGB_LEDS)
1905
 
                rgb_led_update(0, 0);
1906
 
                #endif
1907
 
            }
1908
 
        #ifdef USE_STROBE_STATE
1909
 
        }
1910
 
        #endif
1911
 
        return MISCHIEF_MANAGED;
1912
 
    }
1913
 
 
1914
 
    return EVENT_NOT_HANDLED;
1915
 
}
1916
 
#endif
1917
 
 
1918
 
 
1919
 
#ifdef USE_MUGGLE_MODE
1920
 
uint8_t muggle_state(Event event, uint16_t arg) {
1921
 
    static int8_t ramp_direction;
1922
 
    static int8_t muggle_off_mode;
1923
 
 
1924
 
    // turn LED off when we first enter the mode
1925
 
    if (event == EV_enter_state) {
1926
 
        ramp_direction = 1;
1927
 
 
1928
 
        #ifdef START_AT_MEMORIZED_LEVEL
1929
 
            memorized_level = arg;
1930
 
            muggle_off_mode = 0;
1931
 
            set_level(memorized_level);
1932
 
 
1933
 
            if (! muggle_mode_active) {  // don't write eeprom at every boot
1934
 
                muggle_mode_active = 1;
1935
 
                save_config();
1936
 
            }
1937
 
        #else
1938
 
            muggle_mode_active = 1;
1939
 
            save_config();
1940
 
 
1941
 
            muggle_off_mode = 1;
1942
 
            //memorized_level = MAX_1x7135;
1943
 
            memorized_level = (MUGGLE_FLOOR + MUGGLE_CEILING) / 2;
1944
 
        #endif
1945
 
        return MISCHIEF_MANAGED;
1946
 
    }
1947
 
    // initial press: moon hint
1948
 
    else if (event == EV_click1_press) {
1949
 
        if (muggle_off_mode)
1950
 
            set_level(MUGGLE_FLOOR);
1951
 
    }
1952
 
    // initial release: direct to memorized level
1953
 
    else if (event == EV_click1_release) {
1954
 
        if (muggle_off_mode)
1955
 
            set_level(memorized_level);
1956
 
    }
1957
 
    // if the user keeps pressing, turn off
1958
 
    else if (event == EV_click2_press) {
1959
 
        muggle_off_mode = 1;
1960
 
        set_level(0);
1961
 
    }
1962
 
    // 1 click: on/off
1963
 
    else if (event == EV_1click) {
1964
 
        muggle_off_mode ^= 1;
1965
 
        if (muggle_off_mode) {
1966
 
            set_level(0);
1967
 
        }
1968
 
        /*
1969
 
        else {
1970
 
            set_level(memorized_level);
1971
 
        }
1972
 
        */
1973
 
        return MISCHIEF_MANAGED;
1974
 
    }
1975
 
    // hold: change brightness
1976
 
    else if (event == EV_click1_hold) {
1977
 
        // ramp at half speed
1978
 
        if (arg & 1) return MISCHIEF_MANAGED;
1979
 
 
1980
 
        // if off, start at bottom
1981
 
        if (muggle_off_mode) {
1982
 
            muggle_off_mode = 0;
1983
 
            ramp_direction = 1;
1984
 
            set_level(MUGGLE_FLOOR);
1985
 
        }
1986
 
        else {
1987
 
            uint8_t m;
1988
 
            m = actual_level;
1989
 
            // ramp down if already at ceiling
1990
 
            if ((arg <= 1) && (m >= MUGGLE_CEILING)) ramp_direction = -1;
1991
 
            // ramp
1992
 
            m += ramp_direction;
1993
 
            if (m < MUGGLE_FLOOR)
1994
 
                m = MUGGLE_FLOOR;
1995
 
            if (m > MUGGLE_CEILING)
1996
 
                m = MUGGLE_CEILING;
1997
 
            memorized_level = m;
1998
 
            set_level(m);
1999
 
        }
2000
 
        return MISCHIEF_MANAGED;
2001
 
    }
2002
 
    // reverse ramp direction on hold release
2003
 
    else if (event == EV_click1_hold_release) {
2004
 
        ramp_direction = -ramp_direction;
2005
 
        #ifdef START_AT_MEMORIZED_LEVEL
2006
 
        save_config_wl();  // momentary use should retain brightness level
2007
 
        #endif
2008
 
        return MISCHIEF_MANAGED;
2009
 
    }
2010
 
    /*
2011
 
    // click, hold: change brightness (dimmer)
2012
 
    else if (event == EV_click2_hold) {
2013
 
        ramp_direction = 1;
2014
 
        if (memorized_level > MUGGLE_FLOOR)
2015
 
            memorized_level = actual_level - 1;
2016
 
        set_level(memorized_level);
2017
 
        return MISCHIEF_MANAGED;
2018
 
    }
2019
 
    */
2020
 
    // 6 clicks: exit muggle mode
2021
 
    else if (event == EV_6clicks) {
2022
 
        blink_confirm(1);
2023
 
        muggle_mode_active = 0;
2024
 
        save_config();
2025
 
        set_state(off_state, 0);
2026
 
        return MISCHIEF_MANAGED;
2027
 
    }
2028
 
    // tick: housekeeping
2029
 
    else if (event == EV_tick) {
2030
 
        // un-reverse after 1 second
2031
 
        if (arg == TICKS_PER_SECOND) ramp_direction = 1;
2032
 
 
2033
 
        // turn off, but don't go to the main "off" state
2034
 
        if (muggle_off_mode) {
2035
 
            if (arg > TICKS_PER_SECOND*1) {  // sleep after 1 second
2036
 
                #ifdef USE_AUX_RGB_LEDS_WHILE_ON
2037
 
                rgb_led_set(0);
2038
 
                #endif
2039
 
                go_to_standby = 1;  // sleep while light is off
2040
 
            }
2041
 
        }
2042
 
        return MISCHIEF_MANAGED;
2043
 
    }
2044
 
    #ifdef USE_THERMAL_REGULATION
2045
 
    // overheating is handled specially in muggle mode
2046
 
    else if(event == EV_temperature_high) {
2047
 
        #if 0
2048
 
        blip();
2049
 
        #endif
2050
 
        // ignore warnings while off
2051
 
        if (! muggle_off_mode) {
2052
 
            // step down proportional to the amount of overheating
2053
 
            int16_t new = actual_level - arg;
2054
 
            if (new < MUGGLE_FLOOR) { new = MUGGLE_FLOOR; }
2055
 
            set_level(new);
2056
 
        }
2057
 
        return MISCHIEF_MANAGED;
2058
 
    }
2059
 
    #endif
2060
 
    #ifdef USE_LVP
2061
 
    // low voltage is handled specially in muggle mode
2062
 
    else if(event == EV_voltage_low) {
2063
 
        uint8_t lvl = (actual_level >> 1) + (actual_level >> 2);
2064
 
        if (lvl >= MUGGLE_FLOOR) {
2065
 
            set_level(lvl);
2066
 
        } else {
2067
 
            muggle_off_mode = 1;
2068
 
        }
2069
 
        return MISCHIEF_MANAGED;
2070
 
    }
2071
 
    #endif
2072
 
 
2073
 
    return EVENT_NOT_HANDLED;
2074
 
}
2075
 
#endif
2076
 
 
2077
 
 
2078
 
#ifdef USE_VERSION_CHECK
2079
 
uint8_t version_check_state(Event event, uint16_t arg) {
2080
 
    return EVENT_NOT_HANDLED;
2081
 
}
2082
 
#endif
2083
 
 
2084
 
 
2085
 
// ask the user for a sequence of numbers, then save them and return to caller
2086
 
uint8_t config_state_base(Event event, uint16_t arg,
2087
 
                          uint8_t num_config_steps,
2088
 
                          void (*savefunc)()) {
2089
 
    static uint8_t config_step;
2090
 
    if (event == EV_enter_state) {
2091
 
        config_step = 0;
2092
 
        set_level(0);
2093
 
        return MISCHIEF_MANAGED;
2094
 
    }
2095
 
    // advance forward through config steps
2096
 
    else if (event == EV_tick) {
2097
 
        if (config_step < num_config_steps) {
2098
 
            push_state(number_entry_state, config_step + 1);
2099
 
        }
2100
 
        else {
2101
 
            // TODO: blink out some sort of success pattern
2102
 
            savefunc();
2103
 
            save_config();
2104
 
            //set_state(retstate, retval);
2105
 
            pop_state();
2106
 
        }
2107
 
        return MISCHIEF_MANAGED;
2108
 
    }
2109
 
    // an option was set (return from number_entry_state)
2110
 
    else if (event == EV_reenter_state) {
2111
 
        config_state_values[config_step] = number_entry_value;
2112
 
        config_step ++;
2113
 
        return MISCHIEF_MANAGED;
2114
 
    }
2115
 
    //return EVENT_NOT_HANDLED;
2116
 
    // eat all other events; don't pass any through to parent
2117
 
    return EVENT_HANDLED;
2118
 
}
2119
 
 
2120
 
#ifdef USE_RAMP_CONFIG
2121
 
void ramp_config_save() {
2122
 
    // parse values
2123
 
    uint8_t val;
2124
 
    if (ramp_style) {  // discrete / stepped ramp
2125
 
 
2126
 
        val = config_state_values[0];
2127
 
        if (val) { ramp_discrete_floor = val; }
2128
 
 
2129
 
        val = config_state_values[1];
2130
 
        if (val) { ramp_discrete_ceil = MAX_LEVEL + 1 - val; }
2131
 
 
2132
 
        val = config_state_values[2];
2133
 
        if (val) ramp_discrete_steps = val;
2134
 
 
2135
 
    } else {  // smooth ramp
2136
 
 
2137
 
        val = config_state_values[0];
2138
 
        if (val) { ramp_smooth_floor = val; }
2139
 
 
2140
 
        val = config_state_values[1];
2141
 
        if (val) { ramp_smooth_ceil = MAX_LEVEL + 1 - val; }
2142
 
 
2143
 
    }
2144
 
}
2145
 
 
2146
 
uint8_t ramp_config_state(Event event, uint16_t arg) {
2147
 
    uint8_t num_config_steps;
2148
 
    num_config_steps = 2 + ramp_style;
2149
 
    return config_state_base(event, arg,
2150
 
                             num_config_steps, ramp_config_save);
2151
 
}
2152
 
#endif  // #ifdef USE_RAMP_CONFIG
2153
 
 
2154
 
 
2155
 
#ifdef USE_THERMAL_REGULATION
2156
 
void thermal_config_save() {
2157
 
    // parse values
2158
 
    uint8_t val;
2159
 
 
2160
 
    // calibrate room temperature
2161
 
    val = config_state_values[0];
2162
 
    if (val) {
2163
 
        int8_t rawtemp = temperature - therm_cal_offset;
2164
 
        therm_cal_offset = val - rawtemp;
2165
 
        adc_reset = 2;  // invalidate all recent temperature data
2166
 
    }
2167
 
 
2168
 
    val = config_state_values[1];
2169
 
    if (val) {
2170
 
        // set maximum heat limit
2171
 
        therm_ceil = 30 + val - 1;
2172
 
    }
2173
 
    if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL;
2174
 
}
2175
 
 
2176
 
uint8_t thermal_config_state(Event event, uint16_t arg) {
2177
 
    return config_state_base(event, arg,
2178
 
                             2, thermal_config_save);
2179
 
}
2180
 
#endif  // #ifdef USE_THERMAL_REGULATION
2181
 
 
2182
 
 
2183
 
#ifdef USE_BEACON_MODE
2184
 
void beacon_config_save() {
2185
 
    // parse values
2186
 
    uint8_t val = config_state_values[0];
2187
 
    if (val) {
2188
 
        beacon_seconds = val;
2189
 
    }
2190
 
}
2191
 
 
2192
 
uint8_t beacon_config_state(Event event, uint16_t arg) {
2193
 
    return config_state_base(event, arg,
2194
 
                             1, beacon_config_save);
2195
 
}
2196
 
 
2197
 
inline void beacon_mode_iter() {
2198
 
    // one iteration of main loop()
2199
 
    set_level(memorized_level);
2200
 
    nice_delay_ms(100);
2201
 
    set_level(0);
2202
 
    nice_delay_ms(((beacon_seconds) * 1000) - 100);
2203
 
}
2204
 
#endif  // #ifdef USE_BEACON_MODE
2205
 
 
2206
 
 
2207
 
uint8_t number_entry_state(Event event, uint16_t arg) {
2208
 
    static uint8_t value;
2209
 
    static uint8_t blinks_left;
2210
 
    static uint8_t entry_step;
2211
 
    static uint16_t wait_ticks;
2212
 
    if (event == EV_enter_state) {
2213
 
        value = 0;
2214
 
        blinks_left = arg;
2215
 
        entry_step = 0;
2216
 
        wait_ticks = 0;
2217
 
        return MISCHIEF_MANAGED;
2218
 
    }
2219
 
    // advance through the process:
2220
 
    // 0: wait a moment
2221
 
    // 1: blink out the 'arg' value
2222
 
    // 2: wait a moment
2223
 
    // 3: "buzz" while counting clicks
2224
 
    // 4: save and exit
2225
 
    else if (event == EV_tick) {
2226
 
        // wait a moment
2227
 
        if ((entry_step == 0) || (entry_step == 2)) {
2228
 
            if (wait_ticks < TICKS_PER_SECOND/2)
2229
 
                wait_ticks ++;
2230
 
            else {
2231
 
                entry_step ++;
2232
 
                wait_ticks = 0;
2233
 
            }
2234
 
        }
2235
 
        // blink out the option number
2236
 
        else if (entry_step == 1) {
2237
 
            if (blinks_left) {
2238
 
                if ((wait_ticks & 31) == 10) {
2239
 
                    set_level(RAMP_SIZE/4);
2240
 
                }
2241
 
                else if ((wait_ticks & 31) == 20) {
2242
 
                    set_level(0);
2243
 
                }
2244
 
                else if ((wait_ticks & 31) == 31) {
2245
 
                    blinks_left --;
2246
 
                }
2247
 
                wait_ticks ++;
2248
 
            }
2249
 
            else {
2250
 
                entry_step ++;
2251
 
                wait_ticks = 0;
2252
 
            }
2253
 
        }
2254
 
        else if (entry_step == 3) {  // buzz while waiting for a number to be entered
2255
 
            wait_ticks ++;
2256
 
            // buzz for N seconds after last event
2257
 
            if ((wait_ticks & 3) == 0) {
2258
 
                set_level(RAMP_SIZE/6);
2259
 
            }
2260
 
            else if ((wait_ticks & 3) == 2) {
2261
 
                set_level(RAMP_SIZE/8);
2262
 
            }
2263
 
            // time out after 3 seconds
2264
 
            if (wait_ticks > TICKS_PER_SECOND*3) {
2265
 
                //number_entry_value = value;
2266
 
                set_level(0);
2267
 
                entry_step ++;
2268
 
            }
2269
 
        }
2270
 
        else if (entry_step == 4) {
2271
 
            number_entry_value = value;
2272
 
            pop_state();
2273
 
        }
2274
 
        return MISCHIEF_MANAGED;
2275
 
    }
2276
 
    // count clicks
2277
 
    else if (event == EV_click1_release) {
2278
 
        empty_event_sequence();
2279
 
        if (entry_step == 3) {  // only count during the "buzz"
2280
 
            value ++;
2281
 
            wait_ticks = 0;
2282
 
            // flash briefly
2283
 
            set_level(RAMP_SIZE/2);
2284
 
            delay_4ms(8/2);
2285
 
            set_level(0);
2286
 
        }
2287
 
        return MISCHIEF_MANAGED;
2288
 
    }
2289
 
    return EVENT_NOT_HANDLED;
2290
 
}
2291
 
 
2292
 
 
2293
 
// find the ramp level closest to the target,
2294
 
// using only the levels which are allowed in the current state
2295
 
uint8_t nearest_level(int16_t target) {
2296
 
    // bounds check
2297
 
    // using int16_t here saves us a bunch of logic elsewhere,
2298
 
    // by allowing us to correct for numbers < 0 or > 255 in one central place
2299
 
    uint8_t mode_min = ramp_smooth_floor;
2300
 
    uint8_t mode_max = ramp_smooth_ceil;
2301
 
    if (ramp_style) {
2302
 
        mode_min = ramp_discrete_floor;
2303
 
        mode_max = ramp_discrete_ceil;
2304
 
    }
2305
 
    if (target < mode_min) return mode_min;
2306
 
    if (target > mode_max) return mode_max;
2307
 
    // the rest isn't relevant for smooth ramping
2308
 
    if (! ramp_style) return target;
2309
 
 
2310
 
    uint8_t ramp_range = ramp_discrete_ceil - ramp_discrete_floor;
2311
 
    ramp_discrete_step_size = ramp_range / (ramp_discrete_steps-1);
2312
 
    uint8_t this_level = ramp_discrete_floor;
2313
 
 
2314
 
    for(uint8_t i=0; i<ramp_discrete_steps; i++) {
2315
 
        this_level = ramp_discrete_floor + (i * (uint16_t)ramp_range / (ramp_discrete_steps-1));
2316
 
        int16_t diff = target - this_level;
2317
 
        if (diff < 0) diff = -diff;
2318
 
        if (diff <= (ramp_discrete_step_size>>1))
2319
 
            return this_level;
2320
 
    }
2321
 
    return this_level;
2322
 
}
2323
 
 
2324
 
#ifdef USE_THERMAL_REGULATION
2325
 
void set_level_and_therm_target(uint8_t level) {
2326
 
    target_level = level;
2327
 
    set_level(level);
2328
 
}
2329
 
#endif
2330
 
 
2331
 
void blink_confirm(uint8_t num) {
2332
 
    for (; num>0; num--) {
2333
 
        set_level(MAX_LEVEL/4);
2334
 
        delay_4ms(10/4);
2335
 
        set_level(0);
2336
 
        delay_4ms(100/4);
2337
 
    }
2338
 
}
2339
 
 
2340
 
// Just go dark for a moment to indicate to user that something happened
2341
 
void blip() {
2342
 
    uint8_t temp = actual_level;
2343
 
    set_level(0);
2344
 
    delay_4ms(3);
2345
 
    set_level(temp);
2346
 
}
2347
 
 
2348
 
 
2349
 
#if defined(USE_INDICATOR_LED) && defined(TICK_DURING_STANDBY)
2350
 
// beacon-like mode for the indicator LED
2351
 
void indicator_blink(uint8_t arg) {
2352
 
    // turn off aux LEDs when battery is empty
2353
 
    if (voltage < VOLTAGE_LOW) { indicator_led(0); return; }
2354
 
 
2355
 
    #ifdef USE_FANCIER_BLINKING_INDICATOR
2356
 
 
2357
 
    // fancy blink, set off/low/high levels here:
2358
 
    uint8_t seq[] = {0, 1, 2, 1,  0, 0, 0, 0,
2359
 
                     0, 0, 1, 0,  0, 0, 0, 0};
2360
 
    indicator_led(seq[arg & 15]);
2361
 
 
2362
 
    #else  // basic blink, 1/8th duty cycle
2363
 
 
2364
 
    if (! (arg & 7)) {
2365
 
        indicator_led(2);
2366
 
    }
2367
 
    else {
2368
 
        indicator_led(0);
2369
 
    }
2370
 
 
2371
 
    #endif
2372
 
}
2373
 
#endif
2374
 
 
2375
 
#if defined(USE_AUX_RGB_LEDS) && defined(TICK_DURING_STANDBY)
2376
 
uint8_t voltage_to_rgb() {
2377
 
    uint8_t levels[] = {
2378
 
    // voltage, color
2379
 
          0, 0, // 0, R
2380
 
         33, 1, // 1, R+G
2381
 
         35, 2, // 2,   G
2382
 
         37, 3, // 3,   G+B
2383
 
         39, 4, // 4,     B
2384
 
         41, 5, // 5, R + B
2385
 
         44, 6, // 6, R+G+B  // skip; looks too similar to G+B
2386
 
        255, 6, // 7, R+G+B
2387
 
    };
2388
 
    uint8_t volts = voltage;
2389
 
    if (volts < 29) return 0;
2390
 
 
2391
 
    uint8_t i;
2392
 
    for (i = 0;  volts >= levels[i];  i += 2) {}
2393
 
    uint8_t color_num = levels[(i - 2) + 1];
2394
 
    return pgm_read_byte(rgb_led_colors + color_num);
2395
 
}
2396
 
 
2397
 
// do fancy stuff with the RGB aux LEDs
2398
 
// mode: 0bPPPPCCCC where PPPP is the pattern and CCCC is the color
2399
 
// arg: time slice number
2400
 
void rgb_led_update(uint8_t mode, uint8_t arg) {
2401
 
    static uint8_t rainbow = 0;  // track state of rainbow mode
2402
 
    static uint8_t frame = 0;  // track state of animation mode
2403
 
 
2404
 
    // turn off aux LEDs when battery is empty
2405
 
    // (but if voltage==0, that means we just booted and don't know yet)
2406
 
    uint8_t volts = voltage;  // save a few bytes by caching volatile value
2407
 
    if ((volts) && (volts < VOLTAGE_LOW)) {
2408
 
        rgb_led_set(0);
2409
 
        #ifdef USE_BUTTON_LED
2410
 
        button_led_set(0);
2411
 
        #endif
2412
 
        return;
2413
 
    }
2414
 
 
2415
 
    uint8_t pattern = (mode>>4);  // off, low, high, blinking, ... more?
2416
 
    uint8_t color = mode & 0x0f;
2417
 
 
2418
 
    // preview in blinking mode is awkward... use high instead
2419
 
    if ((! go_to_standby) && (pattern > 2)) { pattern = 2; }
2420
 
 
2421
 
 
2422
 
    const uint8_t *colors = rgb_led_colors;
2423
 
    uint8_t actual_color = 0;
2424
 
    if (color < 7) {  // normal color
2425
 
        actual_color = pgm_read_byte(colors + color);
2426
 
    }
2427
 
    else if (color == 7) {  // rainbow
2428
 
        uint8_t speed = 0x03;  // awake speed
2429
 
        if (go_to_standby) speed = RGB_RAINBOW_SPEED;  // asleep speed
2430
 
        if (0 == (arg & speed)) {
2431
 
            rainbow = (rainbow + 1) % 6;
2432
 
        }
2433
 
        actual_color = pgm_read_byte(colors + rainbow);
2434
 
    }
2435
 
    else {  // voltage
2436
 
        // show actual voltage while asleep...
2437
 
        if (go_to_standby) {
2438
 
            actual_color = voltage_to_rgb();
2439
 
            // choose a color based on battery voltage
2440
 
            //if (volts >= 38) actual_color = pgm_read_byte(colors + 4);
2441
 
            //else if (volts >= 33) actual_color = pgm_read_byte(colors + 2);
2442
 
            //else actual_color = pgm_read_byte(colors + 0);
2443
 
        }
2444
 
        // ... but during preview, cycle colors quickly
2445
 
        else {
2446
 
            actual_color = pgm_read_byte(colors + (((arg>>1) % 3) << 1));
2447
 
        }
2448
 
    }
2449
 
 
2450
 
    // pick a brightness from the animation sequence
2451
 
    if (pattern == 3) {
2452
 
        // uses an odd length to avoid lining up with rainbow loop
2453
 
        uint8_t animation[] = {2, 1, 0, 0,  0, 0, 0, 0,  0,
2454
 
                               1, 0, 0, 0,  0, 0, 0, 0,  0, 1};
2455
 
        frame = (frame + 1) % sizeof(animation);
2456
 
        pattern = animation[frame];
2457
 
    }
2458
 
    uint8_t result;
2459
 
    #ifdef USE_BUTTON_LED
2460
 
    uint8_t button_led_result;
2461
 
    #endif
2462
 
    switch (pattern) {
2463
 
        case 0:  // off
2464
 
            result = 0;
2465
 
            #ifdef USE_BUTTON_LED
2466
 
            button_led_result = 0;
2467
 
            #endif
2468
 
            break;
2469
 
        case 1:  // low
2470
 
            result = actual_color;
2471
 
            #ifdef USE_BUTTON_LED
2472
 
            button_led_result = 1;
2473
 
            #endif
2474
 
            break;
2475
 
        default:  // high
2476
 
            result = (actual_color << 1);
2477
 
            #ifdef USE_BUTTON_LED
2478
 
            button_led_result = 2;
2479
 
            #endif
2480
 
            break;
2481
 
    }
2482
 
    rgb_led_set(result);
2483
 
    #ifdef USE_BUTTON_LED
2484
 
    button_led_set(button_led_result);
2485
 
    #endif
2486
 
}
2487
 
 
2488
 
void rgb_led_voltage_readout(uint8_t bright) {
2489
 
    uint8_t color = voltage_to_rgb();
2490
 
    if (bright) color = color << 1;
2491
 
    rgb_led_set(color);
2492
 
}
2493
 
#endif
2494
 
 
2495
 
 
2496
 
#ifdef USE_FACTORY_RESET
2497
 
void factory_reset() {
2498
 
    // display a warning for a few seconds before doing the actual reset,
2499
 
    // so the user has time to abort if they want
2500
 
    #define SPLODEY_TIME 2500
2501
 
    #define SPLODEY_STEPS 64
2502
 
    #define SPLODEY_TIME_PER_STEP (SPLODEY_TIME/SPLODEY_STEPS)
2503
 
    uint8_t bright;
2504
 
    uint8_t reset = 1;
2505
 
    // wind up to an explosion
2506
 
    for (bright=0; bright<SPLODEY_STEPS; bright++) {
2507
 
        set_level(bright);
2508
 
        nice_delay_ms(SPLODEY_TIME_PER_STEP/2);
2509
 
        set_level(bright>>1);
2510
 
        nice_delay_ms(SPLODEY_TIME_PER_STEP/2);
2511
 
        if (! button_is_pressed()) {
2512
 
            reset = 0;
2513
 
            break;
2514
 
        }
2515
 
    }
2516
 
    // explode, if button pressed long enough
2517
 
    if (reset) {
2518
 
        #ifdef USE_THERMAL_REGULATION
2519
 
        // auto-calibrate temperature...  assume current temperature is 21 C
2520
 
        config_state_values[0] = 21;
2521
 
        config_state_values[1] = 0;
2522
 
        thermal_config_save();
2523
 
        #endif
2524
 
        // save all settings to eeprom
2525
 
        // (assuming they're all at default because we haven't loaded them yet)
2526
 
        save_config();
2527
 
 
2528
 
        bright = MAX_LEVEL;
2529
 
        for (; bright > 0; bright--) {
2530
 
            set_level(bright);
2531
 
            nice_delay_ms(SPLODEY_TIME_PER_STEP/8);
2532
 
        }
2533
 
    }
2534
 
    // explosion cancelled, fade away
2535
 
    else {
2536
 
        for (; bright > 0; bright--) {
2537
 
            set_level(bright);
2538
 
            nice_delay_ms(SPLODEY_TIME_PER_STEP/3);
2539
 
        }
2540
 
    }
2541
 
}
2542
 
#endif
2543
 
 
2544
 
 
2545
 
void load_config() {
2546
 
    if (load_eeprom()) {
2547
 
        ramp_style = eeprom[ramp_style_e];
2548
 
        #ifdef USE_RAMP_CONFIG
2549
 
        ramp_smooth_floor = eeprom[ramp_smooth_floor_e];
2550
 
        ramp_smooth_ceil = eeprom[ramp_smooth_ceil_e];
2551
 
        ramp_discrete_floor = eeprom[ramp_discrete_floor_e];
2552
 
        ramp_discrete_ceil = eeprom[ramp_discrete_ceil_e];
2553
 
        ramp_discrete_steps = eeprom[ramp_discrete_steps_e];
2554
 
        #endif
2555
 
        #ifdef USE_MANUAL_MEMORY
2556
 
        manual_memory = eeprom[manual_memory_e];
2557
 
        #endif
2558
 
        #ifdef USE_TINT_RAMPING
2559
 
        tint = eeprom[tint_e];
2560
 
        #endif
2561
 
        #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
2562
 
        strobe_type = eeprom[strobe_type_e];  // TODO: move this to eeprom_wl?
2563
 
        strobe_delays[0] = eeprom[strobe_delays_0_e];
2564
 
        strobe_delays[1] = eeprom[strobe_delays_1_e];
2565
 
        #endif
2566
 
        #ifdef USE_BIKE_FLASHER_MODE
2567
 
        bike_flasher_brightness = eeprom[bike_flasher_brightness_e];
2568
 
        #endif
2569
 
        #ifdef USE_BEACON_MODE
2570
 
        beacon_seconds = eeprom[beacon_seconds_e];
2571
 
        #endif
2572
 
        #ifdef USE_MUGGLE_MODE
2573
 
        muggle_mode_active = eeprom[muggle_mode_active_e];
2574
 
        #endif
2575
 
        #ifdef USE_THERMAL_REGULATION
2576
 
        therm_ceil = eeprom[therm_ceil_e];
2577
 
        therm_cal_offset = eeprom[therm_cal_offset_e];
2578
 
        #endif
2579
 
        #ifdef USE_INDICATOR_LED
2580
 
        indicator_led_mode = eeprom[indicator_led_mode_e];
2581
 
        #endif
2582
 
        #ifdef USE_AUX_RGB_LEDS
2583
 
        rgb_led_off_mode = eeprom[rgb_led_off_mode_e];
2584
 
        rgb_led_lockout_mode = eeprom[rgb_led_lockout_mode_e];
2585
 
        #endif
2586
 
    }
2587
 
    #ifdef START_AT_MEMORIZED_LEVEL
2588
 
    if (load_eeprom_wl()) {
2589
 
        memorized_level = eeprom_wl[0];
2590
 
    }
2591
 
    #endif
2592
 
}
2593
 
 
2594
 
void save_config() {
2595
 
    eeprom[ramp_style_e] = ramp_style;
2596
 
    #ifdef USE_RAMP_CONFIG
2597
 
    eeprom[ramp_smooth_floor_e] = ramp_smooth_floor;
2598
 
    eeprom[ramp_smooth_ceil_e] = ramp_smooth_ceil;
2599
 
    eeprom[ramp_discrete_floor_e] = ramp_discrete_floor;
2600
 
    eeprom[ramp_discrete_ceil_e] = ramp_discrete_ceil;
2601
 
    eeprom[ramp_discrete_steps_e] = ramp_discrete_steps;
2602
 
    #endif
2603
 
    #ifdef USE_MANUAL_MEMORY
2604
 
    eeprom[manual_memory_e] = manual_memory;
2605
 
    #endif
2606
 
    #ifdef USE_TINT_RAMPING
2607
 
    eeprom[tint_e] = tint;
2608
 
    #endif
2609
 
    #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
2610
 
    eeprom[strobe_type_e] = strobe_type;  // TODO: move this to eeprom_wl?
2611
 
    eeprom[strobe_delays_0_e] = strobe_delays[0];
2612
 
    eeprom[strobe_delays_1_e] = strobe_delays[1];
2613
 
    #endif
2614
 
    #ifdef USE_BIKE_FLASHER_MODE
2615
 
    eeprom[bike_flasher_brightness_e] = bike_flasher_brightness;
2616
 
    #endif
2617
 
    #ifdef USE_BEACON_MODE
2618
 
    eeprom[beacon_seconds_e] = beacon_seconds;
2619
 
    #endif
2620
 
    #ifdef USE_MUGGLE_MODE
2621
 
    eeprom[muggle_mode_active_e] = muggle_mode_active;
2622
 
    #endif
2623
 
    #ifdef USE_THERMAL_REGULATION
2624
 
    eeprom[therm_ceil_e] = therm_ceil;
2625
 
    eeprom[therm_cal_offset_e] = therm_cal_offset;
2626
 
    #endif
2627
 
    #ifdef USE_INDICATOR_LED
2628
 
    eeprom[indicator_led_mode_e] = indicator_led_mode;
2629
 
    #endif
2630
 
    #ifdef USE_AUX_RGB_LEDS
2631
 
    eeprom[rgb_led_off_mode_e] = rgb_led_off_mode;
2632
 
    eeprom[rgb_led_lockout_mode_e] = rgb_led_lockout_mode;
2633
 
    #endif
2634
 
 
2635
 
    save_eeprom();
2636
 
}
2637
 
 
2638
 
#ifdef START_AT_MEMORIZED_LEVEL
2639
 
void save_config_wl() {
2640
 
    eeprom_wl[0] = memorized_level;
2641
 
    save_eeprom_wl();
2642
 
}
2643
 
#endif
2644
 
 
2645
 
 
2646
 
void low_voltage() {
2647
 
    StatePtr state = current_state;
2648
 
 
2649
 
    // TODO: turn off aux LED(s) when power is really low
2650
 
 
2651
 
    if (0) {}  // placeholder
2652
 
 
2653
 
    #ifdef USE_STROBE_STATE
2654
 
    // "step down" from strobe to something low
2655
 
    else if (state == strobe_state) {
2656
 
        set_state(steady_state, RAMP_SIZE/6);
2657
 
    }
2658
 
    #endif
2659
 
 
2660
 
    // in normal or muggle mode, step down or turn off
2661
 
    //else if ((state == steady_state) || (state == muggle_state)) {
2662
 
    else if (state == steady_state) {
2663
 
        if (actual_level > 1) {
2664
 
            uint8_t lvl = (actual_level >> 1) + (actual_level >> 2);
2665
 
            set_level_and_therm_target(lvl);
2666
 
        }
2667
 
        else {
2668
 
            set_state(off_state, 0);
2669
 
        }
2670
 
    }
2671
 
    // all other modes, just turn off when voltage is low
2672
 
    else {
2673
 
        set_state(off_state, 0);
2674
 
    }
2675
 
}
2676
 
 
2677
 
 
 
79
/********* does this build target have special code to include? *********/
 
80
#ifdef HWDEF_C_FILE
 
81
#include incfile(HWDEF_C_FILE)
 
82
#endif
 
83
#ifdef CFG_C_FILE
 
84
#include incfile(CFG_C_FILE)
 
85
#endif
 
86
 
 
87
 
 
88
/********* Include all the regular app headers *********/
 
89
 
 
90
#include "off-mode.h"
 
91
#include "ramp-mode.h"
 
92
#include "config-mode.h"
 
93
#include "aux-leds.h"
 
94
#include "misc.h"
 
95
 
 
96
#ifdef USE_SUNSET_TIMER
 
97
#include "sunset-timer.h"
 
98
#endif
 
99
 
 
100
#ifdef USE_VERSION_CHECK
 
101
#include "version-check-mode.h"
 
102
#endif
 
103
 
 
104
#ifdef USE_BATTCHECK_MODE
 
105
#include "battcheck-mode.h"
 
106
#endif
 
107
 
 
108
#ifdef USE_BEACON_MODE
 
109
#include "beacon-mode.h"
 
110
#endif
 
111
 
 
112
#ifdef USE_THERMAL_REGULATION
 
113
#include "tempcheck-mode.h"
 
114
#endif
 
115
 
 
116
#ifdef USE_LOCKOUT_MODE
 
117
#include "lockout-mode.h"
 
118
#endif
 
119
 
 
120
#ifdef USE_MOMENTARY_MODE
 
121
#include "momentary-mode.h"
 
122
#endif
 
123
 
 
124
#ifdef USE_TACTICAL_MODE
 
125
#include "tactical-mode.h"
 
126
#endif
 
127
 
 
128
// allow the channel mode handler even when only 1 mode
 
129
// (so a tint ramp light could still use 3H even if there's no other mode)
 
130
#if defined(USE_CHANNEL_MODES)
 
131
#include "channel-modes.h"
 
132
#endif
 
133
 
 
134
#ifdef USE_FACTORY_RESET
 
135
#include "factory-reset.h"
 
136
#endif
 
137
 
 
138
// this one detects its own enable/disable settings
 
139
#include "strobe-modes.h"
 
140
 
 
141
#ifdef USE_SOS_MODE
 
142
#include "sos-mode.h"
 
143
#endif
 
144
 
 
145
#ifdef USE_SMOOTH_STEPS
 
146
#include "smooth-steps.h"
 
147
#endif
 
148
 
 
149
// this should be last, so other headers have a chance to declare values
 
150
#include "load-save-config.h"
 
151
 
 
152
 
 
153
/********* Include all the app logic source files *********/
 
154
// (is a bit weird to do things this way,
 
155
//  but it saves a lot of space by letting us use the -fwhole-program flag)
 
156
 
 
157
#include "off-mode.c"
 
158
#include "ramp-mode.c"
 
159
#include "load-save-config.c"
 
160
#include "config-mode.c"
 
161
#include "aux-leds.c"
 
162
#include "misc.c"
 
163
 
 
164
#ifdef USE_SUNSET_TIMER
 
165
#include "sunset-timer.c"
 
166
#endif
 
167
 
 
168
#ifdef USE_VERSION_CHECK
 
169
#include "version-check-mode.c"
 
170
#endif
 
171
 
 
172
#ifdef USE_BATTCHECK_MODE
 
173
#include "battcheck-mode.c"
 
174
#endif
 
175
 
 
176
#ifdef USE_BEACON_MODE
 
177
#include "beacon-mode.c"
 
178
#endif
 
179
 
 
180
#ifdef USE_THERMAL_REGULATION
 
181
#include "tempcheck-mode.c"
 
182
#endif
 
183
 
 
184
#ifdef USE_LOCKOUT_MODE
 
185
#include "lockout-mode.c"
 
186
#endif
 
187
 
 
188
#ifdef USE_MOMENTARY_MODE
 
189
#include "momentary-mode.c"
 
190
#endif
 
191
 
 
192
#ifdef USE_TACTICAL_MODE
 
193
#include "tactical-mode.c"
 
194
#endif
 
195
 
 
196
#if defined(USE_CHANNEL_MODES)
 
197
#include "channel-modes.c"
 
198
#endif
 
199
 
 
200
#ifdef USE_FACTORY_RESET
 
201
#include "factory-reset.c"
 
202
#endif
 
203
 
 
204
#ifdef USE_STROBE_STATE
 
205
#include "strobe-modes.c"
 
206
#endif
 
207
 
 
208
#ifdef USE_SOS_MODE
 
209
#include "sos-mode.c"
 
210
#endif
 
211
 
 
212
#ifdef USE_SMOOTH_STEPS
 
213
#include "smooth-steps.c"
 
214
#endif
 
215
 
 
216
 
 
217
// runs one time at boot, when power is connected
2678
218
void setup() {
2679
 
    #ifdef START_AT_MEMORIZED_LEVEL
2680
 
    // dual switch: e-switch + power clicky
2681
 
    // power clicky acts as a momentary mode
2682
 
    load_config();
2683
 
 
2684
 
    #ifdef USE_MUGGLE_MODE
2685
 
    if (muggle_mode_active)
2686
 
        push_state(muggle_state, memorized_level);
2687
 
    else
2688
 
    #endif
2689
 
    if (button_is_pressed())
2690
 
        // hold button to go to moon
2691
 
        push_state(steady_state, 1);
2692
 
    else
2693
 
        // otherwise use memory
2694
 
        push_state(steady_state, memorized_level);
2695
 
 
2696
 
    #else  // if not START_AT_MEMORIZED_LEVEL
2697
 
 
2698
 
    // blink at power-on to let user know power is connected
2699
 
    set_level(RAMP_SIZE/8);
2700
 
    delay_4ms(3);
2701
 
    set_level(0);
2702
 
 
2703
 
    #ifdef USE_FACTORY_RESET
2704
 
    if (button_is_pressed())
2705
 
        factory_reset();
2706
 
    #endif
2707
 
 
2708
 
    load_config();
2709
 
 
2710
 
    #ifdef USE_TINT_RAMPING
2711
 
    // add tint ramping underneath every other state
2712
 
    push_state(tint_ramping_state, 0);
2713
 
    #endif  // ifdef USE_TINT_RAMPING
2714
 
 
2715
 
    #ifdef USE_MUGGLE_MODE
2716
 
    if (muggle_mode_active)
2717
 
        push_state(muggle_state, (MUGGLE_FLOOR+MUGGLE_CEILING)/2);
2718
 
    else
2719
 
    #endif
 
219
 
 
220
    #ifndef START_AT_MEMORIZED_LEVEL
 
221
 
 
222
        // regular e-switch light, no hard clicky power button
 
223
 
 
224
        // blink at power-on to let user know power is connected
 
225
        blink_once();
 
226
 
 
227
        #ifdef USE_FACTORY_RESET
 
228
        if (button_is_pressed())
 
229
            factory_reset();
 
230
        #endif
 
231
 
 
232
        load_config();
 
233
 
 
234
        #if defined(USE_MANUAL_MEMORY) && defined(USE_MANUAL_MEMORY_TIMER)
 
235
        // without this, initial boot-up brightness is wrong
 
236
        // when manual mem is enabled with a non-zero timer
 
237
        if (cfg.manual_memory) manual_memory_restore();
 
238
        #endif
 
239
 
 
240
        #if defined(USE_CHANNEL_MODES)
 
241
        // add channel mode functions underneath every other state
 
242
        push_state(channel_mode_state, 0);
 
243
        #endif
 
244
 
2720
245
        push_state(off_state, 1);
2721
246
 
 
247
    #else  // if START_AT_MEMORIZED_LEVEL
 
248
 
 
249
        // dual switch: e-switch + power clicky
 
250
        // power clicky acts as a momentary mode
 
251
        load_config();
 
252
 
 
253
        #if defined(USE_CHANNEL_MODES)
 
254
        // add channel mode functions underneath every other state
 
255
        push_state(channel_mode_state, 0);
 
256
        #endif
 
257
 
 
258
        if (button_is_pressed())
 
259
            // hold button to go to moon
 
260
            push_state(steady_state, 1);
 
261
        else
 
262
            // otherwise use memory
 
263
            push_state(steady_state, memorized_level);
 
264
 
2722
265
    #endif  // ifdef START_AT_MEMORIZED_LEVEL
 
266
 
2723
267
}
2724
268
 
2725
269
 
 
270
// runs repeatedly whenever light is "on" (not in standby)
2726
271
void loop() {
2727
272
 
 
273
    // "current_state" is volatile, so cache it to reduce code size
2728
274
    StatePtr state = current_state;
2729
275
 
2730
276
    #ifdef USE_AUX_RGB_LEDS_WHILE_ON
2731
 
        if (! setting_rgb_mode_now) rgb_led_voltage_readout(1);
 
277
    // display battery charge on RGB button during use
 
278
    if (state == steady_state)
 
279
        rgb_led_voltage_readout(actual_level > USE_AUX_RGB_LEDS_WHILE_ON);
2732
280
    #endif
2733
281
 
2734
 
    if (0) {}
 
282
    if (0) {}  // placeholder
2735
283
 
2736
284
    #ifdef USE_VERSION_CHECK
2737
285
    else if (state == version_check_state) {
2738
 
        for (uint8_t i=0; i<sizeof(version_number)-1; i++) {
2739
 
            blink_digit(pgm_read_byte(version_number + i) - '0');
2740
 
            nice_delay_ms(300);
2741
 
        }
2742
 
        // FIXME: when user interrupts with button, "off" takes an extra click
2743
 
        //  before it'll turn back on, because the click to cancel gets sent
2744
 
        //  to the "off" state instead of version_check_state
2745
 
        //while (button_is_pressed()) {}
2746
 
        //empty_event_sequence();
2747
 
 
2748
 
        set_state(off_state, 0);
 
286
        version_check_iter();
2749
287
    }
2750
 
    #endif  // #ifdef USE_VERSION_CHECK
 
288
    #endif
2751
289
 
2752
290
    #ifdef USE_STROBE_STATE
2753
291
    else if ((state == strobe_state)
2754
292
         #ifdef USE_MOMENTARY_MODE
2755
293
         // also handle momentary strobes
2756
 
         ||  ((state == momentary_state) && (momentary_mode == 1) && (momentary_active))
 
294
         || ((
 
295
              (state == momentary_state)
 
296
              #ifdef USE_TACTICAL_MODE
 
297
              || (state == tactical_state)
 
298
              #endif
 
299
             )
 
300
             && (momentary_mode == 1) && (momentary_active))
2757
301
         #endif
2758
302
         ) {
2759
 
        uint8_t st = strobe_type;
2760
 
 
2761
 
        switch(st) {
2762
 
            #if defined(USE_PARTY_STROBE_MODE) || defined(USE_TACTICAL_STROBE_MODE)
2763
 
            #ifdef USE_PARTY_STROBE_MODE
2764
 
            case party_strobe_e:
2765
 
            #endif
2766
 
            #ifdef USE_TACTICAL_STROBE_MODE
2767
 
            case tactical_strobe_e:
2768
 
            #endif
2769
 
                party_tactical_strobe_mode_iter(st);
2770
 
                break;
2771
 
            #endif
2772
 
 
2773
 
            #ifdef USE_LIGHTNING_MODE
2774
 
            case lightning_storm_e:
2775
 
                lightning_storm_iter();
2776
 
                break;
2777
 
            #endif
2778
 
 
2779
 
            #ifdef USE_BIKE_FLASHER_MODE
2780
 
            case bike_flasher_e:
2781
 
                bike_flasher_iter();
2782
 
                break;
2783
 
            #endif
2784
 
        }
2785
 
 
 
303
        strobe_state_iter();
2786
304
    }
2787
305
    #endif  // #ifdef USE_STROBE_STATE
2788
306
 
2789
307
    #ifdef USE_BORING_STROBE_STATE
2790
308
    else if (state == boring_strobe_state) {
2791
 
        switch(boring_strobe_type) {
2792
 
            #ifdef USE_POLICE_STROBE_MODE
2793
 
            case 0: // police strobe
2794
 
                police_strobe_iter();
2795
 
                break;
2796
 
            #endif
2797
 
 
2798
 
            #ifdef USE_SOS_MODE_IN_FF_GROUP
2799
 
            default: // SOS
2800
 
                sos_mode_iter();
2801
 
                break;
2802
 
            #endif
2803
 
        }
 
309
        boring_strobe_state_iter();
2804
310
    }
2805
 
    #endif  // #ifdef USE_BORING_STROBE_STATE
 
311
    #endif
2806
312
 
2807
313
    #ifdef USE_BATTCHECK
2808
314
    else if (state == battcheck_state) {
2809
315
        battcheck();
 
316
        #ifdef USE_SIMPLE_UI
 
317
        // in simple mode, turn off after one readout
 
318
        // FIXME: can eat the next button press
 
319
        //        (state changes in loop() act weird)
 
320
        if (cfg.simple_ui_active) set_state_deferred(off_state, 0);
 
321
        else nice_delay_ms(1000);
 
322
        #endif
 
323
    }
 
324
    #endif
 
325
 
 
326
    #ifdef USE_THERMAL_REGULATION
 
327
    // TODO: blink out therm_ceil during thermal_config_state?
 
328
    else if (state == tempcheck_state) {
 
329
        blink_num(temperature);
 
330
        nice_delay_ms(1000);
2810
331
    }
2811
332
    #endif
2812
333
 
2816
337
    }
2817
338
    #endif
2818
339
 
2819
 
    #ifdef USE_SOS_MODE_IN_BLINKY_GROUP
 
340
    #if defined(USE_SOS_MODE) && defined(USE_SOS_MODE_IN_BLINKY_GROUP)
2820
341
    else if (state == sos_state) {
2821
342
        sos_mode_iter();
2822
343
    }
2823
344
    #endif
2824
345
 
2825
 
    #ifdef USE_THERMAL_REGULATION
2826
 
    // TODO: blink out therm_ceil during thermal_config_state?
2827
 
    else if (state == tempcheck_state) {
2828
 
        blink_num(temperature);
2829
 
        nice_delay_ms(1000);
 
346
    #ifdef USE_SMOOTH_STEPS
 
347
    else if (cfg.smooth_steps_style && smooth_steps_in_progress) {
 
348
        smooth_steps_iter();
2830
349
    }
2831
350
    #endif
2832
351
 
2838
357
    #endif
2839
358
 
2840
359
}
 
360
 
 
361
 
 
362
// instead of handling EV_low_voltage in each mode,
 
363
// it's handled globally here to make the code smaller and simpler
 
364
void low_voltage() {
 
365
 
 
366
    // "current_state" is volatile, so cache it to reduce code size
 
367
    StatePtr state = current_state;
 
368
 
 
369
    // TODO: turn off aux LED(s) when power is really low
 
370
 
 
371
    if (0) {}  // placeholder
 
372
 
 
373
    #ifdef USE_STROBE_STATE
 
374
    // "step down" from strobe to something low
 
375
    else if (state == strobe_state) {
 
376
        set_state(steady_state, RAMP_SIZE/6);
 
377
    }
 
378
    #endif
 
379
 
 
380
    // in normal mode, step down or turn off
 
381
    else if (state == steady_state) {
 
382
        if (actual_level > 1) {
 
383
            uint8_t lvl = (actual_level >> 1) + (actual_level >> 2);
 
384
            set_level_and_therm_target(lvl);
 
385
        }
 
386
        else {
 
387
            set_state(off_state, 0);
 
388
        }
 
389
    }
 
390
    // all other modes, just turn off when voltage is low
 
391
    else {
 
392
        set_state(off_state, 0);
 
393
    }
 
394
 
 
395
}
 
396