Mercurial > code > home > repos > homeauto
comparison service/arduinoNode/arduinoNode.py @ 166:c0180bd2b33a
only recompile if the C code is new. redo Device class api. single temperature sensor is working
Ignore-this: e78106d25dbb2ac8c5e5d8a81d576358
author | drewp@bigasterisk.com |
---|---|
date | Sat, 11 Apr 2015 01:43:59 -0700 |
parents | 49c1756b2edb |
children | d228105749ac |
comparison
equal
deleted
inserted
replaced
165:af4e9d9f0bd8 | 166:c0180bd2b33a |
---|---|
1 """ | |
2 depends on arduino-mk | |
3 """ | |
1 import shutil | 4 import shutil |
2 import tempfile | 5 import tempfile |
3 import glob, sys, logging, subprocess | 6 import glob, sys, logging, subprocess, socket, os, hashlib, time |
4 import cyclone.web | 7 import cyclone.web |
5 from rdflib import Graph, Namespace, URIRef, Literal, RDF | 8 from rdflib import Graph, Namespace, URIRef, Literal, RDF |
6 from twisted.internet import reactor, task | 9 from twisted.internet import reactor, task |
7 import devices | 10 import devices |
11 import dotrender | |
8 | 12 |
9 logging.basicConfig(level=logging.DEBUG) | 13 logging.basicConfig(level=logging.DEBUG) |
10 | 14 |
11 from loggingserial import LoggingSerial | 15 from loggingserial import LoggingSerial |
12 | 16 |
14 from stategraph import StateGraph | 18 from stategraph import StateGraph |
15 | 19 |
16 log = logging.getLogger() | 20 log = logging.getLogger() |
17 logging.getLogger('serial').setLevel(logging.WARN) | 21 logging.getLogger('serial').setLevel(logging.WARN) |
18 | 22 |
23 import rdflib.namespace | |
24 old_split = rdflib.namespace.split_uri | |
25 def new_split(uri): | |
26 try: | |
27 return old_split(uri) | |
28 except Exception: | |
29 return uri, '' | |
30 rdflib.namespace.split_uri = new_split | |
19 | 31 |
20 ROOM = Namespace('http://projects.bigasterisk.com/room/') | 32 ROOM = Namespace('http://projects.bigasterisk.com/room/') |
33 HOST = Namespace('http://bigasterisk.com/ruler/host/') | |
21 | 34 |
22 class Config(object): | 35 class Config(object): |
23 def __init__(self): | 36 def __init__(self): |
24 self.graph = Graph() | 37 self.graph = Graph() |
25 log.info('read config') | 38 log.info('read config') |
26 self.graph.bind('', ROOM) | 39 self.graph.parse('config.n3', format='n3') |
40 self.graph.bind('', ROOM) # not working | |
27 self.graph.bind('rdf', RDF) | 41 self.graph.bind('rdf', RDF) |
28 self.graph.parse('config.n3', format='n3') | |
29 | 42 |
30 def serialDevices(self): | 43 def serialDevices(self): |
31 return dict([(row.dev, row.board) for row in self.graph.query( | 44 return dict([(row.dev, row.board) for row in self.graph.query( |
32 """SELECT ?board ?dev WHERE { | 45 """SELECT ?board ?dev WHERE { |
33 ?board :device ?dev; | 46 ?board :device ?dev; |
34 a :ArduinoBoard . | 47 a :ArduinoBoard . |
35 }""", initNs={'': ROOM})]) | 48 }""", initNs={'': ROOM})]) |
36 | 49 |
37 class Board(object): | 50 class Board(object): |
38 """an arduino connected to this computer""" | 51 """an arduino connected to this computer""" |
39 baudrate = 115200 | 52 baudrate = 115200 |
40 def __init__(self, dev, graph, uri, onChange): | 53 def __init__(self, dev, graph, uri, onChange): |
41 """ | 54 """ |
48 self.graph = graph | 61 self.graph = graph |
49 self.dev = dev | 62 self.dev = dev |
50 | 63 |
51 # The order of this list needs to be consistent between the | 64 # The order of this list needs to be consistent between the |
52 # deployToArduino call and the poll call. | 65 # deployToArduino call and the poll call. |
53 self._inputs = [devices.PingInput(graph, self.uri)] | 66 self._devs = devices.makeDevices(graph, self.uri) |
54 for row in graph.query("""SELECT ?dev WHERE { | 67 self._polledDevs = [d for d in self._devs if d.generatePollCode()] |
55 ?board :hasPin ?pin . | |
56 ?pin :connectedTo ?dev . | |
57 } ORDER BY ?dev""", | |
58 initBindings=dict(board=self.uri), | |
59 initNs={'': ROOM}): | |
60 self._inputs.append(devices.makeBoardInput(graph, row.dev)) | |
61 | 68 |
62 self._statementsFromInputs = {} # input uri: latest statements | 69 self._statementsFromInputs = {} # input uri: latest statements |
63 | 70 |
71 self.open() | |
64 | 72 |
65 def open(self): | 73 def open(self): |
66 self.ser = LoggingSerial(port=self.dev, baudrate=self.baudrate, timeout=2) | 74 self.ser = LoggingSerial(port=self.dev, baudrate=self.baudrate, |
75 timeout=2) | |
67 | 76 |
68 def startPolling(self): | 77 def startPolling(self): |
69 task.LoopingCall(self._poll).start(.5) | 78 task.LoopingCall(self._poll).start(.5) |
70 | 79 |
71 def _poll(self): | 80 def _poll(self): |
78 except Exception as e: | 87 except Exception as e: |
79 log.warn("poll: %r" % e) | 88 log.warn("poll: %r" % e) |
80 | 89 |
81 def _pollWork(self): | 90 def _pollWork(self): |
82 self.ser.write("\x60\x00") | 91 self.ser.write("\x60\x00") |
83 for i in self._inputs: | 92 for i in self._polledDevs: |
84 self._statementsFromInputs[i.uri] = i.readFromPoll(self.ser.read) | 93 self._statementsFromInputs[i.uri] = i.readFromPoll(self.ser.read) |
85 #plus statements about succeeding or erroring on the last poll | 94 #plus statements about succeeding or erroring on the last poll |
86 | 95 |
87 def currentGraph(self): | 96 def currentGraph(self): |
88 g = Graph() | 97 g = Graph() |
98 | |
99 g.add((HOST[socket.gethostname()], ROOM['connectedTo'], self.uri)) | |
100 | |
89 for si in self._statementsFromInputs.values(): | 101 for si in self._statementsFromInputs.values(): |
90 for s in si: | 102 for s in si: |
91 g.add(s) | 103 g.add(s) |
92 return g | 104 return g |
93 | 105 |
94 def generateArduinoCode(self): | 106 def generateArduinoCode(self): |
95 generated = {'baudrate': self.baudrate, 'setups': '', 'polls': ''} | 107 generated = { |
96 for attr in ['setups', 'polls']: | 108 'baudrate': self.baudrate, |
97 for i in self._inputs: | 109 'includes': '', |
98 gen = (i.generateSetupCode() if attr == 'setups' | 110 'global': '', |
99 else i.generatePollCode()) | 111 'setups': '', |
100 generated[attr] += '// for %s\n%s\n' % (i.uri, gen) | 112 'polls': '' |
113 } | |
114 for attr in ['includes', 'global', 'setups', 'polls']: | |
115 for i in self._devs: | |
116 if attr == 'includes': | |
117 gen = '\n'.join('#include "%s"\n' % inc | |
118 for inc in i.generateIncludes()) | |
119 elif attr == 'global': gen = i.generateGlobalCode() | |
120 elif attr == 'setups': gen = i.generateSetupCode() | |
121 elif attr == 'polls': gen = i.generatePollCode() | |
122 else: raise NotImplementedError | |
123 | |
124 if gen: | |
125 generated[attr] += '// for %s\n%s\n' % (i.uri, gen) | |
101 | 126 |
102 return ''' | 127 return ''' |
128 %(includes)s | |
129 | |
130 %(global)s | |
131 | |
103 void setup() { | 132 void setup() { |
104 Serial.begin(%(baudrate)d); | 133 Serial.begin(%(baudrate)d); |
105 Serial.flush(); | 134 Serial.flush(); |
106 %(setups)s | 135 %(setups)s |
107 } | 136 } |
115 return; | 144 return; |
116 } | 145 } |
117 cmd = Serial.read(); | 146 cmd = Serial.read(); |
118 if (cmd == 0x00) { | 147 if (cmd == 0x00) { |
119 %(polls)s; | 148 %(polls)s; |
149 } else if (cmd == 0x01) { | |
150 Serial.write("CODE_CHECKSUM"); | |
120 } | 151 } |
121 } | 152 } |
122 } | 153 } |
123 ''' % generated | 154 ''' % generated |
124 | 155 |
156 | |
157 def codeChecksum(self, code): | |
158 # this is run on the code without CODE_CHECKSUM replaced yet | |
159 return hashlib.sha1(code).hexdigest() | |
160 | |
161 def readBoardChecksum(self, length): | |
162 # this is likely right after reset, so it might take 2 seconds | |
163 for tries in range(6): | |
164 self.ser.write("\x60\x01") | |
165 try: | |
166 return self.ser.read(length) | |
167 except ValueError: | |
168 if tries == 5: | |
169 raise | |
170 time.sleep(.5) | |
171 raise ValueError | |
172 | |
173 def boardIsCurrent(self, currentChecksum): | |
174 try: | |
175 boardCksum = self.readBoardChecksum(len(currentChecksum)) | |
176 if boardCksum == currentChecksum: | |
177 log.info("board has current code (%s)" % currentChecksum) | |
178 return True | |
179 else: | |
180 log.info("board responds with incorrect code version") | |
181 except Exception as e: | |
182 log.info("can't get code version from board: %r" % e) | |
183 return False | |
184 | |
125 def deployToArduino(self): | 185 def deployToArduino(self): |
126 code = self.generateArduinoCode() | 186 code = self.generateArduinoCode() |
187 cksum = self.codeChecksum(code) | |
188 code = code.replace('CODE_CHECKSUM', cksum) | |
189 | |
190 if self.boardIsCurrent(cksum): | |
191 return | |
192 | |
127 try: | 193 try: |
128 if hasattr(self, 'ser'): | 194 if hasattr(self, 'ser'): |
129 self.ser.close() | 195 self.ser.close() |
130 workDir = tempfile.mkdtemp(prefix='arduinoNode_board_deploy') | 196 workDir = tempfile.mkdtemp(prefix='arduinoNode_board_deploy') |
131 try: | 197 try: |
137 | 203 |
138 def _arduinoMake(self, workDir, code): | 204 def _arduinoMake(self, workDir, code): |
139 with open(workDir + '/makefile', 'w') as makefile: | 205 with open(workDir + '/makefile', 'w') as makefile: |
140 makefile.write(''' | 206 makefile.write(''' |
141 BOARD_TAG = %(tag)s | 207 BOARD_TAG = %(tag)s |
142 USER_LIB_PATH := | 208 USER_LIB_PATH := %(libs)s |
143 ARDUINO_LIBS = | 209 ARDUINO_LIBS = %(arduinoLibs)s |
144 MONITOR_PORT = %(dev)s | 210 MONITOR_PORT = %(dev)s |
145 | 211 |
146 include /usr/share/arduino/Arduino.mk | 212 include /usr/share/arduino/Arduino.mk |
147 ''' % { | 213 ''' % { |
148 'dev': self.dev, | 214 'dev': self.dev, |
149 'tag': self.graph.value(self.uri, ROOM['boardTag']), | 215 'tag': self.graph.value(self.uri, ROOM['boardTag']), |
216 'libs': os.path.abspath('arduino-libraries'), | |
217 'arduinoLibs': ' '.join(sum((d.generateArduinoLibs() | |
218 for d in self._devs), [])), | |
150 }) | 219 }) |
151 | 220 |
152 with open(workDir + '/main.ino', 'w') as main: | 221 with open(workDir + '/main.ino', 'w') as main: |
153 main.write(code) | 222 main.write(code) |
154 | 223 |
175 self.set_header('Content-type', 'application/x-trig') | 244 self.set_header('Content-type', 'application/x-trig') |
176 self.write(g.asTrig()) | 245 self.write(g.asTrig()) |
177 | 246 |
178 class Dot(cyclone.web.RequestHandler): | 247 class Dot(cyclone.web.RequestHandler): |
179 def get(self): | 248 def get(self): |
180 nodes = {} # uri: nodeline | 249 configGraph = self.settings.config.graph |
181 edges = [] | 250 dot = dotrender.render(configGraph, self.settings.boards) |
182 | |
183 serial = [0] | |
184 def addNode(node): | |
185 if node not in nodes or isinstance(node, Literal): | |
186 id = 'node%s' % serial[0] | |
187 if isinstance(node, URIRef): | |
188 short = self.settings.config.graph.qname(node) | |
189 else: | |
190 short = str(node) | |
191 nodes[node] = ( | |
192 id, | |
193 '%s [ label="%s", shape = record, color = blue ];' % ( | |
194 id, short)) | |
195 serial[0] += 1 | |
196 else: | |
197 id = nodes[node][0] | |
198 return id | |
199 def addStmt(stmt): | |
200 ns = addNode(stmt[0]) | |
201 no = addNode(stmt[2]) | |
202 edges.append('%s -> %s [ label="%s" ];' % (ns, no, stmt[1])) | |
203 for b in self.settings.boards: | |
204 for stmt in b.currentGraph(): | |
205 # color these differently from config ones | |
206 addStmt(stmt) | |
207 for stmt in self.settings.config.graph: | |
208 addStmt(stmt) | |
209 | |
210 nodes = '\n'.join(line for _, line in nodes.values()) | |
211 edges = '\n'.join(edges) | |
212 dot = ''' | |
213 digraph { | |
214 rankdir = TB; | |
215 charset="utf-8"; | |
216 %(nodes)s | |
217 %(edges)s | |
218 } | |
219 ''' % dict(nodes=nodes, edges=edges) | |
220 self.write(dot) | 251 self.write(dot) |
221 | 252 |
222 class ArduinoCode(cyclone.web.RequestHandler): | 253 class ArduinoCode(cyclone.web.RequestHandler): |
223 def get(self): | 254 def get(self): |
224 board = [b for b in self.settings.boards if | 255 board = [b for b in self.settings.boards if |
245 continue | 276 continue |
246 log.info("we have board %s connected at %s" % (board, dev)) | 277 log.info("we have board %s connected at %s" % (board, dev)) |
247 b = Board(dev, config.graph, board, onChange) | 278 b = Board(dev, config.graph, board, onChange) |
248 boards.append(b) | 279 boards.append(b) |
249 | 280 |
250 #boards[0].deployToArduino() | 281 boards[0].deployToArduino() |
251 | 282 |
252 log.info('open boards') | 283 log.info('open boards') |
253 for b in boards: | 284 for b in boards: |
254 b.open() | |
255 b.startPolling() | 285 b.startPolling() |
256 | 286 |
257 from twisted.python import log as twlog | 287 from twisted.python import log as twlog |
258 twlog.startLogging(sys.stdout) | 288 twlog.startLogging(sys.stdout) |
259 | 289 |