2
* Werner: Werner-style dual-switch UI for SpaghettiMonster.
3
* Side click to go up, side hold to go down, tail click for on/off.
5
* Copyright (C) 2018 Selene Scriven
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.
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.
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/>.
21
/********* User-configurable options *********/
22
// Physical driver type (uncomment one of the following or define it at the gcc command line)
23
//#define FSM_EMISAR_D4_DRIVER
24
//#define FSM_BLF_Q8_DRIVER
25
//#define FSM_FW3A_DRIVER
26
//#define FSM_BLF_GT_DRIVER
28
#define USE_LVP // FIXME: won't build when this option is turned off
30
// parameters for this defined below or per-driver
31
#define USE_THERMAL_REGULATION
32
#define DEFAULT_THERM_CEIL 45 // try not to get hotter than this
34
// battery readout style (pick one)
36
//#define BATTCHECK_8bars // FIXME: breaks build
37
//#define BATTCHECK_4bars // FIXME: breaks build
39
/***** specific settings for known driver types *****/
40
#if defined(FSM_BLF_GT_DRIVER)
41
#include "cfg-blf-gt.h"
43
#elif defined(FSM_BLF_Q8_DRIVER)
44
#include "cfg-blf-q8.h"
46
#elif defined(FSM_EMISAR_D4_DRIVER)
47
#include "cfg-emisar-d4.h"
49
#elif defined(FSM_FW3A_DRIVER)
55
// thermal properties, if not defined per-driver
56
#ifndef MIN_THERM_STEPDOWN
57
#define MIN_THERM_STEPDOWN MAX_1x7135 // lowest value it'll step down to
59
#ifndef THERM_FASTER_LEVEL
61
#define THERM_FASTER_LEVEL MAX_Nx7135 // throttle back faster when high
63
#define THERM_FASTER_LEVEL (RAMP_SIZE*4/5) // throttle back faster when high
66
#ifdef USE_THERMAL_REGULATION
67
#define USE_SET_LEVEL_GRADUALLY // isn't used except for thermal adjustments
71
/********* Configure SpaghettiMonster *********/
72
#define USE_DELAY_ZERO
74
#define RAMP_LENGTH 150 // default, if not overridden in a driver cfg file
77
#define USE_IDLE_MODE // reduce power use while awake and no tasks are pending
78
#define USE_DYNAMIC_UNDERCLOCKING // cut clock speed at very low modes for better efficiency
80
// auto-detect how many eeprom bytes
82
#ifdef USE_THERMAL_REGULATION
83
#define EEPROM_BYTES 5
85
#define EEPROM_BYTES 3
87
// for mode memory on tail switch
89
#define EEPROM_WL_BYTES 1
91
#include "spaghetti-monster.h"
95
uint8_t off_state(EventPtr event, uint16_t arg);
96
// simple numeric entry config menu
97
uint8_t config_state_base(EventPtr event, uint16_t arg,
98
uint8_t num_config_steps,
100
#define MAX_CONFIG_VALUES 3
101
uint8_t config_state_values[MAX_CONFIG_VALUES];
102
// ramping mode and its related config mode
103
uint8_t steady_state(EventPtr event, uint16_t arg);
104
uint8_t ramp_config_state(EventPtr event, uint16_t arg);
106
uint8_t battcheck_state(EventPtr event, uint16_t arg);
108
#ifdef USE_THERMAL_REGULATION
109
uint8_t tempcheck_state(EventPtr event, uint16_t arg);
110
uint8_t thermal_config_state(EventPtr event, uint16_t arg);
113
// general helper function for config modes
114
uint8_t number_entry_state(EventPtr event, uint16_t arg);
115
// return value from number_entry_state()
116
volatile uint8_t number_entry_value;
118
void blink_confirm(uint8_t num);
120
// remember stuff even after battery was changed
123
void save_config_wl();
125
// default ramp options if not overridden earlier per-driver
126
#ifndef RAMP_DISCRETE_FLOOR
127
#define RAMP_DISCRETE_FLOOR 1
129
#ifndef RAMP_DISCRETE_CEIL
130
#define RAMP_DISCRETE_CEIL RAMP_SIZE
132
#ifndef RAMP_DISCRETE_STEPS
133
#define RAMP_DISCRETE_STEPS 7
136
// brightness control
137
uint8_t memorized_level = MAX_1x7135;
138
// smooth vs discrete ramping
139
volatile uint8_t ramp_discrete_floor = RAMP_DISCRETE_FLOOR;
140
volatile uint8_t ramp_discrete_ceil = RAMP_DISCRETE_CEIL;
141
volatile uint8_t ramp_discrete_steps = RAMP_DISCRETE_STEPS;
142
uint8_t ramp_discrete_step_size; // don't set this
144
// calculate the nearest ramp level which would be valid at the moment
145
// (is a no-op for smooth ramp, but limits discrete ramp to only the
146
// correct levels for the user's config)
147
uint8_t nearest_level(int16_t target);
149
#ifdef USE_THERMAL_REGULATION
150
// brightness before thermal step-down
151
uint8_t target_level = 0;
155
uint8_t off_state(EventPtr event, uint16_t arg) {
156
// turn emitter off when entering state
157
if ((event == EV_enter_state) || (event == EV_reenter_state)) {
158
// let the user know the power is connected
160
// but otherwise stay off
162
// sleep while off (lower power use)
164
return MISCHIEF_MANAGED;
166
// go back to sleep eventually if we got bumped but didn't leave "off" state
167
else if (event == EV_tick) {
168
if (arg > TICKS_PER_SECOND*2) {
171
return MISCHIEF_MANAGED;
173
// hold (initially): go to lowest level, but allow abort for regular click
174
else if (event == EV_click1_press) {
175
set_level(nearest_level(1));
176
return MISCHIEF_MANAGED;
178
// hold: go to lowest level
179
else if (event == EV_click1_hold) {
180
// don't start ramping immediately;
181
// give the user time to release at moon level
182
if (arg >= HOLD_TIMEOUT) {
183
set_state(steady_state, 1);
185
return MISCHIEF_MANAGED;
187
// hold, release quickly: go to lowest level
188
else if (event == EV_click1_hold_release) {
189
set_state(steady_state, 1);
190
return MISCHIEF_MANAGED;
192
// 1 click (before timeout): go to memorized level, but allow abort for double click
193
else if (event == EV_click1_release) {
194
set_level(nearest_level(memorized_level));
195
return MISCHIEF_MANAGED;
197
// 1 click: regular mode
198
else if (event == EV_1click) {
199
set_state(steady_state, memorized_level);
200
return MISCHIEF_MANAGED;
202
// 2 clicks (initial press): off, to prep for later events
203
else if (event == EV_click2_press) {
205
return MISCHIEF_MANAGED;
207
// click, hold: go to highest level (for ramping down)
208
else if (event == EV_click2_hold) {
209
set_state(steady_state, MAX_LEVEL);
210
return MISCHIEF_MANAGED;
212
// 2 clicks: highest mode
213
else if (event == EV_2clicks) {
214
set_state(steady_state, nearest_level(MAX_LEVEL));
215
return MISCHIEF_MANAGED;
218
// 3 clicks: battcheck mode / blinky mode group
219
else if (event == EV_3clicks) {
220
set_state(battcheck_state, 0);
221
return MISCHIEF_MANAGED;
224
// 4 clicks: configure ramp
225
else if (event == EV_4clicks) {
226
push_state(ramp_config_state, 0);
227
return MISCHIEF_MANAGED;
229
return EVENT_NOT_HANDLED;
233
uint8_t steady_state(EventPtr event, uint16_t arg) {
234
uint8_t mode_min = ramp_discrete_floor;
235
uint8_t mode_max = ramp_discrete_ceil;
236
uint8_t ramp_step_size = ramp_discrete_step_size;
238
// turn LED on when we first enter the mode
239
if ((event == EV_enter_state) || (event == EV_reenter_state)) {
240
// if we just got back from config mode, go back to memorized level
241
if (event == EV_reenter_state) {
242
arg = memorized_level;
244
// remember this level, unless it's moon or turbo
245
if ((arg > mode_min) && (arg < mode_max))
246
memorized_level = arg;
247
// use the requested level even if not memorized
248
#ifdef USE_THERMAL_REGULATION
251
set_level(nearest_level(arg));
252
return MISCHIEF_MANAGED;
255
else if (event == EV_click1_release) {
256
memorized_level = nearest_level((int16_t)actual_level + ramp_step_size);
257
#ifdef USE_THERMAL_REGULATION
258
target_level = memorized_level;
260
set_level(memorized_level);
261
// make sure next click will respond quickly
262
empty_event_sequence();
263
// remember mode for later
265
return MISCHIEF_MANAGED;
268
else if (event == EV_click1_hold) {
269
// ramp slower in discrete mode
270
if (arg % HOLD_TIMEOUT != 0) {
271
return MISCHIEF_MANAGED;
273
memorized_level = nearest_level((int16_t)actual_level - ramp_step_size);
274
#ifdef USE_THERMAL_REGULATION
275
target_level = memorized_level;
277
set_level(memorized_level);
278
return MISCHIEF_MANAGED;
280
// reverse ramp direction on hold release
281
else if (event == EV_click1_hold_release) {
283
return MISCHIEF_MANAGED;
285
#if defined(USE_SET_LEVEL_GRADUALLY)
286
// gradual thermal regulation
287
else if (event == EV_tick) {
288
#ifdef USE_SET_LEVEL_GRADUALLY
289
// make thermal adjustment speed scale with magnitude
290
if ((arg & 1) && (actual_level < THERM_FASTER_LEVEL)) {
291
return MISCHIEF_MANAGED; // adjust slower when not a high mode
293
#ifdef THERM_HARD_TURBO_DROP
294
else if ((! (actual_level < THERM_FASTER_LEVEL))
295
&& (actual_level > gradual_target)) {
300
// [int(62*4 / (x**0.95)) for x in (1,2,4,8,16,32,64,128)]
301
uint8_t intervals[] = {248, 128, 66, 34, 17, 9, 4, 2};
303
static uint8_t ticks_since_adjust = 0;
304
ticks_since_adjust ++;
305
if (gradual_target > actual_level) diff = gradual_target - actual_level;
307
diff = actual_level - gradual_target;
309
uint8_t magnitude = 0;
310
#ifndef THERM_HARD_TURBO_DROP
311
// if we're on a really high mode, drop faster
312
if (actual_level >= THERM_FASTER_LEVEL) { magnitude ++; }
318
uint8_t ticks_per_adjust = intervals[magnitude];
319
if (ticks_since_adjust > ticks_per_adjust)
322
ticks_since_adjust = 0;
324
//if (!(arg % ticks_per_adjust)) gradual_tick();
325
#ifdef THERM_HARD_TURBO_DROP
329
return MISCHIEF_MANAGED;
332
#ifdef USE_THERMAL_REGULATION
333
// overheating: drop by an amount proportional to how far we are above the ceiling
334
else if (event == EV_temperature_high) {
335
#ifdef THERM_HARD_TURBO_DROP
336
if (actual_level > THERM_FASTER_LEVEL) {
337
#ifdef USE_SET_LEVEL_GRADUALLY
338
set_level_gradually(THERM_FASTER_LEVEL);
340
set_level(THERM_FASTER_LEVEL);
344
if (actual_level > MIN_THERM_STEPDOWN) {
345
int16_t stepdown = actual_level - arg;
346
if (stepdown < MIN_THERM_STEPDOWN) stepdown = MIN_THERM_STEPDOWN;
347
else if (stepdown > MAX_LEVEL) stepdown = MAX_LEVEL;
348
#ifdef USE_SET_LEVEL_GRADUALLY
349
set_level_gradually(stepdown);
354
return MISCHIEF_MANAGED;
356
// underheating: increase slowly if we're lower than the target
357
// (proportional to how low we are)
358
else if (event == EV_temperature_low) {
359
if (actual_level < target_level) {
360
//int16_t stepup = actual_level + (arg>>1);
361
int16_t stepup = actual_level + arg;
362
if (stepup > target_level) stepup = target_level;
363
else if (stepup < MIN_THERM_STEPDOWN) stepup = MIN_THERM_STEPDOWN;
364
#ifdef USE_SET_LEVEL_GRADUALLY
365
set_level_gradually(stepup);
370
return MISCHIEF_MANAGED;
373
return EVENT_NOT_HANDLED;
378
uint8_t battcheck_state(EventPtr event, uint16_t arg) {
380
if (event == EV_1click) {
381
set_state(off_state, 0);
382
return MISCHIEF_MANAGED;
384
// 2 clicks: tempcheck mode
385
else if (event == EV_2clicks) {
387
set_state(tempcheck_state, 0);
388
return MISCHIEF_MANAGED;
390
return EVENT_NOT_HANDLED;
394
#ifdef USE_THERMAL_REGULATION
395
uint8_t tempcheck_state(EventPtr event, uint16_t arg) {
397
if (event == EV_1click) {
398
set_state(off_state, 0);
399
return MISCHIEF_MANAGED;
401
// 2 clicks: battcheck mode
402
else if (event == EV_2clicks) {
404
set_state(battcheck_state, 0);
405
return MISCHIEF_MANAGED;
407
// 4 clicks: thermal config mode
408
else if (event == EV_4clicks) {
409
push_state(thermal_config_state, 0);
410
return MISCHIEF_MANAGED;
412
return EVENT_NOT_HANDLED;
417
// ask the user for a sequence of numbers, then save them and return to caller
418
uint8_t config_state_base(EventPtr event, uint16_t arg,
419
uint8_t num_config_steps,
420
void (*savefunc)()) {
421
static uint8_t config_step;
422
if (event == EV_enter_state) {
425
return MISCHIEF_MANAGED;
427
// advance forward through config steps
428
else if (event == EV_tick) {
429
if (config_step < num_config_steps) {
430
push_state(number_entry_state, config_step + 1);
433
// TODO: blink out some sort of success pattern
436
//set_state(retstate, retval);
439
return MISCHIEF_MANAGED;
441
// an option was set (return from number_entry_state)
442
else if (event == EV_reenter_state) {
443
config_state_values[config_step] = number_entry_value;
445
return MISCHIEF_MANAGED;
447
//return EVENT_NOT_HANDLED;
448
// eat all other events; don't pass any through to parent
449
return EVENT_HANDLED;
452
void ramp_config_save() {
456
val = config_state_values[0];
457
if (val) { ramp_discrete_floor = val; }
459
val = config_state_values[1];
460
if (val) { ramp_discrete_ceil = MAX_LEVEL + 1 - val; }
462
val = config_state_values[2];
463
if (val) ramp_discrete_steps = val;
466
uint8_t ramp_config_state(EventPtr event, uint16_t arg) {
467
uint8_t num_config_steps;
468
num_config_steps = 3;
469
return config_state_base(event, arg,
470
num_config_steps, ramp_config_save);
474
#ifdef USE_THERMAL_REGULATION
475
void thermal_config_save() {
479
// calibrate room temperature
480
val = config_state_values[0];
482
int8_t rawtemp = (temperature >> 1) - therm_cal_offset;
483
therm_cal_offset = val - rawtemp;
486
val = config_state_values[1];
488
// set maximum heat limit
489
therm_ceil = 30 + val;
491
if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL;
494
uint8_t thermal_config_state(EventPtr event, uint16_t arg) {
495
return config_state_base(event, arg,
496
2, thermal_config_save);
501
uint8_t number_entry_state(EventPtr event, uint16_t arg) {
502
static uint8_t value;
503
static uint8_t blinks_left;
504
static uint8_t entry_step;
505
static uint16_t wait_ticks;
506
if (event == EV_enter_state) {
511
return MISCHIEF_MANAGED;
513
// advance through the process:
515
// 1: blink out the 'arg' value
517
// 3: "buzz" while counting clicks
519
else if (event == EV_tick) {
521
if ((entry_step == 0) || (entry_step == 2)) {
522
if (wait_ticks < TICKS_PER_SECOND/2)
529
// blink out the option number
530
else if (entry_step == 1) {
532
if ((wait_ticks & 31) == 10) {
533
set_level(RAMP_SIZE/4);
535
else if ((wait_ticks & 31) == 20) {
538
else if ((wait_ticks & 31) == 31) {
548
else if (entry_step == 3) { // buzz while waiting for a number to be entered
550
// buzz for N seconds after last event
551
if ((wait_ticks & 3) == 0) {
552
set_level(RAMP_SIZE/6);
554
else if ((wait_ticks & 3) == 2) {
555
set_level(RAMP_SIZE/8);
557
// time out after 3 seconds
558
if (wait_ticks > TICKS_PER_SECOND*3) {
559
//number_entry_value = value;
564
else if (entry_step == 4) {
565
number_entry_value = value;
568
return MISCHIEF_MANAGED;
571
else if (event == EV_click1_release) {
572
empty_event_sequence();
573
if (entry_step == 3) { // only count during the "buzz"
577
set_level(RAMP_SIZE/2);
581
return MISCHIEF_MANAGED;
583
return EVENT_NOT_HANDLED;
587
// find the ramp level closest to the target,
588
// using only the levels which are allowed in the current state
589
uint8_t nearest_level(int16_t target) {
591
// using int16_t here saves us a bunch of logic elsewhere,
592
// by allowing us to correct for numbers < 0 or > 255 in one central place
593
uint8_t mode_min = ramp_discrete_floor;
594
uint8_t mode_max = ramp_discrete_ceil;
595
if (target < mode_min) return mode_min;
596
if (target > mode_max) return mode_max;
598
uint8_t ramp_range = ramp_discrete_ceil - ramp_discrete_floor;
599
ramp_discrete_step_size = ramp_range / (ramp_discrete_steps-1);
600
uint8_t this_level = ramp_discrete_floor;
602
for(uint8_t i=0; i<ramp_discrete_steps; i++) {
603
this_level = ramp_discrete_floor + (i * (uint16_t)ramp_range / (ramp_discrete_steps-1));
604
int8_t diff = target - this_level;
605
if (diff < 0) diff = -diff;
606
if (diff <= (ramp_discrete_step_size>>1))
613
void blink_confirm(uint8_t num) {
614
for (; num>0; num--) {
615
set_level(MAX_LEVEL/4);
625
ramp_discrete_floor = eeprom[0];
626
ramp_discrete_ceil = eeprom[1];
627
ramp_discrete_steps = eeprom[2];
628
#ifdef USE_THERMAL_REGULATION
629
therm_ceil = eeprom[3];
630
therm_cal_offset = eeprom[4];
633
if (load_eeprom_wl()) {
634
memorized_level = eeprom_wl[0];
640
eeprom[0] = ramp_discrete_floor;
641
eeprom[1] = ramp_discrete_ceil;
642
eeprom[2] = ramp_discrete_steps;
643
#ifdef USE_THERMAL_REGULATION
644
eeprom[3] = therm_ceil;
645
eeprom[4] = therm_cal_offset;
652
void save_config_wl() {
653
eeprom_wl[0] = memorized_level;
659
StatePtr state = current_state;
661
// in normal mode, step down or turn off
662
if (state == steady_state) {
663
if (actual_level > 1) {
664
uint8_t lvl = (actual_level >> 1) + (actual_level >> 2);
666
#ifdef USE_THERMAL_REGULATION
671
set_state(off_state, 0);
674
// all other modes, just turn off when voltage is low
676
set_state(off_state, 0);
682
// dual switch: e-switch + power clicky
683
// power clicky acts as a momentary mode
686
if (button_is_pressed())
687
// hold button to go to moon
688
push_state(off_state, 0);
690
// otherwise use memory
691
push_state(steady_state, memorized_level);
697
StatePtr state = current_state;
699
#ifdef USE_DYNAMIC_UNDERCLOCKING
705
else if (state == battcheck_state) {
709
#ifdef USE_THERMAL_REGULATION
710
// TODO: blink out therm_ceil during thermal_config_state
711
else if (state == tempcheck_state) {
712
blink_num(temperature>>1);
719
// doze until next clock tick