comparison service/arduinoNode/arduinoNode.py @ 1423:ba56263fe3b2

arduinonode to docker Ignore-this: 8f689c7491819bc47200018b517fd7de darcs-hash:e3602fc781c7b66e98ca950d5782ecc41e506bad
author drewp <drewp@bigasterisk.com>
date Wed, 07 Aug 2019 20:23:04 -0700
parents 672a3d830e7f
children 686079900c20
comparison
equal deleted inserted replaced
1422:975d6c25be7d 1423:ba56263fe3b2
1 from __future__ import division 1 from __future__ import division
2 import glob, sys, logging, subprocess, socket, hashlib, time, tempfile 2 import glob, sys, logging, subprocess, socket, hashlib, time, tempfile, pkg_resources
3 import shutil, json 3 import shutil, json
4 import serial 4 import serial
5 import cyclone.web 5 import cyclone.web
6 from cyclone.httpclient import fetch 6 from cyclone.httpclient import fetch
7 from rdflib import Graph, Namespace, URIRef, Literal, RDF, ConjunctiveGraph 7 from rdflib import Graph, Namespace, URIRef, Literal, RDF, ConjunctiveGraph
22 22
23 logging.basicConfig(level=logging.DEBUG) 23 logging.basicConfig(level=logging.DEBUG)
24 24
25 from loggingserial import LoggingSerial 25 from loggingserial import LoggingSerial
26 26
27 sys.path.append("../../lib")
28 from patchablegraph import PatchableGraph, CycloneGraphHandler, CycloneGraphEventsHandler 27 from patchablegraph import PatchableGraph, CycloneGraphHandler, CycloneGraphEventsHandler
29 from export_to_influxdb import InfluxExporter 28 from export_to_influxdb import InfluxExporter
30 29
31 sys.path.append("/my/proj/rdfdb")
32 from rdfdb.patch import Patch 30 from rdfdb.patch import Patch
33 from rdfdb.rdflibpatch import inContext 31 from rdfdb.rdflibpatch import inContext
34 32
35 33
36 log = logging.getLogger() 34 log = logging.getLogger()
55 self.masterGraph = masterGraph 53 self.masterGraph = masterGraph
56 self.slowMode = slowMode 54 self.slowMode = slowMode
57 self.configGraph = ConjunctiveGraph() 55 self.configGraph = ConjunctiveGraph()
58 56
59 self.etcPrefix = 'arduino/' 57 self.etcPrefix = 'arduino/'
60 58
61 self.boards = [] 59 self.boards = []
62 self.reread() 60 self.reread()
63 61
64 deferToThread(self.watchEtcd) 62 deferToThread(self.watchEtcd)
65 63
76 74
77 def cancelRead(self): 75 def cancelRead(self):
78 if getattr(self, 'rereadLater', None): 76 if getattr(self, 'rereadLater', None):
79 self.rereadLater.cancel() 77 self.rereadLater.cancel()
80 self.rereadLater = None 78 self.rereadLater = None
81 79
82 def reread(self): 80 def reread(self):
83 self.cancelRead() 81 self.cancelRead()
84 log.info('read config') 82 log.info('read config')
85 self.configGraph = ConjunctiveGraph() 83 self.configGraph = ConjunctiveGraph()
86 for v, md in etcd.get_prefix(self.etcPrefix): 84 for v, md in etcd.get_prefix(self.etcPrefix):
115 113
116 log.info('open boards') 114 log.info('open boards')
117 for b in self.boards: 115 for b in self.boards:
118 b.startPolling(period=.1 if not self.slowMode else 10) 116 b.startPolling(period=.1 if not self.slowMode else 10)
119 117
120 118
121 class Board(object): 119 class Board(object):
122 """an arduino connected to this computer""" 120 """an arduino connected to this computer"""
123 baudrate = 115200 121 baudrate = 115200
124 def __init__(self, dev, configGraph, masterGraph, uri): 122 def __init__(self, dev, configGraph, masterGraph, uri):
125 """ 123 """
136 # deployToArduino call and the poll call. 134 # deployToArduino call and the poll call.
137 self._devs = devices.makeDevices(configGraph, self.uri) 135 self._devs = devices.makeDevices(configGraph, self.uri)
138 self._devCommandNum = dict((dev.uri, ACTION_BASE + devIndex) 136 self._devCommandNum = dict((dev.uri, ACTION_BASE + devIndex)
139 for devIndex, dev in enumerate(self._devs)) 137 for devIndex, dev in enumerate(self._devs))
140 self._polledDevs = [d for d in self._devs if d.generatePollCode()] 138 self._polledDevs = [d for d in self._devs if d.generatePollCode()]
141 139
142 self._statementsFromInputs = {} # input device uri: latest statements 140 self._statementsFromInputs = {} # input device uri: latest statements
143 self._lastPollTime = {} # input device uri: time() 141 self._lastPollTime = {} # input device uri: time()
144 self._influx = InfluxExporter(self.configGraph) 142 self._influx = InfluxExporter(self.configGraph)
145 self.open() 143 self.open()
146 for d in self._devs: 144 for d in self._devs:
152 'uri': self.uri, 150 'uri': self.uri,
153 'dev': self.dev, 151 'dev': self.dev,
154 'baudrate': self.baudrate, 152 'baudrate': self.baudrate,
155 'devices': [d.description() for d in self._devs], 153 'devices': [d.description() for d in self._devs],
156 } 154 }
157 155
158 def open(self): 156 def open(self):
159 self.ser = LoggingSerial(port=self.dev, baudrate=self.baudrate, 157 self.ser = LoggingSerial(port=self.dev, baudrate=self.baudrate,
160 timeout=2) 158 timeout=2)
161 159
162 def startPolling(self, period=.5): 160 def startPolling(self, period=.5):
163 task.LoopingCall(self._poll).start(period) 161 task.LoopingCall(self._poll).start(period)
164 162
165 def _poll(self): 163 def _poll(self):
166 """ 164 """
167 even boards with no inputs need some polling to see if they're 165 even boards with no inputs need some polling to see if they're
168 still ok 166 still ok
169 """ 167 """
173 reactor.crash() 171 reactor.crash()
174 raise 172 raise
175 except Exception as e: 173 except Exception as e:
176 import traceback; traceback.print_exc() 174 import traceback; traceback.print_exc()
177 log.warn("poll: %r" % e) 175 log.warn("poll: %r" % e)
178 176
179 def _pollWork(self): 177 def _pollWork(self):
180 t1 = time.time() 178 t1 = time.time()
181 self.ser.write("\x60\x00") # "poll everything" 179 self.ser.write("\x60\x00") # "poll everything"
182 for i in self._polledDevs: 180 for i in self._polledDevs:
183 try: 181 try:
227 # dev1 changes from 2 to 4 but this patch will 225 # dev1 changes from 2 to 4 but this patch will
228 # fail since the '2' statement is gone) 226 # fail since the '2' statement is gone)
229 self.masterGraph.patch(Patch.fromDiff(inContext(prev, dev), 227 self.masterGraph.patch(Patch.fromDiff(inContext(prev, dev),
230 inContext(new, dev))) 228 inContext(new, dev)))
231 self._statementsFromInputs[dev] = new 229 self._statementsFromInputs[dev] = new
232 230
233 def _sendOneshot(self, oneshot): 231 def _sendOneshot(self, oneshot):
234 body = (' '.join('%s %s %s .' % (s.n3(), p.n3(), o.n3()) 232 body = (' '.join('%s %s %s .' % (s.n3(), p.n3(), o.n3())
235 for s,p,o in oneshot)).encode('utf8') 233 for s,p,o in oneshot)).encode('utf8')
236 fetch(method='POST', 234 fetch(method='POST',
237 url='http://bang6:9071/oneShot', 235 url='http://bang6:9071/oneShot',
307 else: 305 else:
308 log.info("board responds with incorrect code version") 306 log.info("board responds with incorrect code version")
309 except Exception as e: 307 except Exception as e:
310 log.info("can't get code version from board: %r" % e) 308 log.info("can't get code version from board: %r" % e)
311 return False 309 return False
312 310
313 def deployToArduino(self): 311 def deployToArduino(self):
314 code, cksum = self.generateArduinoCode() 312 code, cksum = self.generateArduinoCode()
315 313
316 if self._boardIsCurrent(cksum): 314 if self._boardIsCurrent(cksum):
317 return 315 return
318 316
319 try: 317 try:
320 if hasattr(self, 'ser'): 318 if hasattr(self, 'ser'):
321 self.ser.close() 319 self.ser.close()
322 workDir = tempfile.mkdtemp(prefix='arduinoNode_board_deploy') 320 workDir = tempfile.mkdtemp(prefix='arduinoNode_board_deploy')
323 try: 321 try:
336 334
337 with open(workDir + '/main.ino', 'w') as main: 335 with open(workDir + '/main.ino', 'w') as main:
338 main.write(code) 336 main.write(code)
339 337
340 subprocess.check_call(['make', 'upload'], cwd=workDir) 338 subprocess.check_call(['make', 'upload'], cwd=workDir)
341 339
342 340
343 def currentGraph(self): 341 def currentGraph(self):
344 g = Graph() 342 g = Graph()
345 343
346 344
347 for dev in self._devs: 345 for dev in self._devs:
348 for stmt in dev.hostStatements(): 346 for stmt in dev.hostStatements():
349 g.add(stmt) 347 g.add(stmt)
350 return g 348 return g
352 class Dot(cyclone.web.RequestHandler): 350 class Dot(cyclone.web.RequestHandler):
353 def get(self): 351 def get(self):
354 configGraph = self.settings.config.graph 352 configGraph = self.settings.config.graph
355 dot = dotrender.render(configGraph, self.settings.config.boards) 353 dot = dotrender.render(configGraph, self.settings.config.boards)
356 self.write(dot) 354 self.write(dot)
357 355
358 class ArduinoCode(cyclone.web.RequestHandler): 356 class ArduinoCode(cyclone.web.RequestHandler):
359 def get(self): 357 def get(self):
360 board = [b for b in self.settings.config.boards if 358 board = [b for b in self.settings.config.boards if
361 b.uri == URIRef(self.get_argument('board'))][0] 359 b.uri == URIRef(self.get_argument('board'))][0]
362 self.set_header('Content-Type', 'text/plain') 360 self.set_header('Content-Type', 'text/plain')
372 def post(self): 370 def post(self):
373 # for old ui; use PUT instead 371 # for old ui; use PUT instead
374 stmts = list(rdfGraphBody(self.request.body, self.request.headers)) 372 stmts = list(rdfGraphBody(self.request.body, self.request.headers))
375 for b in self.settings.config.boards: 373 for b in self.settings.config.boards:
376 b.outputStatements(stmts) 374 b.outputStatements(stmts)
377 375
378 def put(self): 376 def put(self):
379 subj = URIRef(self.get_argument('s')) 377 subj = URIRef(self.get_argument('s'))
380 pred = URIRef(self.get_argument('p')) 378 pred = URIRef(self.get_argument('p'))
381 379
382 turtleLiteral = self.request.body 380 turtleLiteral = self.request.body
386 obj = Literal(turtleLiteral) 384 obj = Literal(turtleLiteral)
387 385
388 stmt = (subj, pred, obj) 386 stmt = (subj, pred, obj)
389 for b in self.settings.config.boards: 387 for b in self.settings.config.boards:
390 b.outputStatements([stmt]) 388 b.outputStatements([stmt])
391 389
392 390
393 class Boards(cyclone.web.RequestHandler): 391 class Boards(cyclone.web.RequestHandler):
394 def get(self): 392 def get(self):
395 self.set_header('Content-type', 'application/json') 393 self.set_header('Content-type', 'application/json')
396 self.write(json.dumps({ 394 self.write(json.dumps({
397 'host': hostname, 395 'host': hostname,
398 'boards': [b.description() for b in self.settings.config.boards] 396 'boards': [b.description() for b in self.settings.config.boards]
399 }, indent=2)) 397 }, indent=2))
400 398
401 def currentSerialDevices(): 399 def currentSerialDevices():
402 log.info('find connected boards') 400 log.info('find connected boards')
403 return glob.glob('/dev/serial/by-id/*') 401 return glob.glob('/dev/serial/by-id/*')
404 402
405 def main(): 403 def main():
406 arg = docopt(""" 404 arg = docopt("""
407 Usage: arduinoNode.py [options] 405 Usage: arduinoNode.py [options]
408 406
409 -v Verbose 407 -v Verbose
419 if arg['-s']: 417 if arg['-s']:
420 logging.getLogger('serial').setLevel(logging.INFO) 418 logging.getLogger('serial').setLevel(logging.INFO)
421 419
422 masterGraph = PatchableGraph() 420 masterGraph = PatchableGraph()
423 config = Config(masterGraph, slowMode=arg['-l']) 421 config = Config(masterGraph, slowMode=arg['-l'])
422 static = pkg_resources.resource_filename('homeauto_anynode', 'static/')
424 423
425 reactor.listenTCP(9059, cyclone.web.Application([ 424 reactor.listenTCP(9059, cyclone.web.Application([
426 (r"/()", cyclone.web.StaticFileHandler, { 425 (r"/(|output-widgets.html)", cyclone.web.StaticFileHandler, {
427 "path": "static", "default_filename": "index.html"}), 426 "path": static, "default_filename": "index.html"}),
428 (r'/static/(.*)', cyclone.web.StaticFileHandler, {"path": "static"}), 427 (r'/static/(.*)', cyclone.web.StaticFileHandler, {"path": "static"}),
429 (r'/stats/(.*)', StatsHandler, {'serverName': 'arduinoNode'}), 428 (r'/stats/(.*)', StatsHandler, {'serverName': 'arduinoNode'}),
430 (r'/boards', Boards), 429 (r'/boards', Boards),
431 (r"/graph", CycloneGraphHandler, {'masterGraph': masterGraph}), 430 (r"/graph", CycloneGraphHandler, {'masterGraph': masterGraph}),
432 (r"/graph/events", CycloneGraphEventsHandler, {'masterGraph': masterGraph}), 431 (r"/graph/events", CycloneGraphEventsHandler, {'masterGraph': masterGraph}),