diff --git a/espmusicmouse/host_driver/mqtt.py b/espmusicmouse/host_driver/mqtt.py deleted file mode 100644 index 606612d..0000000 --- a/espmusicmouse/host_driver/mqtt.py +++ /dev/null @@ -1,155 +0,0 @@ -from led_cmds import ColorRGBW, EffectStaticConfig, EffectCircularConfig, EffectRandomTwoColorInterpolationConfig, EffectAlexaSwipeConfig, EffectSwipeAndChange -import asyncio -import asyncio_mqtt -import json - -BRIGHTNESS_SCALE = 1 # power supply is a bit weak -> scale brightness down globally - - -class ShelveLightMqtt: - def __init__(self, protocol, client): - self._brightness = 100 - self._protocol = protocol - self._mqtt_client = client - - self._brightness = 0 - self._color = ColorRGBW(0.5, 0.5, 0.5, 0) - self._last_color = ColorRGBW(0.5, 0.5, 0.5, 0) - self._original_color_repr = (127, 127, 127) - self._effect = "static" - - self._discovery_spec = self._create_discovery_msg_light() - - async def init(self): - self._protocol.shelve_led_effect(EffectStaticConfig(ColorRGBW(0, 0, 0, 0))) - await self._notify_mqtt_brightness() - await self._notify_mqtt_state() - await self._notify_mqtt_rgb() - await self._send_autodiscovery_msg() - - async def _send_autodiscovery_msg(self): - topic = f"homeassistant/light/{self._discovery_spec['unique_id']}/config" - await self._mqtt_client.publish(topic, json.dumps(self._discovery_spec).encode()) - - async def _notify_mqtt_rgb(self): - rgb_payload = ",".join(str(e) for e in self._original_color_repr) - print("OUT ", "rgb", rgb_payload) - await self._mqtt_client.publish(self._discovery_spec['rgb_state_topic'], - rgb_payload.encode()) - - async def _notify_mqtt_state(self): - state_payload = "ON" if self._brightness > 0 else "OFF" - print("OUT ", "state", state_payload) - await self._mqtt_client.publish(self._discovery_spec['state_topic'], state_payload.encode()) - - async def _notify_mqtt_brightness(self): - brightness_payload = str(int(self._brightness * 255)) - print("OUT ", "brightness", brightness_payload) - await self._mqtt_client.publish(self._discovery_spec['brightness_state_topic'], - brightness_payload.encode()) - - async def _notify_mqtt_shelve_effect(self, effect): - await self._mqtt_client.publish(self._discovery_spec['effect_state_topic'], effect.encode()) - - def _set_rgb_color(self, color: ColorRGBW): - if self._color != self._last_color: # mqtt sends color multiple times, we want to remember last distinct color as second color for effects - self._last_color = self._color - self._color = color * BRIGHTNESS_SCALE * self._brightness - - def _update_device(self): - if self._effect == "static": - eff = EffectStaticConfig(self._color) - elif self._effect == "circular": - eff = EffectCircularConfig() - eff.color = self._color - elif self._effect == "wipeup": - eff = EffectSwipeAndChange() - eff.swipe.secondary_color = self._color - eff.swipe.primary_color = self._last_color - eff.swipe.bell_curve_width_in_leds = 5 - eff.swipe.swipe_speed = 180 - eff.change.color1 = self._color - eff.change.color2 = self._last_color - elif self._effect == "twocolor": - eff = EffectRandomTwoColorInterpolationConfig() - eff.color1 = self._color - eff.color2 = self._last_color - eff.start_with_existing = True - elif self._effect == "twocolorrandom": - eff = EffectRandomTwoColorInterpolationConfig() - eff.color1 = self._color - eff.color2 = self._last_color - eff.hue1_random = True - eff.hue2_random = True - eff.start_with_existing = True - else: - print(f"Unknown effect {self._effect}") - eff = EffectStaticConfig(ColorRGBW(0, 0, 0, 0)) - self._protocol.shelve_led_effect(eff) - - async def handle_light_message(self, msg): - prefix = 'musicmouse/lights_shelve' - if not msg.topic.startswith(prefix): - return False - cmd = msg.topic.split("/")[-1] - payload = msg.payload.decode() - - print("IN ", cmd, payload) - if cmd == "rgb": - r, g, b = tuple(int(i) for i in payload.split(",")) - self._original_color_repr = (r, g, b) - self._set_rgb_color(ColorRGBW(r / 255, g / 255, b / 255, 0)) - await self._notify_mqtt_rgb() - elif cmd == "switch": - if payload == "ON" and self._brightness == 0: - self._brightness = 1 - elif payload == "OFF": - self._color = ColorRGBW(0, 0, 0, 0) - self._brightness = 0 - self._update_device() - await self._notify_mqtt_rgb() - await self._notify_mqtt_brightness() - await self._notify_mqtt_state() - elif cmd == "brightness": - self._brightness = int(payload) / 255 - self._set_rgb_color(self._color) - await self._notify_mqtt_brightness() - elif cmd == "effect": - self._effect = payload - - @staticmethod - def _create_discovery_msg_light(base_name="musicmouse", display_name="Music Mouse Regal Licht"): - id = "shelve" - return { - 'name': display_name, - 'unique_id': f'{base_name}_{id}', - 'command_topic': f'{base_name}/lights_{id}/switch', - 'state_topic': f'{base_name}/lights_{id}/switch_state', - 'brightness_command_topic': f'{base_name}/lights_{id}/brightness', - 'brightness_state_topic': f'{base_name}/lights_{id}/brightness_state', - 'rgb_command_topic': f'{base_name}/lights_{id}/rgb', - 'rgb_state_topic': f'{base_name}/lights_{id}/rgb_state', - 'effect_command_topic': f'{base_name}/lights_{id}/effect', - 'effect_state_topic': f'{base_name}/lights_{id}/effect_state', - 'effect_list': ['static', 'circular', 'wipeup', 'twocolor', 'twocolorrandom'], - } - - -async def start_mqtt(music_mouse_protocol, server, username, password): - async with asyncio_mqtt.Client(hostname=server, username=username, password=password) as client: - shelve_light = ShelveLightMqtt(music_mouse_protocol, client) - await shelve_light.init() - async with client.filtered_messages("musicmouse/#") as messages: - await client.subscribe("musicmouse/#") - async for message in messages: - await shelve_light.handle_light_message(message) - - -if __name__ == "__main__": - - class DummyProtocol: - def shelve_led_effect(self, effect): - print("EFF ", repr(effect)) - - password = "KNLEFLZF94yA6Zhj141" - asyncio.run(start_mqtt(DummyProtocol(), "homeassistant", "musicmouse", password)) diff --git a/espmusicmouse/host_driver/mqtt_json.py b/espmusicmouse/host_driver/mqtt_json.py index 7cdc6f9..04a0426 100644 --- a/espmusicmouse/host_driver/mqtt_json.py +++ b/espmusicmouse/host_driver/mqtt_json.py @@ -63,12 +63,12 @@ class ShelveLightMqtt: def _update_device(self): s = self._state current_color = self._color_from_json(s['color'], brightness=s["brightness"]) - transition = s.get("transition", 0.0) * 1000 + transition = s.get("transition", 0.3) * 1000 print(f"Effect {s['effect']} Transition {transition}") if s['state'] == "OFF": if transition > 0: - eff = EffectStaticDetailedConfig(ColorRGBW(0,0,0,0), transition_tim_in_ms=transition) + eff = EffectStaticDetailedConfig(ColorRGBW(0,0,0,0), transition_time_in_ms=transition) else: eff = EffectStaticConfig(ColorRGBW(0, 0, 0, 0)) elif s['effect'] == 'static': @@ -104,10 +104,20 @@ class ShelveLightMqtt: eff = EffectStaticDetailedConfig(current_color, begin=0.9, end=0.1, increment=1, transition_time_in_ms=transition) elif s['effect'] == "side_0.2_inc4": eff = EffectStaticDetailedConfig(current_color, begin=0.9, end=0.1, increment=4, transition_time_in_ms=transition) + elif s['effect'] == "side_0.2_inc8": + eff = EffectStaticDetailedConfig(current_color, begin=0.9, end=0.1, increment=8, transition_time_in_ms=transition) elif s['effect'] == "side_0.5": eff = EffectStaticDetailedConfig(current_color, begin=0.75, end=0.25, increment=1, transition_time_in_ms=transition) elif s['effect'] == "side_0.5_inc4": eff = EffectStaticDetailedConfig(current_color, begin=0.75, end=0.25, increment=4, transition_time_in_ms=transition) + elif s['effect'] == "top_0.2": + eff = EffectStaticDetailedConfig(current_color, begin=0.4, end=0.6, increment=1, transition_time_in_ms=transition) + elif s['effect'] == "top_0.2_inc4": + eff = EffectStaticDetailedConfig(current_color, begin=0.4, end=0.6, increment=4, transition_time_in_ms=transition) + elif s['effect'] == "top_0.5": + eff = EffectStaticDetailedConfig(current_color, begin=0.25, end=0.75, increment=1, transition_time_in_ms=transition) + elif s['effect'] == "top_0.5_inc4": + eff = EffectStaticDetailedConfig(current_color, begin=0.25, end=0.75, increment=4, transition_time_in_ms=transition) else: print(f"Unknown effect {s['effect']}") eff = EffectStaticConfig(ColorRGBW(0, 0, 0, 0)) @@ -132,7 +142,8 @@ class ShelveLightMqtt: #}, 'effect': True, 'effect_list': ['static', 'circular', 'wipeup', 'twocolor', 'twocolorrandom', - "side_0.2", "side_0.5", "side_0.2_inc4", "side_0.5_inc4", "top_0.2"], + "side_0.2", "side_0.5", "side_0.2_inc4", "side_0.2_inc8", "side_0.5_inc4", + "top_0.2", "top_0.5", "top_0.2_inc4", "top_0.5_inc4"], 'supported_color_modes': ['rgbw'], } diff --git a/espmusicmouse/lib/ledtl/effects/StaticDetailed.h b/espmusicmouse/lib/ledtl/effects/StaticDetailed.h index d745440..e949020 100644 --- a/espmusicmouse/lib/ledtl/effects/StaticDetailed.h +++ b/espmusicmouse/lib/ledtl/effects/StaticDetailed.h @@ -36,6 +36,8 @@ public: beginIdx_ = constrain(static_cast(cfg.begin * NUM_LEDS + 0.5f), 0, NUM_LEDS - 1); endIdx_ = constrain(static_cast(cfg.end * NUM_LEDS + 0.5f), 0, NUM_LEDS - 1); + while (endIdx_ < beginIdx_) + endIdx_ += NUM_LEDS; } bool finished() const { return finished_; } @@ -45,22 +47,25 @@ public: if (finished_) return 1000000; - const float progress = static_cast(DELAY_MS * calls_) / config_.transition_time_in_ms; + const float progress = config_.transition_time_in_ms > 0.0f ? static_cast(DELAY_MS * calls_) / config_.transition_time_in_ms : 1.f; // Finished case - if(progress > 1.0) { + if (config_.transition_time_in_ms <= 0.0f || progress >= 1.0) + { finished_ = true; + clear(ledStrip_); + for (int i = beginIdx_; i < endIdx_; i += config_.increment) + setLedRGBW(ledStrip_, i % NUM_LEDS, config_.color); return 10000000; } // In-progress case clear(ledStrip_); - for(int i = beginIdx_; i != endIdx_; i += config_.increment) { - ColorRGBW newColor = hsv2rgb(interpolate(rgb2hsv(state_[i]), rgb2hsv(config_.color), progress)); - newColor.w = config_.color.w * progress + state_[i].w * (1 - progress); - setLedRGBW(ledStrip_, i, newColor); - if(i >= NUM_LEDS) - i = 0; + for (int i = beginIdx_; i < endIdx_; i += config_.increment) + { + const auto idx = i % NUM_LEDS; + ColorRGBW newColor = ColorRGBW::interpolate(state_[idx], config_.color, progress); + setLedRGBW(ledStrip_, idx, newColor); } ++calls_; @@ -68,6 +73,11 @@ public: } private: + static int int_interpolate(int prev, int next, float progress) + { + return static_cast(prev) * (1 - progress) + + static_cast(next) * progress; + } EffectStaticDetailedConfig config_; TLedStrip &ledStrip_; ColorRGBW state_[NUM_LEDS]; diff --git a/espmusicmouse/lib/ledtl/helpers/ColorRGBW.h b/espmusicmouse/lib/ledtl/helpers/ColorRGBW.h index f528cd7..edb625d 100644 --- a/espmusicmouse/lib/ledtl/helpers/ColorRGBW.h +++ b/espmusicmouse/lib/ledtl/helpers/ColorRGBW.h @@ -13,4 +13,14 @@ struct ColorRGBW uint8_t(s * b), uint8_t(s * w)}; } + + static inline ColorRGBW interpolate(const ColorRGBW &c1, const ColorRGBW &c2, float f) + { + return ColorRGBW{ + static_cast((1.0f - f) * static_cast(c1.r) + f * static_cast(c2.r)), + static_cast((1.0f - f) * static_cast(c1.g) + f * static_cast(c2.g)), + static_cast((1.0f - f) * static_cast(c1.b) + f * static_cast(c2.b)), + static_cast((1.0f - f) * static_cast(c1.w) + f * static_cast(c2.w)), + }; + } }; \ No newline at end of file