Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for L46N floodlights and possibly the Lorex equivalent #313

Merged
merged 4 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading