-
Notifications
You must be signed in to change notification settings - Fork 0
Remote Control
The application exposes a Unix domain socket for headless remote control — no keyboard or display required. Any command available on the physical keyboard is also available over the socket, plus structured status queries useful for scripting and automated testing.
Connect with socat from an SSH session on the Pi:
socat - UNIX-CONNECT:/run/microscopi/microscopi.sockOr with nc (OpenBSD netcat, available on Pi OS):
nc -U /run/microscopi/microscopi.sockBoth give an interactive line-oriented session. Type a command, press Enter, receive one response line.
-
Transport: Unix domain socket (
AF_UNIX,SOCK_STREAM) -
Framing: newline-terminated UTF-8 text lines (
\nor\r\naccepted) - Model: one command → one response, strictly sequential
- Concurrency: one client at a time; a new connection bumps the previous one
Response format:
| Prefix | Meaning |
|---|---|
OK |
Command succeeded. May be followed by a space and a value (e.g. a file path or JSON) |
PONG |
Response to ping
|
ERR <reason> |
Command failed or was malformed |
ping
PONG
Use this to verify the application is running and the socket is accepting commands before scripting a sequence.
status
{"mode":"P","iso":200,"shutter_us":16667.0,"aperture":0.0,"lens_pos":0.35,"af":true,"ae":true,"recording":false,"still_count":3,"dual_stream":true}Fields:
| Field | Type | Description |
|---|---|---|
mode |
string | Exposure mode: P, A, S, or M
|
iso |
int | Current ISO (0 = AUTO, i.e. driven by AE) |
shutter_us |
float | Current exposure time in microseconds |
aperture |
float | Current f-number; 0.0 if unknown or fixed |
lens_pos |
float | Lens position 0.0 (infinity) – 1.0 (macro); -1.0 if AF is active and position unknown |
af |
bool | Whether autofocus is currently enabled |
ae |
bool | Whether autoexposure is currently active |
recording |
bool | Whether video recording is in progress |
still_count |
int | Number of stills captured this session |
dual_stream |
bool | Whether the camera started in dual-stream mode (Viewfinder + StillCapture simultaneously) |
still
OK /home/microscopi/stills/20260607_143021.jpg
Captures a full-resolution still with EXIF metadata and saves it to stills_dir (configured in microscopi.conf). Returns the full path on success.
If capture_format = raw or jpeg+raw is configured, the raw file is also written alongside the JPEG (same base name, .raw extension). The OK response always refers to the JPEG path.
Note:
stillis blocking — the viewfinder pauses briefly while the capture completes (same behaviour as pressing Space on the keyboard). In dual-stream mode the pause is eliminated.
record_start
OK /home/microscopi/videos/20260607_143025.mkv
record_stop
OK
Starts or stops MKV recording. record_start returns the output file path. ERR already recording / ERR not recording are returned if the state is already what was requested.
focus 0.35
OK
Set lens position directly (range 0.0 = infinity to 1.0 = macro). Disables AF.
focus up
focus down
Step by focus_key_step (default 0.05, configurable in microscopi.conf). Disables AF.
iso 400
OK
Set ISO to the nearest value on the camera's ladder. Actual ISO set may differ slightly — query status to confirm.
iso auto
OK
Return to auto ISO (AE-driven gain). Does not re-enable AE if it was manually disabled.
shutter 16667
OK
Set exposure time in microseconds (e.g. 16667 ≈ 1/60 s, 33333 ≈ 1/30 s, 1000000 = 1 s). Has no effect unless AE is disabled — switch to S or M mode first:
mode s
shutter 4000
mode p
mode a
mode s
mode m
OK
Switches the exposure mode. Case-insensitive; only the first character is read.
| Mode | AE | Shutter | ISO |
|---|---|---|---|
p |
on | auto | auto |
a |
on | auto | user |
s |
off | user | user |
m |
off | user | user |
af on
af off
ae on
ae off
OK
Toggle AF or AE independently of the exposure mode. ae off is equivalent to switching to M/S mode from the OSD's perspective.
crosshair on
crosshair off
OK
Shows or hides the centre circle + crosshair guide overlay.
quit
OK
Initiates a graceful shutdown. Any active recording is closed before the process exits.
help
OK ping status still record_start record_stop focus(<pos>|up|down) iso(<val>|auto) shutter(<us>) mode(p|a|s|m) af(on|off) ae(on|off) crosshair(on|off) quit
Lists all available commands on one line. Useful for a quick reference without opening this page.
import socket, json
def microscopi(cmd):
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.connect('/run/microscopi/microscopi.sock')
s.sendall((cmd + '\n').encode())
return s.recv(4096).decode().strip()
# Check alive
assert microscopi('ping') == 'PONG'
# Read status
raw = microscopi('status')
st = json.loads(raw)
print(f"ISO {st['iso']} {st['shutter_us']/1000:.1f} ms lens {st['lens_pos']:.2f}")
# Capture a still
reply = microscopi('still') # "OK /home/microscopi/stills/..."
path = reply.split(' ', 1)[1]
print('saved to', path)Each call opens a fresh connection because the server allows one client at a time. For a longer interactive session, keep the socket open and send/receive in a loop.
# Capture a still and print the path
socat - UNIX-CONNECT:/run/microscopi/microscopi.sock <<< "still"
# Get status as JSON and parse with jq
socat - UNIX-CONNECT:/run/microscopi/microscopi.sock <<< "status" | jq .
# Focus sweep for a z-stack (21 planes, 0.00 → 1.00)
for i in $(seq 0 5 100); do
pos=$(printf '0.%02d' $i)
socat - UNIX-CONNECT:/run/microscopi/microscopi.sock <<< "focus $pos"
socat - UNIX-CONNECT:/run/microscopi/microscopi.sock <<< "still"
sleep 0.5
donesystemctl reload microscopi sends SIGHUP to the process, which reloads microscopi.conf without restarting. Hot-reloadable keys: crop_*, focus_scroll_step, focus_key_step, show_crosshair, stills_dir, video_dir. Changes to camera, display, or key settings require a full systemctl restart.
The socket path is set in microscopi.conf:
[remote]
# Unix socket path. The parent directory is created automatically.
# Set to empty to disable the socket entirely.
socket_path = /run/microscopi/microscopi.sockThe systemd service declares RuntimeDirectory=microscopi, which tells systemd to create /run/microscopi/ (mode 0755, owned by the microscopi user) before the process starts. This means the default socket path is always available without any manual setup.
To disable the socket (e.g. for a locked-down deployment):
socket_path =This section is for contributors working on the C++ codebase.
Class: src/util/SocketServer (socket_server.h / socket_server.cpp)
The server is entirely non-blocking and single-threaded — it is polled once per frame from the main loop, adding negligible latency (~0 µs when idle). No mutexes or condition variables are involved.
Main loop iteration
├── SIGHUP hot-reload check
├── sock.poll(cmd) ← accept() + read() both non-blocking
│ └── if cmd ready → sock.reply(dispatch_cmd(cmd))
├── input.process_events()
├── camera.get_frame()
└── renderer.present_frame()
SocketServer::poll(cmd)
-
accept()on the non-blocking listener fd. If a new client connects, the oldclient_fd_is closed and replaced. The receive buffer is cleared. -
read()fromclient_fd_(also non-blocking) intorecv_buf_. - If
recv_buf_contains\n, extract and return the first complete line. - On EOF or read error (not
EAGAIN), closeclient_fd_and return false.
dispatch_cmd(line)
A lambda in main() that captures all application state by reference. Tokenises the command line on whitespace, dispatches on the first token, and returns a response string. Because it runs in the main thread, it can call any camera API or modify any state variable without locking.
Commands that are blocking (e.g. still in single-stream mode) will stall the viewfinder for the duration of the capture — this is the same behaviour as the keyboard shortcut and is expected.
Adding a new command
- Add a new
if (verb == "mycommand")branch in thedispatch_cmdlambda insrc/main.cpp. - Return
"OK"or"OK <data>"on success,"ERR <reason>"on failure. - Update the
helpresponse string in the same lambda. - Document it in this wiki page.
Limitations
- One client at a time. A new connection immediately evicts the previous one.
- No authentication. The socket inherits the file-system permissions of
/run/microscopi/(owned bymicroscopi, mode 0755) — any local user can connect. This is intentional for a debug tool on a single-user embedded device. -
write()to the client uses best-effort delivery. If the client's receive buffer is full (extremely unlikely for short responses), the write is dropped and the client is closed.