ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Tasmota Berry Animation 框架自定义动画开发指南:类结构、PARAMS 参数系统与渲染管线

Tasmota Berry Animation 框架自定义动画开发指南:类结构、PARAMS 参数系统与渲染管线 Tasmota Berry Animation 框架自定义动画开发指南类结构、PARAMS 参数系统与渲染管线【免费下载链接】TasmotaAlternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at项目地址: https://gitcode.com/GitHub_Trending/ta/Tasmota导读本文面向希望在 Tasmota Berry Animation 框架中扩展新动画类的开发者完整讲解如何从零编写一个与框架参数系统、value_provider 动态取值器和渲染管线无缝集成的自定义动画类。读完本文你将掌握动画类的标准模板、PARAMS 参数定义与校验机制、引擎专用构造器模式、LUT 查表优化、帧缓冲渲染以及单元/集成测试方法并能够基于仓库内已实现的beacon动画源码编写出可发布的专业级动画类。如果你只是希望使用现成动画而非编程扩展请直接阅读 DSL 参考——DSL 提供了一种无需写代码即可声明式创建动画的方式。框架架构概览统一基类与参数管理Berry Animation Framework 采用统一架构所有视觉元素都继承自基类animation.animation并由一个共享的参数管理系统支撑。其类层级结构详见 Animation_Class_Hierarchy.md可概括为parameterized_object基类参数管理与可播放接口 │ ├── Animation所有视觉元素的统一基类 │ ├── engine_proxy渲染与编排组合 │ ├── solid / crenel / breathe / beacon / comet / twinkle │ ├── gradient / palette_gradient / palette_meter / rich_palette │ └── ... ├── sequence_manager动画序列编排 └── Value ProvidersVALUE_PROVIDER true ├── static_value / strip_length / iteration_number ├── oscillator_valuesmooth / triangle / ramp / elastic / bounce 等波形 └── color_providercolor / brightness 参数含 LUT 机制从源码看参数管理的核心实现在 parameterized_object.be它持有values参数存储表通过member()/setmember()虚拟成员访问实现obj.param_name语法并通过_init_parameter_values()沿类继承链向上收集各层PARAMS中的默认值完成初始化。这意味着子类无需也不能为参数声明实例变量——参数完全由虚拟系统托管。引擎侧animation_engine.be 定义了AnimationEnginecreate_engine工厂默认节流TICK_MS 50毫秒通过tasmota.add_fast_loop注册回调每个 tick 依次执行事件处理、root_animation.update()、渲染混合与push_pixels_buffer_argb/show()硬件输出。动画与序列管理器统一由engine.add(obj)挂载内部由 engine_proxy.be 的add()按类型分发到 animations / sequences / value_providers 三个列表这也印证了本文文档中engine.add(anim)统一方法的表述。动画类的基本结构模板一个符合新规范的自定义动画类模板如下源码路径可对照 beacon.be 的真实实现# solidify:MyAnimation,weak class MyAnimation : animation.animation # NO instance variables for parameters - they are handled by the virtual parameter system # Parameter definitions following the new specification static var PARAMS { my_param1: {default: default_value, type: string}, my_param2: {min: 0, max: 255, default: 100, type: int} # Do NOT include inherited Animation parameters here } def init(engine) # Engine parameter is MANDATORY and cannot be nil super(self).init(engine) # Only initialize non-parameter instance variables (none in this example) # Parameters are handled by the virtual parameter system end # Handle parameter changes (optional) def on_param_changed(name, value) # Add custom logic for parameter changes if needed # Parameter validation is handled automatically by the framework end # Update animation state (no return value needed) def update(time_ms) super(self).update(time_ms) # Your update logic here end def render(frame, time_ms, strip_length) if !self.is_running || frame nil return false end # Use virtual parameter access - automatically resolves value_providers var param1 self.my_param1 var param2 self.my_param2 # Use strip_length parameter instead of self.engine.strip_length for performance # Your rendering logic here # ... return true end # NO setter methods needed - use direct virtual parameter assignment: # obj.my_param1 value # obj.my_param2 value def tostring() return fMyAnimation(param1{self.my_param1}, param2{self.my_param2}, running{self.is_running}) end end模板中的几个关键约定# solidify:MyAnimation,weakTasmota 的 solidify 指令用于将该 Berry 类在编译期固化为 C 代码以提升性能weak表示可被覆盖。框架主入口 animation.be 同样以# solidify:animation,weak固化整个animation模块。构造函数只接收 engineinit(engine)中必须先调用super(self).init(engine)否则参数系统未初始化。update(time_ms)无需返回值基类Animation.update()负责duration/loop生命周期逻辑见 animation_base.be子类只需叠加自己的状态推进逻辑。render()返回布尔值返回true表示本帧修改了帧缓冲false表示未渲染如未运行或 frame 为空。PARAMS 参数系统静态参数定义PARAMS静态变量定义了动画类专属的全部参数。该系统的能力包括参数校验min/max 约束与类型检查默认值处理初始化时自动填充虚拟参数访问通过 getmember/setmember 实现obj.param_name语法value_provider 自动解析读取参数时自动解析动态值。从 parameterized_object.be 可以看到默认值初始化机制_init_parameter_values()使用introspect沿classof(self)向上遍历类层级逐层读取各类的PARAMS并填充default子类默认值优先已设置的参数不会被覆盖。参数定义格式static var PARAMS { parameter_name: { default: default_value, # Default value (optional) min: minimum_value, # Minimum value for integers (optional) max: maximum_value, # Maximum value for integers (optional) enum: [val1, val2, val3], # Valid enum values (optional) type: parameter_type, # Expected type (optional) nillable: true # Whether nil values are allowed (optional) } }支持的类型int整数值未指定 type 时的默认类型string字符串值bool布尔值true/falsebytes字节对象使用isinstance()校验instance对象实例any任意类型不做类型校验。需要特别说明的是类型归一化与兼容在 parameterized_object.be 的_validate_param()中time、percentage、color被归一化为intpalette被归一化为bytes此外int参数接受real浮点值并自动做int(math.round(value))转换。bytes类型校验则通过isinstance(value, bytes)完成。约束的二进制编码底层机制为在 ESP32 嵌入式环境高效存储与传输PARAMS中的约束会被 param_encoder.be 编码为紧凑的bytes()格式详见 parameterized_object.be 的格式注释字节 0约束掩码位字段Bit 0 (0x01)has_minBit 1 (0x02)has_maxBit 2 (0x04)has_defaultBit 3 (0x08)has_explicit_typeBit 4 (0x10)has_enumBit 5 (0x20)is_nillable后续字节带类型前缀的值序列min、max、default、enum每个值由[type_byte][value_data]组成。值类型码包括 int8/int16/int32/string/bytes/bool/nil显式类型码则映射 int/string/bytes/bool/any/instance/function。例如{min: 0, max: 255, default: 128}编码为bytes(07 00 00 01 00FF 00 0080)8 字节{enum: [1, 2, 3], default: 1}编码为bytes(0C 00 01 03 00 01 00 02 00 03)10 字节。框架通过静态方法constraint_mask(encoded, min)检查字段是否存在、constraint_find(encoded, min, default)提取字段值均只做字节级直读、无需完整解码保证运行效率。重要规则不要包含继承参数Animation基类的参数id、priority、duration、loop、opacity、color由框架自动处理见 animation_base.be只定义类专属参数无构造器参数映射新系统只使用 engine 唯一参数的构造器参数通过虚拟成员访问obj.param_name。构造函数实现Engine-Only 构造器模式def init(engine) # 1. ALWAYS call super with engine (engine is the ONLY parameter) super(self).init(engine) # 2. Initialize non-parameter instance variables only self.internal_state initial_value self.buffer nil # Do NOT initialize parameters here - they are handled by the virtual system end从 parameterized_object.be 可见init(engine)会强制校验engine非空且为实例否则抛出value_error: missing engine parameter随后初始化values表、is_running状态并调用_init_parameter_values()填充默认值。这解释了为什么Engine 参数是 MANDATORY 且不能为 nil。参数变更处理def on_param_changed(name, value) # Optional method to handle parameter changes if name scale # Recalculate internal state when scale changes self._update_internal_buffers() elif name color # Handle color changes self._invalidate_color_cache() end end该回调在_set_parameter_value()中于参数写入后被自动调用见 parameterized_object.be。在颜色提供器场景中这也是触发 LUT 失效_lut_dirty true的推荐位置。与旧系统的关键差异Engine-only 构造器构造器只接收 engine 参数无参数初始化参数由调用方通过虚拟成员赋值设置参数无实例变量参数完全由虚拟系统托管自动校验基于 PARAMS 约束自动完成。Value Provider 集成自动 value_provider 解析虚拟参数系统在访问参数时自动解析 value_provider——读取到的是当前时刻的解析值而非 provider 对象本身def render(frame, time_ms, strip_length) # Virtual parameter access automatically resolves value_providers var color self.color # Returns current color value, not the provider var position self.pos # Returns current position value var size self.size # Returns current size value # Use strip_length parameter (computed once by engine_proxy) instead of self.engine.strip_length # Use resolved values in rendering logic for i: position..(position size - 1) if i 0 i strip_length frame.set_pixel_color(i, color) end end return true end其底层实现在 parameterized_object.be 的member()方法中当values表中存储的值是instance类型时会调用resolve_value()而resolve_value()见 L360-L377先通过animation.is_value_provider(value)判断该函数定义于 value_provider.be非 nil、是parameterized_object实例且VALUE_PROVIDER true是则调用value.produce_value(name, time_ms)获取动态值若解析结果为 nil 且参数非 nillable 但存在 default则回退到默认值。设置动态参数用户可以用同一套语法同时设置静态值与 value_provider# Create animation var anim animation.my_animation(engine) # Static values anim.color 0xFFFF0000 anim.pos 5 anim.size 3 # Dynamic values anim.color animation.smooth(0xFF000000, 0xFFFFFFFF, 2000) anim.pos animation.triangle(0, 29, 3000)上述smooth与triangle实际是 oscillator_value_provider.be 中定义在oscillator_value之上的便捷工厂smooth()内部创建oscillator_value并设置form 4COSINE 余弦波形L198-L202triangle()设置form 2TRIANGLE 三角波L288-L292。同一文件还提供了ramp/sawtooth锯齿、square方波、ease_in/ease_out、elastic、bounce、sine_osc等波形工厂均可作为动态参数使用。性能优化缓存参数值对于性能关键路径建议将虚拟参数访问结果缓存到局部变量避免循环内多次触发member()的解析开销def render(frame, time_ms, strip_length) # Cache parameter values to avoid multiple virtual member access var current_color self.color var current_pos self.pos var current_size self.size # Use cached values in loops for i: current_pos..(current_pos current_size - 1) if i 0 i strip_length frame.set_pixel_color(i, current_color) end end return true end颜色提供器 LUT 查表优化对于执行昂贵颜色计算如调色板插值的颜色提供器基类color_provider提供了 LUTLookup Table缓存机制# solidify:MyColorProvider,weak class MyColorProvider : animation.color_provider # Instance variables (all should start with underscore) var _cached_data # Your custom cached data def init(engine) super(self).init(engine) # Initializes _color_lut and _lut_dirty self._cached_data nil end # Mark LUT as dirty when parameters change def on_param_changed(name, value) super(self).on_param_changed(name, value) if name colors || name transition_type self._lut_dirty true # Inherited from color_provider end end # Rebuild LUT when needed def _rebuild_color_lut() # Allocate LUT (e.g., 129 entries * 4 bytes 516 bytes) if self._color_lut nil self._color_lut bytes() self._color_lut.resize(129 * 4) end # Pre-compute colors for values 0, 2, 4, ..., 254, 255 var i 0 while i 128 var value i * 2 var color self._compute_color_expensive(value) self._color_lut.set(i * 4, color, 4) i 1 end # Add final entry for value 255 var color_255 self._compute_color_expensive(255) self._color_lut.set(128 * 4, color_255, 4) self._lut_dirty false end # Update method checks if LUT needs rebuilding def update(time_ms) if self._lut_dirty || self._color_lut nil self._rebuild_color_lut() end return self.is_running end # Fast color lookup using LUT def get_color_for_value(value, time_ms) # Build LUT if needed (lazy initialization) if self._lut_dirty || self._color_lut nil self._rebuild_color_lut() end # Map value to LUT index (divide by 2, special case for 255) var lut_index value 1 if value 255 lut_index 128 end # Retrieve pre-computed color from LUT var color self._color_lut.get(lut_index * 4, 4) # Apply brightness scaling using static method (only if not 255) var brightness self.brightness if brightness ! 255 return animation.color_provider.apply_brightness(color, brightness) end return color end # Access LUT from outside (returns bytes() or nil) # Inherited from color_provider: get_lut() endLUT 机制的基类支撑在 color_provider.be它声明了_color_lut与_lut_dirty两个实例变量、get_lut()访问器以及brightness0-255默认 255参数。LUT 优势框架文档给出的经验数据适用于昂贵颜色计算的典型场景昂贵颜色计算提速 5-10 倍渲染期 CPU 占用降低即使颜色逻辑复杂也能保持动画平滑内存占用小129 项通常约 516 字节。何时使用 LUT需二分查找的调色板插值复杂颜色变换亮度计算任何昂贵的逐像素颜色计算。LUT 使用准则以最大亮度存储颜色查表后再做缩放使用 2 步长分辨率0, 2, 4, ..., 254, 255节省内存影响颜色计算的参数变化时使 LUT 失效若亮度在查表后应用则亮度变化不触发失效。亮度处理color_provider基类内置brightness参数0-255默认 255以及用于亮度缩放的静态方法实现见 color_provider.be其通过tasmota.scale_uint对 R/G/B 各通道缩放并保留 alpha 通道# Static method for brightness scaling (only scales if brightness ! 255) animation.color_provider.apply_brightness(color, brightness)最佳实践以最大亮度255存储 LUT 颜色查表后使用静态方法应用亮度缩放仅当brightness ! 255时才调用静态方法避免不必要开销性能关键的内联代码可直接内联亮度计算而非调用静态方法亮度变化不使 LUT 失效因为亮度在查表后应用。参数访问直接虚拟成员赋值新系统使用直接参数赋值替代 setter 方法# Create animation var anim animation.my_animation(engine) # Direct parameter assignment (recommended) anim.color 0xFF00FF00 anim.pos 10 anim.size 5 # Method chaining is not needed - just set parameters directly其内部走setmember()→has_param()→_set_parameter_value()链路见 parameterized_object.be若参数名不在类层级中会抛出attribute_error。参数校验参数系统根据 PARAMS 约束自动校验# This will raise an exception due to min: 0 constraint anim.size -1 # Raises value_error # This will be accepted anim.size 5 # Parameter updated successfully # Method-based setting returns true/false for validation var success anim.set_param(size, -1) # Returns false, no exception_validate_param()parameterized_object.be依次处理value_provider 实例直接放行 → nil 值nillable 放行 / 有默认值则回退默认值 / 否则抛value_error→ 类型校验含real→int自动取整、bytes的isinstance校验→ int 参数的 min/max 范围校验 → enum 枚举校验。set_param()方法则以try/except捕获value_error返回false适用于不希望抛异常的调用场景。访问原始参数# Get current parameter value (resolved if value_provider) var current_color anim.color # Get raw parameter (returns value_provider if set) var raw_color anim.get_param(color) # Check if parameter is a value_provider if animation.is_value_provider(raw_color) print(Color is dynamic) else print(Color is static) endget_param()返回values表中存储的原始值可能是 value_provider 实例与member()的解析值语义不同见 parameterized_object.be。渲染实现帧缓冲操作frame_bufferframe_buffer.be以bytes对象存储像素每个像素 4 字节 ARGB0xAARRGGBB提供set_pixel_color、get_pixel_color、fill_pixels、clear、resize等操作def render(frame, time_ms, strip_length) if !self.is_running || frame nil return false end # Resolve dynamic parameters var color self.resolve_value(self.color, color, time_ms) var opacity self.resolve_value(self.opacity, opacity, time_ms) # Render your effect using strip_length parameter for i: 0..(strip_length-1) var pixel_color calculate_pixel_color(i, time_ms) frame.set_pixel_color(i, pixel_color) end # Apply opacity if not full (supports numbers, animations) if opacity 255 frame.apply_opacity(opacity) end return true # Frame was modified endopacity值得一提基类Animation的post_render()animation_base.be会在render()之后统一处理透明度——数值模式调用frame.apply_opacity做均匀透明度动画模式则渲染一个透明度动画到独立opacity_frame并作为遮罩应用_apply_opacityL111-L137。常见渲染模式填充模式# Fill entire frame with color frame.fill_pixels(color)基于位置的效果# Render at specific positions var start_pos self.resolve_value(self.pos, pos, time_ms) var size self.resolve_value(self.size, size, time_ms) for i: 0..(size-1) var pixel_pos start_pos i if pixel_pos 0 pixel_pos frame.width frame.set_pixel_color(pixel_pos, color) end end渐变效果# Create gradient across frame for i: 0..(frame.width-1) var progress i / (frame.width - 1.0) # 0.0 to 1.0 var interpolated_color interpolate_color(start_color, end_color, progress) frame.set_pixel_color(i, interpolated_color) end完整示例beacon 动画类以下完整示例综合展示了上述全部概念对应仓库真实实现 beacon.be文档版本略作教学简化# solidify:beacon,weak class beacon : animation.animation # NO instance variables for parameters - they are handled by the virtual parameter system # Parameter definitions following the new specification static var PARAMS { color: {default: 0xFFFFFFFF}, back_color: {default: 0xFF000000}, pos: {default: 0}, beacon_size: {min: 0, default: 1}, slew_size: {min: 0, default: 0} } # Initialize a new Pulse Position animation # Engine parameter is MANDATORY and cannot be nil def init(engine) # Call parent constructor with engine (engine is the ONLY parameter) super(self).init(engine) # Only initialize non-parameter instance variables (none in this case) # Parameters are handled by the virtual parameter system end # Handle parameter changes (optional - can be removed if no special handling needed) def on_param_changed(name, value) # No special handling needed for this animation # Parameter validation is handled automatically by the framework end # Render the pulse to the provided frame buffer def render(frame, time_ms, strip_length) if frame nil return false end var pixel_size strip_length # Use virtual parameter access - automatically resolves value_providers var back_color self.back_color var pos self.pos var slew_size self.slew_size var beacon_size self.beacon_size var color self.color # Fill background if not transparent if back_color ! 0xFF000000 frame.fill_pixels(back_color) end # Calculate pulse boundaries var pulse_min pos var pulse_max pos beacon_size # Clamp to frame boundaries if pulse_min 0 pulse_min 0 end if pulse_max pixel_size pulse_max pixel_size end # Draw the main pulse var i pulse_min while i pulse_max frame.set_pixel_color(i, color) i 1 end # Draw slew regions if slew_size 0 if slew_size 0 # Left slew (fade from background to pulse color) var left_slew_min pos - slew_size var left_slew_max pos if left_slew_min 0 left_slew_min 0 end if left_slew_max pixel_size left_slew_max pixel_size end i left_slew_min while i left_slew_max # Calculate blend factor var blend_factor tasmota.scale_uint(i, pos - slew_size, pos - 1, 255, 0) var alpha 255 - blend_factor var blend_color (alpha 24) | (color 0x00FFFFFF) var blended_color frame.blend(back_color, blend_color) frame.set_pixel_color(i, blended_color) i 1 end # Right slew (fade from pulse color to background) var right_slew_min pos beacon_size var right_slew_max pos beacon_size slew_size if right_slew_min 0 right_slew_min 0 end if right_slew_max pixel_size right_slew_max pixel_size end i right_slew_min while i right_slew_max # Calculate blend factor var blend_factor tasmota.scale_uint(i, pos beacon_size, pos beacon_size slew_size - 1, 0, 255) var alpha 255 - blend_factor var blend_color (alpha 24) | (color 0x00FFFFFF) var blended_color frame.blend(back_color, blend_color) frame.set_pixel_color(i, blended_color) i 1 end end return true end # NO setter methods - use direct virtual parameter assignment instead: # obj.color value # obj.pos value # obj.beacon_size value # obj.slew_size value # String representation of the animation def tostring() return fbeacon(color0x{self.color :08x}, pos{self.pos}, beacon_size{self.beacon_size}, slew_size{self.slew_size}) end end # Export class directly - no redundant factory function needed return {beacon: beacon}与仓库真实实现的差异说明当前仓库中的 beacon.be 在文档版基础上还额外提供了right_edge参数{enum: [0, 1], default: 0}用于控制pos是信标的左边缘还是从右端计数的右边缘并改用frame.fill_pixels(frame.pixels, color, min, max)绘制主体、frame.blend_linear(back_color, color, blend_factor)与tasmota.scale_int完成 slew 区渐变混合。同时基类Animation本身也定义了color参数默认0x00000000透明见 animation_base.be因此 beacon 类中重复定义color仅为教学展示实际应从 PARAMS 中移除。beacon还通过return {beacon: beacon}直接导出类无需冗余工厂函数随后由 animation.be 以import animations/beacon as beacon; register_to_animation(beacon)注册到统一的animation命名空间用户即可通过animation.beacon(engine)实例化。测试你的动画类单元测试为动画编写全面测试仓库对应参考 beacon_animation_test.be其中验证了参数默认值、参数更新、负数 min 约束拒绝、渲染返回 true、像素颜色等断言import animation def test_my_animation() # Create LED strip and engine for testing var strip global.Leds(10) # Use built-in LED strip for testing var engine animation.create_engine(strip) # Test basic construction var anim animation.my_animation(engine) assert(anim ! nil, Animation should be created) # Test parameter setting anim.color 0xFFFF0000 assert(anim.color 0xFFFF0000, Color should be set) # Test parameter updates anim.color 0xFF00FF00 assert(anim.color 0xFF00FF00, Color should be updated) # Test value providers var dynamic_color animation.smooth(engine) dynamic_color.min_value 0xFF000000 dynamic_color.max_value 0xFFFFFFFF dynamic_color.duration 2000 anim.color dynamic_color var raw_color anim.get_param(color) assert(animation.is_value_provider(raw_color), Should accept value provider) # Test rendering var frame animation.frame_buffer(10) anim.start() var result anim.render(frame, 1000, engine.strip_length) assert(result true, Should render successfully) print(✓ All tests passed) end test_my_animation()集成测试与动画引擎联合测试注意测试代码中tasmota.delay(3000)仅为演示延时实际运行在 Tasmota 环境var strip global.Leds(30) # Use built-in LED strip var engine animation.create_engine(strip) var anim animation.my_animation(engine) # Set parameters anim.color 0xFFFF0000 anim.pos 5 anim.beacon_size 3 engine.add(anim) # Unified method for animations and sequence managers engine.run() # Let it run for a few seconds tasmota.delay(3000) engine.stop() print(Integration test completed)引擎生命周期对应 animation_engine.berun()L124-L140设置is_running、启动根engine_proxy并通过tasmota.add_fast_loop注册on_tick闭包stop()L145-L154则移除 fast_loop。每个 tick 内on_tick()依据tick_ms节流随后_update_and_render()依次执行root_animation.update()→ 清空主缓冲 →root_animation.render()混合渲染 →_output_to_strip()push_pixels_buffer_argbshow()输出到硬件。最佳实践性能尽量在render()中减少计算量尽可能缓存解析后的参数值使用整数运算代替浮点避免在渲染循环中分配内存。内存管理尽量复用对象使用完毕后清除对大对象的引用常量使用静态变量。代码组织相关参数分组定义使用描述性变量名复杂算法添加注释遵循 Berry 命名规范。错误处理在构造函数中校验参数优雅处理边界情况render()出错时返回false使用有意义的错误信息。发布你的动画类创建新动画类后按以下步骤将其纳入框架发布流程对应 animation.be 的模块注册机制与 Dsl_Reference.md 的 DSL 说明加入 animation 模块在animation.be中 import 你的类文件并调用register_to_animation()注册到统一命名空间创建工厂函数遵循 engine-first 模式例如仓库中create_engine、smooth、triangle等工厂均接收 engine 作为参数添加 DSL 支持确保 transpiler 能识别你的工厂函数详见 Dsl_Transpilation.md文档化参数在类层级文档中记录参数说明见 Animation_Class_Hierarchy.md用 DSL 测试确保用户可以通过声明式方式访问你的动画。请记住用户应主要通过 DSL 与动画交互程序化 API 主要面向框架开发与高级集成场景DSL 入门可参考 Quick_Start.md。总结本文围绕 Tasmota Berry Animation 框架的动画类开发规范从统一架构、PARAMS 参数系统含底层字节编码约束、engine-only 构造器模式、value_provider 自动解析、LUT 查表优化、帧缓冲渲染、完整 beacon 示例到测试与发布流程构建了完整的开发闭环。结合 parameterized_object.be、animation_base.be、color_provider.be、animation_engine.be 与 beacon.be 等源码你可以开发出与框架参数系统、动态取值器和渲染管线无缝集成、可直接通过 DSL 对外发布的专业级动画类。【免费下载链接】TasmotaAlternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at项目地址: https://gitcode.com/GitHub_Trending/ta/Tasmota创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表