zcc-helper 3.4.dev1__tar.gz → 3.4.dev2__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 (27) hide show
  1. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/PKG-INFO +12 -7
  2. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/setup.py +1 -1
  3. zcc_helper-3.4.dev2/tests/test_controller.py +59 -0
  4. zcc_helper-3.4.dev2/tests/test_device.py +42 -0
  5. zcc_helper-3.4.dev2/tests/test_server.py +11 -0
  6. zcc_helper-3.4.dev2/tests/test_socket.py +52 -0
  7. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/zcc/__main__.py +9 -0
  8. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/zcc/constants.py +1 -1
  9. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/zcc/discovery.py +70 -0
  10. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/zcc_helper.egg-info/PKG-INFO +13 -8
  11. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/zcc_helper.egg-info/SOURCES.txt +4 -0
  12. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/LICENSE.txt +0 -0
  13. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/README.md +0 -0
  14. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/setup.cfg +0 -0
  15. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/zcc/__init__.py +0 -0
  16. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/zcc/controller.py +0 -0
  17. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/zcc/description.py +0 -0
  18. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/zcc/device.py +0 -0
  19. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/zcc/errors.py +0 -0
  20. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/zcc/manufacture_info.py +0 -0
  21. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/zcc/protocol.py +0 -0
  22. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/zcc/socket.py +0 -0
  23. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/zcc/trace.py +0 -0
  24. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/zcc/watchdog.py +0 -0
  25. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/zcc.py +0 -0
  26. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/zcc_helper.egg-info/dependency_links.txt +0 -0
  27. {zcc_helper-3.4.dev1 → zcc_helper-3.4.dev2}/zcc_helper.egg-info/top_level.txt +0 -0
@@ -1,16 +1,23 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: zcc_helper
3
- Version: 3.4.dev1
3
+ Version: 3.4.dev2
4
4
  Summary: ZIMI ZCC helper module
5
- Home-page: UNKNOWN
6
5
  Author: Mark Hannon
7
6
  Author-email: mark.hannon@gmail.com
8
7
  License: MIT
9
8
  Project-URL: Source, https://bitbucket.org/mark_hannon/zcc
10
- Platform: UNKNOWN
11
- Classifier: Programming Language :: Python :: 3.9
9
+ Classifier: Programming Language :: Python :: 3.12
12
10
  Description-Content-Type: text/markdown
13
11
  License-File: LICENSE.txt
12
+ Dynamic: author
13
+ Dynamic: author-email
14
+ Dynamic: classifier
15
+ Dynamic: description
16
+ Dynamic: description-content-type
17
+ Dynamic: license
18
+ Dynamic: license-file
19
+ Dynamic: project-url
20
+ Dynamic: summary
14
21
 
15
22
  # ZCC-HELPER
16
23
 
