marantz-rs232 1.0.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.
- marantz_rs232-1.0.0/PKG-INFO +551 -0
- marantz_rs232-1.0.0/README.md +529 -0
- marantz_rs232-1.0.0/pyproject.toml +46 -0
- marantz_rs232-1.0.0/src/marantz_rs232/__init__.py +221 -0
- marantz_rs232-1.0.0/src/marantz_rs232/__main__.py +550 -0
- marantz_rs232-1.0.0/src/marantz_rs232/probe.py +93 -0
- marantz_rs232-1.0.0/src/marantz_rs232/py.typed +0 -0
- marantz_rs232-1.0.0/src/marantz_rs232/v2003/__init__.py +62 -0
- marantz_rs232-1.0.0/src/marantz_rs232/v2003/const.py +338 -0
- marantz_rs232-1.0.0/src/marantz_rs232/v2003/players.py +294 -0
- marantz_rs232-1.0.0/src/marantz_rs232/v2003/protocol.py +327 -0
- marantz_rs232-1.0.0/src/marantz_rs232/v2003/receiver.py +558 -0
- marantz_rs232-1.0.0/src/marantz_rs232/v2003/state.py +89 -0
- marantz_rs232-1.0.0/src/marantz_rs232/v2007/__init__.py +98 -0
- marantz_rs232-1.0.0/src/marantz_rs232/v2007/const.py +530 -0
- marantz_rs232-1.0.0/src/marantz_rs232/v2007/players.py +729 -0
- marantz_rs232-1.0.0/src/marantz_rs232/v2007/protocol.py +169 -0
- marantz_rs232-1.0.0/src/marantz_rs232/v2007/receiver.py +871 -0
- marantz_rs232-1.0.0/src/marantz_rs232/v2007/state.py +161 -0
- marantz_rs232-1.0.0/src/marantz_rs232/v2015/__init__.py +110 -0
- marantz_rs232-1.0.0/src/marantz_rs232/v2015/const.py +586 -0
- marantz_rs232-1.0.0/src/marantz_rs232/v2015/players.py +650 -0
- marantz_rs232-1.0.0/src/marantz_rs232/v2015/protocol.py +51 -0
- marantz_rs232-1.0.0/src/marantz_rs232/v2015/receiver.py +937 -0
- marantz_rs232-1.0.0/src/marantz_rs232/v2015/state.py +129 -0
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: marantz-rs232
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Async library to control Marantz receivers over RS232
|
|
5
|
+
Author: Paulus Schoutsen
|
|
6
|
+
Author-email: Paulus Schoutsen <balloob@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Topic :: Home Automation
|
|
16
|
+
Classifier: Topic :: System :: Hardware
|
|
17
|
+
Classifier: Framework :: AsyncIO
|
|
18
|
+
Requires-Dist: serialx[esphome]>=1.2.0
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Project-URL: Repository, https://github.com/home-assistant-libs/marantz-rs232
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# marantz-rs232
|
|
24
|
+
|
|
25
|
+
Async Python library to control Marantz AV receivers over RS232 serial, built on [serialx](https://github.com/puddly/serialx).
|
|
26
|
+
|
|
27
|
+
Supports two distinct Marantz protocols:
|
|
28
|
+
|
|
29
|
+
- **Modern** (2015 lineup, `PREFIX+VALUE\r` framing): NR1506, NR1606, SR5010, SR6010, SR7010, AV7702mkII — `MarantzReceiver`.
|
|
30
|
+
- **Legacy** (2007–2010 lineup, `@CMD:VALUE\r` framing): SR7002, SR8002, SR6003, SR7003, SR8003, SR5004, SR6004, AV7005, AV8003 — `MarantzLegacyReceiver`.
|
|
31
|
+
|
|
32
|
+
If you don't know which protocol your receiver speaks, use `probe()` to auto-detect.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install marantz-rs232
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Requires Python 3.12+.
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
### Modern receivers (2015 lineup)
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import asyncio
|
|
48
|
+
from marantz_rs232 import MarantzReceiver, InputSource
|
|
49
|
+
|
|
50
|
+
async def main():
|
|
51
|
+
receiver = MarantzReceiver("/dev/ttyUSB0")
|
|
52
|
+
await receiver.connect()
|
|
53
|
+
await receiver.query_state()
|
|
54
|
+
|
|
55
|
+
# State is fully populated after query_state()
|
|
56
|
+
print(f"Power: {receiver.state.power}")
|
|
57
|
+
print(f"Volume: {receiver.state.main_zone.volume} dB")
|
|
58
|
+
print(f"Input: {receiver.state.main_zone.input_source}")
|
|
59
|
+
|
|
60
|
+
# Control the receiver
|
|
61
|
+
await receiver.main.set_volume(-30.0)
|
|
62
|
+
await receiver.main.select_input_source(InputSource.BD)
|
|
63
|
+
|
|
64
|
+
await receiver.disconnect()
|
|
65
|
+
|
|
66
|
+
asyncio.run(main())
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Legacy receivers (SR7002 era)
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import asyncio
|
|
73
|
+
from marantz_rs232 import MarantzLegacyReceiver, LegacySource, LegacyModel
|
|
74
|
+
|
|
75
|
+
async def main():
|
|
76
|
+
# `model` is optional — defaults to GENERIC. Pass SR8002 to enable
|
|
77
|
+
# Multi Room B and HD Radio metadata.
|
|
78
|
+
receiver = MarantzLegacyReceiver("/dev/ttyUSB0", model=LegacyModel.SR7002)
|
|
79
|
+
await receiver.connect()
|
|
80
|
+
await receiver.query_state()
|
|
81
|
+
|
|
82
|
+
print(f"Power: {receiver.state.main.power}")
|
|
83
|
+
print(f"Volume: {receiver.state.main.volume} dB")
|
|
84
|
+
print(f"Surround: {receiver.state.main.surround_mode}")
|
|
85
|
+
|
|
86
|
+
await receiver.main.set_volume(-30.0)
|
|
87
|
+
await receiver.main.select_source(LegacySource.CD_CDR)
|
|
88
|
+
|
|
89
|
+
await receiver.disconnect()
|
|
90
|
+
|
|
91
|
+
asyncio.run(main())
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Auto-detect (don't know which protocol)
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from marantz_rs232 import probe
|
|
98
|
+
|
|
99
|
+
cls = await probe("/dev/ttyUSB0") # MarantzReceiver or MarantzLegacyReceiver
|
|
100
|
+
receiver = cls("/dev/ttyUSB0")
|
|
101
|
+
await receiver.connect()
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## CLI
|
|
105
|
+
|
|
106
|
+
A built-in CLI lets you quickly test your serial connection:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Modern receiver (default)
|
|
110
|
+
python -m marantz_rs232 /dev/ttyUSB0
|
|
111
|
+
|
|
112
|
+
# Modern + probe input sources
|
|
113
|
+
python -m marantz_rs232 /dev/ttyUSB0 --probe
|
|
114
|
+
|
|
115
|
+
# Legacy (SR7002-era) receiver
|
|
116
|
+
python -m marantz_rs232 /dev/ttyUSB0 --legacy
|
|
117
|
+
python -m marantz_rs232 /dev/ttyUSB0 --legacy --model SR8002
|
|
118
|
+
|
|
119
|
+
# Auto-detect protocol on the wire
|
|
120
|
+
python -m marantz_rs232 /dev/ttyUSB0 --detect
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Features
|
|
124
|
+
|
|
125
|
+
### Full state after query
|
|
126
|
+
|
|
127
|
+
`connect()` only opens and verifies the serial connection. Call `query_state()` when you want the current receiver state populated into the `state` property. After that, state is kept up to date via events from the receiver.
|
|
128
|
+
|
|
129
|
+
Control lives on shared player objects:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
receiver.main
|
|
133
|
+
receiver.zone_2
|
|
134
|
+
receiver.zone_3
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
receiver = MarantzReceiver("/dev/ttyUSB0")
|
|
139
|
+
await receiver.connect()
|
|
140
|
+
await receiver.query_state()
|
|
141
|
+
|
|
142
|
+
state = receiver.state
|
|
143
|
+
state.power # True / False (overall power)
|
|
144
|
+
state.main_zone.power # True / False (main zone)
|
|
145
|
+
state.main_zone.volume # float in dB (0.0 = reference, -80.0 = min, +18.0 = max)
|
|
146
|
+
state.main_zone.mute # True / False
|
|
147
|
+
state.main_zone.input_source # InputSource enum
|
|
148
|
+
state.main_zone.surround_mode # str (e.g. "STEREO", "DOLBY DIGITAL", "DTS SURROUND")
|
|
149
|
+
state.main_zone.digital_input # DigitalInputMode enum
|
|
150
|
+
state.main_zone.audio_decode # AudioDecodeMode enum (AUTO / PCM / DTS)
|
|
151
|
+
state.main_zone.video_select # InputSource or None
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Event subscription
|
|
155
|
+
|
|
156
|
+
Subscribe to state changes to react in real-time. Callbacks receive a `ReceiverState` snapshot on updates, or `None` when the connection is lost.
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
def on_state_change(state):
|
|
160
|
+
if state is None:
|
|
161
|
+
print("Disconnected!")
|
|
162
|
+
return
|
|
163
|
+
mz = state.main_zone
|
|
164
|
+
print(f"Volume: {mz.volume} dB, Source: {mz.input_source}")
|
|
165
|
+
|
|
166
|
+
unsub = receiver.subscribe(on_state_change)
|
|
167
|
+
# Later:
|
|
168
|
+
unsub() # stop receiving events
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Receiver power
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
await receiver.power_on()
|
|
175
|
+
await receiver.power_standby()
|
|
176
|
+
power = await receiver.query_power() # bool
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Main zone
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
await receiver.main.power_on()
|
|
183
|
+
await receiver.main.power_standby()
|
|
184
|
+
on = await receiver.main.query_power() # bool
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Master volume
|
|
188
|
+
|
|
189
|
+
Volume is represented in dB: 0.0 dB is the reference level, -80.0 is minimum, +18.0 is maximum. Half-dB steps are supported.
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
await receiver.main.set_volume(-25.0) # set to -25 dB
|
|
193
|
+
await receiver.main.set_volume(-25.5) # half-dB step
|
|
194
|
+
await receiver.main.volume_up()
|
|
195
|
+
await receiver.main.volume_down()
|
|
196
|
+
db = await receiver.main.query_volume() # float
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Channel volumes
|
|
200
|
+
|
|
201
|
+
Individual channel levels, relative to the master volume. 0.0 dB is neutral, range is -12.0 to +12.0 dB. Available channels depend on the speaker configuration: FL, FR, C, SW, SL, SR, SBL, SBR, SB, FH, FW.
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
await receiver.main.set_channel_volume("FL", 2.0) # front left +2 dB
|
|
205
|
+
await receiver.main.set_channel_volume("SW", -3.5) # subwoofer -3.5 dB
|
|
206
|
+
await receiver.main.channel_volume_up("C")
|
|
207
|
+
await receiver.main.channel_volume_down("FR")
|
|
208
|
+
|
|
209
|
+
# All channel volumes are in state after connect:
|
|
210
|
+
state.main_zone.channel_volumes # {"FL": 0.0, "FR": 0.0, "C": -1.0, ...}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Mute
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
await receiver.main.mute_on()
|
|
217
|
+
await receiver.main.mute_off()
|
|
218
|
+
muted = await receiver.main.query_mute() # bool
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Input source
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
from marantz_rs232 import InputSource
|
|
225
|
+
|
|
226
|
+
await receiver.main.select_input_source(InputSource.BD)
|
|
227
|
+
source = await receiver.main.query_input_source() # InputSource enum
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Available sources depend on the model. See [Input sources](#input-sources) below.
|
|
231
|
+
|
|
232
|
+
### Surround mode
|
|
233
|
+
|
|
234
|
+
Surround mode is kept as a plain string because receivers return many combined mode names (e.g. `"DOLBY DIGITAL"`, `"DTS SURROUND"`, `"AURO3D"`).
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
await receiver.main.set_surround_mode("STEREO")
|
|
238
|
+
await receiver.main.set_surround_mode("DOLBY DIGITAL")
|
|
239
|
+
await receiver.main.set_surround_mode("DTS SURROUND")
|
|
240
|
+
await receiver.main.set_surround_mode("DIRECT")
|
|
241
|
+
await receiver.main.set_surround_mode("PURE DIRECT")
|
|
242
|
+
await receiver.main.set_surround_mode("MCH STEREO")
|
|
243
|
+
await receiver.main.set_surround_mode("AURO3D")
|
|
244
|
+
mode = await receiver.main.query_surround_mode() # str
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Digital input mode
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
from marantz_rs232 import DigitalInputMode
|
|
251
|
+
|
|
252
|
+
await receiver.main.set_digital_input(DigitalInputMode.AUTO)
|
|
253
|
+
await receiver.main.set_digital_input(DigitalInputMode.HDMI)
|
|
254
|
+
await receiver.main.set_digital_input(DigitalInputMode.DIGITAL)
|
|
255
|
+
await receiver.main.set_digital_input(DigitalInputMode.ANALOG)
|
|
256
|
+
await receiver.main.set_digital_input(DigitalInputMode.EXT_IN)
|
|
257
|
+
await receiver.main.set_digital_input(DigitalInputMode.SEVEN_1_IN)
|
|
258
|
+
mode = await receiver.main.query_digital_input() # DigitalInputMode enum or None ("NO")
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Audio decode
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
from marantz_rs232 import AudioDecodeMode
|
|
265
|
+
|
|
266
|
+
await receiver.main.set_audio_decode(AudioDecodeMode.AUTO)
|
|
267
|
+
await receiver.main.set_audio_decode(AudioDecodeMode.PCM)
|
|
268
|
+
await receiver.main.set_audio_decode(AudioDecodeMode.DTS)
|
|
269
|
+
mode = await receiver.main.query_audio_decode()
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Video select
|
|
273
|
+
|
|
274
|
+
Override the video source independently from the main input source:
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
await receiver.main.set_video_select(InputSource.DVD)
|
|
278
|
+
await receiver.main.cancel_video_select() # return to following input
|
|
279
|
+
source = await receiver.main.query_video_select()
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Tone control
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
# Tone control on/off
|
|
286
|
+
await receiver.main.tone_control_on()
|
|
287
|
+
await receiver.main.tone_control_off()
|
|
288
|
+
|
|
289
|
+
# Bass / treble: dB values from -6 to +6
|
|
290
|
+
await receiver.main.set_bass(3)
|
|
291
|
+
await receiver.main.set_treble(-2)
|
|
292
|
+
await receiver.main.bass_up()
|
|
293
|
+
await receiver.main.bass_down()
|
|
294
|
+
await receiver.main.treble_up()
|
|
295
|
+
await receiver.main.treble_down()
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Audyssey / EQ settings
|
|
299
|
+
|
|
300
|
+
```python
|
|
301
|
+
from marantz_rs232 import MultEQ, DynamicVolume, DRC
|
|
302
|
+
|
|
303
|
+
# Cinema EQ
|
|
304
|
+
await receiver.main.cinema_eq_on()
|
|
305
|
+
await receiver.main.cinema_eq_off()
|
|
306
|
+
|
|
307
|
+
# MultEQ XT/XT32
|
|
308
|
+
await receiver.main.set_multeq(MultEQ.AUDYSSEY)
|
|
309
|
+
await receiver.main.set_multeq(MultEQ.FLAT)
|
|
310
|
+
await receiver.main.set_multeq(MultEQ.OFF)
|
|
311
|
+
|
|
312
|
+
# Dynamic EQ
|
|
313
|
+
await receiver.main.dynamic_eq_on()
|
|
314
|
+
await receiver.main.dynamic_eq_off()
|
|
315
|
+
|
|
316
|
+
# Dynamic Volume
|
|
317
|
+
await receiver.main.set_dynamic_volume(DynamicVolume.MED)
|
|
318
|
+
await receiver.main.set_dynamic_volume(DynamicVolume.OFF)
|
|
319
|
+
|
|
320
|
+
# Dynamic Range Compression
|
|
321
|
+
await receiver.main.set_drc(DRC.AUTO)
|
|
322
|
+
await receiver.main.set_drc(DRC.HI)
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
All parameter settings are available in `state` after connect:
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
state.main_zone.tone_control # bool
|
|
329
|
+
state.main_zone.bass # float
|
|
330
|
+
state.main_zone.treble # float
|
|
331
|
+
state.main_zone.cinema_eq # bool
|
|
332
|
+
state.main_zone.multeq # MultEQ enum
|
|
333
|
+
state.main_zone.dynamic_eq # bool
|
|
334
|
+
state.main_zone.dynamic_volume # DynamicVolume enum
|
|
335
|
+
state.main_zone.drc # DRC enum
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Sleep / ECO / Standby / Dimmer
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
from marantz_rs232 import EcoMode, DimmerMode
|
|
342
|
+
|
|
343
|
+
# Sleep timer (minutes)
|
|
344
|
+
await receiver.main.set_sleep(30)
|
|
345
|
+
await receiver.main.sleep_off()
|
|
346
|
+
|
|
347
|
+
# ECO mode
|
|
348
|
+
await receiver.main.set_eco(EcoMode.AUTO)
|
|
349
|
+
await receiver.main.set_eco(EcoMode.ON)
|
|
350
|
+
await receiver.main.set_eco(EcoMode.OFF)
|
|
351
|
+
|
|
352
|
+
# Auto standby
|
|
353
|
+
await receiver.main.set_auto_standby("2H")
|
|
354
|
+
await receiver.main.auto_standby_off()
|
|
355
|
+
|
|
356
|
+
# Front-panel dimmer
|
|
357
|
+
await receiver.main.set_dimmer(DimmerMode.BRI)
|
|
358
|
+
await receiver.main.set_dimmer(DimmerMode.DIM)
|
|
359
|
+
await receiver.main.set_dimmer(DimmerMode.DAR)
|
|
360
|
+
await receiver.main.set_dimmer(DimmerMode.OFF)
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Tuner
|
|
364
|
+
|
|
365
|
+
```python
|
|
366
|
+
from marantz_rs232 import TunerBand, TunerMode
|
|
367
|
+
|
|
368
|
+
await receiver.main.set_tuner_band(TunerBand.FM)
|
|
369
|
+
await receiver.main.set_tuner_mode(TunerMode.AUTO)
|
|
370
|
+
await receiver.main.set_tuner_frequency("105000") # FM 105.0 MHz
|
|
371
|
+
await receiver.main.set_tuner_preset("A1")
|
|
372
|
+
await receiver.main.tuner_frequency_up()
|
|
373
|
+
await receiver.main.tuner_frequency_down()
|
|
374
|
+
await receiver.main.tuner_preset_up()
|
|
375
|
+
await receiver.main.tuner_preset_down()
|
|
376
|
+
|
|
377
|
+
freq = await receiver.main.query_tuner_frequency() # str
|
|
378
|
+
preset = await receiver.main.query_tuner_preset() # str
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Tuner band and mode are available in state (`state.main_zone.tuner_band`, `state.main_zone.tuner_mode`).
|
|
382
|
+
|
|
383
|
+
### Multi-zone
|
|
384
|
+
|
|
385
|
+
Zone 2 and Zone 3 can be controlled independently. Zone state (power, source, volume, mute) is populated by `query_state()` and updated via events.
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
# Zone 2
|
|
389
|
+
await receiver.zone_2.power_on()
|
|
390
|
+
await receiver.zone_2.power_standby()
|
|
391
|
+
await receiver.zone_2.select_input_source(InputSource.TUNER)
|
|
392
|
+
await receiver.zone_2.set_volume(-30.0)
|
|
393
|
+
await receiver.zone_2.volume_up()
|
|
394
|
+
await receiver.zone_2.volume_down()
|
|
395
|
+
await receiver.zone_2.mute_on()
|
|
396
|
+
await receiver.zone_2.mute_off()
|
|
397
|
+
|
|
398
|
+
# Zone 3
|
|
399
|
+
await receiver.zone_3.power_on()
|
|
400
|
+
await receiver.zone_3.power_standby()
|
|
401
|
+
await receiver.zone_3.select_input_source(InputSource.CD)
|
|
402
|
+
await receiver.zone_3.set_volume(-35.0)
|
|
403
|
+
await receiver.zone_3.mute_on()
|
|
404
|
+
await receiver.zone_3.mute_off()
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Zone state in `state`:
|
|
408
|
+
|
|
409
|
+
```python
|
|
410
|
+
state.zone_2.power # bool
|
|
411
|
+
state.zone_2.input_source # InputSource
|
|
412
|
+
state.zone_2.volume # float in dB
|
|
413
|
+
state.zone_2.mute # bool
|
|
414
|
+
state.zone_3.power # bool
|
|
415
|
+
state.zone_3.input_source # InputSource
|
|
416
|
+
state.zone_3.volume # float in dB
|
|
417
|
+
state.zone_3.mute # bool
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Source probing
|
|
421
|
+
|
|
422
|
+
Discover which input sources the receiver actually supports by trying each one:
|
|
423
|
+
|
|
424
|
+
```python
|
|
425
|
+
sources = await receiver.probe_sources()
|
|
426
|
+
# frozenset({InputSource.CD, InputSource.BD, InputSource.TUNER, ...})
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
This briefly switches through all input sources and restores the original when done. Nothing should be playing during probing.
|
|
430
|
+
|
|
431
|
+
### Connection handling
|
|
432
|
+
|
|
433
|
+
The library handles connection errors gracefully:
|
|
434
|
+
|
|
435
|
+
- If the receiver doesn't respond during `connect()`, a `ConnectionError` is raised.
|
|
436
|
+
- If the serial connection is lost (cable unplugged, device error), subscribers receive `None` and `connected` becomes `False`.
|
|
437
|
+
- Write errors during commands propagate the exception and tear down the connection.
|
|
438
|
+
|
|
439
|
+
```python
|
|
440
|
+
try:
|
|
441
|
+
await receiver.connect()
|
|
442
|
+
except ConnectionError:
|
|
443
|
+
print("Receiver not responding")
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## Input sources
|
|
447
|
+
|
|
448
|
+
| Source | Protocol value |
|
|
449
|
+
|--------|---------------|
|
|
450
|
+
| `PHONO` | PHONO |
|
|
451
|
+
| `CD` | CD |
|
|
452
|
+
| `TUNER` | TUNER |
|
|
453
|
+
| `DVD` | DVD |
|
|
454
|
+
| `BD` | BD |
|
|
455
|
+
| `TV` | TV |
|
|
456
|
+
| `SAT_CBL` | SAT/CBL |
|
|
457
|
+
| `SAT` | SAT |
|
|
458
|
+
| `MPLAY` | MPLAY |
|
|
459
|
+
| `VCR` | VCR |
|
|
460
|
+
| `GAME` | GAME |
|
|
461
|
+
| `V_AUX` | V.AUX |
|
|
462
|
+
| `HDRADIO` | HDRADIO |
|
|
463
|
+
| `SIRIUS` | SIRIUS |
|
|
464
|
+
| `SIRIUSXM` | SIRIUSXM |
|
|
465
|
+
| `SPOTIFY` | SPOTIFY |
|
|
466
|
+
| `RHAPSODY` | RHAPSODY |
|
|
467
|
+
| `PANDORA` | PANDORA |
|
|
468
|
+
| `NAPSTER` | NAPSTER |
|
|
469
|
+
| `LASTFM` | LASTFM |
|
|
470
|
+
| `FLICKR` | FLICKR |
|
|
471
|
+
| `IRADIO` | IRADIO |
|
|
472
|
+
| `SERVER` | SERVER |
|
|
473
|
+
| `FAVORITES` | FAVORITES |
|
|
474
|
+
| `CDR` | CDR |
|
|
475
|
+
| `AUX1` - `AUX7` | AUX1-AUX7 |
|
|
476
|
+
| `NET` | NET |
|
|
477
|
+
| `NET_USB` | NET/USB |
|
|
478
|
+
| `BT` | BT |
|
|
479
|
+
| `M_XPORT` | MXPORT |
|
|
480
|
+
| `USB_IPOD` | USB/IPOD |
|
|
481
|
+
|
|
482
|
+
Not all sources exist on every receiver. Use `probe_sources()` to determine which sources your receiver supports.
|
|
483
|
+
|
|
484
|
+
## Serial connection
|
|
485
|
+
|
|
486
|
+
The library uses [serialx](https://github.com/puddly/serialx) for async serial communication. Marantz RS232 receivers use 9600 baud, 8 data bits, no parity, 1 stop bit (8N1) on a DB-9 connector.
|
|
487
|
+
|
|
488
|
+
## Legacy receivers (SR7002 era)
|
|
489
|
+
|
|
490
|
+
For 2007–2010 Marantz units that speak the older `@CMD:VALUE\r` protocol, use `MarantzLegacyReceiver`. The full SR7002/SR8002 spec is implemented: power, mute (audio + video), attenuator, 7.1 ch input, volume (with .5 dB encoding), tone, source (2-character video+audio status), speaker A/B, HDMI out + audio mode, IP converter, surround mode, THX, EQ mode, Dolby Headphone, night mode, M-DAX, lip sync, sleep, menu, cursor, front-key lock, DC triggers, test tone, full tuner family (AM/FM/XM frequency, presets, mode, memory/clear), XM navigation and metadata, status-only signal info (input AD, signal type/state, signal format, sampling frequency, channel status, firmware version, auto lip sync), full Multi Room A, plus SR8002-only Multi Room B (`=` separator) and HD Radio metadata (`*` separator).
|
|
491
|
+
|
|
492
|
+
```python
|
|
493
|
+
from marantz_rs232 import MarantzLegacyReceiver, LegacyModel, LegacySource, LegacyTHXSet
|
|
494
|
+
|
|
495
|
+
# Pass model=LegacyModel.SR8002 to unlock SR8002-only features without warnings.
|
|
496
|
+
receiver = MarantzLegacyReceiver("/dev/ttyUSB0", model=LegacyModel.SR7002)
|
|
497
|
+
await receiver.connect()
|
|
498
|
+
|
|
499
|
+
# Main zone control mirrors the modern API where possible.
|
|
500
|
+
await receiver.main.power_on()
|
|
501
|
+
await receiver.main.set_volume(-25.0)
|
|
502
|
+
await receiver.main.set_thx_mode(LegacyTHXSet.CINEMA)
|
|
503
|
+
await receiver.main.set_tuner_fm_frequency(101.10)
|
|
504
|
+
|
|
505
|
+
# Multi Room A (also Multi Room B on SR8002).
|
|
506
|
+
await receiver.multi_room_a.power_on()
|
|
507
|
+
await receiver.multi_room_a.set_line_volume(-30.0)
|
|
508
|
+
|
|
509
|
+
# Auto-status feedback (`@AST:F`) is enabled on connect, so subscribers
|
|
510
|
+
# see spontaneous receiver state changes the same way as the modern API.
|
|
511
|
+
unsub = receiver.subscribe(lambda state: print(f"changed: {state.main.volume} dB"))
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
`receiver.state` is a `LegacyReceiverState`. The schema differs from the modern receiver — see `marantz_rs232.legacy.LegacyMainState` for the field list.
|
|
515
|
+
|
|
516
|
+
### Auto-detect
|
|
517
|
+
|
|
518
|
+
```python
|
|
519
|
+
from marantz_rs232 import probe
|
|
520
|
+
|
|
521
|
+
# Probes both protocols and returns whichever class matches the wire.
|
|
522
|
+
cls = await probe("/dev/ttyUSB0")
|
|
523
|
+
receiver = cls("/dev/ttyUSB0")
|
|
524
|
+
await receiver.connect()
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
## Supported models
|
|
528
|
+
|
|
529
|
+
| Class | Protocol | Models |
|
|
530
|
+
|-------|----------|--------|
|
|
531
|
+
| `MarantzReceiver` | 2015 IP/RS-232 (`PREFIX+VALUE\r`) | NR1506, NR1606, SR5010, SR6010, SR7010, AV7702mkII |
|
|
532
|
+
| `MarantzLegacyReceiver` | 2007 RS-232 (`@CMD:VALUE\r`) | SR7002, SR8002, SR6003, SR7003, SR8003, SR5004, SR6004, AV7005, AV8003 |
|
|
533
|
+
|
|
534
|
+
The 2015 protocol is documented in `docs/Marantz 2015 NR_SR_AV IP-232 Protocol.xls`. The legacy protocol is documented in `docs/Marantz 2007 SR7002 SR8002 RS232C Control Specification v1.00.pdf`. Other Marantz receivers from the same era using the same command set should also work, possibly with a few unsupported commands.
|
|
535
|
+
|
|
536
|
+
## Development
|
|
537
|
+
|
|
538
|
+
```bash
|
|
539
|
+
# Install dev dependencies
|
|
540
|
+
uv sync
|
|
541
|
+
|
|
542
|
+
# Run tests
|
|
543
|
+
uv run pytest
|
|
544
|
+
|
|
545
|
+
# Run tests with verbose output
|
|
546
|
+
uv run pytest -v
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
## License
|
|
550
|
+
|
|
551
|
+
MIT
|