axis 63__tar.gz → 65__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. axis-65/PKG-INFO +43 -0
  2. {axis-63 → axis-65}/axis/__main__.py +7 -2
  3. {axis-63 → axis-65}/axis/interfaces/mqtt.py +1 -1
  4. {axis-63 → axis-65}/axis/interfaces/parameters/param_cgi.py +15 -2
  5. {axis-63 → axis-65}/axis/interfaces/vapix.py +7 -10
  6. {axis-63 → axis-65}/axis/models/configuration.py +1 -0
  7. {axis-63 → axis-65}/axis/models/event_instance.py +1 -1
  8. {axis-63 → axis-65}/axis/models/parameters/stream_profile.py +1 -1
  9. {axis-63 → axis-65}/axis/stream_manager.py +4 -3
  10. axis-65/axis.egg-info/PKG-INFO +43 -0
  11. axis-65/axis.egg-info/requires.txt +24 -0
  12. {axis-63 → axis-65}/pyproject.toml +16 -18
  13. {axis-63 → axis-65}/tests/test_configuration.py +2 -0
  14. {axis-63 → axis-65}/tests/test_port_cgi.py +5 -5
  15. {axis-63 → axis-65}/tests/test_stream_manager.py +24 -0
  16. {axis-63 → axis-65}/tests/test_vapix.py +58 -17
  17. axis-63/PKG-INFO +0 -24
  18. axis-63/axis.egg-info/PKG-INFO +0 -24
  19. axis-63/axis.egg-info/requires.txt +0 -24
  20. {axis-63 → axis-65}/LICENSE +0 -0
  21. {axis-63 → axis-65}/README.md +0 -0
  22. {axis-63 → axis-65}/axis/__init__.py +0 -0
  23. {axis-63 → axis-65}/axis/device.py +0 -0
  24. {axis-63 → axis-65}/axis/errors.py +0 -0
  25. {axis-63 → axis-65}/axis/interfaces/__init__.py +0 -0
  26. {axis-63 → axis-65}/axis/interfaces/api_discovery.py +0 -0
  27. {axis-63 → axis-65}/axis/interfaces/api_handler.py +0 -0
  28. {axis-63 → axis-65}/axis/interfaces/applications/__init__.py +0 -0
  29. {axis-63 → axis-65}/axis/interfaces/applications/application_handler.py +0 -0
  30. {axis-63 → axis-65}/axis/interfaces/applications/applications.py +0 -0
  31. {axis-63 → axis-65}/axis/interfaces/applications/fence_guard.py +0 -0
  32. {axis-63 → axis-65}/axis/interfaces/applications/loitering_guard.py +0 -0
  33. {axis-63 → axis-65}/axis/interfaces/applications/motion_guard.py +0 -0
  34. {axis-63 → axis-65}/axis/interfaces/applications/object_analytics.py +0 -0
  35. {axis-63 → axis-65}/axis/interfaces/applications/vmd4.py +0 -0
  36. {axis-63 → axis-65}/axis/interfaces/basic_device_info.py +0 -0
  37. {axis-63 → axis-65}/axis/interfaces/event_instances.py +0 -0
  38. {axis-63 → axis-65}/axis/interfaces/event_manager.py +0 -0
  39. {axis-63 → axis-65}/axis/interfaces/light_control.py +0 -0
  40. {axis-63 → axis-65}/axis/interfaces/parameters/__init__.py +0 -0
  41. {axis-63 → axis-65}/axis/interfaces/parameters/brand.py +0 -0
  42. {axis-63 → axis-65}/axis/interfaces/parameters/image.py +0 -0
  43. {axis-63 → axis-65}/axis/interfaces/parameters/io_port.py +0 -0
  44. {axis-63 → axis-65}/axis/interfaces/parameters/param_handler.py +0 -0
  45. {axis-63 → axis-65}/axis/interfaces/parameters/properties.py +0 -0
  46. {axis-63 → axis-65}/axis/interfaces/parameters/ptz.py +0 -0
  47. {axis-63 → axis-65}/axis/interfaces/parameters/stream_profile.py +0 -0
  48. {axis-63 → axis-65}/axis/interfaces/pir_sensor_configuration.py +0 -0
  49. {axis-63 → axis-65}/axis/interfaces/port_cgi.py +0 -0
  50. {axis-63 → axis-65}/axis/interfaces/port_management.py +0 -0
  51. {axis-63 → axis-65}/axis/interfaces/ptz.py +0 -0
  52. {axis-63 → axis-65}/axis/interfaces/pwdgrp_cgi.py +0 -0
  53. {axis-63 → axis-65}/axis/interfaces/stream_profiles.py +0 -0
  54. {axis-63 → axis-65}/axis/interfaces/user_groups.py +0 -0
  55. {axis-63 → axis-65}/axis/interfaces/view_areas.py +0 -0
  56. {axis-63 → axis-65}/axis/models/__init__.py +0 -0
  57. {axis-63 → axis-65}/axis/models/api.py +0 -0
  58. {axis-63 → axis-65}/axis/models/api_discovery.py +0 -0
  59. {axis-63 → axis-65}/axis/models/applications/__init__.py +0 -0
  60. {axis-63 → axis-65}/axis/models/applications/application.py +0 -0
  61. {axis-63 → axis-65}/axis/models/applications/fence_guard.py +0 -0
  62. {axis-63 → axis-65}/axis/models/applications/loitering_guard.py +0 -0
  63. {axis-63 → axis-65}/axis/models/applications/motion_guard.py +0 -0
  64. {axis-63 → axis-65}/axis/models/applications/object_analytics.py +0 -0
  65. {axis-63 → axis-65}/axis/models/applications/vmd4.py +0 -0
  66. {axis-63 → axis-65}/axis/models/basic_device_info.py +0 -0
  67. {axis-63 → axis-65}/axis/models/event.py +0 -0
  68. {axis-63 → axis-65}/axis/models/light_control.py +0 -0
  69. {axis-63 → axis-65}/axis/models/mqtt.py +0 -0
  70. {axis-63 → axis-65}/axis/models/parameters/__init__.py +0 -0
  71. {axis-63 → axis-65}/axis/models/parameters/brand.py +0 -0
  72. {axis-63 → axis-65}/axis/models/parameters/image.py +0 -0
  73. {axis-63 → axis-65}/axis/models/parameters/io_port.py +0 -0
  74. {axis-63 → axis-65}/axis/models/parameters/param_cgi.py +0 -0
  75. {axis-63 → axis-65}/axis/models/parameters/properties.py +0 -0
  76. {axis-63 → axis-65}/axis/models/parameters/ptz.py +0 -0
  77. {axis-63 → axis-65}/axis/models/pir_sensor_configuration.py +0 -0
  78. {axis-63 → axis-65}/axis/models/port_cgi.py +0 -0
  79. {axis-63 → axis-65}/axis/models/port_management.py +0 -0
  80. {axis-63 → axis-65}/axis/models/ptz_cgi.py +0 -0
  81. {axis-63 → axis-65}/axis/models/pwdgrp_cgi.py +0 -0
  82. {axis-63 → axis-65}/axis/models/stream_profile.py +0 -0
  83. {axis-63 → axis-65}/axis/models/user_group.py +0 -0
  84. {axis-63 → axis-65}/axis/models/view_area.py +0 -0
  85. {axis-63 → axis-65}/axis/py.typed +0 -0
  86. {axis-63 → axis-65}/axis/rtsp.py +0 -0
  87. {axis-63 → axis-65}/axis.egg-info/SOURCES.txt +0 -0
  88. {axis-63 → axis-65}/axis.egg-info/dependency_links.txt +0 -0
  89. {axis-63 → axis-65}/axis.egg-info/entry_points.txt +0 -0
  90. {axis-63 → axis-65}/axis.egg-info/top_level.txt +0 -0
  91. {axis-63 → axis-65}/setup.cfg +0 -0
  92. {axis-63 → axis-65}/tests/test_api_discovery.py +0 -0
  93. {axis-63 → axis-65}/tests/test_api_handler.py +0 -0
  94. {axis-63 → axis-65}/tests/test_basic_device_info.py +0 -0
  95. {axis-63 → axis-65}/tests/test_device.py +0 -0
  96. {axis-63 → axis-65}/tests/test_event.py +0 -0
  97. {axis-63 → axis-65}/tests/test_event_instances.py +0 -0
  98. {axis-63 → axis-65}/tests/test_event_stream.py +0 -0
  99. {axis-63 → axis-65}/tests/test_light_control.py +0 -0
  100. {axis-63 → axis-65}/tests/test_mqtt.py +0 -0
  101. {axis-63 → axis-65}/tests/test_pir_sensor_configuration.py +0 -0
  102. {axis-63 → axis-65}/tests/test_port_management.py +0 -0
  103. {axis-63 → axis-65}/tests/test_ptz.py +0 -0
  104. {axis-63 → axis-65}/tests/test_pwdgrp_cgi.py +0 -0
  105. {axis-63 → axis-65}/tests/test_rtsp.py +0 -0
  106. {axis-63 → axis-65}/tests/test_stream_profiles.py +0 -0
  107. {axis-63 → axis-65}/tests/test_user_groups.py +0 -0
  108. {axis-63 → axis-65}/tests/test_view_areas.py +0 -0
axis-65/PKG-INFO ADDED
@@ -0,0 +1,43 @@
1
+ Metadata-Version: 2.4
2
+ Name: axis
3
+ Version: 65
4
+ Summary: A Python library for communicating with devices from Axis Communications
5
+ Author-email: Robert Svensson <Kane610@users.noreply.github.com>
6
+ License: MIT
7
+ Project-URL: Source Code, https://github.com/Kane610/axis
8
+ Project-URL: Bug Reports, https://github.com/Kane610/axis/issues
9
+ Project-URL: Forum, https://community.home-assistant.io/t/axis-camera-component/
10
+ Keywords: axis,vapix,homeassistant
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Home Automation
17
+ Requires-Python: >=3.12.0
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: faust-cchardet>=2.1.18
21
+ Requires-Dist: httpx>=0.26
22
+ Requires-Dist: orjson>3.9
23
+ Requires-Dist: packaging>23
24
+ Requires-Dist: xmltodict>=0.13.0
25
+ Provides-Extra: requirements
26
+ Requires-Dist: httpx==0.28.1; extra == "requirements"
27
+ Requires-Dist: orjson==3.11.1; extra == "requirements"
28
+ Requires-Dist: packaging==25.0; extra == "requirements"
29
+ Requires-Dist: xmltodict==0.14.2; extra == "requirements"
30
+ Provides-Extra: requirements-test
31
+ Requires-Dist: mypy==1.17.1; extra == "requirements-test"
32
+ Requires-Dist: pytest==8.4.1; extra == "requirements-test"
33
+ Requires-Dist: pytest-aiohttp==1.1.0; extra == "requirements-test"
34
+ Requires-Dist: pytest-asyncio==0.26.0; extra == "requirements-test"
35
+ Requires-Dist: pytest-cov==6.2.1; extra == "requirements-test"
36
+ Requires-Dist: respx==0.22.0; extra == "requirements-test"
37
+ Requires-Dist: ruff==0.11.11; extra == "requirements-test"
38
+ Requires-Dist: types-xmltodict==v0.14.0.20241009; extra == "requirements-test"
39
+ Provides-Extra: requirements-dev
40
+ Requires-Dist: pre-commit==4.2.0; extra == "requirements-dev"
41
+ Dynamic: license-file
42
+
43
+ Python project to set up a connection towards Axis Communications devices and to subscribe to specific events on the metadatastream.
@@ -17,13 +17,18 @@ def event_handler(event: axis.models.event.Event) -> None:
17
17
 
