-
Notifications
You must be signed in to change notification settings - Fork 397
/
simple_button.py
201 lines (166 loc) · 7.26 KB
/
simple_button.py
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
import time
import logging
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
logger = logging.getLogger(__name__)
map_edge_parse = {'falling': GPIO.FALLING, 'rising': GPIO.RISING, 'both': GPIO.BOTH}
map_pull_parse = {'pull_up': GPIO.PUD_UP, 'pull_down': GPIO.PUD_DOWN, 'pull_off': GPIO.PUD_OFF}
map_edge_print = {GPIO.FALLING: 'falling', GPIO.RISING: 'rising', GPIO.BOTH: 'both'}
map_pull_print = {GPIO.PUD_UP: 'pull_up', GPIO.PUD_DOWN: 'pull_down', GPIO.PUD_OFF: 'pull_off'}
def parse_edge_key(edge):
if edge in [GPIO.FALLING, GPIO.RISING, GPIO.BOTH]:
return edge
try:
result = map_edge_parse[edge.lower()]
except KeyError:
result = edge
raise KeyError('Unknown Edge type {edge}'.format(edge=edge))
return result
def parse_pull_up_down(pull_up_down):
if pull_up_down in [GPIO.PUD_UP, GPIO.PUD_DOWN, GPIO.PUD_OFF]:
return pull_up_down
try:
result = map_pull_parse[pull_up_down]
except KeyError:
result = pull_up_down
raise KeyError('Unknown Pull Up/Down type {pull_up_down}'.format(pull_up_down=pull_up_down))
return result
def print_edge_key(edge):
try:
result = map_edge_print[edge]
except KeyError:
result = edge
return result
def print_pull_up_down(pull_up_down):
try:
result = map_pull_print[pull_up_down]
except KeyError:
result = pull_up_down
return result
# This function takes a holding time (fractional seconds), a channel, a GPIO state and an action reference (function).
# It checks if the GPIO is in the state since the function was called. If the state
# changes it return False. If the time is over the function returns True.
def checkGpioStaysInState(holdingTime, gpioChannel, gpioHoldingState):
# Get a reference start time (https://docs.python.org/3/library/time.html#time.perf_counter)
startTime = time.perf_counter()
# Continously check if time is not over
while True:
time.sleep(0.1)
currentState = GPIO.input(gpioChannel)
endTime = time.perf_counter() - startTime
if holdingTime < endTime:
break
# Return if state does not match holding state
if (gpioHoldingState != currentState):
return False
# Else: Wait
if (gpioHoldingState != currentState):
return False
return True
class SimpleButton:
def __init__(self, pin, action=lambda *args: None, action2=lambda *args: None, name=None,
bouncetime=500, antibouncehack=False, edge='falling',
hold_time=.3, hold_mode=None, pull_up_down='pull_up'):
self.pin = pin
self.name = name
self.edge = parse_edge_key(edge)
self.hold_time = hold_time
self.hold_mode = hold_mode
# TODO is pull_up always true a bug?
self.pull_up = True
self.pull_up_down = parse_pull_up_down(pull_up_down)
self.bouncetime = bouncetime
self.antibouncehack = antibouncehack
GPIO.setup(self.pin, GPIO.IN, pull_up_down=self.pull_up_down)
self._action = action
self._action2 = action2
GPIO.add_event_detect(self.pin, edge=self.edge, callback=self.callbackFunctionHandler,
bouncetime=self.bouncetime)
self.callback_with_pin_argument = False
def callbackFunctionHandler(self, *args):
if len(args) > 0 and args[0] == self.pin and not self.callback_with_pin_argument:
logger.debug('Remove pin argument by callbackFunctionHandler - args before: {}'.format(args))
args = args[1:]
logger.debug('args after: {}'.format(args))
if self.antibouncehack:
time.sleep(0.1)
inval = GPIO.input(self.pin)
if inval != GPIO.LOW:
return None
return self._handleCallbackFunction(*args)
@property
def when_pressed(self):
logger.info('{}: action'.format(self.name))
return self._action
@property
def when_held(self):
logger.info('{}: action2'.format(self.name))
return self._action2
@when_pressed.setter
def when_pressed(self, func):
logger.info('{}: set when_pressed'.format(self.name))
self._action = func
GPIO.remove_event_detect(self.pin)
logger.info('add new action')
GPIO.add_event_detect(self.pin, edge=self.edge, callback=self.callbackFunctionHandler,
bouncetime=self.bouncetime)
@DeprecationWarning
def set_callbackFunction(self, callbackFunction):
self.when_pressed = callbackFunction
def _handleCallbackFunction(self, *args):
logger.info('{}: handleCallbackFunction, mode: {}'.format(self.name, self.hold_mode))
if self.hold_mode == "Repeat":
# Instantly call primary action
self.when_pressed(*args)
# Repeated call of primary action if button is held long enough
while checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
self.when_pressed(*args)
elif self.hold_mode == "Postpone":
# Postponed call of primary action (once)
if checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
self.when_pressed(*args)
while checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
pass
elif self.hold_mode == "SecondFunc":
# Call of secondary action (once)
# execute primary action if not held past hold_time
if checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
self.when_held(*args)
while checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
pass
else:
self.when_pressed(*args)
elif self.hold_mode == "SecondFuncRepeat":
# Repeated call of secondary action (multiple times if button is held long enough)
# execute primary action if not held past hold_time
if checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
self.when_held(*args)
while checkGpioStaysInState(self.hold_time, self.pin, GPIO.LOW):
self.when_held(*args)
else:
self.when_pressed(*args)
else:
self.when_pressed(*args)
def __del__(self):
logger.debug('remove event detection')
GPIO.remove_event_detect(self.pin)
@property
def is_pressed(self):
# TODO should this be 'if pull_up_down == GPIO.PUD_UP'? pull_up is always true!
if self.pull_up:
return not GPIO.input(self.pin)
return GPIO.input(self.pin)
def __repr__(self):
return ('<SimpleButton-{}(pin={},edge={},hold_mode={},hold_time={},'
'bouncetime={},antibouncehack={},pull_up_down={})>').format(
self.name, self.pin, print_edge_key(self.edge), self.hold_mode, self.hold_time,
self.bouncetime, self.antibouncehack, print_pull_up_down(self.pull_up_down))
# Uncomment for manual tests
# if __name__ == "__main__":
# from signal import pause
# print('please enter pin no to test')
# pin = int(input())
# func = lambda *args: print('FunctionCall with {}'.format(args))
# btn = SimpleButton(pin=pin, action=func, hold_mode='Repeat')
# print('running')
# pause()