21ba31b17be79a468a6b4293882f1d54f6c23189
[miniband.git] / raspberrypi / music-maker-handler.py
1 # This code is copyright ...... under the GPL v2.
2 # This code is derived from scratch_gpio_handler by Simon Walters, which
3 # is derived from scratch_handler by Thomas Preston
4 # Version 0.1: It's kind of working.
5
6 from array import *
7 import threading
8 import socket
9 import time
10 import sys
11 import struct
12 import serial
13 import io
14 import datetime as dt
15 import logging
16
17 '''
18 from Tkinter import Tk
19 from tkSimpleDialog import askstring
20 root = Tk()
21 root.withdraw()
22 '''
23
24 PORT = 42001
25 DEFAULT_HOST = '127.0.0.1'
26 BUFFER_SIZE = 240 #used to be 100
27 SOCKET_TIMEOUT = 1
28 DEVICES = ['/dev/ttyACM0']
29 #DRUM_DEVICE = '/dev/ttyACM0'
30 #GUITAR_DEVICE = '/dev/ttyUSB1'
31 #MARACAS_DEVICE = '/dev/ttyACM1'
32 ARDUINO_BAUD_RATE = 57600
33
34 BROADCAST_NAMES = {'guitar': 'guitar',
35 'drum': {0: 'cymbal',
36 1: 'hihat',
37 2: 'slowdrum',
38 3: 'snare',
39 4: 'tomtom'},
40 'maracas': 'maracas'}
41
42 SENSOR_NAMES = {'guitar': 'guitar_pitch'}
43
44 #DRUM_INSTRUMENT_NAMES = {0: 'cymbal',
45 #1: 'hihat',
46 #2: 'slowdrum',
47 #3: 'snare',
48 #4: 'tomtom'}
49
50 #DRUM_VALUE_NAMES = {0: 'drum-volume',
51 #1: 'drum-volume',
52 #2: 'drum-volume',
53 #3: 'drum-volume',
54 #4: 'drum-volume'}
55
56 #GUITAR_INSTRUMENT_NAMES = {0: 'guitar'}
57 #GUITAR_VALUE_NAMES = {0: 'guitar_pitch'}
58
59 #MARACAS_INSTRUMENT_NAMES = {0: 'maracas', 2: 'maracas'}
60 #MARACAS_VALUE_NAMES = {0: 'maracas_vigour', 2: 'maracas_vigour'}
61
62 logging.basicConfig(level = logging.INFO)
63 #logging.basicConfig(level = logging.DEBUG)
64
65 class MyError(Exception):
66 def __init__(self, value):
67 self.value = value
68
69 def __str__(self):
70 return repr(self.value)
71
72 class ScratchSender(threading.Thread):
73 def __init__(self, socket):
74 threading.Thread.__init__(self)
75 self.scratch_socket = socket
76 self._stop = threading.Event()
77
78 def join(self,timeout=None):
79 """
80 Stop the thread
81 """
82 self._stop.set()
83 threading.Thread.join(self, timeout)
84
85 #def stop(self):
86 #self._stop.set()
87
88 def stopped(self):
89 return self._stop.isSet()
90
91 def run(self):
92 # Detect sensors here
93 while not self.stopped():
94 time.sleep(0.01) # be kind to cpu - not certain why :)
95
96 def send_scratch_command(self, cmd):
97 n = len(cmd)
98 a = array('c')
99 a.append(chr((n >> 24) & 0xFF))
100 a.append(chr((n >> 16) & 0xFF))
101 a.append(chr((n >> 8) & 0xFF))
102 a.append(chr(n & 0xFF))
103 self.scratch_socket.send(a.tostring() + cmd)
104
105
106 class ArduinoListener(threading.Thread):
107 def __init__(self, device, speed, sender, instruments, values):
108 threading.Thread.__init__(self)
109 self.arduino_device = serial.Serial(device, speed, timeout=0.5)
110 self._stop = threading.Event()
111 self.scratch_sender = sender
112 self.instruments = instruments
113 self.values = values
114
115 def join(self,timeout=None):
116 """
117 Stop the thread
118 """
119 self._stop.set()
120 threading.Thread.join(self, timeout)
121
122 #def stop(self):
123 #self._stop.set()
124
125 def stopped(self):
126 return self._stop.isSet()
127
128 def run(self):
129 self.arduino_device.readline() # discard the first (partial) line
130 while not self.stopped():
131 logging.debug('Thread waiting for a signal')
132 try:
133 device_line = self.arduino_device.readline()
134 if device_line :
135 instrument, instrument_value_string = device_line.rstrip().split(',', 1)
136 instrument_value = int(instrument_value_string)
137 logging.info('Instrument: %s, Value: %d' % (instrument, instrument_value))
138 if instrument in self.values:
139 try:
140 logging.info("sensor-update %s %d" % (self.values[instrument], (instrument_value * 100) / 1024))
141 self.scratch_sender.send_scratch_command("sensor-update %s %d" % (self.values[instrument], (instrument_value * 100) / 1024))
142 except KeyError:
143 # Do nothing
144 pass
145 if isinstance(self.instruments[instrument], dict):
146 broadcast = self.instruments[instrument][instrument_value]
147 else:
148 broadcast = self.instruments[instrument]
149 try:
150 logging.info("broadcast %s" % broadcast)
151 self.scratch_sender.send_scratch_command('broadcast %s' % broadcast)
152 except KeyError:
153 # Do nothing
154 pass
155
156 except serial.SerialException:
157 logging.error('Serial exception')
158 logging.debug('Thread run() exiting')
159
160
161
162 def create_socket(host, port):
163 while True:
164 try:
165 logging.info('Connecting to Scratch')
166 scratch_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
167 scratch_sock.connect((host, port))
168 break
169 except socket.error:
170 logging.warning("There was an error connecting to Scratch!")
171 logging.warning("I couldn't find a Mesh session at host: %s, port: %s" % (host, port) )
172 time.sleep(3)
173 #sys.exit(1)
174
175 return scratch_sock
176
177 def cleanup_threads(threads):
178 logging.debug("Stopping %d threads" % len(threads))
179 #for thread in threads:
180 #thread.stop()
181 #logging.debug("Threads stopped")
182 for thread in threads:
183 thread.join()
184 logging.debug("Threads joined")
185
186 if __name__ == '__main__':
187 if len(sys.argv) > 1:
188 host = sys.argv[1]
189 else:
190 host = DEFAULT_HOST
191
192
193 cycle_trace = 'start'
194 while True:
195 if (cycle_trace == 'disconnected'):
196 logging.info("Scratch disconnected")
197 cleanup_threads(listeners + sender)
198 time.sleep(1)
199 cycle_trace = 'start'
200
201 if (cycle_trace == 'start'):
202 # open the socket
203 logging.info('Connecting to Scratch...')
204 the_socket = create_socket(host, PORT)
205 logging.info('Connected to Scratch')
206 the_socket.settimeout(SOCKET_TIMEOUT)
207 sender = ScratchSender(the_socket)
208 #listeners = []
209 #for device in DEVICES:
210 #listeners.append(ArduinoListener(device, ARDUINO_BAUD_RATE, sender, BROADCAST_NAMES, SENSOR_NAMES))
211
212 listeners = [ArduinoListener(device, ARDUINO_BAUD_RATE, sender, BROADCAST_NAMES, SENSOR_NAMES) for device in DEVICES]
213 cycle_trace = 'running'
214 logging.info("Listeners running....")
215 sender.start()
216 for listener in listeners:
217 listener.start()
218
219 # wait for ctrl+c
220 try:
221 #just pause
222 time.sleep(0.1)
223 except KeyboardInterrupt:
224 logging.warning("Interrrupted")
225 cleanup_threads(listeners + [sender])
226 sys.exit()
227