# HG changeset patch # User drewp # Date 1555914405 25200 # Node ID 3c732f3f9f657d2d3abf903d21644b07974fc453 # Parent 93ce29b117798e7dba1d43c603674d7013061358 rm old (2010) code for talking to arduino/firmata with a web ui and some activitystreams stuff Ignore-this: 3227955154a42eb5b67b49dd1f15890c darcs-hash:1e9f0060b712752c69ecd9cde1990438162f06f8 diff -r 93ce29b11779 -r 3c732f3f9f65 service/theaterArduino/index.html --- 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 @@ - - - - - - - - -

pyduino web interface

- -

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.

- -
- pin/d2 : - - -
- -
- pin/d3 : - - -
- -
- pin/d4 : - - -
- -
- pin/d5 : - - -
- -
- pin/d6 : - - -
- -
- pin/d7 : - - -
- -
- pin/d8 : - - -
- -
- pin/d9 : - - -
- -
- pin/d10 : - - -
- -
- pin/d11 : - - -
- -
- pin/d12 : - - -
- -
- pin/d13 : - - -
- -
- - -
- - - - - - diff -r 93ce29b11779 -r 3c732f3f9f65 service/theaterArduino/theaterArduino.py --- 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() diff -r 93ce29b11779 -r 3c732f3f9f65 service/theaterArduino/watchpins.html --- 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 @@ - - - - - theater motion sensor history: -
-max age: -
- - - - - diff -r 93ce29b11779 -r 3c732f3f9f65 service/theaterArduino/watchpins.py --- 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()