18
18
 
19
19
  async def axis_device(
20
- host: str, port: int, username: str, password: str
20
+ host: str, port: int, username: str, password: str, is_companion: bool = False
21
21
  ) -> axis.device.AxisDevice:
22
22
  """Create a Axis device."""
23
23
  session = AsyncClient(verify=False) # noqa: S501
24
24
  device = axis.device.AxisDevice(
25
25
  axis.models.configuration.Configuration(
26
- session, host, port=port, username=username, password=password
26
+ session,
27
+ host,
28
+ port=port,
29
+ username=username,
30
+ password=password,
31
+ is_companion=is_companion,
27
32
  )
28
33
  )
29
34
 
@@ -25,7 +25,7 @@ from .api_handler import ApiHandler
25
25
  DEFAULT_TOPICS = ["//."]
26
26
 
27
27
 
28
- def mqtt_json_to_event(msg: bytes | str) -> dict[str, Any]:
28
+ def mqtt_json_to_event(msg: bytes | bytearray | memoryview | str) -> dict[str, Any]:
29
29
  """Convert JSON message from MQTT to event format."""
30
30
  message = orjson.loads(msg)
31
31
  topic = message["topic"].replace("onvif", "tns1").replace("axis", "tnsaxis")
@@ -1,7 +1,19 @@
1
1
  """Axis Vapix parameter management."""
2
2
 
3
3
  from collections.abc import Sequence
4
- from typing import TYPE_CHECKING, Any
4
+ from typing import TYPE_CHECKING, Any, TypedDict
5
+
6
+ if TYPE_CHECKING:
7
+
8
+ class _DetectResultType(TypedDict):
9
+ encoding: str
10
+ confidence: float
11
+
12
+ def detect(byte_str: bytes | bytearray) -> _DetectResultType:
13
+ """Typed interface for chardet detect method."""
14
+ ...
15
+ else:
16
+ from cchardet import detect
5
17
 
6
18
  from ...models.api_discovery import ApiId
7
19
  from ...models.parameters.param_cgi import ParameterGroup, ParamRequest, params_to_dict
@@ -36,7 +48,8 @@ class Params(ApiHandler[Any]):
36
48
  async def _api_request(self, group: ParameterGroup | None = None) -> dict[str, Any]:
37
49
  """Fetch parameter data and convert it into a dictionary."""
38
50
  bytes_data = await self.vapix.api_request(ParamRequest(group))
39
- return params_to_dict(bytes_data.decode()).get("root") or {}
51
+ encoding = detect(bytes_data)["encoding"] or "utf-8"
52
+ return params_to_dict(bytes_data.decode(encoding=encoding)).get("root") or {}
40
53
 
41
54
  async def _update(self, group: ParameterGroup | None = None) -> Sequence[str]:
42
55
  """Request parameter data and update items."""
@@ -11,17 +11,11 @@ import httpx
11
11
  from ..errors import RequestError, raise_error
12
12
  from ..models.pwdgrp_cgi import SecondaryGroup
