Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco authored Jun 13, 2024
2 parents 1c09589 + ec00121 commit 02a4dca
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 76 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## v1.2.2 (2024-06-13)

### Fix


- Restore some unreachable code in _process_device_update (#50) ([`c638cd3`](https://github.com/uilibs/uiprotect/commit/c638cd3b087d63279bd8f798bd8831fc2e11a916))


## v1.2.1 (2024-06-13)

### Fix


- Blocking i/o in the event loop (#49) ([`36a4355`](https://github.com/uilibs/uiprotect/commit/36a4355170566b9d7cfb1632d9c35c28b693d9ce))


## v1.2.0 (2024-06-13)

### Feature
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
project = "uiprotect"
copyright = "2024, UI Protect Maintainers"
author = "UI Protect Maintainers"
release = "1.2.0"
release = "1.2.2"

# General configuration
extensions = [
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "uiprotect"
version = "1.2.0"
version = "1.2.2"
description = "Python API for Unifi Protect (Unofficial)"
authors = ["UI Protect Maintainers <[email protected]>"]
license = "MIT"
Expand Down
122 changes: 58 additions & 64 deletions src/uiprotect/data/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,9 @@
}


def _process_light_event(event: Event) -> None:
if event.light is None:
return

if _event_is_in_range(event, event.light.last_motion):
event.light.last_motion_event_id = event.id
def _process_light_event(event: Event, light: Light) -> None:
if _event_is_in_range(event, light.last_motion):
light.last_motion_event_id = event.id


def _event_is_in_range(event: Event, dt: datetime | None) -> bool:
Expand All @@ -95,22 +92,20 @@ def _event_is_in_range(event: Event, dt: datetime | None) -> bool:
)


def _process_sensor_event(event: Event) -> None:
if event.sensor is None:
return
def _process_sensor_event(event: Event, sensor: Sensor) -> None:
if event.type is EventType.MOTION_SENSOR:
if _event_is_in_range(event, event.sensor.motion_detected_at):
event.sensor.last_motion_event_id = event.id
if _event_is_in_range(event, sensor.motion_detected_at):
sensor.last_motion_event_id = event.id
elif event.type in {EventType.SENSOR_CLOSED, EventType.SENSOR_OPENED}:
if _event_is_in_range(event, event.sensor.open_status_changed_at):
event.sensor.last_contact_event_id = event.id
if _event_is_in_range(event, sensor.open_status_changed_at):
sensor.last_contact_event_id = event.id
elif event.type is EventType.SENSOR_EXTREME_VALUE:
if _event_is_in_range(event, event.sensor.extreme_value_detected_at):
event.sensor.extreme_value_detected_at = event.end
event.sensor.last_value_event_id = event.id
if _event_is_in_range(event, sensor.extreme_value_detected_at):
sensor.extreme_value_detected_at = event.end
sensor.last_value_event_id = event.id
elif event.type is EventType.SENSOR_ALARM:
if _event_is_in_range(event, event.sensor.alarm_triggered_at):
event.sensor.last_value_event_id = event.id
if _event_is_in_range(event, sensor.alarm_triggered_at):
sensor.last_value_event_id = event.id


_CAMERA_SMART_AND_LINE_EVENTS = {
Expand All @@ -120,10 +115,7 @@ def _process_sensor_event(event: Event) -> None:
_CAMERA_SMART_AUDIO_EVENT = EventType.SMART_AUDIO_DETECT


def _process_camera_event(event: Event) -> None:
if (camera := event.camera) is None:
return

def _process_camera_event(event: Event, camera: Camera) -> None:
event_type = event.type
dt_attr, event_attr = CAMERA_EVENT_ATTR_MAP[event_type]
dt: datetime | None = getattr(camera, dt_attr)
Expand Down Expand Up @@ -322,12 +314,13 @@ def get_device_from_id(self, device_id: str) -> ProtectAdoptableDeviceModel | No
return cast(ProtectAdoptableDeviceModel, devices.get(ref.id))

def process_event(self, event: Event) -> None:
if event.type in CAMERA_EVENT_ATTR_MAP and event.camera is not None:
_process_camera_event(event)
elif event.type == EventType.MOTION_LIGHT and event.light is not None:
_process_light_event(event)
elif event.type == EventType.MOTION_SENSOR and event.sensor is not None:
_process_sensor_event(event)
event_type = event.type
if event_type in CAMERA_EVENT_ATTR_MAP and (camera := event.camera):
_process_camera_event(event, camera)
elif event_type is EventType.MOTION_LIGHT and (light := event.light):
_process_light_event(event, light)
elif event_type is EventType.MOTION_SENSOR and (sensor := event.sensor):
_process_sensor_event(event, sensor)

self.events[event.id] = event

Expand Down Expand Up @@ -473,47 +466,48 @@ def _process_device_update(
key = f"{model_type}s"
devices: dict[str, ProtectModelWithId] = getattr(self, key)
action_id: str = action["id"]
if action_id in devices:
if action_id not in devices:
raise ValueError(f"Unknown device update for {model_type}: {action_id}")
obj = devices[action_id]
data = obj.unifi_dict_to_dict(data)
old_obj = obj.copy()
obj = obj.update_from_dict(deepcopy(data))

if isinstance(obj, Event):
self.process_event(obj)
elif isinstance(obj, Camera):
if "last_ring" in data and obj.last_ring:
is_recent = obj.last_ring + RECENT_EVENT_MAX >= utc_now()
_LOGGER.debug("last_ring for %s (%s)", obj.id, is_recent)
if is_recent:
obj.set_ring_timeout()
elif (
isinstance(obj, Sensor)
and "alarm_triggered_at" in data
and obj.alarm_triggered_at
):
if action_id not in devices:
# ignore updates to events that phase out
if model_type != _ModelType_Event_value:
_LOGGER.debug("Unexpected %s: %s", key, action_id)
return None

obj = devices[action_id]
model = obj.model
data = obj.unifi_dict_to_dict(data)
old_obj = obj.copy()
obj = obj.update_from_dict(deepcopy(data))

if model is ModelType.EVENT:
if TYPE_CHECKING:
assert isinstance(obj, Event)
self.process_event(obj)
elif model is ModelType.CAMERA:
if TYPE_CHECKING:
assert isinstance(obj, Camera)
if "last_ring" in data and obj.last_ring:
is_recent = obj.last_ring + RECENT_EVENT_MAX >= utc_now()
_LOGGER.debug("last_ring for %s (%s)", obj.id, is_recent)
if is_recent:
obj.set_ring_timeout()
elif model is ModelType.SENSOR:
if TYPE_CHECKING:
assert isinstance(obj, Sensor)
if "alarm_triggered_at" in data and obj.alarm_triggered_at:
is_recent = obj.alarm_triggered_at + RECENT_EVENT_MAX >= utc_now()
_LOGGER.debug("alarm_triggered_at for %s (%s)", obj.id, is_recent)
if is_recent:
obj.set_alarm_timeout()

devices[action_id] = obj

self._create_stat(packet, data, False)
return WSSubscriptionMessage(
action=WSAction.UPDATE,
new_update_id=self.last_update_id,
changed_data=data,
new_obj=obj,
old_obj=old_obj,
)

# ignore updates to events that phase out
if model_type != _ModelType_Event_value:
_LOGGER.debug("Unexpected %s: %s", key, action_id)
return None
devices[action_id] = obj
self._create_stat(packet, data, False)
return WSSubscriptionMessage(
action=WSAction.UPDATE,
new_update_id=self.last_update_id,
changed_data=data,
new_obj=obj,
old_obj=old_obj,
)

def process_ws_packet(
self,
Expand Down
19 changes: 9 additions & 10 deletions src/uiprotect/data/nvr.py
Original file line number Diff line number Diff line change
Expand Up @@ -1198,16 +1198,15 @@ async def reboot(self) -> None:

async def _read_cache_file(self, file_path: Path) -> set[Version] | None:
versions: set[Version] | None = None

if file_path.is_file():
try:
_LOGGER.debug("Reading release cache file: %s", file_path)
async with aiofiles.open(file_path, "rb") as cache_file:
versions = {
Version(v) for v in orjson.loads(await cache_file.read())
}
except Exception:
_LOGGER.warning("Failed to parse cache file: %s", file_path)
try:
_LOGGER.debug("Reading release cache file: %s", file_path)
async with aiofiles.open(file_path, "rb") as cache_file:
versions = {Version(v) for v in orjson.loads(await cache_file.read())}
except FileNotFoundError:
# ignore missing file
pass
except Exception:
_LOGGER.warning("Failed to parse cache file: %s", file_path)

return versions

Expand Down

0 comments on commit 02a4dca

Please sign in to comment.