Mercurial > code > home > repos > homeauto
changeset 975:f3023410d875
polymer board debug page with working output widgets
Ignore-this: 3157d0c47a91afe47b30a5f182629d93
darcs-hash:20150414063012-312f9-69ac15a9c7bab2b3b5ca00142fe4435ded7d6d0f
author | drewp <drewp@bigasterisk.com> |
---|---|
date | Mon, 13 Apr 2015 23:30:12 -0700 |
parents | f707210c13bd |
children | 6754aa8aab38 |
files | service/arduinoNode/arduinoNode.py service/arduinoNode/devices.py service/arduinoNode/static/index.html service/arduinoNode/static/output-widgets.html |
diffstat | 4 files changed, 314 insertions(+), 9 deletions(-) [+] |
line wrap: on
line diff
--- a/service/arduinoNode/arduinoNode.py Sun Apr 12 03:44:14 2015 -0700 +++ b/service/arduinoNode/arduinoNode.py Mon Apr 13 23:30:12 2015 -0700 @@ -5,7 +5,7 @@ """ from __future__ import division import glob, sys, logging, subprocess, socket, os, hashlib, time, tempfile -import shutil +import shutil, json import serial import cyclone.web from rdflib import Graph, Namespace, URIRef, Literal, RDF @@ -72,6 +72,15 @@ self.open() + def description(self): + """for web page""" + return { + 'uri': self.uri, + 'dev': self.dev, + 'baudrate': self.baudrate, + 'devices': [d.description() for d in self._devs], + } + def open(self): self.ser = LoggingSerial(port=self.dev, baudrate=self.baudrate, timeout=2) @@ -285,11 +294,6 @@ subprocess.check_call(['make', 'upload'], cwd=workDir) - -class Index(cyclone.web.RequestHandler): - def get(self): - self.set_header("Content-Type", "text/html") - self.write(open("index.html").read()) class GraphPage(cyclone.web.RequestHandler): def get(self): @@ -330,7 +334,15 @@ stmts = list(rdfGraphBody(self.request.body, self.request.headers)) for b in self.settings.boards: b.outputStatements(stmts) + +class Boards(cyclone.web.RequestHandler): + def get(self): + self.set_header('Content-type', 'application/json') + self.write(json.dumps({ + 'boards': [b.description() for b in self.settings.boards] + }, indent=2)) + def currentSerialDevices(): log.info('find connected boards') return glob.glob('/dev/serial/by-id/*') @@ -362,7 +374,10 @@ log.setLevel(logging.DEBUG) reactor.listenTCP(9059, cyclone.web.Application([ - (r"/", Index), + (r"/()", cyclone.web.StaticFileHandler, { + "path": "static", "default_filename": "index.html"}), + (r'/static/(.*)', cyclone.web.StaticFileHandler, {"path": "static"}), + (r'/boards', Boards), (r"/graph", GraphPage), (r'/output', OutputPage), (r'/arduinoCode', ArduinoCode),
--- a/service/arduinoNode/devices.py Sun Apr 12 03:44:14 2015 -0700 +++ b/service/arduinoNode/devices.py Mon Apr 13 23:30:12 2015 -0700 @@ -36,6 +36,16 @@ def __init__(self, graph, uri, pinNumber): self.graph, self.uri = graph, uri self.pinNumber = pinNumber + + def description(self): + return { + 'uri': self.uri, + 'className': self.__class__.__name__, + 'pinNumber': self.pinNumber, + 'outputPatterns': self.outputPatterns(), + 'watchPrefixes': self.watchPrefixes(), + 'outputWidgets': self.outputWidgets(), + } def readFromPoll(self, read): """ @@ -46,20 +56,37 @@ """ raise NotImplementedError('readFromPoll in %s' % self.__class__) + def watchPrefixes(self): + """ + subj,pred pairs of the statements that might be returned from + readFromPoll, so the dashboard knows what it should + watch. This should be eliminated, as the dashboard should just + always watch the whole tree of statements starting self.uri + """ + return [] + def generateIncludes(self): + """filenames of .h files to #include""" return [] def generateArduinoLibs(self): + """names of libraries for the ARDUINO_LIBS line in the makefile""" return [] def generateGlobalCode(self): + """C code to emit in the global section""" return '' def generateSetupCode(self): + """C code to emit in setup()""" return '' def generatePollCode(self): - """if this returns nothing, we don't try to poll this device""" + """ + C code to run a poll update. This should Serial.write its output + for readFromPoll to consume. If this returns nothing, we don't + try to poll this device. + """ return '' def generateActionCode(self): @@ -78,6 +105,13 @@ """ return [] + def outputWidgets(self): + """ + structs to make output widgets on the dashboard. ~1 of these per + handler you have in sendOutput + """ + return [] + def sendOutput(self, statements, write, read): """ If we got statements that match this class's outputPatterns, this @@ -103,11 +137,15 @@ def generatePollCode(self): return "Serial.write('k');" + def readFromPoll(self, read): if read(1) != 'k': raise ValueError('invalid ping response') return [(self.uri, ROOM['ping'], ROOM['ok'])] + def watchPrefixes(self): + return [(self.uri, ROOM['ping'])] + @register class MotionSensorInput(DeviceType): deviceType = ROOM['MotionSensor'] @@ -129,8 +167,15 @@ return [(self.uri, ROOM['sees'], ROOM['motion'] if motion else ROOM['noMotion'])] + def watchPrefixes(self): + return [(self.uri, ROOM['sees'])] + @register class OneWire(DeviceType): + """ + A OW bus with temperature sensors (and maybe other devices, which + are also to be handled under this object) + """ deviceType = ROOM['OneWire'] def generateIncludes(self): @@ -176,12 +221,20 @@ def readFromPoll(self, read): newTemp = readLine(read) retries = ord(read(1)) + # uri will change; there could (likely) be multiple connected sensors return [ (self.uri, ROOM['temperatureF'], Literal(newTemp, datatype=XSD['decimal'])), (self.uri, ROOM['temperatureRetries'], Literal(retries)), ] + def watchPrefixes(self): + # these uris will become dynamic! see note on watchPrefixes + # about eliminating it. + return [(self.uri, ROOM['temperatureF']), + (self.uri, ROOM['temperatureRetries']), + ] + def byteFromFloat(f): return chr(int(min(255, max(0, f * 255)))) @@ -209,6 +262,46 @@ while(Serial.available() < 1) NULL; analogWrite(%(pin)d, Serial.read()); ''' % dict(pin=self.pinNumber) + + def outputWidgets(self): + return [{ + 'element': 'output-slider', + 'min': 0, + 'max': 1, + 'step': 1 / 255, + 'subj': self.uri, + 'pred': ROOM['brightness'], + }] + +@register +class DigitalOutput(DeviceType): + deviceType = ROOM['DigitalOutput'] + def generateSetupCode(self): + return 'pinMode(%(pin)d, OUTPUT); digitalWrite(%(pin)d, LOW);' % { + 'pin': self.pinNumber, + } + + def outputPatterns(self): + return [(self.uri, ROOM['level'], None)] + + def sendOutput(self, statements, write, read): + assert len(statements) == 1 + assert statements[0][:2] == (self.uri, ROOM['level']) + value = {"high": 1, "low": 0}[str(statements[0][2])] + write(chr(value)) + + def generateActionCode(self): + return r''' + while(Serial.available() < 1) NULL; + digitalWrite(%(pin)d, Serial.read()); + ''' % dict(pin=self.pinNumber) + + def outputWidgets(self): + return [{ + 'element': 'output-switch', + 'subj': self.uri, + 'pred': ROOM['level'], + }] @register class ST7576Lcd(DeviceType): @@ -273,14 +366,26 @@ assert len(value) < 254, repr(value) write(chr(len(value)) + value) + def outputWidgets(self): + return [{ + 'element': 'output-fixed-text', + 'cols': 21, + 'rows': 8, + 'subj': self.uri, + 'pred': ROOM['text'], + }] + def generateActionCode(self): return ''' while(Serial.available() < 1) NULL; byte bufSize = Serial.read(); - for (byte i = 0; i < bufSize; i++) { + for (byte i = 0; i < bufSize; ++i) { while(Serial.available() < 1) NULL; newtxt[i] = Serial.read(); } + for (byte i = bufSize; i < sizeof(newtxt); ++i) { + newtxt[i] = 0; + } glcd.clear(); glcd.drawstring(0,0, newtxt); glcd.display();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/service/arduinoNode/static/index.html Mon Apr 13 23:30:12 2015 -0700 @@ -0,0 +1,115 @@ +<!doctype html> +<html> + <head> + <title>arduinoNode</title> + <meta charset="utf-8" /> + <link rel="import" href="/lib/polymer/0.5.2/polymer/polymer.html"> + <link rel="import" href="/lib/polymer/0.5.2/core-ajax/core-ajax.html"> + <link rel="import" href="/lib/polymer/0.5.2/paper-button/paper-button.html"> + <link rel="import" href="/lib/polymer/0.5.2/core-resizable/core-resizable.html"> + <link rel="import" href="/room/ari/static/rdf-observe.html"> + <link rel="import" href="/room/ari/static/rdf-oneshot.html"> + <link rel="import" href="static/output-widgets.html"> + + <style> + body { + font-family: monospace; + } + </style> + </head> + <body layout vertical fullbleed> + <script> + window.NS = { + dev: 'http://projects.bigasterisk.com/device/', + room: 'http://projects.bigasterisk.com/room/', + rdfs: 'http://www.w3.org/2000/01/rdf-schema#', + }; + </script> + + <polymer-element name="linked-uri" noscript attributes="href"> + <template> + <a href="{{href}}">{{href}}</a> + </template> + </polymer-element> + + <polymer-element name="arduinonode-boards" noscript> + <template> + <style> + h1 { + margin: 0; + font-size: 130%; + } + ul { + padding-left: 5px; + } + .board, .device { + border: 1px solid gray; + border-radius: 10px; + margin: 13px; + padding: 7px; + box-shadow: 2px 5px 5px rgba(0, 0, 0, 0.14); + } + .board { + background: rgb(244, 244, 244); + } + .device { + background: #fff; + } + </style> + <core-ajax + url="boards" + auto="true" + handleAs="json" + response="{{ret}}"></core-ajax> + <template repeat="{{board in ret.boards}}"> + <div class="board"> + <h1>Board <linked-uri href="{{board.uri}}"></linked-uri></h1> + <h2>Devices</h2> + <ul> + <template repeat="{{dev in board.devices}}"> + <div class="device"> + <h1> + Device <linked-uri href="{{dev.uri}}"></linked-uri> + (class {{dev.className}}) + </h1> + <div>watching:</div> + <ul> + <template repeat="{{prefix in dev.watchPrefixes}}"> + <div>{{prefix[0]}}, {{prefix[1]}}, ?obj</div> + </template> + </ul> + + <div>send output:</div> + <ul> + <template repeat="{{out in dev.outputWidgets}}"> + <div> + <output-widget-any desc="{{out}}"></output-widget-any> + </div> + </template> + </ul> + </div> + </template> + </ul> + + </div> + </template> + </template> + + </polymer-element> + <arduinonode-boards></arduinonode-boards> + + + <polymer-element name="data-dump" noscript> + <template> + <rdf-observe + graph="http://bang:9059/graph" + subject="sensor:motion0" + predicate="room:sees" + out="{{out}}"> + </rdf-observe> + <div>sees: {{out['room:sees']}}</div> + </template> + </polymer-element> + <!-- <data-dump></data-dump> --> + </body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/service/arduinoNode/static/output-widgets.html Mon Apr 13 23:30:12 2015 -0700 @@ -0,0 +1,70 @@ +<link rel="import" href="/lib/polymer/0.5.2/core-ajax/core-ajax.html"> + +<polymer-element name="output-widget" attributes="subj pred"> + <template> + <core-ajax id="output" url="../output" method="POST"></core-ajax> + {{subj}} set {{pred}} to + </template> + <script> + function ntriple(s, p, o) { + // incomplete + o = o.replace('\n', '\\n'); + return '<'+s+'> <'+p+'> "'+o+'".'; + } + Polymer({ + valueChanged: function() { + this.$.output.body = ntriple(this.subj, this.pred, this.value); + this.$.output.go(); + } + }); + </script> +</polymer-element> + +<polymer-element name="output-slider" extends="output-widget" + attributes="min max step" + noscript> + <template> + <shadow></shadow> + <input type="range" + min="{{min}}" max="{{max}}" step="{{step}}" + value="{{value}}"> {{value}} + </template> +</polymer-element> + +<polymer-element name="output-fixed-text" extends="output-widget" + attributes="rows cols" + noscript> + <template> + <textarea rows="{{rows}}" cols="{{cols}}" value="{{value}}"></textarea> + </template> +</polymer-element> + +<polymer-element name="output-switch" extends="output-widget"> + <template> + <shadow></shadow> + <input type="checkbox" checked="{{check}}"> {{value}} + </template> + <script> + Polymer({ + check: false, + checkChanged: function() { + this.value = this.check ? "high" : "low"; + } + }); + </script> +</polymer-element> + +<polymer-element name="output-widget-any" attributes="desc"> + <template></template> + <script> + Polymer({ + domReady: function() { + var elem = document.createElement(this.desc.element); + this.shadowRoot.appendChild(elem); + for (var k of Object.keys(this.desc)) { + elem.setAttribute(k, this.desc[k]); + } + } + }); + </script> +</polymer-element>