wled: Add extra control for wled effects

For the "Percent" preset the intensity controls the amount of the bar lit up 0-100
Work around issue when preset is re-enabled if sending intensity or speed while no preset is active

Removed control endpoint
Added missed toggle endpoint
Transition time to 0 for control changes

Signed-off-by:  Richard Mitchell <richardjm+moonraker@gmail.com>
This commit is contained in:
Richard Mitchell 2022-05-09 11:32:14 +01:00 committed by GitHub
parent fcfa563d02
commit 56097a35ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 463 additions and 18 deletions

View File

@ -1491,6 +1491,20 @@ gcode:
state=True, state=True,
preset=preset)} preset=preset)}
[gcode_macro WLED_CONTROL]
description: Control effect values and brightness
gcode:
{% set strip = params.STRIP|default('lights')|string %}
{% set brightness = params.BRIGHTNESS|default(-1)|int %}
{% set intensity = params.INTENSITY|default(-1)|int %}
{% set speed = params.SPEED|default(-1)|int %}
{action_call_remote_method("set_wled_state",
strip=strip,
brightness=brightness,
intensity=intensity,
speed=speed)}
[gcode_macro WLED_OFF] [gcode_macro WLED_OFF]
description: Turn WLED strip off description: Turn WLED strip off
gcode: gcode:

View File

