Mercurial > code > home > repos > homeauto
comparison service/arduinoNode/arduinoNode.py @ 1139:db955e7943af
arduinonode reads config from etcd. use pushConfig.py to inform all nodes
Ignore-this: 3155d873bb30ca82b48ace7531a550e5
darcs-hash:0b2be13fbb4dc2571bb1bb8ac97f6a1350f36e77
author | drewp <drewp@bigasterisk.com> |
---|---|
date | Sat, 03 Mar 2018 17:53:37 -0800 |
parents | 1e43ec4a5f23 |
children | 3010238b94a0 |
comparison
equal
deleted
inserted
replaced
1138:08615804ee0e | 1139:db955e7943af |
---|---|
1 """ | |
2 depends on packages: | |
3 arduino-mk | |
4 indent | |
5 """ | |
6 from __future__ import division | 1 from __future__ import division |
7 import glob, sys, logging, subprocess, socket, os, hashlib, time, tempfile | 2 import glob, sys, logging, subprocess, socket, hashlib, time, tempfile |
8 import shutil, json | 3 import shutil, json |
9 import serial | 4 import serial |
10 import cyclone.web | 5 import cyclone.web |
11 from cyclone.httpclient import fetch | 6 from cyclone.httpclient import fetch |
12 from rdflib import Graph, Namespace, URIRef, Literal, RDF, ConjunctiveGraph | 7 from rdflib import Graph, Namespace, URIRef, Literal, RDF, ConjunctiveGraph |
13 from rdflib.parser import StringInputSource | 8 from rdflib.parser import StringInputSource |
14 from twisted.internet import reactor, task | 9 from twisted.internet import reactor, task |
10 from twisted.internet.defer import inlineCallbacks | |
11 from twisted.internet.threads import deferToThread | |
15 from docopt import docopt | 12 from docopt import docopt |
13 import etcd3 | |
16 | 14 |
17 import devices | 15 import devices |
18 import write_arduino_code | 16 import write_arduino_code |
19 import dotrender | 17 import dotrender |
20 import rdflib_patch | 18 import rdflib_patch |
41 | 39 |
42 ACTION_BASE = 10 # higher than any of the fixed command numbers | 40 ACTION_BASE = 10 # higher than any of the fixed command numbers |
43 | 41 |
44 hostname = socket.gethostname() | 42 hostname = socket.gethostname() |
45 CTX = ROOM['arduinosOn%s' % hostname] | 43 CTX = ROOM['arduinosOn%s' % hostname] |
44 etcd = etcd3.client(host='bang6') | |
46 | 45 |
47 class Config(object): | 46 class Config(object): |
48 def __init__(self, masterGraph): | 47 def __init__(self, masterGraph, slowMode=False): |
49 self.graph = ConjunctiveGraph() | 48 self.masterGraph = masterGraph |
49 self.slowMode = slowMode | |
50 self.configGraph = ConjunctiveGraph() | |
51 | |
52 self.etcPrefix = 'arduino/' | |
53 | |
54 self.boards = [] | |
55 self.reread() | |
56 | |
57 self.rereadLater = None | |
58 deferToThread(self.watchEtcd) | |
59 | |
60 def watchEtcd(self): | |
61 events, cancel = etcd.watch_prefix(self.etcPrefix) | |
62 reactor.addSystemEventTrigger('before', 'shutdown', cancel) | |
63 for ev in events: | |
64 log.info('%s changed', ev.key) | |
65 reactor.callFromThread(self.configChanged) | |
66 | |
67 def configChanged(self): | |
68 if self.rereadLater: | |
69 self.rereadLater.cancel() | |
70 self.rereadLater = reactor.callLater(.1, self.reread) | |
71 | |
72 def reread(self): | |
73 self.rereadLater = None | |
50 log.info('read config') | 74 log.info('read config') |
51 for f in os.listdir('config'): | 75 self.configGraph = ConjunctiveGraph() |
52 if f.startswith('.'): continue | 76 for v, md in etcd.get_prefix(self.etcPrefix): |
53 self.graph.parse('config/%s' % f, format='n3') | 77 log.info(' read file %r', md.key) |
54 self.graph.bind('', ROOM) # not working | 78 self.configGraph.parse(StringInputSource(v), format='n3') |
55 self.graph.bind('rdf', RDF) | 79 self.configGraph.bind('', ROOM) # not working |
80 self.configGraph.bind('rdf', RDF) | |
56 # config graph is too noisy; maybe make it a separate resource | 81 # config graph is too noisy; maybe make it a separate resource |
57 #masterGraph.patch(Patch(addGraph=self.graph)) | 82 #masterGraph.patch(Patch(addGraph=self.configGraph)) |
83 self.setupBoards() | |
58 | 84 |
59 def serialDevices(self): | 85 def serialDevices(self): |
60 return dict([(row.dev, row.board) for row in self.graph.query( | 86 return dict([(row.dev, row.board) for row in self.configGraph.query( |
61 """SELECT ?board ?dev WHERE { | 87 """SELECT ?board ?dev WHERE { |
62 ?board :device ?dev; | 88 ?board :device ?dev; |
63 a :ArduinoBoard . | 89 a :ArduinoBoard . |
64 }""", initNs={'': ROOM})]) | 90 }""", initNs={'': ROOM})]) |
91 | |
92 def setupBoards(self): | |
93 current = currentSerialDevices() | |
94 | |
95 self.boards = [] | |
96 for dev, board in self.serialDevices().items(): | |
97 if str(dev) not in current: | |
98 continue | |
99 log.info("we have board %s connected at %s" % (board, dev)) | |
100 b = Board(dev, self.configGraph, self.masterGraph, board) | |
101 self.boards.append(b) | |
102 | |
103 for b in self.boards: | |
104 b.deployToArduino() | |
105 | |
106 log.info('open boards') | |
107 for b in self.boards: | |
108 b.startPolling(period=.1 if not self.slowMode else 10) | |
109 | |
65 | 110 |
66 class Board(object): | 111 class Board(object): |
67 """an arduino connected to this computer""" | 112 """an arduino connected to this computer""" |
68 baudrate = 115200 | 113 baudrate = 115200 |
69 def __init__(self, dev, configGraph, masterGraph, uri): | 114 def __init__(self, dev, configGraph, masterGraph, uri): |
165 self._influx.exportToInflux(stmts) | 210 self._influx.exportToInflux(stmts) |
166 | 211 |
167 def _sendOneshot(self, oneshot): | 212 def _sendOneshot(self, oneshot): |
168 body = (' '.join('%s %s %s .' % (s.n3(), p.n3(), o.n3()) | 213 body = (' '.join('%s %s %s .' % (s.n3(), p.n3(), o.n3()) |
169 for s,p,o in oneshot)).encode('utf8') | 214 for s,p,o in oneshot)).encode('utf8') |
170 bang6 = 'fcb8:4119:fb46:96f8:8b07:1260:0f50:fcfa' | |
171 fetch(method='POST', | 215 fetch(method='POST', |
172 url='http://[%s]:9071/oneShot' % bang6, | 216 url='http://bang6:9071/oneShot', |
173 headers={'Content-Type': ['text/n3']}, postdata=body, | 217 headers={'Content-Type': ['text/n3']}, postdata=body, |
174 timeout=5) | 218 timeout=5) |
175 | 219 |
176 def outputStatements(self, stmts): | 220 def outputStatements(self, stmts): |
177 unused = set(stmts) | 221 unused = set(stmts) |
285 return g | 329 return g |
286 | 330 |
287 class Dot(cyclone.web.RequestHandler): | 331 class Dot(cyclone.web.RequestHandler): |
288 def get(self): | 332 def get(self): |
289 configGraph = self.settings.config.graph | 333 configGraph = self.settings.config.graph |
290 dot = dotrender.render(configGraph, self.settings.boards) | 334 dot = dotrender.render(configGraph, self.settings.config.boards) |
291 self.write(dot) | 335 self.write(dot) |
292 | 336 |
293 class ArduinoCode(cyclone.web.RequestHandler): | 337 class ArduinoCode(cyclone.web.RequestHandler): |
294 def get(self): | 338 def get(self): |
295 board = [b for b in self.settings.boards if | 339 board = [b for b in self.settings.config.boards if |
296 b.uri == URIRef(self.get_argument('board'))][0] | 340 b.uri == URIRef(self.get_argument('board'))][0] |
297 self.set_header('Content-Type', 'text/plain') | 341 self.set_header('Content-Type', 'text/plain') |
298 code, cksum = board.generateArduinoCode() | 342 code, cksum = board.generateArduinoCode() |
299 self.write(code) | 343 self.write(code) |
300 | 344 |
305 | 349 |
306 class OutputPage(cyclone.web.RequestHandler): | 350 class OutputPage(cyclone.web.RequestHandler): |
307 def post(self): | 351 def post(self): |
308 # for old ui; use PUT instead | 352 # for old ui; use PUT instead |
309 stmts = list(rdfGraphBody(self.request.body, self.request.headers)) | 353 stmts = list(rdfGraphBody(self.request.body, self.request.headers)) |
310 for b in self.settings.boards: | 354 for b in self.settings.config.boards: |
311 b.outputStatements(stmts) | 355 b.outputStatements(stmts) |
312 | 356 |
313 def put(self): | 357 def put(self): |
314 subj = URIRef(self.get_argument('s')) | 358 subj = URIRef(self.get_argument('s')) |
315 pred = URIRef(self.get_argument('p')) | 359 pred = URIRef(self.get_argument('p')) |
319 obj = Literal(float(turtleLiteral)) | 363 obj = Literal(float(turtleLiteral)) |
320 except ValueError: | 364 except ValueError: |
321 obj = Literal(turtleLiteral) | 365 obj = Literal(turtleLiteral) |
322 | 366 |
323 stmt = (subj, pred, obj) | 367 stmt = (subj, pred, obj) |
324 for b in self.settings.boards: | 368 for b in self.settings.config.boards: |
325 b.outputStatements([stmt]) | 369 b.outputStatements([stmt]) |
326 | 370 |
327 | 371 |
328 class Boards(cyclone.web.RequestHandler): | 372 class Boards(cyclone.web.RequestHandler): |
329 def get(self): | 373 def get(self): |
330 self.set_header('Content-type', 'application/json') | 374 self.set_header('Content-type', 'application/json') |
331 self.write(json.dumps({ | 375 self.write(json.dumps({ |
332 'host': hostname, | 376 'host': hostname, |
333 'boards': [b.description() for b in self.settings.boards] | 377 'boards': [b.description() for b in self.settings.config.boards] |
334 }, indent=2)) | 378 }, indent=2)) |
335 | 379 |
336 def currentSerialDevices(): | 380 def currentSerialDevices(): |
337 log.info('find connected boards') | 381 log.info('find connected boards') |
338 return glob.glob('/dev/serial/by-id/*') | 382 return glob.glob('/dev/serial/by-id/*') |
340 def main(): | 384 def main(): |
341 arg = docopt(""" | 385 arg = docopt(""" |
342 Usage: arduinoNode.py [options] | 386 Usage: arduinoNode.py [options] |
343 | 387 |
344 -v Verbose | 388 -v Verbose |
389 -s serial logging | |
390 -l slow polling | |
345 """) | 391 """) |
346 log.setLevel(logging.WARN) | 392 log.setLevel(logging.WARN) |
347 if arg['-v']: | 393 if arg['-v']: |
348 from twisted.python import log as twlog | 394 from twisted.python import log as twlog |
349 twlog.startLogging(sys.stdout) | 395 twlog.startLogging(sys.stdout) |
350 | 396 |
351 log.setLevel(logging.DEBUG) | 397 log.setLevel(logging.DEBUG) |
398 if arg['-s']: | |
399 logging.getLogger('serial').setLevel(logging.INFO) | |
352 | 400 |
353 masterGraph = PatchableGraph() | 401 masterGraph = PatchableGraph() |
354 config = Config(masterGraph) | 402 config = Config(masterGraph, slowMode=arg['-l']) |
355 current = currentSerialDevices() | |
356 | |
357 boards = [] | |
358 for dev, board in config.serialDevices().items(): | |
359 if str(dev) not in current: | |
360 continue | |
361 log.info("we have board %s connected at %s" % (board, dev)) | |
362 b = Board(dev, config.graph, masterGraph, board) | |
363 boards.append(b) | |
364 | |
365 for b in boards: | |
366 b.deployToArduino() | |
367 | |
368 log.info('open boards') | |
369 for b in boards: | |
370 b.startPolling(period=.1 if not arg['-v'] else 10) | |
371 | |
372 | 403 |
373 reactor.listenTCP(9059, cyclone.web.Application([ | 404 reactor.listenTCP(9059, cyclone.web.Application([ |
374 (r"/()", cyclone.web.StaticFileHandler, { | 405 (r"/()", cyclone.web.StaticFileHandler, { |
375 "path": "static", "default_filename": "index.html"}), | 406 "path": "static", "default_filename": "index.html"}), |
376 (r'/static/(.*)', cyclone.web.StaticFileHandler, {"path": "static"}), | 407 (r'/static/(.*)', cyclone.web.StaticFileHandler, {"path": "static"}), |
378 (r"/graph", CycloneGraphHandler, {'masterGraph': masterGraph}), | 409 (r"/graph", CycloneGraphHandler, {'masterGraph': masterGraph}), |
379 (r"/graph/events", CycloneGraphEventsHandler, {'masterGraph': masterGraph}), | 410 (r"/graph/events", CycloneGraphEventsHandler, {'masterGraph': masterGraph}), |
380 (r'/output', OutputPage), | 411 (r'/output', OutputPage), |
381 (r'/arduinoCode', ArduinoCode), | 412 (r'/arduinoCode', ArduinoCode), |
382 (r'/dot', Dot), | 413 (r'/dot', Dot), |
383 ], config=config, boards=boards), interface='::') | 414 ], config=config), interface='::') |
384 reactor.run() | 415 reactor.run() |
385 | 416 |
386 main() | 417 main() |