Mercurial > code > home > repos > homeauto
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() |