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