led: Support automated LED updates based on display_template results

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2022-03-23 18:06:09 -04:00
parent a6ab56c0a6
commit 1ab3ac39e4
3 changed files with 126 additions and 10 deletions

View File

@ -3552,12 +3552,18 @@ text:
### [display_template] ### [display_template]
Display data text "macros" (one may define any number of sections with Display data text "macros" (one may define any number of sections with
a display_template prefix). This feature allows one to reduce a display_template prefix). See the
repetitive definitions in display_data sections. One may use the [command templates](Command_Templates.md) document for information on
builtin render() function in display_data sections to evaluate a template evaluation.
template. For example, if one were to define `[display_template
my_template]` then one could use `{ render('my_template') }` in a This feature allows one to reduce repetitive definitions in
display_data section. display_data sections. One may use the builtin `render()` function in
display_data sections to evaluate a template. For example, if one were
to define `[display_template my_template]` then one could use `{
render('my_template') }` in a display_data section.
This feature can also be used for continuous LED updates using the
[SET_LED_TEMPLATE](G-Codes.md#set_led_template) command.
``` ```
[display_template my_template_name] [display_template my_template_name]
@ -3570,9 +3576,9 @@ display_data section.
# "param_speed = 75" might have a caller with # "param_speed = 75" might have a caller with
# "render('my_template_name', param_speed=80)". Parameter names may # "render('my_template_name', param_speed=80)". Parameter names may
# not use upper case characters. # not use upper case characters.
#text: text:
# The text to return when the render() function is called for this # The text to return when the this template is rendered. This field
# template. This field is evaluated using command templates (see # is evaluated using command templates (see
# docs/Command_Templates.md). This parameter must be provided. # docs/Command_Templates.md). This parameter must be provided.
``` ```

View File

@ -714,6 +714,23 @@ timeout. If careful timing is not needed, the optional SYNC=0
parameter can be specified to apply the changes without resetting the parameter can be specified to apply the changes without resetting the
idle timeout. idle timeout.
#### SET_LED_TEMPLATE
`SET_LED_TEMPLATE LED=<led_name> TEMPLATE=<template_name>
[INDEX=<index>]`: Assign a
[display_template](Config_Reference.md#display_template) to a given
[LED](Config_Reference.md#leds). For example, if one defined a
`[display_template my_led_template]` config section then one could
assign `TEMPLATE=my_led_template` here. The display_template should
produce a comma separated string containing four floating point
numbers corresponding to red, green, blue, and white color settings.
The template will be continuously evaluated and the LED will be
automatically set to the resulting colors. If INDEX is not specified
then all chips in the LED's daisy-chain will be set to the template,
otherwise only the chip with the given index will be updated. If
TEMPLATE is an empty string then this command will clear any previous
template assigned to the LED (one can then use `SET_LED` commands to
manage the LED's color settings).
### [output_pin] ### [output_pin]
The following command is available when an The following command is available when an

View File

@ -4,6 +4,10 @@
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
import logging import logging
from .display import display
# Time between each led template update
RENDER_TIME = 0.500
# Helper code for common LED initialization and control # Helper code for common LED initialization and control
class LEDHelper: class LEDHelper:
@ -66,8 +70,97 @@ class LEDHelper:
class PrinterLED: class PrinterLED:
def __init__(self, config): def __init__(self, config):
self.printer = config.get_printer() self.printer = config.get_printer()
self.led_helpers = {}
self.active_templates = {}
self.render_timer = None
# Load templates
dtemplates = display.lookup_display_templates(config)
self.templates = dtemplates.get_display_templates()
gcode_macro = self.printer.lookup_object("gcode_macro")
self.create_template_context = gcode_macro.create_template_context
# Register handlers
gcode = self.printer.lookup_object('gcode')
gcode.register_command("SET_LED_TEMPLATE", self.cmd_SET_LED_TEMPLATE,
desc=self.cmd_SET_LED_TEMPLATE_help)
def setup_helper(self, config, update_func, led_count=1, has_white=False): def setup_helper(self, config, update_func, led_count=1, has_white=False):
return LEDHelper(config, update_func, led_count, has_white) led_helper = LEDHelper(config, update_func, led_count, has_white)
name = config.get_name().split()[-1]
self.led_helpers[name] = led_helper
return led_helper
def _activate_timer(self):
if self.render_timer is not None or not self.active_templates:
return
reactor = self.printer.get_reactor()
self.render_timer = reactor.register_timer(self._render, reactor.NOW)
def _activate_template(self, led_helper, index, template):
key = (led_helper, index)
if template is not None:
self.active_templates[key] = template
return
if key in self.active_templates:
del self.active_templates[key]
def _render(self, eventtime):
if not self.active_templates:
# Nothing to do - unregister timer
reactor = self.printer.get_reactor()
reactor.register_timer(self.render_timer)
self.render_timer = None
return reactor.NEVER
# Setup gcode_macro template context
context = self.create_template_context(eventtime)
def render(name, **kwargs):
return self.templates[name].render(context, **kwargs)
context['render'] = render
# Render all templates
need_transmit = {}
rendered = {}
for (led_helper, index), template in self.active_templates.items():
color = rendered.get(template)
if color is None:
try:
text = template.render(context)
parts = [max(0., min(1., float(f)))
for f in text.split(',', 4)]
except Exception as e:
logging.exception("led template render error")
parts = []
if len(parts) < 4:
parts += [0.] * (4 - len(parts))
rendered[template] = color = tuple(parts)
prev_color = led_helper.led_state[index-1]
if color != prev_color:
if led_helper not in need_transmit:
need_transmit[led_helper] = 1
led_helper.led_state = list(led_helper.led_state)
led_helper.led_state[index-1] = color
context.clear() # Remove circular references for better gc
# Transmit pending changes
for led_helper in need_transmit.keys():
try:
led_helper.update_func(led_helper.led_state, None)
except Exception as e:
logging.exception("led template transmit error")
return eventtime + RENDER_TIME
cmd_SET_LED_TEMPLATE_help = "Assign a display_template to an LED"
def cmd_SET_LED_TEMPLATE(self, gcmd):
led_name = gcmd.get("LED")
led_helper = self.led_helpers.get(led_name)
if led_helper is None:
raise gcmd.error("Unknown LED '%s'" % (led_name,))
led_count = led_helper.led_count
index = gcmd.get_int("INDEX", None, minval=1, maxval=led_count)
template = None
tpl_name = gcmd.get("TEMPLATE")
if tpl_name:
template = self.templates.get(tpl_name)
if template is None:
raise gcmd.error("Unknown display_template '%s'" % (tpl_name,))
if index is not None:
self._activate_template(led_helper, index, template)
else:
for i in range(led_count):
self._activate_template(led_helper, i+1, template)
self._activate_timer()
PIN_MIN_TIME = 0.100 PIN_MIN_TIME = 0.100
MAX_SCHEDULE_TIME = 5.0 MAX_SCHEDULE_TIME = 5.0