# HG changeset patch
# User drewp@bigasterisk.com
# Date 1453185802 28800
# Node ID 666e0e756ce629fb8e78ddc82d435f9f6d659268
# Parent fb23df31b6421fb12debc1e1ad821f8b21ccfac1
piNode support for temp sensors. proper hostname lookup
Ignore-this: a68c3319bffb16c55cbb5f329118f0a4
diff -r fb23df31b642 -r 666e0e756ce6 service/piNode/config.n3
--- 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: .
+@prefix board3ow: .
+
+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
diff -r fb23df31b642 -r 666e0e756ce6 service/piNode/devices.py
--- 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):
diff -r fb23df31b642 -r 666e0e756ce6 service/piNode/piNode.py
--- 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()