~toykeeper/flashlight-firmware/fsm

« back to all changes in this revision

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

  • Committer: Selene Scriven
  • Date: 2018-08-07 02:57:44 UTC
  • mfrom: (351 fsm)
  • mto: This revision was merged to the branch mainline in revision 352.
  • Revision ID: bzr@toykeeper.net-20180807025744-ib9kjf5os4hxqze2
merged upstream fsm branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
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.
 
4
 *
 
5
 * Copyright (C) 2018 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/>.
 
19
 */
 
20
 
 
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
 
27
 
 
28
#define USE_LVP  // FIXME: won't build when this option is turned off
 
29
 
 
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
 
33
 
 
34
// battery readout style (pick one)
 
35
#define BATTCHECK_VpT
 
36
//#define BATTCHECK_8bars  // FIXME: breaks build
 
37
//#define BATTCHECK_4bars  // FIXME: breaks build
 
38
 
 
39
/***** specific settings for known driver types *****/
 
40
#if defined(FSM_BLF_GT_DRIVER)
 
41
#include "cfg-blf-gt.h"
 
42
 
 
43
#elif defined(FSM_BLF_Q8_DRIVER)
 
44
#include "cfg-blf-q8.h"
 
45
 
 
46
#elif defined(FSM_EMISAR_D4_DRIVER)
 
47
#include "cfg-emisar-d4.h"
 
48
 
 
49
#elif defined(FSM_FW3A_DRIVER)
 
50
#include "cfg-fw3a.h"
 
51
 
 
52
#endif
 
53
 
 
54
 
 
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
 
58
#endif
 
59
#ifndef THERM_FASTER_LEVEL
 
60
    #ifdef MAX_Nx7135
 
61
    #define THERM_FASTER_LEVEL MAX_Nx7135  // throttle back faster when high
 
62
    #else
 
63
    #define THERM_FASTER_LEVEL (RAMP_SIZE*4/5)  // throttle back faster when high
 
64
    #endif
 
65
#endif
 
66
#ifdef USE_THERMAL_REGULATION
 
67
#define USE_SET_LEVEL_GRADUALLY  // isn't used except for thermal adjustments
 
68
#endif
 
69
 
 
70
 
 
71
/********* Configure SpaghettiMonster *********/
 
72
#define USE_DELAY_ZERO
 
73
#define USE_RAMPING
 
74
#define RAMP_LENGTH 150  // default, if not overridden in a driver cfg file
 
75
#define USE_BATTCHECK
 
76
#define MAX_CLICKS 4
 
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
 
79
 
 
80
// auto-detect how many eeprom bytes
 
81
#define USE_EEPROM
 
82
#ifdef USE_THERMAL_REGULATION
 
83
#define EEPROM_BYTES 5
 
84
#else
 
85
#define EEPROM_BYTES 3
 
86
#endif
 
87
// for mode memory on tail switch
 
88
#define USE_EEPROM_WL
 
89
#define EEPROM_WL_BYTES 1
 
90
 
 
91
#include "spaghetti-monster.h"
 
92
 
 
93
 
 
94
// FSM states
 
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,
 
99
                          void (*savefunc)());
 
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);
 
105
#ifdef USE_BATTCHECK
 
106
uint8_t battcheck_state(EventPtr event, uint16_t arg);
 
107
#endif
 
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);
 
111
#endif
 
112
 
 
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;
 
117
 
 
118
void blink_confirm(uint8_t num);
 
119
 
 
120
// remember stuff even after battery was changed
 
121
void load_config();
 
122
void save_config();
 
123
void save_config_wl();
 
124
 
 
125
// default ramp options if not overridden earlier per-driver
 
126
#ifndef RAMP_DISCRETE_FLOOR
 
127
  #define RAMP_DISCRETE_FLOOR 1
 
128
#endif
 
129
#ifndef RAMP_DISCRETE_CEIL
 
130
  #define RAMP_DISCRETE_CEIL RAMP_SIZE
 
131
#endif
 
132
#ifndef RAMP_DISCRETE_STEPS
 
133
  #define RAMP_DISCRETE_STEPS 7
 
134
#endif
 
135
 
 
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
 
143
 
 
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);
 
148
 
 
149
#ifdef USE_THERMAL_REGULATION
 
150
// brightness before thermal step-down
 
151
uint8_t target_level = 0;
 
152
#endif
 
153
 
 
154
 
 
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
 
159
        blink_confirm(1);
 
160
        // but otherwise stay off
 
161
        set_level(0);
 
162
        // sleep while off  (lower power use)
 
163
        go_to_standby = 1;
 
164
        return MISCHIEF_MANAGED;
 
165
    }
 
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) {
 
169
            go_to_standby = 1;
 
170
        }
 
171
        return MISCHIEF_MANAGED;
 
172
    }
 
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;
 
177
    }
 
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);
 
184
        }
 
185
        return MISCHIEF_MANAGED;
 
186
    }
 
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;
 
191
    }
 
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;
 
196
    }
 
197
    // 1 click: regular mode
 
198
    else if (event == EV_1click) {
 
199
        set_state(steady_state, memorized_level);
 
200
        return MISCHIEF_MANAGED;
 
201
    }
 
202
    // 2 clicks (initial press): off, to prep for later events
 
203
    else if (event == EV_click2_press) {
 
204
        set_level(0);
 
205
        return MISCHIEF_MANAGED;
 
206
    }
 
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;
 
211
    }
 
212
    // 2 clicks: highest mode
 
213
    else if (event == EV_2clicks) {
 
214
        set_state(steady_state, nearest_level(MAX_LEVEL));
 
215
        return MISCHIEF_MANAGED;
 
216
    }
 
217
    #ifdef USE_BATTCHECK
 
218
    // 3 clicks: battcheck mode / blinky mode group
 
219
    else if (event == EV_3clicks) {
 
220
        set_state(battcheck_state, 0);
 
221
        return MISCHIEF_MANAGED;
 
222
    }
 
223
    #endif
 
224
    // 4 clicks: configure ramp
 
225
    else if (event == EV_4clicks) {
 
226
        push_state(ramp_config_state, 0);
 
227
        return MISCHIEF_MANAGED;
 
228
    }
 
229
    return EVENT_NOT_HANDLED;
 
230
}
 
231
 
 
232
 
 
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;
 
237
 
 
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;
 
243
        }
 
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
 
249
        target_level = arg;
 
250
        #endif
 
251
        set_level(nearest_level(arg));
 
252
        return MISCHIEF_MANAGED;
 
253
    }
 
254
    // click: brighter
 
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;
 
259
        #endif
 
260
        set_level(memorized_level);
 
261
        // make sure next click will respond quickly
 
262
        empty_event_sequence();
 
263
        // remember mode for later
 
264
        save_config_wl();
 
265
        return MISCHIEF_MANAGED;
 
266
    }
 
267
    // hold: dimmer
 
268
    else if (event == EV_click1_hold) {
 
269
        // ramp slower in discrete mode
 
270
        if (arg % HOLD_TIMEOUT != 0) {
 
271
            return MISCHIEF_MANAGED;
 
272
        }
 
273
        memorized_level = nearest_level((int16_t)actual_level - ramp_step_size);
 
274
        #ifdef USE_THERMAL_REGULATION
 
275
        target_level = memorized_level;
 
276
        #endif
 
277
        set_level(memorized_level);
 
278
        return MISCHIEF_MANAGED;
 
279
    }
 
280
    // reverse ramp direction on hold release
 
281
    else if (event == EV_click1_hold_release) {
 
282
        save_config_wl();
 
283
        return MISCHIEF_MANAGED;
 
284
    }
 
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
 
292
        }
 
293
        #ifdef THERM_HARD_TURBO_DROP
 
