# HG changeset patch # User drewp@bigasterisk.com # Date 1706557963 28800 # Node ID 24a5741083658a2143da0d8a15544ef4b9c131d2 # Parent 4e350dcdc4feb43de9306e0df163aef65357c428 more protocols; bugs in setColor diff -r 4e350dcdc4fe -r 24a574108365 color_convert.py --- a/color_convert.py Mon Jan 29 11:50:28 2024 -0800 +++ b/color_convert.py Mon Jan 29 11:52:43 2024 -0800 @@ -15,6 +15,8 @@ w: float = 0 x: float = 0 y: float = 0 + cw: float = 0 + ww: float = 0 brightness: float = 0 def summary(self) -> dict: @@ -23,3 +25,12 @@ # fix this to send what z2m likes def zbConv(c: Color) -> DeviceColor: return DeviceColor(r=c.r, g=c.g, b=c.b, brightness=max(c.r, c.g, c.b)) + +def oneWhiteConv(c: Color) -> DeviceColor: + return DeviceColor(r=c.r, g=c.g, b=c.b, w=max(c.r, c.g, c.b)) + +def twoWhitesConv(c: Color) -> DeviceColor: + return DeviceColor(r=c.r, g=c.g, b=c.b, cw=max(c.r, c.g, c.b)) + +def relayConv(c: Color) -> DeviceColor: + return DeviceColor(brightness=1 if (max(c.r, c.g, c.b) > 0) else 0) \ No newline at end of file diff -r 4e350dcdc4fe -r 24a574108365 light.py --- a/light.py Mon Jan 29 11:50:28 2024 -0800 +++ b/light.py Mon Jan 29 11:52:43 2024 -0800 @@ -4,11 +4,11 @@ from typing import Callable from color import Color -from color_convert import DeviceColor, zbConv +from color_convert import DeviceColor, oneWhiteConv, relayConv, twoWhitesConv, zbConv from mqtt_io import MqttIo -from protocols import Transport, ZigbeeTransport +from protocols import ShellyGen1WebTransport, SonoffRelayTransport, TasmotaWebTransport, Transport, ZigbeeTransport -log = logging.getLogger('light') +log = logging.getLogger('lite') @dataclass @@ -43,18 +43,21 @@ return {'light': d} async def setColor(self, c: Color): - log.info(f'setColor from {self.requestingColor} to {c}') if c == self.requestingColor: return self.requestingColor = c - self.requestingDeviceColor = self.convertColor(self.requestingColor) + dc = self.convertColor(self.requestingColor) + + log.info(f'setColor from {self.requestingColor} to {c} = {dc.summary()=}') + if dc != self.requestingDeviceColor: + self.requestingDeviceColor = dc - if self.notifyChanged: - self.notifyChanged() + if self.notifyChanged: + self.notifyChanged() - # waits for the relevant round-trip - log.info(f'transport send {self.requestingDeviceColor.summary()}') - await self.transport.send(self.requestingDeviceColor) + # waits for the relevant round-trip + log.info(f'transport send {self.requestingDeviceColor.summary()}') + await self.transport.send(self.requestingDeviceColor) self.emittingColor = self.requestingColor if self.notifyChanged: @@ -65,6 +68,18 @@ return Light(name=name, convertColor=zbConv, transport=ZigbeeTransport(mqtt, name, ieee)) +def makeTasmota(name: str, hostname: str) -> Light: + return Light(name=name, convertColor=twoWhitesConv, transport=TasmotaWebTransport(hostname)) + + +def makeShellyRGW2(name: str, hostname: str) -> Light: + return Light(name=name, convertColor=oneWhiteConv, transport=ShellyGen1WebTransport(hostname)) + + +def makeSonoffRelay(mqtt: MqttIo, name: str, topic: str) -> Light: + return Light(name=name, convertColor=relayConv, transport=SonoffRelayTransport(mqtt, topic)) + + class Lights: _d: dict[str, Light] = {} @@ -72,6 +87,34 @@ self.mqtt = mqtt self.add(makeZbBar(mqtt, 'do-bar', '0xa4c13844948d2da4')) + self.add(makeTasmota('do-lamp', 'tasmota-9E2AB7-2743')) + self.add(makeShellyRGW2('ki-ceiling', 'shellyrgbw2-e868e7f34c35')) + self.add(makeShellyRGW2('ki-counter', 'shellyrgbw2-e868e7f34cb2')) + # br-floor online=online | metric=1 (graph) lqi=93 bright=12 + # br-wall online=online | metric=1 (graph) lqi=30 bright=0 + + # en online=online | metric=1 (graph) lqi=36 bright=216 + # ft-ceil online=online | metric=1 (graph) lqi=96 bright=252 + # go-high online=online | metric=1 (graph) lqi=93 bright=253 + # li-toys online=online | metric=1 (graph) lqi=96 bright=28 + # py online=online | metric=1 (graph) lqi=93 bright=216 + # rr-lamp online=offline | metric=0 (graph) lqi=... bright=... + # sh-top online=online | metric=1 (graph) lqi=93 bright=254 + # ws-hanging online=online | metric=1 (graph) lqi=39 bright=... + # light-li-high-shelf uptimesec=2232334 wifi=-65 dim=100 color=0000002FD0 + # light-sh uptimesec=... wifi=... dim=... color=... + # light-tr-wall uptimesec=5082251 wifi=-38 dim=34 color=5700000000 + + # wled-ft-hanging offline #FFA000 bright=128 + # string-tr online #8CFF82 bright=128 + # light-tr-ball online #808A03 bright=12 + # string-hr online #C9FFDC bright=131 + + self.add(makeSonoffRelay(mqtt, 'li-lamp0', 'sonoff_0')) + self.add(makeSonoffRelay(mqtt, 'li-lamp1', 'sonoff_1')) + self.add(makeSonoffRelay(mqtt, 'li-lamp2', 'sonoff_2')) + self.add(makeSonoffRelay(mqtt, 'li-lamp3', 'sonoff_3')) + self.add(makeSonoffRelay(mqtt, 'li-lamp4', 'sonoff_4')) def add(self, d: Light): d.notifyChanged = self.notifyChanged diff -r 4e350dcdc4fe -r 24a574108365 protocols.py --- a/protocols.py Mon Jan 29 11:50:28 2024 -0800 +++ b/protocols.py Mon Jan 29 11:52:43 2024 -0800 @@ -1,7 +1,10 @@ +import logging import json +import aiohttp from color_convert import DeviceColor from mqtt_io import MqttIo +log = logging.getLogger('prot') class Transport: @@ -27,7 +30,57 @@ self.ieee = ieee def linked(self): - return {'url': f'https://bigasterisk.com/zigbee/console/#/device/{self.ieee}/info', 'label': 'do-bar'} + return {'url': f'https://bigasterisk.com/zigbee/console/#/device/{self.ieee}/info', 'label': self.name} async def send(self, dc: DeviceColor): await self.mqtt.publish(f'zigbee/{self.name}/set', json.dumps(zigbeeHexMessage(dc))) + + +class SonoffRelayTransport(Transport): + + def __init__(self, mqtt: MqttIo, name: str): + self.mqtt = mqtt + self.name = name + + def linked(self): + return {'label': self.name} + + async def send(self, dc: DeviceColor): + topic = f'{self.name}/switch/sonoff_basic_relay/command' + msg = 'ON' if dc.brightness else 'OFF' + log.info(f'sonoff {topic=} {msg=}') + await self.mqtt.publish(topic, msg) + + +class _WebTransport(Transport): + + def __init__(self, hostname: str): + self.hostname = hostname + self._session = aiohttp.ClientSession() + + def linked(self): + return {'url': f'http://{self.hostname}/', 'label': self.hostname} + + +class TasmotaWebTransport(_WebTransport): + + async def send(self, dc: DeviceColor): + cmnd = 'Color ' + ','.join(str(int(x * 255)) for x in (dc.r, dc.g, dc.b, dc.cw, dc.ww)) + async with self._session.get(f'http://{self.hostname}/cm', params={'cmnd': cmnd}) as resp: + await resp.text() + # {"POWER":"ON","Dimmer":21,"Color":"3636363600","HSBColor":"0,0,21","White":21,"CT":153,"Channel":[21,21,21,21,0]} + + +class ShellyGen1WebTransport(_WebTransport): + + async def send(self, dc: DeviceColor): + # also see https://shelly-api-docs.shelly.cloud/gen1/#shelly-rgbw2-color-status for metrics + async with self._session.get(f'http://{self.hostname}/light/0', + params={ + 'red': int(dc.r * 255), + 'green': int(dc.g * 255), + 'blue': int(dc.b * 255), + 'white': int(dc.w * 255), + }) as resp: + await resp.text() + # {..."mode":"color","red":255,"green":242,"blue":0,"white":255,"gain":59,"effect":0,"transition":0,"power":18.00,"overpower":false}