-
-
Notifications
You must be signed in to change notification settings - Fork 15
Add door control API #90
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
base: master
Are you sure you want to change the base?
Changes from all commits
78eea76
2a3a9cf
a03bd91
776c8bf
bd92b6d
01f4733
6ba6f11
8762d20
ecca638
4a4c1fa
3113545
02d7ea1
b3bf6f6
d133c1b
fed1fdf
98d2a75
5fa5026
f2fe2c6
07a9dd7
8015c6a
cdee38b
ed9eafc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,246 @@ | ||
| """Door Control API. | ||
|
|
||
| The Axis Door control API makes it possible to control the behavior and functionality | ||
| of physical access controls in the Axis devices (e.g. A1001, A1601) | ||
| """ | ||
|
|
||
| from .api import APIItem, APIItems | ||
|
|
||
| URL = "/vapix/doorcontrol" | ||
|
|
||
| API_DISCOVERY_ID = "door-control" | ||
|
|
||
| CAPABILITY_ACCESS = "Access" | ||
| CAPABILITY_LOCK = "Lock" | ||
| CAPABILITY_UNLOCK = "Unlock" | ||
| CAPABILITY_BLOCK = "Block" | ||
| CAPABILITY_DOUBLE_LOCK = "DoubleLock" | ||
| CAPABILITY_LOCK_DOWN = "LockDown" | ||
| CAPABILITY_LOCK_OPEN = "LockOpen" | ||
| CAPABILITY_DOOR_MONITOR = "DoorMonitor" | ||
| CAPABILITY_LOCK_MONITOR = "LockMonitor" | ||
| CAPABILITY_DOUBLE_LOCK_MONITOR = "DoubleLockMonitor" | ||
| CAPABILITY_ALARM = "Alarm" | ||
| CAPABILITY_TAMPER = "Tamper" | ||
| CAPABILITY_WARNING = "Warning" | ||
| CAPABILITY_CONFIGURABLE = "Configurable" | ||
|
|
||
| SUPPORTED_CAPABILITIES = ( | ||
| CAPABILITY_ACCESS, | ||
| CAPABILITY_LOCK, | ||
| CAPABILITY_UNLOCK, | ||
| CAPABILITY_BLOCK, | ||
| CAPABILITY_DOUBLE_LOCK, | ||
| CAPABILITY_LOCK_DOWN, | ||
| CAPABILITY_LOCK_OPEN, | ||
| CAPABILITY_DOOR_MONITOR, | ||
| CAPABILITY_LOCK_MONITOR, | ||
| CAPABILITY_DOUBLE_LOCK_MONITOR, | ||
| CAPABILITY_ALARM, | ||
| CAPABILITY_TAMPER, | ||
| CAPABILITY_WARNING, | ||
| CAPABILITY_CONFIGURABLE | ||
| ) | ||
|
|
||
|
|
||
| class DoorControl(APIItems): | ||
| """Door control for Axis devices.""" | ||
|
|
||
| def __init__(self, request: object) -> None: | ||
| """Initialize door control manager.""" | ||
| super().__init__({}, request, URL, Door) | ||
|
|
||
| # TODO: Question: Is this used to get status information for the door? Or to update the object properties? | ||
drewclauson marked this conversation as resolved.
Show resolved
Hide resolved
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Time to remove it? ;) |
||
| async def update(self) -> None: | ||
| """Refresh data.""" | ||
| raw = await self.get_door_info_list() | ||
| self.process_raw(raw) | ||
|
|
||
| @staticmethod | ||
| def pre_process_raw(raw: dict) -> dict: | ||
| """Return a dictionary of doors.""" | ||
| door_control_data = raw.get("DoorInfo", {}) | ||
| return {api["token"]: api for api in door_control_data} | ||
|
|
||
| async def get_service_capabilities(self) -> dict: | ||
| """List the capabilities of the door controller.""" | ||
| return await self._request( | ||
| "post", | ||
| URL, | ||
| json={"tdc:GetServiceCapabilities": {}}, | ||
| ) | ||
|
|
||
| async def get_door_info_list(self) -> dict: | ||
| """List the doors.""" | ||
| return await self._request( | ||
| "post", | ||
| URL, | ||
| json={"tdc:GetDoorInfoList": {}}, | ||
| ) | ||
|
|
||
| async def get_door_info(self, door_tokens: list) -> dict: | ||
| """List the door information.""" | ||
| return await self._request( | ||
| "post", | ||
| URL, | ||
| json={"tdc:GetDoorInfo": {"Token": door_tokens}} | ||
| ) | ||
|
|
||
| async def get_door_state(self, door_token: str) -> dict: | ||
| """List the door information.""" | ||
| return await self._request( | ||
| "post", | ||
| URL, | ||
| json={"tdc:GetDoorState": {"Token": door_token}} | ||
| ) | ||
|
|
||
| # region Door Actions | ||
| async def access_door(self, door_token: str) -> None: | ||
| """Access a Door. | ||
|
|
||
| It invokes the functionality typically used when a card holder presents a card to a card reader at the door and is granted access. | ||
| The DoorMode shall change to Accessed. | ||
| The Door shall remain accessible for the defined time as configured in the device. | ||
| When the time span elapses, the DoorMode shall change back to its previous state. | ||
| A device must have the Lock capability to utilize this method. | ||
| """ | ||
| await self._request( | ||
| "post", | ||
| URL, | ||
| json={"tdc:AccessDoor": {"Token": door_token}} | ||
| ) | ||
|
|
||
| async def lock_door(self, door_token: str) -> None: | ||
| """Lock a Door. | ||
|
|
||
| The DoorMode shall change to Locked. | ||
| A device must have the Lock capability to utilize this method. | ||
| """ | ||
| await self._request( | ||
| "post", | ||
| URL, | ||
| json={"tdc:LockDoor": {"Token": door_token}} | ||
| ) | ||
|
|
||
| async def unlock_door(self, door_token: str) -> None: | ||
| """Unlock a Door until it is explicitly locked again. | ||
|
|
||
| The DoorMode shall change to Unlocked. | ||
| A device must have the Unlock capability to utilize this method. | ||
| """ | ||
| await self._request( | ||
| "post", | ||
| URL, | ||
| json={"tdc:UnlockDoor": {"Token": door_token}} | ||
| ) | ||
|
|
||
| async def block_door(self, door_token: str) -> None: | ||
| """Block a Door and prevent momentary access (AccessDoor command). | ||
|
|
||
| The DoorMode shall change to Blocked. | ||
| A device must have the Block capability to utilize this method. | ||
| """ | ||
| await self._request( | ||
| "post", | ||
| URL, | ||
| json={"tdc:BlockDoor": {"Token": door_token}} | ||
| ) | ||
|
|
||
| async def lock_down_door(self, door_token: str) -> None: | ||
| """Locks down a door and prevents other actions until a LockDownReleaseDoor command is invoked. | ||
|
|
||
| The DoorMode shall change to LockedDown. | ||
| The device shall ignore other door control commands until a LockDownReleaseDoor command is performed. | ||
| A device must have the LockDown capability to utilize this method. | ||
| """ | ||
| await self._request( | ||
| "post", | ||
| URL, | ||
| json={"tdc:LockDownDoor": {"Token": door_token}} | ||
| ) | ||
|
|
||
| async def lock_down_release_door(self, door_token: str) -> None: | ||
| """Releases the LockedDown state of a Door. | ||
|
|
||
| The DoorMode shall change back to its previous/next state. | ||
| It is not defined what the previous/next state shall be, but typically - Locked. | ||
| This method will only succeed if the current DoorMode is LockedDown. | ||
| """ | ||
| await self._request( | ||
| "post", | ||
| URL, | ||
| json={"tdc:LockDownReleaseDoor": {"Token": door_token}} | ||
| ) | ||
|
|
||
| async def lock_open_door(self, door_token: str) -> None: | ||
| """Unlocks a Door and prevents other actions until LockOpenReleaseDoor method is invoked. | ||
|
|
||
| The DoorMode shall change to LockedOpen. | ||
| The device shall ignore other door control commands until a LockOpenReleaseDoor command is performed. | ||
| A device must have the LockOpen capability to utilize this method. | ||
| """ | ||
| await self._request( | ||
| "post", | ||
| URL, | ||
| json={"tdc:LockOpenDoor": {"Token": door_token}} | ||
| ) | ||
|
|
||
| async def lock_open_release_door(self, door_token: str) -> None: | ||
| """Releases the LockedOpen state of a Door. | ||
|
|
||
| The DoorMode shall change state from the LockedOpen state back to its previous/next state. | ||
| It is not defined what the previous/next state shall be, but typically - Unlocked. | ||
| This method shall only succeed if the current DoorMode is LockedOpen. | ||
| """ | ||
| await self._request( | ||
| "post", | ||
| URL, | ||
| json={"tdc:LockOpenReleaseDoor": {"Token": door_token}} | ||
| ) | ||
|
|
||
| async def double_lock_door(self, door_token: str) -> None: | ||
| """Lock the door with a double lock.""" | ||
| await self._request( | ||
| "post", | ||
| URL, | ||
| json={"tdc:DoubleLockDoor": {"Token": door_token}} | ||
| ) | ||
|
|
||
| async def release_door(self, door_token: str) -> None: | ||
| """Release the door from a priority level.""" | ||
| await self._request( | ||
| "post", | ||
| URL, | ||
| json={"axtdc:ReleaseDoor": {"Token": door_token}} | ||
| ) | ||
| # endregion | ||
|
|
||
|
|
||
| class Door(APIItem): | ||
| """API Discovery item.""" | ||
|
|
||
| @property | ||
| def door_token(self) -> str: | ||
| """Token of Door.""" | ||
| return self.raw["token"] | ||
|
|
||
| @property | ||
| def door_name(self) -> str: | ||
| """Name of Door.""" | ||
| return self.raw["Name"] | ||
|
|
||
| @property | ||
| def door_description(self) -> str: | ||
| """Door Description.""" | ||
| return self.raw["Description"] | ||
|
|
||
| @property | ||
| def door_capabilities(self) -> dict: | ||
| """Capabilities of Door.""" | ||
| return self.raw["Capabilities"] | ||
|
|
||
| async def is_capable_of(self, capability: str) -> bool: | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whats the reasoning behind this? When will this be used? It doesnt seem to have test coverage when looking for the method |
||
| """Retrieve whether door has the specified capability.""" | ||
| if capability not in SUPPORTED_CAPABILITIES: | ||
| return False | ||
| return self.raw["Capabilities"][capability] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,8 @@ | |
|
|
||
| DAYNIGHT_INIT = b'<?xml version="1.0" encoding="UTF-8"?>\n<tt:MetadataStream xmlns:tt="http://www.onvif.org/ver10/schema">\n<tt:Event><wsnt:NotificationMessage xmlns:tns1="http://www.onvif.org/ver10/topics" xmlns:tnsaxis="http://www.axis.com/2009/event/topics" xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2" xmlns:wsa5="http://www.w3.org/2005/08/addressing"><wsnt:Topic Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Simple">tns1:VideoSource/tnsaxis:DayNightVision</wsnt:Topic><wsnt:ProducerReference><wsa5:Address>uri://1c8ae81b-3b00-46cf-bf76-79cc3fa533dc/ProducerReference</wsa5:Address></wsnt:ProducerReference><wsnt:Message><tt:Message UtcTime="2019-02-06T18:58:51.007104Z" PropertyOperation="Initialized"><tt:Source><tt:SimpleItem Name="VideoSourceConfigurationToken" Value="1"/></tt:Source><tt:Key></tt:Key><tt:Data><tt:SimpleItem Name="day" Value="1"/></tt:Data></tt:Message></wsnt:Message></wsnt:NotificationMessage></tt:Event></tt:MetadataStream>\n' | ||
|
|
||
| DOOR_MODE_INIT = b'<?xml version="1.0" encoding="UTF-8"?>\n<tt:MetadataStream xmlns:tt="http://www.onvif.org/ver10/schema">\n <tt:Event><wsnt:NotificationMessage xmlns:tns1="http://www.onvif.org/ver10/topics" xmlns:tnsaxis="http://www.axis.com/2009/event/topics" xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2" xmlns:wsa5="http://www.w3.org/2005/08/addressing"><wsnt:Topic Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Simple">tns1:Door/State/DoorMode</wsnt:Topic><wsnt:ProducerReference><wsa5:Address>uri://755cc9bb-cf3a-410b-bd1b-0ec97c6d6256/ProducerReference</wsa5:Address></wsnt:ProducerReference><wsnt:Message><tt:Message UtcTime="2020-09-05T04:25:51.692744Z" PropertyOperation="Initialized"><tt:Source><tt:SimpleItem Name="DoorToken" Value="Axis-5fba94a4-8601-4627-bdda-cc408f69e026"/></tt:Source><tt:Key></tt:Key><tt:Data><tt:SimpleItem Name="state" Value="Accessed"/></tt:Data></tt:Message></wsnt:Message></wsnt:NotificationMessage></tt:Event></tt:MetadataStream>\n' | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you generate additional events? The better coverage and examples the easier it will be to maintain |
||
|
|
||
| FENCE_GUARD_INIT = b'<?xml version="1.0" encoding="UTF-8"?>\n<tt:MetadataStream xmlns:tt="http://www.onvif.org/ver10/schema">\n<tt:Event><wsnt:NotificationMessage xmlns:tns1="http://www.onvif.org/ver10/topics" xmlns:tnsaxis="http://www.axis.com/2009/event/topics" xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2" xmlns:wsa5="http://www.w3.org/2005/08/addressing"><wsnt:Topic Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Simple">tnsaxis:CameraApplicationPlatform/FenceGuard/Camera1Profile1</wsnt:Topic><wsnt:ProducerReference><wsa5:Address>uri://755cc9bb-cf3a-410b-bd1b-0ec97c6d6256/ProducerReference</wsa5:Address></wsnt:ProducerReference><wsnt:Message><tt:Message UtcTime="2020-09-04T20:08:34.701060Z" PropertyOperation="Initialized"><tt:Source></tt:Source><tt:Key></tt:Key><tt:Data><tt:SimpleItem Name="active" Value="0"/></tt:Data></tt:Message></wsnt:Message></wsnt:NotificationMessage></tt:Event></tt:MetadataStream>\n' | ||
|
|
||
| GLOBAL_SCENE_CHANGE = b'<?xml version="1.0" encoding="UTF-8"?>\n<tt:MetadataStream xmlns:tt="http://www.onvif.org/ver10/schema">\n<tt:Event><wsnt:NotificationMessage xmlns:tns1="http://www.onvif.org/ver10/topics" xmlns:tnsaxis="http://www.axis.com/2009/event/topics" xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2" xmlns:wsa5="http://www.w3.org/2005/08/addressing"><wsnt:Topic Dialect="http://docs.oasis-open.org/wsn/t-1/TopicExpression/Simple">tns1:VideoSource/GlobalSceneChange/ImagingService</wsnt:Topic><wsnt:ProducerReference><wsa5:Address>uri://755cc9bb-cf3a-410b-bd1b-0ec97c6d6256/ProducerReference</wsa5:Address></wsnt:ProducerReference><wsnt:Message><tt:Message UtcTime="2020-09-05T04:45:30.199233Z" PropertyOperation="Initialized"><tt:Source><tt:SimpleItem Name="Source" Value="0"/></tt:Source><tt:Key></tt:Key><tt:Data><tt:SimpleItem Name="State" Value="0"/></tt:Data></tt:Message></wsnt:Message></wsnt:NotificationMessage></tt:Event></tt:MetadataStream>\n' | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove device examples, access control is enough, I think video door stations might be applicable here as well and I don't want to keep updating the list.