0
|
1 """
|
|
2 talks to the arduino outside the front door. Don't write straight to
|
|
3 this LCD; use frontDoorMessage for that.
|
|
4
|
|
5 lcd is this wide
|
|
6 |-------------------|
|
|
7 22:05 85F in, 71F out
|
|
8
|
|
9 """
|
|
10
|
|
11 from __future__ import division
|
|
12
|
|
13 import cyclone.web, json, traceback, os, sys
|
|
14 from twisted.python import log
|
|
15 from twisted.internet import reactor, task
|
|
16 from twisted.web.client import getPage
|
|
17
|
|
18 sys.path.append("/my/proj/house/frontdoor")
|
|
19 from loggingserial import LoggingSerial
|
|
20
|
|
21 sys.path.append("/my/proj/homeauto/lib")
|
|
22 from cycloneerr import PrettyErrorHandler
|
|
23
|
|
24 class Board(object):
|
|
25 """
|
|
26 arduino board actions, plus the last values we wrote to it
|
|
27 """
|
|
28 def __init__(self, port):
|
|
29 self.ser = LoggingSerial(port=port)
|
|
30 self.ser.flush()
|
|
31
|
|
32 self.ser.write("\xff\x00\x00")
|
|
33 self.ser.write("\xff\x03\x00")
|
|
34 self.currentText = ""
|
|
35 self.currentBrightness = 0
|
|
36
|
|
37 def ping(self):
|
|
38 self.getDoor()
|
|
39
|
|
40 def getDoor(self):
|
|
41 self.ser.write("\xff\x01")
|
|
42 ret = self.ser.readJson()
|
|
43 return ret['door']
|
|
44
|
|
45 def getLcd(self):
|
|
46 return self.currentText
|
|
47
|
|
48 def setLcd(self, txt):
|
|
49 """
|
|
50 up to 8*21 chars
|
|
51 """
|
|
52 self.currentText = txt
|
|
53 self.ser.write("\xff\x00" + txt + "\x00")
|
|
54
|
|
55 def getLcdBrightness(self):
|
|
56 return self.currentBrightness
|
|
57
|
|
58 def setLcdBrightness(self, b):
|
|
59 """b in 0 to 255"""
|
|
60 self.currentBrightness = b
|
|
61 self.ser.write("\xff\x03" + chr(b))
|
|
62
|
|
63 def getTemperature(self):
|
|
64 """returns parsed json from the board"""
|
|
65 self.ser.write("\xff\x02")
|
|
66 # this can take 1.25 seconds per retry
|
|
67 f = self.ser.readJson()
|
|
68
|
|
69 if f['temp'] > 184 or f['temp'] < -100:
|
|
70 # this fails a lot, maybe 50% of the time. retry if
|
|
71 # you want
|
|
72 raise ValueError("out of range temp value (%s)" % f)
|
|
73 return f
|
|
74
|
|
75 class index(PrettyErrorHandler, cyclone.web.RequestHandler):
|
|
76 def get(self):
|
|
77 self.settings.board.ping()
|
|
78
|
|
79 self.set_header("Content-Type", "application/xhtml+xml")
|
|
80 self.write(open("index.html").read())
|
|
81
|
|
82 class Lcd(PrettyErrorHandler, cyclone.web.RequestHandler):
|
|
83 def get(self):
|
|
84 self.set_header("Content-Type", "text/plain")
|
|
85 self.write(self.settings.board.getLcd())
|
|
86
|
|
87 def put(self):
|
|
88 self.settings.board.setLcd(self.request.body)
|
|
89 self.set_status(204)
|
|
90
|
|
91 class Backlight(PrettyErrorHandler, cyclone.web.RequestHandler):
|
|
92 def get(self):
|
|
93 self.set_header("Content-Type", "application/json")
|
|
94 self.write(json.dumps({
|
|
95 "backlight" : self.settings.board.getLcdBrightness()}))
|
|
96
|
|
97 def put(self):
|
|
98 """param brightness=0 to brightness=255"""
|
|
99 self.settings.board.setLcdBrightness(
|
|
100 int(self.get_argument('brightness')))
|
|
101 self.write("ok")
|
|
102 post = put
|
|
103
|
|
104 class Door(PrettyErrorHandler, cyclone.web.RequestHandler):
|
|
105 def get(self):
|
|
106 self.set_header("Content-Type", "text/plain")
|
|
107 self.write(self.settings.board.getDoor())
|
|
108
|
|
109 class Temperature(PrettyErrorHandler, cyclone.web.RequestHandler):
|
|
110 def get(self):
|
|
111 f = self.settings.board.getTemperature()
|
|
112 self.set_header("Content-Type", "application/json")
|
|
113 self.write(f)
|
|
114
|
|
115 class Application(cyclone.web.Application):
|
|
116 def __init__(self, board):
|
|
117 handlers = [
|
|
118 (r"/", index),
|
|
119 (r'/lcd', Lcd),
|
|
120 (r'/door', Door),
|
|
121 (r'/temperature', Temperature),
|
|
122 (r'/lcd/backlight', Backlight),
|
|
123 ]
|
|
124 settings = {"board" : board}
|
|
125 cyclone.web.Application.__init__(self, handlers, **settings)
|
|
126
|
|
127
|
|
128 class Poller(object):
|
|
129 def __init__(self, board, postUrl, boardName):
|
|
130 self.board = board
|
|
131 self.postUrl = postUrl
|
|
132 self.boardName = boardName
|
|
133 self.last = None
|
|
134
|
|
135 def poll(self):
|
|
136 try:
|
|
137 new = self.board.getDoor()
|
|
138 if new != self.last:
|
|
139 msg = json.dumps(dict(board=self.boardName,
|
|
140 name="frontDoor", state=new))
|
|
141 getPage(self.postUrl,
|
|
142 method="POST",
|
|
143 postdata=msg,
|
|
144 headers={'Content-Type' : 'application/json'}
|
|
145 ).addErrback(self.reportError, msg)
|
|
146
|
|
147 self.last = new
|
|
148 except (IOError, OSError):
|
|
149 os.abort()
|
|
150 except Exception, e:
|
|
151 print "poll error", e
|
|
152 traceback.print_exc()
|
|
153
|
|
154 def reportError(self, msg, *args):
|
|
155 print "post error", msg, args
|
|
156
|
|
157 if __name__ == '__main__':
|
|
158
|
|
159 port = '/dev/ttyUSB0'
|
|
160 if not os.path.exists(port):
|
|
161 port = '/dev/ttyUSB1'
|
|
162
|
|
163 config = { # to be read from a file
|
|
164 'arduinoPort': port,
|
|
165 'servePort' : 9080,
|
|
166 'pollFrequency' : 1,
|
|
167 'boardName' : 'frontDoor', # gets sent with updates
|
|
168 'doorChangePost' : 'http://bang.bigasterisk.com:9069/inputChange',
|
|
169 # todo: need options to preset inputs/outputs at startup
|
|
170 }
|
|
171
|
|
172 log.startLogging(sys.stdout)
|
|
173
|
|
174 board = Board(port=config['arduinoPort'])
|
|
175
|
|
176 p = Poller(board, config['doorChangePost'], config['boardName'])
|
|
177 task.LoopingCall(p.poll).start(1/config['pollFrequency'])
|
|
178 reactor.listenTCP(config['servePort'], Application(board))
|
|
179 reactor.run()
|