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.
@@ -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
@@ -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."""