294
        else if ((! (actual_level < THERM_FASTER_LEVEL))
 
295
                && (actual_level > gradual_target)) {
 
296
            gradual_tick();
 
297
        }
 
298
        else {
 
299
        #endif
 
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};
 
302
        uint8_t diff;
 
303
        static uint8_t ticks_since_adjust = 0;
 
304
        ticks_since_adjust ++;
 
305
        if (gradual_target > actual_level) diff = gradual_target - actual_level;
 
306
        else {
 
307
            diff = actual_level - gradual_target;
 
308
        }
 
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 ++; }
 
313
        #endif
 
314
        while (diff) {
 
315
            magnitude ++;
 
316
            diff >>= 1;
 
317
        }
 
318
        uint8_t ticks_per_adjust = intervals[magnitude];
 
319
        if (ticks_since_adjust > ticks_per_adjust)
 
320
        {
 
321
            gradual_tick();
 
322
            ticks_since_adjust = 0;
 
323
        }
 
324
        //if (!(arg % ticks_per_adjust)) gradual_tick();
 
325
        #ifdef THERM_HARD_TURBO_DROP
 
326
        }
 
327
        #endif
 
328
        #endif
 
329
        return MISCHIEF_MANAGED;
 
330
    }
 
331
    #endif
 
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);
 
339
            #else
 
340
            set_level(THERM_FASTER_LEVEL);
 
341
            #endif
 
342
        } else
 
343
        #endif
 
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);
 
350
            #else
 
351
            set_level(stepdown);
 
352
            #endif
 
353
        }
 
354
        return MISCHIEF_MANAGED;
 
355
    }
 
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);
 
366
            #else
 
367
            set_level(stepup);
 
368
            #endif
 
369
        }
 
370
        return MISCHIEF_MANAGED;
 
371
    }
 
372
    #endif
 
373
    return EVENT_NOT_HANDLED;
 
374
}
 
375
 
 
376
 
 
377
#ifdef USE_BATTCHECK
 
378
uint8_t battcheck_state(EventPtr event, uint16_t arg) {
 
379
    // 1 click: off
 
380
    if (event == EV_1click) {
 
381
        set_state(off_state, 0);
 
382
        return MISCHIEF_MANAGED;
 
383
    }
 
384
    // 2 clicks: tempcheck mode
 
385
    else if (event == EV_2clicks) {
 
386
        blink_confirm(2);
 
387
        set_state(tempcheck_state, 0);
 
388
        return MISCHIEF_MANAGED;
 
389
    }
 
390
    return EVENT_NOT_HANDLED;
 
391
}
 
392
#endif
 
393
 
 
394
#ifdef USE_THERMAL_REGULATION
 
395
uint8_t tempcheck_state(EventPtr event, uint16_t arg) {
 
396
    // 1 click: off
 
397
    if (event == EV_1click) {
 
398
        set_state(off_state, 0);
 
399
        return MISCHIEF_MANAGED;
 
400
    }
 
401
    // 2 clicks: battcheck mode
 
402
    else if (event == EV_2clicks) {
 
403
        blink_confirm(1);
 
404
        set_state(battcheck_state, 0);
 
405
        return MISCHIEF_MANAGED;
 
406
    }
 
407
    // 4 clicks: thermal config mode
 
408
    else if (event == EV_4clicks) {
 
409
        push_state(thermal_config_state, 0);
 
410
        return MISCHIEF_MANAGED;
 
411
    }
 
412
    return EVENT_NOT_HANDLED;
 
413
}
 
414
#endif
 
415
 
 
416
 
 
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) {
 
423
        config_step = 0;
 
424
        set_level(0);
 
425
        return MISCHIEF_MANAGED;
 
426
    }
 
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);
 
431
        }
 
432
        else {
 
433
            // TODO: blink out some sort of success pattern
 
434
            savefunc();
 
435
            save_config();
 
436
            //set_state(retstate, retval);
 
437
            pop_state();
 
438
        }
 
