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
197
198
199
200
201
// Copyright 2021 Zion Koyl
// Copyright 2021-2022 Jacob Alexander
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

use core::ops::Not;

#[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum State {
    On,
    Off,
}

impl Not for State {
    type Output = Self;

    fn not(self) -> Self::Output {
        match self {
            State::On => State::Off,
            State::Off => State::On,
        }
    }
}

/// The KeyState handles all of the decision making and state changes based on a high or low signal from a GPIO pin
#[derive(Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct KeyState<
    const CSIZE: usize,
    const SCAN_PERIOD_US: u32,
    const DEBOUNCE_US: u32,
    const IDLE_MS: u32,
> {
    /// Most recently GPIO reading (not debounced)
    raw_state: State,

    /// True key state after debounce processing (debounced)
    state: State,

    /// Used to determine if the key is idle (in Off state for IDLE_MS)
    idle: bool,

    /// Tracking bounce
    debounce_tracking: bool,

    /// Used to determine the state after debounce
    /// Increments if a value is not what's set as state.
    /// Decrements if the value is already set as state.
    /// If positive set to On
    /// If negative set to Off
    /// This value is reset to 0 after setting the new state
    raw_state_average: i32,

    /// Used to track the number of cycles since state has changed.
    cycles_since_state_change: u32,

    /// This is used to track the list GPIO read bounce
    ///
    /// If cycles * scan_period > DEBOUNCE_US then raw_state is assigned to state.
    cycles_since_last_bounce: u32,
}

impl<const CSIZE: usize, const SCAN_PERIOD_US: u32, const DEBOUNCE_US: u32, const IDLE_MS: u32>
    KeyState<CSIZE, SCAN_PERIOD_US, DEBOUNCE_US, IDLE_MS>
{
    pub fn new() -> Self {
        Self {
            raw_state: State::Off,
            state: State::Off,
            idle: false,
            debounce_tracking: false,
            raw_state_average: 0,
            cycles_since_state_change: 0,
            cycles_since_last_bounce: 0,
        }
    }

    /// Record the GPIO read event and adjust debounce state machine accordingly
    ///
    /// Returns:
    /// (State, idle, cycles_since_state_change)
    pub fn record(&mut self, on: bool) -> (State, bool, u32) {
        // Track raw state average
        // This is used to set the new state
        if self.debounce_tracking {
            if on && self.state == State::Off || !on && self.state == State::On {
                self.raw_state_average += 1;
            } else {
                self.raw_state_average -= 1;
            }
        }

        // Update the raw state as a bounce event if not the same as the previous scan iteration
        // e.g. GPIO read value has changed since the last iteration
        if on && self.raw_state == State::Off || !on && self.raw_state == State::On {
            // Update raw state
            self.raw_state = if on { State::On } else { State::Off };

            // Reset bounce cycle counter
            self.cycles_since_last_bounce = 0;

            // Start debounce tracking (if we haven't already started)
            self.debounce_tracking = true;
            self.raw_state_average = 0; // Reset average

            // Return current state
            return self.state();
        }

        // Increment debounce cycle counter
        self.cycles_since_last_bounce += 1;

        // If we've hit max, don't wrap-around to 0
        // Set to the defined bounce limit so not to affect any logic
        if self.cycles_since_last_bounce >= u32::MAX / SCAN_PERIOD_US * CSIZE as u32 {
            self.cycles_since_last_bounce = DEBOUNCE_US / SCAN_PERIOD_US / CSIZE as u32;
        }

        // Update the debounced state if it has changed and exceeded the debounce timer
        // (debounce timer resets if there is any bouncing during the debounce interval).
        if self.cycles_since_last_bounce * SCAN_PERIOD_US * CSIZE as u32 >= DEBOUNCE_US {
            // Since we have hit the cycles_since_last_bounce threshold, we can keep it here
            self.cycles_since_last_bounce -= 1;

            if self.raw_state != self.state && self.raw_state_average != 0 {
                // Update state
                // If the average is greater than 0, change the state
                let new_state = if self.raw_state_average > 0 {
                    !self.state
                } else {
                    self.state
                };

                // No longer idle
                self.idle = false;

                // Stop debounce tracking
                self.debounce_tracking = false;
                self.raw_state_average = 0;

                // Reset state transition cycle counter
                // and update state if it has changed.
                if new_state != self.state {
                    self.state = new_state;
                    self.cycles_since_state_change = 0;
                }

                // Return current state
                return self.state();
            }
        }

        // Increment state cycle counter
        self.cycles_since_state_change += 1;

        // If we've hit max, don't wrap-around to 0
        // Set the defined idle limit so not to affect any logic
        if self.cycles_since_state_change >= u32::MAX / SCAN_PERIOD_US / CSIZE as u32 {
            self.cycles_since_state_change = IDLE_MS * 1000 / CSIZE as u32 / SCAN_PERIOD_US;
        }

        // Determine if key is idle
        // Must be both in the off state and have been off >= IDLE_MS
        self.idle = self.state == State::Off
            && self.cycles_since_state_change * SCAN_PERIOD_US * CSIZE as u32 / 1000 >= IDLE_MS;

        // Return current state
        self.state()
    }

    /// Returns thet current state and cycles since the state changed
    ///
    /// (State, idle, cycles_since_state_change)
    pub fn state(&self) -> (State, bool, u32) {
        (self.state, self.idle, self.cycles_since_state_change)
    }

    /// Number of cycles since the last state change
    /// 0 when the state first changes
    pub fn cycles_since_state_change(&self) -> u32 {
        self.cycles_since_state_change
    }

    /// True if the switch is idle
    /// idle indicates the switch is off and there have been no events
    pub fn idle(&self) -> bool {
        self.idle
    }
}

impl<const CSIZE: usize, const SCAN_PERIOD_US: u32, const DEBOUNCE_US: u32, const IDLE_MS: u32>
    Default for KeyState<CSIZE, SCAN_PERIOD_US, DEBOUNCE_US, IDLE_MS>
{
    fn default() -> Self {
        Self::new()
    }
}