vsslctrl 0.1.6.dev1__py3-none-any.whl → 0.1.7.dev1__py3-none-any.whl

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.
vsslctrl/api_alpha.py CHANGED
@@ -16,6 +16,7 @@ from .data_structure import (
16
16
  ZoneEQStatusExtKeys,
17
17
  ZoneRouterStatusExtKeys,
18
18
  DeviceStatusExtKeys,
19
+ DeviceStatusExtendedExtKeys,
19
20
  )
20
21
 
21
22
 
@@ -94,10 +95,10 @@ class APIAlpha(APIBase):
94
95
 
95
96
  #
96
97
  # 00 [0] - 0B [11]
97
- # Status BT (Bluetooth)
98
+ # Status device extended
98
99
  #
99
100
  def request_action_00_0B(self):
100
- self._log_debug("Requesting status bluetooth")
101
+ self._log_debug("Requesting device status extended")
101
102
  self.send(bytearray([16, 0, 1, 11])) # HEX: 1000010B
102
103
 
103
104
  #
@@ -746,28 +747,25 @@ class APIAlpha(APIBase):
746
747
 
747
748
  #
748
749
  # 00_0B
749
- # System Status 0B (Not Sure)
750
+ # Device Status Extended 0B
750
751
  #
751
752
  # {'IRMskL': '241', 'IRMskH': '255', 'BTSta': '0', 'Crs': '0', 'Fes': '0', 'Drk': '0'}
752
753
  #
753
- #
754
- # IRMskL / IRMskH:
755
- # These could potentially be related to Infrared (IR) remote control signals. "IRMskL" and "IRMskH" might represent the low and high values of the modulation frequency or pulse width for an infrared signal.
756
- #
757
- # BTSta:
758
- # This might represent the Bluetooth status, with "0" indicating that Bluetooth is currently not active or disconnected.
759
- #
760
- # Crs:
761
- # It could stand for "Crossfade" and may represent a setting related to crossfading between audio tracks.
762
- #
763
- # Fes:
764
- # This might stand for "Frequency" or "Filter Effect Setting," representing a parameter related to frequency or filtering effects.
765
- #
766
- # Drk: ?
767
- #
768
754
  def response_action_00_0B(self, metadata: list):
769
755
  self._log_debug(f"Received 0B Status: {metadata}")
770
- pass
756
+
757
+ # Bluetooth
758
+ if DeviceStatusExtendedExtKeys.BLUETOOTH_STATUS in metadata:
759
+ self.vssl.settings._set_property(
760
+ "bluetooth", int(metadata[DeviceStatusExtendedExtKeys.BLUETOOTH_STATUS])
761
+ )
762
+
763
+ # Subwoofer Crossover
764
+ if DeviceStatusExtendedExtKeys.SUBWOOFER_CROSSOVER in metadata:
765
+ self.zone.settings.subwoofer._set_property(
766
+ "crossover",
767
+ int(metadata[DeviceStatusExtendedExtKeys.SUBWOOFER_CROSSOVER]),
768
+ )
771
769
 
772
770
  #
773
771
  # 2A [42]
@@ -92,6 +92,57 @@ class DeviceStatusExtKeys:
92
92
  return f"B{zone_id}Src"
93
93
 
94
94
 