439
        return MISCHIEF_MANAGED;
 
440
    }
 
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;
 
444
        config_step ++;
 
445
        return MISCHIEF_MANAGED;
 
446
    }
 
447
    //return EVENT_NOT_HANDLED;
 
448
    // eat all other events; don't pass any through to parent
 
449
    return EVENT_HANDLED;
 
450
}
 
451
 
 
452
void ramp_config_save() {
 
453
    // parse values
 
454
    uint8_t val;
 
455
 
 
456
    val = config_state_values[0];
 
457
    if (val) { ramp_discrete_floor = val; }
 
458
 
 
459
    val = config_state_values[1];
 
460
    if (val) { ramp_discrete_ceil = MAX_LEVEL + 1 - val; }
 
461
 
 
462
    val = config_state_values[2];
 
463
    if (val) ramp_discrete_steps = val;
 
464
}
 
465
 
 
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);
 
471
}
 
472
 
 
473
 
 
474
#ifdef USE_THERMAL_REGULATION
 
475
void thermal_config_save() {
 
476
    // parse values
 
477
    uint8_t val;
 
478
 
 
479
    // calibrate room temperature
 
480
    val = config_state_values[0];
 
481
    if (val) {
 
482
        int8_t rawtemp = (temperature >> 1) - therm_cal_offset;
 
483
        therm_cal_offset = val - rawtemp;
 
484
    }
 
485
 
 
486
    val = config_state_values[1];
 
487
    if (val) {
 
488
        // set maximum heat limit
 
489
        therm_ceil = 30 + val;
 
490
    }
 
491
    if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL;
 
492
}
 
493
 
 
494
uint8_t thermal_config_state(EventPtr event, uint16_t arg) {
 
495
    return config_state_base(event, arg,
 
496
                             2, thermal_config_save);
 
497
}
 
498
#endif
 
499
 
 
500
 
 
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) {
 
507
        value = 0;
 
508
        blinks_left = arg;
 
509
        entry_step = 0;
 
510
        wait_ticks = 0;
 
511
        return MISCHIEF_MANAGED;
 
512
    }
 
513
    // advance through the process:
 
514
    // 0: wait a moment
 
515
    // 1: blink out the 'arg' value
 
516
    // 2: wait a moment
 
517
    // 3: "buzz" while counting clicks
 
518
    // 4: save and exit
 
519
    else if (event == EV_tick) {
 
520
        // wait a moment
 
521
        if ((entry_step == 0) || (entry_step == 2)) {
 
522
            if (wait_ticks < TICKS_PER_SECOND/2)
 
523
                wait_ticks ++;
 
524
            else {
 
525
                entry_step ++;
 
526
                wait_ticks = 0;
 
527
            }
 
528
        }
 
529
        // blink out the option number
 
530
        else if (entry_step == 1) {
 
531
            if (blinks_left) {
 
532
                if ((wait_ticks & 31) == 10) {
 
533
                    set_level(RAMP_SIZE/4);
 
534
                }
 
535
                else if ((wait_ticks & 31) == 20) {
 
536
                    set_level(0);
 
537
                }
 
538
                else if ((wait_ticks & 31) == 31) {
 
539
                    blinks_left --;
 
540
                }
 
541
                wait_ticks ++;
 
542
            }
 
543
            else {
 
544
                entry_step ++;
 
545
                wait_ticks = 0;
 
546
            }
 
547
        }
 
548
        else if (entry_step == 3) {  // buzz while waiting for a number to be entered
 
549
            wait_ticks ++;
 
550
            // buzz for N seconds after last event
 
551
            if ((wait_ticks & 3) == 0) {
 
552
                set_level(RAMP_SIZE/6);
 
553
            }
 
554
            else if ((wait_ticks & 3) == 2) {
 
555
                set_level(RAMP_SIZE/8);
 
556
            }
 
557
            // time out after 3 seconds
 
558
            if (wait_ticks > TICKS_PER_SECOND*3) {
 
559
                //number_entry_value = value;
 
560
                set_level(0);
 
561
                entry_step ++;
 
562
            }
 
563
        }
 
564
        else if (entry_step == 4) {
 
565
            number_entry_value = value;
 
566
            pop_state();
 
567
        }
 
568
        return MISCHIEF_MANAGED;
 
569
    }
 
570
    // count clicks
 
571
    else if (event == EV_click1_release) {
 
572
        empty_event_sequence();
 
573
        if (entry_step == 3) {  // only count during the "buzz"
 
574
            value ++;
 
575
            wait_ticks = 0;
 
576
            // flash briefly
 
577
            set_level(RAMP_SIZE/2);
 
578
            delay_4ms(8/2);
 
579
            set_level(0);
 
580
        }
 
581
        return MISCHIEF_MANAGED;
 
582
    }
 
583
    return EVENT_NOT_HANDLED;
 
584
}
 
585
 
 
586
 
 
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) {
 
590
    // bounds check
 
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;
 
597
 
 
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;
 
601
 
 
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))
 
607
            return this_level;
 
608
    }
 
609
    return this_level;
 
610
}
 
611
 
 
612
 
 
613
void blink_confirm(uint8_t num) {
 
614
    for (; num>0; num--) {
 
615
        set_level(MAX_LEVEL/4);
 
616
        delay_4ms(10/4);
 
617
        set_level(0);
 
618
        delay_4ms(100/4);
 
619
    }
 
620
}
 
621
 
 
622
 
 
623
void load_config() {
 
624
    if (load_eeprom()) {
 
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];
 
631
        #endif
 
632
    }
 
633
    if (load_eeprom_wl()) {
 
634
        memorized_level = eeprom_wl[0];
 
635
    }
 
636
}
 
637
 
 
638
 
 
639
void save_config() {
 
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;
 
646
    #endif
 
647
 
 
648
    save_eeprom();
 
649
}
 
650
 
 
651
 
 
652
void save_config_wl() {
 
653
    eeprom_wl[0] = memorized_level;
 
654
    save_eeprom_wl();
 
655
}
 
656
 
 
657
 
 
658
void low_voltage() {
 
659
    StatePtr state = current_state;
 
660
 
 
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);
 
665
            set_level(lvl);
 
666
            #ifdef USE_THERMAL_REGULATION
 
667
            target_level = lvl;
 
668
            #endif
 
669
        }
 
670
        else {
 
671
            set_state(off_state, 0);
 
672
        }
 
673
    }
 
674
    // all other modes, just turn off when voltage is low
 
675
    else {
 
676
        set_state(off_state, 0);
 
677
    }
 
678
}
 
679
 
 
680
 
 
681
void setup() {
 
682
    // dual switch: e-switch + power clicky
 
683
    // power clicky acts as a momentary mode
 
684
    load_config();
 
685
 
 
686
    if (button_is_pressed())
 
687
        // hold button to go to moon
 
688
        push_state(off_state, 0);
 
689
    else
 
690
        // otherwise use memory
 
691
        push_state(steady_state, memorized_level);
 
692
}
 
693
 
 
694
 
 
695
void loop() {
 
696
 
 
697
    StatePtr state = current_state;
 
698
 
 
699
    #ifdef USE_DYNAMIC_UNDERCLOCKING
 
700
    auto_clock_speed();
 
701
    #endif
 
702
    if (0) {}
 
703
 
 
704
    #ifdef USE_BATTCHECK
 
705
    else if (state == battcheck_state) {
 
706
        battcheck();
 
707
    }
 
708
    #endif
 
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);
 
713
        nice_delay_ms(1000);
 
714
    }
 
715
    #endif
 
716
 
 
717
    #ifdef USE_IDLE_MODE
 
718
    else {
 
719
        // doze until next clock tick
 
720
        idle_mode();
 
721
    }
 
722
    #endif
 
723
 
 
724
}