Mercurial > code > home > repos > light-bridge
annotate light.py @ 14:e3dbd04dab96
add mqtt; talk to first light (no throttling)
author | drewp@bigasterisk.com |
---|---|
date | Sun, 28 Jan 2024 20:49:42 -0800 |
parents | 1c865af058e7 |
children | 61d4ccecfed8 |
rev | line source |
---|---|
2 | 1 import asyncio |
14
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
2 import json |
2 | 3 import logging |
13
1c865af058e7
start make* funcs and add links to light addresses
drewp@bigasterisk.com
parents:
12
diff
changeset
|
4 from dataclasses import dataclass |
11
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
5 from typing import Callable |
2 | 6 |
5
7eeda7f4f9cd
spell it to_dict, for compat with DataClassJsonMixin
drewp@bigasterisk.com
parents:
4
diff
changeset
|
7 from color import Color |
14
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
8 from mqtt_io import MqttIo |
5
7eeda7f4f9cd
spell it to_dict, for compat with DataClassJsonMixin
drewp@bigasterisk.com
parents:
4
diff
changeset
|
9 |
2 | 10 log = logging.getLogger('light') |
11 | |
12 | |
9 | 13 @dataclass(frozen=True) |
14 class DeviceColor: | |
15 """neutral representation of the adjusted color that we send to a device""" | |
16 r: float = 0 | |
17 g: float = 0 | |
18 b: float = 0 | |
19 w: float = 0 | |
20 x: float = 0 | |
21 y: float = 0 | |
22 | |
23 def summary(self) -> dict: | |
24 return dict([(k, round(v, 3)) for k, v in self.__dict__.items() if v > 0]) | |
25 | |
26 | |
14
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
27 class Transport: |
13
1c865af058e7
start make* funcs and add links to light addresses
drewp@bigasterisk.com
parents:
12
diff
changeset
|
28 |
1c865af058e7
start make* funcs and add links to light addresses
drewp@bigasterisk.com
parents:
12
diff
changeset
|
29 def linked(self): |
1c865af058e7
start make* funcs and add links to light addresses
drewp@bigasterisk.com
parents:
12
diff
changeset
|
30 return {'label': str(self)} |
1c865af058e7
start make* funcs and add links to light addresses
drewp@bigasterisk.com
parents:
12
diff
changeset
|
31 |
14
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
32 async def send(self, dc: DeviceColor): |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
33 raise TypeError |
13
1c865af058e7
start make* funcs and add links to light addresses
drewp@bigasterisk.com
parents:
12
diff
changeset
|
34 |
1c865af058e7
start make* funcs and add links to light addresses
drewp@bigasterisk.com
parents:
12
diff
changeset
|
35 |
14
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
36 def zigbeeHexMessage(color: DeviceColor, bw=False) -> dict: |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
37 bright = max(color.r, color.g, color.b) |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
38 msg: dict = {"transition": 0, "brightness": int(255 * bright)} |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
39 if not bw: |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
40 c = "#%02x%02x%02x" % (int(color.r * 255), int(color.g * 255), int(color.b * 255)) |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
41 msg["color"] = {"hex": c} |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
42 return msg |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
43 |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
44 |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
45 class ZigbeeTransport(Transport): |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
46 |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
47 def __init__(self, mqtt: MqttIo, name: str, ieee: str): |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
48 self.mqtt = mqtt |
13
1c865af058e7
start make* funcs and add links to light addresses
drewp@bigasterisk.com
parents:
12
diff
changeset
|
49 self.name = name |
1c865af058e7
start make* funcs and add links to light addresses
drewp@bigasterisk.com
parents:
12
diff
changeset
|
50 self.ieee = ieee |
1c865af058e7
start make* funcs and add links to light addresses
drewp@bigasterisk.com
parents:
12
diff
changeset
|
51 |
1c865af058e7
start make* funcs and add links to light addresses
drewp@bigasterisk.com
parents:
12
diff
changeset
|
52 def linked(self): |
1c865af058e7
start make* funcs and add links to light addresses
drewp@bigasterisk.com
parents:
12
diff
changeset
|
53 return {'url': f'https://bigasterisk.com/zigbee/console/#/device/{self.ieee}/info', 'label': 'do-bar'} |
1c865af058e7
start make* funcs and add links to light addresses
drewp@bigasterisk.com
parents:
12
diff
changeset
|
54 |
14
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
55 async def send(self, dc: DeviceColor): |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
56 await self.mqtt.publish(f'zigbee/{self.name}/set', json.dumps(zigbeeHexMessage(dc, bw=False))) |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
57 |
13
1c865af058e7
start make* funcs and add links to light addresses
drewp@bigasterisk.com
parents:
12
diff
changeset
|
58 |
2 | 59 @dataclass |
60 class Light: | |
61 name: str | |
14
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
62 address: Transport |
9 | 63 |
64 requestingColor: Color = Color.fromHex('#000000') | |
65 requestingDeviceColor: DeviceColor = DeviceColor() | |
66 | |
67 emittingColor: Color = Color.fromHex('#000000') | |
68 online: bool | None = None | |
69 latencyMs: float | None = None | |
70 | |
11
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
71 notifyChanged: Callable | None = None |
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
72 |
9 | 73 def __post_init__(self): |
74 self.requestingDeviceColor = self.deviceColor(self.requestingColor) | |
2 | 75 |
5
7eeda7f4f9cd
spell it to_dict, for compat with DataClassJsonMixin
drewp@bigasterisk.com
parents:
4
diff
changeset
|
76 def to_dict(self): |
9 | 77 d = { |
78 'name': self.name, | |
13
1c865af058e7
start make* funcs and add links to light addresses
drewp@bigasterisk.com
parents:
12
diff
changeset
|
79 'address': self.address.linked(), |
9 | 80 'requestingColor': self.requestingColor.hex(), |
81 'requestingDeviceColor': self.requestingDeviceColor.summary(), | |
82 'emittingColor': self.emittingColor.hex(), | |
83 'online': self.online, | |
84 'latencyMs': self.latencyMs, | |
2 | 85 } |
86 | |
9 | 87 return {'light': d} |
88 | |
89 def deviceColor(self, c: Color) -> DeviceColor: | |
90 # do LUT here | |
91 return DeviceColor(r=c.r, g=c.g, b=c.b) | |
92 | |
93 async def setColor(self, c: Color): | |
11
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
94 log.info(f'setColor from {self.requestingColor} to {c}') |
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
95 if c == self.requestingColor: |
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
96 return |
9 | 97 self.requestingColor = c |
98 self.requestingDeviceColor = self.deviceColor(self.requestingColor) | |
14
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
99 |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
100 if self.notifyChanged: |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
101 self.notifyChanged() |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
102 |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
103 # waits for the relevant round-trip |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
104 log.info(f'transport send {self.requestingDeviceColor}') |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
105 await self.address.send(self.requestingDeviceColor) |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
106 |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
107 self.emittingColor = self.requestingColor |
11
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
108 if self.notifyChanged: |
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
109 self.notifyChanged() |
9 | 110 |
2 | 111 |
14
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
112 def makeZbBar(mqtt: MqttIo, name: str, ieee: str) -> Light: |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
113 return Light(name=name, address=ZigbeeTransport(mqtt, name, ieee)) |
13
1c865af058e7
start make* funcs and add links to light addresses
drewp@bigasterisk.com
parents:
12
diff
changeset
|
114 |
1c865af058e7
start make* funcs and add links to light addresses
drewp@bigasterisk.com
parents:
12
diff
changeset
|
115 |
2 | 116 class Lights: |
117 _d: dict[str, Light] = {} | |
118 | |
14
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
119 def __init__(self, mqtt: MqttIo): |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
120 self.mqtt = mqtt |
e3dbd04dab96
add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents:
13
diff
changeset
|
121 self.add(makeZbBar(mqtt, 'do-bar', '0xa4c13844948d2da4')) |
6 | 122 |
2 | 123 def add(self, d: Light): |
11
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
124 d.notifyChanged = self.notifyChanged |
2 | 125 self._d[d.name] = d |
126 | |
11
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
127 self.notifyChanged() |
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
128 |
2 | 129 def byName(self, name: str) -> Light: |
130 return self._d[name] | |
131 | |
12 | 132 def to_dict(self): |
133 return {'lights': [d.to_dict() for d in sorted(self._d.values(), key=lambda r: r.name)]} | |
134 | |
135 # the following is bad. Get a better events lib. | |
136 _changeSleepTask: asyncio.Task | None = None | |
137 | |
2 | 138 async def changes(self): # yields None on any data change |
139 while True: | |
11
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
140 log.info('Lights has a change') |
2 | 141 yield None |
11
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
142 self._changeSleepTask = asyncio.create_task(self._sleep()) |
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
143 try: |
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
144 await self._changeSleepTask |
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
145 except asyncio.CancelledError: |
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
146 pass |
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
147 self._changeSleepTask = None |
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
148 |
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
149 async def _sleep(self): |
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
150 await asyncio.sleep(100) |
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
151 |
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
152 def notifyChanged(self): |
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
153 log.info('Lights.notifyChanged()') |
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
154 if self._changeSleepTask is not None: |
028ed39aa78f
more ts types; attempted multiplayer but the change events are managed wrong
drewp@bigasterisk.com
parents:
9
diff
changeset
|
155 self._changeSleepTask.cancel() |