-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathcosmiccontroller.py
More file actions
283 lines (215 loc) · 9.29 KB
/
cosmiccontroller.py
File metadata and controls
283 lines (215 loc) · 9.29 KB
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
#!/usr/bin/python
#
# $Id: cosmiccontroller.py,v 1.0 2019/10/15 17:53:00 Gordon Exp $
# (C) Copyright IQaudIO Limited, 2017
# Use this code to read from the CosmicController's buttons (1/2/3 and rotary encoder)
# toggle the CosmicController's LEDs (default it at button press)
# and adjust ALSA volume by means of rotary encoder
# NOTE: Rotary encoder's button is time sensitive...
# click = toggle mute
#
# hardware:
# IQaudIO CosmicController board v1
# rotary encoder with common tied to earth
# momentary switch with one terminal tied to earth
# all input will use raspberry internal pullups
from time import sleep
from subprocess import call
import datetime
import subprocess
import RPi.GPIO as GPIO
# set debug value for output
# 3=extreme, 2=verbose, 1=standard, 0=none
debug = 2
# set to 1 for unique LED (only one lit at any time). Yet to be implemented
unique_led = 0
# pins in use
encoder_a = 23
encoder_b = 24
pushbutton = 27 # set to zero if not connected
newbutton1 = 4
newbutton2 = 5
newbutton3 = 6
newled1 = 14
newled2 = 15
newled3 = 16
# control pin - defined in hardware so do not change
mutepin = 22
# control name in amixer. IQaudIO Master volume control
control='Digital'
# volume change per encoder detent step in percent
# this can be non-integer, but it is then not completely regular
volumestepsize = 3 # percent of full span
# volume mapping - not implemented
# set to '-M' for alsamixer-like mapping
# set to '-R' for raw percentage volume
# volumemapping = '' # must be '-M' or '-R'
# guard times for poweroff function (seconds)
holdtimemute = 4 # amp mutes after switch held this long
holdtimeoff = 6 # system powers off after this long (total)
# debounce time (on switch, not encoder)
switchdebounce = 30. # milliseconds
# loop timing
# volume will only increment this many times per second
# regardless of how many edge transitions occur within interval
volumeloophz = 3
# button press timing loop
buttonloopwait = 0.02 # seconds, recommend 0.02
###########################################################
if debug : print "\nIQaudIO Cosmic Controller script initialising"
# initialise hardware
# all get onboard pullup (approx 50k)
GPIO.setmode(GPIO.BCM)
GPIO.setup(encoder_a, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(encoder_b, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(pushbutton, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(newbutton1, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(newbutton2, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(newbutton3, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(mutepin, GPIO.OUT)
GPIO.setup(newled1, GPIO.OUT)
GPIO.setup(newled2, GPIO.OUT)
GPIO.setup(newled3, GPIO.OUT)
# Turn off all three LEDs
GPIO.output(newled1,0)
GPIO.output(newled2,0)
GPIO.output(newled3,0)
# create events callback functions
encoderpos = 0
lastencoderpos = 0
encodercount = 0
lastencodercount = 0
def encoder_callback(channel):
global lastencoderpos
global encodercount
if (debug>2) : print('Edge detected on encoder channel %s'%channel)
encoderpos=((GPIO.input(encoder_a)&1)<<1)|(GPIO.input(encoder_b)&1)
c = (lastencoderpos<<2)|encoderpos
if (c==0b1101 or c==0b0100 or c==0b0010 or c==0b1011):
encodercount = encodercount + 1
if (debug>2) : print(" encoder count now: %d"%encodercount)
else:
if (c==0b1110 or c==0b0111 or c==0b0001 or c==0b1000):
encodercount = encodercount -1
if (debug>2) : print(" encoder count now: %d"%encodercount)
else:
# there shouldn't be any else
if (debug>2) : print(" unrecognised encoder state discarded")
pass
lastencoderpos=encoderpos
def pushbutton_callback(channel):
if (debug>1) : print('Edge detected on button channel %s'%channel)
# record time press was detected
presstime = datetime.datetime.utcnow()
# wait until released by polling
while (GPIO.input(channel)==0):
pressduration=(datetime.datetime.utcnow()-presstime).total_seconds()
if (pressduration>float(holdtimemute)):
if debug : print('Muting amplifier due to button hold')
GPIO.output(mutepin,0)
sleep (buttonloopwait)
# find final duration
pressduration=(datetime.datetime.utcnow()-presstime).total_seconds()
if (pressduration>(float(holdtimeoff))):
if debug : print ('Sytem shutdown due to button hold')
# Uncomment line below for shutdown to be supported.
# subprocess.call(['shutdown -h now "System halted by volume control" &'], shell=True)
else:
if ( (pressduration>(float(switchdebounce)/1000.0)) and (pressduration<float(holdtimemute)) ):
if debug : print('Toggle mute state')
if (debug>1) : print(" button pressed for %f seconds" %pressduration)
if (GPIO.input(mutepin)==0):
GPIO.output(mutepin,1)
else:
GPIO.output(mutepin,0)
if (debug>1):
call(["/usr/bin/amixer", "sset", control, "toggle"])
else:
call(["/usr/bin/amixer", "-q", "sset", control, "toggle"])
def newbutton1_callback(channel):
if (debug>1) : print('Edge detected on button1 channel %s'%channel)
# record time press was detected
presstime = datetime.datetime.utcnow()
# wait until released by polling
while (GPIO.input(channel)==0):
pressduration=(datetime.datetime.utcnow()-presstime).total_seconds()
sleep (buttonloopwait)
# find final duration
pressduration=(datetime.datetime.utcnow()-presstime).total_seconds()
if ( (pressduration>(float(switchdebounce)/1000.0)) and (pressduration<float(holdtimemute)) ):
if debug : print('Toggle led 1 state')
if (debug>1) : print(" button pressed for %f seconds" %pressduration)
if (GPIO.input(newled1)==0):
GPIO.output(newled1,1)
else:
GPIO.output(newled1,0)
def newbutton2_callback(channel):
if (debug>1) : print('Edge detected on button1 channel %s'%channel)
# record time press was detected
presstime = datetime.datetime.utcnow()
# wait until released by polling
while (GPIO.input(channel)==0):
pressduration=(datetime.datetime.utcnow()-presstime).total_seconds()
sleep (buttonloopwait)
# find final duration
pressduration=(datetime.datetime.utcnow()-presstime).total_seconds()
if ( (pressduration>(float(switchdebounce)/1000.0)) and (pressduration<float(holdtimemute)) ):
if debug : print('Toggle led 2 state')
if (debug>1) : print(" button pressed for %f seconds" %pressduration)
if (GPIO.input(newled2)==0):
GPIO.output(newled2,1)
else:
GPIO.output(newled2,0)
def newbutton3_callback(channel):
if (debug>1) : print('Edge detected on button1 channel %s'%channel)
# record time press was detected
presstime = datetime.datetime.utcnow()
# wait until released by polling
while (GPIO.input(channel)==0):
pressduration=(datetime.datetime.utcnow()-presstime).total_seconds()
sleep (buttonloopwait)
# find final duration
pressduration=(datetime.datetime.utcnow()-presstime).total_seconds()
if ( (pressduration>(float(switchdebounce)/1000.0)) and (pressduration<float(holdtimemute)) ):
if debug : print('Toggle led 3 state')
if (debug>1) : print(" button pressed for %f seconds" %pressduration)
if (GPIO.input(newled3)==0):
GPIO.output(newled3,1)
else:
GPIO.output(newled3,0)
# register callback functions
GPIO.add_event_detect(encoder_a, GPIO.BOTH, callback=encoder_callback)
GPIO.add_event_detect(encoder_b, GPIO.BOTH, callback=encoder_callback)
GPIO.add_event_detect(pushbutton, GPIO.FALLING, callback=pushbutton_callback, bouncetime=int(switchdebounce))
GPIO.add_event_detect(newbutton1, GPIO.FALLING, callback=newbutton1_callback, bouncetime=int(switchdebounce))
GPIO.add_event_detect(newbutton2, GPIO.FALLING, callback=newbutton2_callback, bouncetime=int(switchdebounce))
GPIO.add_event_detect(newbutton3, GPIO.FALLING, callback=newbutton3_callback, bouncetime=int(switchdebounce))
# main code loop - we look out for changes in encoder position and
# update volume control accordingly
if debug : print "IQaudIO Cosmic Controller script running"
mln=0
try:
while True:
# this is an empty infinite loop but count it anyway
mln = (mln +1)%1000
if debug>2 : print "%03d main loop" %mln
if (encodercount <> lastencodercount):
if (debug>1) : print "encoder count change to %d"%encodercount
# decide on step size
if (encodercount > lastencodercount):
v = str(volumestepsize)+'%+'
else:
v = str(volumestepsize)+'%-'
# implement step size, eitehr with or without report to user
# Uncomment below to adjust volume control.
if (debug>1):
call(["/usr/bin/amixer", "sset", control, v])
else:
call(["/usr/bin/amixer", "-q", "sset", control, v])
lastencodercount=encodercount
sleep (1.0/volumeloophz)
except KeyboardInterrupt:
pass
# clean up after keyboard interrup
if debug : print "\nIQaudIO Cosmic Controller script exiting"
GPIO.cleanup()