95
+ """
96
+ JSON Structure
97
+
98
+ DO NOT CHANGE - VSSL Defined
99
+
100
+
101
+ IRMskL / IRMskH:
102
+ These could potentially be related to Infrared (IR) remote control signals. "IRMskL" and "IRMskH" might represent the low and high values of the modulation frequency or pulse width for an infrared signal.
103
+
104
+ BTSta:
105
+ This might represent the Bluetooth status, with "0" indicating that Bluetooth is currently not active or disconnected.
106
+
107
+ Crs:
108
+ It could stand for "Crossfade" and may represent a setting related to crossfading between audio tracks.
109
+
110
+ Fes:
111
+ This might stand for "Frequency" or "Filter Effect Setting," representing a parameter related to frequency or filtering effects.
112
+
113
+ Drk: ?
114
+
115
+
116
+ A3.x
117
+ {
118
+ 'IRMskL': '241',
119
+ 'IRMskH': '255',
120
+ 'BTSta': '0',
121
+ 'Crs': '0',
122
+ 'Fes': '0',
123
+ 'Drk': '0'
124
+ }
125
+
126
+ A6.x
127
+ {
128
+ "IRMskL":"255",
129
+ "IRMskH":"255",
130
+ "BTSta":"0",
131
+ "Crs":"0",
132
+ "Fes":"0",
133
+ "Drk":"0"
134
+ '
135
+
136
+ """
137
+
138
+
139
+ class DeviceStatusExtendedExtKeys:
140
+ IR_HIGH = "IRMskH" # Guess - To Confirm
141
+ IR_LOW = "IRMskL" # Guess - To Confirm
142
+ BLUETOOTH_STATUS = "BTSta" # Guess - To Confirm
143
+ SUBWOOFER_CROSSOVER = "Crs" # Guess - To Confirm
144
+
145
+
95
146
  """
96
147
  JSON Structure
97
148
 
vsslctrl/device.py CHANGED
@@ -36,6 +36,7 @@ class Features(VsslIntEnum):
36
36
  GROUPING = 1000
37
37
  BLUETOOTH = 1001
38
38
  PARTY_MODE = 1002
39
+ SUBWOOFER_CROSSOVER = 1003
39
40
 
40
41
 
41
42
  class Model:
@@ -65,7 +66,7 @@ class Models(VsslEnum):
65
66
  "zones": SINGLE_ZONE,
66
67
  "input_sources": INPUT_SOURCES_FOR_1_ZONE_DEVICE,
67
68
  "analog_output_sources": ANALOG_OUTPUT_SOURCES_FOR_1_ZONE_DEVICE,
68
- "features": [Features.BLUETOOTH],
69
+ "features": [Features.BLUETOOTH, Features.SUBWOOFER_CROSSOVER],
69
70
  }
70
71
  )
71
72
  A3 = Model(
@@ -92,7 +93,7 @@ class Models(VsslEnum):
92
93
  "zones": SINGLE_ZONE,
93
94
  "input_sources": INPUT_SOURCES_FOR_1_ZONE_DEVICE,
94
95
  "analog_output_sources": ANALOG_OUTPUT_SOURCES_FOR_1_ZONE_DEVICE,
95
- "features": [Features.GROUPING, Features.BLUETOOTH],
96
+ "features": [Features.BLUETOOTH, Features.SUBWOOFER_CROSSOVER],
96
97
  }
97
98
  )
98
99
  A3X = Model(
vsslctrl/io.py CHANGED
@@ -49,8 +49,8 @@ class InputRouter(ZoneDataClass):
49
49
  def __init__(self, zone: "zone.Zone"):
50
50
  self.zone = zone
51
51
 
52
- self._priority = self.Priorities.STREAM
53
- self._source = self.Sources.STREAM
52
+ self._priority = self.DEFAULTS["priority"]
53
+ self._source = self.DEFAULTS["source"]
54
54
 
55
55
  #
56
56
  # Input Priority
@@ -138,7 +138,7 @@ class AnalogOutput(ZoneDataClass):
138
138
  def __init__(self, zone: "zone.Zone"):
139
139
  self.zone = zone
140
140
 
141
- self._is_fixed_volume = False
141
+ self._is_fixed_volume = self.DEFAULTS["is_fixed_volume"]
142
142
  self._source = self.Sources(zone.id + 2)
143
143
 
144
144
  #
@@ -199,7 +199,7 @@ class AnalogInput(ZoneDataClass):
199
199
  self.zone = zone
200
200
 
201
201
  self._name = f"Analog In {self.zone.id}"
202
- self._fixed_gain = 0
202
+ self._fixed_gain = self.DEFAULTS["fixed_gain"]
203
203
 
204
204
  #
205
205
  # Analog Input Name
vsslctrl/settings.py CHANGED
@@ -2,6 +2,7 @@ import logging
2
2
  from typing import Dict, Union
3
3
  from .utils import clamp_volume
4
4
  from .io import AnalogInput
5
+ from .device import Features as DeviceFeatures
5
6
  from .data_structure import (
6
7
  VsslIntEnum,
7
8
  VsslDataClass,
@@ -16,6 +17,7 @@ class VsslSettings(VsslDataClass):
16
17
  class Keys:
17
18
  NAME = "name"
18
19
  OPTICAL_INPUT_NAME = "optical_input_name"
20
+ BLUETOOTH = "bluetooth"
19
21
 
20
22
  #
21
23
  # VSSL Events
@@ -24,17 +26,23 @@ class VsslSettings(VsslDataClass):
24
26
  PREFIX = VSSL_SETTINGS_EVENT_PREFIX
25
27
  NAME_CHANGE = PREFIX + "name_changed"
26
28
  OPTICAL_INPUT_NAME_CHANGE = PREFIX + "optical_input_name_changed"
29
+ BLUETOOTH_CHANGE = PREFIX + "bluetooth_changed"
27
30
 
28
31
  #
29
32
  # Defaults
30
33
  #
31
- DEFAULTS = {Keys.NAME: None, Keys.OPTICAL_INPUT_NAME: "Optical In"}
34
+ DEFAULTS = {
35
+ Keys.NAME: None,
36
+ Keys.OPTICAL_INPUT_NAME: "Optical In",
37
+ Keys.BLUETOOTH: 0,
38
+ }
32
39
 
33
40
  def __init__(self, vssl: "vsslctrl.Vssl"):
34
41
  self._vssl = vssl
35
42
 
36
43
  self._name = None # device name
37
44
  self._optical_input_name = self.DEFAULTS[self.Keys.OPTICAL_INPUT_NAME]
45
+ self._bluetooth = self.DEFAULTS[self.Keys.BLUETOOTH]
38
46
  self.power = VsslPowerSettings(vssl)
39
47
 
40
48
  #
@@ -63,6 +71,30 @@ class VsslSettings(VsslDataClass):
63
71
  if zone:
64
72
  zone.api_alpha.request_action_15_12(name)
65
73
 
74
+ #
75
+ # Bluetooth
76
+ #
77
+ @property
78
+ def bluetooth(self):
79
+ return bool(self._bluetooth)
80
+
81
+ @bluetooth.setter
82
+ def bluetooth(self, enabled: bool):
83
+ if not self._vssl.model.supports_feature(DeviceFeatures.BLUETOOTH):
84
+ self._vssl._log_error(
85
+ f"VSSL {self._vssl.model.name} does not support Bluetooth"
86
+ )
87
+ return
88
+
89
+ zone = self._vssl.get_connected_zone()
90
+ if zone:
91
+ # TODO
92
+ # zone.api_alpha.request_action_bluetooth(not not enabled)
93
+ pass
94
+
95
+ def bluetooth_toggle(self):
96
+ self.bluetooth = False if self.bluetooth else True
97
+
66
98
 
67
99
  class VsslPowerSettings(VsslDataClass):
68
100
  #
@@ -165,6 +197,7 @@ class ZoneSettings(ZoneDataClass):
165
197
  self._mono = self.StereoMono.Stereo
166
198
 
167
199
  self.eq = EQSettings(zone)
200
+ self.subwoofer = SubwooferSettings(zone)
168
201
  self.volume = VolumeSettings(zone)
169
202
  self.analog_input = AnalogInput(zone)
170
203
 
@@ -653,3 +686,59 @@ class EQSettings(ZoneDataClass):
653
686
  @khz15_db.setter
654
687
  def khz15_db(self, val: int):
655
688
  self._set_frequency_on_device_db(self.Freqs.KHZ15, val)
689
+
690
+
691
+ # Not in io.py to prevent circ import in device.py
692
+ class SubwooferSettings(ZoneDataClass):
693
+ MIN_VALUE = 0
694
+ MAX_VALUE = 1000
695
+
696
+ #
697
+ # Subwoofer events
698
+ #
699
+ class Events:
700
+ PREFIX = "zone.subwoofer."
701
+ CROSSOVER_CHANGE = PREFIX + "crossover_change"
702
+
703
+ #
704
+ # Defaults
705
+ #
706
+ DEFAULTS = {"crossover": 0}
707
+
708
+ def __init__(self, zone: "zone.Zone"):
709
+ self.zone = zone
710
+
711
+ self._crossover = self.DEFAULTS["crossover"]
712
+
713
+ #
714
+ # Clamp between min and max
715
+ #
716
+ def _clamp_crossover(self, value: int = 100):
717
+ return int(max(self.MIN_VALUE, min(value, self.MAX_VALUE)))
718
+
719
+ #
720
+ # Crossover freq
721
+ #
722
+ @property
723
+ def crossover(self):
724
+ return self._crossover
725
+
726
+ @crossover.setter
727
+ def crossover(self, freq: int):
728
+ if not self.zone.vssl.model.supports_feature(
729
+ DeviceFeatures.SUBWOOFER_CROSSOVER
730
+ ):
731
+ self.zone._log_error(
732
+ f"VSSL {self.zone.vssl.model.name} does not have a subwoofer output"
733
+ )
734
+ return
735
+ #
736
+ # TODO
737
+ #
738
+ # self.zone.api_alpha.request_action_crossover(freq)
739
+
740
+ def _set_crossover(self, freq: int):
741
+ freq = self._clamp_crossover(freq)
742
+ if self.crossover != freq:
743
+ self._crossover = freq
744
+ return True
vsslctrl/zone.py CHANGED
@@ -74,7 +74,7 @@ class Zone:
74
74
  self._request_eq_status,
75
75
  self._request_track,
76
76
  self._request_name,
77
- self._request_bt_status,
77
+ self._request_status_extended,
78
78
  ],
79
79
  )
80
80
 
@@ -128,6 +128,8 @@ class Zone:
128
128
  self.initialisation.set()
129
129
  self.vssl.event_bus.publish(self.Events.INITIALISED, self.id, self)
130
130
 
131
+ self._log_info(f"Zone {self.id} initialised")
132
+
131
133
  return self
132
134
 
133
135
  @property
@@ -406,7 +408,7 @@ class Zone:
406
408
  self.api_alpha.request_action_00_0A()
407
409
  return self
408
410
 
409
- def _request_bt_status(self):
411
+ def _request_status_extended(self):
410
412
  self.api_alpha.request_action_00_0B()
411
413
  return self
412
414
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vsslctrl
3
- Version: 0.1.6.dev1
3
+ Version: 0.1.7.dev1
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
@@ -1,23 +1,23 @@
1
1
  vsslctrl/__init__.py,sha256=W7cljoE7L3DukAVr_TEqkxFycYrUbJiqaHrOlbPkmKU,145
2
- vsslctrl/api_alpha.py,sha256=xJLTSEBWWKu5L9GLUbBv8LibZROEffOENc3vc22E1tw,33344
2
+ vsslctrl/api_alpha.py,sha256=RMmPR-_knVn1qzyAe9yohoMshrA9EnADscf7havSSN4,33202
3
3
  vsslctrl/api_base.py,sha256=a80CRtUkYK2OwXQOL8bP-YOvt4YE-ALd-VONza_gjK8,9623
4
4
  vsslctrl/api_bravo.py,sha256=pchZN16KIvdSrqkI1WU9PA9IEmxkyyP-XifxpRC0jDE,12283
5
5
  vsslctrl/core.py,sha256=13EEax6H3OWlkjIP49-l5z1nNyZQtb-BC_DwtVGPJuQ,8833
6
- vsslctrl/data_structure.py,sha256=Gq1Ip3A-sr9Ber571SPmdh9pEgOOXnG96gIenqx0dFE,7683
6
+ vsslctrl/data_structure.py,sha256=6L8k_0w-kHcJpmhXwsAR8Rvz16hWK8n2MWjapwjd9q8,8819
7
7
  vsslctrl/decorators.py,sha256=lG8cYSSA-fNvdcp_90lv58tj50bF6iPoigLKcucWSSI,1707
8
- vsslctrl/device.py,sha256=P4sYiibPPFp6iDuF93-OKIsWBL_9vtIxBPDc5uRwvbA,3418
8
+ vsslctrl/device.py,sha256=YSg5eO_jjFy1ygvhrc9LbjvrQENRV3ht0D_e0JJxmJo,3490
9
9
  vsslctrl/discovery.py,sha256=81uA3rbm-X_CR0Q0LsCQJyCs7638Bb-mgqNZWqV_dq4,5572
10
10
  vsslctrl/event_bus.py,sha256=9liAEJnZfthJwy5O-qc3pDIWRBv5AcV19PjMCwH_niU,5014
11
11
  vsslctrl/exceptions.py,sha256=EEfNlu--9PMIidj3V3ZB8c7p3My8siqKw7YIOjPFaqE,419
12
12
  vsslctrl/group.py,sha256=B7lmJb6DIwXGnMyTKsp-JfRWVCVRFQYzlvS71tutfx4,5368
13
- vsslctrl/io.py,sha256=crzz5-vsdQfVFBL9a1Btle8mq5zx41SsVSt4N5cWPaE,6048
14
- vsslctrl/settings.py,sha256=6zZUf2y-JvmEz6aREMZqxjTptMtVJFO6dGkXdCxR1NU,16587
13
+ vsslctrl/io.py,sha256=ndoWLyjgyrtDMk1aTeh5OVGoYIFwk6RVsekscxyRKGU,6108
14
+ vsslctrl/settings.py,sha256=fFVb5pUSMfqH_aZXj6xyI1OAgz-H_R0OcOph3ci_JmQ,18857
15
15
  vsslctrl/track.py,sha256=_lQNHYe8ZBm3v1lehFLjZruF5-Rz8PGWuQN-zISBgwc,10136
16
16
  vsslctrl/transport.py,sha256=XJFy0kl9y7hm3YV4_9ElBrq3u6--_k73UKJqKdO6Rqg,6993
17
17
  vsslctrl/utils.py,sha256=FWOMHM_sl1Cp1xab-V6b-A5B7qRmDN67hMMxV4ReWCk,2087
18
- vsslctrl/zone.py,sha256=94Rq_IX9HObTx2nxdVlxs_ZnNrBqrewBA7ksatwT_78,12911
19
- vsslctrl-0.1.6.dev1.dist-info/LICENSE,sha256=67Ekj8PIxjEvkPd7L3GYJ8dyvn1EC4sPM3q3FKtpN6w,1065
20
- vsslctrl-0.1.6.dev1.dist-info/METADATA,sha256=7SK_zgiNWPIQJ7H55_fXjHbMdDVKcfjZ_10HUX802X8,16640
21
- vsslctrl-0.1.6.dev1.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
22
- vsslctrl-0.1.6.dev1.dist-info/top_level.txt,sha256=MRycO_RKageNX27UZ2bLJDMlfeWTQzUZlsd3hS-0u3Y,9
23
- vsslctrl-0.1.6.dev1.dist-info/RECORD,,
18
+ vsslctrl/zone.py,sha256=teSk8oE3LKteidRnuVK3koUOqtN-90LclEx0-T4FhAE,12978
19
+ vsslctrl-0.1.7.dev1.dist-info/LICENSE,sha256=67Ekj8PIxjEvkPd7L3GYJ8dyvn1EC4sPM3q3FKtpN6w,1065
20
+ vsslctrl-0.1.7.dev1.dist-info/METADATA,sha256=dnBDtqVPZFY0b74EQfIOVCJM_IlMLlJinmWsaANTzQg,16640
21
+ vsslctrl-0.1.7.dev1.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
22
+ vsslctrl-0.1.7.dev1.dist-info/top_level.txt,sha256=MRycO_RKageNX27UZ2bLJDMlfeWTQzUZlsd3hS-0u3Y,9
23
+ vsslctrl-0.1.7.dev1.dist-info/RECORD,,