1539
|
1 """
|
|
2 We get output statements that are like light9's deviceAttrs (:dev1 :color "#ff0000"),
|
|
3 convert those to outputAttrs (:dev1 :red 255; :green 0; :blue 0) and post them to mqtt.
|
|
4
|
|
5 This is like light9/bin/collector.
|
|
6 """
|
|
7 import json
|
|
8 from mqtt_client import MqttClient
|
|
9 from docopt import docopt
|
|
10 from rdflib import Namespace, Literal
|
|
11 from twisted.internet import reactor
|
|
12 import cyclone.web
|
|
13
|
|
14 from patchablegraph import PatchableGraph, CycloneGraphHandler, CycloneGraphEventsHandler
|
|
15 from standardservice.logsetup import log, verboseLogging
|
|
16 import rdf_over_http
|
|
17
|
|
18 ROOM = Namespace('http://projects.bigasterisk.com/room/')
|
|
19
|
|
20 devs = {
|
|
21 ROOM['kitchenLight']: {
|
|
22 'root': 'h801_skylight',
|
|
23 'ctx': ROOM['kitchenH801']
|
|
24 },
|
|
25 ROOM['kitchenCounterLight']: {
|
|
26 'root': 'h801_counter',
|
|
27 'ctx': ROOM['kitchenH801']
|
|
28 },
|
|
29 ROOM['livingLampShelf']: {
|
|
30 'root': 'sonoff_0/switch/sonoff_basic_relay/command',
|
|
31 'ctx': ROOM['sonoff_0'],
|
|
32 'values': 'binary',
|
|
33 },
|
1540
|
34 ROOM['livingLamp1']: {
|
|
35 'root': 'sonoff_1/switch/sonoff_basic_relay/command',
|
|
36 'ctx': ROOM['sonoff_1'],
|
|
37 'values': 'binary',
|
|
38 },
|
|
39 ROOM['livingLamp2']: {
|
|
40 'root': 'sonoff_2/switch/sonoff_basic_relay/command',
|
|
41 'ctx': ROOM['sonoff_2'],
|
|
42 'values': 'binary',
|
|
43 },
|
|
44 ROOM['livingLamp3']: {
|
|
45 'root': 'sonoff_3/switch/sonoff_basic_relay/command',
|
|
46 'ctx': ROOM['sonoff_3'],
|
|
47 'values': 'binary',
|
|
48 },
|
|
49 ROOM['livingLamp4']: {
|
|
50 'root': 'sonoff_4/switch/sonoff_basic_relay/command',
|
|
51 'ctx': ROOM['sonoff_4'],
|
|
52 'values': 'binary',
|
|
53 },
|
|
54 ROOM['livingLamp5']: {
|
|
55 'root': 'sonoff_5/switch/sonoff_basic_relay/command',
|
|
56 'ctx': ROOM['sonoff_5'],
|
|
57 'values': 'binary',
|
|
58 },
|
|
59 #-t theater_blaster/ir_out -m 'input_game'
|
|
60 #-t theater_blaster/ir_out -m 'input_bd'
|
|
61 #-t theater_blaster/ir_out -m 'input_cbl'
|
|
62 #-t theater_blaster/ir_out -m 'input_pc'
|
|
63 #-t theater_blaster/ir_out/volume_up -m '{"times":1}'
|
|
64 #-t theater_blaster/ir_out/volume_down -m '{"times":1}'
|
1539
|
65 }
|
|
66
|
|
67
|
|
68 class OutputPage(cyclone.web.RequestHandler):
|
|
69 def put(self):
|
|
70 for stmt in rdf_over_http.rdfStatementsFromRequest(
|
|
71 self.request.arguments,
|
|
72 self.request.body,
|
|
73 self.request.headers):
|
|
74 self._onStatement(stmt)
|
|
75
|
|
76 def _onStatement(self, stmt):
|
|
77 log.info(f'incoming statement: {stmt}')
|
|
78 ignored = True
|
|
79 for dev, attrs in devs.items():
|
|
80 if stmt[0] == ROOM['frontWindow']:
|
|
81 ignored = ignored and self._publishFrontScreenText(stmt)
|
|
82
|
|
83 if stmt[0:2] == (dev, ROOM['brightness']):
|
|
84 log.info(f'brightness request: {stmt}')
|
|
85 brightness = stmt[2].toPython()
|
|
86
|
|
87 if attrs.get('values', '') == 'binary':
|
|
88 self._publishOnOff(attrs, brightness)
|
|
89 else:
|
|
90 self._publishRgbw(attrs, brightness)
|
|
91 ignored = False
|
|
92 if ignored:
|
|
93 log.warn("ignoring %s", stmt)
|
|
94
|
|
95 def _publishOnOff(self, attrs, brightness):
|
|
96 msg = 'OFF'
|
|
97 if brightness > 0:
|
|
98 msg = 'ON'
|
|
99 self._publish(topic=attrs['root'], message=msg)
|
|
100
|
|
101 def _publishRgbw(self, attrs, brightness):
|
|
102 for chan, scale in [('w1', 1),
|
|
103 ('r', 1),
|
|
104 ('g', .8),
|
|
105 ('b', .8)]:
|
|
106 self._publish(
|
|
107 topic=f"{attrs['root']}/light/kit_{chan}/command",
|
|
108 messageJson={
|
|
109 'state': 'ON',
|
|
110 'brightness': int(brightness * 255)
|
|
111 })
|
|
112
|
|
113 def _publishFrontScreenText(self, stmt):
|
|
114 ignored = True
|
|
115 for line in ['line1', 'line2', 'line3', 'line4']:
|
|
116 if stmt[1] == ROOM[line]:
|
|
117 ignored = False
|
|
118 self.settings.mqtt.publish(
|
|
119 b'frontwindow/%s' % line.encode('ascii'),
|
|
120 stmt[2].toPython())
|
|
121 return ignored
|
|
122
|
|
123 def _publish(self, topic: str, messageJson: object=None,
|
|
124 message: str=None):
|
|
125 if messageJson is not None:
|
|
126 message = json.dumps(messageJson)
|
|
127 self.settings.mqtt.publish(
|
|
128 topic.encode('ascii'),
|
|
129 message.encode('ascii'))
|
|
130
|
|
131
|
|
132 if __name__ == '__main__':
|
|
133 arg = docopt("""
|
|
134 Usage: rdf_to_mqtt.py [options]
|
|
135
|
|
136 -v Verbose
|
|
137 """)
|
|
138 verboseLogging(arg['-v'])
|
|
139
|
|
140 mqtt = MqttClient(clientId='rdf_to_mqtt', brokerPort=1883)
|
|
141
|
|
142 port = 10008
|
|
143 reactor.listenTCP(port, cyclone.web.Application([
|
|
144 (r"/()", cyclone.web.StaticFileHandler,
|
|
145 {"path": ".", "default_filename": "index.html"}),
|
|
146 (r'/output', OutputPage),
|
|
147 ], mqtt=mqtt, debug=arg['-v']), interface='::')
|
|
148 log.warn('serving on %s', port)
|
|
149
|
|
150 reactor.run()
|