@ -3466,7 +3466,7 @@ JSON-RPC request:
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "machine.device_power.off", "method": "machine.device_power.off",
"params": { "params": {
"dev_one":null, "dev_one": null,
"dev_two": null "dev_two": null
}, },
"id": 4564 "id": 4564
@ -3479,6 +3479,340 @@ An object containing power state for each requested device:
"printer": "off" "printer": "off"
} }
``` ```
### WLED APIs
The APIs for WLED are available when the `[wled]` component has been configured. For lower-level control of wled consider using the WLED [JOSN API](https://kno.wled.ge/interfaces/json-api/) directly.
#### Get strips
HTTP request:
```http
GET /machine/wled/strips
```
JSON-RPC request:
```json
{
"jsonrpc": "2.0",
"method":"machine.wled.strips",
"id": 7123
}
```
Returns:
Strip information for all wled strips.
```json
{
"result": {
"strips": {
"lights": {
"strip": "lights",
"status": "on",
"chain_count": 79,
"preset": -1,
"brightness": 255,
"intensity": -1,
"speed": -1,
"error": null
},
"desk": {
"strip": "desk",
"status": "on",
"chain_count": 60,
"preset": 8,
"brightness": -1,
"intensity": -1,
"speed": -1,
"error": null
}
}
}
}
```
#### Get strip status
HTTP request:
```http
GET /machine/wled/status?strip1&strip2
```
JSON-RPC request:
```json
{
"jsonrpc": "2.0",
"method":"machine.wled.status",
"params": {
"lights": null,
"desk": null
},
"id": 7124
}
```
Returns:
Strip information for requested strips.
```json
{
"result": {
"lights": {
"strip": "lights",
"status": "on",
"chain_count": 79,
"preset": -1,
"brightness": 255,
"intensity": -1,
"speed": -1,
"error": null
},
"desk": {
"strip": "desk",
"status": "on",
"chain_count": 60,
"preset": 8,
"brightness": -1,
"intensity": -1,
"speed": -1,
"error": null
}
}
}
```
#### Turn strip on
Turns the specified strips on to the initial colors or intial preset.
HTTP request:
```http
POST /machine/wled/on?strip1&strip2
```
JSON-RPC request:
```json
{
"jsonrpc": "2.0",
"method":"machine.wled.on",
"params": {
"lights": null,
"desk": null
},
"id": 7125
}
```
Returns:
Strip information for requested strips.
```json
{
"result": {
"lights": {
"strip": "lights",
"status": "on",
"chain_count": 79,
"preset": -1,
"brightness": 255,
"intensity": -1,
"speed": -1,
"error": null
},
"desk": {
"strip": "desk",
"status": "on",
"chain_count": 60,
"preset": 8,
"brightness": -1,
"intensity": -1,
"speed": -1,
"error": null
}
}
}
```
#### Turn strip off
Turns off all specified strips.
HTTP request:
```http
POST /machine/wled/off?strip1&strip2
```
JSON-RPC request:
```json
{
"jsonrpc": "2.0",
"method":"machine.wled.off",
"params": {
"lights": null,
"desk": null
},
"id": 7126
}
```
Returns:
The new state of the specified strips.
```json
{
"result": {
"lights": {
"strip": "lights",
"status": "off",
"chain_count": 79,
"preset": -1,
"brightness": 255,
"intensity": -1,
"speed": -1,
"error": null
},
"desk": {
"strip": "desk",
"status": "off",
"chain_count": 60,
"preset": 8,
"brightness": -1,
"intensity": -1,
"speed": -1,
"error": null
}
}
}
```
#### Toggle strip on/off state
Turns each strip off if it is on and on if it is off.
HTTP request:
```http
POST /machine/wled/off?strip1&strip2
```
JSON-RPC request:
```json
{
"jsonrpc": "2.0",
"method":"machine.wled.toggle",
"params": {
"lights": null,
"desk": null
},
"id": 7127
}
```
Returns:
The new state of the specified strips.
```json
{
"result": {
"lights": {
"strip": "lights",
"status": "on",
"chain_count": 79,
"preset": -1,
"brightness": 255,
"intensity": -1,
"speed": -1,
"error": null
},
"desk": {
"strip": "desk",
"status": "off",
"chain_count": 60,
"preset": 8,
"brightness": -1,
"intensity": -1,
"speed": -1,
"error": null
}
}
}
```
#### Control individual strip state
Toggle, turn on, turn off, turn on with preset, turn on with brightness, or
turn on preset will some of brightness, intensity, and speed. Or simply set
some of brightness, intensity, and speed.
HTTP requests:
Turn strip `lights` off
```http
POST /machine/wled/strip?strip=lights&action=off
```
Turn strip `lights` on to the initial colors or intial preset.
```http
POST /machine/wled/strip?strip=lights&action=on
```
Turn strip `lights` on activating preset 3.
```http
POST /machine/wled/strip?strip=lights&action=on&preset=3
```
Turn strip `lights` on activating preset 3 while specifying speed and
intensity.
```http
POST /machine/wled/strip?strip=lights&action=on&preset=3&intensity=50&speed=255
```
Change strip `lights` brightness (if on) and speed (if a preset is active).
```http
POST /machine/wled/strip?strip=lights&action=control&brightness=99&speed=50
```
JSON-RPC request:
Returns information for the specified strip.
```json
{
"jsonrpc": "2.0",
"method":"machine.wled.get_strip",
"params": {
"strip": "lights",
},
"id": 7128
}
```
Calls the action with the arguments for the specified strip.
```json
{
"jsonrpc": "2.0",
"method":"machine.wled.post_strip",
"params": {
"strip": "lights",
"action": "on",
"preset": 1,
"brightness": 255,
"intensity": 255,
"speed": 255
},
"id": 7129
}
```
!!! note
The `action` argument may be `on`, `off`, `toggle` or `control`. Any
other value will result in an error.
The `intensity` and `speed` arguments are only used if a preset is active.
Permitted ranges are 1-255 for `brightness` and 0-255 for `intensity` and
`speed`. When action is `on` a `preset` with some or all of `brightness`,
`intensity` and `speed` may also be specified. If the action `control` is used
one or all of `brightness`, `intensity`, and `speed` must be specified.
Returns:
State of the strip.
```json
{
"result": {
"lights": {
"strip": "lights",
"status": "on",
"chain_count": 79,
"preset": 1,
"brightness": 50,
"intensity": 255,
"speed": 255,
"error": null
}
}
}
```
### OctoPrint API emulation ### OctoPrint API emulation
Partial support of OctoPrint API is implemented with the purpose of Partial support of OctoPrint API is implemented with the purpose of

View File

@ -65,9 +65,12 @@ class Strip():
def get_strip_info(self: Strip) -> Dict[str, Any]: def get_strip_info(self: Strip) -> Dict[str, Any]:
return { return {
"strip": self.name, "strip": self.name,
"status": self.onoff, "status": self.onoff.value,
"chain_count": self.chain_count, "chain_count": self.chain_count,
"preset": self.preset, "preset": self.preset,
"brightness": self.brightness,
"intensity": self.intensity,
"speed": self.speed,
"error": self.error_state "error": self.error_state
} }
@ -75,6 +78,9 @@ class Strip():
self.send_full_chain_data = True self.send_full_chain_data = True
self.onoff = OnOff.on self.onoff = OnOff.on
self.preset = self.initial_preset self.preset = self.initial_preset
self.brightness = 255
self.intensity = -1
self.speed = -1
if self.initial_preset >= 0: if self.initial_preset >= 0:
self._update_color_data(self.initial_red, self._update_color_data(self.initial_red,
self.initial_green, self.initial_green,
@ -133,6 +139,10 @@ class Strip():
else: else:
self.send_full_chain_data = True self.send_full_chain_data = True
self.preset = preset self.preset = preset
# Without reading the data back from wled we don't know the values
self.brightness = -1
self.intensity = -1
self.speed = -1
await self._send_wled_command({"on": True, "ps": preset}) await self._send_wled_command({"on": True, "ps": preset})
async def wled_off(self: Strip) -> None: async def wled_off(self: Strip) -> None:
@ -143,6 +153,60 @@ class Strip():
self.send_full_chain_data = True self.send_full_chain_data = True
await self._send_wled_command({"on": False}) await self._send_wled_command({"on": False})
async def wled_control(self: Strip, brightness: int, intensity: int,
speed: int) -> None:
logging.debug(
f"WLED: {self.name} control {self.onoff} BRIGHTNESS={brightness} "
f"INTENSITY={intensity} SPEED={speed} CURRENTPRESET={self.preset}")
if self.onoff == OnOff.off:
logging.info("wled control only permitted when strip is on")
return
# Even if a preset is not activated sending seg {} information will
# turn it back on
control: Dict[str, Any]
if self.preset != -1:
control = {"tt": 0, "seg": {}}
else:
control = {"tt": 0}
shouldSend: bool = False
# Using 0 is not recommended in wled docs
if brightness > 0:
if brightness > 255:
logging.info("BRIGHTNESS should be between 1 and 255")
else:
shouldSend = True
self.brightness = brightness
control["bri"] = self.brightness
# Brightness in seg {} - only if a preset is on
if self.preset != -1:
control["seg"]["bri"] = self.brightness
# Intensity - only if a preset is on
if intensity > -1 and self.preset != -1:
if intensity > 255:
logging.info("INTENSITY should be between 0 and 255")
else:
shouldSend = True
self.intensity = intensity
control["seg"]["ix"] = self.intensity
# Speed - only if a preset is on
if speed > -1 and self.preset != -1:
if speed > 255:
logging.info("SPEED should be between 0 and 255")
else:
shouldSend = True
self.speed = speed
control["seg"]["sx"] = self.speed
# Control brightness, intensity, and speed for segment
# This will allow full control for effects such as "Percent"
if shouldSend:
await self._send_wled_command(control)
def _wled_pixel(self: Strip, index: int) -> List[int]: def _wled_pixel(self: Strip, index: int) -> List[int]:
led_color_data: List[int] = [] led_color_data: List[int] = []
for p in self._chain_data[(index-1)*self._COLORSIZE: for p in self._chain_data[(index-1)*self._COLORSIZE:
@ -158,13 +222,21 @@ class Strip():
f"INDEX={index} TRANSMIT={transmit}") f"INDEX={index} TRANSMIT={transmit}")
self._update_color_data(red, green, blue, white, index) self._update_color_data(red, green, blue, white, index)
if transmit: if transmit:
# Clear preset (issues with sending seg{} will revert to preset)
self.preset = -1
# If we are coming from a preset without a wled_control
# we don't know a brightness, this will also ensure
# behaviour is consistent prior to introduction of wled_control
if self.brightness == -1:
self.brightness = 255
# Base command for setting an led (for all active segments) # Base command for setting an led (for all active segments)
# See https://kno.wled.ge/interfaces/json-api/ # See https://kno.wled.ge/interfaces/json-api/
state: Dict[str, Any] = {"on": True, state: Dict[str, Any] = {"on": True,
"tt": 0, "tt": 0,
"bri": 255, "bri": self.brightness,
"seg": {"bri": 255, "i": []}} "seg": {"bri": self.brightness, "i": []}}
if index is None: if index is None:
# All pixels same color only send range command of first color # All pixels same color only send range command of first color
self.send_full_chain_data = False self.send_full_chain_data = False
@ -329,6 +401,9 @@ class WLED:
self.server.register_endpoint( self.server.register_endpoint(
"/machine/wled/off", ["POST"], "/machine/wled/off", ["POST"],
self._handle_batch_wled_request) self._handle_batch_wled_request)
self.server.register_endpoint(
"/machine/wled/toggle", ["POST"],
self._handle_batch_wled_request)
self.server.register_endpoint( self.server.register_endpoint(
"/machine/wled/strip", ["GET", "POST"], "/machine/wled/strip", ["GET", "POST"],
self._handle_single_wled_request) self._handle_single_wled_request)
@ -372,8 +447,9 @@ class WLED:
# Full control of wled # Full control of wled
# state: True, False, "on", "off" # state: True, False, "on", "off"
# preset: wled preset (int) to use (ignored if state False or "Off") # preset: wled preset (int) to use (ignored if state False or "Off")
async def set_wled_state(self: WLED, strip: str, state: str, async def set_wled_state(self: WLED, strip: str, state: str = None,
preset: int = -1) -> None: preset: int = -1, brightness: int = -1,
intensity: int = -1, speed: int = -1) -> None:
status = None status = None
if isinstance(state, bool): if isinstance(state, bool):
@ -383,21 +459,28 @@ class WLED:
if status in ["true", "false"]: if status in ["true", "false"]:
status = OnOff.on if status == "true" else OnOff.off status = OnOff.on if status == "true" else OnOff.off
if status is None and preset == -1: if status is None and preset == -1 and brightness == -1 and \
intensity == -1 and speed == -1:
logging.info( logging.info(
f"Invalid state received but no preset passed: {state}") f"Invalid state received but no control or preset data passed")
return return
if strip not in self.strips: if strip not in self.strips:
logging.info(f"Unknown WLED strip: {strip}") logging.info(f"Unknown WLED strip: {strip}")
return return
# All other arguments are ignored
if status == OnOff.off: if status == OnOff.off:
# All other arguments are ignored
await self.strips[strip].wled_off() await self.strips[strip].wled_off()
else:
# Turn on if on or a preset is specified
if status == OnOff.on or preset != -1:
await self.strips[strip].wled_on(preset) await self.strips[strip].wled_on(preset)
# Control
if brightness != -1 or intensity != -1 or speed != -1:
await self.strips[strip].wled_control(brightness, intensity, speed)
# Individual pixel control, for compatibility with SET_LED # Individual pixel control, for compatibility with SET_LED
async def set_wled(self: WLED, async def set_wled(self: WLED,
strip: str, strip: str,
@ -429,6 +512,9 @@ class WLED:
) -> Dict[str, Any]: ) -> Dict[str, Any]:
strip_name: str = web_request.get_str('strip') strip_name: str = web_request.get_str('strip')
preset: int = web_request.get_int('preset', -1) preset: int = web_request.get_int('preset', -1)
brightness: int = web_request.get_int('brightness', -1)
intensity: int = web_request.get_int('intensity', -1)
speed: int = web_request.get_int('speed', -1)
req_action = web_request.get_action() req_action = web_request.get_action()
if strip_name not in self.strips: if strip_name not in self.strips:
@ -438,10 +524,11 @@ class WLED:
return {strip_name: strip.get_strip_info()} return {strip_name: strip.get_strip_info()}
elif req_action == "POST": elif req_action == "POST":
action = web_request.get_str('action').lower() action = web_request.get_str('action').lower()
if action not in ["on", "off", "toggle"]: if action not in ["on", "off", "toggle", "control"]:
raise self.server.error( raise self.server.error(
f"Invalid requested action '{action}'") f"Invalid requested action '{action}'")
result = await self._process_request(strip, action, preset) result = await self._process_request(strip, action, preset,
brightness, intensity, speed)
return {strip_name: result} return {strip_name: result}
async def _handle_batch_wled_request(self: WLED, async def _handle_batch_wled_request(self: WLED,
@ -456,7 +543,8 @@ class WLED:
req = ep.split("/")[-1] req = ep.split("/")[-1]
for name, strip in requested_strips.items(): for name, strip in requested_strips.items():
if strip is not None: if strip is not None:
result[name] = await self._process_request(strip, req, -1) result[name] = await self._process_request(strip, req, -1,
-1, -1, -1)
else: else:
result[name] = {"error": "strip_not_found"} result[name] = {"error": "strip_not_found"}
return result return result
@ -464,7 +552,10 @@ class WLED:
async def _process_request(self: WLED, async def _process_request(self: WLED,
strip: Strip, strip: Strip,
req: str, req: str,
preset: int preset: int,
brightness: int,
intensity: int,
speed: int
) -> Dict[str, Any]: ) -> Dict[str, Any]:
strip_onoff = strip.onoff strip_onoff = strip.onoff
@ -472,15 +563,21 @@ class WLED:
return strip.get_strip_info() return strip.get_strip_info()
if req == "toggle": if req == "toggle":
req = "on" if strip_onoff == OnOff.off else "off" req = "on" if strip_onoff == OnOff.off else "off"
if req in ["on", "off"]:
if req in ["on", "off", "control"]:
# Always do something, could be turning off colors, or changing # Always do something, could be turning off colors, or changing
# preset, easier not to have to worry # preset, easier not to have to worry
if req == "on": if req == "on" or req == "control":
strip_onoff = OnOff.on if req == "on":
await strip.wled_on(preset) strip_onoff = OnOff.on
await strip.wled_on(preset)
if brightness != -1 or intensity != -1 or speed != -1:
await strip.wled_control(brightness, intensity, speed)
else: else:
strip_onoff = OnOff.off strip_onoff = OnOff.off
await strip.wled_off() await strip.wled_off()
return strip.get_strip_info() return strip.get_strip_info()
raise self.server.error(f"Unsupported wled request: {req}") raise self.server.error(f"Unsupported wled request: {req}")