From 9f3f22741d383ea09ea4c2b22494ba78d812fca4 Mon Sep 17 00:00:00 2001 From: Felix Auer Date: Thu, 26 Feb 2026 16:45:28 +0100 Subject: [PATCH 1/6] Add description for the host and port settings --- crosspoint_reader/config.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crosspoint_reader/config.py b/crosspoint_reader/config.py index 6e3e241..8a0408a 100644 --- a/crosspoint_reader/config.py +++ b/crosspoint_reader/config.py @@ -5,6 +5,7 @@ from qt.core import ( QDialogButtonBox, QFormLayout, QHBoxLayout, + QLabel, QLineEdit, QPlainTextEdit, QPushButton, @@ -47,6 +48,12 @@ class CrossPointConfigWidget(QWidget): layout.addRow('Host', self.host) layout.addRow('Port', self.port) + + notice = QLabel('Host and port settings are fallback values used only when the device is not auto-discoverable by UDP broadcast.') + notice.setWordWrap(True) + notice.setStyleSheet('color: gray; font-style: italic;') + layout.addRow('', notice) + layout.addRow('Upload path', self.path) layout.addRow('Chunk size', self.chunk_size) layout.addRow('', self.debug) From 046550045f3d03964ae2ce04927b2ec590330059 Mon Sep 17 00:00:00 2001 From: Justin Mitchell Date: Mon, 6 Apr 2026 00:49:58 -0400 Subject: [PATCH 2/6] Add subnet broadcast discovery for CrossPoint WS Implements automatic discovery of local broadcast addresses by querying network interfaces. The function filters out localhost addresses and calculates broadcast addresses for each valid network interface, enabling CrossPoint WebSocket discovery across multiple subnets. --- crosspoint_reader/ws_client.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crosspoint_reader/ws_client.py b/crosspoint_reader/ws_client.py index d87fa0b..c095ae6 100644 --- a/crosspoint_reader/ws_client.py +++ b/crosspoint_reader/ws_client.py @@ -189,6 +189,21 @@ def _broadcast_from_host(host): return '.'.join(parts) +def _local_broadcast_addrs(): + addrs = set() + try: + for info in socket.getaddrinfo(socket.gethostname(), None, socket.AF_INET): + ip = info[4][0] + if ip.startswith('127.'): + continue + bcast = _broadcast_from_host(ip) + if bcast: + addrs.add(bcast) + except Exception: + pass + return addrs + + def discover_device(timeout=2.0, debug=False, logger=None, extra_hosts=None): ports = [8134, 54982, 48123, 39001, 44044, 59678] local_port = 0 @@ -210,6 +225,10 @@ def discover_device(timeout=2.0, debug=False, logger=None, extra_hosts=None): pass targets = [] + for bcast in _local_broadcast_addrs(): + _log(logger, debug, f'[CrossPoint WS] discovery subnet broadcast {bcast}') + for port in ports: + targets.append((bcast, port)) for port in ports: targets.append(('255.255.255.255', port)) for host in extra_hosts or []: From 2340149b1a9bbb1748c0bd450da2c0c05cff624d Mon Sep 17 00:00:00 2001 From: Justin Mitchell Date: Mon, 6 Apr 2026 01:01:19 -0400 Subject: [PATCH 3/6] Fixes delete endpoint and ws discovery --- crosspoint_reader/driver.py | 27 ++++++++++++++++++++------- crosspoint_reader/ws_client.py | 1 + 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/crosspoint_reader/driver.py b/crosspoint_reader/driver.py index 053a391..a90ad90 100644 --- a/crosspoint_reader/driver.py +++ b/crosspoint_reader/driver.py @@ -136,7 +136,8 @@ class CrossPointDevice(DeviceConfig, DevicePlugin): def _http_post_form(self, path, data, timeout=5): url = self._http_base() + path body = urllib.parse.urlencode(data).encode('utf-8') - req = urllib.request.Request(url, data=body, method='POST') + req = urllib.request.Request(url, data=body, method='POST', + headers={'Content-Type': 'application/x-www-form-urlencoded'}) try: with urllib.request.urlopen(req, timeout=timeout) as resp: return resp.status, resp.read().decode('utf-8', 'ignore') @@ -250,7 +251,8 @@ class CrossPointDevice(DeviceConfig, DevicePlugin): """ url = self._http_base() + '/mkdir' body = urllib.parse.urlencode({'name': name, 'path': path}).encode('utf-8') - req = urllib.request.Request(url, data=body, method='POST') + req = urllib.request.Request(url, data=body, method='POST', + headers={'Content-Type': 'application/x-www-form-urlencoded'}) try: with urllib.request.urlopen(req, timeout=5) as resp: resp.read() @@ -353,11 +355,22 @@ class CrossPointDevice(DeviceConfig, DevicePlugin): def delete_books(self, paths, end_session=True): - for path in paths: - status, body = self._http_post_form('/delete', {'path': path, 'type': 'file'}) - if status != 200: - raise ControlError(desc=f'Delete failed for {path}: {body}') - self._log(f'[CrossPoint] deleted {path}') + import json as _json + self._log(f'[CrossPoint] deleting {len(paths)} books: {paths}') + url = self._http_base() + '/delete' + # Server expects form field 'paths' containing a JSON array string + body = urllib.parse.urlencode({'paths': _json.dumps(list(paths))}).encode('utf-8') + req = urllib.request.Request(url, data=body, method='POST', + headers={'Content-Type': 'application/x-www-form-urlencoded'}) + try: + with urllib.request.urlopen(req, timeout=10) as resp: + self._log(f'[CrossPoint] delete OK: {resp.status}') + except urllib.error.HTTPError as exc: + err_body = exc.read().decode('utf-8', 'ignore') if exc.fp else '' + self._log(f'[CrossPoint] delete error {exc.code}: {err_body}') + raise ControlError(desc=f'Delete failed: {exc.code} {err_body}') + except Exception as exc: + raise ControlError(desc=f'Delete failed: {exc}') def remove_books_from_metadata(self, paths, booklists): def norm(p): diff --git a/crosspoint_reader/ws_client.py b/crosspoint_reader/ws_client.py index c095ae6..d07e25b 100644 --- a/crosspoint_reader/ws_client.py +++ b/crosspoint_reader/ws_client.py @@ -229,6 +229,7 @@ def discover_device(timeout=2.0, debug=False, logger=None, extra_hosts=None): _log(logger, debug, f'[CrossPoint WS] discovery subnet broadcast {bcast}') for port in ports: targets.append((bcast, port)) + # 255.255.255.255 as fallback — works on Linux/Windows, silently fails on macOS for port in ports: targets.append(('255.255.255.255', port)) for host in extra_hosts or []: From 390f22b7a32c8c590399a4402fd0879948f1d682 Mon Sep 17 00:00:00 2001 From: Justin Mitchell Date: Mon, 6 Apr 2026 01:02:56 -0400 Subject: [PATCH 4/6] Bump driver version to 0.1.2 Co-Authored-By: Claude Opus 4.6 (1M context) --- crosspoint_reader/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crosspoint_reader/driver.py b/crosspoint_reader/driver.py index a90ad90..35ba44a 100644 --- a/crosspoint_reader/driver.py +++ b/crosspoint_reader/driver.py @@ -21,7 +21,7 @@ class CrossPointDevice(DeviceConfig, DevicePlugin): description = 'CrossPoint Reader wireless device' supported_platforms = ['windows', 'osx', 'linux'] author = 'CrossPoint Reader' - version = (0, 1, 1) + version = (0, 1, 2) # Invalid USB vendor info to avoid USB scans matching. VENDOR_ID = [0xFFFF] From f9e1551ae83512d8b6d5b06bf42fdeec09507df2 Mon Sep 17 00:00:00 2001 From: Justin Mitchell Date: Thu, 9 Apr 2026 13:29:18 -0400 Subject: [PATCH 5/6] fixes conflict with smart device plugin --- crosspoint_reader/ws_client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crosspoint_reader/ws_client.py b/crosspoint_reader/ws_client.py index d07e25b..288ef02 100644 --- a/crosspoint_reader/ws_client.py +++ b/crosspoint_reader/ws_client.py @@ -260,6 +260,9 @@ def discover_device(timeout=2.0, debug=False, logger=None, extra_hosts=None): text = data.decode('utf-8', 'ignore') except Exception: continue + if not text.startswith('crosspoint'): + _log(logger, debug, f'[CrossPoint WS] discovery ignoring non-crosspoint response: {text}') + continue semi = text.find(';') port = 81 if semi != -1: From 4cf2f3cd4f2a83d338d2ef188f457cf69eabdc8c Mon Sep 17 00:00:00 2001 From: Justin Mitchell Date: Thu, 9 Apr 2026 13:29:52 -0400 Subject: [PATCH 6/6] Bump driver version to 0.1.3 --- crosspoint_reader/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crosspoint_reader/driver.py b/crosspoint_reader/driver.py index 35ba44a..64e4f8b 100644 --- a/crosspoint_reader/driver.py +++ b/crosspoint_reader/driver.py @@ -21,7 +21,7 @@ class CrossPointDevice(DeviceConfig, DevicePlugin): description = 'CrossPoint Reader wireless device' supported_platforms = ['windows', 'osx', 'linux'] author = 'CrossPoint Reader' - version = (0, 1, 2) + version = (0, 1, 3) # Invalid USB vendor info to avoid USB scans matching. VENDOR_ID = [0xFFFF]