changeset 1532:7cc7700302c2

more service renaming; start a lot more serv.n3 job files Ignore-this: 635aaefc7bd2fa5558eefb8b3fc9ec75 darcs-hash:2c8b587cbefa4db427f9a82676abdb47e651187e
author drewp <drewp@bigasterisk.com>
date Thu, 06 Feb 2020 16:36:35 -0800
parents a3dc6a31590f
children a598d2141587
files service/arduinoNode/serv.n3 service/audioInputLevels/serv.n3 service/collector/serv.n3 service/dhcpleases/serv.n3 service/dpms/serv.n3 service/environment/serv.n3 service/frontDoorLock/serv.n3 service/irRemote/serv.n3 service/mqtt_graph_bridge/Dockerfile service/mqtt_graph_bridge/config.n3 service/mqtt_graph_bridge/h801/config_counter.yaml service/mqtt_graph_bridge/h801/config_skylight.yaml service/mqtt_graph_bridge/h801/makefile service/mqtt_graph_bridge/index.html service/mqtt_graph_bridge/mqtt_graph_bridge.py service/mqtt_graph_bridge/rdf_over_http.py service/mqtt_graph_bridge/readme service/mqtt_graph_bridge/requirements.txt service/mqtt_graph_bridge/tasks.py service/mqtt_to_rdf/Dockerfile service/mqtt_to_rdf/config_bed_bar.n3 service/mqtt_to_rdf/config_cardreader.n3 service/mqtt_to_rdf/config_nightlight_ari.n3 service/mqtt_to_rdf/index.html service/mqtt_to_rdf/rdf_from_mqtt.py service/mqtt_to_rdf/requirements.txt service/mqtt_to_rdf/serv.n3 service/mqtt_to_rdf/tasks.py service/piNode/serv.n3 service/powerEagle/serv.n3 service/rdf_from_mqtt/Dockerfile service/rdf_from_mqtt/config_bed_bar.n3 service/rdf_from_mqtt/config_cardreader.n3 service/rdf_from_mqtt/config_nightlight_ari.n3 service/rdf_from_mqtt/index.html service/rdf_from_mqtt/rdf_from_mqtt.py service/rdf_from_mqtt/requirements.txt service/rdf_from_mqtt/serv.n3 service/rdf_from_mqtt/tasks.py service/rdf_to_mqtt/Dockerfile service/rdf_to_mqtt/config.n3 service/rdf_to_mqtt/h801/config_counter.yaml service/rdf_to_mqtt/h801/config_skylight.yaml service/rdf_to_mqtt/h801/makefile service/rdf_to_mqtt/index.html service/rdf_to_mqtt/mqtt_graph_bridge.py service/rdf_to_mqtt/rdf_over_http.py service/rdf_to_mqtt/readme service/rdf_to_mqtt/requirements.txt service/rdf_to_mqtt/serv.n3 service/rdf_to_mqtt/tasks.py service/reasoning/serv.n3 service/store/serv.n3 service/wifi/serv.n3 service/xidle/serv.n3
diffstat 55 files changed, 1021 insertions(+), 831 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/arduinoNode/serv.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,4 @@
+@prefix : <http://bigasterisk.com/ns/serv#> .
+@prefix auth: <http://bigasterisk.com/ns/serv/auth#> .
+@prefix serv: <http://bigasterisk.com/services/> .
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/audioInputLevels/serv.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,4 @@
+@prefix : <http://bigasterisk.com/ns/serv#> .
+@prefix auth: <http://bigasterisk.com/ns/serv/auth#> .
+@prefix serv: <http://bigasterisk.com/services/> .
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/collector/serv.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,4 @@
+@prefix : <http://bigasterisk.com/ns/serv#> .
+@prefix auth: <http://bigasterisk.com/ns/serv/auth#> .
+@prefix serv: <http://bigasterisk.com/services/> .
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/dhcpleases/serv.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,4 @@
+@prefix : <http://bigasterisk.com/ns/serv#> .
+@prefix auth: <http://bigasterisk.com/ns/serv/auth#> .
+@prefix serv: <http://bigasterisk.com/services/> .
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/dpms/serv.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,22 @@
+@prefix : <http://bigasterisk.com/ns/serv#> .
+@prefix auth: <http://bigasterisk.com/ns/serv/auth#> .
+@prefix serv: <http://bigasterisk.com/services/> .
+
+serv:dpms a :Service;
+      :path "/dpms/";
+      :openid auth:admin;
+      :serverHost "bang";
+      :internalPort 9095;
+      :prodDockerFlags (
+      "-p" "9095:9095"
+      "--net=host"
+      );
+      :localDockerFlags (
+        "-v" "`pwd`:/opt"
+      );
+      :localRunCmdline (
+        "python3" "dpms.py" "-v"
+      );
+      :dockerFile "Dockerfile"
+.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/environment/serv.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,4 @@
+@prefix : <http://bigasterisk.com/ns/serv#> .
+@prefix auth: <http://bigasterisk.com/ns/serv/auth#> .
+@prefix serv: <http://bigasterisk.com/services/> .
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/frontDoorLock/serv.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,4 @@
+@prefix : <http://bigasterisk.com/ns/serv#> .
+@prefix auth: <http://bigasterisk.com/ns/serv/auth#> .
+@prefix serv: <http://bigasterisk.com/services/> .
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/irRemote/serv.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,4 @@
+@prefix : <http://bigasterisk.com/ns/serv#> .
+@prefix auth: <http://bigasterisk.com/ns/serv/auth#> .
+@prefix serv: <http://bigasterisk.com/services/> .
+
--- a/service/mqtt_graph_bridge/Dockerfile	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-FROM bang6:5000/base_x86
-
-WORKDIR /opt
-
-COPY requirements.txt ./
-RUN pip3 install --index-url https://projects.bigasterisk.com/ --extra-index-url https://pypi.org/simple -r requirements.txt
-RUN pip3 install -U 'https://github.com/drewp/cyclone/archive/python3.zip?v3'
-
-COPY *.py *.html *.css *.js ./
-
-EXPOSE 10011:10011
-
-CMD [ "python3", "./mqtt_graph_bridge.py" ]
--- a/service/mqtt_graph_bridge/config.n3	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-@prefix : <http://projects.bigasterisk.com/room/> .
-@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
-@prefix fr: <http://bigasterisk.com/foaf/> .
-
-:kitchenSkylight a :Device;
-  :mqttTopicHead ("h801_skylight" "light");
-  :convertedAttr [
-    :deviceAttr :white;
-    :mqttTopicTail ("kit_w1" "command");
-    :valueConversion :to8Bit;
-    :message '{"state":"ON","brightness":%value%}'
-  ],
-  :convertedAttr [
-    :deviceAttr :color;
-    :mqttTopicTail ("kit_r" "command");
-    :valueConversion :extractRed8Bit;
-    :message '{"state":"ON","brightness":%value%}'
-   ],
-  :convertedAttr [
-    :deviceAttr :color;
-    :mqttTopicTail ("kit_g" "command");
-    :valueConversion :extractGreen8Bit;
-    :message '{"state":"ON","brightness":%value%}'
-   ],
-  :convertedAttr [
-    :deviceAttr :color;
-    :mqttTopicTail ("kit_r" "command");
-    :valueConversion :extractBlue8Bit;
-    :message '{"state":"ON","brightness":%value%}'
-   ],
-
-:nightlightAriTemperature a :ExportedMeasurement;
-  :mqttTopicHead ("nightlight_ari" "sensor" "temperature" "state");
-
-  :missingAfterSec 150;
-  :ignoreInputValueBelow -999;
-
-  :valueProcess [
-    :conversion :celsiusToFarenheit; #and round(x,2)
-    ];
-
-  :graphStatements [
-    :outputPredicate :temperatureF;  
-    :statementLifetime :untilReplaced;
-
-    # is this just another valueProcess?
-    :outputRecentPredicate :recentLowTemperatureF; :recentPeriodSec 30;
-  ],
-  
-  :influxMeasurement [
-    :measurement "temperatureF";
-    :predicate :temperatureF;
-    :tag [:key "host"; :value "nightlight_ari"],
-         [:key "location"; :value "ariRoom"]] .
--- a/service/mqtt_graph_bridge/h801/config_counter.yaml	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-substitutions:
-  pwm_freq: 310 Hz
-
-#310 low buzz, nicer than 520
-#520 low pitch buzz, ok
-#600 sound is present but might be ok
-#800 bad sound
-#1500 sound is bad but we had it like that
-#3000 very bad snd
-#6000 bad snd and flicker
-#9000 flicker
-  
-esphomeyaml:
-  name: h801_counter
-  platform: ESP8266
-  board: esp8285
-  build_path: build
-
-wifi:
-  ssid: !secret wifi_ssid
-  password: !secret wifi_password
-  manual_ip:
-    static_ip: 10.2.0.59
-    gateway: 10.2.0.1
-    subnet: 255.255.255.0
-
-mqtt:
-  broker: '10.2.0.1'
-  port: 1883
-
-logger:
-  baud_rate: 115200
-
-ota:
-
-output:
-  - id: pwm_b
-    platform: esp8266_pwm
-    pin: 12
-    frequency: ${pwm_freq}
-  - id: pwm_g
-    platform: esp8266_pwm
-    pin: 13
-    frequency: ${pwm_freq}
-  - id: pwm_r
-    platform: esp8266_pwm
-    pin: 15
-    frequency: ${pwm_freq}
-  - id: pwm_w1
-    platform: esp8266_pwm
-    pin: 14
-    frequency: ${pwm_freq}
-  - id: pwm_w2
-    platform: esp8266_pwm
-    pin: 4
-    frequency: ${pwm_freq}
-    
-light:
-  - platform: monochromatic
-    name: "Kit_r"
-    output: pwm_r
-  - platform: monochromatic
-    name: "Kit_g"
-    output: pwm_g
-  - platform: monochromatic
-    name: "Kit_b"
-    output: pwm_b
-  - platform: monochromatic
-    name: "Kit_w1"
-    output: pwm_w1
-  - platform: monochromatic
-    name: "Kit_w2"
-    output: pwm_w2
--- a/service/mqtt_graph_bridge/h801/config_skylight.yaml	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-substitutions:
-  pwm_freq: 310 Hz
-  
-esphomeyaml:
-  name: h801_skylight
-  platform: ESP8266
-  board: esp8285
-  build_path: build
-
-wifi:
-  ssid: !secret wifi_ssid
-  password: !secret wifi_password
-  manual_ip:
-    static_ip: 10.2.0.67
-    gateway: 10.2.0.1
-    subnet: 255.255.255.0
-
-mqtt:
-  broker: '10.2.0.1'
-  port: 1883
-
-logger:
-  baud_rate: 115200
-
-ota:
-
-output:
-  - id: pwm_b
-    platform: esp8266_pwm
-    pin: 12
-    frequency: ${pwm_freq}
-  - id: pwm_r
-    platform: esp8266_pwm
-    pin: 13
-    frequency: ${pwm_freq}
-  - id: pwm_g
-    platform: esp8266_pwm
-    pin: 15
-    frequency: ${pwm_freq}
-  - id: pwm_w1
-    platform: esp8266_pwm
-    pin: 14
-    frequency: ${pwm_freq}
-  - id: pwm_w2
-    platform: esp8266_pwm
-    pin: 4
-    frequency: ${pwm_freq}
-    
-light:
-  - platform: monochromatic
-    name: "Kit_r"
-    output: pwm_r
-  - platform: monochromatic
-    name: "Kit_g"
-    output: pwm_g
-  - platform: monochromatic
-    name: "Kit_b"
-    output: pwm_b
-  - platform: monochromatic
-    name: "Kit_w1"
-    output: pwm_w1
-  - platform: monochromatic
-    name: "Kit_w2"
-    output: pwm_w2
--- a/service/mqtt_graph_bridge/h801/makefile	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-update:
-	/my/dl/dl/esphomeyaml/bin/esphomeyaml config_skylight.yaml run --no-logs
--- a/service/mqtt_graph_bridge/index.html	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <title>mqtt_graph_bridge</title>
-    <meta charset="utf-8">
-    <script src="/lib/polymer/1.0.9/webcomponentsjs/webcomponents.min.js"></script>
-    <script src="/lib/require/require-2.3.3.js"></script>
-    <script src="/rdf/common_paths_and_ns.js"></script>
-
-    <link rel="stylesheet" href="/rdf/browse/style.css">
-
-  </head>
-  <body class="rdfBrowsePage">
-    mqtt_graph_bridge
-
-
-    <p>Send demo statements to bridge:</p>
-    <div><button data-post="output?s=:kitchenLight&p=:brightness" data-body="0.0">Send (:kitchenLight :brightness 0.0)</button></div>
-    <div><button data-post="output?s=:kitchenLight&p=:brightness" data-body="1.0">Send (:kitchenLight :brightness 1.0)</button></div>
-    <div><button data-post="output?s=:livingLampShelf&p=:brightness" data-body="0.0">Send (:livingLampShelf :brightness 0.0)</button></div>
-    <div><button data-post="output?s=:livingLampShelf&p=:brightness" data-body="1.0">Send (:livingLampShelf :brightness 1.0)</button></div>
-
-    <script>
-     Array.from(document.querySelectorAll("button")).forEach((el) => {
-       el.addEventListener("click", (ev) => {
-         fetch(el.dataset.post, {
-           method: "PUT",
-           body: el.dataset.body
-         });
-       });
-     });
-    </script>
-
-  </body>
-</html>
--- a/service/mqtt_graph_bridge/mqtt_graph_bridge.py	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,130 +0,0 @@
-"""
-We get output statements that are like light9's deviceAttrs (:dev1 :color "#ff0000"),
-convert those to outputAttrs (:dev1 :red 255; :green 0; :blue 0) and post them to mqtt.
-
-This is like light9/bin/collector.
-"""
-import json
-
-from docopt import docopt
-from rdflib import Namespace, Literal
-from twisted.internet import reactor
-import cyclone.web
-
-from mqtt_client import MqttClient
-from patchablegraph import PatchableGraph, CycloneGraphHandler, CycloneGraphEventsHandler
-from standardservice.logsetup import log, verboseLogging
-import rdf_over_http
-
-ROOM = Namespace('http://projects.bigasterisk.com/room/')
-
-devs = {
-    ROOM['kitchenLight']: {
-        'root': 'h801_skylight',
-        'ctx': ROOM['kitchenH801']
-    },
-    ROOM['kitchenCounterLight']: {
-        'root': 'h801_counter',
-        'ctx': ROOM['kitchenH801']
-    },
-    ROOM['livingLampShelf']: {
-        'root': 'sonoff_0/switch/sonoff_basic_relay/command',
-        'ctx': ROOM['sonoff_0'],
-        'values': 'binary',
-    },
-}
-
-
-class OutputPage(cyclone.web.RequestHandler):
-    def put(self):
-        for stmt in rdf_over_http.rdfStatementsFromRequest(
-                self.request.arguments,
-                self.request.body,
-                self.request.headers):
-            self._onStatement(stmt)
-
-    def _onStatement(self, stmt):
-        log.info(f'incoming statement: {stmt}')
-        ignored = True
-        for dev, attrs in devs.items():
-            if stmt[0] == ROOM['frontWindow']:
-                ignored = ignored and self._publishFrontScreenText(stmt)
-
-            if stmt[0:2] == (dev, ROOM['brightness']):
-                log.info(f'brightness request: {stmt}')
-                brightness = stmt[2].toPython()
-
-                if attrs.get('values', '') == 'binary':
-                    self._publishOnOff(attrs, brightness)
-                else:
-                    self._publishRgbw(attrs, brightness)
-                    # try to stop saving this; let the device be the master usually
-                    self.settings.masterGraph.patchObject(
-                        attrs['ctx'],
-                        stmt[0], stmt[1], stmt[2])
-                ignored = False
-        if ignored:
-            log.warn("ignoring %s", stmt)
-
-    def _publishOnOff(self, attrs, brightness):
-        msg = 'OFF'
-        if brightness > 0:
-            msg = 'ON'
-        self._publish(topic=attrs['root'], message=msg)
-
-    def _publishRgbw(self, attrs, brightness):
-        for chan, scale in [('w1', 1),
-                            ('r', 1),
-                            ('g', .8),
-                            ('b', .8)]:
-            self._publish(
-                topic=f"{attrs['root']}/light/kit_{chan}/command",
-                messageJson={
-                    'state': 'ON',
-                    'brightness': int(brightness * 255)
-                })
-
-    def _publishFrontScreenText(self, stmt):
-        ignored = True
-        for line in ['line1', 'line2', 'line3', 'line4']:
-            if stmt[1] == ROOM[line]:
-                ignored = False
-                self.settings.mqtt.publish(
-                    b'frontwindow/%s' % line.encode('ascii'),
-                    stmt[2].toPython())
-        return ignored
-
-    def _publish(self, topic: str, messageJson: object=None,
-                 message: str=None):
-        if messageJson is not None:
-            message = json.dumps(messageJson)
-        self.settings.mqtt.publish(
-            topic.encode('ascii'),
-            message.encode('ascii'))
-
-
-if __name__ == '__main__':
-    arg = docopt("""
-    Usage: mqtt_graph_bridge.py [options]
-
-    -v   Verbose
-    """)
-    verboseLogging(arg['-v'])
-
-    masterGraph = PatchableGraph()
-
-    mqtt = MqttClient(clientId='mqtt_graph_bridge', brokerPort=1883)
-
-    port = 10008
-    reactor.listenTCP(port, cyclone.web.Application([
-        (r"/()", cyclone.web.StaticFileHandler,
-         {"path": ".", "default_filename": "index.html"}),
-        (r'/output', OutputPage),
-        ], mqtt=mqtt, debug=arg['-v']), interface='::')
-    log.warn('serving on %s', port)
-
-    for dev, attrs in devs.items():
-        masterGraph.patchObject(attrs['ctx'],
-                                dev, ROOM['brightness'], Literal(0.0))
-
-    reactor.run()
--- a/service/mqtt_graph_bridge/rdf_over_http.py	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-from rdflib import Graph, URIRef, Literal, Namespace
-from rdflib.parser import StringInputSource
-
-ROOM = Namespace('http://projects.bigasterisk.com/room/')
-
-
-def rdfGraphBody(body, headers):
-    g = Graph()
-    g.parse(StringInputSource(body), format='nt')
-    return g
-
-
-def expandQueryParamUri(txt) -> URIRef:
-    if txt.startswith(':'):
-        return ROOM[txt.lstrip(':')]
-    # etc
-    return URIRef(txt)
-
-
-def rdfStatementsFromRequest(arg, body, headers):
-    if arg.get('s') and arg.get('p'):
-        subj = expandQueryParamUri(arg['s'][-1])
-        pred = expandQueryParamUri(arg['p'][-1])
-        turtleLiteral = body
-        try:
-            obj = Literal(float(turtleLiteral))
-        except ValueError:
-            obj = Literal(turtleLiteral)
-        yield (subj, pred, obj)
-    else:
-        g = rdfGraphBody(body, headers)
-        assert len(g) == 1, len(g)
-        yield g.triples((None, None, None)).next()
-        # could support multiple stmts
--- a/service/mqtt_graph_bridge/readme	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-tail all changes:
-mosquitto_sub -v -t 004BD965/\# 
-
-bang(pts/8):~% mosquitto_pub -t 004BD965/w1/light/switch -m ON  
-bang(pts/8):~% mosquitto_pub -t 004BD965/w1/light/switch -m OFF
-
-
--- a/service/mqtt_graph_bridge/requirements.txt	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-cyclone
-rdflib-jsonld==0.4.0
-rdflib==4.2.2
-twisted-mqtt==0.3.6
-rx==1.6.1
-git+http://github.com/drewp/scales.git@448d59fb491b7631877528e7695a93553bfaaa93#egg=scales
-
-cycloneerr
-patchablegraph==0.11.0
-rdfdb==0.21.0
-standardservice==0.6.0
-mqtt_client==0.7.0
--- a/service/mqtt_graph_bridge/tasks.py	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-from invoke import task
-
-JOB = 'mqtt_graph_bridge'
-PORT = 10008
-TAG = f'bang6:5000/{JOB}_x86:latest'
-
-@task
-def build_image(ctx):
-    ctx.run(f'docker build --network=host -t {TAG} .')
-
-@task(pre=[build_image])
-def push_image(ctx):
-    ctx.run(f'docker push {TAG}')
-
-@task(pre=[build_image])
-def shell(ctx):
-    ctx.run(f'docker run --name={JOB}_shell --rm -it --cap-add SYS_PTRACE --net=host {TAG} /bin/bash', pty=True)
-
-@task(pre=[build_image])
-def local_run(ctx):
-    ctx.run(f'docker run --name={JOB}_local --rm -it --net=host {TAG} python3 mqtt_graph_bridge.py -v', pty=True)
-
-@task(pre=[push_image])
-def redeploy(ctx):
-    ctx.run(f'supervisorctl -s http://bang:9001/ restart {JOB}_{PORT}')
-
-@task
-def program_board_over_usb(ctx):
-    tag = 'esphome/esphome'
-    ctx.run(f"docker run --rm -v `pwd`:/config --device=/dev/ttyUSB0 -it {tag} door.yaml run", pty=True)
-# config_skylight.yaml run --no-logs
-
-@task
-def monitor_usb(ctx):
-    tag = 'esphome/esphome'
-    ctx.run(f"docker run --rm -v `pwd`:/config --device=/dev/ttyUSB0 -it {tag} door.yaml logs", pty=True)
-
-@task
-def tail_mqtt(ctx):
-    ctx.run(f'mosquitto_sub -h bang -p 10010 -d -v -t \#')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/mqtt_to_rdf/Dockerfile	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,13 @@
+FROM bang6:5000/base_x86
+
+WORKDIR /opt
+
+COPY requirements.txt ./
+RUN pip3 install --index-url https://projects.bigasterisk.com/ --extra-index-url https://pypi.org/simple -r requirements.txt
+RUN pip3 install -U 'https://github.com/drewp/cyclone/archive/python3.zip?v3'
+
+COPY *.py *.html *.css *.js *.n3 ./
+
+EXPOSE 10018:10018
+
+CMD [ "python3", "./rdf_from_mqtt.py" ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/mqtt_to_rdf/config_bed_bar.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,27 @@
+@prefix : <http://projects.bigasterisk.com/room/> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix fr: <http://bigasterisk.com/foaf/> .
+
+:buttonMap a :ValueMap;
+    :map [:from "OFF"; :to :notPressed], [:from "ON"; :to :pressed]
+  .
+
+:bedBarAsherButton1 a :MqttStatementSource;
+  :mqttTopic ("bed_bar_asher" "binary_sensor" "button_1" "state");
+  :parser :buttonMap;
+  :graphStatements [:outputPredicate :state;] .
+  
+:bedBarAsherButton2 a :MqttStatementSource;
+  :mqttTopic ("bed_bar_asher" "binary_sensor" "button_2" "state");
+  :parser :buttonMap;
+  :graphStatements [:outputPredicate :state;] .
+  
+:bedBarAsherButton3 a :MqttStatementSource;
+  :mqttTopic ("bed_bar_asher" "binary_sensor" "button_3" "state");
+  :parser :buttonMap;
+  :graphStatements [:outputPredicate :state;] .
+  
+:bedBarAsherButton4 a :MqttStatementSource;
+  :mqttTopic ("bed_bar_asher" "binary_sensor" "button_4" "state");
+  :parser :buttonMap;
+  :graphStatements [:outputPredicate :state;] .
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/mqtt_to_rdf/config_cardreader.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,14 @@
+@prefix : <http://projects.bigasterisk.com/room/> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix fr: <http://bigasterisk.com/foaf/> .
+
+:cardReader a :MqttStatementSource;
+  :mqttTopic ("frontwindow" "tag");
+  :parser :tagIdToUri;  # AA-BB-CC-DD to <http://bigasterisk.com/rfidCard/aabbccdd>
+  
+  :graphStatements [
+     :outputPredicate :currentRead;
+     :statementLifetime "5s";
+  ]
+  .
+  
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/mqtt_to_rdf/config_nightlight_ari.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,26 @@
+@prefix : <http://projects.bigasterisk.com/room/> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix fr: <http://bigasterisk.com/foaf/> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+
+:nightlightAriTemperature a :MqttStatementSource;
+  :mqttTopic ("nightlight_ari" "sensor" "temperature" "state");
+
+  :parser xsd:double;
+  :conversions (:celsiusToFarenheit
+                [:ignoreValueBelow -999]);
+  :graphStatements [
+    :outputPredicate :temperatureF;  
+    :statementLifetime "150s";
+  # ], [
+  #  :conversions ([:recentLow "30s"]);
+  #  :outputPredicate :recentLowTemperatureF;
+  ];
+  
+  :influxMeasurement [ # replaces this block in piNode configs
+    :measurement "temperatureF";
+    :predicate :temperatureF;
+    :tag [:key "host"; :value "nightlight_ari"],
+         [:key "location"; :value "ariRoom"]] .
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/mqtt_to_rdf/index.html	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,32 @@
+<!doctype html>
+<html>
+  <head>
+    <title>rdf_from_mqtt</title>
+    <meta charset="utf-8">
+    <script src="/lib/polymer/1.0.9/webcomponentsjs/webcomponents.min.js"></script>
+    <script src="/lib/require/require-2.3.3.js"></script>
+    <script src="/rdf/common_paths_and_ns.js"></script>
+
+    <link rel="stylesheet" href="/rdf/browse/style.css">
+
+    <link rel="import" href="/rdf/streamed-graph.html">
+    <link rel="import" href="/lib/polymer/1.0.9/polymer/polymer.html">
+
+    <meta name="mobile-web-app-capable" content="yes">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+  </head>
+  <body class="rdfBrowsePage">
+    <template id="t" is="dom-bind">
+      <streamed-graph url="mqtt/events" graph="{{graph}}"></streamed-graph>
+      <div id="out"></div>
+      <script type="module" src="/rdf/streamed_graph_view.js"></script>
+    </template>
+
+      <div class="served-resources">
+        <a href="stats/">/stats/</a>
+        <a href="mqtt">/mqtt</a>
+        <a href="mqtt/events">/mqtt/events</a>
+      </div>
+
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/mqtt_to_rdf/rdf_from_mqtt.py	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,202 @@
+"""
+Subscribe to mqtt topics; generate RDF statements.
+"""
+import json
+import sys
+from docopt import docopt
+from rdflib import Namespace, URIRef, Literal, Graph, RDF, XSD
+from rdflib.parser import StringInputSource
+from rdflib.term import Node
+from twisted.internet import reactor
+import cyclone.web
+import rx, rx.operators, rx.scheduler.eventloop
+from greplin import scales
+from greplin.scales.cyclonehandler import StatsHandler
+
+from export_to_influxdb import InfluxExporter
+from mqtt_client import MqttClient
+
+from patchablegraph import PatchableGraph, CycloneGraphHandler, CycloneGraphEventsHandler
+from rdfdb.patch import Patch
+from rdfdb.rdflibpatch import graphFromQuads
+from standardservice.logsetup import log, verboseLogging
+from standardservice.scalessetup import gatherProcessStats
+
+ROOM = Namespace('http://projects.bigasterisk.com/room/')
+
+gatherProcessStats()
+
+def parseDurationLiteral(lit: Literal) -> float:
+    if lit.endswith('s'):
+        return float(lit.split('s')[0])
+    raise NotImplementedError(f'duration literal: {lit}')
+
+
+class MqttStatementSource:
+    def __init__(self, uri, config, masterGraph, mqtt, influx):
+        self.uri = uri
+        self.config = config
+        self.masterGraph = masterGraph
+        self.mqtt = mqtt
+        self.influx = influx
+
+        self.mqttTopic = self.topicFromConfig(self.config)
+
+        statPath = '/subscribed_topic/' + self.mqttTopic.decode('ascii').replace('/', '|')
+        scales.init(self, statPath)
+        self._mqttStats = scales.collection(
+            statPath + '/incoming', scales.IntStat('count'),
+            scales.RecentFpsStat('fps'))
+
+
+        rawBytes = self.subscribeMqtt()
+        rawBytes = rx.operators.do_action(self.countIncomingMessage)(rawBytes)
+        parsed = self.getParser()(rawBytes)
+
+        g = self.config
+        for conv in g.items(g.value(self.uri, ROOM['conversions'])):
+            parsed = self.conversionStep(conv)(parsed)
+
+        outputQuadsSets = rx.combine_latest(
+            *[self.makeQuads(parsed, plan)
+              for plan in g.objects(self.uri, ROOM['graphStatements'])])
+
+        outputQuadsSets.subscribe_(self.updateQuads)
+
+    def topicFromConfig(self, config) -> bytes:
+        topicParts = list(config.items(config.value(self.uri, ROOM['mqttTopic'])))
+        return b'/'.join(t.encode('ascii') for t in topicParts)
+
+
+    def subscribeMqtt(self):
+        return self.mqtt.subscribe(self.mqttTopic)
+
+    def countIncomingMessage(self, _):
+        self._mqttStats.fps.mark()
+        self._mqttStats.count += 1
+
+    def getParser(self):
+        g = self.config
+        parser = g.value(self.uri, ROOM['parser'])
+        if parser == XSD.double:
+            return rx.operators.map(lambda v: Literal(float(v.decode('ascii'))))
+        elif parser == ROOM['tagIdToUri']:
+            return rx.operators.map(self.tagIdToUri)
+        elif parser == ROOM['onOffBrightness']:
+            return rx.operators.map(lambda v: Literal(0.0 if v == b'OFF' else 1.0))
+        elif parser == ROOM['jsonBrightness']:
+            return rx.operators.map(self.parseJsonBrightness)
+        elif ROOM['ValueMap'] in g.objects(parser, RDF.type):
+            return rx.operators.map(lambda v: self.remap(parser, v.decode('ascii')))
+        else:
+            raise NotImplementedError(parser)
+
+    def parseJsonBrightness(self, mqttValue: bytes):
+        msg = json.loads(mqttValue.decode('ascii'))
+        return Literal(float(msg['brightness'] / 255) if msg['state'] == 'ON' else 0.0)
+
+    def conversionStep(self, conv: Node):
+        g = self.config
+        if conv == ROOM['celsiusToFarenheit']:
+            return rx.operators.map(lambda value: Literal(round(value.toPython() * 1.8 + 32, 2)))
+        elif g.value(conv, ROOM['ignoreValueBelow'], default=None) is not None:
+            threshold = g.value(conv, ROOM['ignoreValueBelow'])
+            return rx.operators.filter(lambda value: value.toPython() >= threshold.toPython())
+        else:
+            raise NotImplementedError(conv)
+
+    def makeQuads(self, parsed, plan):
+        g = self.config
+        def quadsFromValue(valueNode):
+            return set([
+                (self.uri,
+                 g.value(plan, ROOM['outputPredicate']),
+                 valueNode,
+                 self.uri)
+            ])
+
+        def emptyQuads(element):
+            return set([])
+
+        quads = rx.operators.map(quadsFromValue)(parsed)
+
+        dur = g.value(plan, ROOM['statementLifetime'])
+        if dur is not None:
+            sec = parseDurationLiteral(dur)
+            quads = quads.pipe(
+                rx.operators.debounce(sec, rx.scheduler.eventloop.TwistedScheduler(reactor)),
+                rx.operators.map(emptyQuads),
+                rx.operators.merge(quads),
+                )
+
+        return quads
+
+    def updateQuads(self, newGraphs):
+        newQuads = set.union(*newGraphs)
+        g = graphFromQuads(newQuads)
+        log.debug(f'{self.uri} update to {len(newQuads)} statements')
+
+        self.influx.exportToInflux(newQuads)
+
+        self.masterGraph.patchSubgraph(self.uri, g)
+
+    def tagIdToUri(self, value: bytearray) -> URIRef:
+        justHex = value.decode('ascii').replace('-', '').lower()
+        int(justHex, 16)  # validate
+        return URIRef(f'http://bigasterisk.com/rfidCard/{justHex}')
+
+    def remap(self, parser, valueStr: str):
+        g = self.config
+        value = Literal(valueStr)
+        for entry in g.objects(parser, ROOM['map']):
+            if value == g.value(entry, ROOM['from']):
+                return g.value(entry, ROOM['to'])
+        raise KeyError(value)
+
+
+if __name__ == '__main__':
+    arg = docopt("""
+    Usage: rdf_from_mqtt.py [options]
+
+    -v        Verbose
+    --cs=STR  Only process config filenames with this substring
+    """)
+    verboseLogging(arg['-v'])
+
+    config = Graph()
+    for fn in [
+            "config_cardreader.n3",
+            "config_nightlight_ari.n3",
+            "config_bed_bar.n3",
+            "config_air_quality_indoor.n3",
+            "config_air_quality_outdoor.n3",
+            "config_living_lamps.n3",
+            "config_kitchen.n3",
+    ]:
+        if not arg['--cs'] or arg['--cs'] in fn:
+            config.parse(fn, format='n3')
+
+    masterGraph = PatchableGraph()
+
+    mqtt = MqttClient(clientId='rdf_from_mqtt', brokerHost='bang',
+                      brokerPort=1883)
+    influx = InfluxExporter(config)
+
+    srcs = []
+    for src in config.subjects(RDF.type, ROOM['MqttStatementSource']):
+        srcs.append(MqttStatementSource(src, config, masterGraph, mqtt, influx))
+    log.info(f'set up {len(srcs)} sources')
+
+    port = 10018
+    reactor.listenTCP(port, cyclone.web.Application([
+        (r"/()", cyclone.web.StaticFileHandler,
+         {"path": ".", "default_filename": "index.html"}),
+        (r'/stats/(.*)', StatsHandler, {'serverName': 'rdf_from_mqtt'}),
+        (r"/graph/mqtt", CycloneGraphHandler, {'masterGraph': masterGraph}),
+        (r"/graph/mqtt/events", CycloneGraphEventsHandler,
+         {'masterGraph': masterGraph}),
+        ], mqtt=mqtt, masterGraph=masterGraph, debug=arg['-v']),
+                      interface='::')
+    log.warn('serving on %s', port)
+
+    reactor.run()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/mqtt_to_rdf/requirements.txt	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,15 @@
+pytype
+
+cyclone
+rdflib-jsonld==0.4.0
+rdflib==4.2.2
+twisted-mqtt==0.3.6
+git+http://github.com/drewp/scales.git@550bcba42c5e96152ff5c5bd753fbb33ffdfe460#egg=scales
+git+https://github.com/ReactiveX/RxPY.git@6deb66e827f34a88b4605773d7671322b9cbbd08#egg=rx
+
+cycloneerr
+export_to_influxdb==0.4.0
+mqtt_client==0.9.0
+patchablegraph==0.11.0
+rdfdb==0.21.0
+standardservice==0.6.0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/mqtt_to_rdf/serv.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,24 @@
+@prefix : <http://bigasterisk.com/ns/serv#> .
+@prefix auth: <http://bigasterisk.com/ns/serv/auth#> .
+@prefix serv: <http://bigasterisk.com/services/> .
+
+serv:mqtt_to_rdf a :Service;
+      :path "/mqtt_to_rdf/";
+      :openid auth:admin;
+      :serverHost "bang";
+      :internalPort 10018;
+      :prodDockerFlags (
+      "-p" "10018:10018"
+      "--net=host");
+      :localDockerFlags (
+        "-v" "`pwd`:/opt"
+        "-v" "/my/proj/homeauto/lib:/lib_src"
+      );
+      :localRunCmdline (
+      
+      "python3" "mqtt_to_rdf.py" "-v"
+#"--cs" "living"
+);
+      :dockerFile "Dockerfile"
+.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/mqtt_to_rdf/tasks.py	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,14 @@
+from invoke import task, Collection
+
+import sys
+sys.path.append('/my/proj/release')
+from serv_tasks import serv_tasks
+
+ns = Collection()
+serv_tasks(ns, 'serv.n3', 'mqtt_to_rdf')
+
+@ns.add_task
+@task
+def tail_mqtt(ctx):
+    internal_mqtt_port = 10010
+    ctx.run(f'mosquitto_sub -h bang -p 1883 -d -v -t \#')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/piNode/serv.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,4 @@
+@prefix : <http://bigasterisk.com/ns/serv#> .
+@prefix auth: <http://bigasterisk.com/ns/serv/auth#> .
+@prefix serv: <http://bigasterisk.com/services/> .
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/powerEagle/serv.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,23 @@
+@prefix : <http://bigasterisk.com/ns/serv#> .
+@prefix auth: <http://bigasterisk.com/ns/serv/auth#> .
+@prefix serv: <http://bigasterisk.com/services/> .
+
+
+serv:powerEagle a :Service;
+      :path "/powerEagle/";
+      :openid auth:admin;
+      :serverHost "bang";
+      :internalPort 10016;
+      :prodDockerFlags (
+      "-p" "10016:10016"
+      "--net=host"
+      );
+      :localDockerFlags (
+        "-v" "`pwd`:/opt"
+      );
+      :localRunCmdline (
+        "python3" "reader.py" "-v"
+      );
+      :dockerFile "Dockerfile"
+.
+
--- a/service/rdf_from_mqtt/Dockerfile	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-FROM bang6:5000/base_x86
-
-WORKDIR /opt
-
-COPY requirements.txt ./
-RUN pip3 install --index-url https://projects.bigasterisk.com/ --extra-index-url https://pypi.org/simple -r requirements.txt
-RUN pip3 install -U 'https://github.com/drewp/cyclone/archive/python3.zip?v3'
-
-COPY *.py *.html *.css *.js *.n3 ./
-
-EXPOSE 10018:10018
-
-CMD [ "python3", "./rdf_from_mqtt.py" ]
--- a/service/rdf_from_mqtt/config_bed_bar.n3	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-@prefix : <http://projects.bigasterisk.com/room/> .
-@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
-@prefix fr: <http://bigasterisk.com/foaf/> .
-
-:buttonMap a :ValueMap;
-    :map [:from "OFF"; :to :notPressed], [:from "ON"; :to :pressed]
-  .
-
-:bedBarAsherButton1 a :MqttStatementSource;
-  :mqttTopic ("bed_bar_asher" "binary_sensor" "button_1" "state");
-  :parser :buttonMap;
-  :graphStatements [:outputPredicate :state;] .
-  
-:bedBarAsherButton2 a :MqttStatementSource;
-  :mqttTopic ("bed_bar_asher" "binary_sensor" "button_2" "state");
-  :parser :buttonMap;
-  :graphStatements [:outputPredicate :state;] .
-  
-:bedBarAsherButton3 a :MqttStatementSource;
-  :mqttTopic ("bed_bar_asher" "binary_sensor" "button_3" "state");
-  :parser :buttonMap;
-  :graphStatements [:outputPredicate :state;] .
-  
-:bedBarAsherButton4 a :MqttStatementSource;
-  :mqttTopic ("bed_bar_asher" "binary_sensor" "button_4" "state");
-  :parser :buttonMap;
-  :graphStatements [:outputPredicate :state;] .
\ No newline at end of file
--- a/service/rdf_from_mqtt/config_cardreader.n3	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-@prefix : <http://projects.bigasterisk.com/room/> .
-@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
-@prefix fr: <http://bigasterisk.com/foaf/> .
-
-:cardReader a :MqttStatementSource;
-  :mqttTopic ("frontwindow" "tag");
-  :parser :tagIdToUri;  # AA-BB-CC-DD to <http://bigasterisk.com/rfidCard/aabbccdd>
-  
-  :graphStatements [
-     :outputPredicate :currentRead;
-     :statementLifetime "5s";
-  ]
-  .
-  
\ No newline at end of file
--- a/service/rdf_from_mqtt/config_nightlight_ari.n3	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-@prefix : <http://projects.bigasterisk.com/room/> .
-@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
-@prefix fr: <http://bigasterisk.com/foaf/> .
-@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
-
-
-:nightlightAriTemperature a :MqttStatementSource;
-  :mqttTopic ("nightlight_ari" "sensor" "temperature" "state");
-
-  :parser xsd:double;
-  :conversions (:celsiusToFarenheit
-                [:ignoreValueBelow -999]);
-  :graphStatements [
-    :outputPredicate :temperatureF;  
-    :statementLifetime "150s";
-  # ], [
-  #  :conversions ([:recentLow "30s"]);
-  #  :outputPredicate :recentLowTemperatureF;
-  ];
-  
-  :influxMeasurement [ # replaces this block in piNode configs
-    :measurement "temperatureF";
-    :predicate :temperatureF;
-    :tag [:key "host"; :value "nightlight_ari"],
-         [:key "location"; :value "ariRoom"]] .
-
--- a/service/rdf_from_mqtt/index.html	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <title>rdf_from_mqtt</title>
-    <meta charset="utf-8">
-    <script src="/lib/polymer/1.0.9/webcomponentsjs/webcomponents.min.js"></script>
-    <script src="/lib/require/require-2.3.3.js"></script>
-    <script src="/rdf/common_paths_and_ns.js"></script>
-
-    <link rel="stylesheet" href="/rdf/browse/style.css">
-
-    <link rel="import" href="/rdf/streamed-graph.html">
-    <link rel="import" href="/lib/polymer/1.0.9/polymer/polymer.html">
-
-    <meta name="mobile-web-app-capable" content="yes">
-    <meta name="viewport" content="width=device-width, initial-scale=1">
-  </head>
-  <body class="rdfBrowsePage">
-    <template id="t" is="dom-bind">
-      <streamed-graph url="mqtt/events" graph="{{graph}}"></streamed-graph>
-      <div id="out"></div>
-      <script type="module" src="/rdf/streamed_graph_view.js"></script>
-    </template>
-
-      <div class="served-resources">
-        <a href="stats/">/stats/</a>
-        <a href="mqtt">/mqtt</a>
-        <a href="mqtt/events">/mqtt/events</a>
-      </div>
-
-  </body>
-</html>
--- a/service/rdf_from_mqtt/rdf_from_mqtt.py	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,202 +0,0 @@
-"""
-Subscribe to mqtt topics; generate RDF statements.
-"""
-import json
-import sys
-from docopt import docopt
-from rdflib import Namespace, URIRef, Literal, Graph, RDF, XSD
-from rdflib.parser import StringInputSource
-from rdflib.term import Node
-from twisted.internet import reactor
-import cyclone.web
-import rx, rx.operators, rx.scheduler.eventloop
-from greplin import scales
-from greplin.scales.cyclonehandler import StatsHandler
-
-from export_to_influxdb import InfluxExporter
-from mqtt_client import MqttClient
-
-from patchablegraph import PatchableGraph, CycloneGraphHandler, CycloneGraphEventsHandler
-from rdfdb.patch import Patch
-from rdfdb.rdflibpatch import graphFromQuads
-from standardservice.logsetup import log, verboseLogging
-from standardservice.scalessetup import gatherProcessStats
-
-ROOM = Namespace('http://projects.bigasterisk.com/room/')
-
-gatherProcessStats()
-
-def parseDurationLiteral(lit: Literal) -> float:
-    if lit.endswith('s'):
-        return float(lit.split('s')[0])
-    raise NotImplementedError(f'duration literal: {lit}')
-
-
-class MqttStatementSource:
-    def __init__(self, uri, config, masterGraph, mqtt, influx):
-        self.uri = uri
-        self.config = config
-        self.masterGraph = masterGraph
-        self.mqtt = mqtt
-        self.influx = influx
-
-        self.mqttTopic = self.topicFromConfig(self.config)
-
-        statPath = '/subscribed_topic/' + self.mqttTopic.decode('ascii').replace('/', '|')
-        scales.init(self, statPath)
-        self._mqttStats = scales.collection(
-            statPath + '/incoming', scales.IntStat('count'),
-            scales.RecentFpsStat('fps'))
-
-
-        rawBytes = self.subscribeMqtt()
-        rawBytes = rx.operators.do_action(self.countIncomingMessage)(rawBytes)
-        parsed = self.getParser()(rawBytes)
-
-        g = self.config
-        for conv in g.items(g.value(self.uri, ROOM['conversions'])):
-            parsed = self.conversionStep(conv)(parsed)
-
-        outputQuadsSets = rx.combine_latest(
-            *[self.makeQuads(parsed, plan)
-              for plan in g.objects(self.uri, ROOM['graphStatements'])])
-
-        outputQuadsSets.subscribe_(self.updateQuads)
-
-    def topicFromConfig(self, config) -> bytes:
-        topicParts = list(config.items(config.value(self.uri, ROOM['mqttTopic'])))
-        return b'/'.join(t.encode('ascii') for t in topicParts)
-
-
-    def subscribeMqtt(self):
-        return self.mqtt.subscribe(self.mqttTopic)
-
-    def countIncomingMessage(self, _):
-        self._mqttStats.fps.mark()
-        self._mqttStats.count += 1
-
-    def getParser(self):
-        g = self.config
-        parser = g.value(self.uri, ROOM['parser'])
-        if parser == XSD.double:
-            return rx.operators.map(lambda v: Literal(float(v.decode('ascii'))))
-        elif parser == ROOM['tagIdToUri']:
-            return rx.operators.map(self.tagIdToUri)
-        elif parser == ROOM['onOffBrightness']:
-            return rx.operators.map(lambda v: Literal(0.0 if v == b'OFF' else 1.0))
-        elif parser == ROOM['jsonBrightness']:
-            return rx.operators.map(self.parseJsonBrightness)
-        elif ROOM['ValueMap'] in g.objects(parser, RDF.type):
-            return rx.operators.map(lambda v: self.remap(parser, v.decode('ascii')))
-        else:
-            raise NotImplementedError(parser)
-
-    def parseJsonBrightness(self, mqttValue: bytes):
-        msg = json.loads(mqttValue.decode('ascii'))
-        return Literal(float(msg['brightness'] / 255) if msg['state'] == 'ON' else 0.0)
-
-    def conversionStep(self, conv: Node):
-        g = self.config
-        if conv == ROOM['celsiusToFarenheit']:
-            return rx.operators.map(lambda value: Literal(round(value.toPython() * 1.8 + 32, 2)))
-        elif g.value(conv, ROOM['ignoreValueBelow'], default=None) is not None:
-            threshold = g.value(conv, ROOM['ignoreValueBelow'])
-            return rx.operators.filter(lambda value: value.toPython() >= threshold.toPython())
-        else:
-            raise NotImplementedError(conv)
-
-    def makeQuads(self, parsed, plan):
-        g = self.config
-        def quadsFromValue(valueNode):
-            return set([
-                (self.uri,
-                 g.value(plan, ROOM['outputPredicate']),
-                 valueNode,
-                 self.uri)
-            ])
-
-        def emptyQuads(element):
-            return set([])
-
-        quads = rx.operators.map(quadsFromValue)(parsed)
-
-        dur = g.value(plan, ROOM['statementLifetime'])
-        if dur is not None:
-            sec = parseDurationLiteral(dur)
-            quads = quads.pipe(
-                rx.operators.debounce(sec, rx.scheduler.eventloop.TwistedScheduler(reactor)),
-                rx.operators.map(emptyQuads),
-                rx.operators.merge(quads),
-                )
-
-        return quads
-
-    def updateQuads(self, newGraphs):
-        newQuads = set.union(*newGraphs)
-        g = graphFromQuads(newQuads)
-        log.debug(f'{self.uri} update to {len(newQuads)} statements')
-
-        self.influx.exportToInflux(newQuads)
-
-        self.masterGraph.patchSubgraph(self.uri, g)
-
-    def tagIdToUri(self, value: bytearray) -> URIRef:
-        justHex = value.decode('ascii').replace('-', '').lower()
-        int(justHex, 16)  # validate
-        return URIRef(f'http://bigasterisk.com/rfidCard/{justHex}')
-
-    def remap(self, parser, valueStr: str):
-        g = self.config
-        value = Literal(valueStr)
-        for entry in g.objects(parser, ROOM['map']):
-            if value == g.value(entry, ROOM['from']):
-                return g.value(entry, ROOM['to'])
-        raise KeyError(value)
-
-
-if __name__ == '__main__':
-    arg = docopt("""
-    Usage: rdf_from_mqtt.py [options]
-
-    -v        Verbose
-    --cs=STR  Only process config filenames with this substring
-    """)
-    verboseLogging(arg['-v'])
-
-    config = Graph()
-    for fn in [
-            "config_cardreader.n3",
-            "config_nightlight_ari.n3",
-            "config_bed_bar.n3",
-            "config_air_quality_indoor.n3",
-            "config_air_quality_outdoor.n3",
-            "config_living_lamps.n3",
-            "config_kitchen.n3",
-    ]:
-        if not arg['--cs'] or arg['--cs'] in fn:
-            config.parse(fn, format='n3')
-
-    masterGraph = PatchableGraph()
-
-    mqtt = MqttClient(clientId='rdf_from_mqtt', brokerHost='bang',
-                      brokerPort=1883)
-    influx = InfluxExporter(config)
-
-    srcs = []
-    for src in config.subjects(RDF.type, ROOM['MqttStatementSource']):
-        srcs.append(MqttStatementSource(src, config, masterGraph, mqtt, influx))
-    log.info(f'set up {len(srcs)} sources')
-
-    port = 10018
-    reactor.listenTCP(port, cyclone.web.Application([
-        (r"/()", cyclone.web.StaticFileHandler,
-         {"path": ".", "default_filename": "index.html"}),
-        (r'/stats/(.*)', StatsHandler, {'serverName': 'rdf_from_mqtt'}),
-        (r"/graph/mqtt", CycloneGraphHandler, {'masterGraph': masterGraph}),
-        (r"/graph/mqtt/events", CycloneGraphEventsHandler,
-         {'masterGraph': masterGraph}),
-        ], mqtt=mqtt, masterGraph=masterGraph, debug=arg['-v']),
-                      interface='::')
-    log.warn('serving on %s', port)
-
-    reactor.run()
--- a/service/rdf_from_mqtt/requirements.txt	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-pytype
-
-cyclone
-rdflib-jsonld==0.4.0
-rdflib==4.2.2
-twisted-mqtt==0.3.6
-git+http://github.com/drewp/scales.git@550bcba42c5e96152ff5c5bd753fbb33ffdfe460#egg=scales
-git+https://github.com/ReactiveX/RxPY.git@6deb66e827f34a88b4605773d7671322b9cbbd08#egg=rx
-
-cycloneerr
-export_to_influxdb==0.4.0
-mqtt_client==0.9.0
-patchablegraph==0.11.0
-rdfdb==0.21.0
-standardservice==0.6.0
--- a/service/rdf_from_mqtt/serv.n3	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-@prefix : <http://bigasterisk.com/ns/serv#> .
-@prefix auth: <http://bigasterisk.com/ns/serv/auth#> .
-@prefix serv: <http://bigasterisk.com/services/> .
-
-serv:rdf_from_mqtt a :Service;
-      :path "/rdf_from_mqtt/";
-      :openid auth:admin;
-      :serverHost "bang";
-      :internalPort 10018;
-      :prodDockerFlags (
-      "-p" "10018:10018"
-      "--net=host");
-      :localDockerFlags (
-        "-v" "`pwd`:/opt"
-        "-v" "/my/proj/homeauto/lib:/lib_src"
-      );
-      :localRunCmdline (
-      
-      "python3" "rdf_from_mqtt.py" "-v"
-#"--cs" "living"
-);
-      :dockerFile "Dockerfile"
-.
-
--- a/service/rdf_from_mqtt/tasks.py	Wed Feb 05 17:20:28 2020 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-from invoke import task, Collection
-
-import sys
-sys.path.append('/my/proj/release')
-from serv_tasks import serv_tasks
-
-ns = Collection()
-serv_tasks(ns, 'serv.n3', 'rdf_from_mqtt')
-
-@ns.add_task
-@task
-def tail_mqtt(ctx):
-    internal_mqtt_port = 10010
-    ctx.run(f'mosquitto_sub -h bang -p 1883 -d -v -t \#')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/rdf_to_mqtt/Dockerfile	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,13 @@
+FROM bang6:5000/base_x86
+
+WORKDIR /opt
+
+COPY requirements.txt ./
+RUN pip3 install --index-url https://projects.bigasterisk.com/ --extra-index-url https://pypi.org/simple -r requirements.txt
+RUN pip3 install -U 'https://github.com/drewp/cyclone/archive/python3.zip?v3'
+
+COPY *.py *.html *.css *.js ./
+
+EXPOSE 10011:10011
+
+CMD [ "python3", "./mqtt_graph_bridge.py" ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/rdf_to_mqtt/config.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,54 @@
+@prefix : <http://projects.bigasterisk.com/room/> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix fr: <http://bigasterisk.com/foaf/> .
+
+:kitchenSkylight a :Device;
+  :mqttTopicHead ("h801_skylight" "light");
+  :convertedAttr [
+    :deviceAttr :white;
+    :mqttTopicTail ("kit_w1" "command");
+    :valueConversion :to8Bit;
+    :message '{"state":"ON","brightness":%value%}'
+  ],
+  :convertedAttr [
+    :deviceAttr :color;
+    :mqttTopicTail ("kit_r" "command");
+    :valueConversion :extractRed8Bit;
+    :message '{"state":"ON","brightness":%value%}'
+   ],
+  :convertedAttr [
+    :deviceAttr :color;
+    :mqttTopicTail ("kit_g" "command");
+    :valueConversion :extractGreen8Bit;
+    :message '{"state":"ON","brightness":%value%}'
+   ],
+  :convertedAttr [
+    :deviceAttr :color;
+    :mqttTopicTail ("kit_r" "command");
+    :valueConversion :extractBlue8Bit;
+    :message '{"state":"ON","brightness":%value%}'
+   ],
+
+:nightlightAriTemperature a :ExportedMeasurement;
+  :mqttTopicHead ("nightlight_ari" "sensor" "temperature" "state");
+
+  :missingAfterSec 150;
+  :ignoreInputValueBelow -999;
+
+  :valueProcess [
+    :conversion :celsiusToFarenheit; #and round(x,2)
+    ];
+
+  :graphStatements [
+    :outputPredicate :temperatureF;  
+    :statementLifetime :untilReplaced;
+
+    # is this just another valueProcess?
+    :outputRecentPredicate :recentLowTemperatureF; :recentPeriodSec 30;
+  ],
+  
+  :influxMeasurement [
+    :measurement "temperatureF";
+    :predicate :temperatureF;
+    :tag [:key "host"; :value "nightlight_ari"],
+         [:key "location"; :value "ariRoom"]] .
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/rdf_to_mqtt/h801/config_counter.yaml	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,73 @@
+substitutions:
+  pwm_freq: 310 Hz
+
+#310 low buzz, nicer than 520
+#520 low pitch buzz, ok
+#600 sound is present but might be ok
+#800 bad sound
+#1500 sound is bad but we had it like that
+#3000 very bad snd
+#6000 bad snd and flicker
+#9000 flicker
+  
+esphomeyaml:
+  name: h801_counter
+  platform: ESP8266
+  board: esp8285
+  build_path: build
+
+wifi:
+  ssid: !secret wifi_ssid
+  password: !secret wifi_password
+  manual_ip:
+    static_ip: 10.2.0.59
+    gateway: 10.2.0.1
+    subnet: 255.255.255.0
+
+mqtt:
+  broker: '10.2.0.1'
+  port: 1883
+
+logger:
+  baud_rate: 115200
+
+ota:
+
+output:
+  - id: pwm_b
+    platform: esp8266_pwm
+    pin: 12
+    frequency: ${pwm_freq}
+  - id: pwm_g
+    platform: esp8266_pwm
+    pin: 13
+    frequency: ${pwm_freq}
+  - id: pwm_r
+    platform: esp8266_pwm
+    pin: 15
+    frequency: ${pwm_freq}
+  - id: pwm_w1
+    platform: esp8266_pwm
+    pin: 14
+    frequency: ${pwm_freq}
+  - id: pwm_w2
+    platform: esp8266_pwm
+    pin: 4
+    frequency: ${pwm_freq}
+    
+light:
+  - platform: monochromatic
+    name: "Kit_r"
+    output: pwm_r
+  - platform: monochromatic
+    name: "Kit_g"
+    output: pwm_g
+  - platform: monochromatic
+    name: "Kit_b"
+    output: pwm_b
+  - platform: monochromatic
+    name: "Kit_w1"
+    output: pwm_w1
+  - platform: monochromatic
+    name: "Kit_w2"
+    output: pwm_w2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/rdf_to_mqtt/h801/config_skylight.yaml	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,64 @@
+substitutions:
+  pwm_freq: 310 Hz
+  
+esphomeyaml:
+  name: h801_skylight
+  platform: ESP8266
+  board: esp8285
+  build_path: build
+
+wifi:
+  ssid: !secret wifi_ssid
+  password: !secret wifi_password
+  manual_ip:
+    static_ip: 10.2.0.67
+    gateway: 10.2.0.1
+    subnet: 255.255.255.0
+
+mqtt:
+  broker: '10.2.0.1'
+  port: 1883
+
+logger:
+  baud_rate: 115200
+
+ota:
+
+output:
+  - id: pwm_b
+    platform: esp8266_pwm
+    pin: 12
+    frequency: ${pwm_freq}
+  - id: pwm_r
+    platform: esp8266_pwm
+    pin: 13
+    frequency: ${pwm_freq}
+  - id: pwm_g
+    platform: esp8266_pwm
+    pin: 15
+    frequency: ${pwm_freq}
+  - id: pwm_w1
+    platform: esp8266_pwm
+    pin: 14
+    frequency: ${pwm_freq}
+  - id: pwm_w2
+    platform: esp8266_pwm
+    pin: 4
+    frequency: ${pwm_freq}
+    
+light:
+  - platform: monochromatic
+    name: "Kit_r"
+    output: pwm_r
+  - platform: monochromatic
+    name: "Kit_g"
+    output: pwm_g
+  - platform: monochromatic
+    name: "Kit_b"
+    output: pwm_b
+  - platform: monochromatic
+    name: "Kit_w1"
+    output: pwm_w1
+  - platform: monochromatic
+    name: "Kit_w2"
+    output: pwm_w2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/rdf_to_mqtt/h801/makefile	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,2 @@
+update:
+	/my/dl/dl/esphomeyaml/bin/esphomeyaml config_skylight.yaml run --no-logs
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/rdf_to_mqtt/index.html	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,35 @@
+<!doctype html>
+<html>
+  <head>
+    <title>mqtt_graph_bridge</title>
+    <meta charset="utf-8">
+    <script src="/lib/polymer/1.0.9/webcomponentsjs/webcomponents.min.js"></script>
+    <script src="/lib/require/require-2.3.3.js"></script>
+    <script src="/rdf/common_paths_and_ns.js"></script>
+
+    <link rel="stylesheet" href="/rdf/browse/style.css">
+
+  </head>
+  <body class="rdfBrowsePage">
+    mqtt_graph_bridge
+
+
+    <p>Send demo statements to bridge:</p>
+    <div><button data-post="output?s=:kitchenLight&p=:brightness" data-body="0.0">Send (:kitchenLight :brightness 0.0)</button></div>
+    <div><button data-post="output?s=:kitchenLight&p=:brightness" data-body="1.0">Send (:kitchenLight :brightness 1.0)</button></div>
+    <div><button data-post="output?s=:livingLampShelf&p=:brightness" data-body="0.0">Send (:livingLampShelf :brightness 0.0)</button></div>
+    <div><button data-post="output?s=:livingLampShelf&p=:brightness" data-body="1.0">Send (:livingLampShelf :brightness 1.0)</button></div>
+
+    <script>
+     Array.from(document.querySelectorAll("button")).forEach((el) => {
+       el.addEventListener("click", (ev) => {
+         fetch(el.dataset.post, {
+           method: "PUT",
+           body: el.dataset.body
+         });
+       });
+     });
+    </script>
+
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/rdf_to_mqtt/mqtt_graph_bridge.py	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,130 @@
+"""
+We get output statements that are like light9's deviceAttrs (:dev1 :color "#ff0000"),
+convert those to outputAttrs (:dev1 :red 255; :green 0; :blue 0) and post them to mqtt.
+
+This is like light9/bin/collector.
+"""
+import json
+
+from docopt import docopt
+from rdflib import Namespace, Literal
+from twisted.internet import reactor
+import cyclone.web
+
+from mqtt_client import MqttClient
+from patchablegraph import PatchableGraph, CycloneGraphHandler, CycloneGraphEventsHandler
+from standardservice.logsetup import log, verboseLogging
+import rdf_over_http
+
+ROOM = Namespace('http://projects.bigasterisk.com/room/')
+
+devs = {
+    ROOM['kitchenLight']: {
+        'root': 'h801_skylight',
+        'ctx': ROOM['kitchenH801']
+    },
+    ROOM['kitchenCounterLight']: {
+        'root': 'h801_counter',
+        'ctx': ROOM['kitchenH801']
+    },
+    ROOM['livingLampShelf']: {
+        'root': 'sonoff_0/switch/sonoff_basic_relay/command',
+        'ctx': ROOM['sonoff_0'],
+        'values': 'binary',
+    },
+}
+
+
+class OutputPage(cyclone.web.RequestHandler):
+    def put(self):
+        for stmt in rdf_over_http.rdfStatementsFromRequest(
+                self.request.arguments,
+                self.request.body,
+                self.request.headers):
+            self._onStatement(stmt)
+
+    def _onStatement(self, stmt):
+        log.info(f'incoming statement: {stmt}')
+        ignored = True
+        for dev, attrs in devs.items():
+            if stmt[0] == ROOM['frontWindow']:
+                ignored = ignored and self._publishFrontScreenText(stmt)
+
+            if stmt[0:2] == (dev, ROOM['brightness']):
+                log.info(f'brightness request: {stmt}')
+                brightness = stmt[2].toPython()
+
+                if attrs.get('values', '') == 'binary':
+                    self._publishOnOff(attrs, brightness)
+                else:
+                    self._publishRgbw(attrs, brightness)
+                    # try to stop saving this; let the device be the master usually
+                    self.settings.masterGraph.patchObject(
+                        attrs['ctx'],
+                        stmt[0], stmt[1], stmt[2])
+                ignored = False
+        if ignored:
+            log.warn("ignoring %s", stmt)
+
+    def _publishOnOff(self, attrs, brightness):
+        msg = 'OFF'
+        if brightness > 0:
+            msg = 'ON'
+        self._publish(topic=attrs['root'], message=msg)
+
+    def _publishRgbw(self, attrs, brightness):
+        for chan, scale in [('w1', 1),
+                            ('r', 1),
+                            ('g', .8),
+                            ('b', .8)]:
+            self._publish(
+                topic=f"{attrs['root']}/light/kit_{chan}/command",
+                messageJson={
+                    'state': 'ON',
+                    'brightness': int(brightness * 255)
+                })
+
+    def _publishFrontScreenText(self, stmt):
+        ignored = True
+        for line in ['line1', 'line2', 'line3', 'line4']:
+            if stmt[1] == ROOM[line]:
+                ignored = False
+                self.settings.mqtt.publish(
+                    b'frontwindow/%s' % line.encode('ascii'),
+                    stmt[2].toPython())
+        return ignored
+
+    def _publish(self, topic: str, messageJson: object=None,
+                 message: str=None):
+        if messageJson is not None:
+            message = json.dumps(messageJson)
+        self.settings.mqtt.publish(
+            topic.encode('ascii'),
+            message.encode('ascii'))
+
+
+if __name__ == '__main__':
+    arg = docopt("""
+    Usage: mqtt_graph_bridge.py [options]
+
+    -v   Verbose
+    """)
+    verboseLogging(arg['-v'])
+
+    masterGraph = PatchableGraph()
+
+    mqtt = MqttClient(clientId='mqtt_graph_bridge', brokerPort=1883)
+
+    port = 10008
+    reactor.listenTCP(port, cyclone.web.Application([
+        (r"/()", cyclone.web.StaticFileHandler,
+         {"path": ".", "default_filename": "index.html"}),
+        (r'/output', OutputPage),
+        ], mqtt=mqtt, debug=arg['-v']), interface='::')
+    log.warn('serving on %s', port)
+
+    for dev, attrs in devs.items():
+        masterGraph.patchObject(attrs['ctx'],
+                                dev, ROOM['brightness'], Literal(0.0))
+
+    reactor.run()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/rdf_to_mqtt/rdf_over_http.py	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,34 @@
+from rdflib import Graph, URIRef, Literal, Namespace
+from rdflib.parser import StringInputSource
+
+ROOM = Namespace('http://projects.bigasterisk.com/room/')
+
+
+def rdfGraphBody(body, headers):
+    g = Graph()
+    g.parse(StringInputSource(body), format='nt')
+    return g
+
+
+def expandQueryParamUri(txt) -> URIRef:
+    if txt.startswith(':'):
+        return ROOM[txt.lstrip(':')]
+    # etc
+    return URIRef(txt)
+
+
+def rdfStatementsFromRequest(arg, body, headers):
+    if arg.get('s') and arg.get('p'):
+        subj = expandQueryParamUri(arg['s'][-1])
+        pred = expandQueryParamUri(arg['p'][-1])
+        turtleLiteral = body
+        try:
+            obj = Literal(float(turtleLiteral))
+        except ValueError:
+            obj = Literal(turtleLiteral)
+        yield (subj, pred, obj)
+    else:
+        g = rdfGraphBody(body, headers)
+        assert len(g) == 1, len(g)
+        yield g.triples((None, None, None)).next()
+        # could support multiple stmts
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/rdf_to_mqtt/readme	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,7 @@
+tail all changes:
+mosquitto_sub -v -t 004BD965/\# 
+
+bang(pts/8):~% mosquitto_pub -t 004BD965/w1/light/switch -m ON  
+bang(pts/8):~% mosquitto_pub -t 004BD965/w1/light/switch -m OFF
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/rdf_to_mqtt/requirements.txt	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,12 @@
+cyclone
+rdflib-jsonld==0.4.0
+rdflib==4.2.2
+twisted-mqtt==0.3.6
+rx==1.6.1
+git+http://github.com/drewp/scales.git@448d59fb491b7631877528e7695a93553bfaaa93#egg=scales
+
+cycloneerr
+patchablegraph==0.11.0
+rdfdb==0.21.0
+standardservice==0.6.0
+mqtt_client==0.7.0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/rdf_to_mqtt/serv.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,23 @@
+@prefix : <http://bigasterisk.com/ns/serv#> .
+@prefix auth: <http://bigasterisk.com/ns/serv/auth#> .
+@prefix serv: <http://bigasterisk.com/services/> .
+
+
+serv:rdf_to_mqtt a :Service;
+      :path "/rdf_to_mqtt/";
+      :openid auth:admin;
+      :serverHost "bang";
+      :internalPort 10008;
+      :prodDockerFlags (
+      "-p" "10008:10008"
+      "--net=host"
+      );
+      :localDockerFlags (
+        "-v" "`pwd`:/opt"
+      );
+      :localRunCmdline (
+        "python3" "rdf_to_mqtt.py" "-v"
+      );
+      :dockerFile "Dockerfile"
+.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/rdf_to_mqtt/tasks.py	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,40 @@
+from invoke import task
+
+JOB = 'rdf_to_mqtt'
+PORT = 10008
+TAG = f'bang6:5000/{JOB}_x86:latest'
+
+@task
+def build_image(ctx):
+    ctx.run(f'docker build --network=host -t {TAG} .')
+
+@task(pre=[build_image])
+def push_image(ctx):
+    ctx.run(f'docker push {TAG}')
+
+@task(pre=[build_image])
+def shell(ctx):
+    ctx.run(f'docker run --name={JOB}_shell --rm -it --cap-add SYS_PTRACE --net=host {TAG} /bin/bash', pty=True)
+
+@task(pre=[build_image])
+def local_run(ctx):
+    ctx.run(f'docker run --name={JOB}_local --rm -it --net=host -v /my/proj/homeauto/lib:/lib_src {TAG} python3 rdf_to_mqtt.py -v', pty=True)
+
+@task(pre=[push_image])
+def redeploy(ctx):
+    ctx.run(f'supervisorctl -s http://bang:9001/ restart {JOB}_{PORT}')
+
+@task
+def program_board_over_usb(ctx):
+    tag = 'esphome/esphome'
+    ctx.run(f"docker run --rm -v `pwd`:/config --device=/dev/ttyUSB0 -it {tag} door.yaml run", pty=True)
+# config_skylight.yaml run --no-logs
+
+@task
+def monitor_usb(ctx):
+    tag = 'esphome/esphome'
+    ctx.run(f"docker run --rm -v `pwd`:/config --device=/dev/ttyUSB0 -it {tag} door.yaml logs", pty=True)
+
+@task
+def tail_mqtt(ctx):
+    ctx.run(f'mosquitto_sub -h bang -p 10010 -d -v -t \#')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/reasoning/serv.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,23 @@
+@prefix : <http://bigasterisk.com/ns/serv#> .
+@prefix auth: <http://bigasterisk.com/ns/serv/auth#> .
+@prefix serv: <http://bigasterisk.com/services/> .
+
+
+serv:reasoning a :Service;
+      :path "/reasoning/";
+      :openid auth:admin;
+      :serverHost "bang";
+      :internalPort 9071;
+      :prodDockerFlags (
+      "-p" "9071:9071"
+      "--net=host"
+      );
+      :localDockerFlags (
+        "-v" "`pwd`:/opt"
+      );
+      :localRunCmdline (
+        "python3" "reasoning.py" "-v"
+      );
+      :dockerFile "Dockerfile"
+.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/store/serv.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,22 @@
+@prefix : <http://bigasterisk.com/ns/serv#> .
+@prefix auth: <http://bigasterisk.com/ns/serv/auth#> .
+@prefix serv: <http://bigasterisk.com/services/> .
+
+serv:store a :Service;
+      :path "/store/";
+      :openid auth:admin;
+      :serverHost "bang";
+      :internalPort 10015;
+      :prodDockerFlags (
+      "-p" "10015:10015"
+      "--net=host"
+      );
+      :localDockerFlags (
+        "-v" "`pwd`:/opt"
+      );
+      :localRunCmdline (
+        "python3" "store.py" "-v"
+      );
+      :dockerFile "Dockerfile"
+.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/wifi/serv.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,22 @@
+@prefix : <http://bigasterisk.com/ns/serv#> .
+@prefix auth: <http://bigasterisk.com/ns/serv/auth#> .
+@prefix serv: <http://bigasterisk.com/services/> .
+
+
+serv:wifi a :Service;
+      :path "/wifi/";
+      :openid auth:admin;
+      :serverHost "bang";
+      :internalPort 9070;
+      :prodDockerFlags (
+      "-p" "9070:9070"
+      "--net=host"
+      );
+      :localDockerFlags (
+        "-v" "`pwd`:/opt"
+      );
+      :localRunCmdline (
+        "python3" "wifi.py" "-v"
+      );
+      :dockerFile "Dockerfile"
+.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/xidle/serv.n3	Thu Feb 06 16:36:35 2020 -0800
@@ -0,0 +1,23 @@
+@prefix : <http://bigasterisk.com/ns/serv#> .
+@prefix auth: <http://bigasterisk.com/ns/serv/auth#> .
+@prefix serv: <http://bigasterisk.com/services/> .
+
+
+serv:xidle a :Service;
+      :path "/xidle/";
+      :openid auth:admin;
+      :serverHost "bang";
+      :internalPort 9107;
+      :prodDockerFlags (
+      "-p" "9107:9107"
+      "--net=host"
+      );
+      :localDockerFlags (
+        "-v" "`pwd`:/opt"
+      );
+      :localRunCmdline (
+        "python3" "xidle.py" "-v"
+      );
+      :dockerFile "Dockerfile"
+.
+