13
13
  from .api_discovery import ApiDiscoveryHandler
14
- from .applications import (
15
- ApplicationsHandler,
16
- )
14
+ from .applications import ApplicationsHandler
17
15
  from .applications.fence_guard import FenceGuardHandler
18
- from .applications.loitering_guard import (
19
- LoiteringGuardHandler,
20
- )
16
+ from .applications.loitering_guard import LoiteringGuardHandler
21
17
  from .applications.motion_guard import MotionGuardHandler
22
- from .applications.object_analytics import (
23
- ObjectAnalyticsHandler,
24
- )
18
+ from .applications.object_analytics import ObjectAnalyticsHandler
25
19
  from .applications.vmd4 import Vmd4Handler
26
20
  from .basic_device_info import BasicDeviceInfoHandler
27
21
  from .event_instances import EventInstanceHandler
@@ -242,13 +236,16 @@ class Vapix:
242
236
 
243
237
  async def api_request(self, api_request: ApiRequest) -> bytes:
244
238
  """Make a request to the device."""
239
+ params = api_request.params or {}
240
+ if self.device.config.is_companion:
241
+ params["Axis-Orig-Sw"] = "true"
245
242
  return await self.request(
246
243
  method=api_request.method,
247
244
  path=api_request.path,
248
245
  content=api_request.content,
249
246
  data=api_request.data,
250
247
  headers=api_request.headers,
251
- params=api_request.params,
248
+ params=params,
252
249
  )
253
250
 
254
251
  async def request(
@@ -17,6 +17,7 @@ class Configuration:
17
17
  port: int = 80
18
18
  web_proto: str = "http"
19
19
  verify_ssl: bool = False
20
+ is_companion: bool = False
20
21
 
21
22
  @property
22
23
  def url(self) -> str:
@@ -44,7 +44,7 @@ def get_events(data: dict[str, Any]) -> list[dict[str, Any]]:
44
44
  event_list = get_events(value) # Recursive call
45
45
 
46
46
  for event in event_list:
47
- event["topic"] = f'{key}/{event["topic"]}' # Compose the topic
47
+ event["topic"] = f"{key}/{event['topic']}" # Compose the topic
48
48
  events.append(event)
49
49
 
50
50
  return events
@@ -51,7 +51,7 @@ class StreamProfileParam(ParamItem):
51
51
  description=profile["Description"],
52
52
  parameters=profile["Parameters"],
53
53
  )
54
- for profile in cast(dict[str, ProfileParamT], raw_profiles).values()
54
+ for profile in cast("dict[str, ProfileParamT]", raw_profiles).values()
55
55
  ]
56
56
 
57
57
  return cls(
@@ -13,9 +13,7 @@ if TYPE_CHECKING:
13
13
 
14
14
  _LOGGER = logging.getLogger(__name__)
15
15
 
16
- RTSP_URL = (
17
- "rtsp://{host}/axis-media/media.amp?video={video}&audio={audio}&event={event}"
18
- )
16
+ RTSP_URL = "rtsp://{host}/axis-media/media.amp?video={video}&audio={audio}&event={event}{axis_orig_sw}"
19
17
 
20
18
  RETRY_TIMER = 15
21
19
 
@@ -43,6 +41,9 @@ class StreamManager:
43
41
  video=self.video_query,
44
42
  audio=self.audio_query,
45
43
  event=self.event_query,
44
+ axis_orig_sw="&Axis-Orig-Sw=true"
45
+ if self.device.config.is_companion
46
+ else "",
46
47
  )
47
48
  _LOGGER.debug(rtsp_url)
48
49
  return rtsp_url
