discstation 0.1.23 → 0.1.26
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.
- package/README.md +43 -8
- package/arduino/c6/DiscStation_C6.ino +293 -41
- package/arduino/c6/secrets.h.example +14 -0
- package/arduino/v1/DiscStation.ino +214 -19
- package/arduino/v1/secrets.h.example +14 -0
- package/discstation.env.example +7 -0
- package/package.json +1 -1
- package/requirements.txt +1 -0
- package/src/discstation.py +150 -15
- package/src/discstation_burn.py +9 -0
- package/src/discstation_host.py +73 -0
package/src/discstation_burn.py
CHANGED
|
@@ -192,6 +192,9 @@ BAUD = 115200
|
|
|
192
192
|
|
|
193
193
|
def check_cancel(ser):
|
|
194
194
|
try:
|
|
195
|
+
if line_reader is not None:
|
|
196
|
+
line = line_reader(ser, 0)
|
|
197
|
+
return bool(line) and line in ("CANCEL", "PLAY_STOP")
|
|
195
198
|
if ser and ser.in_waiting:
|
|
196
199
|
line = ser.readline().decode(errors="ignore").strip()
|
|
197
200
|
note_serial_activity()
|
|
@@ -517,6 +520,12 @@ def detect_disc_type(device):
|
|
|
517
520
|
# pipeline emits also updates the web/SSE status in real time.
|
|
518
521
|
status_sink = None
|
|
519
522
|
|
|
523
|
+
# discstation.py sets this to its buffered read_serial_line so check_cancel
|
|
524
|
+
# reads the same way station_loop does (partial lines get buffered and
|
|
525
|
+
# reassembled). A bare ser.readline() here can slice a "CANCEL\n" in half
|
|
526
|
+
# on a mid-transmission read and then never match it.
|
|
527
|
+
line_reader = None
|
|
528
|
+
|
|
520
529
|
|
|
521
530
|
def send(ser, msg):
|
|
522
531
|
global _serial_write_failed
|
package/src/discstation_host.py
CHANGED
|
@@ -8,9 +8,11 @@ import os
|
|
|
8
8
|
import platform
|
|
9
9
|
import re
|
|
10
10
|
import shutil
|
|
11
|
+
import socket
|
|
11
12
|
import subprocess
|
|
12
13
|
import sys
|
|
13
14
|
import threading
|
|
15
|
+
import time
|
|
14
16
|
from pathlib import Path
|
|
15
17
|
|
|
16
18
|
from serial.tools import list_ports
|
|
@@ -67,6 +69,77 @@ def serial_port():
|
|
|
67
69
|
return _stable_serial_path(sorted(preferred)[0])
|
|
68
70
|
|
|
69
71
|
|
|
72
|
+
_REMOTE_MDNS_TYPE = "_discstation._tcp.local."
|
|
73
|
+
_REMOTE_DEFAULT_HOST = "discstation.local"
|
|
74
|
+
_remote_cache = {"at": 0.0, "value": None}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def remote_host():
|
|
78
|
+
"""Address of the Wi-Fi appliance remote, or None.
|
|
79
|
+
|
|
80
|
+
`DISC_REMOTE_HOST` unset / `auto` (default) -> mDNS browse for the
|
|
81
|
+
firmware's `_discstation._tcp` advert, falling back to resolving
|
|
82
|
+
`discstation.local` via the OS resolver. Result cached ~60s so the
|
|
83
|
+
reconnect loop / hot-swap poll don't hammer mDNS.
|
|
84
|
+
`DISC_REMOTE_HOST=<ip|host>` -> that address, verbatim, instantly.
|
|
85
|
+
`DISC_REMOTE_HOST=off` / `none` / `0` -> None (never look; USB / web only).
|
|
86
|
+
|
|
87
|
+
Only consulted by main() when no USB serial is present. Best-effort -
|
|
88
|
+
main() tries to connect and falls back on failure."""
|
|
89
|
+
setting = (os.environ.get("DISC_REMOTE_HOST") or "").strip().lower()
|
|
90
|
+
if setting in ("off", "none", "no", "false", "0", "disabled"):
|
|
91
|
+
return None
|
|
92
|
+
if setting and setting != "auto":
|
|
93
|
+
return (os.environ.get("DISC_REMOTE_HOST") or "").strip()
|
|
94
|
+
|
|
95
|
+
if time.time() - _remote_cache["at"] < 60:
|
|
96
|
+
return _remote_cache["value"]
|
|
97
|
+
|
|
98
|
+
value = _discover_remote()
|
|
99
|
+
_remote_cache["at"] = time.time()
|
|
100
|
+
_remote_cache["value"] = value
|
|
101
|
+
return value
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _discover_remote():
|
|
105
|
+
try:
|
|
106
|
+
from zeroconf import Zeroconf, ServiceBrowser
|
|
107
|
+
except ImportError:
|
|
108
|
+
Zeroconf = None
|
|
109
|
+
if Zeroconf is not None:
|
|
110
|
+
found = {}
|
|
111
|
+
|
|
112
|
+
class _Listener:
|
|
113
|
+
def add_service(self, zc, type_, name):
|
|
114
|
+
info = zc.get_service_info(type_, name, timeout=1500)
|
|
115
|
+
if info:
|
|
116
|
+
for addr in info.parsed_addresses():
|
|
117
|
+
if "." in addr: # IPv4
|
|
118
|
+
found["addr"] = addr
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
update_service = add_service
|
|
122
|
+
|
|
123
|
+
def remove_service(self, *a):
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
zc = Zeroconf()
|
|
127
|
+
try:
|
|
128
|
+
ServiceBrowser(zc, _REMOTE_MDNS_TYPE, _Listener())
|
|
129
|
+
deadline = time.time() + 2.5
|
|
130
|
+
while time.time() < deadline and "addr" not in found:
|
|
131
|
+
time.sleep(0.1)
|
|
132
|
+
finally:
|
|
133
|
+
zc.close()
|
|
134
|
+
if found.get("addr"):
|
|
135
|
+
return found["addr"]
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
return socket.gethostbyname(_REMOTE_DEFAULT_HOST)
|
|
139
|
+
except OSError:
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
|
|
70
143
|
def _stable_serial_path(device):
|
|
71
144
|
"""Map a volatile /dev/ttyUSBN to its stable /dev/serial/by-id/ symlink so a
|
|
72
145
|
USB re-enumeration (ttyUSB1 -> ttyUSB0) doesn't strand the reconnect loop."""
|