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
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
2
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
1 import asyncio
14
e3dbd04dab96 add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents: 13
diff changeset
2 import json
2
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
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
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
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
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
10 log = logging.getLogger('light')
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
11
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
12
9
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
13 @dataclass(frozen=True)
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
14 class DeviceColor:
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
15 """neutral representation of the adjusted color that we send to a device"""
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
16 r: float = 0
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
17 g: float = 0
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
18 b: float = 0
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
19 w: float = 0
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
20 x: float = 0
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
21 y: float = 0
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
22
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
23 def summary(self) -> dict:
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
24 return dict([(k, round(v, 3)) for k, v in self.__dict__.items() if v > 0])
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
25
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
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
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
59 @dataclass
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
60 class Light:
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
61 name: str
14
e3dbd04dab96 add mqtt; talk to first light (no throttling)
drewp@bigasterisk.com
parents: 13
diff changeset
62 address: Transport
9
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
63
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
64 requestingColor: Color = Color.fromHex('#000000')
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
65 requestingDeviceColor: DeviceColor = DeviceColor()
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
66
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
67 emittingColor: Color = Color.fromHex('#000000')
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
68 online: bool | None = None
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
69 latencyMs: float | None = None
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
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
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
73 def __post_init__(self):
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
74 self.requestingDeviceColor = self.deviceColor(self.requestingColor)
2
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
75
5
7eeda7f4f9cd spell it to_dict, for compat with DataClassJsonMixin
drewp@bigasterisk.com
parents: 4
diff changeset
76 def to_dict(self):
9
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
77 d = {
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
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
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
80 'requestingColor': self.requestingColor.hex(),
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
81 'requestingDeviceColor': self.requestingDeviceColor.summary(),
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
82 'emittingColor': self.emittingColor.hex(),
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
83 'online': self.online,
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
84 'latencyMs': self.latencyMs,
2
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
85 }
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
86
9
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
87 return {'light': d}
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
88
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
89 def deviceColor(self, c: Color) -> DeviceColor:
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
90 # do LUT here
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
91 return DeviceColor(r=c.r, g=c.g, b=c.b)
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
92
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
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
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
97 self.requestingColor = c
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
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
9f427d8073c3 redo data model; add ui colors
drewp@bigasterisk.com
parents: 6
diff changeset
110
2
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
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
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
116 class Lights:
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
117 _d: dict[str, Light] = {}
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
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
fc8ed0efcd72 move init to Lights
drewp@bigasterisk.com
parents: 5
diff changeset
122
2
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
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
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
125 self._d[d.name] = d
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
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
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
129 def byName(self, name: str) -> Light:
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
130 return self._d[name]
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
131
12
7cc004eafb82 refactor (approx)
drewp@bigasterisk.com
parents: 11
diff changeset
132 def to_dict(self):
7cc004eafb82 refactor (approx)
drewp@bigasterisk.com
parents: 11
diff changeset
133 return {'lights': [d.to_dict() for d in sorted(self._d.values(), key=lambda r: r.name)]}
7cc004eafb82 refactor (approx)
drewp@bigasterisk.com
parents: 11
diff changeset
134
7cc004eafb82 refactor (approx)
drewp@bigasterisk.com
parents: 11
diff changeset
135 # the following is bad. Get a better events lib.
7cc004eafb82 refactor (approx)
drewp@bigasterisk.com
parents: 11
diff changeset
136 _changeSleepTask: asyncio.Task | None = None
7cc004eafb82 refactor (approx)
drewp@bigasterisk.com
parents: 11
diff changeset
137
2
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
138 async def changes(self): # yields None on any data change
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
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
c6fd04e07db0 refactor light.py
drewp@bigasterisk.com
parents:
diff changeset
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()