Mercurial > code > home > repos > homeauto
changeset 182:9fff29ebca71
start pinode
Ignore-this: bfafd9994f7f9a61919d49274417ebbb
author | drewp@bigasterisk.com |
---|---|
date | Sun, 31 May 2015 00:56:55 -0700 |
parents | 71577a849628 |
children | 634d6e477953 |
files | service/piNode/config.n3 service/piNode/devices.py service/piNode/piNode.py |
diffstat | 3 files changed, 287 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/service/piNode/config.n3 Sun May 31 00:56:55 2015 -0700 @@ -0,0 +1,43 @@ +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix : <http://projects.bigasterisk.com/room/> . +@prefix ha: <http://bigasterisk.com/homeauto/> . +@prefix sensor: <http://bigasterisk.com/homeauto/sensor/> . +@prefix houseLoc: <http://bigasterisk.com/homeauto/houseLoc/> . + +@prefix board2pin: <http://bigasterisk.com/homeauto/board2/pin/> . +@prefix board2ow: <http://bigasterisk.com/homeauto/board2/oneWire/> . + +ha:node2 a :PiBoard; + :hostname "sticker"; + :hasPin + board2pin:GPIO2, + board2pin:GPIO3, + board2pin:GPIO4, + board2pin:GPIO17, + board2pin:GPIO27 + . + +board2pin:GPIO2 :gpioNumber 2 . +board2pin:GPIO3 :gpioNumber 3 . +board2pin:GPIO4 :gpioNumber 4 . +board2pin:GPIO17 :gpioNumber 17 . +board2pin:GPIO27 :gpioNumber 27 . + +board2pin:GPIO17 :connectedTo sensor:motion1 . +sensor:motion1 a :MotionSensor; + :sees houseLoc:bed . + +:bedLedStrip a :RgbStrip; + :redChannel board2pin:GPIO2; + :greenChannel board2pin:GPIO3; + :blueChannel board2pin:GPIO4 . + +board2pin:GPIO27 :connectedTo :headboardWhite . +:headboardWhite a :LedOutput . + +#board2pin:b29 :connectedTo board2ow: . +#board2ow: a :OneWire; +# :connectedTo board2ow:temperatureSensor . +#board2ow:temperatureSensor a :TemperatureSensor; +# :position :bed . +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/service/piNode/devices.py Sun May 31 00:56:55 2015 -0700 @@ -0,0 +1,122 @@ +from __future__ import division +import pigpio +import time +from rdflib import Namespace, RDF, URIRef, Literal + +ROOM = Namespace('http://projects.bigasterisk.com/room/') + +class DeviceType(object): + deviceType = None + @classmethod + def findInstances(cls, graph, board, pi): + """ + return any number of instances of this class for all the separately + controlled devices on the board. Two LEDS makes two instances, + but two sensors on the same onewire bus makes only one device + (which yields more statements). + """ + for row in graph.query("""SELECT ?dev ?pinNumber WHERE { + ?board :hasPin ?pin . + ?pin :pinNumber ?pinNumber; + :connectedTo ?dev . + ?dev a ?thisType . + } ORDER BY ?dev""", + initBindings=dict(board=board, + thisType=cls.deviceType), + initNs={'': ROOM}): + yield cls(graph, row.dev, pi, int(row.pinNumber)) + + def __init__(self, graph, uri, pi, pinNumber): + self.graph, self.uri, self.pi = graph, uri, pi + 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 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 poll(self): + return [] # statements + + def outputPatterns(self): + """ + Triple patterns, using None as a wildcard, that should be routed + to sendOutput + """ + 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): + """ + If we got statements that match this class's outputPatterns, this + will be called with the statements that matched. + + Todo: it would be fine to read back confirmations or + whatever. Just need a way to collect them into graph statements. + """ + raise NotImplementedError + +_knownTypes = set() +def register(deviceType): + _knownTypes.add(deviceType) + return deviceType + +@register +class MotionSensorInput(DeviceType): + deviceType = ROOM['MotionSensor'] + + def setup(self): + self.pi.set_mode(17, pigpio.INPUT) + self.pi.set_pull_up_down(17, pigpio.PUD_DOWN) + + def poll(self): + motion = self.pi.read(17) + + return [ + (self.uri, ROOM['sees'], + ROOM['motion'] if motion else ROOM['noMotion']), + self.recentMotionStatement(motion), + ] + + def recentMotionStatement(self, motion): + if not hasattr(self, 'lastMotionTime'): + self.lastMotionTime = 0 + now = time.time() + if motion: + self.lastMotionTime = now + recentMotion = now - self.lastMotionTime < 60 * 10 + return (self.uri, ROOM['seesRecently'], + ROOM['motion'] if recentMotion else ROOM['noMotion']) + + def watchPrefixes(self): + return [ + (self.uri, ROOM['sees']), + (self.uri, ROOM['seesRecently']), + ] + +def makeDevices(graph, board, pi): + out = [] + for dt in sorted(_knownTypes, key=lambda cls: cls.__name__): + out.extend(dt.findInstances(graph, board, pi)) + return out +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/service/piNode/piNode.py Sun May 31 00:56:55 2015 -0700 @@ -0,0 +1,122 @@ +from __future__ import division +import glob, sys, logging, subprocess, socket, os, hashlib, time, tempfile +import shutil, json, socket +import cyclone.web +from rdflib import Graph, Namespace, URIRef, Literal, RDF +from rdflib.parser import StringInputSource +from twisted.internet import reactor, task +from docopt import docopt +logging.basicConfig(level=logging.DEBUG) +sys.path.append("/my/site/magma") +from stategraph import StateGraph +sys.path.append('/home/pi/dim/PIGPIO') +import pigpio + +import devices + +log = logging.getLogger() +logging.getLogger('serial').setLevel(logging.WARN) +ROOM = Namespace('http://projects.bigasterisk.com/room/') +HOST = Namespace('http://bigasterisk.com/ruler/host/') + +hostname = socket.gethostname() + +class GraphPage(cyclone.web.RequestHandler): + def get(self): + g = StateGraph(ctx=ROOM['pi/%s' % hostname]) + + for stmt in self.settings.board.currentGraph(): + g.add(stmt) + + if self.get_argument('config', 'no') == 'yes': + for stmt in self.settings.config.graph: + g.add(stmt) + + self.set_header('Content-type', 'application/x-trig') + self.write(g.asTrig()) + +class Board(object): + """similar to arduinoNode.Board but without the communications stuff""" + def __init__(self, graph, uri, onChange): + self.graph, self.uri = graph, uri + self.pi = pigpio.pi() + self._devs = devices.makeDevices(graph, self.uri, self.pi) + self._statementsFromInputs = {} # input device uri: latest statements + + def startPolling(self): + task.LoopingCall(self._poll).start(.5) + + def _poll(self): + for i in self._devs: + self._statementsFromInputs[i.uri] = i.poll() + + def outputStatements(self, stmts): + unused = set(stmts) + for dev in self._devs: + stmtsForDev = [] + for pat in dev.outputPatterns(): + if [term is None for term in pat] != [False, False, True]: + raise NotImplementedError + for stmt in stmts: + if stmt[:2] == pat[:2]: + stmtsForDev.append(stmt) + unused.discard(stmt) + if stmtsForDev: + log.info("output goes to action handler for %s" % dev.uri) + dev.sendOutput(stmtsForDev) + log.info("success") + if unused: + log.warn("No devices cared about these statements:") + for s in unused: + log.warn(repr(s)) + +class OutputPage(cyclone.web.RequestHandler): + + def put(self): + subj = URIRef(self.get_argument('s')) + pred = URIRef(self.get_argument('p')) + + turtleLiteral = self.request.body + try: + obj = Literal(float(turtleLiteral)) + except TypeError: + obj = Literal(turtleLiteral) + + stmt = (subj, pred, obj) + self.settings.board.outputStatements([stmt]) + + +def main(): + arg = docopt(""" + Usage: piNode.py [options] + + -v Verbose + """) + log.setLevel(logging.WARN) + if arg['-v']: + from twisted.python import log as twlog + twlog.startLogging(sys.stdout) + + log.setLevel(logging.DEBUG) + + config = Config() + + def onChange(): + # notify reasoning + pass + + thisBoard = URIRef('http://bigasterisk.com/homeauto/node2') + + board = Board(config.graph, thisBoard, onChange) + + reactor.listenTCP(9059, cyclone.web.Application([ + (r"/()", cyclone.web.StaticFileHandler, { + "path": "static", "default_filename": "index.html"}), + (r'/static/(.*)', cyclone.web.StaticFileHandler, {"path": "static"}), + (r"/graph", GraphPage), + (r'/output', OutputPage), + (r'/dot', Dot), + ], config=config, board=board)) + reactor.run() + +main()