Get with File on P-ION: connect and transfer the way Ontrol's Niagara driver does

A real P-ION (fw 1.32) restarted on every Sox file get. Ontrol's Niagara 4
driver gets app.sab from the same device, so its settings are copied:
- DASP hello asks for 256-byte packets (idealMax/absMax), so file chunks
  are 238 bytes
- the message-length header (0x81) goes to Orion only, decided once the
  platform id is read
- the app is saved on the device before app.sab is read, the save resent
  every 5 s rather than 1 s (a P-ION save takes 2-3+ s)
- the file open is resent every 15 s; DASP gains a per-packet resend
  interval for this

Tested on a P-ION at 192.168.1.252: save 3.2 s, app.sab + kits.scode +
app.sax in 15.8 s, no resends, device stayed up. The unsaved-changes
question became a notice, since the app is now always saved first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 08:15:12 +03:00
co-authored by Claude Opus 5.5
parent 6b23bafffc
commit 2880c2a6f2
6 changed files with 80 additions and 37 deletions
+31 -10
View File
@@ -35,6 +35,18 @@ BUF_ID = 7
STR_ID = 8
# DASP resend intervals (seconds) for requests a device can be slow to take, as Ontrol's
# Niagara driver sets them: a P-ION saving a large app takes 2-3+ s, and a file open can
# take 10+ s on some devices. Resent every second instead, the device is sent copies
# while it is still busy with the first.
SAVE_SEND_RETRY = 5.0
FILE_OPEN_SEND_RETRY = 15.0
# The DASP packet sizes Ontrol's Niagara driver asks for in its hello.
IDEAL_MAX = 256
ABS_MAX = 256
class SoxException(Exception):
pass
@@ -464,6 +476,10 @@ class SoxClient:
# Same 512-byte limit as read_schema: whatever is cut off is left
# blank rather than failing the connect.
platform_id = res.try_str() or ""
# Ontrol's message-length header is for Orion only; the Niagara driver never
# sends it to a P-ION or R-ION.
if self._session is not None:
self._session.include_message_len = platform_id.startswith("ontrol-orion")
scode_flags = res.u1() if res.remaining > 0 else 0
kit_versions = []
for p in parts:
@@ -662,13 +678,13 @@ class SoxClient:
# ── Invoke Action ───────────────────────────────────────────
def invoke(self, comp_id, slot_id, type_id=None, arg=None):
def invoke(self, comp_id, slot_id, type_id=None, arg=None, send_retry=None):
req = _prepare_request(ord('i'))
req.write_u2(comp_id)
req.write_u1(slot_id)
if arg is not None and type_id is not None:
encode_value(req, type_id, arg)
res = self.request(req)
res = self.request(req, send_retry)
res.check_response('I')
# ── Add Component ───────────────────────────────────────────
@@ -934,7 +950,7 @@ class SoxClient:
opened = closed = False
try:
try:
res = self.request(req)
res = self.request(req, FILE_OPEN_SEND_RETRY)
except SoxException as e:
# A P-ION on firmware 1.32 never answers a file get -- it drops off
# the network for ~20 s, as a restart would -- even for a file it
@@ -986,16 +1002,21 @@ class SoxClient:
# ── Networking ──────────────────────────────────────────────
def request(self, req):
results = self.requests([req])
def request(self, req, send_retry=None):
results = self.requests([req], send_retry)
return results[0]
def requests(self, reqs):
def requests(self, reqs, send_retry=None):
"""Sends the batch and waits for every reply.
send_retry (seconds) sets the DASP resend interval of these datagrams, for a
request the device is known to be slow to take.
"""
if self.is_closed:
raise SoxException("SoxClient closed")
if len(reqs) > 255:
return self._chunk_requests(reqs)
return self._chunk_requests(reqs, send_retry)
with self._lock:
for i, req in enumerate(reqs):
@@ -1006,7 +1027,7 @@ class SoxClient:
self._req_event.clear()
for req in reqs:
self._session.send(req.to_bytes())
self._session.send(req.to_bytes(), send_retry)
deadline = time.monotonic() + 30.0
while True:
@@ -1030,11 +1051,11 @@ class SoxClient:
self._responses = None
raise SoxException("Request timeout")
def _chunk_requests(self, reqs):
def _chunk_requests(self, reqs, send_retry=None):
results = []
for i in range(0, len(reqs), 255):
chunk = reqs[i:i + 255]
results.extend(self.requests(chunk))
results.extend(self.requests(chunk, send_retry))
return results
# called by receiver thread