Mercurial > code > home > repos > homeauto
changeset 1026:8e075449ba0a
piNode support for temp sensors. proper hostname lookup
Ignore-this: a68c3319bffb16c55cbb5f329118f0a4
darcs-hash:3b4c8482b870731064ae5685d32a4206b7a2d7d6
author | drewp <drewp@bigasterisk.com> |
---|---|
date | Mon, 18 Jan 2016 22:43:22 -0800 |
parents | f58b5536f683 |
children | 208b960fde31 |
files | service/piNode/config.n3 service/piNode/devices.py service/piNode/piNode.py |
diffstat | 3 files changed, 155 insertions(+), 12 deletions(-) [+] |
line wrap: on
line diff
--- a/service/piNode/config.n3 Sun Jan 03 02:32:46 2016 -0800 +++ b/service/piNode/config.n3 Mon Jan 18 22:43:22 2016 -0800 @@ -43,4 +43,31 @@ # :connectedTo board2ow:temperatureSensor . #board2ow:temperatureSensor a :TemperatureSensor; # :position :bed . - + +@prefix board3pin: <http://bigasterisk.com/homeauto/board3/pin/> . +@prefix board3ow: <http://bigasterisk.com/homeauto/board3/oneWire/> . + +ha:node3 a :PiBoard; + :hostname "kitchen"; + :onboardDevice ha:kitchenPiTemp; + :hasPin + board3pin:GPIO4, + board3pin:GPIO17 + . + +ha:kitchenPiTemp a :OnboardTemperature . + +board3pin:GPIO4 :gpioNumber 4; :connectedTo sensor:tempHumidKitchen . +sensor:tempHumidKitchen a :TempHumidSensor; + :sees houseLoc:kitchenCounter; + :graphiteName "system.house.temp.kitchenCounter" . + +board3pin:GPIO17 :gpioNumber 17; :connectedTo board3ow: . +board3ow: a :OneWire; :connectedTo board3ow:dev-000003a5a94c . +board3ow:dev-000003a5a94c a :TemperatureSensor; + :position houseLoc:kitchenCounter; + :graphiteName "system.house.temp.kitchenCounter_ds_test" . + +ha:node4 a :PiBoard; + :hostname "living"; + :onboardDevice ha:livingPiTemp . \ No newline at end of file
--- a/service/piNode/devices.py Sun Jan 03 02:32:46 2016 -0800 +++ b/service/piNode/devices.py Mon Jan 18 22:43:22 2016 -0800 @@ -1,20 +1,23 @@ from __future__ import division -import time, logging +import time, logging, os from rdflib import Namespace, RDF, URIRef, Literal try: import pigpio except ImportError: pigpio = None +import w1thermsensor +import sys log = logging.getLogger() ROOM = Namespace('http://projects.bigasterisk.com/room/') XSD = Namespace('http://www.w3.org/2001/XMLSchema#') +RDFS = Namespace('http://www.w3.org/2000/01/rdf-schema#') class DeviceType(object): - deviceType = None + deviceType = NotImplementedError @classmethod def findInstances(cls, graph, board, pi): """ @@ -23,6 +26,7 @@ but two sensors on the same onewire bus makes only one device (which yields more statements). """ + log.debug("graph has any connected devices of type %s?", cls.deviceType) for row in graph.query("""SELECT ?dev ?gpioNumber WHERE { ?board :hasPin ?pin . ?pin :gpioNumber ?gpioNumber; @@ -30,8 +34,7 @@ ?dev a ?thisType . } ORDER BY ?dev""", initBindings=dict(board=board, - thisType=cls.deviceType), - initNs={'': ROOM}): + thisType=cls.deviceType)): yield cls(graph, row.dev, pi, int(row.gpioNumber)) def __init__(self, graph, uri, pi, pinNumber): @@ -91,6 +94,8 @@ @register class MotionSensorInput(DeviceType): + # compare motion sensor lib at http://pythonhosted.org/gpiozero/inputs/ + # which is a bit fancier deviceType = ROOM['MotionSensor'] def setup(self): @@ -126,6 +131,9 @@ @register class RgbStrip(DeviceType): """3 PWMs for r/g/b on a strip""" + # pigpio daemon is working fine, but + # https://github.com/RPi-Distro/python-gpiozero/blob/59ba7154c5918745ac894ea03503667d6473c760/gpiozero/output_devices.py#L213 + # can also apparently do PWM deviceType = ROOM['RgbStrip'] @classmethod @@ -183,6 +191,82 @@ @register +class TempHumidSensor(DeviceType): + deviceType = ROOM['TempHumidSensor'] + + def __init__(self, *a, **kw): + DeviceType.__init__(self, *a, **kw) + sys.path.append('/opt/pigpio/EXAMPLES/Python/DHT22_AM2302_SENSOR') + import DHT22 + self.sensor = DHT22.sensor(self.pi, self.pinNumber) + + def poll(self): + self.sensor.trigger() + humid, tempC = self.sensor.humidity(), self.sensor.temperature() + + stmts = set() + if humid is not None: + stmts.add((self.uri, ROOM['humidity'], Literal(humid))) + else: + stmts.add((self.uri, RDFS['comment'], + Literal('DHT read returned None'))) + if tempC is not None: + stmts.add((self.uri, ROOM['temperatureF'], + Literal(tempC * 9 / 5 + 32))) + else: + stmts.add((self.uri, RDFS['comment'], + Literal('DHT read returned None'))) + return stmts + + def watchPrefixes(self): + return [ + (self.uri, ROOM['temperatureF']), + (self.uri, ROOM['humidity']), + ] + +@register +class OneWire(DeviceType): + """ + Also see /my/proj/ansible/roles/raspi_io_node/tasks/main.yml for + some system config that contains the pin number that you want to + use for onewire. The pin number in this config is currently ignored. + """ + deviceType = ROOM['OneWire'] + # deliberately written like arduinoNode's one for an easier merge. + def __init__(self, *a, **kw): + DeviceType.__init__(self, *a, **kw) + log.info("scan for w1 devices") + self._sensors = w1thermsensor.W1ThermSensor.get_available_sensors() + for s in self._sensors: + # Something looks different about these ids + # ('000003a5a94c') vs the ones I get from arduino + # ('2813bea50300003d'). Not sure if I'm parsing them + # differently or what. + s.uri = URIRef(os.path.join(self.uri, 'dev-%s' % s.id)) + log.info(' found temperature sensor %s' % s.uri) + + def poll(self): + try: + stmts = [] + for sensor in self._sensors: + stmts.append((self.uri, ROOM['connectedTo'], sensor.uri)) + try: + tempF = sensor.get_temperature(sensor.DEGREES_F) + stmts.append((sensor.uri, ROOM['temperatureF'], + Literal(tempF))) + except w1thermsensor.core.SensorNotReadyError as e: + log.warning(e) + + return stmts + except Exception as e: + log.error(e) + os.abort() + + def watchPrefixes(self): + return [(s.uri, ROOM['temperatureF']) for s in self._sensors] + + +@register class LedOutput(DeviceType): deviceType = ROOM['LedOutput'] @@ -219,7 +303,7 @@ for row in graph.query('''SELECT DISTINCT ?uri WHERE { ?board :onboardDevice ?uri . ?uri a :OnboardTemperature . - }'''): + }''', initBindings=dict(board=board)): yield cls(graph, row.uri, pi, pinNumber=None) def poll(self):
--- a/service/piNode/piNode.py Sun Jan 03 02:32:46 2016 -0800 +++ b/service/piNode/piNode.py Mon Jan 18 22:43:22 2016 -0800 @@ -1,5 +1,5 @@ from __future__ import division -import sys, logging, socket, json +import sys, logging, socket, json, time import cyclone.web from rdflib import Namespace, URIRef, Literal, Graph, RDF from rdflib.parser import StringInputSource @@ -10,7 +10,7 @@ sys.path.append("../../../../site/magma") from stategraph import StateGraph -sys.path.append('/home/pi/dim/PIGPIO') +sys.path.append('/opt/pigpio') try: import pigpio except ImportError: @@ -21,6 +21,9 @@ import devices +# from /my/proj/room +from carbondata import CarbonClient + log = logging.getLogger() logging.getLogger('serial').setLevel(logging.WARN) ROOM = Namespace('http://projects.bigasterisk.com/room/') @@ -33,7 +36,7 @@ self.graph = Graph() log.info('read config') self.graph.parse('config.n3', format='n3') - self.graph.bind('', ROOM) # not working + self.graph.bind('', ROOM) # maybe working self.graph.bind('rdf', RDF) class GraphPage(cyclone.web.RequestHandler): @@ -58,6 +61,7 @@ self._devs = devices.makeDevices(graph, self.uri, self.pi) log.debug('found %s devices', len(self._devs)) self._statementsFromInputs = {} # input device uri: latest statements + self._carbon = CarbonClient(serverHost='bang') def startPolling(self): task.LoopingCall(self._poll).start(.5) @@ -65,7 +69,8 @@ def _poll(self): for i in self._devs: self._statementsFromInputs[i.uri] = i.poll() - + self._exportToGraphite() + def outputStatements(self, stmts): unused = set(stmts) for dev in self._devs: @@ -85,6 +90,25 @@ log.warn("No devices cared about these statements:") for s in unused: log.warn(repr(s)) + + # needs merge with arduinoNode.py + def _exportToGraphite(self): + # note this is writing way too often- graphite is storing at a lower res + now = time.time() + # 20 sec is not precise; just trying to reduce wifi traffic + if getattr(self, 'lastGraphiteExport', 0) + 20 > now: + return + self.lastGraphiteExport = now + log.debug('graphite export:') + # objects of these statements are suitable as graphite values. + graphitePredicates = {ROOM['temperatureF']} + # bug: one sensor can have temp and humid- this will be ambiguous + for s, graphiteName in self.graph.subject_objects(ROOM['graphiteName']): + for group in self._statementsFromInputs.values(): + for stmt in group: + if stmt[0] == s and stmt[1] in graphitePredicates: + log.debug(' sending %s -> %s', stmt[0], graphiteName) + self._carbon.send(graphiteName, stmt[2].toPython(), now) def currentGraph(self): g = Graph() @@ -154,8 +178,16 @@ # notify reasoning pass - thisBoard = URIRef('http://bigasterisk.com/homeauto/node2') - + thisHost = Literal(socket.gethostname()) + for row in config.graph.query( + 'SELECT ?board WHERE { ?board a :PiBoard; :hostname ?h }', + initBindings=dict(h=thisHost)): + thisBoard = row.board + break + else: + raise ValueError("config had no board for :hostname %r" % thisHost) + + log.info("found config for board %r" % thisBoard) board = Board(config.graph, thisBoard, onChange) board.startPolling()