@@ -0,0 +1,43 @@
1
+ Metadata-Version: 2.4
2
+ Name: axis
3
+ Version: 65
4
+ Summary: A Python library for communicating with devices from Axis Communications
5
+ Author-email: Robert Svensson <Kane610@users.noreply.github.com>
6
+ License: MIT
7
+ Project-URL: Source Code, https://github.com/Kane610/axis
8
+ Project-URL: Bug Reports, https://github.com/Kane610/axis/issues
9
+ Project-URL: Forum, https://community.home-assistant.io/t/axis-camera-component/
10
+ Keywords: axis,vapix,homeassistant
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Home Automation
17
+ Requires-Python: >=3.12.0
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: faust-cchardet>=2.1.18
21
+ Requires-Dist: httpx>=0.26
22
+ Requires-Dist: orjson>3.9
23
+ Requires-Dist: packaging>23
24
+ Requires-Dist: xmltodict>=0.13.0
25
+ Provides-Extra: requirements
26
+ Requires-Dist: httpx==0.28.1; extra == "requirements"
27
+ Requires-Dist: orjson==3.11.1; extra == "requirements"
28
+ Requires-Dist: packaging==25.0; extra == "requirements"
29
+ Requires-Dist: xmltodict==0.14.2; extra == "requirements"
30
+ Provides-Extra: requirements-test
31
+ Requires-Dist: mypy==1.17.1; extra == "requirements-test"
32
+ Requires-Dist: pytest==8.4.1; extra == "requirements-test"
33
+ Requires-Dist: pytest-aiohttp==1.1.0; extra == "requirements-test"
34
+ Requires-Dist: pytest-asyncio==0.26.0; extra == "requirements-test"
35
+ Requires-Dist: pytest-cov==6.2.1; extra == "requirements-test"
36
+ Requires-Dist: respx==0.22.0; extra == "requirements-test"
37
+ Requires-Dist: ruff==0.11.11; extra == "requirements-test"
38
+ Requires-Dist: types-xmltodict==v0.14.0.20241009; extra == "requirements-test"
39
+ Provides-Extra: requirements-dev
40
+ Requires-Dist: pre-commit==4.2.0; extra == "requirements-dev"
41
+ Dynamic: license-file
42
+
43
+ Python project to set up a connection towards Axis Communications devices and to subscribe to specific events on the metadatastream.
@@ -0,0 +1,24 @@
1
+ faust-cchardet>=2.1.18
2
+ httpx>=0.26
3
+ orjson>3.9
4
+ packaging>23
5
+ xmltodict>=0.13.0
6
+
7
+ [requirements]
8
+ httpx==0.28.1
9
+ orjson==3.11.1
10
+ packaging==25.0
11
+ xmltodict==0.14.2
12
+
13
+ [requirements-dev]
14
+ pre-commit==4.2.0
15
+
16
+ [requirements-test]
17
+ mypy==1.17.1
18
+ pytest==8.4.1
19
+ pytest-aiohttp==1.1.0
20
+ pytest-asyncio==0.26.0
21
+ pytest-cov==6.2.1
22
+ respx==0.22.0
23
+ ruff==0.11.11
24
+ types-xmltodict==v0.14.0.20241009
@@ -1,10 +1,10 @@
1
1
  [build-system]
