├── README.md ├── custom_components └── flux_led │ ├── __init__.py │ ├── light.py │ └── manifest.json └── hacs.json /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | Modifies homeassistant/components/flux_led/light.py to fix issue where LED lightstrip using MagicHome controller lights turn off immediately after turning them on. Based on modification by john32 with some additional files and instructions. See https://community.home-assistant.io/t/flux-led-magiclight-dont-work-since-few-updated/145179/5 3 | 4 | This is designed to specifically fix v8 firwware MagicHome Flux LED WiFi RGB controllers, I do NOT have RGBW or RGBCW strips or controllers ot test with, so your mileage may vary if you have other controllers or other firmware than v8. 5 | 6 | # Usage 7 | 8 | ### HACS installation 9 | This can be installed via HACS! To do this, follow these instructions: 10 | 1. Copy the URL to this respository: https://github.com/CorneliousJD/Flux_LED/ 11 | 2. In HACS, select integrations 12 | 3. Click the 3 vertical dots (⋮) at the top right of the HACS integration page 13 | 4. Select 'Custom repositories' 14 | 5. Paste this repositories in the 'Add custom repository URL' field, select the integration category, then click add 15 | 6. The integration will now show as a new installable repository in HACS 16 | 17 | ### Manual installation 18 | You will need to put these files into your HomeAssistant /config/custom_components/flux_led/ folder. 19 | After that, if you are running in a docker container you will need to make sure you enter the container and run 20 | 21 | ``` 22 | cd /config/custom_components/flux_led 23 | 24 | chown root:root 25 | ``` 26 | 27 | 28 | This should give the files the proper permissions needed for HomeAssistant Core to access the files. 29 | Go ahead and restart HomeAssistant and then go into Developer Tools, then Log, and you should see 30 | 31 | *WARNING (MainThread) [homeassistant.loader] You are using a custom integration for flux_led which has not been tested by Home Assistant. This component might cause stability problems, be sure to disable it if you experience issues with Home Assistant.* 32 | 33 | Once you see that, you will know it's loading the new custom component you added. Go ahead and test from here, lights should now turn on/off, note that there is more of a delay than normal with this fix, it takes a second or two in order for it ot update it's state when you request a change, but working slowly is better than not working at all! 34 | 35 | *As a workaround for the slow-ish polling, you can set `scan_interval` in the configuration.yml to a pretty low value. This makes it much snappier.* 36 | 37 | Example: 38 | ``` 39 | - platform: flux_led 40 | scan_interval: 0.5 41 | devices: 42 | 10.0.2.4: 43 | name: LED TV 44 | mode: rgb 45 | ``` 46 | 47 | Thanks go to john32 on HomeAssistant Forums, and @skylord123 on GitHub for pointing me in the right direction here. 48 | -------------------------------------------------------------------------------- /custom_components/flux_led/__init__.py: -------------------------------------------------------------------------------- 1 | """The flux_led component.""" -------------------------------------------------------------------------------- /custom_components/flux_led/light.py: -------------------------------------------------------------------------------- 1 | """Support for Flux lights.""" 2 | import logging 3 | import random 4 | import socket 5 | 6 | from flux_led import BulbScanner, WifiLedBulb 7 | import voluptuous as vol 8 | 9 | from homeassistant.components.light import ( 10 | ATTR_BRIGHTNESS, 11 | ATTR_COLOR_TEMP, 12 | ATTR_EFFECT, 13 | ATTR_HS_COLOR, 14 | ATTR_WHITE_VALUE, 15 | EFFECT_COLORLOOP, 16 | EFFECT_RANDOM, 17 | PLATFORM_SCHEMA, 18 | SUPPORT_BRIGHTNESS, 19 | SUPPORT_COLOR, 20 | SUPPORT_COLOR_TEMP, 21 | SUPPORT_EFFECT, 22 | SUPPORT_WHITE_VALUE, 23 | LightEntity, 24 | ) 25 | from homeassistant.const import ATTR_MODE, CONF_DEVICES, CONF_NAME, CONF_PROTOCOL 26 | import homeassistant.helpers.config_validation as cv 27 | import homeassistant.util.color as color_util 28 | 29 | _LOGGER = logging.getLogger(__name__) 30 | 31 | CONF_AUTOMATIC_ADD = "automatic_add" 32 | CONF_CUSTOM_EFFECT = "custom_effect" 33 | CONF_COLORS = "colors" 34 | CONF_SPEED_PCT = "speed_pct" 35 | CONF_TRANSITION = "transition" 36 | 37 | DOMAIN = "flux_led" 38 | 39 | SUPPORT_FLUX_LED = SUPPORT_BRIGHTNESS | SUPPORT_EFFECT | SUPPORT_COLOR 40 | 41 | MODE_RGB = "rgb" 42 | MODE_RGBW = "rgbw" 43 | 44 | # This mode enables white value to be controlled by brightness. 45 | # RGB value is ignored when this mode is specified. 46 | MODE_WHITE = "w" 47 | 48 | # Constant color temp values for 2 flux_led special modes 49 | # Warm-white and Cool-white modes 50 | COLOR_TEMP_WARM_VS_COLD_WHITE_CUT_OFF = 285 51 | 52 | # List of supported effects which aren't already declared in LIGHT 53 | EFFECT_RED_FADE = "red_fade" 54 | EFFECT_GREEN_FADE = "green_fade" 55 | EFFECT_BLUE_FADE = "blue_fade" 56 | EFFECT_YELLOW_FADE = "yellow_fade" 57 | EFFECT_CYAN_FADE = "cyan_fade" 58 | EFFECT_PURPLE_FADE = "purple_fade" 59 | EFFECT_WHITE_FADE = "white_fade" 60 | EFFECT_RED_GREEN_CROSS_FADE = "rg_cross_fade" 61 | EFFECT_RED_BLUE_CROSS_FADE = "rb_cross_fade" 62 | EFFECT_GREEN_BLUE_CROSS_FADE = "gb_cross_fade" 63 | EFFECT_COLORSTROBE = "colorstrobe" 64 | EFFECT_RED_STROBE = "red_strobe" 65 | EFFECT_GREEN_STROBE = "green_strobe" 66 | EFFECT_BLUE_STROBE = "blue_strobe" 67 | EFFECT_YELLOW_STROBE = "yellow_strobe" 68 | EFFECT_CYAN_STROBE = "cyan_strobe" 69 | EFFECT_PURPLE_STROBE = "purple_strobe" 70 | EFFECT_WHITE_STROBE = "white_strobe" 71 | EFFECT_COLORJUMP = "colorjump" 72 | EFFECT_CUSTOM = "custom" 73 | 74 | EFFECT_MAP = { 75 | EFFECT_COLORLOOP: 0x25, 76 | EFFECT_RED_FADE: 0x26, 77 | EFFECT_GREEN_FADE: 0x27, 78 | EFFECT_BLUE_FADE: 0x28, 79 | EFFECT_YELLOW_FADE: 0x29, 80 | EFFECT_CYAN_FADE: 0x2A, 81 | EFFECT_PURPLE_FADE: 0x2B, 82 | EFFECT_WHITE_FADE: 0x2C, 83 | EFFECT_RED_GREEN_CROSS_FADE: 0x2D, 84 | EFFECT_RED_BLUE_CROSS_FADE: 0x2E, 85 | EFFECT_GREEN_BLUE_CROSS_FADE: 0x2F, 86 | EFFECT_COLORSTROBE: 0x30, 87 | EFFECT_RED_STROBE: 0x31, 88 | EFFECT_GREEN_STROBE: 0x32, 89 | EFFECT_BLUE_STROBE: 0x33, 90 | EFFECT_YELLOW_STROBE: 0x34, 91 | EFFECT_CYAN_STROBE: 0x35, 92 | EFFECT_PURPLE_STROBE: 0x36, 93 | EFFECT_WHITE_STROBE: 0x37, 94 | EFFECT_COLORJUMP: 0x38, 95 | } 96 | EFFECT_CUSTOM_CODE = 0x60 97 | 98 | TRANSITION_GRADUAL = "gradual" 99 | TRANSITION_JUMP = "jump" 100 | TRANSITION_STROBE = "strobe" 101 | 102 | FLUX_EFFECT_LIST = sorted(list(EFFECT_MAP)) + [EFFECT_RANDOM] 103 | 104 | CUSTOM_EFFECT_SCHEMA = vol.Schema( 105 | { 106 | vol.Required(CONF_COLORS): vol.All( 107 | cv.ensure_list, 108 | vol.Length(min=1, max=16), 109 | [ 110 | vol.All( 111 | vol.ExactSequence((cv.byte, cv.byte, cv.byte)), vol.Coerce(tuple) 112 | ) 113 | ], 114 | ), 115 | vol.Optional(CONF_SPEED_PCT, default=50): vol.All( 116 | vol.Range(min=0, max=100), vol.Coerce(int) 117 | ), 118 | vol.Optional(CONF_TRANSITION, default=TRANSITION_GRADUAL): vol.All( 119 | cv.string, vol.In([TRANSITION_GRADUAL, TRANSITION_JUMP, TRANSITION_STROBE]) 120 | ), 121 | } 122 | ) 123 | 124 | DEVICE_SCHEMA = vol.Schema( 125 | { 126 | vol.Optional(CONF_NAME): cv.string, 127 | vol.Optional(ATTR_MODE, default=MODE_RGBW): vol.All( 128 | cv.string, vol.In([MODE_RGBW, MODE_RGB, MODE_WHITE]) 129 | ), 130 | vol.Optional(CONF_PROTOCOL): vol.All(cv.string, vol.In(["ledenet"])), 131 | vol.Optional(CONF_CUSTOM_EFFECT): CUSTOM_EFFECT_SCHEMA, 132 | } 133 | ) 134 | 135 | PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( 136 | { 137 | vol.Optional(CONF_DEVICES, default={}): {cv.string: DEVICE_SCHEMA}, 138 | vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean, 139 | } 140 | ) 141 | 142 | 143 | def setup_platform(hass, config, add_entities, discovery_info=None): 144 | """Set up the Flux lights.""" 145 | lights = [] 146 | light_ips = [] 147 | 148 | for ipaddr, device_config in config.get(CONF_DEVICES, {}).items(): 149 | device = {} 150 | device["name"] = device_config[CONF_NAME] 151 | device["ipaddr"] = ipaddr 152 | device[CONF_PROTOCOL] = device_config.get(CONF_PROTOCOL) 153 | device[ATTR_MODE] = device_config[ATTR_MODE] 154 | device[CONF_CUSTOM_EFFECT] = device_config.get(CONF_CUSTOM_EFFECT) 155 | light = FluxLight(device) 156 | lights.append(light) 157 | light_ips.append(ipaddr) 158 | 159 | if not config.get(CONF_AUTOMATIC_ADD, False): 160 | add_entities(lights, True) 161 | return 162 | 163 | # Find the bulbs on the LAN 164 | scanner = BulbScanner() 165 | scanner.scan(timeout=10) 166 | for device in scanner.getBulbInfo(): 167 | ipaddr = device["ipaddr"] 168 | if ipaddr in light_ips: 169 | continue 170 | device["name"] = "{} {}".format(device["id"], ipaddr) 171 | device[ATTR_MODE] = None 172 | device[CONF_PROTOCOL] = None 173 | device[CONF_CUSTOM_EFFECT] = None 174 | light = FluxLight(device) 175 | lights.append(light) 176 | 177 | add_entities(lights, True) 178 | 179 | 180 | class FluxLight(LightEntity): 181 | """Representation of a Flux light.""" 182 | 183 | def __init__(self, device): 184 | """Initialize the light.""" 185 | self._name = device["name"] 186 | self._ipaddr = device["ipaddr"] 187 | self._protocol = device[CONF_PROTOCOL] 188 | self._mode = device[ATTR_MODE] 189 | self._custom_effect = device[CONF_CUSTOM_EFFECT] 190 | self._bulb = None 191 | self._error_reported = False 192 | 193 | def _connect(self): 194 | """Connect to Flux light.""" 195 | 196 | self._bulb = WifiLedBulb(self._ipaddr, timeout=5) 197 | if self._protocol: 198 | self._bulb.setProtocol(self._protocol) 199 | 200 | # After bulb object is created the status is updated. We can 201 | # now set the correct mode if it was not explicitly defined. 202 | if not self._mode: 203 | if self._bulb.rgbwcapable: 204 | self._mode = MODE_RGBW 205 | else: 206 | self._mode = MODE_RGB 207 | 208 | def _disconnect(self): 209 | """Disconnect from Flux light.""" 210 | self._bulb = None 211 | 212 | @property 213 | def available(self) -> bool: 214 | """Return True if entity is available.""" 215 | return self._bulb is not None 216 | 217 | @property 218 | def name(self): 219 | """Return the name of the device if any.""" 220 | return self._name 221 | 222 | @property 223 | def is_on(self): 224 | """Return true if device is on.""" 225 | return self._bulb.isOn() 226 | 227 | @property 228 | def brightness(self): 229 | """Return the brightness of this light between 0..255.""" 230 | if self._mode == MODE_WHITE: 231 | return self.white_value 232 | 233 | return self._bulb.brightness 234 | 235 | @property 236 | def hs_color(self): 237 | """Return the color property.""" 238 | return color_util.color_RGB_to_hs(*self._bulb.getRgb()) 239 | 240 | @property 241 | def supported_features(self): 242 | """Flag supported features.""" 243 | if self._mode == MODE_RGBW: 244 | return SUPPORT_FLUX_LED | SUPPORT_WHITE_VALUE | SUPPORT_COLOR_TEMP 245 | 246 | if self._mode == MODE_WHITE: 247 | return SUPPORT_BRIGHTNESS 248 | 249 | return SUPPORT_FLUX_LED 250 | 251 | @property 252 | def white_value(self): 253 | """Return the white value of this light between 0..255.""" 254 | return self._bulb.getRgbw()[3] 255 | 256 | @property 257 | def effect_list(self): 258 | """Return the list of supported effects.""" 259 | if self._custom_effect: 260 | return FLUX_EFFECT_LIST + [EFFECT_CUSTOM] 261 | 262 | return FLUX_EFFECT_LIST 263 | 264 | @property 265 | def effect(self): 266 | """Return the current effect.""" 267 | current_mode = self._bulb.raw_state[3] 268 | 269 | if current_mode == EFFECT_CUSTOM_CODE: 270 | return EFFECT_CUSTOM 271 | 272 | for effect, code in EFFECT_MAP.items(): 273 | if current_mode == code: 274 | return effect 275 | 276 | return None 277 | 278 | def turn_on(self, **kwargs): 279 | """Turn the specified or all lights on.""" 280 | # if not self.is_on: 281 | # self._bulb.turnOn() 282 | 283 | hs_color = kwargs.get(ATTR_HS_COLOR) 284 | 285 | if hs_color: 286 | rgb = color_util.color_hs_to_RGB(*hs_color) 287 | else: 288 | rgb = None 289 | 290 | brightness = kwargs.get(ATTR_BRIGHTNESS) 291 | effect = kwargs.get(ATTR_EFFECT) 292 | white = kwargs.get(ATTR_WHITE_VALUE) 293 | color_temp = kwargs.get(ATTR_COLOR_TEMP) 294 | 295 | # handle special modes 296 | if color_temp is not None: 297 | if brightness is None: 298 | brightness = self.brightness 299 | if color_temp > COLOR_TEMP_WARM_VS_COLD_WHITE_CUT_OFF: 300 | self._bulb.setRgbw(w=brightness) 301 | else: 302 | self._bulb.setRgbw(w2=brightness) 303 | return 304 | 305 | # Show warning if effect set with rgb, brightness, or white level 306 | if effect and (brightness or white or rgb): 307 | _LOGGER.warning( 308 | "RGB, brightness and white level are ignored when" 309 | " an effect is specified for a flux bulb" 310 | ) 311 | 312 | # Random color effect 313 | if effect == EFFECT_RANDOM: 314 | self._bulb.setRgb( 315 | random.randint(0, 255), random.randint(0, 255), random.randint(0, 255) 316 | ) 317 | return 318 | 319 | if effect == EFFECT_CUSTOM: 320 | if self._custom_effect: 321 | self._bulb.setCustomPattern( 322 | self._custom_effect[CONF_COLORS], 323 | self._custom_effect[CONF_SPEED_PCT], 324 | self._custom_effect[CONF_TRANSITION], 325 | ) 326 | return 327 | 328 | # Effect selection 329 | if effect in EFFECT_MAP: 330 | self._bulb.setPresetPattern(EFFECT_MAP[effect], 50) 331 | return 332 | 333 | # Preserve current brightness on color/white level change 334 | if brightness is None: 335 | brightness = self.brightness 336 | 337 | # If these are 0 then bulb.isOn will return false, not what we want 338 | if hs_color is None or (hs_color[0] == 0 and hs_color[1] == 0): 339 | hs_color = (1, 1, 1) 340 | 341 | if brightness is None or brightness == 0: 342 | brightness = 100 343 | 344 | # Preserve color on brightness/white level change 345 | if rgb is None: 346 | rgb = self._bulb.getRgb() 347 | 348 | if white is None and self._mode == MODE_RGBW: 349 | white = self.white_value 350 | 351 | # handle W only mode (use brightness instead of white value) 352 | if self._mode == MODE_WHITE: 353 | self._bulb.setRgbw(0, 0, 0, w=brightness) 354 | 355 | # handle RGBW mode 356 | elif self._mode == MODE_RGBW: 357 | self._bulb.setRgbw(*tuple(rgb), w=white, brightness=brightness) 358 | 359 | # handle RGB mode 360 | else: 361 | self._bulb.setRgb(*tuple(rgb), brightness=brightness) 362 | 363 | def turn_off(self, **kwargs): 364 | """Turn the specified or all lights off.""" 365 | self._bulb.turnOff() 366 | 367 | def update(self): 368 | """Synchronize state with bulb.""" 369 | if not self.available: 370 | try: 371 | self._connect() 372 | self._error_reported = False 373 | except socket.error: 374 | self._disconnect() 375 | if not self._error_reported: 376 | _LOGGER.warning( 377 | "Failed to connect to bulb %s, %s", self._ipaddr, self._name 378 | ) 379 | self._error_reported = True 380 | return 381 | 382 | self._bulb.update_state(retry=2) -------------------------------------------------------------------------------- /custom_components/flux_led/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "flux_led", 3 | "name": "Flux led", 4 | "version": "1.0", 5 | "documentation": "https://www.home-assistant.io/components/flux_led", 6 | "requirements": [ 7 | "flux_led==0.22" 8 | ], 9 | "dependencies": [], 10 | "codeowners": [] 11 | } -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Flux LED fork", 3 | "render_readme": "true", 4 | "content_in_root": false 5 | } 6 | --------------------------------------------------------------------------------