Skip to content

Conversation

@asxzy
Copy link

@asxzy asxzy commented Jan 13, 2026

Summary

  • Adds support for AK001-ZJ21413 (0xB6) - Surplife outdoor permanent LED lighting with RGBW LEDs
  • Introduces new PROTOCOL_LEDENET_EXTENDED_CUSTOM protocol for devices that only respond with extended state format (0xEA 0x81)
  • Implements extended custom effect commands (0xE1 0x21/0x22) supporting 24 unique animated patterns with customizable colors, speed, density, and direction

New Features

Device Support:

  • Surplife outdoor permanent lighting (model 0xB6) with single white LED channel

Extended Custom Effects (0xE1 0x21):

  • 24 animated patterns: wave, meteor, streamer, chase, breathe, strobe, fireworks, comet, rainbow bridge, and more
  • Configurable parameters: up to 8 colors, speed (0-100), density (0-100), direction (L→R/R→L)
  • 2 static patterns: static gradient and static fill

Segment Colors (0xE1 0x22):

  • Set individual colors for each of 20 segments on the light strip

New API Methods:

  • async_set_extended_custom_effect(pattern_id, colors, speed, density, direction, option)
  • async_set_custom_segment_colors(segments)

Technical Changes

  • New ProtocolLEDENETExtendedCustom class extending ProtocolLEDENET25Byte
  • Uses HSV+W color format internally: [H/2, S, V, 0x00, W] (5 bytes per color)
  • Fixed state message processing to check extended state format before standard format
  • Added extract_model_version_from_state() helper for consistent model/version extraction
  • construct_levels_change() uses STATIC_FILL pattern (0x66) for solid RGBW colors

Test plan

  • Unit tests for 0xB6 device state parsing and protocol detection
  • Tests for extended custom effect command construction
  • Tests for segment color command construction
  • Tests for RGBW level changes via STATIC_FILL pattern
  • Manual testing with physical Surplife device

@codecov
Copy link

codecov bot commented Jan 13, 2026

Codecov Report