2
- requires = ["setuptools==68.0.0", "wheel==0.40.0"]
2
+ requires = ["setuptools==80.9.0", "wheel==0.46.1"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "axis"
7
- version = "63"
7
+ version = "65"
8
8
  license = {text = "MIT"}
9
9
  description = "A Python library for communicating with devices from Axis Communications"
10
10
  readme = "README.md"
@@ -20,6 +20,7 @@ classifiers = [
20
20
  ]
21
21
  requires-python = ">=3.12.0"
22
22
  dependencies = [
23
+ "faust-cchardet>=2.1.18",
23
24
  "httpx>=0.26",
24
25
  "orjson>3.9",
25
26
  "packaging>23",
@@ -28,24 +29,23 @@ dependencies = [
28
29
 
29
30
  [project.optional-dependencies]
30
31
  requirements = [
31
- "httpx==0.27.2",
32
- "orjson==3.10.9",
33
- "packaging==24.1",
32
+ "httpx==0.28.1",
33
+ "orjson==3.11.1",
34
+ "packaging==25.0",
34
35
  "xmltodict==0.14.2",
35
36
  ]
36
- requirements_test = [
37
- "mypy==1.12.1",
38
- "pytest==8.3.3",
39
- "pytest-aiohttp==1.0.5",
40
- "pytest-asyncio==0.24.0",
41
- "pytest-cov==5.0.0",
42
- "respx==0.21.1",
43
- "ruff==0.7.0",
44
- "types-orjson==3.6.2",
37
+ requirements-test = [
38
+ "mypy==1.17.1",
39
+ "pytest==8.4.1",
40
+ "pytest-aiohttp==1.1.0",
41
+ "pytest-asyncio==0.26.0",
42
+ "pytest-cov==6.2.1",
43
+ "respx==0.22.0",
44
+ "ruff==0.11.11",
45
45
  "types-xmltodict==v0.14.0.20241009",
46
46
  ]
47
- requirements_dev = [
48
- "pre-commit==4.0.1"
47
+ requirements-dev = [
48
+ "pre-commit==4.2.0"
49
49
  ]
50
50
 
51
51
  [project.urls]
@@ -148,8 +148,6 @@ lint.select = [
148
148
  ]
149
149
 
150
150
  lint.ignore = [
151
- "ANN101", # Missing type annotation for {name} in method
152
- "ANN102", # Missing type annotation for {name} in classmethod
153
151
  "ANN401", # Dynamically typed expressions (typing.Any) are disallowed in {name}
154
152
  "COM812", # Trailing comma missing
155
153
  "D203", # 1 blank line required before class docstring
@@ -28,6 +28,7 @@ def test_configuration():
28
28
  assert config.web_proto == "https"
29
29
  assert config.verify_ssl is True
30
30
  assert config.url == "https://192.168.0.1:443"
31
+ assert config.is_companion is False
31
32
 
32
33
 
33
34
  def test_minimal_configuration():
@@ -47,3 +48,4 @@ def test_minimal_configuration():
47
48
  assert config.web_proto == "http"
48
49
  assert config.verify_ssl is False
49
50
  assert config.url == "http://192.168.1.1:80"
51
+ assert config.is_companion is False
@@ -20,7 +20,7 @@ def ports(axis_device) -> Ports:
20
20
  async def test_ports(respx_mock, ports: Ports) -> None:
21
21
  """Test that different types of ports work."""
22
22
  update_ports_route = respx_mock.post(f"http://{HOST}/axis-cgi/param.cgi").respond(
23
- text="""root.Input.NbrOfInputs=3
23
+ content="""root.Input.NbrOfInputs=3
24
24
  root.IOPort.I0.Direction=input
25
25
  root.IOPort.I0.Usage=Button
26
26
  root.IOPort.I1.Configurable=no
@@ -44,8 +44,8 @@ root.IOPort.I3.Output.Mode=bistable
44
44
  root.IOPort.I3.Output.Name=Tampering
45
45
  root.IOPort.I3.Output.PulseTime=0
46
46
  root.Output.NbrOfOutputs=1
47
- """,
48
- headers={"Content-Type": "text/plain"},
47
+ """.encode("iso-8859-1"),
48
+ headers={"Content-Type": "text/plain; charset=iso-8859-1"},
49
49
  )
50
50
 
51
51
  await ports.update()
@@ -99,8 +99,8 @@ root.Output.NbrOfOutputs=1
99
99
  async def test_no_ports(respx_mock, ports: Ports) -> None:
100
100
  """Test that no ports also work."""
101
101
  route = respx_mock.post(f"http://{HOST}/axis-cgi/param.cgi").respond(
102
- text="",
103
- headers={"Content-Type": "text/plain"},
102
+ content="".encode("iso-8859-1"),
103
+ headers={"Content-Type": "text/plain; charset=iso-8859-1"},
104
104
  )
105
105
 
106
106
  await ports.update()
@@ -20,6 +20,12 @@ def stream_manager(axis_device) -> StreamManager:
20
20
  return axis_device.stream
21
21
 
22
22
 
23
+ @pytest.fixture
24
+ def stream_manager_companion(axis_companion_device) -> StreamManager:
25
+ """Return the StreamManager mock object."""
26
+ return axis_companion_device.stream
27
+
28
+
23
29
  async def test_stream_url(stream_manager):
24
30
  """Verify stream url."""
25
31
  assert stream_manager.video_query == 0
@@ -38,6 +44,24 @@ async def test_stream_url(stream_manager):
38
44
  )
39
45
 
40
46
 
47
+ async def test_stream_url_companion(stream_manager_companion):
48
+ """Verify stream url."""
49
+ assert stream_manager_companion.video_query == 0
50
+ assert stream_manager_companion.audio_query == 0
51
+ assert stream_manager_companion.event_query == "off"
52
+ assert (
53
+ stream_manager_companion.stream_url
54
+ == f"rtsp://{HOST}/axis-media/media.amp?video=0&audio=0&event=off&Axis-Orig-Sw=true"
55
+ )
56
+
57
+ stream_manager_companion.event = True
58
+ assert stream_manager_companion.event_query == "on"
59
+ assert (
60
+ stream_manager_companion.stream_url
61
+ == f"rtsp://{HOST}/axis-media/media.amp?video=0&audio=0&event=on&Axis-Orig-Sw=true"
62
+ )
63
+
64
+
41
65
  @patch("axis.stream_manager.RTSPClient")
42
66
  async def test_initialize_stream(rtsp_client, stream_manager):
43
67
  """Test stream commands."""
@@ -49,6 +49,12 @@ def vapix(axis_device: AxisDevice) -> Vapix:
49
49
  return axis_device.vapix
50
50
 
51
51
 
52
+ @pytest.fixture
53
+ def vapix_companion_device(axis_companion_device: AxisDevice) -> Vapix:
54
+ """Return the vapix object."""
55
+ return axis_companion_device.vapix
56
+
57
+
52
58
  def test_vapix_not_initialized(vapix: Vapix) -> None:
53
59
  """Test Vapix class without initialising any data."""
54
60
  assert dict(vapix.basic_device_info.items()) == {}
@@ -92,8 +98,10 @@ async def test_initialize(respx_mock, vapix: Vapix):
92
98
  }
93
99
  )
94
100
 
95
- respx_mock.post("/axis-cgi/param.cgi").respond(text=PARAM_CGI_RESPONSE)
96
-
101
+ respx_mock.post("/axis-cgi/param.cgi").respond(
102
+ content=PARAM_CGI_RESPONSE.encode("iso-8859-1"),
103
+ headers={"Content-Type": "text/plain; charset=iso-8859-1"},
104
+ )
97
105
  respx_mock.post("/axis-cgi/applications/list.cgi").respond(
98
106
  text=APPLICATIONS_RESPONSE,
99
107
  headers={"Content-Type": "text/xml"},
@@ -219,14 +227,15 @@ async def test_initialize_api_discovery_unsupported(respx_mock, vapix: Vapix):
219
227
  async def test_initialize_param_cgi(respx_mock, vapix: Vapix):
220
228
  """Verify that you can list parameters."""
221
229
  respx_mock.post("/axis-cgi/param.cgi").respond(
222
- text=PARAM_CGI_RESPONSE,
223
- headers={"Content-Type": "text/plain"},
230
+ content=PARAM_CGI_RESPONSE.encode("iso-8859-1"),
231
+ headers={"Content-Type": "text/plain; charset=iso-8859-1"},
224
232
  )
225
233
  respx_mock.post("/axis-cgi/lightcontrol.cgi").respond(
226
234
  json=LIGHT_CONTROL_RESPONSE,
227
235
  )
228
236
  await vapix.initialize_param_cgi()
229
237
 
238
+ assert "Axis-Orig-Sw" not in respx_mock.calls.last.request.url.params
230
239
  assert vapix.firmware_version == "9.10.1"
231
240
  assert vapix.product_number == "M1065-LW"
232
241
  assert vapix.product_type == "Network Camera"
@@ -243,11 +252,43 @@ async def test_initialize_param_cgi(respx_mock, vapix: Vapix):
243
252
  assert vapix.users.supported
244
253
 
245
254
 
255
+ async def test_initialize_param_cgi_for_companion_device(
256
+ respx_mock, vapix_companion_device: Vapix
257
+ ):
258
+ """Verify that you can list parameters."""
259
+ respx_mock.post("/axis-cgi/param.cgi").respond(
260
+ content=PARAM_CGI_RESPONSE.encode("iso-8859-1"),
261
+ headers={"Content-Type": "text/plain; charset=iso-8859-1"},
262
+ )
263
+ respx_mock.post("/axis-cgi/lightcontrol.cgi").respond(
264
+ json=LIGHT_CONTROL_RESPONSE,
265
+ )
266
+ await vapix_companion_device.initialize_param_cgi()
267
+
268
+ assert "Axis-Orig-Sw" in respx_mock.calls.last.request.url.params
269
+
270
+ assert vapix_companion_device.firmware_version == "9.10.1"
271
+ assert vapix_companion_device.product_number == "M1065-LW"
272
+ assert vapix_companion_device.product_type == "Network Camera"
273
+ assert vapix_companion_device.serial_number == "ACCC12345678"
274
+ assert len(vapix_companion_device.streaming_profiles) == 2
275
+
276
+ assert len(vapix_companion_device.basic_device_info) == 0
277
+ assert len(vapix_companion_device.ports.values()) == 1
278
+ assert len(vapix_companion_device.light_control.values()) == 1
279
+ assert len(vapix_companion_device.mqtt) == 0
280
+ assert len(vapix_companion_device.stream_profiles) == 0
281
+ assert len(vapix_companion_device.params.stream_profile_handler) == 1
282
+
283
+ assert vapix_companion_device.users.supported
284
+
285
+
246
286
  async def test_initialize_params_no_data(respx_mock, vapix: Vapix):
247
287
  """Verify that you can list parameters."""
248
- param_route = respx_mock.post(
249
- "/axis-cgi/param.cgi",
250
- ).respond(text="")
288
+ param_route = respx_mock.post("/axis-cgi/param.cgi").respond(
289
+ content="".encode("iso-8859-1"),
290
+ headers={"Content-Type": "text/plain; charset=iso-8859-1"},
291
+ )
251
292
  await vapix.initialize_param_cgi(preload_data=False)
252
293
 
253
294
  assert param_route.call_count == 4
@@ -256,8 +297,8 @@ async def test_initialize_params_no_data(respx_mock, vapix: Vapix):
256
297
  async def test_initialize_applications(respx_mock, vapix: Vapix):
257
298
  """Verify you can list and retrieve descriptions of applications."""
258
299
  respx_mock.post("/axis-cgi/param.cgi").respond(
259
- text=PARAM_CGI_RESPONSE,
260
- headers={"Content-Type": "text/plain"},
300
+ content=PARAM_CGI_RESPONSE.encode("iso-8859-1"),
301
+ headers={"Content-Type": "text/plain; charset=iso-8859-1"},
261
302
  )
262
303
  respx_mock.post("/axis-cgi/lightcontrol.cgi").respond(
263
304
  json=LIGHT_CONTROL_RESPONSE,
@@ -296,8 +337,8 @@ async def test_initialize_applications(respx_mock, vapix: Vapix):
296
337
  async def test_initialize_applications_unauthorized(respx_mock, vapix: Vapix, code):
297
338
  """Verify initialize applications doesnt break on too low credentials."""
298
339
  respx_mock.post("/axis-cgi/param.cgi").respond(
299
- text=PARAM_CGI_RESPONSE,
300
- headers={"Content-Type": "text/plain"},
340
+ content=PARAM_CGI_RESPONSE.encode("iso-8859-1"),
341
+ headers={"Content-Type": "text/plain; charset=iso-8859-1"},
301
342
  )
302
343
  respx_mock.post("/axis-cgi/lightcontrol.cgi").respond(
303
344
  json=LIGHT_CONTROL_RESPONSE,
@@ -313,8 +354,8 @@ async def test_initialize_applications_unauthorized(respx_mock, vapix: Vapix, co
313
354
  async def test_initialize_applications_not_running(respx_mock, vapix: Vapix):
314
355
  """Verify you can list and retrieve descriptions of applications."""
315
356
  respx_mock.post("/axis-cgi/param.cgi").respond(
316
- text=PARAM_CGI_RESPONSE,
317
- headers={"Content-Type": "text/plain"},
357
+ content=PARAM_CGI_RESPONSE.encode("iso-8859-1"),
358
+ headers={"Content-Type": "text/plain; charset=iso-8859-1"},
318
359
  )
319
360
  respx_mock.post("/axis-cgi/lightcontrol.cgi").respond(
320
361
  json=LIGHT_CONTROL_RESPONSE,
@@ -351,10 +392,10 @@ async def test_initialize_event_instances(respx_mock, vapix: Vapix):
351
392
 
352
393
  async def test_applications_dont_load_without_params(respx_mock, vapix: Vapix):
353
394
  """Applications depends on param cgi to be loaded first."""
354
- param_route = respx_mock.post(
355
- "/axis-cgi/param.cgi",
356
- ).respond(text="key=value")
357
-
395
+ param_route = respx_mock.post("/axis-cgi/param.cgi").respond(
396
+ content="key=value".encode("iso-8859-1"),
397
+ headers={"Content-Type": "text/plain; charset=iso-8859-1"},
398
+ )
358
399
  applications_route = respx_mock.post("/axis-cgi/applications/list.cgi")
359
400
 
360
401
  await vapix.initialize_param_cgi(preload_data=False)
axis-63/PKG-INFO DELETED
@@ -1,24 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: axis
3
- Version: 63
4
- Summary: A Python library for communicating with devices from Axis Communications
5
- Author-email: Robert Svensson <Kane610@users.noreply.github.com>
6
- License: MIT
7
- Project-URL: Source Code, https://github.com/Kane610/axis
8
- Project-URL: Bug Reports, https://github.com/Kane610/axis/issues
9
- Project-URL: Forum, https://community.home-assistant.io/t/axis-camera-component/
10
- Keywords: axis,vapix,homeassistant
11
- Classifier: Development Status :: 5 - Production/Stable
12
- Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: MIT License
14
- Classifier: Operating System :: OS Independent
15
- Classifier: Programming Language :: Python :: 3.12
16
- Classifier: Topic :: Home Automation
17
- Requires-Python: >=3.12.0
18
- Description-Content-Type: text/markdown
19
- Provides-Extra: requirements
20
- Provides-Extra: requirements_test
21
- Provides-Extra: requirements_dev
22
- License-File: LICENSE
23
-
24
- Python project to set up a connection towards Axis Communications devices and to subscribe to specific events on the metadatastream.
@@ -1,24 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: axis
3
- Version: 63
4
- Summary: A Python library for communicating with devices from Axis Communications
5
- Author-email: Robert Svensson <Kane610@users.noreply.github.com>
6
- License: MIT
7
- Project-URL: Source Code, https://github.com/Kane610/axis
8
- Project-URL: Bug Reports, https://github.com/Kane610/axis/issues
9
- Project-URL: Forum, https://community.home-assistant.io/t/axis-camera-component/
10
- Keywords: axis,vapix,homeassistant
11
- Classifier: Development Status :: 5 - Production/Stable
12
- Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: MIT License
14
- Classifier: Operating System :: OS Independent
15
- Classifier: Programming Language :: Python :: 3.12
16
- Classifier: Topic :: Home Automation
17
- Requires-Python: >=3.12.0
18
- Description-Content-Type: text/markdown
19
- Provides-Extra: requirements
20
- Provides-Extra: requirements_test
21
- Provides-Extra: requirements_dev
22
- License-File: LICENSE
23
-
24
- Python project to set up a connection towards Axis Communications devices and to subscribe to specific events on the metadatastream.
@@ -1,24 +0,0 @@
1
- httpx>=0.26
2
- orjson>3.9
3
- packaging>23
4
- xmltodict>=0.13.0
5
-
6
- [requirements]
7
- httpx==0.27.2
8
- orjson==3.10.9
9
- packaging==24.1
10
- xmltodict==0.14.2
11
-
12
- [requirements_dev]
13
- pre-commit==4.0.1
14
-
15
- [requirements_test]
16
- mypy==1.12.1
17
- pytest==8.3.3
18
- pytest-aiohttp==1.0.5
19
- pytest-asyncio==0.24.0
20
- pytest-cov==5.0.0
21
- respx==0.21.1
22
- ruff==0.7.0
23
- types-orjson==3.6.2
24
- types-xmltodict==v0.14.0.20241009
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes