wled: deprecate color_order option

Support more synonym strings for colors
Case insensitive string type checks
Show errors in UI if e.g. wrong color was selected
Public color_config not color_order as it has confused users
Fix documentation for white stealthburner example

Signed-off-by:  Richard Mitchell <richardjm+moonraker@gmail.com>
This commit is contained in:
Richard Mitchell 2022-01-25 11:02:36 +00:00 committed by GitHub
parent f5ceefbb8d
commit 1cac7399f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 101 additions and 106 deletions

View File

@ -1103,7 +1103,9 @@ explanation of each parameter:
default is False.
### `[wled]`
Enables control of an WLED strip.
Enables control of a [WLED](https://kno.wled.ge/) strip. Moonraker always
supports 4 color channel strips - the color order is defined within WLED
itself.
```ini
# moonraker.conf
@ -1131,8 +1133,8 @@ initial_white:
chain_count:
# Number of addressable neopixels for use (default: 1)
color_order:
# *** DEPRECATED - Color order is defined per GPIO in WLED directly ***
# Color order for WLED strip, RGB or RGBW (default: RGB)
```
Below are some examples:
```ini
@ -1166,7 +1168,7 @@ one or more macros, such as:
# printer.cfg
[gcode_macro WLED_ON]
description: Turn WLED strip on using optional preset
description: Turn WLED strip on using optional preset and resets led colors
gcode:
{% set strip = params.STRIP|string %}
{% set preset = params.PRESET|default(-1)|int %}
@ -1186,7 +1188,7 @@ gcode:
state=False)}
[gcode_macro SET_WLED]
description: SET_LED like functionlity for WLED
description: SET_LED like functionlity for WLED, applies to all active segments
gcode:
{% set strip = params.STRIP|string %}
{% set red = params.RED|default(0)|float %}

View File

@ -2,6 +2,11 @@
This file will track changes that require user intervention,
such as a configuration change or a reinstallation.
### January 22th 2022
- The `color_order` option in the `[wled]` section has been deprecated.
This is configured in wled directly. This is not a breaking change,
the setting will simply be ignored not affecting functionality.
### December 24th 2021
- The `password_file` option in the `[mqtt]` section has been deprecated.
Use the `password` option instead. This option may be a template, thus

View File

@ -33,29 +33,20 @@ if TYPE_CHECKING:
from . import klippy_apis
APIComp = klippy_apis.KlippyAPI
class ColorOrder(str, Enum):
RGB: str = "RGB"
RGBW: str = "RGBW"
def Elem_Size(self):
if self is ColorOrder.RGB:
return 3
return 4
class OnOff(str, Enum):
on: str = "on"
off: str = "off"
class Strip():
_COLORSIZE: int = 4
def __init__(self: Strip,
name: str,
color_order: ColorOrder,
cfg: ConfigHelper):
self.server = cfg.get_server()
self.request_mutex = asyncio.Lock()
self.name = name
self.color_order = color_order
self.initial_preset: int = cfg.getint("initial_preset", -1)
self.initial_red: float = cfg.getfloat("initial_red", 0.5)
@ -64,7 +55,9 @@ class Strip():
self.initial_white: float = cfg.getfloat("initial_white", 0.5)
self.chain_count: int = cfg.getint("chain_count", 1)
self._chain_data = bytearray(self.chain_count * color_order.Elem_Size())
# Supports rgbw always
self._chain_data = bytearray(
self.chain_count * self._COLORSIZE)
self.onoff = OnOff.off
self.preset = self.initial_preset
@ -75,7 +68,6 @@ class Strip():
"status": self.onoff,
"chain_count": self.chain_count,
"preset": self.preset,
"color_order": self.color_order,
"error": self.error_state
}
@ -105,10 +97,7 @@ class Strip():
blue = int(blue * 255. + .5)
green = int(green * 255. + .5)
white = int(white * 255. + .5)
if self.color_order is ColorOrder.RGB:
led_data = [red, green, blue]
else:
led_data = [red, green, blue, white]
led_data = [red, green, blue, white]
if index is None:
self._chain_data[:] = led_data * self.chain_count
@ -149,8 +138,18 @@ class Strip():
async def wled_off(self: Strip) -> None:
logging.debug(f"WLED: {self.name} off")
self.onoff = OnOff.off
# Without this calling SET_WLED for a single pixel after WLED_OFF
# would send just that pixel
self.send_full_chain_data = True
await self._send_wled_command({"on": False})
def _wled_pixel(self: Strip, index: int) -> List[int]:
led_color_data: List[int] = []
for p in self._chain_data[(index-1)*self._COLORSIZE:
(index)*self._COLORSIZE]:
led_color_data.append(p)
return led_color_data
async def set_wled(self: Strip,
red: float, green: float, blue: float, white: float,
index: Optional[int], transmit: bool) -> None:
@ -159,40 +158,40 @@ class Strip():
f"INDEX={index} TRANSMIT={transmit}")
self._update_color_data(red, green, blue, white, index)
if transmit:
elem_size = self.color_order.Elem_Size()
if self.onoff == OnOff.off:
# Without a separate On call individual led control doesn"t
# turn the led strip back on
self.onoff = OnOff.on
await self._send_wled_command({"on": True})
# Base command for setting an led (for all active segments)
# See https://kno.wled.ge/interfaces/json-api/
state: Dict[str, Any] = {"on": True,
"tt": 0,
"bri": 255,
"seg": {"bri": 255, "i": []}}
if index is None:
# All pixels same color only send range command
elem = []
for p in self._chain_data[0:elem_size]:
elem.append(p)
# All pixels same color only send range command of first color
self.send_full_chain_data = False
await self._send_wled_command(
{"seg": {"i": [0, self.chain_count-1, elem]}})
state["seg"]["i"] = [0, self.chain_count, self._wled_pixel(1)]
elif self.send_full_chain_data:
# Send a full set of color data (e.g. previous preset)
state: Dict[str, Any] = {"seg": {"i": []}}
self.send_full_chain_data = False
cdata = []
for i in range(self.chain_count):
idx = i * elem_size
elem = []
for p in self._chain_data[idx: idx+elem_size]:
elem.append(p)
cdata.append(elem)
cdata.append(self._wled_pixel(i+1))
state["seg"]["i"] = cdata
self.send_full_chain_data = False
await self._send_wled_command(state)
else:
# Only one pixel has changed so send just that one
elem = []
for p in self._chain_data[(index-1)*elem_size:
(index-1)*elem_size+elem_size]:
elem.append(p)
await self._send_wled_command({"seg": {"i": [index, elem]}})
# Only one pixel has changed since last full data sent
# so send just that one
state["seg"]["i"] = [index-1, self._wled_pixel(index)]
# Send wled control command
await self._send_wled_command(state)
if self.onoff == OnOff.off:
# Without a repeated call individual led control doesn't
# turn the led strip back on or doesn't set brightness
# correctly from off
# Confirmed as a bug:
# https://discord.com/channels/473448917040758787/757254961640898622/934135556370202645
self.onoff = OnOff.on
await self._send_wled_command(state)
else:
# If not transmitting this time easiest just to send all data when
# next transmitting
@ -201,9 +200,8 @@ class Strip():
class StripHttp(Strip):
def __init__(self: StripHttp,
name: str,
color_order: ColorOrder,
cfg: ConfigHelper):
super().__init__(name, color_order, cfg)
super().__init__(name, cfg)
# Read the uri information
addr: str = cfg.get("address")
@ -235,9 +233,8 @@ class StripHttp(Strip):
class StripSerial(Strip):
def __init__(self: StripSerial,
name: str,
color_order: ColorOrder,
cfg: ConfigHelper):
super().__init__(name, color_order, cfg)
super().__init__(name, cfg)
# Read the serial information (requires wled 0.13 2108250 or greater)
self.serialport: str = cfg.get("serial")
@ -261,25 +258,22 @@ class StripSerial(Strip):
class WLED:
def __init__(self: WLED, config: ConfigHelper) -> None:
try:
# root_logger = logging.getLogger()
# root_logger.setLevel(logging.DEBUG)
# root_logger = logging.getLogger()
# root_logger.setLevel(logging.DEBUG)
self.server = config.get_server()
prefix_sections = config.get_prefix_sections("wled")
logging.info(f"WLED component loading strips: {prefix_sections}")
color_orders = {
"RGB": ColorOrder.RGB,
"RGBW": ColorOrder.RGBW
}
strip_types = {
"http": StripHttp,
"serial": StripSerial
}
self.strips = {}
for section in prefix_sections:
cfg = config[section]
self.server = config.get_server()
prefix_sections = config.get_prefix_sections("wled")
logging.info(f"WLED component loading strips: {prefix_sections}")
strip_types = {
"HTTP": StripHttp,
"SERIAL": StripSerial
}
self.strips = {}
for section in prefix_sections:
cfg = config[section]
try:
name_parts = cfg.get_name().split(maxsplit=1)
if len(name_parts) != 2:
raise cfg.error(
@ -288,52 +282,46 @@ class WLED:
logging.info(f"WLED strip: {name}")
color_order_cfg: str = cfg.get("color_order", "RGB")
color_order = color_orders.get(color_order_cfg)
if color_order is None:
raise cfg.error(
f"Color order not supported: {color_order_cfg}")
# Discard old color_order setting, always support 4 color strips
_ = cfg.get("color_order", "", deprecate=True)
strip_type: str = cfg.get("type", "http")
strip_class: Optional[Type[Strip]]
strip_class = strip_types.get(strip_type)
strip_class = strip_types.get(strip_type.upper())
if strip_class is None:
raise cfg.error(f"Unsupported Strip Type: {strip_type}")
try:
strip = strip_class(name, color_order, cfg)
except Exception as e:
msg = f"Failed to initialise strip [{cfg.get_name()}]\n{e}"
self.server.add_warning(msg)
continue
raise config.error(f"Unsupported Strip Type: {strip_type}")
self.strips[name] = strip
self.strips[name] = strip_class(name, cfg)
# Register two remote methods for GCODE
self.server.register_remote_method(
"set_wled_state", self.set_wled_state)
self.server.register_remote_method(
"set_wled", self.set_wled)
except Exception as e:
# Ensures errors such as "Color not supported" are visible
msg = f"Failed to initialise strip [{cfg.get_name()}]\n{e}"
self.server.add_warning(msg)
continue
# As moonraker is about making things a web api, let's try it
# Yes, this is largely a cut-n-paste from power.py
self.server.register_endpoint(
"/machine/wled/strips", ["GET"],
self._handle_list_strips)
self.server.register_endpoint(
"/machine/wled/status", ["GET"],
self._handle_batch_wled_request)
self.server.register_endpoint(
"/machine/wled/on", ["POST"],
self._handle_batch_wled_request)
self.server.register_endpoint(
"/machine/wled/off", ["POST"],
self._handle_batch_wled_request)
self.server.register_endpoint(
"/machine/wled/strip", ["GET", "POST"],
self._handle_single_wled_request)
# Register two remote methods for GCODE
self.server.register_remote_method(
"set_wled_state", self.set_wled_state)
self.server.register_remote_method(
"set_wled", self.set_wled)
except Exception as e:
logging.exception(e)
# As moonraker is about making things a web api, let's try it
# Yes, this is largely a cut-n-paste from power.py
self.server.register_endpoint(
"/machine/wled/strips", ["GET"],
self._handle_list_strips)
self.server.register_endpoint(
"/machine/wled/status", ["GET"],
self._handle_batch_wled_request)
self.server.register_endpoint(
"/machine/wled/on", ["POST"],
self._handle_batch_wled_request)
self.server.register_endpoint(
"/machine/wled/off", ["POST"],
self._handle_batch_wled_request)
self.server.register_endpoint(
"/machine/wled/strip", ["GET", "POST"],
self._handle_single_wled_request)
async def component_init(self) -> None:
try: