Backend performance and code quality improvements
Performance (hot path): - Fix double brightness: removed duplicate scaling from 9 device clients (wled, adalight, ambiled, openrgb, hue, spi, chroma, gamesense, usbhid, espnow) — processor loop is now the single source of brightness - Bounded send_timestamps deque with maxlen, removed 3 cleanup loops - Running FPS sum O(1) instead of sum()/len() O(n) per frame - datetime.now(timezone.utc) → time.monotonic() with lazy conversion - Device info refresh interval 30 → 300 iterations - Composite: gate layer_snapshots copy on preview client flag - Composite: versioned sub_streams snapshot (copy only on change) - Composite: pre-resolved blend methods (dict lookup vs getattr) - ApiInput: np.copyto in-place instead of astype allocation Code quality: - BaseJsonStore: RLock on get/delete/get_all/count (was created but unused) - EntityNotFoundError → proper 404 responses across 15 route files - Remove 21 defensive getattr(x,'tags',[]) — field guaranteed on all models - Fix Dict[str,any] → Dict[str,Any] in template/audio_template stores - Log 4 silenced exceptions (automation engine, metrics, system) - ValueStream.get_value() now @abstractmethod - Config.from_yaml: add encoding="utf-8" - OutputTargetStore: remove 25-line _load override, use _legacy_json_keys - BaseJsonStore: add _legacy_json_keys for migration support - Remove unnecessary except Exception→500 from postprocessing list endpoint Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -168,9 +168,7 @@ class AdalightClient(LEDClient):
|
||||
else:
|
||||
arr = np.array(pixels, dtype=np.uint16)
|
||||
|
||||
if brightness < 255:
|
||||
arr = arr * brightness // 255
|
||||
|
||||
# Note: brightness already applied by processor loop (_cached_brightness)
|
||||
np.clip(arr, 0, 255, out=arr)
|
||||
rgb_bytes = arr.astype(np.uint8).tobytes()
|
||||
return self._header + rgb_bytes
|
||||
|
||||
@@ -40,9 +40,7 @@ class AmbiLEDClient(AdalightClient):
|
||||
else:
|
||||
arr = np.array(pixels, dtype=np.uint16)
|
||||
|
||||
if brightness < 255:
|
||||
arr = arr * brightness // 255
|
||||
|
||||
# Note: brightness already applied by processor loop (_cached_brightness)
|
||||
# Clamp to 0–250: values >250 are command bytes in AmbiLED protocol
|
||||
np.clip(arr, 0, 250, out=arr)
|
||||
rgb_bytes = arr.astype(np.uint8).tobytes()
|
||||
|
||||
@@ -145,7 +145,7 @@ class ChromaClient(LEDClient):
|
||||
else:
|
||||
pixel_arr = np.array(pixels, dtype=np.uint8)
|
||||
|
||||
bri_scale = brightness / 255.0
|
||||
# Note: brightness already applied by processor loop (_cached_brightness)
|
||||
device_info = CHROMA_DEVICES.get(self._chroma_device_type)
|
||||
if not device_info:
|
||||
return False
|
||||
@@ -156,10 +156,7 @@ class ChromaClient(LEDClient):
|
||||
# Chroma uses BGR packed as 0x00BBGGRR integers
|
||||
colors = []
|
||||
for i in range(n):
|
||||
r, g, b = pixel_arr[i]
|
||||
r = int(r * bri_scale)
|
||||
g = int(g * bri_scale)
|
||||
b = int(b * bri_scale)
|
||||
r, g, b = int(pixel_arr[i][0]), int(pixel_arr[i][1]), int(pixel_arr[i][2])
|
||||
colors.append(r | (g << 8) | (b << 16))
|
||||
|
||||
# Pad to max_leds if needed
|
||||
|
||||
@@ -115,7 +115,8 @@ class ESPNowClient(LEDClient):
|
||||
else:
|
||||
pixel_bytes = bytes(c for rgb in pixels for c in rgb)
|
||||
|
||||
frame = _build_frame(self._peer_mac, pixel_bytes, brightness)
|
||||
# Note: brightness already applied by processor loop; pass 255 to firmware
|
||||
frame = _build_frame(self._peer_mac, pixel_bytes, 255)
|
||||
try:
|
||||
self._serial.write(frame)
|
||||
except Exception as e:
|
||||
|
||||
@@ -187,7 +187,7 @@ class GameSenseClient(LEDClient):
|
||||
else:
|
||||
pixel_arr = np.array(pixels, dtype=np.uint8)
|
||||
|
||||
bri_scale = brightness / 255.0
|
||||
# Note: brightness already applied by processor loop (_cached_brightness)
|
||||
|
||||
# Use average color for single-zone devices, or first N for multi-zone
|
||||
if len(pixel_arr) == 0:
|
||||
@@ -195,9 +195,9 @@ class GameSenseClient(LEDClient):
|
||||
|
||||
# Compute average color for the zone
|
||||
avg = pixel_arr.mean(axis=0)
|
||||
r = int(avg[0] * bri_scale)
|
||||
g = int(avg[1] * bri_scale)
|
||||
b = int(avg[2] * bri_scale)
|
||||
r = int(avg[0])
|
||||
g = int(avg[1])
|
||||
b = int(avg[2])
|
||||
|
||||
event_data = {
|
||||
"game": GAME_NAME,
|
||||
|
||||
@@ -46,13 +46,13 @@ def _build_entertainment_frame(
|
||||
header[15] = 0x00 # reserved
|
||||
|
||||
# Light data
|
||||
bri_scale = brightness / 255.0
|
||||
# Note: brightness already applied by processor loop (_cached_brightness)
|
||||
data = bytearray()
|
||||
for idx, (r, g, b) in enumerate(lights):
|
||||
light_id = idx # 0-based light index in entertainment group
|
||||
r16 = int(r * bri_scale * 257) # scale 0-255 to 0-65535
|
||||
g16 = int(g * bri_scale * 257)
|
||||
b16 = int(b * bri_scale * 257)
|
||||
r16 = int(r * 257) # scale 0-255 to 0-65535
|
||||
g16 = int(g * 257)
|
||||
b16 = int(b * 257)
|
||||
data += struct.pack(">BHHH", light_id, r16, g16, b16)
|
||||
|
||||
return bytes(header) + bytes(data)
|
||||
|
||||
@@ -302,9 +302,7 @@ class OpenRGBLEDClient(LEDClient):
|
||||
return
|
||||
self._last_sent_pixels = pixel_array.copy()
|
||||
|
||||
# Apply brightness scaling after dedup
|
||||
if brightness < 255:
|
||||
pixel_array = (pixel_array.astype(np.uint16) * brightness >> 8).astype(np.uint8)
|
||||
# Note: brightness already applied by processor loop (_cached_brightness)
|
||||
|
||||
# Separate mode: resample full pixel array independently per zone
|
||||
if self._zone_mode == "separate" and len(self._target_zones) > 1:
|
||||
|
||||
@@ -162,7 +162,7 @@ class SPIClient(LEDClient):
|
||||
if not self._connected:
|
||||
return
|
||||
|
||||
bri_scale = brightness / 255.0
|
||||
# Note: brightness already applied by processor loop (_cached_brightness)
|
||||
|
||||
if isinstance(pixels, np.ndarray):
|
||||
pixel_arr = pixels
|
||||
@@ -176,7 +176,7 @@ class SPIClient(LEDClient):
|
||||
except ImportError:
|
||||
return
|
||||
|
||||
self._strip.setBrightness(brightness)
|
||||
self._strip.setBrightness(255)
|
||||
for i in range(min(len(pixel_arr), self._led_count)):
|
||||
r, g, b = pixel_arr[i]
|
||||
self._strip.setPixelColor(i, Color(int(r), int(g), int(b)))
|
||||
@@ -185,7 +185,7 @@ class SPIClient(LEDClient):
|
||||
elif self._spi:
|
||||
# SPI bitbang path: convert RGB to WS2812 wire format
|
||||
# Each bit is encoded as 3 SPI bits: 1=110, 0=100
|
||||
scaled = (pixel_arr[:self._led_count].astype(np.float32) * bri_scale).astype(np.uint8)
|
||||
scaled = pixel_arr[:self._led_count]
|
||||
# GRB order for WS2812
|
||||
grb = scaled[:, [1, 0, 2]]
|
||||
raw_bytes = grb.tobytes()
|
||||
|
||||
@@ -100,7 +100,7 @@ class USBHIDClient(LEDClient):
|
||||
else:
|
||||
pixel_list = list(pixels)
|
||||
|
||||
bri_scale = brightness / 255.0
|
||||
# Note: brightness already applied by processor loop (_cached_brightness)
|
||||
|
||||
# Build HID reports — split across multiple reports if needed
|
||||
# Each report: [REPORT_ID][CMD][OFFSET_LO][OFFSET_HI][COUNT][R G B R G B ...]
|
||||
@@ -119,9 +119,9 @@ class USBHIDClient(LEDClient):
|
||||
|
||||
for i, (r, g, b) in enumerate(chunk):
|
||||
base = 5 + i * 3
|
||||
report[base] = int(r * bri_scale)
|
||||
report[base + 1] = int(g * bri_scale)
|
||||
report[base + 2] = int(b * bri_scale)
|
||||
report[base] = int(r)
|
||||
report[base + 1] = int(g)
|
||||
report[base + 2] = int(b)
|
||||
|
||||
reports.append(bytes(report))
|
||||
offset += len(chunk)
|
||||
|
||||
@@ -378,9 +378,7 @@ class WLEDClient(LEDClient):
|
||||
True if successful
|
||||
"""
|
||||
try:
|
||||
if brightness < 255:
|
||||
pixels = (pixels.astype(np.uint16) * brightness >> 8).astype(np.uint8)
|
||||
|
||||
# Note: brightness already applied by processor loop (_cached_brightness)
|
||||
logger.debug(f"Sending {len(pixels)} LEDs via DDP")
|
||||
self._ddp_client.send_pixels_numpy(pixels)
|
||||
logger.debug(f"Successfully sent pixel colors via DDP")
|
||||
@@ -419,7 +417,7 @@ class WLEDClient(LEDClient):
|
||||
# Build WLED JSON state
|
||||
payload = {
|
||||
"on": True,
|
||||
"bri": int(brightness),
|
||||
"bri": 255, # brightness already applied by processor loop
|
||||
"seg": [
|
||||
{
|
||||
"id": segment_id,
|
||||
@@ -461,9 +459,7 @@ class WLEDClient(LEDClient):
|
||||
else:
|
||||
pixel_array = np.array(pixels, dtype=np.uint8)
|
||||
|
||||
if brightness < 255:
|
||||
pixel_array = (pixel_array.astype(np.uint16) * brightness >> 8).astype(np.uint8)
|
||||
|
||||
# Note: brightness already applied by processor loop (_cached_brightness)
|
||||
self._ddp_client.send_pixels_numpy(pixel_array)
|
||||
|
||||
# ===== LEDClient abstraction methods =====
|
||||
|
||||
Reference in New Issue
Block a user