@@ -274,5 +281,3 @@ python -m zcc --execute --device 'bddf0500-4d15-4457-b063-c12ed208a0b0_3' --acti
274
281
  ```
275
282
 
276
283
  This version of the command is relatively slow as it first of all discovers the ZCC on the local LAN, builds a device inventory and then executes the action.
277
-
278
-
@@ -19,7 +19,7 @@ setup(
19
19
  author_email="mark.hannon@gmail.com",
20
20
  license="MIT",
21
21
  classifiers=[
22
- "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.12",
23
23
  ],
24
24
  packages=find_packages(exclude=["contrib", "docs", "tests"]),
25
25
  project_urls={"Source": "https://bitbucket.org/mark_hannon/zcc"},
@@ -0,0 +1,59 @@
1
+ '''Test Basic Controller functionality.'''
2
+ import pytest
3
+
4
+ from zcc.controller import ControlPoint, ControlPointError
5
+ from zcc.device import ControlPointDevice
6
+
7
+ from tests.test_server import test_server
8
+
9
+
10
+ def test_controller_discover(test_server):
11
+ '''Test for connection to a Controller connected to the Test Server'''
12
+
13
+ controller = ControlPoint(timeout=1)
14
+
15
+ assert controller.ready is True
16
+ assert controller.host is not None
17
+ assert controller.port == 5003
18
+ assert controller.brand == 'zimi'
19
+ assert len(controller.devices) == 31
20
+ assert len(controller.doors) == 0
21
+ assert len(controller.fans) == 0
22
+ assert len(controller.lights) == 20
23
+ assert len(controller.outlets) == 0
24
+
25
+
26
+ def test_controller_discover_timeout():
27
+ '''Test if a UDP discovery times out - goes out over wire'''
28
+
29
+ try:
30
+ ControlPoint(timeout=1)
31
+ assert False
32
+ except ControlPointError as error:
33
+ assert error.args[0] == '__init() failed - unable to discover and connect to ZCC'
34
+
35
+
36
+ def test_controller_device(test_server):
37
+ '''Test turn_on works for valid devices'''
38
+
39
+ controller = ControlPoint(timeout=1)
40
+
41
+ light = controller.lights[0]
42
+
43
+ assert light.is_off() is not True
44
+ assert light.is_on() is True
45
+
46
+ assert light.is_opening is not True
47
+ assert light.is_open is not True
48
+ assert light.location == "lounge LED strip/Lounge"
49
+ assert light.name == "lounge LED strip"
50
+
51
+ with pytest.raises(ControlPointDeviceError):
52
+ light.open_door()
53
+
54
+ with pytest.raises(ControlPointDeviceError):
55
+ light.close_door()
56
+
57
+ light.turn_on()
58
+
59
+ light.turn_off()
@@ -0,0 +1,42 @@
1
+ import unittest
2
+
3
+ from zcc.device import ControlPointDevice
4
+
5
+
6
+ class DeviceTest(unittest.TestCase):
7
+
8
+ identifier = 'test_device_id'
9
+
10
+ def test_init(self):
11
+ device = ControlPointDevice(None, DeviceTest.identifier)
12
+ self.assertEqual(device.controller, None)
13
+ self.assertEqual(device.identifier, DeviceTest.identifier)
14
+ self.assertEqual(device.actions, {})
15
+ self.assertEqual(device.properties, {})
16
+ self.assertEqual(device.states, {})
17
+
18
+ def test_add_action(self):
19
+ device = ControlPointDevice(None, DeviceTest.identifier)
20
+ device.actions = {'TurnOn': {'actionParams': {}},
21
+ 'TurnOff': {'actionParams': {}}}
22
+ print(device)
23
+ self.assertEqual(device.controller, None)
24
+ self.assertEqual(device.identifier, DeviceTest.identifier)
25
+
26
+ def test_add_properties(self):
27
+ device = ControlPointDevice(None, DeviceTest.identifier)
28
+ device.properties = {'name': 'Entry Pendant',
29
+ 'controlPointType': 'switch', 'roomId': 2, 'roomName': 'Front Door '}
30
+ self.assertEqual(device.controller, None)
31
+ self.assertEqual(device.identifier, DeviceTest.identifier)
32
+
33
+ def test_add_states(self):
34
+ device = ControlPointDevice(None, DeviceTest.identifier)
35
+ device.states = {'controlState': {
36
+ 'switch': {...}}, 'isConnected': True}
37
+ self.assertEqual(device.controller, None)
38
+ self.assertEqual(device.identifier, DeviceTest.identifier)
39
+
40
+
41
+ if __name__ == '__main__':
42
+ unittest.main()
@@ -0,0 +1,11 @@
1
+ '''Setup Test Server.'''
2
+ import pytest
3
+
4
+ from zcc.controller import ControlPoint, ControlPointError
5
+ from tests.server import TestServer
6
+
7
+
8
+ @pytest.fixture
9
+ def test_server():
10
+ '''Create (and delete) a Test Server'''
11
+ return TestServer()
@@ -0,0 +1,52 @@
1
+ import socket
2
+ import pytest
3
+ from unittest.mock import Mock
4
+ from mockito import when, unstub
5
+
6
+ from zcc.socket import ControlPointSocket
7
+
8
+ TEST_HOST = '10.0.0.1'
9
+ TEST_PORT = 4567
10
+ TEST_TIMEOUT = 5
11
+
12
+
13
+ @pytest.fixture
14
+ def mock_tcp_socket():
15
+ mock_socket = Mock(spec=socket.socket)
16
+ return mock_socket
17
+
18
+
19
+ def test_socket_create(mock_tcp_socket, when):
20
+
21
+ when(socket).socket(socket.AF_INET,
22
+ socket.SOCK_STREAM)\
23
+ .thenReturn(mock_tcp_socket)
24
+
25
+ controller_socket = ControlPointSocket(TEST_HOST, TEST_PORT)
26
+
27
+ assert controller_socket.host == TEST_HOST
28
+ assert controller_socket.port == TEST_PORT
29
+ assert controller_socket.sock == mock_tcp_socket
30
+
31
+ controller_socket.close()
32
+ assert controller_socket.sock == None
33
+
34
+
35
+ def test_socket_create_timeout(mock_tcp_socket, when):
36
+
37
+ when(socket).socket(socket.AF_INET,
38
+ socket.SOCK_STREAM)\
39
+ .thenReturn(mock_tcp_socket)
40
+
41
+ controller_socket = ControlPointSocket(
42
+ TEST_HOST, TEST_PORT, timeout=TEST_TIMEOUT)
43
+
44
+ assert controller_socket.host == TEST_HOST
45
+ assert controller_socket.port == TEST_PORT
46
+ assert controller_socket.sock == mock_tcp_socket
47
+
48
+ assert mock_tcp_socket.settimeout.call_count == 1
49
+ assert mock_tcp_socket.settimeout.call_args.args == (TEST_TIMEOUT,)
50
+
51
+ controller_socket.close()
52
+ assert controller_socket.sock == None
@@ -52,6 +52,9 @@ def __options(args):
52
52
  cxg.add_argument(
53
53
  "--execute", action="store_true", help="execute an action on a device"
54
54
  )
55
+ cxg.add_argument(
56
+ "--test-connection", action="store_true", help="test the connection to a device"
57
+ )
55
58
 
56
59
  host_group = parser.add_argument_group("host")
57
60
  host_group.add_argument("--host", action="store", help="zcc host name|address")
@@ -96,6 +99,12 @@ async def main(args):
96
99
  await ControlPointDiscoveryService(verbosity=options.verbosity).discover()
97
100
  return
98
101
 
102
+ if options.test_connection and options.host and options.port:
103
+ await ControlPointDiscoveryService(
104
+ verbosity=options.verbosity
105
+ ).validate_connection(host=options.host, port=options.port)
106
+ return
107
+
99
108
  if options.host and options.port:
100
109
  description = ControlPointDescription(host=options.host, port=options.port)
101
110
  else:
@@ -9,4 +9,4 @@ APP_ID = "ZvWqWJQB"
9
9
  APP_TOKEN = "3422ecbb-cf6a-404c-b3dc-2023dd213535"
10
10
 
11
11
  NAME = "zcc_helper"
12
- VERSION = "3.4-dev1"
12
+ VERSION = "3.4-dev2"
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
+ from enum import StrEnum
6
7
  import json
7
8
  from json.decoder import JSONDecodeError
8
9
  import logging
@@ -10,11 +11,25 @@ import socket
10
11
  from typing import Tuple
11
12
 
12
13
  from zcc.constants import LEVEL_BY_VERBOSITY
14
+ from zcc.controller import ControlPoint
13
15
  from zcc.description import ControlPointDescription
14
16
  from zcc.errors import ControlPointError
15
17
  from zcc.protocol import ControlPointProtocol
16
18
 
17
19
 
20
+ class ControlPointDiscoveryErrors(StrEnum):
21
+ """Discovery errors."""
22
+
23
+ ALREADY_CONFIGURED = "already_configured"
24
+ CANNOT_CONNECT = "cannot_connect"
25
+ CONNECTION_REFUSED = "connection_refused"
26
+ DISCOVERY_FAILURE = "discovery_failure"
27
+ INVALID_HOST = "invalid_host"
28
+ INVALID_PORT = "invalid_port"
29
+ TIMEOUT = "timeout"
30
+ UNKNOWN = "unknown"
31
+
32
+
18
33
  class ControlPointDiscoveryProtocol(asyncio.DatagramProtocol):
19
34
  """Listens for ZCC announcements on the defined UDP port."""
20
35
 
@@ -76,6 +91,7 @@ class ControlPointDiscoveryService:
76
91
  self.discovery_complete = self.loop.create_future()
77
92
  self.discovery_result: list[ControlPointDescription] = []
78
93
  self.never_completes = self.loop.create_future()
94
+ self.validation_result: ControlPointDescription
79
95
 
80
96
  async def discover(
81
97
  self, wait_for_all: bool = False
@@ -135,3 +151,57 @@ class ControlPointDiscoveryService:
135
151
  async def discovers(self) -> list[ControlPointDescription]:
136
152
  """Discover all local zimi controllers."""
137
153
  return await self.discover(wait_for_all=True)
154
+
155
+ async def validate_connection(
156
+ self, host: str, port: int
157
+ ) -> ControlPointDescription | dict[str, str]:
158
+ """Validate ability to connect and close a connection.
159
+
160
+ Return a ControlPointDescription if OK or error dictionary if not.
161
+ """
162
+
163
+ error = None
164
+
165
+ try:
166
+ socket.gethostbyname(host)
167
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
168
+ s.settimeout(10)
169
+ try:
170
+ s.connect((host, port))
171
+ s.close()
172
+ except ConnectionRefusedError:
173
+ error = {"error": ControlPointDiscoveryErrors.CONNECTION_REFUSED}
174
+ except TimeoutError:
175
+ error = {"error": ControlPointDiscoveryErrors.TIMEOUT}
176
+ except socket.gaierror:
177
+ error = {"error": ControlPointDiscoveryErrors.CANNOT_CONNECT}
178
+ except socket.gaierror:
179
+ error = {"error": ControlPointDiscoveryErrors.INVALID_HOST}
180
+
181
+ if error:
182
+ self.logger.error(error)
183
+ return error
184
+
185
+ api = ControlPoint(ControlPointDescription(host=host, port=port))
186
+
187
+ try:
188
+ await api.connect(fast=True)
189
+ except ControlPointError:
190
+ return {"error": ControlPointDiscoveryErrors.CANNOT_CONNECT}
191
+
192
+ self.validation_result = ControlPointDescription(
193
+ brand=api.brand,
194
+ product=api.product,
195
+ host=host,
196
+ port=port,
197
+ mac=api.mac,
198
+ available_tcps=api.available_tcps,
199
+ api_version=api.api_version,
200
+ firmware_version=api.firmware_version
201
+ )
202
+
203
+ api.disconnect()
204
+
205
+ self.logger.info(self.validation_result)
206
+
207
+ return self.validation_result
@@ -1,16 +1,23 @@
1
- Metadata-Version: 2.1
2
- Name: zcc-helper
3
- Version: 3.4.dev1
1
+ Metadata-Version: 2.4
2
+ Name: zcc_helper
3
+ Version: 3.4.dev2
4
4
  Summary: ZIMI ZCC helper module
5
- Home-page: UNKNOWN
6
5
  Author: Mark Hannon
7
6
  Author-email: mark.hannon@gmail.com
8
7
  License: MIT
9
8
  Project-URL: Source, https://bitbucket.org/mark_hannon/zcc
10
- Platform: UNKNOWN
11
- Classifier: Programming Language :: Python :: 3.9
9
+ Classifier: Programming Language :: Python :: 3.12
12
10
  Description-Content-Type: text/markdown
13
11
  License-File: LICENSE.txt
12
+ Dynamic: author
13
+ Dynamic: author-email
14
+ Dynamic: classifier
15
+ Dynamic: description
16
+ Dynamic: description-content-type
17
+ Dynamic: license
18
+ Dynamic: license-file
19
+ Dynamic: project-url
20
+ Dynamic: summary
14
21
 
15
22
  # ZCC-HELPER
16
23
 
@@ -274,5 +281,3 @@ python -m zcc --execute --device 'bddf0500-4d15-4457-b063-c12ed208a0b0_3' --acti
274
281
  ```
275
282
 
276
283
  This version of the command is relatively slow as it first of all discovers the ZCC on the local LAN, builds a device inventory and then executes the action.
277
-
278
-
@@ -2,6 +2,10 @@ LICENSE.txt
2
2
  README.md
3
3
  setup.py
4
4
  zcc.py
5
+ tests/test_controller.py
6
+ tests/test_device.py
7
+ tests/test_server.py
8
+ tests/test_socket.py
5
9
  zcc/__init__.py
6
10
  zcc/__main__.py
7
11
  zcc/constants.py
File without changes
File without changes
File without changes
File without changes