Skip to content

Commit

Permalink
Add support for L46N floodlights and possibly the Lorex equivalent (#313
Browse files Browse the repository at this point in the history
)

* Add Dahua floodlight recognition

* Add coaxial calls for newer floods

* FloodLightMode stored on floodlight on/off

* Store floodlight state in coordinator
  • Loading branch information
cchamilt authored Nov 10, 2023
1 parent 7b03493 commit 2257928
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 30 deletions.
35 changes: 22 additions & 13 deletions custom_components/dahua/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ def __init__(self, hass: HomeAssistant, events: list, address: str, port: int, r
# If cleared the time will be 0. The time unit is seconds epoch
self._dahua_event_timestamp: Dict[str, int] = dict()

self._floodlight_mode = 2

super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL_SECONDS)

async def async_start_event_listener(self):
Expand Down Expand Up @@ -253,8 +255,8 @@ async def _async_update_data(self):
is_doorbell = self.is_doorbell()
_LOGGER.info("Device is a doorbell=%s", is_doorbell)

is_amcrest_flood_light = self.is_amcrest_flood_light()
_LOGGER.info("Device is an Amcrest floodlight=%s", is_amcrest_flood_light)
is_flood_light = self.is_flood_light()
_LOGGER.info("Device is a floodlight=%s", is_flood_light)

try:
await self.client.async_get_config_lighting(self._channel, self._profile_mode)
Expand Down Expand Up @@ -327,7 +329,7 @@ async def _async_update_data(self):
if result is not None:
data.update(result)

if self.supports_security_light() or self.is_amcrest_flood_light():
if self.supports_security_light() or self.is_flood_light():
light_v2 = await self.client.async_get_lighting_v2()
if light_v2 is not None:
data.update(light_v2)
Expand Down Expand Up @@ -522,7 +524,8 @@ def supports_siren(self) -> bool:
Returns true if this camera has a siren. For example, the IPC-HDW3849HP-AS-PV does
https://dahuawiki.com/Template:NameConvention
"""
return "-AS-PV" in self.model
m = self.model.upper()
return "-AS-PV" in m or "L46N" in m or m.startswith("W452ASD")

def supports_security_light(self) -> bool:
"""
Expand All @@ -541,9 +544,10 @@ def is_amcrest_doorbell(self) -> bool:
""" Returns true if this is an Amcrest doorbell """
return self.model.upper().startswith("AD")

def is_amcrest_flood_light(self) -> bool:
""" Returns true if this camera is an Amcrest Floodlight camera (eg.ASH26-W) """
return self.model.upper().startswith("ASH26")
def is_flood_light(self) -> bool:
""" Returns true if this camera is an floodlight camera (eg.ASH26-W) """
m = self.model.upper()
return m.startswith("ASH26") or "L26N" in m or "L46N" in m or m.startswith("V261LC") or m.startswith("W452ASD")

def supports_infrared_light(self) -> bool:
"""
Expand All @@ -560,7 +564,7 @@ def supports_illuminator(self) -> bool:
IPC-HDW3849HP-AS-PV does
"""
return not (
self.is_amcrest_doorbell() or self.is_amcrest_flood_light()) and "table.Lighting_V2[{0}][0][0].Mode".format(
self.is_amcrest_doorbell() or self.is_flood_light()) and "table.Lighting_V2[{0}][0][0].Mode".format(
self._channel) in self.data

def is_motion_detection_enabled(self) -> bool:
Expand Down Expand Up @@ -629,12 +633,17 @@ def is_illuminator_on(self) -> bool:

return self.data.get("table.Lighting_V2[{0}][{1}][0].Mode".format(self._channel, profile_mode), "") == "Manual"

def is_amcrest_flood_light_on(self) -> bool:
"""Return true if the amcrest flood light light is on"""
# profile_mode 0=day, 1=night, 2=scene
profile_mode = self.get_profile_mode()
def is_flood_light_on(self) -> bool:

if self._supports_coaxial_control:
#'coaxialControlIO.cgi?action=getStatus&channel=1'
return self.data.get("status.status.WhiteLight", "") == "On"
else:
"""Return true if the amcrest flood light light is on"""
# profile_mode 0=day, 1=night, 2=scene
profile_mode = self.get_profile_mode()

return self.data.get(f'table.Lighting_V2[{self._channel}][{profile_mode}][1].Mode') == "Manual"
return self.data.get(f'table.Lighting_V2[{self._channel}][{profile_mode}][1].Mode') == "Manual"

def is_ring_light_on(self) -> bool:
"""Return true if ring light is on for an Amcrest Doorbell"""
Expand Down
25 changes: 21 additions & 4 deletions custom_components/dahua/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,23 @@ async def async_get_light_global_enabled(self) -> dict:
url = "/cgi-bin/configManager.cgi?action=getConfig&name=LightGlobal[0].Enable"
return await self.get(url)

async def async_get_floodlightmode(self) -> dict:
""" async_get_config_floodlightmode gets floodlight mode """
url = "/cgi-bin/configManager.cgi?action=getConfig&name=FloodLightMode.Mode"
try:
return await self.async_get_config("FloodLightMode.Mode")
except aiohttp.ClientResponseError as e:
return 2

async def async_set_floodlightmode(self, mode: int) -> dict:
""" async_set_floodlightmode will set the floodlight lighting control """
# 1 - Motion Acvtivation
# 2 - Manual (for manual switching)
# 3 - Schedule
# 4 - PIR
url = "/cgi-bin/configManager.cgi?action=setConfig&FloodLightMode.Mode={mode}".format(mode=mode)
return await self.get(url)

