Mercurial > code > home > repos > homeauto
changeset 509:c9cadfcb4fdc
rm old (2010) code for talking to arduino/firmata with a web ui and some activitystreams stuff
Ignore-this: 3227955154a42eb5b67b49dd1f15890c
author | drewp@bigasterisk.com |
---|---|
date | Sun, 21 Apr 2019 23:26:45 -0700 |
parents | 43bb3e69821d |
children | b1337ad3ec2d |
files | service/theaterArduino/index.html service/theaterArduino/theaterArduino.py service/theaterArduino/watchpins.html service/theaterArduino/watchpins.py |
diffstat | 4 files changed, 0 insertions(+), 649 deletions(-) [+] |
line wrap: on
line diff
--- a/service/theaterArduino/index.html Sun Apr 21 03:30:59 2019 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,160 +0,0 @@ -<?xml version="1.0" encoding="iso-8859-1"?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" -"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml"> - <head> - <title></title> - </head> - <body> - - <h1>pyduino web interface</h1> - - <p>Use GET or PUT on the resources below. The value is a "0" or "1" - string. PUT "output" to /pin/d2/mode (etc) to make it writable.</p> - - <div> - pin/d2 : <input type="checkbox" name="d2" value="set" id="d2"/> - <span class="mode"> <input type="radio" name="d2-mode" value="input" id="d2-input"/> <label for="d2-input">input</label> - <input type="radio" name="d2-mode" value="output" id="d2-output"/> <label for="d2-output">output</label> </span> - </div> - - <div> - pin/d3 : <input type="checkbox" name="d3" value="set" id="d3"/> - <span class="mode"> <input type="radio" name="d3-mode" value="input" id="d3-input"/> <label for="d3-input">input</label> - <input type="radio" name="d3-mode" value="output" id="d3-output"/> <label for="d3-output">output</label> </span> - </div> - - <div> - pin/d4 : <input type="checkbox" name="d4" value="set" id="d4"/> - <span class="mode"> <input type="radio" name="d4-mode" value="input" id="d4-input"/> <label for="d4-input">input</label> - <input type="radio" name="d4-mode" value="output" id="d4-output"/> <label for="d4-output">output</label> </span> - </div> - - <div> - pin/d5 : <input type="checkbox" name="d5" value="set" id="d5"/> - <span class="mode"> <input type="radio" name="d5-mode" value="input" id="d5-input"/> <label for="d5-input">input</label> - <input type="radio" name="d5-mode" value="output" id="d5-output"/> <label for="d5-output">output</label> </span> - </div> - - <div> - pin/d6 : <input type="checkbox" name="d6" value="set" id="d6"/> - <span class="mode"> <input type="radio" name="d6-mode" value="input" id="d6-input"/> <label for="d6-input">input</label> - <input type="radio" name="d6-mode" value="output" id="d6-output"/> <label for="d6-output">output</label> </span> - </div> - - <div> - pin/d7 : <input type="checkbox" name="d7" value="set" id="d7"/> - <span class="mode"> <input type="radio" name="d7-mode" value="input" id="d7-input"/> <label for="d7-input">input</label> - <input type="radio" name="d7-mode" value="output" id="d7-output"/> <label for="d7-output">output</label> </span> - </div> - - <div> - pin/d8 : <input type="checkbox" name="d8" value="set" id="d8"/> - <span class="mode"> <input type="radio" name="d8-mode" value="input" id="d8-input"/> <label for="d8-input">input</label> - <input type="radio" name="d8-mode" value="output" id="d8-output"/> <label for="d8-output">output</label> </span> - </div> - - <div> - pin/d9 : <input type="checkbox" name="d9" value="set" id="d9"/> - <span class="mode"> <input type="radio" name="d9-mode" value="input" id="d9-input"/> <label for="d9-input">input</label> - <input type="radio" name="d9-mode" value="output" id="d9-output"/> <label for="d9-output">output</label> </span> - </div> - - <div> - pin/d10 : <input type="checkbox" name="d10" value="set" id="d10"/> - <span class="mode"> <input type="radio" name="d10-mode" value="input" id="d10-input"/> <label for="d10-input">input</label> - <input type="radio" name="d10-mode" value="output" id="d10-output"/> <label for="d10-output">output</label> </span> - </div> - - <div> - pin/d11 : <input type="checkbox" name="d11" value="set" id="d11"/> - <span class="mode"> <input type="radio" name="d11-mode" value="input" id="d11-input"/> <label for="d11-input">input</label> - <input type="radio" name="d11-mode" value="output" id="d11-output"/> <label for="d11-output">output</label> </span> - </div> - - <div> - pin/d12 : <input type="checkbox" name="d12" value="set" id="d12"/> - <span class="mode"> <input type="radio" name="d12-mode" value="input" id="d12-input"/> <label for="d12-input">input</label> - <input type="radio" name="d12-mode" value="output" id="d12-output"/> <label for="d12-output">output</label> </span> - </div> - - <div> - pin/d13 : <input type="checkbox" name="d13" value="set" id="d13"/> - <span class="mode"> <input type="radio" name="d13-mode" value="input" id="d13-input"/> <label for="d13-input">input</label> - <input type="radio" name="d13-mode" value="output" id="d13-output"/> <label for="d13-output">output</label> </span> - </div> - - <div><button id="refresh">Refresh</button> <input type="checkbox" name="autoRefresh" id="autoRefresh"/> <label for="autoRefresh">Auto refresh</label></div> - - - <div id="ajaxError"/> - - <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js"></script> - <script type="text/javascript"> - // <![CDATA[ - $(function() { - $("#ajaxError").ajaxError(function (ev, xhr) { - $(this).text("Error: " + xhr.responseText); - }); - - function refresh() { - $(".mode input").css('opacity', .2); - $("input[value=set]").css('opacity', .2) - .each(function (i, inp) { - var id = $(inp).attr('name'); - $.ajax({ - url: "pin/" + id, - type: "GET", - success: function (data, textStatus, xhr) { - $(inp).css('opacity', 1).val(data == "1" ? ["set"] : []); - }, - }); - $.ajax({ - url: "pin/" + id + "/mode", - type: "GET", - success: function (data, textStatus, xhr) { - var match = $("#" + id + "-" + data); - match.parent().find("input").css('opacity', 1); - match.click(); - } - }); - }); - } - - function refreshLoop() { - if ($("#autoRefresh").is(":checked")) { - refresh(); - setTimeout(function() { refreshLoop(); }, - 500); // refresh is async, so these could pile up - } - } - - $("#autoRefresh").click(refreshLoop); - $("#refresh").click(refresh); - - $(".mode input").removeAttr('disabled').change(function () { - var id = $(this).attr('id').replace(/-.*/, ""); - $.ajax({ - type: "PUT", - contentType: "text/plain", - url: "pin/" + id + "/mode", - data: $(this).val(), - }); - }); - - $("input[value=set]").change(function () { - var id = $(this).attr('id'); - $.ajax({ - type: "PUT", - contentType: "text/plain", - url: "pin/"+id, - data: $("#"+id+":checked").val() ? "1" : "0", - }); - }); - refresh(); - }); - // ]]> -</script> - - </body> -</html>
--- a/service/theaterArduino/theaterArduino.py Sun Apr 21 03:30:59 2019 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,145 +0,0 @@ -""" -arduino example sketches, 'StandardFirmata'. - -####easy_install http://github.com/lupeke/python-firmata/tarball/master - -Now using http://code.google.com/p/pyduino, modified to run at 57600 -baud like my arduino's code does. pyduino is better than the lupeke -one in that you can read your settings off the output pins - -Note that there are some startup delays and you may not hear about -input changes for a few seconds. -""" -from __future__ import division -import sys, cyclone.web, time, simplejson, os -from twisted.web.client import getPage -from twisted.internet import reactor, task - -sys.path.append("/my/proj/homeauto/lib") -from cycloneerr import PrettyErrorHandler -from logsetup import log - -sys.path.append("pyduino-read-only") -import pyduino - -def _num(name): - if name.startswith('d'): - return int(name[1:]) - raise ValueError(name) - -class pin(PrettyErrorHandler, cyclone.web.RequestHandler): - def get(self, name): - self.set_header("Content-Type", "text/plain") - arduino = self.settings.arduino - arduino.iterate() - self.write(str(int(arduino.digital[_num(name)].read()))) - - def put(self, name): - t1 = time.time() - self.settings.arduino.digital[_num(name)].write(int(self.request.body)) - log.debug("arduino write in %.1f ms" % (1000 * (time.time() - t1))) - - -class pinMode(PrettyErrorHandler, cyclone.web.RequestHandler): - def get(self, name): - self.set_header("Content-Type", "text/plain") - mode = self.settings.arduino.digital[_num(name)].get_mode() - self.write({pyduino.DIGITAL_INPUT : "input", - pyduino.DIGITAL_OUTPUT : "output"}[mode]) - - def put(self, name): - mode = { - "input" : pyduino.DIGITAL_INPUT, - "output" : pyduino.DIGITAL_OUTPUT}[self.request.body.strip()] - self.settings.arduino.digital[_num(name)].set_mode(mode) - -class Pid(PrettyErrorHandler, cyclone.web.RequestHandler): - def get(self): - self.set_header("Content-Type", "text/plain") - self.write(str(os.getpid())) - -class index(PrettyErrorHandler, cyclone.web.RequestHandler): - def get(self): - """ - this is a suitable status check; it does a round-trip to arduino - """ - # this would be a good ping() call for pyduino - self.settings.arduino.sp.write(chr(pyduino.REPORT_VERSION)) - self.settings.arduino.iterate() - - self.set_header("Content-Type", "application/xhtml+xml") - self.write(open('index.html').read()) - -class Application(cyclone.web.Application): - def __init__(self, arduino): - handlers = [ - (r"/", index), - (r'/pin/(.*)/mode', pinMode), - (r'/pin/(.*)', pin), - (r'/pid', Pid), - # web refresh could benefit a lot from a json resource that - # gives all the state - ] - settings = {"arduino" : arduino,} - cyclone.web.Application.__init__(self, handlers, **settings) - -class WatchPins(object): - def __init__(self, arduino, conf): - self.arduino, self.conf = arduino, conf - self.lastState = {} - self.pins = conf['watchPins'] - if self.pins == 'allInput': - self.watchAllInputs() - for pin in self.pins: - arduino.digital_ports[pin >> 3].set_active(1) - arduino.digital[pin].set_mode(pyduino.DIGITAL_INPUT) - - def watchAllInputs(self): - raise NotImplementedError("this needs to be updated whenever the modes change") - self.pins = [p for p in range(2, 13+1) if - self.arduino.digital[p].get_mode() == - pyduino.DIGITAL_INPUT] - - def reportPostError(self, fail, pin, value, url): - log.error("failed to send pin %s update (now %s) to %r: %r" % (pin, value, url, fail)) - - def poll(self): - try: - self._poll() - except Exception, e: - log.error("during poll:", exc_info=1) - - def _poll(self): - # this can IndexError for a port number being out of - # range. I'm not sure how- maybe error data coming in the - # port? - arduino.iterate() - for pin in self.pins: - current = arduino.digital[pin].read() - if current != self.lastState.get(pin, None): - d = getPage( - self.conf['post'], - method="POST", - postdata=simplejson.dumps(dict(board=self.conf['boardName'], pin=pin, level=int(current))), - headers={'Content-Type' : 'application/json'}) - d.addErrback(self.reportPostError, pin, current, self.conf['post']) - - self.lastState[pin] = current - -if __name__ == '__main__': - - config = { # to be read from a file - 'arduinoPort': '/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A900cepU-if00-port0', - 'servePort' : 9056, - 'pollFrequency' : 20, - 'post' : 'http://bang:9069/pinChange', - 'boardName' : 'theater', # gets sent with updates - 'watchPins' : [9, 10], # or 'allInput' (not yet working) - # todo: need options to preset inputs/outputs at startup - } - - arduino = pyduino.Arduino(config['arduinoPort']) - wp = WatchPins(arduino, config) - task.LoopingCall(wp.poll).start(1/config['pollFrequency']) - reactor.listenTCP(config['servePort'], Application(arduino)) - reactor.run()
--- a/service/theaterArduino/watchpins.html Sun Apr 21 03:30:59 2019 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ -<html> - <head> - </head> - <body> - theater motion sensor history: - <div id="chart_div" style="width: 900px; height: 250px;"></div> -max age: <input type="range" name="maxAge" min="10" max="300" value="180"> - <div id="status"></div> - <script type="text/javascript" src="https://www.google.com/jsapi"></script> - <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> - <script type="text/javascript"> - google.load("visualization", "1", {packages:["corechart"]}); - -var latestData = null; -var chart = null; - - function loop() { - getNewPoints(); - setTimeout(function () { webkitRequestAnimationFrame(loop); }, 1200); - } - google.setOnLoadCallback(function () { - chart = new google.visualization.LineChart(document.getElementById('chart_div')) - loop(); - }); - - function getValue(subj, pred, trig) { - var groups = trig.match('<'+subj+'> <'+pred+'> "(.*?)"'); - if (groups == null) { - return null; - } - return groups[1]; - } - - function getNewPoints() { - $("#status").text("get graph..."); - $.ajax({ - url: "graph", - success: function(data) { - $("#status").text(""); - var pointsJson = getValue( - "http://projects.bigasterisk.com/device/theaterDoorOutsideMotion", - "http://projects.bigasterisk.com/room/history", - data); - var realPoints = []; - if (pointsJson != null) { - realPoints = JSON.parse(pointsJson); - } - var steppedPoints = new google.visualization.DataTable(); - steppedPoints.addColumn('number', 't'); - steppedPoints.addColumn('number', 'value'); - - var prev = null; - realPoints.forEach(function(r) { - if (prev) { - steppedPoints.addRows([[r[0], prev[1]]]); - } - steppedPoints.addRows([[r[0], r[1]]]); - prev = r; - }); - latestData = steppedPoints; - redraw(); - } - }); - } - $("input[name=maxAge]").change(redraw); - - function redraw() { - // https://developers.google.com/chart/interactive/docs/gallery/linechart#Configuration_Options - var options = { - title: 'theaterDoorOutsideMotion', - legend: { - position: "none", - }, - lineWidth: .5, - hAxis: { - title: "seconds ago", - viewWindow: { - min: -$("input[name=maxAge]").val(), - max: 0 - }, - gridlines: { count: -1 } - }, - vAxis: { - baseline: -999, - title: "motion sensed", - viewWindow: { - min: -.5, - max: 1.5, - } - } - }; - - chart.draw(latestData, options); - } - - </script> - </body> -</html>
--- a/service/theaterArduino/watchpins.py Sun Apr 21 03:30:59 2019 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,246 +0,0 @@ -""" -listener to the POST messages sent by theaterArduino.py when a pin changes. -records interesting events to mongodb, sends further messages. - -Will also serve activity stream. -""" -import sys, os, datetime, cyclone.web, simplejson, time -from twisted.internet import reactor -from twisted.internet.error import ConnectError -from twisted.internet.defer import inlineCallbacks, returnValue -from twisted.web.client import getPage -from dateutil.tz import tzutc -from pymongo import Connection -from rdflib import Namespace, Literal, Graph -from rdflib.parser import StringInputSource -sys.path.append("/my/site/magma") -from activitystream import ActivityStream -from stategraph import StateGraph - -sys.path.append("/my/proj/homeauto/lib") -from cycloneerr import PrettyErrorHandler -from logsetup import log - -DEV = Namespace("http://projects.bigasterisk.com/device/") -ROOM = Namespace("http://projects.bigasterisk.com/room/") -zeroTime = datetime.datetime.fromtimestamp(0, tzutc()) - -class PinChange(PrettyErrorHandler, cyclone.web.RequestHandler): - def post(self): - # there should be per-pin debounce settings so we don't log - # all the noise of a transition change - - msg = simplejson.loads(self.request.body) - msg['t'] = datetime.datetime.now(tzutc()) - msg['name'] = {9: 'downstairsDoorOpen', - 10: 'downstairsDoorMotion', - }[msg['pin']] - log.info("pinchange post %r", msg) - self.settings.mongo.insert(msg) - - history = self.settings.history - if msg['pin'] == 10: - history['motionHistory'] = (history.get('motionHistory', []) + [(msg['t'], msg['level'])])[-50:] - if msg['level'] == 1: - if history.get('prevMotion', 0) == 0: - history['motionStart'] = msg['t'] - - history['prevMotion'] = msg['level'] - - -class InputChange(PrettyErrorHandler, cyclone.web.RequestHandler): - """ - several other inputs post to here to get their events recorded, - too. This file shouldn't be in theaterArduino. See bedroomArduino, - frontDoorArduino, garageArduino. - """ - def post(self): - msg = simplejson.loads(self.request.body) - msg['t'] = datetime.datetime.now(tzutc()) - log.info(msg) - self.settings.mongo.insert(msg) - - # trigger to entrancemusic? rdf graph change PSHB? - -class GraphHandler(PrettyErrorHandler, cyclone.web.RequestHandler): - """ - fetch the pins from drv right now (so we don't have stale data), - and return an rdf graph describing what we know about the world - """ - @inlineCallbacks - def get(self): - g = StateGraph(ctx=DEV['houseSensors']) - - frontDoorDefer = getPage("http://slash:9080/door", timeout=2) # head start? - - doorOpen = int((yield getPage("http://bang:9056/pin/d9", timeout=1))) - g.add((DEV['theaterDoorOpen'], ROOM['state'], - ROOM['open'] if doorOpen else ROOM['closed'])) - - for s in self.motionStatements( - currentMotion=int((yield getPage("http://bang:9056/pin/d10", - timeout=1)))): - g.add(s) - - try: - for s in (yield self.getBedroomStatements()): - g.add(s) - except ConnectError, e: - g.add((ROOM['bedroomStatementFetch'], ROOM['error'], - Literal("getBedroomStatements: %s" % e))) - - try: - frontDoor = yield frontDoorDefer - g.add((DEV['frontDoorOpen'], ROOM['state'], - ROOM[frontDoor] if frontDoor in ['open', 'closed'] else - ROOM['error'])) - except Exception, e: - g.add((DEV['frontDoorOpen'], ROOM['error'], Literal(str(e)))) - - self.set_header('Content-type', 'application/x-trig') - self.write(g.asTrig()) - - def motionStatements(self, currentMotion): - uri = DEV['theaterDoorOutsideMotion'] - - yield (uri, ROOM['state'], ROOM['motion'] if currentMotion else ROOM['noMotion']) - - now = datetime.datetime.now(tzutc()) - if currentMotion: - try: - dt = now - self.settings.history['motionStart'] - yield (uri, ROOM['motionDurationSec'], Literal(dt.total_seconds())) - if dt > datetime.timedelta(seconds=4): - yield (uri, ROOM['state'], ROOM['sustainedMotion']) - except KeyError: - pass - - # this is history without the db, which means the window is - # limited and it could reset any time - if 'motionHistory' in self.settings.history: - yield ((uri, ROOM['history'], - Literal(simplejson.dumps( - [(round((t - now).total_seconds(), ndigits=2), v) - for t,v in self.settings.history['motionHistory']])))) - - @inlineCallbacks - def getBedroomStatements(self): - trig = yield getPage("http://bang:9088/graph", timeout=1) - stmts = set() - for line in trig.splitlines(): - if "http://projects.bigasterisk.com/device/bedroomMotion" in line: - g = Graph() - g.parse(StringInputSource(line+"\n"), format="nt") - for s in g: - stmts.add(s) - returnValue(stmts) - -class Activity(PrettyErrorHandler, cyclone.web.RequestHandler): - def get(self): - a = ActivityStream() - self.settings.mongo.ensure_index('t') - remaining = {'downstairsDoorMotion':10, 'downstairsDoorOpen':10, - 'frontDoorMotion':30, 'frontDoor':50, 'bedroomMotion': 10} - recent = {} - toAdd = [] - for row in list(self.settings.mongo.find(sort=[('t', -1)], - limit=10000)): - try: - r = remaining[row['name']] - if r < 1: - continue - remaining[row['name']] = r - 1 - except KeyError: - pass - - # lots todo - if row['name'] == 'downstairsDoorMotion': - if row['level'] == 0: - continue - kw = dict( - actorUri="http://...", - actorName="downstairs door", - verbUri="...", - verbEnglish="sees", - objectUri="...", - objectName="backyard motion", - objectIcon="/magma/static/backyardMotion.png") - elif row['name'] == 'downstairsDoorOpen': - kw = dict(actorUri="http://bigasterisk.com/foaf/someone", - actorName="someone", - verbUri="op", - verbEnglish="opens" if row['level'] else "closes", - objectUri="...", - objectName="downstairs door", - objectIcon="/magma/static/downstairsDoor.png") - elif row['name'] == 'frontDoor': - kw = dict(actorUri="http://bigasterisk.com/foaf/someone", - actorName="someone", - verbUri="op", - verbEnglish="opens" if row['state']=='open' else "closes", - objectUri="...", - objectName="front door", - objectIcon="/magma/static/frontDoor.png") - elif row['name'] == 'frontDoorMotion': - if row['state'] == False: - continue - if 'frontDoorMotion' in recent: - pass#if row['t' - kw = dict( - actorUri="http://...", - actorName="front door", - verbUri="...", - verbEnglish="sees", - objectUri="...", - objectName="front yard motion", - objectIcon="/magma/static/frontYardMotion.png") - recent['frontDoorMotion'] = kw - elif row['name'] == 'bedroomMotion': - if not row['state']: - continue - kw = dict( - actorUri="http://...", - actorName="bedroom", - verbUri="...", - verbEnglish="sees", - objectUri="...", - objectName="bedroom motion", - objectIcon="/magma/static/bedroomMotion.png") - recent['bedroomMotion'] = kw - else: - raise NotImplementedError(row) - - kw.update({'published' : row['t'], - 'entryUriComponents' : ('sensor', row['board'])}) - toAdd.append(kw) - - toAdd.reverse() - for kw in toAdd: - a.addEntry(**kw) - - self.set_header("Content-type", "application/atom+xml") - self.write(a.makeAtom()) - -class Application(cyclone.web.Application): - def __init__(self): - handlers = [ - (r'/()', cyclone.web.StaticFileHandler, - {"path" : ".", "default_filename" : "watchpins.html"}), - (r'/pinChange', PinChange), - (r'/inputChange', InputChange), - (r'/activity', Activity), - (r'/graph', GraphHandler), - ] - settings = { - 'mongo' : Connection('bang', 27017, - tz_aware=True)['house']['sensor'], - 'history' : { - }, - } - cyclone.web.Application.__init__(self, handlers, **settings) - -if __name__ == '__main__': - #from twisted.python import log as twlog - #twlog.startLogging(sys.stdout) - reactor.listenTCP(9069, Application()) - reactor.run()