comparison service/theaterArduino/theaterArduino.py @ 809:bebb8f7c5a3e

move a bunch of services into this tree, give them all web status pages Ignore-this: a11e90f9d2cd9470565c743f54943c4b darcs-hash:20110808073131-312f9-a7f420d66388cedae458276d672a27a9249f1e2f.gz
author drewp <drewp@bigasterisk.com>
date Mon, 08 Aug 2011 00:31:31 -0700
parents
children 84af13435de7
comparison
equal deleted inserted replaced
808:867f59c83dba 809:bebb8f7c5a3e
1 """
2 arduino example sketches, 'StandardFirmata'.
3
4 ####easy_install http://github.com/lupeke/python-firmata/tarball/master
5
6 Now using http://code.google.com/p/pyduino, modified to run at 57600
7 baud like my arduino's code does. pyduino is better than the lupeke
8 one in that you can read your settings off the output pins
9
10 Note that there are some startup delays and you may not hear about
11 input changes for a few seconds.
12 """
13 from __future__ import division
14 import sys, cyclone.web, time, simplejson, os
15 from twisted.web.client import getPage
16 from twisted.internet import reactor, task
17
18 sys.path.append("/my/proj/homeauto/lib")
19 from cycloneerr import PrettyErrorHandler
20 from logsetup import log
21
22 sys.path.append("pyduino-read-only")
23 import pyduino
24
25 def _num(name):
26 if name.startswith('d'):
27 return int(name[1:])
28 raise ValueError(name)
29
30 class pin(PrettyErrorHandler, cyclone.web.RequestHandler):
31 def get(self, name):
32 self.set_header("Content-Type", "text/plain")
33 arduino = self.settings.arduino
34 arduino.iterate()
35 self.write(str(int(arduino.digital[_num(name)].read())))
36
37 def put(self, name):
38 t1 = time.time()
39 self.settings.arduino.digital[_num(name)].write(int(self.request.body))
40 log.debug("arduino write in %.1f ms" % (1000 * (time.time() - t1)))
41
42
43 class pinMode(PrettyErrorHandler, cyclone.web.RequestHandler):
44 def get(self, name):
45 self.set_header("Content-Type", "text/plain")
46 mode = self.settings.arduino.digital[_num(name)].get_mode()
47 self.write({pyduino.DIGITAL_INPUT : "input",
48 pyduino.DIGITAL_OUTPUT : "output"}[mode])
49
50 def put(self, name):
51 mode = {
52 "input" : pyduino.DIGITAL_INPUT,
53 "output" : pyduino.DIGITAL_OUTPUT}[self.request.body.strip()]
54 self.settings.arduino.digital[_num(name)].set_mode(mode)
55
56 class Pid(PrettyErrorHandler, cyclone.web.RequestHandler):
57 def get(self):
58 self.set_header("Content-Type", "text/plain")
59 self.write(str(os.getpid()))
60
61 class index(PrettyErrorHandler, cyclone.web.RequestHandler):
62 def get(self):
63 """
64 this is a suitable status check; it does a round-trip to arduino
65 """
66 # this would be a good ping() call for pyduino
67 self.settings.arduino.sp.write(chr(pyduino.REPORT_VERSION))
68 self.settings.arduino.iterate()
69
70 self.set_header("Content-Type", "application/xhtml+xml")
71 self.write(open('index.html').read())
72
73 class Application(cyclone.web.Application):
74 def __init__(self, arduino):
75 handlers = [
76 (r"/", index),
77 (r'/pin/(.*)/mode', pinMode),
78 (r'/pin/(.*)', pin),
79 (r'/pid', Pid),
80 # web refresh could benefit a lot from a json resource that
81 # gives all the state
82 ]
83 settings = {"arduino" : arduino,}
84 cyclone.web.Application.__init__(self, handlers, **settings)
85
86 class WatchPins(object):
87 def __init__(self, arduino, conf):
88 self.arduino, self.conf = arduino, conf
89 self.lastState = {}
90 self.pins = conf['watchPins']
91 if self.pins == 'allInput':
92 self.watchAllInputs()
93 for pin in self.pins:
94 arduino.digital_ports[pin >> 3].set_active(1)
95 arduino.digital[pin].set_mode(pyduino.DIGITAL_INPUT)
96
97 def watchAllInputs(self):
98 raise NotImplementedError("this needs to be updated whenever the modes change")
99 self.pins = [p for p in range(2, 13+1) if
100 self.arduino.digital[p].get_mode() ==
101 pyduino.DIGITAL_INPUT]
102
103 def reportPostError(self, fail, pin, value, url):
104 log.error("failed to send pin %s update (now %s) to %r: %r" % (pin, value, url, fail))
105
106 def poll(self):
107 try:
108 self._poll()
109 except Exception, e:
110 log.error("during poll:", exc_info=1)
111
112 def _poll(self):
113 # this can IndexError for a port number being out of
114 # range. I'm not sure how- maybe error data coming in the
115 # port?
116 arduino.iterate()
117 for pin in self.pins:
118 current = arduino.digital[pin].read()
119 if current != self.lastState.get(pin, None):
120 d = getPage(
121 self.conf['post'],
122 method="POST",
123 postdata=simplejson.dumps(dict(board=self.conf['boardName'], pin=pin, level=int(current))),
124 headers={'Content-Type' : 'application/json'})
125 d.addErrback(self.reportPostError, pin, current, self.conf['post'])
126
127 self.lastState[pin] = current
128
129 if __name__ == '__main__':
130
131 config = { # to be read from a file
132 'arduinoPort': '/dev/ttyUSB0',
133 'servePort' : 9056,
134 'pollFrequency' : 20,
135 'post' : 'http://bang:9069/pinChange',
136 'boardName' : 'theater', # gets sent with updates
137 'watchPins' : [9, 10], # or 'allInput' (not yet working)
138 # todo: need options to preset inputs/outputs at startup
139 }
140
141 arduino = pyduino.Arduino(config['arduinoPort'])
142 wp = WatchPins(arduino, config)
143 task.LoopingCall(wp.poll).start(1/config['pollFrequency'])
144 reactor.listenTCP(config['servePort'], Application(arduino))
145 reactor.run()