tesmartctl 0.0.7.2__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 WBEVAN
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,399 @@
1
+ Metadata-Version: 2.4
2
+ Name: tesmartctl
3
+ Version: 0.0.7.2
4
+ Summary: Command-line control for TESmart 8x1 / 16x1 HDMI switches over LAN or RS232
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Provides-Extra: serial
10
+ Requires-Dist: pyserial>=3.5; extra == "serial"
11
+ Dynamic: license-file
12
+
13
+ # tesmartctl
14
+
15
+ Command-line, HTTP, and browser control for TESmart 8×1 and 16×1 HDMI switches (HSW0801, HKS0801, HSW1601, HKS1601 and the same protocol family). It talks to the switch over its LAN port or the 3-pin RS232 port.
16
+
17
+ This is an independent project. It is not published by, or affiliated with, TESmart.
18
+
19
+ LAN control uses only the Python standard library. RS232 needs `pyserial`. Python 3.10 or newer. Licensed under the [MIT License](LICENSE).
20
+
21
+ ## What it does
22
+
23
+ | | |
24
+ | --- | --- |
25
+ | Read | Active input, and the IP, port, gateway, and mask stored in the switch |
26
+ | Switch | Select an input by number or by a local name, or step next and previous through a list you choose |
27
+ | Peek | Show another input for a few seconds, then return. Not a cycle |
28
+ | Cycle | Walk a chosen list by hand (`next` / `previous`) or on a timer (`cycle run`) |
29
+ | Settings | Buzzer, front-panel display timeout, auto input detection. The switch cannot report these back |
30
+ | Address | Change the switch's IP, port, gateway, and mask, with two warnings and a read-back. Applied only after a power cycle |
31
+ | Listen | Keep one connection open: a line protocol on stdin or TCP, or HTTP URLs for Stream Deck, Companion, Home Assistant, and similar |
32
+ | Panel | Optional browser page at `/panel` (`listen --http PORT --panel`), including an API explorer |
33
+
34
+ The switch cannot say which HDMI inputs have a signal. A cycle is the list you configure, stored on this computer, not in the switch. Local input names are stored the same way.
35
+
36
+ | Document | What it covers |
37
+ | --- | --- |
38
+ | This file | Install, configuration, every command |
39
+ | [`Docs/protocol.md`](Docs/protocol.md) | Byte protocol the switch speaks |
40
+ | [`Docs/listener.md`](Docs/listener.md) | Long-running line protocol and HTTP URLs |
41
+ | [`Docs/panel.md`](Docs/panel.md) | Browser control panel at `/panel` |
42
+ | [`Docs/embed.md`](Docs/embed.md) | Input buttons on a page you already have |
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ git clone https://github.com/WBEVAN/TESmartController.git
48
+ cd TESmartController
49
+ pip install . # adds the tesmartctl command
50
+ pip install '.[serial]' # also installs pyserial, for the RS232 port
51
+ pip install -e . # editable: edits in this tree take effect immediately
52
+ ```
53
+
54
+ Without installing, run from the repository directory:
55
+
56
+ ```bash
57
+ python3 -m tesmart status
58
+ ```
59
+
60
+ If the shell cannot find `tesmartctl` after install, open a new shell, or rehash your version manager. Requires Python 3.10 or newer.
61
+
62
+ ## Help and examples
63
+
64
+ Every command prints worked examples at the end of its `--help`:
65
+
66
+ ```bash
67
+ tesmartctl --help # command list plus the most common invocations
68
+ tesmartctl input --help # flags and examples for one command
69
+ tesmartctl net set --help
70
+ tesmartctl examples # every example for every command, in one listing
71
+ ```
72
+
73
+ The same examples appear in this README. They are generated from one table (`tesmart/examples.py`), so the terminal and the docs agree.
74
+
75
+ ## Quick start: change inputs
76
+
77
+ From the command line:
78
+
79
+ ```bash
80
+ tesmartctl input 3 # switch to HDMI 3
81
+ tesmartctl input Office # switch by a local name (see `tesmartctl name`)
82
+ tesmartctl next # next input in the saved cycle
83
+ tesmartctl peek 2 # look at HDMI 2 for 5 seconds, then come back
84
+ tesmartctl input # which input is active now
85
+ ```
86
+
87
+ From a URL, after starting the HTTP listener once (for example as a login item or a service):
88
+
89
+ ```bash
90
+ tesmartctl listen --http 0.0.0.0:8080 # add --panel for the browser control panel
91
+ ```
92
+
93
+ ```text
94
+ http://<host>:8080/input/3 switch to HDMI 3
95
+ http://<host>:8080/input/Office switch by local name
96
+ http://<host>:8080/next next input in the saved cycle
97
+ http://<host>:8080/previous previous input
98
+ http://<host>:8080/peek/2?seconds=8 look at HDMI 2 for 8 seconds, then come back
99
+ http://<host>:8080/input read the active input, {"active_input": 3}
100
+ http://<host>:8080/input?format=text same, as a bare number
101
+ ```
102
+
103
+ `<host>` is the machine running `tesmartctl`, not the switch. Use `127.0.0.1` with a port-only `--http 8080` when the controller runs on the same computer. Use `0.0.0.0:8080` to accept other devices on your network. The listener has no authentication, so only expose it to a network you trust.
104
+
105
+ ### Stream Deck, macro pads, and home control
106
+
107
+ Each input change is one URL or one command, so anything that can open a URL or run a shell command can drive the switch:
108
+
109
+ | Controller | How to wire one button |
110
+ | --- | --- |
111
+ | Elgato Stream Deck | **Website** action with `http://127.0.0.1:8080/input/3` and "GET request in background" ticked, or a **System > Open** action running `tesmartctl input 3`. |
112
+ | Bitfocus Companion | **Generic HTTP**, GET `http://<host>:8080/input/3`. |
113
+ | Home Assistant | A `rest_command` (`url: http://<host>:8080/input/{{ input }}`) called from a button, script, or automation. A `rest` sensor on `/input?format=text` shows the active input. |
114
+ | Homebridge / HomeKit | An HTTP switch or button plugin pointed at `/input/N`, one accessory per input. |
115
+ | Node-RED | An **http request** node, GET `/input/3`. |
116
+ | Apple Shortcuts | **Get Contents of URL** with `http://<host>:8080/input/3`. |
117
+ | Keyboard Maestro, Hammerspoon, cron | Run `tesmartctl input 3`, or `curl -s http://127.0.0.1:8080/input/3`. |
118
+
119
+ Home Assistant example (`configuration.yaml`):
120
+
121
+ ```yaml
122
+ rest_command:
123
+ tesmart_input:
124
+ url: "http://192.168.1.20:8080/input/{{ input }}"
125
+ tesmart_peek:
126
+ url: "http://192.168.1.20:8080/peek/{{ input }}?seconds={{ seconds | default(5) }}"
127
+
128
+ sensor:
129
+ - platform: rest
130
+ name: TESmart active input
131
+ resource: "http://192.168.1.20:8080/input?format=text"
132
+ scan_interval: 10
133
+ ```
134
+
135
+ Here `192.168.1.20` stands for the computer running the listener. Every response says which input the switch actually reports (`active_input`), so a controller can show the real state rather than assuming the switch obeyed. See [Listener](#listener) for every route.
136
+
137
+ ## Connection
138
+
139
+ The switch's factory address is `192.168.1.10`, TCP port `5000`. You can move it to another address on your network with `tesmartctl net set`. After that, save the new address as the CLI default so later commands do not need `--host`.
140
+
141
+ Each setting (`host`, `port`, `serial`, `inputs`) is resolved in this order:
142
+
143
+ 1. Command-line flag (`--host`, `--port`, `--serial`, `--inputs`)
144
+ 2. Env file `~/.config/tesmartctl/env` (override the path with `--env-file` or `TESMART_ENV_FILE`)
145
+ 3. Environment variable, only when it is set (`TESMART_HOST`, `TESMART_PORT`, `TESMART_SERIAL`, `TESMART_INPUTS`)
146
+ 4. Built-in default (`192.168.1.10`, `5000`, no serial device, `8` inputs)
147
+
148
+ ```bash
149
+ tesmartctl config show # effective values and where each came from
150
+ tesmartctl config set host 192.168.1.50 # also: port, serial, inputs
151
+ tesmartctl config unset host
152
+ tesmartctl config path
153
+ ```
154
+
155
+ Input names are local. The switch cannot store them. They are written to the same env file as `TESMART_NAME_1=Office` and appear in `status`, `input`, and `rotate`.
156
+
157
+ ```bash
158
+ tesmartctl name set 2 Office
159
+ tesmartctl name set 1 "Living room"
160
+ tesmartctl name list
161
+ tesmartctl name unset 1
162
+ tesmartctl input Office # select by the local name
163
+ ```
164
+
165
+ The env file is plain `KEY=VALUE` lines (`TESMART_HOST=192.168.1.50`) and can be edited by hand. `--serial /dev/tty.usbserial-xxx` uses RS232 at 9600 8N1 instead of LAN. `--inputs 16` is for a 16×1. `--timeout 1.5` is how long a reply is awaited. `--json` prints JSON from the one-shot commands.
166
+
167
+ Global flags go before the subcommand: `tesmartctl --host 192.168.1.50 status`.
168
+
169
+ ## What the switch will report
170
+
171
+ | Setting | Read | Write |
172
+ | --- | --- | --- |
173
+ | Active input (1–8, or 1–16) | yes | yes, and the switch reports the input it actually selected |
174
+ | LAN IP, TCP port, gateway, mask | yes | yes, applied only after a power cycle |
175
+ | Buzzer | no | yes |
176
+ | Front-panel LED timeout (never, 10s, 30s) | no | yes |
177
+ | Auto input detection | no | yes |
178
+
179
+ Buzzer, LED timeout, and auto-detect accept a set command and send nothing back. A successful command means the bytes were sent, not that the value was read back.
180
+
181
+ The switch drops or garbles commands sent faster than about once a second. `tesmartctl` waits that long between writes on one connection.
182
+
183
+ This is an 8-input, 1-output switch. The protocol can read the selected input and cannot read which inputs have a signal, so it cannot calculate which sources are connected. The front-panel LEDs show that locally. `rotate` therefore walks every input from 1 to 8, or an explicit list you pass with `--only`.
184
+
185
+ ## Commands
186
+
187
+ ### Read
188
+
189
+ ```bash
190
+ tesmartctl status # active input plus saved LAN settings
191
+ tesmartctl status --json
192
+ tesmartctl status --no-network # skip the four LAN queries
193
+ tesmartctl input # active input only
194
+ tesmartctl net show # IP, port, gateway, mask
195
+ tesmartctl monitor # print the input whenever it changes
196
+ ```
197
+
198
+ `status` looks like:
199
+
200
+ ```text
201
+ Endpoint: tcp://192.168.1.50:5000
202
+ Active input: 2 (Office) of 8
203
+ Input names (stored locally, not on the switch):
204
+ 1 Living room
205
+ 2 Office
206
+ Network (persisted; applied after power cycle):
207
+ IP: 192.168.1.50
208
+ Port: 5000
209
+ Gateway: 192.168.1.1
210
+ Mask: 255.255.255.0
211
+ Cycle: manual (next/previous); 1, 2, 4 (chosen inputs)
212
+ Peek: 5s on another input, then back (`peek N`)
213
+ ```
214
+
215
+ The cycle is stored on this machine. `manual` moves only when you ask for `next` or `previous`. `automatic` is the same list on a timer, and it starts only while `cycle run` is running. With no saved list the cycle is every input.
216
+
217
+ Peek is not a cycle. `peek 3` shows input 3 for a few seconds and then returns to the input that was active. The duration is stored on this machine.
218
+
219
+ `monitor` stays running and prints a new line when the front panel, the remote, auto-detect, or another client changes the input. Ctrl-C stops it.
220
+
221
+ ### Change the input
222
+
223
+ ```bash
224
+ tesmartctl input 3 # switch to HDMI 3 and read it back
225
+ tesmartctl input 3 --no-verify # send the command and do not read it back
226
+ tesmartctl cycle inputs 1,2,4 # save the inputs to walk
227
+ tesmartctl cycle mode manual # next and previous only
228
+ tesmartctl next # Office -> next chosen input
229
+ tesmartctl previous
230
+ tesmartctl cycle mode automatic --every 15 # then start the timer:
231
+ tesmartctl cycle run
232
+ tesmartctl --json input 3
233
+ ```
234
+
235
+ Inputs are 1-based. The command exits non-zero when the switch reports a different input than the one requested. `input 9` is rejected locally and is not sent.
236
+
237
+ ### Peek at another input
238
+
239
+ ```bash
240
+ tesmartctl peek 3 # show input 3 for 5 seconds, then come back
241
+ tesmartctl peek Office --seconds 10
242
+ tesmartctl peek 3 --seconds 8 --save # and make 8 seconds the default
243
+ ```
244
+
245
+ `peek` flips to the input, waits, and flips back to the one that was active. Ctrl-C comes back at once. If the input is changed by something else during the peek (front panel, remote, another client), `peek` leaves that choice alone, reports it, and exits non-zero. The default duration is `5` seconds, stored as `TESMART_PEEK_SECONDS` in the env file.
246
+
247
+ The listener can also make the *next* ordinary input change a peek, so a button that calls `/input/3` peeks instead of sticking. `GET /peek/once` arms a single change. `GET /peek/always` keeps doing it until `GET /peek/off`. Detail is in [`Docs/listener.md`](Docs/listener.md).
248
+
249
+ ### Write-only settings
250
+
251
+ ```bash
252
+ tesmartctl buzzer off # on | off
253
+ tesmartctl led 10 # never | 10 | 30 (seconds)
254
+ tesmartctl autodetect on # on | off
255
+ ```
256
+
257
+ Accepted words for on/off also include `1`/`0`, `true`/`false`, `enable`/`disable`, `unmute`/`mute`.
258
+
259
+ ### LAN address
260
+
261
+ ```bash
262
+ tesmartctl net set \
263
+ --ip 192.168.1.50 --gateway 192.168.1.1 --mask 255.255.255.0 --yes
264
+ tesmartctl net show # read the stored values back before rebooting
265
+ ```
266
+
267
+ `--tcp-port` sets the switch's own listen port (factory `5000`). Each value is stored immediately and used only after the switch is power-cycled. There is no factory reset for these settings.
268
+
269
+ **Risk notice.** A wrong IP address, port, gateway, or mask makes the switch unreachable over the network, and recovery is only possible with a serial (RS232) cable. This software sends the values exactly as entered; it cannot check that they are valid for your network, cannot undo the change, and cannot recover a switch that is no longer reachable. You change these settings at your own risk. This software and its authors accept no liability for any loss, damage, downtime, or cost that results from doing so.
270
+
271
+ `net set` warns twice: without `--yes` it prints the notice and refuses; with `--yes` it prints the notice again, writes, then reads every value back from the switch and reports whether each one is `stored` or a `MISMATCH`. The exit code is `0` only when every value reads back as requested. Until the switch is power-cycled it keeps answering on its current address, so a mismatch can still be corrected.
272
+
273
+ `--save-default` writes the new `--ip` and `--tcp-port` into the CLI env file after the read-back confirms them, so later commands follow the switch to the new address.
274
+
275
+ ### Raw bytes
276
+
277
+ ```bash
278
+ tesmartctl raw AA BB 03 10 00 EE
279
+ # Sent: AA BB 03 10 00 EE
280
+ # Received: AA BB 03 11 01 EE
281
+ # frame cmd=0x11 value=0x01 term=0xEE -> input 2
282
+ ```
283
+
284
+ `--listen 3` waits longer for the reply. See [`Docs/protocol.md`](Docs/protocol.md) for the frame layout. The input query value is 0-based in the reply (`0x01` is input 2).
285
+
286
+ ## Listener
287
+
288
+ `listen` keeps one connection open. Use it when something else (a script, `nc`, a browser, a home-automation "open URL" action) needs to change inputs without starting a new process each time. Full detail is in [`Docs/listener.md`](Docs/listener.md).
289
+
290
+ ```bash
291
+ tesmartctl listen # one command per line on stdin
292
+ printf 'get\nset 3\nquit\n' | tesmartctl listen
293
+
294
+ tesmartctl listen --bind 9753 # raw TCP on this computer only, 127.0.0.1:9753
295
+ tesmartctl listen --bind 0.0.0.0:9753 # raw TCP on every interface
296
+ echo set 4 | nc 127.0.0.1 9753 # prints the input the switch reports
297
+
298
+ tesmartctl listen --http 8080 # HTTP on this computer only, http://127.0.0.1:8080/
299
+ tesmartctl listen --http 0.0.0.0:8080 # HTTP on every interface; other devices use this computer's address
300
+ tesmartctl listen --http 0.0.0.0:8080 --panel
301
+ ```
302
+
303
+ `--bind` and `--http` cannot be combined. Run two processes if you want both. A port number alone binds `127.0.0.1`, so only this computer can connect. `0.0.0.0` accepts every network interface. Other devices then use the address of the computer running `tesmartctl`, for example `http://192.168.1.20:8080/input/3`. There is no authentication, so only do this on a network you trust.
304
+
305
+ `--panel` is off unless you add it. It serves a browser page for selecting inputs, stepping the cycle, peeking, and the switch settings the vendor Windows controller exposes (buzzer, front-panel display timeout, auto input detection, LAN address). Detail is in [`Docs/panel.md`](Docs/panel.md).
306
+
307
+ ![Control panel](Docs/images/panel.png)
308
+
309
+ The **API** button in the top bar opens an explorer that lists every `/api` route, sends it, and shows the raw status and body the listener returned.
310
+
311
+ ![API explorer](Docs/images/api-explorer.png)
312
+
313
+ Line commands: `get` (also `port`, `status`, `?`) prints the active input; `set 3`, `set Office`, or just `3` switches and prints the input the switch reports; `next` or `rotate 1,2,4` moves to the next input; `peek 3` or `peek 3 10` shows an input briefly and replies once back; `quit` closes the session. `--json` makes each reply one JSON object. The line command `status` is only the active input. The full readable status is the HTTP route and `tesmartctl status`.
314
+
315
+ A full session on stdin:
316
+
317
+ ```text
318
+ $ tesmartctl listen
319
+ Command listener on stdin for tcp://192.168.1.50:5000 (Ctrl-C or 'quit' to stop).
320
+ commands: get | set <n> | <n> | next | previous | rotate [1,2,4] | peek <n> [seconds] | quit
321
+ get
322
+ 2
323
+ set 3
324
+ 3
325
+ Office
326
+ 2
327
+ next
328
+ 3
329
+ quit
330
+ ```
331
+
332
+ HTTP routes, GET or POST:
333
+
334
+ ```text
335
+ http://127.0.0.1:8080/ list of routes
336
+ http://127.0.0.1:8080/input {"active_input": 2}
337
+ http://127.0.0.1:8080/status input plus saved IP, port, gateway, mask
338
+ http://127.0.0.1:8080/status?network=0 input only, skips the LAN queries
339
+ http://127.0.0.1:8080/status?format=text same fields as plain lines
340
+ http://127.0.0.1:8080/rotate next input; {"previous": 2, "active_input": 3}
341
+ http://127.0.0.1:8080/rotate?only=1,2,4 next input within that list
342
+ http://127.0.0.1:8080/input?rotate=1 same as /rotate
343
+ http://127.0.0.1:8080/peek/3 show 3 for the saved seconds, then back; replies once back
344
+ http://127.0.0.1:8080/peek?input=3&seconds=8 same with an explicit duration
345
+ http://127.0.0.1:8080/peek/once the next input change peeks, then later changes stick
346
+ http://127.0.0.1:8080/peek/always every plain input change peeks until /peek/off
347
+ http://127.0.0.1:8080/peek/off plain input changes stick again
348
+ http://127.0.0.1:8080/input?set=3 {"active_input": 3, "requested": 3}
349
+ http://127.0.0.1:8080/input/3 same as /input?set=3
350
+ http://127.0.0.1:8080/input/Office switch by a local name
351
+ http://127.0.0.1:8080/set?input=3 same as /input?set=3
352
+ http://127.0.0.1:8080/get same as /input
353
+ http://127.0.0.1:8080/input/3?format=text 3
354
+ ```
355
+
356
+ `active_input` is what the switch reports after the change. Compare it with `requested` when they might differ. `400` is a bad number or a switch rejection, `404` is an unknown route, `502` means the switch connection failed. `/status` takes a few seconds because each LAN query is paced.
357
+
358
+ ## Library
359
+
360
+ ```python
361
+ from tesmart import TcpTransport, TesmartSwitch, LedTimeout
362
+
363
+ with TesmartSwitch(TcpTransport("192.168.1.50")) as sw:
364
+ print(sw.get_active_input())
365
+ sw.set_input(4)
366
+ sw.set_buzzer(False)
367
+ sw.set_led_timeout(LedTimeout.SECONDS_30)
368
+ sw.set_auto_detect(True)
369
+ print(sw.read_status())
370
+ ```
371
+
372
+ `SerialTransport("/dev/tty.usbserial-xxx")` is the RS232 equivalent and imports `pyserial`.
373
+
374
+ ## Layout
375
+
376
+ | Path | Responsibility |
377
+ | --- | --- |
378
+ | `tesmart/protocol.py` | Hex and ASCII encode/decode, no I/O |
379
+ | `tesmart/transport.py` | TCP and serial transports |
380
+ | `tesmart/switch.py` | Device operations, receive buffer, command pacing |
381
+ | `tesmart/config.py` | Flag, env file, environment variable, default |
382
+ | `tesmart/peek.py` | Peek: flip to an input, wait, flip back |
383
+ | `tesmart/listener.py` | Stdin and raw-TCP command listener |
384
+ | `tesmart/web.py` | HTTP routes |
385
+ | `tesmart/panel.py` | `/panel` and its `/api` routes, only with `--panel` |
386
+ | `tesmart/static/` | Panel HTML, CSS, and JS |
387
+ | `tesmart/cli.py` | `tesmartctl` argument parsing and output |
388
+ | `tesmart/examples.py` | Worked examples shown by `--help` and `tesmartctl examples` |
389
+ | `Docs/protocol.md` | Wire protocol |
390
+ | `Docs/listener.md` | Line protocol and HTTP routes |
391
+ | `Docs/panel.md` | Browser control panel |
392
+ | `Docs/embed.md` | Input buttons on another page |
393
+ | `examples/buttons/` | Sample page of input links, no script or framework |
394
+ | `Docs/images/` | Screenshots used in the documentation |
395
+ | `LICENSE` | MIT License |
396
+
397
+ ## License
398
+
399
+ [MIT](LICENSE). This software is provided as is, without warranty. Changing the switch's network address can make it unreachable over the LAN; that risk, and the absence of any liability for it, is spelled out in [LAN address](#lan-address) and in the panel before a write is accepted.