AskSin++
Button.h
1 //- -----------------------------------------------------------------------------------------------------------------------
2 // AskSin++
3 // 2016-10-31 papa Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
4 //- -----------------------------------------------------------------------------------------------------------------------
5 #ifndef __BUTTON_H__
6 #define __BUTTON_H__
7 
8 #include "AlarmClock.h"
9 #include "Debug.h"
10 
11 #ifdef ARDUINO_ARCH_AVR
12  typedef uint8_t WiringPinMode;
13 #endif
14 
15 namespace as {
16 
17 template <uint8_t OFFSTATE=HIGH,uint8_t ONSTATE=LOW,WiringPinMode MODE=INPUT_PULLUP>
18 class StateButton: public Alarm {
19 
20 #define DEBOUNCETIME millis2ticks(50)
21 
22 public:
23  enum States {
24  invalid = 0,
25  none = 1,
26  released = 2,
27  pressed = 3,
28  debounce = 4,
29  longpressed = 5,
30  longreleased = 6,
31  };
32 
33  class CheckAlarm : public Alarm {
34  public:
35  StateButton& sb;
36  CheckAlarm (StateButton& _sb) : Alarm(0), sb(_sb) {}
37  ~CheckAlarm () {}
38  virtual void trigger(__attribute__((unused)) AlarmClock& clock) {
39  sb.check();
40  }
41  };
42 
43 protected:
44  uint8_t stat : 3;
45  uint8_t pinstate : 1;
46  uint8_t pin;
47  uint16_t longpresstime;
48  CheckAlarm ca;
49 
50 public:
51  StateButton() :
52  Alarm(0), stat(none), pinstate(OFFSTATE), pin(0), longpresstime(millis2ticks(400)), ca(*this) {
53  }
54  virtual ~StateButton() {
55  }
56 
57  void setLongPressTime(uint16_t t) {
58  longpresstime = t;
59  }
60 
61  uint8_t getPin () {
62  return pin;
63  }
64 
65  virtual void trigger(AlarmClock& clock) {
66  uint8_t nextstate = invalid;
67  uint16_t nexttick = 0;
68  switch ( state() ) {
69  case released:
70  case longreleased:
71  nextstate = none;
72  break;
73 
74  case debounce:
75  nextstate = pressed;
76  if (pinstate == ONSTATE) {
77  // set timer for detect longpressed
78  nexttick = longpresstime - DEBOUNCETIME;
79  } else {
80  nextstate = released;
81  nexttick = DEBOUNCETIME;
82  }
83  break;
84 
85  case pressed:
86  case longpressed:
87  if( pinstate == ONSTATE) {
88  nextstate = longpressed;
89  nexttick = longpresstime;
90  }
91  break;
92  }
93  // reactivate alarm if needed
94  if( nexttick != 0 ) {
95  tick = nexttick;
96  clock.add(*this);
97  }
98  // trigger the state change
99  if( nextstate != invalid ) {
100  state(nextstate);
101  }
102  }
103 
104  virtual void state(uint8_t s) {
105  switch(s) {
106  case released: DPRINTLN(F(" released")); break;
107  case pressed: DPRINTLN(F(" pressed")); break;
108  case debounce: DPRINTLN(F(" debounce")); break;
109  case longpressed: DPRINTLN(F(" longpressed")); break;
110  case longreleased: DPRINTLN(F(" longreleased")); break;
111  default: DPRINTLN(F("")); break;
112  }
113  stat = s;
114  }
115 
116  uint8_t state() const {
117  return stat;
118  }
119 
120  void irq () {
121  sysclock.cancel(ca);
122  // use alarm to run code outside of interrupt
123  sysclock.add(ca);
124  }
125 
126  void check() {
127  uint8_t ps = digitalRead(pin);
128  if( pinstate != ps ) {
129  pinstate = ps;
130  uint16_t nexttick = 0;
131  uint8_t nextstate = state();
132  switch ( state() ) {
133  case none:
134  nextstate = debounce;
135  nexttick = DEBOUNCETIME;
136  break;
137 
138  case pressed:
139  case longpressed:
140  if (pinstate == OFFSTATE) {
141  nextstate = state() == pressed ? released : longreleased;
142  nexttick = DEBOUNCETIME;
143  }
144  break;
145  default:
146  break;
147  }
148  if( nexttick != 0 ) {
149  sysclock.cancel(*this);
150  tick = nexttick;
151  sysclock.add(*this);
152  }
153  if( nextstate != state () ) {
154  state(nextstate);
155  }
156  }
157  }
158 
159  void init(uint8_t pin) {
160  this->pin = pin;
161  pinMode(pin, MODE);
162  }
163 };
164 
165 // define standard button switches to GND
166 typedef StateButton<HIGH,LOW,INPUT_PULLUP> Button;
167 
168 template <class DEVTYPE,uint8_t OFFSTATE=HIGH,uint8_t ONSTATE=LOW,WiringPinMode MODE=INPUT_PULLUP>
169 class ConfigButton : public StateButton<OFFSTATE,ONSTATE,MODE> {
170  DEVTYPE& device;
171 public:
173 
174  ConfigButton (DEVTYPE& dev,uint8_t longpresstime=3) : device(dev) {
175  this->setLongPressTime(seconds2ticks(longpresstime));
176  }
177  virtual ~ConfigButton () {}
178  virtual void state (uint8_t s) {
179  uint8_t old = ButtonType::state();
180  ButtonType::state(s);
181  if( s == ButtonType::released ) {
182  device.startPairing();
183  }
184  else if( s == ButtonType::longpressed ) {
185  if( old == ButtonType::longpressed ) {
186  if( device.getList0().localResetDisable() == false ) {
187  device.reset(); // long pressed again - reset
188  }
189  }
190  else {
191  device.led().set(LedStates::key_long);
192  }
193  }
194  }
195 };
196 
197 template <class DEVTYPE,uint8_t OFFSTATE=HIGH,uint8_t ONSTATE=LOW,WiringPinMode MODE=INPUT_PULLUP>
198 class ConfigToggleButton : public StateButton<OFFSTATE,ONSTATE,MODE> {
199  DEVTYPE& device;
200 public:
202 
203  ConfigToggleButton (DEVTYPE& dev,uint8_t longpresstime=3) : device(dev) {
204  this->setLongPressTime(seconds2ticks(longpresstime));
205  }
206  virtual ~ConfigToggleButton () {}
207  virtual void state (uint8_t s) {
208  uint8_t old = ButtonType::state();
209  ButtonType::state(s);
210  if( s == ButtonType::released ) {
211  RemoteEventMsg& msg = (RemoteEventMsg&)device.message();
212  uint8_t cnt = device.nextcount();
213  msg.init(cnt,1,cnt,false,false);
214  device.getDeviceID(msg.to());
215  device.getDeviceID(msg.from());
216  msg.clearAck();
217  if( device.process(msg) == false ) {
218  DPRINTLN(F("No self peer. Create internal peering to toggle state!"));
219  }
220  }
221  else if( s == ButtonType::longreleased ) {
222  device.startPairing();
223  }
224  else if( s == ButtonType::longpressed ) {
225  if( old == ButtonType::longpressed ) {
226  if( device.getList0().localResetDisable() == false ) {
227  device.reset(); // long pressed again - reset
228  }
229  }
230  else {
231  device.led().set(LedStates::key_long);
232  }
233  }
234  }
235  Peer peer () const {
236  HMID self;
237  device.getDeviceID(self);
238  return Peer(self,1);
239  }
240 };
241 
242 template <class DEVTYPE,uint8_t OFFSTATE=HIGH,uint8_t ONSTATE=LOW,WiringPinMode MODE=INPUT_PULLUP>
243 class InternalButton : public StateButton<OFFSTATE,ONSTATE,MODE> {
244  DEVTYPE& device;
245  uint8_t num, counter;
246 public:
248 
249  InternalButton (DEVTYPE& dev,uint8_t n,uint8_t longpresstime=4) : device(dev), num(n), counter(0) {
250  this->setLongPressTime(decis2ticks(longpresstime));
251  }
252  virtual ~InternalButton () {}
253  virtual void state (uint8_t s) {
254  ButtonType::state(s);
255  if( s == ButtonType::released ) {
256  shortPress();
257  }
258  else if( s == ButtonType::longpressed ) {
259  RemoteEventMsg& msg = fillMsg(true);
260  device.process(msg);
261  }
262  else if( s == ButtonType::longreleased ) {
263  counter++;
264  }
265  }
266  RemoteEventMsg& fillMsg (bool lg) {
267  RemoteEventMsg& msg = (RemoteEventMsg&)device.message();
268  uint8_t cnt = device.nextcount();
269  msg.init(cnt,num,counter,lg,false);
270  device.getDeviceID(msg.to());
271  device.getDeviceID(msg.from());
272  msg.clearAck();
273  return msg;
274  }
275  Peer peer () const {
276  HMID self;
277  device.getDeviceID(self);
278  return Peer(self,num);
279  }
280  // trigger a short press event - press button by software
281  void shortPress () {
282  RemoteEventMsg& msg = fillMsg(false);
283  device.process(msg);
284  counter++;
285  }
286 };
287 
288 class BaseEncoder {
289  int8_t counter;
290  uint8_t datapin;
291 public:
292  BaseEncoder () : counter(0), datapin(0) {}
293  void encirq () {
294  uint8_t data = digitalRead(datapin);
295  counter += data == LOW ? 1 : -1;
296  }
297  void init (uint8_t cpin,uint8_t dpin) {
298  pinMode(cpin, INPUT_PULLUP);
299  pinMode(dpin, INPUT_PULLUP);
300  datapin = dpin;
301  }
302  int8_t read () {
303  int8_t result=0;
304  ATOMIC_BLOCK( ATOMIC_RESTORESTATE )
305  {
306  result = counter;
307  counter = 0;
308  }
309  return result;
310  }
311 };
312 
313 template<class DeviceType>
314 class InternalEncoder : public InternalButton<DeviceType>, public BaseEncoder {
315 
316 public:
317  InternalEncoder (DeviceType& dev,uint8_t num) : InternalButton<DeviceType>(dev,num), BaseEncoder() {};
318  virtual ~InternalEncoder () {};
319 
320  void init (uint8_t sw) {
322  }
323  void init (uint8_t cpin,uint8_t dpin) {
324  BaseEncoder::init(cpin,dpin);
325  }
326  template<class ChannelType>
327  void process (ChannelType& channel) {
328  int8_t dx = read();
329  if( dx != 0 ) {
330  typename ChannelType::List3 l3 = channel.getList3(this->peer());
331  if( l3.valid() ) {
332  if( dx > 0 ) channel.dimUp(l3.sh());
333  else channel.dimDown(l3.sh());
334  }
335  }
336  }
337 };
338 
339 #define buttonISR(btn,pin) class btn##ISRHandler { \
340  public: \
341  static void isr () { btn.irq(); } \
342 }; \
343 btn.init(pin); \
344 if( digitalPinToInterrupt(pin) == NOT_AN_INTERRUPT ) \
345  enableInterrupt(pin,btn##ISRHandler::isr,CHANGE); \
346 else \
347  attachInterrupt(digitalPinToInterrupt(pin),btn##ISRHandler::isr,CHANGE);
348 
349 #define encoderISR(enc,clkpin,datapin) class enc##ENCISRHandler { \
350  public: \
351  static void isr () { enc.encirq(); } \
352 }; \
353 enc.init(clkpin,datapin); \
354 if( digitalPinToInterrupt(clkpin) == NOT_AN_INTERRUPT ) \
355  enableInterrupt(clkpin,enc##ENCISRHandler::isr,FALLING); \
356 else \
357  attachInterrupt(digitalPinToInterrupt(clkpin),enc##ENCISRHandler::isr,FALLING);
358 
359 }
360 
361 #endif
as::Alarm
Definition: Alarm.h:15
as::ConfigButton
Definition: Button.h:169
as::Peer
Definition: Peer.h:14
as::AlarmClock
Definition: AlarmClock.h:32
as::BaseEncoder
Definition: Button.h:288
as::InternalButton
Definition: Button.h:243
as::InternalEncoder
Definition: Button.h:314
as::ConfigToggleButton
Definition: Button.h:198
as::RemoteEventMsg
Definition: Message.h:463
as::DeviceType
Definition: Device.h:43
as::StateButton::CheckAlarm
Definition: Button.h:33
as::StateButton
Definition: Button.h:18
as::HMID
Definition: HMID.h:14