Skip to content

Commit 5a2c9d4

Browse files
authored
Merge pull request #82 from sirkirby/led-switch-compatibility
Refactor LED toggle functionality in network.py to use DeviceSetLedStatus with fallback
2 parents b7bbb3d + 32930a5 commit 5a2c9d4

1 file changed

Lines changed: 58 additions & 40 deletions

File tree

  • custom_components/unifi_network_rules/udm

custom_components/unifi_network_rules/udm/network.py

Lines changed: 58 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -144,60 +144,78 @@ async def set_device_led(self, device: Device, enable: bool = True) -> bool:
144144
like brightness and color control, a Light entity would be more appropriate.
145145
146146
Implementation:
147-
Sends only essential device fields to avoid InvalidPayload errors on older
148-
UniFi Network versions that reject read-only fields in update requests.
147+
Uses DeviceSetLedStatus with fallback to minimal payload of device endpoint.
149148
"""
150149
try:
151150
device_id = device.id
152-
status = "on" if enable else "off"
153-
154-
# Get device MAC safely for logging
155151
device_raw = getattr(device, 'raw', {}) if hasattr(device, 'raw') else {}
156152
device_mac = device_raw.get('mac', device_raw.get('serial', 'unknown'))
157153

158-
# Create minimal payload with only essential fields to avoid InvalidPayload errors
159-
# Many fields in the full device payload are read-only and cause API errors
160-
device_payload = {
161-
'_id': device_id,
162-
'led_override': status
163-
}
154+
LOGGER.debug("Attempting LED toggle for device %s (MAC: %s) to %s",
155+
device_id, device_mac, "on" if enable else "off")
164156

165-
# Include optional LED fields if they exist in the original data
166-
# to maintain any existing LED configuration
167-
if 'led_override_color' in device_raw:
168-
device_payload['led_override_color'] = device_raw['led_override_color']
169-
if 'led_override_color_brightness' in device_raw:
170-
device_payload['led_override_color_brightness'] = device_raw['led_override_color_brightness']
157+
# Approach 1: Try using DeviceSetLedStatus from aiounifi first
158+
try:
159+
LOGGER.debug("Trying DeviceSetLedStatus approach for %s", device_mac)
160+
161+
# Use 'default' for enabled (normal LED behavior), 'off' for disabled
162+
led_value = "default" if enable else "off"
163+
164+
request = DeviceSetLedStatus.create(device, led_value)
165+
result = await self.controller.request(request)
166+
167+
if result and isinstance(result, dict):
168+
meta = result.get('meta', {})
169+
if meta.get('rc') == 'ok':
170+
LOGGER.debug("DeviceSetLedStatus succeeded for %s with value: %s", device_mac, led_value)
171+
# Update local state
172+
if hasattr(device, 'raw') and device.raw:
173+
device.raw['led_override'] = led_value
174+
return True
175+
else:
176+
LOGGER.debug("DeviceSetLedStatus failed for %s: %s", device_mac, meta)
177+
178+
except Exception as err:
179+
LOGGER.debug("DeviceSetLedStatus approach failed for %s: %s", device_mac, err)
171180

172-
# Use the legacy API endpoint for device updates (not v2)
173-
# Path format: /rest/device/{device_id}
174-
path = f"/rest/device/{device_id}"
175-
request = self.create_api_request("PUT", path, data=device_payload, is_v2=False)
181+
# Approach 2: Fallback to minimal payload of device endpoint
182+
try:
183+
LOGGER.debug("Trying minimal payload approach (original working method) for %s", device_mac)
184+
185+
# Use original working LED values: 'on' for enabled, 'off' for disabled
186+
led_value = "on" if enable else "off"
187+
device_payload = {
188+
'_id': device_id,
189+
'led_override': led_value
190+
}
191+
192+
path = f"/rest/device/{device_id}"
193+
request = self.create_api_request("PUT", path, data=device_payload, is_v2=False)
194+
result = await self.controller.request(request)
195+
196+
if result and isinstance(result, dict):
197+
meta = result.get('meta', {})
198+
if meta.get('rc') == 'ok':
199+
LOGGER.debug("Minimal payload approach succeeded for %s with value: %s", device_mac, led_value)
200+
# Update local state
201+
if hasattr(device, 'raw') and device.raw:
202+
device.raw['led_override'] = led_value
203+
return True
204+
else:
205+
LOGGER.debug("Minimal payload approach failed for %s: %s", device_mac, meta)
206+
207+
except Exception as err:
208+
LOGGER.debug("Minimal payload approach failed for %s: %s", device_mac, err)
176209

177-
result = await self.controller.request(request)
210+
# Both approaches failed
211+
LOGGER.error("Both LED toggle approaches failed for device %s (MAC: %s)", device_id, device_mac)
212+
return False
178213

179-
# Check for successful response
180-
if result and isinstance(result, dict):
181-
meta = result.get('meta', {})
182-
if meta.get('rc') == 'ok':
183-
LOGGER.debug("Device %s LED set to %s", device_mac, status)
184-
# Update the device's local state for immediate feedback
185-
if hasattr(device, 'raw') and device.raw:
186-
device.raw['led_override'] = status
187-
elif hasattr(device, 'led_override'):
188-
device.led_override = status
189-
return True
190-
else:
191-
LOGGER.error("API returned error for device %s LED update: %s", device_mac, meta)
192-
return False
193-
else:
194-
LOGGER.error("Failed to set LED for device %s - unexpected API response", device_mac)
195-
return False
196214
except Exception as err:
197215
# Get device MAC safely for error logging
198216
device_raw = getattr(device, 'raw', {}) if hasattr(device, 'raw') else {}
199217
device_mac = device_raw.get('mac', device_raw.get('serial', 'unknown'))
200-
LOGGER.error("Error setting device LED for %s: %s", device_mac, str(err))
218+
LOGGER.error("Critical error in LED toggle for %s: %s", device_mac, str(err))
201219
return False
202220

203221
async def get_device_led_states(self) -> Dict[str, Dict[str, Any]]:

0 commit comments

Comments
 (0)