UDP Interface Overview
The HMCLS device exposes four UDP ports:
| Port | Direction | Purpose |
|---|---|---|
| 55152 | PC → Device | Text commands (one per UDP packet) |
| 55153 | Device → PC | Command replies, data lines and events |
| 55151 | Device → PC | Binary neural data stream — see Data Stream |
| 5000 | PC ↔ Device | Studio program upload and control |
Sending a command
Commands on port 55152 are plain ASCII text, one command per UDP packet. No trailing newline is needed — the whole packet is the command.
| Parameter | Value |
|---|---|
| Maximum command length | 127 bytes |
| Character encoding | ASCII |
| Transport | UDP (no transport-level acknowledgement) |
A command may start with an optional correlation id, written #<n> (a number and a space). The device repeats it in its reply, so you can match replies to commands when several are in flight:
#12 SPEED 999
Replies
Every reply goes back to the IP address that sent the command, on port 55153.
Commands that change something answer with a single status line:
[#id ]OK <COMMAND> [key=value ...] success
[#id ]ERR <COMMAND> <reason> refusal
#12 OK SPEED fs=8000.0
#13 ERR SPEED running
Commands that read something (for example GET_RMS, GET_TIME, STATUS) answer with their own data lines and no OK line. If they are refused, you still get an ERR line.
An unknown command is answered with ERR <word> unknown_cmd.
Error reasons
| Reason | Meaning |
|---|---|
running | Acquisition is running; stop it first |
busy | Another operation (measurement, setup) is in progress |
not_setup | SETUP must be done first |
no_headstage | No headstage is connected |
bad_arg | Missing or invalid argument |
unsupported | Value recognised but not available |
unknown_cmd | Not a command |
i2c_error | The I2C transaction failed |
bad_ip, self, empty, too_long, send_failed | NET relay errors — see NET |
Events
The device also sends events — lines starting with EVT — when something changes on its own (a headstage is plugged in, settings change, another PC takes the board…). Events are sent only to the destination PC set with DEST, never to the sender of the current command. See Events.
Destination and ownership
The data stream and the events go to one PC at a time: the one set by DEST. Without a destination, nothing is streamed and no event is sent, but commands still work and are answered. See Session and Ownership.
Protocol version
INFO reports the protocol version (proto=1) and the data-stream format version (stream=1). Check them before relying on a feature.
Quick start: Python
Save the helper below as hmcls_udp.py. It opens the reply port once, sends commands with a correlation id and matches the replies.
The HMCLS app keeps port 55153 open while it is running. Scripts that bind to that port will fail with "address already in use" unless the app is closed first. Setting SO_REUSEADDR (as the helper does) does not reliably let two programs share the port.
import itertools
import re
import socket
import time
CMD_PORT = 55152
TEXT_PORT = 55153
REPLY = re.compile(r"^#(\d+) (OK|ERR) (.*)$")
class Board:
def __init__(self, ip="192.168.1.11"):
self.ip = ip
self._ids = itertools.count(1)
self.tx = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.rx = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.rx.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.rx.bind(("0.0.0.0", TEXT_PORT))
def _send(self, cmd):
cmd_id = next(self._ids)
self.tx.sendto(f"#{cmd_id} {cmd}".encode(), (self.ip, CMD_PORT))
return cmd_id
def _lines(self, timeout):
self.rx.settimeout(timeout)
try:
data, _ = self.rx.recvfrom(2048)
except socket.timeout:
return None
return data.decode(errors="replace").splitlines()
def request(self, cmd, timeout=1.0):
"""Send a command that answers OK/ERR. Returns the key=value fields; raises on ERR."""
cmd_id = self._send(cmd)
end = time.time() + timeout
while time.time() < end:
for line in self._lines(max(0.05, end - time.time())) or []:
m = REPLY.match(line)
if m and int(m.group(1)) == cmd_id:
if m.group(2) == "ERR":
raise RuntimeError(m.group(3))
return dict(t.split("=", 1) for t in m.group(3).split() if "=" in t)
raise TimeoutError(cmd)
def query(self, cmd, timeout=0.5):
"""Send a read command. Returns every line received until the port is quiet."""
cmd_id = self._send(cmd)
out = []
while True:
lines = self._lines(timeout)
if lines is None:
return out
for line in lines:
m = REPLY.match(line)
if m and int(m.group(1)) == cmd_id and m.group(2) == "ERR":
raise RuntimeError(m.group(3))
out.append(line)
from hmcls_udp import Board
board = Board("192.168.1.11")
print(board.request("INFO")) # {'fw': '1.0', 'proto': '1', ...}
print(board.query("GET_TIME")) # ['TIME 0']