Skip to main content

UDP Interface Overview

The HMCLS device exposes four UDP ports:

PortDirectionPurpose
55152PC → DeviceText commands (one per UDP packet)
55153Device → PCCommand replies, data lines and events
55151Device → PCBinary neural data stream — see Data Stream
5000PC ↔ DeviceStudio 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.

ParameterValue
Maximum command length127 bytes
Character encodingASCII
TransportUDP (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​

ReasonMeaning
runningAcquisition is running; stop it first
busyAnother operation (measurement, setup) is in progress
not_setupSETUP must be done first
no_headstageNo headstage is connected
bad_argMissing or invalid argument
unsupportedValue recognised but not available
unknown_cmdNot a command
i2c_errorThe I2C transaction failed
bad_ip, self, empty, too_long, send_failedNET 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.

Port 55153 and the HMCLS app

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.

hmcls_udp.py
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']