vsslctrl 0.1.14.dev1__tar.gz → 0.2.0__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 (37) hide show
  1. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/PKG-INFO +35 -70
  2. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/README.md +32 -68
  3. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/pyproject.toml +1 -1
  4. vsslctrl-0.2.0/tests/test_api.py +64 -0
  5. vsslctrl-0.2.0/tests/test_device.py +87 -0
  6. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/tests/test_event_bus.py +5 -4
  7. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/tests/test_integration.py +116 -100
  8. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/tests/test_settings.py +11 -12
  9. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/tests/test_zone.py +2 -24
  10. vsslctrl-0.2.0/vsslctrl/__init__.py +7 -0
  11. vsslctrl-0.2.0/vsslctrl/api_alpha.py +1814 -0
  12. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl/api_base.py +120 -165
  13. vsslctrl-0.2.0/vsslctrl/api_bravo.py +645 -0
  14. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl/core.py +116 -124
  15. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl/data_structure.py +156 -62
  16. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl/decorators.py +46 -15
  17. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl/device.py +58 -13
  18. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl/discovery.py +42 -27
  19. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl/event_bus.py +19 -1
  20. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl/group.py +27 -55
  21. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl/io.py +5 -14
  22. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl/settings.py +153 -25
  23. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl/track.py +3 -45
  24. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl/transport.py +2 -2
  25. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl/utils.py +8 -17
  26. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl/zone.py +105 -117
  27. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl.egg-info/PKG-INFO +35 -70
  28. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl.egg-info/SOURCES.txt +2 -0
  29. vsslctrl-0.1.14.dev1/vsslctrl/__init__.py +0 -7
  30. vsslctrl-0.1.14.dev1/vsslctrl/api_alpha.py +0 -1265
  31. vsslctrl-0.1.14.dev1/vsslctrl/api_bravo.py +0 -427
  32. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/LICENSE +0 -0
  33. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/setup.cfg +0 -0
  34. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl/exceptions.py +0 -0
  35. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl.egg-info/dependency_links.txt +0 -0
  36. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl.egg-info/requires.txt +0 -0
  37. {vsslctrl-0.1.14.dev1 → vsslctrl-0.2.0}/vsslctrl.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: vsslctrl
3
- Version: 0.1.14.dev1
3
+ Version: 0.2.0
4
4
  Summary: Package for controlling VSSL's range of streaming amplifiers
5
5
  Author-email: vsslctrl <vsslcontrolled@proton.me>
6
6
  Classifier: Development Status :: 4 - Beta
@@ -18,28 +18,30 @@ Requires-Dist: pytest==8.1.1; extra == "dev"
18
18
  Requires-Dist: pytest_asyncio==0.23.6; extra == "dev"
19
19
  Provides-Extra: optional
20
20
  Requires-Dist: zeroconf==0.132.2; extra == "optional"
21
+ Dynamic: license-file
21
22
 
22
23
  # vsslctrl
23
24
 