❌ Patch coverage is 69.03226% with 192 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
flux_led/fluxled.py 23.11% 148 Missing and 15 partials ⚠️
flux_led/timer.py 90.66% 6 Missing and 8 partials ⚠️
flux_led/aiodevice.py 74.07% 6 Missing and 1 partial ⚠️
flux_led/protocol.py 95.27% 6 Missing ⚠️
flux_led/base_device.py 95.74% 1 Missing and 1 partial ⚠️
Files with missing lines Coverage Δ
flux_led/const.py 100.00% <100.00%> (ø)
flux_led/device.py 73.46% <100.00%> (+0.98%) ⬆️
flux_led/models_db.py 98.98% <100.00%> (+0.08%) ⬆️
flux_led/pattern.py 100.00% <100.00%> (ø)
flux_led/base_device.py 95.62% <95.74%> (-0.01%) ⬇️
flux_led/protocol.py 98.61% <95.27%> (-0.53%) ⬇️
flux_led/aiodevice.py 94.93% <74.07%> (-1.31%) ⬇️
flux_led/timer.py 81.77% <90.66%> (+20.13%) ⬆️
flux_led/fluxled.py 22.66% <23.11%> (+22.66%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@asxzy asxzy marked this pull request as ready for review January 13, 2026 21:30
@asxzy asxzy marked this pull request as draft January 13, 2026 22:04
@asxzy asxzy force-pushed the asxzy/add_device_AK001-ZJ21413 branch from 47b30e7 to 995c338 Compare January 13, 2026 22:48
@asxzy asxzy marked this pull request as ready for review January 13, 2026 22:57
@asxzy
Copy link
Author

asxzy commented Jan 13, 2026

Hi codeowners,

I have a outdoor light by Surplife installed few days ago. The controller is not supported by the project -- "Cannot determine protocol" . The protocol is supported, but the device is not mapped in. I did most of the code via AI, and the feature is verified manually.

Let me know if anything is missing before merging it in. Once it is in, I can request change to integrate this with home assistant.

Yi

@asxzy asxzy marked this pull request as draft January 13, 2026 23:14
Comment on lines 733 to 740
# Handle protocol determination for extended state
if (
self._determine_protocol_future
and not self._determine_protocol_future.done()
):
assert self._protocol is not None
self._set_protocol_from_msg(msg, self._protocol.name)
self._determine_protocol_future.set_result(True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like duplicated code.. I think we have this elsewhere

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the feedback. I will update the code later today.

Comment on lines 1260 to 1277
if len(full_msg) >= 20 and full_msg[0] == 0xEA and full_msg[1] == 0x81:
# Extended state format - use extended state byte positions
self._model_num = full_msg[LEDENET_EXTENDED_STATE_MODEL_POS]
self._model_data = get_model(self._model_num, fallback_protocol)
version_num = (
full_msg[LEDENET_EXTENDED_STATE_VERSION_POS]
if len(full_msg) > LEDENET_EXTENDED_STATE_VERSION_POS
else 1
)
else:
# Standard state format - use standard state byte positions
self._model_num = full_msg[LEDENET_STATE_MODEL_POS]
self._model_data = get_model(self._model_num, fallback_protocol)
version_num = (
full_msg[LEDENET_STATE_VERSION_POS]
if len(full_msg) > LEDENET_STATE_VERSION_POS
else 1
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be extracted into a helper

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for the Surplife outdoor permanent LED lighting system (model 0xB6, AK001-ZJ21413). The device exclusively uses the extended state format (0xEA 0x81) introduced in PR #428, requiring modifications to message handling priority and protocol determination logic to support devices that only respond with this format.

Changes:

  • Modified message processing order to check extended state format before regular state format to support extended-state-only devices
  • Enhanced protocol determination to extract model and version information from extended state messages
  • Added byte position constants for parsing state messages to improve code maintainability
  • Added complete model and hardware definitions for the 0xB6 device with RGB+CCT color modes

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
flux_led/aiodevice.py Reordered message processing to prioritize extended state format and added protocol determination support for extended state responses
flux_led/base_device.py Enhanced _set_protocol_from_msg() to handle both standard and extended state formats using byte position constants
flux_led/protocol.py Added byte position constants and extended state validation methods to base protocol classes
flux_led/models_db.py Added model definition for 0xB6 Surplife device and hardware configuration entry
tests/test_sync.py Added sync API test for 0xB6 device identification and protocol validation
tests/test_aio.py Added async API tests covering extended state setup and protocol validation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


def is_valid_extended_state_response(self, raw_state: bytes) -> bool:
"""Check if a state response is valid."""
return raw_state[0] == 0xEA and raw_state[1] == 0x81 and len(raw_state) >= 20
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method lacks bounds checking before accessing array indices. If raw_state has length less than 2, accessing raw_state[0] and raw_state[1] could raise an IndexError. The length check (len(raw_state) >= 20) happens after the index access. The checks should be reordered to: return len(raw_state) >= 20 and raw_state[0] == 0xEA and raw_state[1] == 0x81

Suggested change
return raw_state[0] == 0xEA and raw_state[1] == 0x81 and len(raw_state) >= 20
return len(raw_state) >= 20 and raw_state[0] == 0xEA and raw_state[1] == 0x81

Copilot uses AI. Check for mistakes.
Comment on lines 1438 to 1442
if calls == 2:
# Standard state response (sync mode expects this during setup)
# Real device uses extended state, but mocking standard for compatibility
self.assertEqual(expected, 12)
return bytearray(b"\x23\x61\x24\x64\x00\x00\x00\x00\x01\x00\xf0\x34")
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test comment states this device "ONLY responds with extended state format" but the test actually uses a standard state response (0x81) instead of extended state (0xEA 0x81). This doesn't accurately test the device behavior described in the PR. Consider adding a test that properly validates the device with extended state format to ensure the sync API can handle extended-state-only devices.

Copilot uses AI. Check for mistakes.
@asxzy
Copy link
Author

asxzy commented Jan 15, 2026

Hi @bdraco , I originally thought I can add the device in with existing protocol and call it a day. The existing protocol can perform on/off solid color change well, but the app is providing more features that is not use by other devices. When I doing more package capturing and analysis, I found this device is taking more command that change the pattern and segments. I think it will be cleaner to start a new protocol instead. Will update this PR later.

@asxzy asxzy marked this pull request as ready for review January 16, 2026 01:59
@asxzy asxzy requested a review from bdraco January 16, 2026 01:59
@asxzy
Copy link
Author

asxzy commented Jan 16, 2026

@bdraco codecov failed and I can't see the detailed message. Let me know if anything need to be adjusted.

@asxzy
Copy link
Author

asxzy commented Jan 16, 2026

flux_led/fluxled.py does not have unit test and it is out of scope of this PR. I will skip it for now.

@asxzy
Copy link
Author

asxzy commented Jan 19, 2026

@bdraco let me know if anything is missing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants