~toykeeper/flashlight-firmware/anduril2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
// config-mode.c: Config mode base functions for Anduril.
// Copyright (C) 2017-2023 Selene ToyKeeper
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include "config-mode.h"

// general helper function for config modes
uint8_t number_entry_state(Event event, uint16_t arg);
// return value from number_entry_state()
volatile uint8_t number_entry_value;


#if defined(USE_CONFIG_COLORS) && (NUM_CHANNEL_MODES > 1)
// TODO: promote this to fsm-channels.c ?
void set_chan_if(bool cond, uint8_t chan) {
    if ((cond) && (chan != channel_mode))
        set_channel_mode(chan);
}
#endif

// allow the user to set a new value for a config option
// can be called two ways:
//   - with a "click" action: Configures first menu item only.
//   - with a "hold" action: Sets user select a menu item and then
//     choose a new value for it.  User should hold button until light
//     blinks N times, to choose menu item N.  Then let go, and light
//     should give a buzzing prompt to enter a number.  Click N times
//     at the prompt to set the new value to N.
// after completing this process, config state calls the savefunc callback
// and then returns to caller/parent state
uint8_t config_state_base(
        Event event,
        uint16_t arg,
        uint8_t num_config_steps,
        void (*savefunc)(uint8_t step, uint8_t value)) {

    static uint8_t config_step;
    #ifdef USE_CONFIG_COLORS
    static uint8_t orig_channel;
    #endif
    if (event == EV_enter_state) {
        #if defined(USE_CONFIG_COLORS) && (NUM_CHANNEL_MODES > 1)
        orig_channel = channel_mode;
        #endif
        config_step = 0;
        set_level(0);
        // if button isn't held, configure first menu item
        if (! button_last_state) {
            config_step ++;
            push_state(number_entry_state, 0);
        }
    }

    // if initial "hold" event still active
    // blink while holding to indicate option number
    #define B_CLICK_FLAGS      (B_CLICK|B_HOLD|B_PRESS|B_RELEASE|B_TIMEOUT)
    #define B_ANY_HOLD         (B_CLICK|B_HOLD|B_PRESS)
    #define B_ANY_HOLD_RELEASE (B_CLICK|B_HOLD|B_RELEASE|B_TIMEOUT)
    else if ((event & B_CLICK_FLAGS) == B_ANY_HOLD) {
        if (config_step <= num_config_steps) {
            #if defined(USE_CONFIG_COLORS) && (NUM_CHANNEL_MODES > 1)
                uint8_t chan = config_step - 1;
                if (chan < NUM_CHANNEL_MODES)
                    set_chan_if(config_color_per_step, chan);
            #endif
            if ((TICKS_PER_SECOND/10) == (arg % (TICKS_PER_SECOND*3/2))) {
                config_step ++;
                // blink when config step advances
                if (config_step <= num_config_steps) {
                    #ifdef CONFIG_BLINK_CHANNEL
                    set_chan_if(!config_color_per_step, CONFIG_BLINK_CHANNEL);
                    #endif
                    set_level(RAMP_SIZE * 3 / 8);
                }
            }
            else {
                // stay on at a low level to indicate menu is active
                #ifdef CONFIG_WAITING_CHANNEL
                set_chan_if(!config_color_per_step, CONFIG_WAITING_CHANNEL);
                #endif
                set_level(RAMP_SIZE * 1 / 8);
            }
        } else {
            // turn off when end of menu is reached
            set_level(0);
        }
    }

    // button release: activate number entry for one menu item
    else if ((event & B_CLICK_FLAGS) == B_ANY_HOLD_RELEASE) {
        // ask the user for a number, if they selected a menu item
        if (config_step && config_step <= num_config_steps) {
            #if defined(USE_CONFIG_COLORS) && (NUM_CHANNEL_MODES > 1)
                // put the colors back how they were
                set_channel_mode(orig_channel);
            #endif
            push_state(number_entry_state, 0);
        }
        // exit after falling out of end of menu
        else {
            pop_state();
        }
    }

    // an option was set (return from number_entry_state)
    else if (event == EV_reenter_state) {
        // process value with parent's callback
        savefunc(config_step, number_entry_value);
        // make changes persist in eeprom
        save_config();
        pop_state();
    }

    #if defined(USE_CONFIG_COLORS) && (NUM_CHANNEL_MODES > 1)
    else if (event == EV_leave_state) {
        // put the colors back how they were
        set_channel_mode(orig_channel);
    }
    #endif

    // eat all other events; don't pass any through to parent
    return EVENT_HANDLED;
}

uint8_t number_entry_state(Event event, uint16_t arg) {
    static uint8_t entry_step;

    if (event == EV_enter_state) {
        number_entry_value = 0;
        entry_step = 0;
        set_level(0);  // initial pause should be dark
    }

    // advance through the process:
    // 0: wait a moment
    // 1: "buzz" while counting clicks
    // 2: save and exit
    else if (event == EV_tick) {
        // wait a moment
        if (entry_step == 0) {
            if (arg > TICKS_PER_SECOND/2) {
                entry_step ++;
                empty_event_sequence();  // reset tick counter to 0
            }
        }
        // buzz while waiting for a number to be entered
        else if (entry_step == 1) {
            // time out and exit after 3 seconds
            if (arg > TICKS_PER_SECOND*3) {
                entry_step ++;
                set_level(0);
            }
            // buzz for N seconds after last event
            // (flicker every other frame,
            //  except first frame (so we can see flashes after each click))
            else if (arg) {
                #ifdef CONFIG_WAITING_CHANNEL
                set_chan_if(1, CONFIG_WAITING_CHANNEL);
                #endif
                set_level( (RAMP_SIZE/8)
                           + ((arg&2)<<2) );
            }
        }
        // all done, save result and return to parent state
        else {
            pop_state();
        }
        return EVENT_HANDLED;
    }

    // count clicks: click = +1, hold = +10
    else if ((event == EV_click1_release)
            #ifdef USE_NUMBER_ENTRY_PLUS10
            || (event == EV_click1_hold_release)
            #endif
            ) {
        entry_step = 1;  // in case user clicked during initial delay
        #ifdef USE_NUMBER_ENTRY_PLUS10
            if (event == EV_click1_hold_release) number_entry_value += 10;
            else
        #endif
        number_entry_value ++;  // update the result
        empty_event_sequence();  // reset FSM's click count
        #ifdef CONFIG_BLINK_CHANNEL
        set_channel_mode(CONFIG_BLINK_CHANNEL);
        #endif
        set_level(RAMP_SIZE/2);  // flash briefly
        return EVENT_HANDLED;
    }

    // eat all other events; don't pass any through to parent
    return EVENT_HANDLED;
}