Fix XcomRS232: frame responses by scom length field (not CR/LF) and open the port as 8E1 - #9
Open
njalooo wants to merge 1 commit into
Open
Fix XcomRS232: frame responses by scom length field (not CR/LF) and open the port as 8E1#9njalooo wants to merge 1 commit into
njalooo wants to merge 1 commit into
Conversation
… as 8E1 The Xcom-232i over raw RS-232 does not append the CR/LF terminator used by Xcom-LAN/Moxa Data Packing, and scom frames are self-delimiting by the header data_length field. read_until(CRLF) therefore timed out (empty response) on a bare 232i. Also open the port with EVEN parity (8E1) as required, instead of relying on pyserial's default 8N1.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
XcomRS232cannot communicate with a bare Xcom-232i over a direct RS-232link. Every request times out with
AssertionError: got empty response,100% of the time. This PR fixes two related defects in
XcomRS232.sendPackage:Response framing. The implementation read the response with
ser.read_until(SERIAL_TERMINATOR)whereSERIAL_TERMINATOR = b'\x0D\x0A'.A bare Xcom-232i does not append CR/LF to its replies — the scom
protocol is self-delimited by the
data_lengthfield in the frame header.read_until(CRLF)therefore waits for a delimiter that never arrives,times out, and returns nothing (and the subsequent
response[:-2]slicewould corrupt the frame even when bytes did arrive).
Serial parity. The port was opened with
serial.Serial(device, baudrate, timeout=...), i.e. pyserial's default8N1. The Xcom-232i requires 8E1 (EVEN parity). With 8N1 the 232i
silently rejects every frame (the scom data-link layer drops frames with a
parity/checksum error and sends no response — see the Studer "Technical
specification - Xtender serial protocol", data-link layer error handling).
Both defects independently produce the same symptom (empty response), which
is why direct-serial use of the library failed completely.
Why CR/LF was wrong here
The CR/LF terminator is not a property of the Xcom-232i. It is an artifact
of the Moxa NPort "Data Packing" feature used in an Xcom-LAN setup, where
a Moxa gateway bridges the 232i's serial port to TCP and is configured to
delimit/pack TCP segments on
0x0D 0x0A. That path is handled separately bythe
XcomLANTCP/XcomLANUDPclasses over the network — and notably, eventhose classes do not use
read_until(CRLF); theyrecv()and callPackage.parseBytes()directly. So the CR/LF terminator was present only inthe serial class, where it never applies. A Moxa is never in front of the
XcomRS232serial transport (if a Moxa is present, you reach it via the LANclasses over Ethernet). The CR/LF handling in
XcomRS232was therefore simplymisplaced.
The fix
XcomRS232.sendPackagenow:bytesize=EIGHTBITS,parity=PARITY_EVEN,stopbits=STOPBITS_ONE); parity is also exposed as aconstructor argument defaulting to EVEN, for the rare case of a 232i
reconfigured otherwise;
Package.parse(ser)consume exactly oneself-delimited scom frame directly from the serial stream — it seeks the
0xAAstart byte, reads the 11-byte header + 2 header-checksum bytes, thendata_lengthbody bytes + 2 data-checksum bytes, validating both checksums.No CR/LF is appended to the request or expected in the response.
This reuses the existing, already-correct
Package.parse()streaming parser,so no protocol logic is duplicated.
Diagnosis (how this was isolated)
The root cause was found by elimination against a known-good reference:
pyserial, length-based read,38400 8E1) reliably read info object 3000 (battery voltage) and other
values from XT1/XT2/XT3 (addresses 101–103), the BSP (601), and the gateway
alias (1) — proving the wiring, baud, parity, addressing, frame encoding and
checksum were all correct, and the 232i was healthy.
exact same object 3000. This localised the fault to the library's request/
response handling, not the link.
src=1andsrc=1000, matching the library'ssrc=1.XcomRS232.pyrevealedread_until(SERIAL_TERMINATOR)and theabsence of any parity argument on
serial.Serial(...). Cross-checkingprotocol.py(Package.parse/Header.length = 11) andXcomLAN.py(which does not use CR/LF) confirmed the CR/LF was misplaced and the
parser already supported length-based framing.
Testing
Hardware: ASUS Tinker Board (Armbian, Debian 13 trixie, armhf) → StarTech
USB-RS232 adapter (Prolific PL2303GC) → straight-through DE-9 → Studer
Xcom-232i → Studer bus with 3× Xtender XTM 4000-48 (addr 101–103) and a BSP
(addr 601). Serial link: 38400 8E1, no flow control.
Before the fix: every
getValue/getValueByIDraisedAssertionError: got empty response.After the fix, on real hardware:
All reads return correct, sensible values, repeatably, against XT1 (101) and
the BSP (601). Parameter reads (TYPE_PARAMETER, 1xxx IDs) and info reads
(TYPE_INFO, 3000) both work.
Note on baud rate: the library examples use 115200; the Xcom-232i default is
38400. The fix is baud-independent — these tests used 38400 because that is
the device default — but worth noting in case users hit an empty response that
is actually a baud mismatch.
Backwards compatibility
XcomRS232(serialDevice=..., baudrate=...)now additionally defaults to 8E1, which is required by thehardware. A new optional
parity=argument is added (defaultPARITY_EVEN).XcomLANTCP,XcomLANUDP) is untouched.SERIAL_TERMINATOR/MSG_MAX_LENGTHimport usage is removed from theserial read path.
Risk
Low. The change is confined to
XcomRS232.sendPackageand its constructor,reuses the existing
Package.parse()parser, and does not affect the LANtransports or the protocol layer.