samsung-exlink 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.
- samsung_exlink-1.0.0/PKG-INFO +222 -0
- samsung_exlink-1.0.0/README.md +200 -0
- samsung_exlink-1.0.0/pyproject.toml +54 -0
- samsung_exlink-1.0.0/src/samsung_exlink/__init__.py +79 -0
- samsung_exlink-1.0.0/src/samsung_exlink/__main__.py +237 -0
- samsung_exlink-1.0.0/src/samsung_exlink/const.py +190 -0
- samsung_exlink-1.0.0/src/samsung_exlink/models.py +63 -0
- samsung_exlink-1.0.0/src/samsung_exlink/protocol.py +133 -0
- samsung_exlink-1.0.0/src/samsung_exlink/py.typed +0 -0
- samsung_exlink-1.0.0/src/samsung_exlink/state.py +33 -0
- samsung_exlink-1.0.0/src/samsung_exlink/tv.py +573 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: samsung-exlink
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Async library to control Samsung consumer TVs 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/samsung-exlink
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# samsung-exlink
|
|
24
|
+
|
|
25
|
+
Async Python library to control Samsung consumer TVs (Frame, Q-series, 8000-series, etc.) over their RS232 serial connection -- which Samsung markets as **ExLink** (also written EX-Link / EXT Link) -- built on [serialx](https://github.com/puddly/serialx).
|
|
26
|
+
|
|
27
|
+
"ExLink" and "RS232" refer to the same thing here: the TV's serial control port. This library speaks that protocol; it does not use the network/WiFi APIs.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install samsung-exlink
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Requires Python 3.12+.
|
|
36
|
+
|
|
37
|
+
## Hardware
|
|
38
|
+
|
|
39
|
+
Samsung consumer TVs expose RS232 either via:
|
|
40
|
+
|
|
41
|
+
- A native 3.5 mm "ExLink" jack (Q70 and above) -- plug in and go.
|
|
42
|
+
- 3.5 mm pin-out: tip = TV TX (RX into your controller), ring = TV RX, sleeve = GND.
|
|
43
|
+
- A USB port via the proprietary Samsung USB-to-ExLink dongle (8000 / Q60).
|
|
44
|
+
- The TV's service menu (Mute -> 1 -> 8 -> 2 -> Power on the IR remote) must
|
|
45
|
+
have **EXT Link Support** and **USB Serial** enabled.
|
|
46
|
+
|
|
47
|
+
The serial link is 9600 8N1.
|
|
48
|
+
|
|
49
|
+
## Quick start
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import asyncio
|
|
53
|
+
from samsung_exlink import SamsungTV, InputSource, Key, PictureMode
|
|
54
|
+
|
|
55
|
+
async def main():
|
|
56
|
+
tv = SamsungTV("/dev/ttyUSB0")
|
|
57
|
+
await tv.connect()
|
|
58
|
+
try:
|
|
59
|
+
await tv.power_on()
|
|
60
|
+
await tv.set_volume(25)
|
|
61
|
+
await tv.select_input_source(InputSource.HDMI1)
|
|
62
|
+
await tv.set_picture_mode(PictureMode.MOVIE)
|
|
63
|
+
await tv.send_key(Key.KEY_MENU)
|
|
64
|
+
finally:
|
|
65
|
+
await tv.disconnect()
|
|
66
|
+
|
|
67
|
+
asyncio.run(main())
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The constructor also accepts an `esphome://host/?port_name=NAME` URL for use
|
|
71
|
+
with an ESPHome UART proxy.
|
|
72
|
+
|
|
73
|
+
## CLI
|
|
74
|
+
|
|
75
|
+
A built-in CLI lets you quickly test your connection:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
python -m samsung_exlink /dev/ttyUSB0 power-on
|
|
79
|
+
python -m samsung_exlink /dev/ttyUSB0 power-off
|
|
80
|
+
python -m samsung_exlink /dev/ttyUSB0 volume 25
|
|
81
|
+
python -m samsung_exlink /dev/ttyUSB0 volume-up
|
|
82
|
+
python -m samsung_exlink /dev/ttyUSB0 volume-down
|
|
83
|
+
python -m samsung_exlink /dev/ttyUSB0 mute
|
|
84
|
+
python -m samsung_exlink /dev/ttyUSB0 channel-up
|
|
85
|
+
python -m samsung_exlink /dev/ttyUSB0 channel-down
|
|
86
|
+
python -m samsung_exlink /dev/ttyUSB0 source HDMI1
|
|
87
|
+
python -m samsung_exlink /dev/ttyUSB0 key KEY_MENU
|
|
88
|
+
python -m samsung_exlink /dev/ttyUSB0 raw 0d 00 00 1a # MENU via raw command
|
|
89
|
+
python -m samsung_exlink /dev/ttyUSB0 status # query power/volume/mute/source/channel
|
|
90
|
+
python -m samsung_exlink /dev/ttyUSB0 listen # passively log frames
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The `status` command queries the TV directly. To translate the raw source
|
|
94
|
+
byte into a named input, pass a known generation with `--model` (one of
|
|
95
|
+
`frame_2022`, `h_series_2016`):
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
python -m samsung_exlink --model frame_2022 /dev/ttyUSB0 status
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Protocol
|
|
102
|
+
|
|
103
|
+
Samsung consumer TVs use a fixed 7-byte binary frame:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
08 22 [cmd1] [cmd2] [cmd3] [value] [checksum]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- `08 22` is the fixed header.
|
|
110
|
+
- `cmd1`, `cmd2`, `cmd3`, `value` form the command (see the protocol PDF
|
|
111
|
+
bundled with this repo).
|
|
112
|
+
- `checksum = (256 - sum(bytes 0..5)) & 0xFF`.
|
|
113
|
+
|
|
114
|
+
The TV replies with a 3-byte response: `03 0C F1` for ACK, `03 0C FF` for
|
|
115
|
+
NACK. Most commands are fire-and-forget, so the library also tracks state
|
|
116
|
+
from what it sends. A subset of state (power, volume, mute, channel, source)
|
|
117
|
+
can additionally be read back with a `cmd1=0xF0` status query -- the TV
|
|
118
|
+
answers with `03 0C F5` plus a 10-byte payload. See
|
|
119
|
+
[State tracking](#state-tracking) below.
|
|
120
|
+
|
|
121
|
+
### Examples
|
|
122
|
+
|
|
123
|
+
| Operation | Frame |
|
|
124
|
+
|------------------|-------------------------------------|
|
|
125
|
+
| Power off | `08 22 00 00 00 01 D5` |
|
|
126
|
+
| Power on | `08 22 00 00 00 02 D4` |
|
|
127
|
+
| Volume = 25 | `08 22 01 00 00 19 BC` |
|
|
128
|
+
| Mute toggle | `08 22 02 00 00 00 D4` |
|
|
129
|
+
| Source: HDMI1 | `08 22 0A 00 05 00 C7` |
|
|
130
|
+
| Source: TV | `08 22 0A 00 00 00 CC` |
|
|
131
|
+
| Key: MENU | `08 22 0D 00 00 1A AF` |
|
|
132
|
+
|
|
133
|
+
## Features
|
|
134
|
+
|
|
135
|
+
### State tracking
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
state = tv.state
|
|
139
|
+
state.power # bool | None (None = unknown)
|
|
140
|
+
state.volume # int | None (0-100)
|
|
141
|
+
state.mute # bool | None
|
|
142
|
+
state.input_source # InputSource | None
|
|
143
|
+
state.picture_mode # PictureMode | None
|
|
144
|
+
state.sound_mode # SoundMode | None
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Fields are populated as commands succeed, and also refreshed by the `query_*`
|
|
148
|
+
methods below. Power state is unknown after connect; call `power_on()` /
|
|
149
|
+
`power_off()`, or `query_power()` to read it from the TV.
|
|
150
|
+
|
|
151
|
+
### Status queries
|
|
152
|
+
|
|
153
|
+
Some TVs answer a small set of status queries directly:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
await tv.query_power() # PowerState.ON / STANDBY / OFF
|
|
157
|
+
await tv.query_volume() # 0-100
|
|
158
|
+
await tv.query_mute() # bool
|
|
159
|
+
await tv.query_channel() # (major, mid, minor)
|
|
160
|
+
await tv.query_source() # raw source byte (generation-specific)
|
|
161
|
+
await tv.query_source_input() # InputSource | None (needs a source map)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Each query updates `tv.state` and returns the value. The byte returned by
|
|
165
|
+
`query_source()` differs by TV generation, so mapping it back to an
|
|
166
|
+
`InputSource` needs a source map. Supply a known one via the constructor:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from samsung_exlink import SamsungTV, MODELS
|
|
170
|
+
|
|
171
|
+
tv = SamsungTV("/dev/ttyUSB0", model=MODELS["frame_2022"])
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
or discover it for your TV once with `await tv.probe_sources()` (which
|
|
175
|
+
cycles every input and records the byte each reports) and pass the returned
|
|
176
|
+
mapping back as `source_map=...` on future runs. Queries raise `TimeoutError`
|
|
177
|
+
on TVs/firmwares that don't support them.
|
|
178
|
+
|
|
179
|
+
### Event subscription
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
def on_state_change(state):
|
|
183
|
+
if state is None:
|
|
184
|
+
print("Disconnected")
|
|
185
|
+
return
|
|
186
|
+
print(f"Volume: {state.volume}, Source: {state.input_source}")
|
|
187
|
+
|
|
188
|
+
unsubscribe = tv.subscribe(on_state_change)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The callback is called with a `TVState` snapshot on each change, or `None`
|
|
192
|
+
when the connection is torn down.
|
|
193
|
+
|
|
194
|
+
### Raw access
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
response = await tv.send_raw(0x0d, 0x00, 0x00, 0x1A) # KEY_MENU
|
|
198
|
+
# response is the 3-byte reply (e.g. b"\x03\x0c\xf1")
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`send_raw` returns the reply without raising on NACK, for diagnostics.
|
|
202
|
+
|
|
203
|
+
## Available commands
|
|
204
|
+
|
|
205
|
+
First-class helpers cover power (`power_on`/`power_off`/`power_toggle`),
|
|
206
|
+
volume (`set_volume`/`volume_up`/`volume_down`), `mute`, channels
|
|
207
|
+
(`channel_up`/`channel_down`), `select_input_source`, `set_picture_mode`,
|
|
208
|
+
`set_sound_mode`, and the Frame/Anynet+ toggles `set_art_mode`,
|
|
209
|
+
`set_ambient_mode`, and `set_hdmi_cec`.
|
|
210
|
+
|
|
211
|
+
The `Key` enum mirrors the Samsung remote-control key map and includes
|
|
212
|
+
power, source, navigation, transport, color, and number keys. The
|
|
213
|
+
`InputSource` enum covers TV, AV1-3, S-Video1-3, Component1-3, PC1-3,
|
|
214
|
+
HDMI1-4, DVI1-3, and RVU.
|
|
215
|
+
|
|
216
|
+
For commands not yet wrapped (e.g. white-balance, color space, screen
|
|
217
|
+
adjustments), use `send_raw()` with the values from the protocol PDF
|
|
218
|
+
(`docs/Samsung-RS232-Control.pdf`).
|
|
219
|
+
|
|
220
|
+
## License
|
|
221
|
+
|
|
222
|
+
MIT
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# samsung-exlink
|
|
2
|
+
|
|
3
|
+
Async Python library to control Samsung consumer TVs (Frame, Q-series, 8000-series, etc.) over their RS232 serial connection -- which Samsung markets as **ExLink** (also written EX-Link / EXT Link) -- built on [serialx](https://github.com/puddly/serialx).
|
|
4
|
+
|
|
5
|
+
"ExLink" and "RS232" refer to the same thing here: the TV's serial control port. This library speaks that protocol; it does not use the network/WiFi APIs.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install samsung-exlink
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Python 3.12+.
|
|
14
|
+
|
|
15
|
+
## Hardware
|
|
16
|
+
|
|
17
|
+
Samsung consumer TVs expose RS232 either via:
|
|
18
|
+
|
|
19
|
+
- A native 3.5 mm "ExLink" jack (Q70 and above) -- plug in and go.
|
|
20
|
+
- 3.5 mm pin-out: tip = TV TX (RX into your controller), ring = TV RX, sleeve = GND.
|
|
21
|
+
- A USB port via the proprietary Samsung USB-to-ExLink dongle (8000 / Q60).
|
|
22
|
+
- The TV's service menu (Mute -> 1 -> 8 -> 2 -> Power on the IR remote) must
|
|
23
|
+
have **EXT Link Support** and **USB Serial** enabled.
|
|
24
|
+
|
|
25
|
+
The serial link is 9600 8N1.
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import asyncio
|
|
31
|
+
from samsung_exlink import SamsungTV, InputSource, Key, PictureMode
|
|
32
|
+
|
|
33
|
+
async def main():
|
|
34
|
+
tv = SamsungTV("/dev/ttyUSB0")
|
|
35
|
+
await tv.connect()
|
|
36
|
+
try:
|
|
37
|
+
await tv.power_on()
|
|
38
|
+
await tv.set_volume(25)
|
|
39
|
+
await tv.select_input_source(InputSource.HDMI1)
|
|
40
|
+
await tv.set_picture_mode(PictureMode.MOVIE)
|
|
41
|
+
await tv.send_key(Key.KEY_MENU)
|
|
42
|
+
finally:
|
|
43
|
+
await tv.disconnect()
|
|
44
|
+
|
|
45
|
+
asyncio.run(main())
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The constructor also accepts an `esphome://host/?port_name=NAME` URL for use
|
|
49
|
+
with an ESPHome UART proxy.
|
|
50
|
+
|
|
51
|
+
## CLI
|
|
52
|
+
|
|
53
|
+
A built-in CLI lets you quickly test your connection:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
python -m samsung_exlink /dev/ttyUSB0 power-on
|
|
57
|
+
python -m samsung_exlink /dev/ttyUSB0 power-off
|
|
58
|
+
python -m samsung_exlink /dev/ttyUSB0 volume 25
|
|
59
|
+
python -m samsung_exlink /dev/ttyUSB0 volume-up
|
|
60
|
+
python -m samsung_exlink /dev/ttyUSB0 volume-down
|
|
61
|
+
python -m samsung_exlink /dev/ttyUSB0 mute
|
|
62
|
+
python -m samsung_exlink /dev/ttyUSB0 channel-up
|
|
63
|
+
python -m samsung_exlink /dev/ttyUSB0 channel-down
|
|
64
|
+
python -m samsung_exlink /dev/ttyUSB0 source HDMI1
|
|
65
|
+
python -m samsung_exlink /dev/ttyUSB0 key KEY_MENU
|
|
66
|
+
python -m samsung_exlink /dev/ttyUSB0 raw 0d 00 00 1a # MENU via raw command
|
|
67
|
+
python -m samsung_exlink /dev/ttyUSB0 status # query power/volume/mute/source/channel
|
|
68
|
+
python -m samsung_exlink /dev/ttyUSB0 listen # passively log frames
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The `status` command queries the TV directly. To translate the raw source
|
|
72
|
+
byte into a named input, pass a known generation with `--model` (one of
|
|
73
|
+
`frame_2022`, `h_series_2016`):
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
python -m samsung_exlink --model frame_2022 /dev/ttyUSB0 status
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Protocol
|
|
80
|
+
|
|
81
|
+
Samsung consumer TVs use a fixed 7-byte binary frame:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
08 22 [cmd1] [cmd2] [cmd3] [value] [checksum]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- `08 22` is the fixed header.
|
|
88
|
+
- `cmd1`, `cmd2`, `cmd3`, `value` form the command (see the protocol PDF
|
|
89
|
+
bundled with this repo).
|
|
90
|
+
- `checksum = (256 - sum(bytes 0..5)) & 0xFF`.
|
|
91
|
+
|
|
92
|
+
The TV replies with a 3-byte response: `03 0C F1` for ACK, `03 0C FF` for
|
|
93
|
+
NACK. Most commands are fire-and-forget, so the library also tracks state
|
|
94
|
+
from what it sends. A subset of state (power, volume, mute, channel, source)
|
|
95
|
+
can additionally be read back with a `cmd1=0xF0` status query -- the TV
|
|
96
|
+
answers with `03 0C F5` plus a 10-byte payload. See
|
|
97
|
+
[State tracking](#state-tracking) below.
|
|
98
|
+
|
|
99
|
+
### Examples
|
|
100
|
+
|
|
101
|
+
| Operation | Frame |
|
|
102
|
+
|------------------|-------------------------------------|
|
|
103
|
+
| Power off | `08 22 00 00 00 01 D5` |
|
|
104
|
+
| Power on | `08 22 00 00 00 02 D4` |
|
|
105
|
+
| Volume = 25 | `08 22 01 00 00 19 BC` |
|
|
106
|
+
| Mute toggle | `08 22 02 00 00 00 D4` |
|
|
107
|
+
| Source: HDMI1 | `08 22 0A 00 05 00 C7` |
|
|
108
|
+
| Source: TV | `08 22 0A 00 00 00 CC` |
|
|
109
|
+
| Key: MENU | `08 22 0D 00 00 1A AF` |
|
|
110
|
+
|
|
111
|
+
## Features
|
|
112
|
+
|
|
113
|
+
### State tracking
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
state = tv.state
|
|
117
|
+
state.power # bool | None (None = unknown)
|
|
118
|
+
state.volume # int | None (0-100)
|
|
119
|
+
state.mute # bool | None
|
|
120
|
+
state.input_source # InputSource | None
|
|
121
|
+
state.picture_mode # PictureMode | None
|
|
122
|
+
state.sound_mode # SoundMode | None
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Fields are populated as commands succeed, and also refreshed by the `query_*`
|
|
126
|
+
methods below. Power state is unknown after connect; call `power_on()` /
|
|
127
|
+
`power_off()`, or `query_power()` to read it from the TV.
|
|
128
|
+
|
|
129
|
+
### Status queries
|
|
130
|
+
|
|
131
|
+
Some TVs answer a small set of status queries directly:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
await tv.query_power() # PowerState.ON / STANDBY / OFF
|
|
135
|
+
await tv.query_volume() # 0-100
|
|
136
|
+
await tv.query_mute() # bool
|
|
137
|
+
await tv.query_channel() # (major, mid, minor)
|
|
138
|
+
await tv.query_source() # raw source byte (generation-specific)
|
|
139
|
+
await tv.query_source_input() # InputSource | None (needs a source map)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Each query updates `tv.state` and returns the value. The byte returned by
|
|
143
|
+
`query_source()` differs by TV generation, so mapping it back to an
|
|
144
|
+
`InputSource` needs a source map. Supply a known one via the constructor:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from samsung_exlink import SamsungTV, MODELS
|
|
148
|
+
|
|
149
|
+
tv = SamsungTV("/dev/ttyUSB0", model=MODELS["frame_2022"])
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
or discover it for your TV once with `await tv.probe_sources()` (which
|
|
153
|
+
cycles every input and records the byte each reports) and pass the returned
|
|
154
|
+
mapping back as `source_map=...` on future runs. Queries raise `TimeoutError`
|
|
155
|
+
on TVs/firmwares that don't support them.
|
|
156
|
+
|
|
157
|
+
### Event subscription
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
def on_state_change(state):
|
|
161
|
+
if state is None:
|
|
162
|
+
print("Disconnected")
|
|
163
|
+
return
|
|
164
|
+
print(f"Volume: {state.volume}, Source: {state.input_source}")
|
|
165
|
+
|
|
166
|
+
unsubscribe = tv.subscribe(on_state_change)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The callback is called with a `TVState` snapshot on each change, or `None`
|
|
170
|
+
when the connection is torn down.
|
|
171
|
+
|
|
172
|
+
### Raw access
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
response = await tv.send_raw(0x0d, 0x00, 0x00, 0x1A) # KEY_MENU
|
|
176
|
+
# response is the 3-byte reply (e.g. b"\x03\x0c\xf1")
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`send_raw` returns the reply without raising on NACK, for diagnostics.
|
|
180
|
+
|
|
181
|
+
## Available commands
|
|
182
|
+
|
|
183
|
+
First-class helpers cover power (`power_on`/`power_off`/`power_toggle`),
|
|
184
|
+
volume (`set_volume`/`volume_up`/`volume_down`), `mute`, channels
|
|
185
|
+
(`channel_up`/`channel_down`), `select_input_source`, `set_picture_mode`,
|
|
186
|
+
`set_sound_mode`, and the Frame/Anynet+ toggles `set_art_mode`,
|
|
187
|
+
`set_ambient_mode`, and `set_hdmi_cec`.
|
|
188
|
+
|
|
189
|
+
The `Key` enum mirrors the Samsung remote-control key map and includes
|
|
190
|
+
power, source, navigation, transport, color, and number keys. The
|
|
191
|
+
`InputSource` enum covers TV, AV1-3, S-Video1-3, Component1-3, PC1-3,
|
|
192
|
+
HDMI1-4, DVI1-3, and RVU.
|
|
193
|
+
|
|
194
|
+
For commands not yet wrapped (e.g. white-balance, color space, screen
|
|
195
|
+
adjustments), use `send_raw()` with the values from the protocol PDF
|
|
196
|
+
(`docs/Samsung-RS232-Control.pdf`).
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
MIT
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "samsung-exlink"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Async library to control Samsung consumer TVs over RS232"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Paulus Schoutsen", email = "balloob@gmail.com" }
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"serialx[esphome]>=1.2.0",
|
|
13
|
+
]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Programming Language :: Python :: 3.14",
|
|
22
|
+
"Topic :: Home Automation",
|
|
23
|
+
"Topic :: System :: Hardware",
|
|
24
|
+
"Framework :: AsyncIO",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
samsung-exlink = "samsung_exlink.__main__:main"
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Repository = "https://github.com/home-assistant-libs/samsung-exlink"
|
|
32
|
+
|
|
33
|
+
[build-system]
|
|
34
|
+
requires = ["uv_build>=0.8.4,<0.9.0"]
|
|
35
|
+
build-backend = "uv_build"
|
|
36
|
+
|
|
37
|
+
[dependency-groups]
|
|
38
|
+
dev = [
|
|
39
|
+
"pytest>=9.0",
|
|
40
|
+
"pytest-asyncio>=1.0",
|
|
41
|
+
"pytest-timeout>=2.4.0",
|
|
42
|
+
"ruff>=0.10",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[tool.pytest.ini_options]
|
|
46
|
+
asyncio_mode = "auto"
|
|
47
|
+
timeout = 1
|
|
48
|
+
|
|
49
|
+
[tool.ruff]
|
|
50
|
+
target-version = "py312"
|
|
51
|
+
src = ["src", "tests", "scripts"]
|
|
52
|
+
|
|
53
|
+
[tool.ruff.lint]
|
|
54
|
+
select = ["E", "F", "I", "UP", "B"]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Async library to control Samsung consumer TVs over RS232 using serialx."""
|
|
2
|
+
|
|
3
|
+
from .const import (
|
|
4
|
+
ACK_RESPONSE,
|
|
5
|
+
BAUD_RATE,
|
|
6
|
+
COMMAND_TIMEOUT,
|
|
7
|
+
FRAME_LENGTH,
|
|
8
|
+
HEADER,
|
|
9
|
+
NACK_RESPONSE,
|
|
10
|
+
QUERY_DATA_FOLLOWS,
|
|
11
|
+
QUERY_PAYLOAD_LENGTH,
|
|
12
|
+
QUERY_RESPONSE_HEADER,
|
|
13
|
+
RESPONSE_LENGTH,
|
|
14
|
+
ColorTone,
|
|
15
|
+
InputSource,
|
|
16
|
+
Key,
|
|
17
|
+
PictureMode,
|
|
18
|
+
PictureSize,
|
|
19
|
+
PowerCommand,
|
|
20
|
+
PowerState,
|
|
21
|
+
QueryCategory,
|
|
22
|
+
SoundMode,
|
|
23
|
+
)
|
|
24
|
+
from .models import ALL_MODELS, MODELS, TVModel
|
|
25
|
+
from .protocol import (
|
|
26
|
+
PendingResponse,
|
|
27
|
+
QueryResponse,
|
|
28
|
+
build_frame,
|
|
29
|
+
calculate_checksum,
|
|
30
|
+
is_ack,
|
|
31
|
+
is_nack,
|
|
32
|
+
parse_frame,
|
|
33
|
+
parse_query_payload,
|
|
34
|
+
)
|
|
35
|
+
from .state import TVState
|
|
36
|
+
from .tv import (
|
|
37
|
+
CommandRejected,
|
|
38
|
+
SamsungTV,
|
|
39
|
+
SamsungTVError,
|
|
40
|
+
StateCallback,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"ACK_RESPONSE",
|
|
45
|
+
"ALL_MODELS",
|
|
46
|
+
"BAUD_RATE",
|
|
47
|
+
"COMMAND_TIMEOUT",
|
|
48
|
+
"ColorTone",
|
|
49
|
+
"CommandRejected",
|
|
50
|
+
"FRAME_LENGTH",
|
|
51
|
+
"HEADER",
|
|
52
|
+
"InputSource",
|
|
53
|
+
"Key",
|
|
54
|
+
"MODELS",
|
|
55
|
+
"NACK_RESPONSE",
|
|
56
|
+
"PendingResponse",
|
|
57
|
+
"PictureMode",
|
|
58
|
+
"PictureSize",
|
|
59
|
+
"PowerCommand",
|
|
60
|
+
"PowerState",
|
|
61
|
+
"QUERY_DATA_FOLLOWS",
|
|
62
|
+
"QUERY_PAYLOAD_LENGTH",
|
|
63
|
+
"QUERY_RESPONSE_HEADER",
|
|
64
|
+
"QueryCategory",
|
|
65
|
+
"QueryResponse",
|
|
66
|
+
"RESPONSE_LENGTH",
|
|
67
|
+
"SamsungTV",
|
|
68
|
+
"SamsungTVError",
|
|
69
|
+
"SoundMode",
|
|
70
|
+
"StateCallback",
|
|
71
|
+
"TVModel",
|
|
72
|
+
"TVState",
|
|
73
|
+
"build_frame",
|
|
74
|
+
"calculate_checksum",
|
|
75
|
+
"is_ack",
|
|
76
|
+
"is_nack",
|
|
77
|
+
"parse_frame",
|
|
78
|
+
"parse_query_payload",
|
|
79
|
+
]
|