POMEAS motorized zoom lenses are controlled over RS-232 (or Ethernet) with a compact ASCII protocol of five commands. Every command is three bytes: the letter X, a command letter, and a carriage return 0x0D. The controller answers position and travel queries with a nine-byte frame, and reports motion completion with the single status byte 0x55. This reference documents the protocol exactly as implemented, including the two conversion formulas and the behaviour that most often surprises integrators: the controller stops answering while the motor is moving.
It is written for engineers who are connecting a motorized zoom lens to a PLC, a motion controller, a single-board computer, or a Linux host — situations where the Windows SDK is not available and the raw byte stream is the only option.
| Item | Value |
|---|---|
| Commands | 5 (XH, XG, XZ, XM, XN) |
| Frame terminator | 0x0D (CR) — on both commands and replies |
| Serial configuration | 9600 baud, 8 data bits, no parity |
| Ethernet port | 4196 |
| Stop indicator | 0x55 — any other value, or a read timeout, means "still moving" |
| Travel formula | maxLength = (readData − 750) × 1.3 |
| Position formula | pos = (readData − 500) × 1.3 |
| Polling guidance | Wait at least 50 ms between status calls; use an overall timeout of 10 s or more |
| Parameter | Value | Notes |
|---|---|---|
| Physical layer | RS-232 (direct or via USB-to-RS-232) or Ethernet | The controller supports one or the other — not both at the same time |
| Baud rate | 9600 | The protocol document and the control-card API documentation agree on this value |
| Data bits | 8 | — |
| Parity | None | — |
| TCP port | 4196 | Used when connecting over the network interface |
| Terminator | 0x0D | Both directions |
Before any of this matters, the wiring has to be correct: connect and lock the motor cable and the RS-232 cable before applying power. Hot-plugging the motor cable while the controller is powered is the single most damaging action in the field — it can permanently damage the motor. If your motor cable needs to run more than 5 m, order the factory long cable rather than splicing several short ones, because spliced or self-made cables do not meet the impedance and shielding requirements.
Every command has the form 'X' + <command letter> + 0x0D.
| # | Function | ASCII | HEX | Reply |
|---|---|---|---|---|
| 1 | Return to origin (home) | X H CR | 0x58 0x48 0x0D | None |
| 2 | Move to a target pulse position | X G + 6 HEX digits + CR | 0x58 0x47 … 0x0D | None |
| 3 | Query motion status | X Z CR | 0x58 0x5A 0x0D | 1 byte |
| 4 | Read total travel (maximum pulse) | X M CR | 0x58 0x4D 0x0D | 9 bytes |
| 5 | Read current position | X N CR | 0x58 0x4E 0x0D | 9 bytes |
XHTX: 0x58 0x48 0x0D # 'X' 'H' CR
The lens returns to its mechanical origin. Note that the lens also performs this reset automatically every time it is powered on, and the initialisation takes 25–35 seconds, during which the lens accepts no commands at all. A host application should therefore wait at least 35 seconds after power-up before opening the port.
XGThe payload is formatted with the C format string "%C%C%6X%C": the letter X, the letter G, a six-digit hexadecimal number padded on the left with space characters (0x20) when it is shorter than six digits, and a terminating 0x0D.
| Target pulse | ASCII characters | Byte sequence |
|---|---|---|
| 100 | X G ␠␠␠␠ 6 4 CR | 0x58 0x47 0x20 0x20 0x20 0x20 0x36 0x34 0x0D |
| 1000 | X G ␠␠␠ 3 E 8 CR | 0x58 0x47 0x20 0x20 0x20 0x33 0x45 0x38 0x0D |
| 5000 | X G ␠␠ 1 3 8 8 CR | 0x58 0x47 0x20 0x20 0x31 0x33 0x38 0x38 0x0D |
| 10000 | X G ␠␠ 2 7 1 0 CR | 0x58 0x47 0x20 0x20 0x32 0x37 0x31 0x30 0x0D |
Important behaviour: if the requested position equals the current position, the lens does not move and reports no error. A host application must therefore never treat "command sent" as proof that the link is working. Reading the position back is the reliable link probe.
// Move to an absolute pulse position
int moveTo(int fd, long pulse) {
char buf[16];
// "%C%C%6X%C" - six hex digits, space-padded, CR-terminated
int n = snprintf(buf, sizeof(buf), "%c%c%6lX%c", 'X', 'G', pulse, '\r');
return write(fd, buf, n) == n ? 0 : -1;
}
XZTX: 0x58 0x5A 0x0D
wait 20 ms
RX: 1 byte
| Reply | Meaning |
|---|---|
0x55 | Motion finished — lens has stopped |
| Any other value | Still moving |
| No data at all | Also treated as "still moving" |
This is the most important behavioural detail in the whole protocol, and it is documented by the manufacturer: while the control card is driving the motor, communications are suspended. The serial or network link may return nothing at all during a zoom movement.
The practical consequence is that the polling loop must be built around retries rather than around failure detection. The reference implementation is:
XZ.0x55, wait and retry — do not declare a fault.The manufacturer's own API flow chart uses a 50 ms wait before each status call and an overall timeout of 10 seconds or more. Only when that overall timeout expires without a valid reply should the host conclude that the lens connection has failed.
// Wait for motion to complete: 50 ms per poll, 10 s overall timeout
int waitStop(int fd, int timeout_ms) {
int waited = 0;
char c;
while (waited < timeout_ms) {
usleep(50 * 1000); // reference flow chart: wait 50 ms or more
write(fd, "XZ\r", 3); // 0x58 0x5A 0x0D
usleep(20 * 1000); // protocol: wait 20 ms before reading
if (read(fd, &c, 1) == 1 && (unsigned char)c == 0x55)
return 0; // stopped
waited += 70;
}
return -1; // connection or motion fault
}
XMTX: 0x58 0x4D 0x0D
wait 20 ms
RX: 9 bytes
| Byte | Content |
|---|---|
| 1 | 0x58 (fixed) |
| 2 | empty |
| 3–7 | Travel data — five bytes, parsed as an integer with "%5X" into readData |
| 8 | 0x0D |
| 9 | empty |
sscanf(tbuffer + 2, "%5X", &readData);
maxLength = (readData - 750) * 1.3;
Worked example from the manufacturer's documentation: a reply of 0x58 -- -- -- '2' '7' '1' '0' 0x0D -- gives readData = 10000, so maxLength = (10000 − 750) × 1.3 = 12025.
XNTX: 0x58 0x4E 0x0D
wait 20 ms
RX: 9 bytes (identical structure to XM)
sscanf(tbuffer + 2, "%5X", &readData);
pos = (readData - 500) * 1.3;
Worked example: readData = 10000 gives pos = (10000 − 500) × 1.3 = 12350.
(1) XH return to origin -> poll XZ until stopped
(2) XM read total travel -> validates the link, yields maxLength
(3) XG <6 HEX> move to target position -> poll XZ until stopped
(4) XN read current position -> compare with the target (closed loop)
Two points make this sequence work reliably:
The whole protocol is strictly serial in semantics — command → motion → status read-back → next command. It does not support issuing commands concurrently to the same lens.
This protocol provides position read-back only. It does not define or guarantee a repeatability figure. Testing methodology is covered in the companion article on magnification and pulse mapping; any numeric accuracy claim should come from your own measurements under your own conditions.
It means two very different things, and choosing the wrong model is a common and expensive mistake.
| Form | Model marking | Actual capability |
|---|---|---|
| Passive magnification feedback | Suffix DS9 or DS6, e.g. PMS-LZ-63100DS9 | Zoom is still manual. RS-232 only outputs the current magnification or detent position. It cannot accept commands. |
| Active motorized control | Zoom-type code 04, 05, 09 or 10, e.g. PMS-LZ-650104 | All five commands are accepted. RS-232 actively drives magnification. |
Check the model suffix before ordering. A DS9 lens can be read but not commanded.
Not a fault. The control card suspends communications while it drives the motor. This is designed behaviour. Handle it with timeout-and-retry, not with a fault alarm or a restart.
If the target equals the current position, the lens stays still and silently succeeds. Either track the current position in the host application, or read it back with XN before each move.
Pulse positions are lens-specific. They come from the per-model pulse table supplied with the SDK, and the same magnification maps to different pulse counts on different models and different motor types. Read the total travel with XM first and confirm that the target lies inside the valid range.
| Protocol command | Legacy SDK API | SDK V4.4.7 |
|---|---|---|
XH home | MoveHome(Motor) | GoHome |
XG move | MoveGoto(Motor, long dest) | MoveTo(pulse) |
XZ status | MoveStatus(Motor) → 0 moving / 1 idle / 2 error | GetStatus |
XM travel | MoveMaxLength(Motor) | GetMaxPos |
XN position | MovePos(Motor) | GetPos |
Do not mix the two approaches on the same link. They address the same hardware registers, and mixing them — particularly around the homing reference — produces inconsistent state.
9600 baud, 8 data bits, no parity. The Ethernet option uses TCP port 4196.
Because the control card deliberately suspends communications while it drives the motor. Retry your status query with a timeout instead of treating the silence as a failure.
Check the zoom-type code in the model number. Codes 04, 05, 09 and 10 denote motorized types. A DS9 or DS6 suffix denotes passive feedback only, where zoom remains manual.
Yes. The command set is five ASCII strings, so a PLC with a free serial port can format and send them directly. The PLC-side program is yours to write; POMEAS supplies the protocol definition, not a PLC function block.
No. The DLL is a Windows convenience layer over the same byte-level protocol. Any platform that can open a serial port or TCP socket can control the lens.
XG come fromReal integration example: Motorized zoom lens equipment integration: WD, mechanical length and parfocality verified - a documented automation case, not a product spec sheet.
Simply enter your email to receive the latest news and insights from Pomeas. Stay connected with Pomeas and be the first to discover new innovations in optical excellence.