Skip to content

Remote Control

Happyarch edited this page Jun 7, 2026 · 1 revision

Remote Control (Unix Socket)

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.


Quick start

Connect with socat from an SSH session on the Pi:

socat - UNIX-CONNECT:/run/microscopi/microscopi.sock

Or with nc (OpenBSD netcat, available on Pi OS):

nc -U /run/microscopi/microscopi.sock

Both give an interactive line-oriented session. Type a command, press Enter, receive one response line.


Protocol

  • Transport: Unix domain socket (AF_UNIX, SOCK_STREAM)
  • Framing: newline-terminated UTF-8 text lines (\n or \r\n accepted)
  • 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

Command reference

Health check

ping
PONG

Use this to verify the application is running and the socket is accepting commands before scripting a sequence.


Status (JSON)

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)

Capture a still

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: still is 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.


Video recording

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

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

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 speed

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

Exposure mode

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

Autofocus / autoexposure toggles

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 overlay

crosshair on
crosshair off
OK

Shows or hides the centre circle + crosshair guide overlay.


Quit

quit
OK

Initiates a graceful shutdown. Any active recording is closed before the process exits.


Help

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.


Scripting examples

Python

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.

Bash / socat one-liners

# 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
done

systemctl reload (config hot-reload)

systemctl 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.


Configuration

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.sock

The 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 =

Implementation notes

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)

  1. accept() on the non-blocking listener fd. If a new client connects, the old client_fd_ is closed and replaced. The receive buffer is cleared.
  2. read() from client_fd_ (also non-blocking) into recv_buf_.
  3. If recv_buf_ contains \n, extract and return the first complete line.
  4. On EOF or read error (not EAGAIN), close client_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

  1. Add a new if (verb == "mycommand") branch in the dispatch_cmd lambda in src/main.cpp.
  2. Return "OK" or "OK <data>" on success, "ERR <reason>" on failure.
  3. Update the help response string in the same lambda.
  4. 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 by microscopi, 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.