""" Combine global, group, device (and optional zone) brightness into one 0–255 wire value. Formula: ``(f1/255) * (f2/255) * ... * (fn/255) * 255`` with integer rounding — one ``b`` sent to the driver. """ from __future__ import annotations from models.device import normalize_mac def clamp255(value) -> int: try: v = int(value) except (TypeError, ValueError): return 255 return max(0, min(255, v)) def multiply_brightness_factors(factors: list) -> int: """Product ``(f1/255)*(f2/255)*...*255``; each factor clamped to 0..255.""" if not factors: return 255 fs = [clamp255(f) for f in factors] if len(fs) == 1: return fs[0] num = 1 for f in fs: num *= f den = 255 ** (len(fs) - 1) return max(0, min(255, (num + den // 2) // den)) def _mac_in_device_list(raw_list, target_mac: str) -> bool: tm = normalize_mac(target_mac) if not tm: return False if not isinstance(raw_list, list): return False for raw in raw_list: if normalize_mac(str(raw)) == tm: return True return False def effective_brightness_for_mac( settings_obj, groups_model, devices_model, mac: str, *, zone_brightness=None, ) -> int: """ Factors (each 0..255): Pi **global_brightness**, each group's **output_brightness** (neutral 255 if the device is in no group), device **output_brightness** (default 255), optional **zone_brightness** from the zone slider when applying live. """ m = normalize_mac(mac) if not m: return 255 g_global = clamp255(settings_obj.get("global_brightness", 255)) dev_doc = devices_model.read(m) if dev_doc is not None and dev_doc.get("output_brightness") is not None: d_b = clamp255(dev_doc.get("output_brightness")) else: d_b = 255 group_factors = [] for _gid, gdoc in groups_model.items(): if not isinstance(gdoc, dict): continue if not _mac_in_device_list(gdoc.get("devices"), m): continue group_factors.append(clamp255(gdoc.get("output_brightness", 255))) if not group_factors: group_factors = [255] factors = [g_global, *group_factors, d_b] if zone_brightness is not None: factors.append(clamp255(zone_brightness)) return multiply_brightness_factors(factors)