async def async_set_lighting_v1(self, channel: int, enabled: bool, brightness: int) -> dict:
""" async_get_lighting_v1 will turn the IR light (InfraRed light) on or off """
# on = Manual, off = Off
Expand Down Expand Up @@ -365,7 +382,7 @@ async def async_setprivacymask(self, index: int, enabled: bool):
index, str(enabled).lower()
)
return await self.get(url, True)

async def async_set_night_switch_mode(self, channel: int, mode: str):
"""
async_set_night_switch_mode is the same as async_set_video_profile_mode when accessing the camera
Expand Down Expand Up @@ -469,10 +486,10 @@ async def async_set_lighting_v2(self, channel: int, enabled: bool, brightness: i
_LOGGER.debug("Turning light on: %s", url)
return await self.get(url)

# async def async_set_lighting_v2_for_amcrest_flood_lights(self, channel: int, enabled: bool, brightness: int, profile_mode: str) -> dict:
async def async_set_lighting_v2_for_amcrest_flood_lights(self, channel: int, enabled: bool, profile_mode: str) -> dict:
# async def async_set_lighting_v2_for_flood_lights(self, channel: int, enabled: bool, brightness: int, profile_mode: str) -> dict:
async def async_set_lighting_v2_for_flood_lights(self, channel: int, enabled: bool, profile_mode: str) -> dict:
"""
async_set_lighting_v2_for_amcrest_floodlights will turn on or off the flood light on the camera. If turning on, the brightness will be used.
async_set_lighting_v2_for_floodlights will turn on or off the flood light on the camera. If turning on, the brightness will be used.
brightness is in the range of 0 to 100 inclusive where 100 is the brightest.
NOTE: While the flood lights do support an auto or "smart" mode, the api does not handle this change properly.
If one wishes to make the change back to auto, it must be done in the 'Amcrest Smart Home' smartphone app.
Expand Down
40 changes: 27 additions & 13 deletions custom_components/dahua/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ async def async_setup_entry(hass: HomeAssistant, entry, async_add_entities):
if coordinator.supports_illuminator():
entities.append(DahuaIlluminator(coordinator, entry, "Illuminator"))

if coordinator.is_amcrest_flood_light():
entities.append(AmcrestFloodLight(coordinator, entry, "Flood Light"))
if coordinator.is_flood_light():
entities.append(FloodLight(coordinator, entry, "Flood Light"))

if coordinator.supports_security_light() and not coordinator.is_amcrest_doorbell():
# The Amcrest doorbell works a little different and is added in select.py
Expand Down Expand Up @@ -205,9 +205,9 @@ async def async_turn_off(self, **kwargs):
await self._coordinator.async_refresh()


class AmcrestFloodLight(DahuaBaseEntity, LightEntity):
class FloodLight(DahuaBaseEntity, LightEntity):
"""
Representation of a Amcrest Flood Light (for cameras that have them)
Representation of a Amcrest, Dahua, and Lorex Flood Light (for cameras that have them)
Unlike the 'Dahua Illuminator', Amcrest Flood Lights do not play nicely
with adjusting the 'White Light' brightness.
"""
Expand All @@ -233,7 +233,7 @@ def unique_id(self):
@property
def is_on(self):
"""Return true if the light is on"""
return self._coordinator.is_amcrest_flood_light_on()
return self._coordinator.is_flood_light_on()

@property
def supported_features(self):
Expand All @@ -247,17 +247,31 @@ def should_poll(self):

async def async_turn_on(self, **kwargs):
"""Turn the light on"""
channel = self._coordinator.get_channel()
profile_mode = self._coordinator.get_profile_mode()
await self._coordinator.client.async_set_lighting_v2_for_amcrest_flood_lights(channel, True, profile_mode)
await self._coordinator.async_refresh()
if self._coordinator._supports_coaxial_control:
channel = self._coordinator.get_channel()
self._coordinator._floodlight_mode = await self._coordinator.client.async_get_floodlightmode()
await self._coordinator.client.async_set_floodlightmode(2)
await self._coordinator.client.async_set_coaxial_control_state(channel, SECURITY_LIGHT_TYPE, True)
await self._coordinator.async_refresh()

else:
channel = self._coordinator.get_channel()
profile_mode = self._coordinator.get_profile_mode()
await self._coordinator.client.async_set_lighting_v2_for_flood_lights(channel, True, profile_mode)
await self._coordinator.async_refresh()

async def async_turn_off(self, **kwargs):
"""Turn the light off"""
channel = self._coordinator.get_channel()
profile_mode = self._coordinator.get_profile_mode()
await self._coordinator.client.async_set_lighting_v2_for_amcrest_flood_lights(channel, False, profile_mode)
await self._coordinator.async_refresh()
if self._coordinator._supports_coaxial_control:
channel = self._coordinator.get_channel()
await self._coordinator.client.async_set_coaxial_control_state(channel, SECURITY_LIGHT_TYPE, False)
await self._coordinator.client.async_set_floodlightmode(self._coordinator._floodlight_mode)
await self._coordinator.async_refresh()
else:
channel = self._coordinator.get_channel()
profile_mode = self._coordinator.get_profile_mode()
await self._coordinator.client.async_set_lighting_v2_for_flood_lights(channel, False, profile_mode)
await self._coordinator.async_refresh()


class DahuaSecurityLight(DahuaBaseEntity, LightEntity):
Expand Down

0 comments on commit 2257928

Please sign in to comment.