24
- Package for controlling [VSSL's](https://www.vssl.com/) range of streaming amplifiers.
25
+ Python package for controlling [VSSL's](https://www.vssl.com/) range of streaming amplifiers.
25
26
 
26
27
  ## Coverage
27
28
 
28
- Tested on:
29
+ Test suite run on:
29
30
 
30
- | Model | Software Version | Note |
31
- | ------------|--------- | -------------
32
- | A.3x | p15305.016.3701 |
31
+ | Model | Software Version
32
+ | ------------|---------
33
+ | `A.1x` | p15243.022.3703
34
+ | `A.3x` | p15305.016.3701
33
35
 
34
36
 
35
37
  Home Assistant [integration](https://github.com/vsslctrl/integration.home-assistant) with basic functionality working on:
36
38
 
37
39
  | Model | Software Version | User Reported |
38
40
  | ------------|--------- | -------------
39
- | A.1 | p15265.033.3703 | ✔️
40
- | A.3 | p12013.141.3703 | ✔️
41
- | A.3x | p15305.016.3701 |
42
- | A.6x | p15305.017.3701 | ✔️
41
+ | `A.1` | p15265.033.3703 | ✔️
42
+ | `A.3` | p12013.141.3703 | ✔️
43
+ | `A.3x` | p15305.016.3701 |
44
+ | `A.6x` | p15305.017.3701 | ✔️
43
45
 
44
46
 
45
47
  ## Testers Needed
@@ -54,33 +56,28 @@ There should not be any *[VSSL Agent's](https://vssl.gitbook.io/vssl-rest-api/ge
54
56
 
55
57
  **`vsslctrl` is not endorsed or affiliated with [VSSL](https://www.vssl.com/) in any manner.**
56
58
 
57
- ## TODOs
58
-
59
- * Better test coverage
60
- * A.1(x) testing - e.g output settings
61
- * More controls - e.g IR Control
62
-
63
59
  ## Basic Usage
64
60
 
65
61
  `vsslctrl` needs to be running inside a **[asyncio](https://docs.python.org/3/library/asyncio.html)** event loop.
66
62
 
67
- ### A.1 Example
63
+ ### `A.1(x)` Example
68
64
 
69
65
  ```python
70
66
  import asyncio
71
- from vsslctrl import Vssl, DeviceModels, Zone, ZoneIDs
67
+ from vsslctrl import Vssl
72
68
 
73
69
  async def main():
74
70
 
75
71
  # Represents a physical VSSL amplifier
76
- vssl = Vssl(DeviceModels.A1)
72
+ vssl = Vssl()
73
+
77
74
  a1 = vssl.add_zone('192.168.1.10')
78
75
 
79
76
  # Connect and initialise zone.
80
77
  await vssl.initialise()
81
78
 
82
79
  """Control Examples"""
83
- # Print device name
80
+ # Print zone/device name
84
81
  print(a1.settings.name)
85
82
  # Set volume to 25%
86
83
  a1.volume = 25
@@ -95,21 +92,21 @@ async def main():
95
92
  asyncio.run(main())
96
93
  ```
97
94
 
98
- ### A.3x Example
95
+ ### `A.3(x)` Example
99
96
 
100
97
  ```python
101
98
  import asyncio
102
- from vsslctrl import Vssl, DeviceModels, Zone, ZoneIDs
99
+ from vsslctrl import Vssl
103
100
 
104
101
  async def main():
105
102
 
106
103
  # Represents a physical VSSL amplifier
107
- vssl = Vssl(DeviceModels.A3X)
104
+ vssl = Vssl()
108
105
 
109
106
  # Add each you wish to control
110
- zone1 = vssl.add_zone('192.168.1.10', ZoneIDs.ZONE_1)
111
- zone2 = vssl.add_zone('192.168.1.11', ZoneIDs.ZONE_2)
112
- zone3 = vssl.add_zone('192.168.1.12', ZoneIDs.ZONE_3)
107
+ zone1 = vssl.add_zone('192.168.1.10')
108
+ zone2 = vssl.add_zone('192.168.1.11')
109
+ zone3 = vssl.add_zone('192.168.1.12')
113
110
  #... up to 6 zones for A.6(x)
114
111
 
115
112
  # Connect and initialise zones.
@@ -205,37 +202,6 @@ print(zone1.settings.name)
205
202
 
206
203
  # API Reference
207
204
 
208
- # `DeviceModels`
209
-
210
- A device model has to be passed to VSSL so it knows internally what features are supported by the device.
211
- This might be removed in the future if we can differentiate different models from the API.
212
-
213
- | Property | Description |
214
- | ------------|--------- |
215
- | `A1X` | A.1x |
216
- | `A3X` | A.3x |
217
- | `A6X` | A.6x |
218
- | `A1` | A1 |
219
- | `A3` | A3 |
220
- | `A6` | A6 |
221
-
222
- # `ZoneIDs`
223
-
224
- A `ZoneIDs` must be passed to each `zone` you which to control and it must match the zone on the VSSL device.
225
-
226
- If you are unsure of your `ZoneIDs` you could use the discovery helper to find out the correct mapping.
227
-
228
- | Property | Description | A.1(x) | A.3(x) | A.6(x)
229
- | ------------|--------- |-------- | -------- | -------- |
230
- | `A1` | |✔️ | | |
231
- | `ZONE_1` | Zone 1 | | ✔️| ✔️|
232
- | `ZONE_2` | Zone 2 | | ✔️| ✔️|
233
- | `ZONE_3` | Zone 3 | | ✔️| ✔️|
234
- | `ZONE_4` | Zone 4 | | | ✔️|
235
- | `ZONE_5` | Zone 5 | | | ✔️|
236
- | `ZONE_6` | Zone 6 | | | ✔️|
237
-
238
-
239
205
  # `Vssl`
240
206
 
241
207
  | Property | Description | Type |
@@ -257,11 +223,13 @@ vssl.factory_reset()
257
223
 
258
224
  ## `Vssl.settings`
259
225
 
226
+ `A.1(x)` doesn't use `Vssl.settings.name` use `Zone.settings.name` instead.
227
+
260
228
  | Property | Description | Type | Model: Default |
261
229
  | ---------------------- | ----------- | ----------- | ----------- |
262
- | `name` | Device name | `str` |
263
- | `bus_1_name` | Name of Bus 1 | `str` | <ul><li>A.1: Optical Input</li><li>A.3x/6x: Not Used</li></ul>
264
- | `bus_2_name` | Name of Bus 2 | `str` | <ul><li>A.1: Coax Input</li><li>A.3x/6x: Optical Input</li></ul>
230
+ | `name` | Device name | `str` |
231
+ | `bus_1_name` | Name of Bus 1 | `str` | <ul><li>`A.1`: Optical Input</li><li>`A.3x`/`A.6x`: Not Used</li></ul>
232
+ | `bus_2_name` | Name of Bus 2 | `str` | <ul><li>`A.1`: Coax Input</li><li>`A.3x`/`A.6x`: Optical Input</li></ul>
265
233
  | `bluetooth` | Bluetooth enabled / disabled | `bool` |
266
234
  | `bluetooth_toggle()` | Toggle Bluetooth | `func` |
267
235
 
@@ -385,8 +353,8 @@ zone1.transport.state = ZoneTransport.States.PAUSE
385
353
  | `priority` | Change input priority.<br/>Stream or local to have precedence | `int` | `InputRouter.Priorities`
386
354
 
387
355
 
388
- ### A.1(x) Source Routing Order
389
- A.1 and A.1x don't support manually changing the input source. Instead a fixed source routing order is used:
356
+ ### `A.1(x)` Source Routing Order
357
+ `A.1` and `A.1x` don't support manually changing the input source. Instead a fixed source routing order is used:
390
358
 
391
359
  1. Optical Input
392
360
  2. Coaxial Input
@@ -466,7 +434,7 @@ zone1.analog_output.is_fixed_volume = True
466
434
  | `name` | Name | `str` |
467
435
  | `disabled` | Disable the zone | `bool` || `False`
468
436
  | `disabled_toggle()` | disable / enable | `func` |
469
- | `mono` | Set output to mono or stereo | `int` | `ZoneSettings.StereoMono` | `Stereo`
437
+ | `mono` | Set output to mono or stereo | `int` | `ZoneSettings.StereoMono` | `STEREO`
470
438
  | `mono_toggle()` | Toggle mono or stereo | `func` |
471
439
 
472
440
  ```python
@@ -474,9 +442,9 @@ zone1.analog_output.is_fixed_volume = True
474
442
  # Set name
475
443
  zone1.settings.name = 'Living Room'
476
444
  # Disable Zone
477
- zone1.disabled = True
445
+ zone1.settings.disabled = True
478
446
  # Toggle mono output
479
- zone1.mono_toggle()
447
+ zone1.settings.mono_toggle()
480
448
  ```
481
449
 
482
450
  ## `Zone.settings.analog_input`
@@ -509,7 +477,7 @@ zone1.settings.analog_input.fixed_gain = 50
509
477
  # Set default on volume to 50%
510
478
  zone1.settings.volume.default_on = 50
511
479
  # Set maximum volume for left channel to 75%
512
- zone1.settings.volume.default_on = 75
480
+ zone1.settings.volume.max_left = 75
513
481
  ```
514
482
 
515
483
  ## `Zone.settings.eq`
@@ -561,12 +529,9 @@ Motivation for this project was to integrate VSSLs amplifiers into [Home Assista
561
529
 
562
530
  ## Known Issues & Limitiations
563
531
 
564
- * Not tested on A.1x or original A series range of amplifiers (testers welcome)
565
532
  * VSSL can not start a stream except for playing a URL directly. This is a limitation of the hardware itself.
566
533
  * Not all sources set the volume to 0 when the zone is muted
567
534
  * Airplay `Zone.track.progress` is not available.
568
- * Cant stop a URL playback, feedback is worng at least
569
535
  * VSSL likes to cache old track metadata. For example when playing a URL after Spotify, often the device will respond with the previous (Spotify) tracks metadata
570
536
  * `stop()` is intended to disconnect the client and pause the stream. Doesn’t always function this way, depending on stream source
571
- * Occasionally a zones might stop responding to certain commands, issuing the `reboot` command generally corrects
572
537
 
@@ -1,24 +1,25 @@
1
1
  # vsslctrl
2
2
 
3
- Package for controlling [VSSL's](https://www.vssl.com/) range of streaming amplifiers.
3
+ Python package for controlling [VSSL's](https://www.vssl.com/) range of streaming amplifiers.
4
4
 
5
5
  ## Coverage
6
6
 
7
- Tested on:
7
+ Test suite run on:
8
8
 
9
- | Model | Software Version | Note |
10
- | ------------|--------- | -------------
11
- | A.3x | p15305.016.3701 |
9
+ | Model | Software Version
10
+ | ------------|---------
11
+ | `A.1x` | p15243.022.3703
12
+ | `A.3x` | p15305.016.3701
12
13
 
13
14
 
14
15
  Home Assistant [integration](https://github.com/vsslctrl/integration.home-assistant) with basic functionality working on:
15
16
 
16
17
  | Model | Software Version | User Reported |
17
18
  | ------------|--------- | -------------
18
- | A.1 | p15265.033.3703 | ✔️
19
- | A.3 | p12013.141.3703 | ✔️
20
- | A.3x | p15305.016.3701 |
21
- | A.6x | p15305.017.3701 | ✔️
19
+ | `A.1` | p15265.033.3703 | ✔️
20
+ | `A.3` | p12013.141.3703 | ✔️
21
+ | `A.3x` | p15305.016.3701 |
22
+ | `A.6x` | p15305.017.3701 | ✔️
22
23
 
23
24
 
24
25
  ## Testers Needed
@@ -33,33 +34,28 @@ There should not be any *[VSSL Agent's](https://vssl.gitbook.io/vssl-rest-api/ge
33
34
 
34
35
  **`vsslctrl` is not endorsed or affiliated with [VSSL](https://www.vssl.com/) in any manner.**
35
36
 
36
- ## TODOs
37
-
38
- * Better test coverage
39
- * A.1(x) testing - e.g output settings
40
- * More controls - e.g IR Control
41
-
42
37
  ## Basic Usage
43
38
 
44
39
  `vsslctrl` needs to be running inside a **[asyncio](https://docs.python.org/3/library/asyncio.html)** event loop.
45
40
 
46
- ### A.1 Example
41
+ ### `A.1(x)` Example
47
42
 
48
43
  ```python
49
44
  import asyncio
50
- from vsslctrl import Vssl, DeviceModels, Zone, ZoneIDs
45
+ from vsslctrl import Vssl
51
46
 
52
47
  async def main():
53
48
 
54
49
  # Represents a physical VSSL amplifier
55
- vssl = Vssl(DeviceModels.A1)
50
+ vssl = Vssl()
51
+
56
52
  a1 = vssl.add_zone('192.168.1.10')
57
53
 
58
54
  # Connect and initialise zone.
59
55
  await vssl.initialise()
60
56
 
61
57
  """Control Examples"""
62
- # Print device name
58
+ # Print zone/device name
63
59
  print(a1.settings.name)
64
60
  # Set volume to 25%
65
61
  a1.volume = 25
@@ -74,21 +70,21 @@ async def main():
74
70
  asyncio.run(main())
75
71
  ```
76
72
 
77
- ### A.3x Example
73
+ ### `A.3(x)` Example
78
74
 
79
75
  ```python
80
76
  import asyncio
81
- from vsslctrl import Vssl, DeviceModels, Zone, ZoneIDs
77
+ from vsslctrl import Vssl
82
78
 
83
79
  async def main():
84
80
 
85
81
  # Represents a physical VSSL amplifier
86
- vssl = Vssl(DeviceModels.A3X)
82
+ vssl = Vssl()
87
83
 
88
84
  # Add each you wish to control
89
- zone1 = vssl.add_zone('192.168.1.10', ZoneIDs.ZONE_1)
90
- zone2 = vssl.add_zone('192.168.1.11', ZoneIDs.ZONE_2)
91
- zone3 = vssl.add_zone('192.168.1.12', ZoneIDs.ZONE_3)
85
+ zone1 = vssl.add_zone('192.168.1.10')
86
+ zone2 = vssl.add_zone('192.168.1.11')
87
+ zone3 = vssl.add_zone('192.168.1.12')
92
88
  #... up to 6 zones for A.6(x)
93
89
 
94
90
  # Connect and initialise zones.
@@ -184,37 +180,6 @@ print(zone1.settings.name)
184
180
 
185
181
  # API Reference
186
182
 
187
- # `DeviceModels`
188
-
189
- A device model has to be passed to VSSL so it knows internally what features are supported by the device.
190
- This might be removed in the future if we can differentiate different models from the API.
191
-
192
- | Property | Description |
193
- | ------------|--------- |
194
- | `A1X` | A.1x |
195
- | `A3X` | A.3x |
196
- | `A6X` | A.6x |
197
- | `A1` | A1 |
198
- | `A3` | A3 |
199
- | `A6` | A6 |
200
-
201
- # `ZoneIDs`
202
-
203
- A `ZoneIDs` must be passed to each `zone` you which to control and it must match the zone on the VSSL device.
204
-
205
- If you are unsure of your `ZoneIDs` you could use the discovery helper to find out the correct mapping.
206
-
207
- | Property | Description | A.1(x) | A.3(x) | A.6(x)
208
- | ------------|--------- |-------- | -------- | -------- |
209
- | `A1` | |✔️ | | |
210
- | `ZONE_1` | Zone 1 | | ✔️| ✔️|
211
- | `ZONE_2` | Zone 2 | | ✔️| ✔️|
212
- | `ZONE_3` | Zone 3 | | ✔️| ✔️|
213
- | `ZONE_4` | Zone 4 | | | ✔️|
214
- | `ZONE_5` | Zone 5 | | | ✔️|
215
- | `ZONE_6` | Zone 6 | | | ✔️|
216
-
217
-
218
183
  # `Vssl`
219
184
 
220
185
  | Property | Description | Type |
@@ -236,11 +201,13 @@ vssl.factory_reset()
236
201
 
237
202
  ## `Vssl.settings`
238
203
 
204
+ `A.1(x)` doesn't use `Vssl.settings.name` use `Zone.settings.name` instead.
205
+
239
206
  | Property | Description | Type | Model: Default |
240
207
  | ---------------------- | ----------- | ----------- | ----------- |
241
- | `name` | Device name | `str` |
242
- | `bus_1_name` | Name of Bus 1 | `str` | <ul><li>A.1: Optical Input</li><li>A.3x/6x: Not Used</li></ul>
243
- | `bus_2_name` | Name of Bus 2 | `str` | <ul><li>A.1: Coax Input</li><li>A.3x/6x: Optical Input</li></ul>
208
+ | `name` | Device name | `str` |
209
+ | `bus_1_name` | Name of Bus 1 | `str` | <ul><li>`A.1`: Optical Input</li><li>`A.3x`/`A.6x`: Not Used</li></ul>
210
+ | `bus_2_name` | Name of Bus 2 | `str` | <ul><li>`A.1`: Coax Input</li><li>`A.3x`/`A.6x`: Optical Input</li></ul>
244
211
  | `bluetooth` | Bluetooth enabled / disabled | `bool` |
245
212
  | `bluetooth_toggle()` | Toggle Bluetooth | `func` |
246
213
 
@@ -364,8 +331,8 @@ zone1.transport.state = ZoneTransport.States.PAUSE
364
331
  | `priority` | Change input priority.<br/>Stream or local to have precedence | `int` | `InputRouter.Priorities`
365
332
 
366
333
 
367
- ### A.1(x) Source Routing Order
368
- A.1 and A.1x don't support manually changing the input source. Instead a fixed source routing order is used:
334
+ ### `A.1(x)` Source Routing Order
335
+ `A.1` and `A.1x` don't support manually changing the input source. Instead a fixed source routing order is used:
369
336
 
370
337
  1. Optical Input
371
338
  2. Coaxial Input
@@ -445,7 +412,7 @@ zone1.analog_output.is_fixed_volume = True
445
412
  | `name` | Name | `str` |
446
413
  | `disabled` | Disable the zone | `bool` || `False`
447
414
  | `disabled_toggle()` | disable / enable | `func` |
448
- | `mono` | Set output to mono or stereo | `int` | `ZoneSettings.StereoMono` | `Stereo`
415
+ | `mono` | Set output to mono or stereo | `int` | `ZoneSettings.StereoMono` | `STEREO`
449
416
  | `mono_toggle()` | Toggle mono or stereo | `func` |
450
417
 
451
418
  ```python
@@ -453,9 +420,9 @@ zone1.analog_output.is_fixed_volume = True
453
420
  # Set name
454
421
  zone1.settings.name = 'Living Room'
455
422
  # Disable Zone
456
- zone1.disabled = True
423
+ zone1.settings.disabled = True
457
424
  # Toggle mono output
458
- zone1.mono_toggle()
425
+ zone1.settings.mono_toggle()
459
426
  ```
460
427
 
461
428
  ## `Zone.settings.analog_input`
@@ -488,7 +455,7 @@ zone1.settings.analog_input.fixed_gain = 50
488
455
  # Set default on volume to 50%
489
456
  zone1.settings.volume.default_on = 50
490
457
  # Set maximum volume for left channel to 75%
491
- zone1.settings.volume.default_on = 75
458
+ zone1.settings.volume.max_left = 75
492
459
  ```
493
460
 
494
461
  ## `Zone.settings.eq`
@@ -540,12 +507,9 @@ Motivation for this project was to integrate VSSLs amplifiers into [Home Assista
540
507
 
541
508
  ## Known Issues & Limitiations
542
509
 
543
- * Not tested on A.1x or original A series range of amplifiers (testers welcome)
544
510
  * VSSL can not start a stream except for playing a URL directly. This is a limitation of the hardware itself.
545
511
  * Not all sources set the volume to 0 when the zone is muted
546
512
  * Airplay `Zone.track.progress` is not available.
547
- * Cant stop a URL playback, feedback is worng at least
548
513
  * VSSL likes to cache old track metadata. For example when playing a URL after Spotify, often the device will respond with the previous (Spotify) tracks metadata
549
514
  * `stop()` is intended to disconnect the client and pause the stream. Doesn’t always function this way, depending on stream source
550
- * Occasionally a zones might stop responding to certain commands, issuing the `reboot` command generally corrects
551
515
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vsslctrl"
7
- version = "0.1.14.dev1"
7
+ version = "0.2.0"
8
8
  description = "Package for controlling VSSL's range of streaming amplifiers"
9
9
  authors = [
10
10
  { name="vsslctrl", email="vsslcontrolled@proton.me" },
@@ -0,0 +1,64 @@
1
+ import asyncio
2
+ import pytest
3
+ import pytest_asyncio
4
+
5
+ from vsslctrl import Vssl
6
+ from vsslctrl.api_alpha import APIAlpha
7
+ from vsslctrl.api_bravo import APIBravo
8
+
9
+ FUTURE_TIMEOUT = 5
10
+
11
+ # Mark all tests in this module with the pytest custom "integration" marker so
12
+ # they can be selected or deselected as a whole, eg:
13
+ # py.test -m "integration"
14
+ # or
15
+ # py.test -m "no integration"
16
+ pytestmark = pytest.mark.integration
17
+
18
+
19
+ @pytest_asyncio.fixture(scope="session")
20
+ async def zone(request):
21
+ ip = request.config.option.ip
22
+ if ip is None:
23
+ pytest.fail("No ip address specified. Use the --ip option.")
24
+
25
+ vssl = Vssl()
26
+
27
+ zone = vssl.add_zone(ip)
28
+
29
+ await vssl.initialise()
30
+
31
+ if not zone.initialised:
32
+ pytest.fail(f"Zone not initialised, dunno!")
33
+
34
+ # Yield the device to the test function
35
+ yield zone
36
+
37
+ # Tear down. Restore state
38
+ await vssl.disconnect()
39
+
40
+
41
+ @pytest_asyncio.fixture(scope="session")
42
+ async def eb(zone):
43
+ return zone.vssl.event_bus
44
+
45
+
46
+ @pytest_asyncio.fixture(scope="session")
47
+ async def vssl(zone):
48
+ return zone.vssl
49
+
50
+
51
+ class TestAPI:
52
+ @pytest.mark.asyncio
53
+ async def test_api_has_events(self):
54
+ required_events = [
55
+ "CONNECTING",
56
+ "CONNECTED",
57
+ "DISCONNECTING",
58
+ "DISCONNECTED",
59
+ "RECONNECTING",
60
+ ]
61
+
62
+ for event in required_events:
63
+ assert hasattr(APIAlpha.Events, event)
64
+ assert hasattr(APIBravo.Events, event)
@@ -0,0 +1,87 @@
1
+ import asyncio
2
+ import pytest
3
+ import pytest_asyncio
4
+
5
+ from vsslctrl import VSSL_VERSION
6
+ from vsslctrl.data_structure import ModelIDs
7
+ from vsslctrl.device import Models
8
+
9
+
10
+ class TestDevice:
11
+ @pytest.mark.asyncio
12
+ async def test_device_models(self):
13
+ model = Models.A1X.value
14
+ assert model.model_id == ModelIDs.A1X
15
+
16
+ model = Models.get_by_name("A.1x")
17
+ assert model.model_id == ModelIDs.A1X
18
+ assert model.model_id != ModelIDs.A1
19
+ assert model.name == "A.1x"
20
+ assert len(model.zones) == 1
21
+
22
+ model = Models.get_by_name("A1")
23
+ assert model.model_id != ModelIDs.A1X
24
+ assert model.model_id == ModelIDs.A1
25
+
26
+ model = Models.get_by_id(ModelIDs.A1X)
27
+ assert model.model_id == ModelIDs.A1X
28
+
29
+ model = Models.get_by_id("11")
30
+ assert model.model_id == ModelIDs.A1X
31
+
32
+ model = Models.get_by_name("random string")
33
+ assert model == None
34
+
35
+ model = Models.get_by_id(10)
36
+ assert model == None
37
+
38
+ model = Models.get_by_id("random string")
39
+ assert model == None
40
+
41
+ model = Models.get_by_id(ModelIDs.A3X)
42
+ assert model.model_id == ModelIDs.A3X
43
+ assert len(model.zones) == 3
44
+
45
+ model = Models.get_by_id(ModelIDs.A3)
46
+ assert model.model_id == ModelIDs.A3
47
+ assert len(model.zones) == 3
48
+
49
+ model = Models.get_by_id(ModelIDs.A6X)
50
+ assert model.model_id == ModelIDs.A6X
51
+ assert len(model.zones) == 6
52
+
53
+ model = Models.get_by_id(ModelIDs.A6)
54
+ assert model.model_id == ModelIDs.A6
55
+ assert len(model.zones) == 6
56
+
57
+ # Test Find
58
+ model = Models.find("A.1x")
59
+ assert model.model_id == ModelIDs.A1X
60
+ assert model.model_id != ModelIDs.A1
61
+ assert model.name == "A.1x"
62
+ assert len(model.zones) == 1
63
+ assert Models.is_valid(model)
64
+
65
+ model = Models.find("vssl A.6x")
66
+ assert model.model_id == ModelIDs.A6X
67
+ assert model.model_id != ModelIDs.A6
68
+ assert model.name == "A.6x"
69
+ assert len(model.zones) == 6
70
+
71
+ model = Models.find("vssl A6")
72
+ assert model.model_id == ModelIDs.A6
73
+ assert model.model_id != ModelIDs.A6X
74
+ assert model.name == "A.6"
75
+ assert len(model.zones) == 6
76
+
77
+ model = Models.find("A 3")
78
+ assert model.model_id == ModelIDs.A3
79
+ assert model.model_id != ModelIDs.A3X
80
+ assert model.name == "A.3"
81
+ assert len(model.zones) == 3
82
+
83
+ model = Models.find("VSSL A.3x")
84
+ assert model.model_id == ModelIDs.A3X
85
+ assert model.model_id != ModelIDs.A3
86
+ assert model.name == "A.3x"
87
+ assert len(model.zones) == 3
@@ -1,15 +1,15 @@
1
1
  import asyncio
2
2
  import pytest
3
3
  import pytest_asyncio
4
- from vsslctrl.event_bus import EventBus
4
+ from vsslctrl.event_bus import event_bus as _event_bus
5
5
 
6
6
 
7
7
  class TestEventBus:
8
8
  @pytest_asyncio.fixture(autouse=True)
9
9
  async def event_bus(self):
10
- bus = EventBus()
11
- yield bus
12
- bus.stop() # Ensure the event bus is stopped after each test
10
+ event_bus = _event_bus()
11
+ yield event_bus
12
+ event_bus.stop() # Ensure the event bus is stopped after each test
13
13
 
14
14
  @pytest.mark.asyncio
15
15
  async def test_event_bus(self, event_bus):
@@ -19,6 +19,7 @@ class TestEventBus:
19
19
  async def publish_with_entity(self, entity=None):
20
20
  # Define a callback function to be subscribed to the event
21
21
  async def callback(data, *args, **kwargs):
22
+ print("hello")
22
23
  callback.called = True
23
24
  callback.data = data
24
25