mountdaemon_10micron
A daemon for controlling 10Micron equatorial mounts (e.g. GM4000HPS) over a serial port. It exposes a text command interface for local clients, a binary Stellarium-protocol interface for planetarium software, and periodically publishes the current telescope state into a FITS-header file for downstream acquisition software.
The daemon is written in C (C23), uses ERFA for all astronomical computations, usefull_macros for utility routines, and optionally weather proxy for getting meteo data.
Table of contents
- Overview
- Building and installation
- Running the daemon
- Command-line options and configuration file
- Astronomical parameters
- Coordinate systems
- Network interfaces
- Command reference
- FITS-header file
- Emulation mode
- Signals and process management
- Architecture
- Known limitations
Overview
mountdaemon_10micron connects to a 10Micron mount over a serial line (default /dev/ttyUSB0 at
115200 baud) and performs three independent jobs:
- Serves a local text command interface — clients send newline-terminated commands (
tagra,gotord,park, …) over a TCP or UNIX socket and receive plainkey=valuereplies. This is the primary interface for an observatory control system. - Serves a Stellarium-compatible binary protocol — a planetarium program (Stellarium, Cartes du Ciel, etc.) can connect to a second TCP socket, read the current telescope position, and issue goto commands. This allows visual pointing with the planetarium.
- Writes a FITS-header file — at least every
MOUNT_CHECK_Tseconds the daemon writes a fresh FITS-header block with the current telescope position, sidereal time, MJD and place data. The file is atomically replaced so a downstream acquisition program can read it at any moment.
Additionally, the daemon:
- corrects the mount's internal clock and refraction model once per hour from local weather data;
- parks the mount automatically if the weather feed is lost while tracking;
- reconnects to the mount if the serial link drops;
- supports an emulation mode, in which the mount is simulated locally — useful for testing clients without hardware.
The daemon is built around the ERFA library, so coordinates are rigorously converted between ICRS (J2000), CIRS, observed, and horizontal systems, and refraction is accounted for using the current pressure and temperature.
Building and installation
Dependencies
- a C23-capable compiler (GCC 13+ or Clang 16+);
- CMake ≥ 4.0;
- pkg-config;
- liberfa ≥ 2.0.0;
- usefull_macros ≥ 0.3.5;
- libweather (linked with
-lweather); libm.
Build
git clone --depth=1 https://github.com/eddyem/small_tel
cd Daemons/10micron_stellarium
mkdir build && cd build
cmake ..
make
Options recognised by CMake:
| Option | Default | Meaning |
|---|---|---|
DEBUG |
OFF |
Build in debug mode (-Og -g3 -Werror, no fork). |
Install
su -c "make install"
The binary is installed to <prefix>/bin/mountdaemon_10micron. The default prefix is /usr/local.
Running the daemon
mountdaemon_10micron -d /dev/ttyS0 -S 9600 \
--latitude 43.65:39:00 --longitude 41.44:06:00 --altitude 2070 \
--dut1 0.1 -p :10000 -P localhost:10001 -v
The daemon forks a supervisor process that re-spawns the working process if it crashes. It writes a
PID-file (/tmp/mountdaemon_10micron.pid by default), which is removed on clean exit.
Stopping the daemon: send SIGTERM (or SIGINT, SIGQUIT) to the supervisor PID from
the PID-file. The supervisor kills its child and exits.
Command-line options and configuration file
All options may be given either on the command line or in a configuration file passed with -c <file>. Command-line values always take precedence over the configuration file. The config file
uses key = value lines — key names are the long option names below without the leading --.
| Long option | Short | Argument | Default | Description |
|---|---|---|---|---|
--device |
-d |
string | /dev/ttyUSB0 |
Serial device connected to the mount. |
--emulation |
-e |
— | 0 |
Run in emulation mode (no hardware needed). |
--logfile |
-l |
string | no | Redirect log output to this file. |
--hdrfile |
-o |
string | /tmp/10micron.fitsheader |
File to write the FITS-header into. |
--pidfile |
string | /tmp/mountdaemon_10micron.pid |
PID-file path. | |
--port |
-p |
string | :10000 |
Port (or host:port) for the Stellarium server. |
--cmdport |
-P |
string | localhost:10001 |
Port (or UNIX-socket path) for the command console. |
--sleept |
-t |
int | 100 |
Main-loop sleep, µs. |
--isunix |
-U |
— | 0 |
Use a UNIX-domain socket for cmdport. |
--sertmout |
-T |
double | 1.0 |
Serial answer timeout, seconds. |
--serspeed |
-S |
int | 115200 |
Serial speed. |
--maxclients |
int | 5 |
Max clients per socket. | |
--mountname |
string | "10Micron GM4000HPS" |
Mount name for the FITS header. | |
--verbose |
-v |
— | 0 |
Increase verbosity (each -v adds a level). |
--parka |
-A |
string | built-in | Parking azimuth, degrees or DD:MM:SS. |
--parkz |
-Z |
string | built-in | Parking zenith distance. |
--dut1 |
double | 0 |
UT1 − UTC, seconds. | |
--polarx |
double | 0 |
IERS polar-motion X, arcsec. | |
--polary |
double | 0 |
IERS polar-motion Y, arcsec. | |
--latitude |
string | built-in | Site latitude (degrees, DD:MM:SS, or decimal). |
|
--longitude |
string | built-in | Site longitude (degrees, positive east). | |
--altitude |
string | built-in | Site altitude, metres. | |
--help |
-h |
— | — | Show help. |
--config |
-c |
string | — | Read options from this file. |
Example configuration file
device = /dev/serial/by-id/usb-FTDI_USB-RS232-if00-port0
serspeed = 115200
sertmout = 1.5
port = :10000
cmdport = /tmp/mountdaemon.sock
isunix = 1
sleept = 200
maxclients = 10
latitude = 43.65:39:00
longitude = 41.44:06:00
altitude = 2070
dut1 = 0.1
polarx = 0.05
polary = 0.12
mountname = "10Micron GM4000HPS"
logfile = /var/log/mountdaemon.log
Astronomical parameters
The following parameters influence pointing accuracy and FITS-header content. They may be set at start-up via CLI/config, and (except place data) also at runtime via network commands.
| Parameter | Runtime command | Units | Typical range | Notes |
|---|---|---|---|---|
| Site latitude | — | degrees | −90 … +90 | Set at start-up only. |
| Site longitude | — | degrees | −180 … +180 (E+) | Set at start-up only. |
| Site altitude | — | metres | −500 … +9000 | Set at start-up only. |
| DUT1 | dut1 |
seconds | −1 … +1 | UT1 − UTC. Affects LST by up to ±15″, affecting pointing. |
| Polar motion X | polarx |
arcsec | ±1000 | IERS X coordinate of celestial pole. |
| Polar motion Y | polary |
arcsec | ±1000 | IERS Y coordinate of celestial pole. |
If not set, the daemon uses internal defaults (site: SAO RAS, altitude 2070 m). DUT1 and polar motion default to 0.
Coordinate systems
The daemon distinguishes the following frames:
| Frame | Notation | Units | Notes |
|---|---|---|---|
| Catalog (ICRS / J2000) | J2000 |
RA: hours, Dec: degrees | Coordinates entered by the user via tagra/tagdec. |
| Epoch of date (mean equinox) | Jnow |
RA: hours, Dec: degrees | Used internally for pointing. |
| CIRS (geocentric apparent) | — | radians | Result of eraAtci13. |
| Observed (with refraction) | — | radians | Result of eraAtco13. |
| Horizontal | Az/Zd |
degrees | Azimuth, clockwise from North. ZD = 90° − altitude. |
- Target epoch is stored as an MJD. By default it is J2000 (
ERFA_DJM00= 51544.5). It can be set to any epoch via thetagmjdcommand. - The command
gotordconverts the input coordinates from the target epoch to Jnow usingJXtoJnow()before sending to the mount. - The command
gotorhinterprets the input as an hour angle and converts to RA using the current LST. - The current telescope position returned by the mount is in Jnow, and the FITS-header records it
under
RA,HA,DEC,AZ,ZD.
Network interfaces
Command socket
Default address: localhost:10001 (TCP) or a UNIX socket if --isunix is set.
Protocol: line-oriented text. A client sends one command per line, optionally with a value:
tagra = 12.345
tagdec = +45:30:00
gotord
The daemon replies with key=value (newline-terminated) for getters, or nothing (only implicit
success/failure) for setters/actions. Commands may be issued one at a time or in a stream. All
sockets are multiplexed by a thread-per-client model inside usefull_macros's sl_sock
infrastructure.
Up to --maxclients simultaneous clients are accepted. If more try to connect, they receive Try later: too much clients connected and the connection is closed.
Values may be given either as a decimal number (12.345, -26.5) or as DD:MM:SS / HH:MM:SS
(sexagesimal). Angles are interpreted as degrees unless the field name (RA, HA, LST) explicitly
implies hours.
Stellarium socket
Default address: :10000 (all interfaces, port 10000). Protocol: binary little-endian,
according to Stellarium's "Telescope Control" plugin specification.
Incoming (client → server) message, 20 bytes:
| Field | Type | Meaning |
|---|---|---|
len |
uint16 |
Total message length (20). |
type |
uint16 |
0. |
time |
uint64 |
µs since epoch (unused). |
RA |
uint32 |
Target RA. 0 = 0h, 0x80000000 = 12h, 0x100000000 = 24h. |
DEC |
int32 |
Target Dec. -0x40000000 = −90°, 0x40000000 = +90°. |
Outgoing (server → client) message, 24 bytes: same fields plus a trailing int32 status (0 = ok,
other = error).
The daemon treats incoming messages as tagra/tagdec in the current epoch (which for Stellarium
is normally J2000). It does not automatically start a slew — the client must send gotord on
the command socket.
Command reference
All commands are case-sensitive. Getter command names are typically used without an argument; setter commands require an argument of the indicated type.
Target coordinates (input)
| Command | Type | Units | Description |
|---|---|---|---|
tagra |
get/set | hours (0…24) | Target right ascension. |
tagdec |
get/set | degrees (−90…+90) | Target declination. |
tagha |
get/set | hours | Target hour angle (alternative to RA). |
tagaz |
get/set | degrees (0…360) | Target azimuth (clock from North). |
tagzd |
get/set | degrees (0…90) | Target zenith distance. |
tagmjd |
get/set | MJD or J<year> |
Epoch of the input coordinates. J2050 = 2050.0, 51544.5 = J2000. |
Pointing
| Command | Description |
|---|---|
gotord |
Slew to the input RA/Dec (converted from target epoch to Jnow) and start tracking. |
gotorh |
Slew using the stored hour angle: RA = LST − HA, then track. |
gotoaz |
Slew to the input Az/ZD and stop. |
stop |
Stop any motion (emergency stop). |
stoptrk |
Stop tracking but leave the mount in place. |
track |
Start tracking from the current position. |
park |
Slew to the parking position (see parkaz/parkzd). |
shutdown |
Power off the mount. Requires a numeric key to confirm (see below). |
State queries
| Command | Units | Description |
|---|---|---|
status |
— | Human-readable mount status. |
telra |
hours | Current telescope RA (Jnow). |
telha |
hours | Current telescope hour angle. |
teldec |
degrees | Current telescope declination (Jnow). |
telaz |
degrees | Current telescope azimuth. |
telzd |
degrees | Current telescope zenith distance. |
lst |
hours | Local sidereal time. |
unixt |
seconds | Server UNIX time. |
tel* commands return RESULT_FAIL if the weather feed is lost or the mount is in Error state —
this prevents a client from acting on stale coordinates.
Place and almanac data
| Command | Type | Description |
|---|---|---|
place |
get | Site latitude, longitude, altitude. |
dut1 |
get/set | UT1 − UTC, seconds. |
polarx |
get/set | IERS X pole coordinate, arcsec. |
polary |
get/set | IERS Y pole coordinate, arcsec. |
Place data can only be set at start-up (via CLI/config).
Parking
| Command | Type | Description |
|---|---|---|
parkaz |
get/set | Parking azimuth, degrees. |
parkzd |
get/set | Parking zenith distance, degrees. |
Sending custom command
The raw command allows to send any unsupported command string directly to mount.
E.g. to set lunar tracking rate send raw = :TL#. If mount gives no answer for command, you will
get message "No answer", otherwise you'll get this answer.
Shutdown
The shutdown command implements a simple confirmation handshake:
- A client sends
shutdownwithout an argument. The daemon generates a random key, stores it with a timestamp, and returnsshutdown=<key>. - Within 5 minutes, a client must send
shutdown = <key>. If the key matches, the mount is powered off.
This protects against accidental shutdown commands.
FITS-header file
Every MOUNT_CHECK_T (0.5 s) the daemon collects the current state and writes a complete
FITS-header block into the file given by --hdrfile. The write is atomic: a temporary file is
created with mkstemp() and rename()d over the destination, so a reader never sees a partial
block.
The following keywords are written (only if meaningful):
| Keyword | Comment |
|---|---|
TIMESYS |
'UTC'. |
ORIGIN |
'SAO RAS'. |
MOUNTNAM |
Mount name (from --mountname). |
POLARX |
X pole coordinate, arcsec (if non-zero). |
POLARY |
Y pole coordinate, arcsec (if non-zero). |
DUT1 |
UT1 − UTC, seconds (if non-zero). |
INPRA,INPDEC |
Input target RA/Dec, if the last input was celestial. |
INPAZ,INPZD |
Input target Az/ZD, if the last input was horizontal. |
TAGRA,TAGDEC |
Last slewed-to target, always celestial. |
RA,HA,DEC |
Current telescope position in Jnow. |
AZ,ZD |
Current telescope position in the horizontal frame. |
TELSTAT |
Human-readable mount status. |
INPEQUIN |
Epoch (year) of the input coordinates. |
EQUINOX |
Epoch (year) of the current telescope coordinates. |
MJD |
MJD of the header. |
PIERSIDE |
Pier side of the mount ('E'/'W'). |
ELEVAT |
Site altitude, m. |
LONGITUD |
Site longitude, degrees east. |
LATITUDE |
Site latitude, degrees north. |
LSTEND |
Local sidereal time, hours. |
Weather keywords (HUMIDITY, PRESSURE, EXTTEMP, RAIN, SKYQUAL, WINDSPD, WINDMAX,
WEATTIME) are intentionally not written by this daemon — the weather data is expected to be
merged by the weather daemon's own header writer.
Emulation mode
Started with -e/--emulation, the daemon does not open the serial port and instead simulates a
mount with the following behaviour:
- Start-up position: Az = 180°, ZD = 80°.
- Slew rates: 5°/s in RA, 8°/s in Dec.
- Slew completes when the angular distance is below 1″ or when the modelled time has elapsed.
ZD_LIMIT = 80°— slews beyond this limit are refused or stopped.- Meridian flip is simulated: if reaching the target from the "flipped" side is faster, the mount will go through a flipped state (Dec > 90°, RA += 12h).
- Statuses
Stopped,Slewing,Trackingare fully modelled; the others are not.
This is useful for developing clients (control-system, planetarium) without access to real hardware. All command-socket and Stellarium-socket behaviour is identical to the real-mount mode.
Signals and process management
The daemon runs as a supervisor + worker pair:
- The supervisor (
main()beforefork()) watches the child, re-spawns it after a crash, and holds the PID-file. - The worker is the actual server: it opens the serial port, listens on sockets, and processes commands.
Signals handled:
| Signal | Action |
|---|---|
SIGTERM |
Remove PID-file, kill worker, exit. |
SIGINT |
Same as SIGTERM. |
SIGQUIT |
Same as SIGTERM. |
SIGHUP |
Ignored (may be used later for config-file re-reading) |
SIGTSTP |
Ignored (so the daemon can survive Ctrl-Z from a shell session). |
If the worker dies with a non-zero exit status, the supervisor logs the event. If the worker dies within 10 minutes of the previous restart, this is treated as a crash-loop and logged with a warning.
Architecture
┌─────────────────────────┐
│ supervisor (fork) │
└────────────┬────────────┘
│ fork + waitpid
┌────────────▼────────────┐
cmd clients ──►│ worker process │◄── Stellarium clients
│ │
│ ┌───────────────────┐ │
│ │ cmd_socket (TCP │ │
│ │ or UNIX, thread │ │
│ │ per client) │ │
│ └───────────────────┘ │
│ ┌───────────────────┐ │
│ │ stellarium_sock │ │
│ │ (TCP, thread │ │
│ │ per client) │ │
│ └───────────────────┘ │
│ ┌───────────────────┐ │
│ │ main loop │ │
│ │ (0.5 s tick) │ │
│ └────────┬──────────┘ │
│ │ │
└───────────┼─────────────┘
│ serial (mutex-protected)
▼
┌──────────────┐
│ 10Micron │
│ mount │
└──────────────┘
Threading model:
- The main thread runs the state-collection loop: every
MOUNT_CHECK_Tseconds it queries mount status, coordinates, azimuth, pier side, weather, and updates the FITS header. - The command socket is served by
sl_sockwhich spawns one thread per connected client. All command handlers run in those threads; access to the serial device is serialised bymntdev_mutex. - The Stellarium socket spawns one thread per connected client, with each thread performing a bidirectional exchange with the client.
- Shared state (
HDR, mount status, input coordinates) is protected implicitly — all updates go through accessor functions, and the serial port is guarded bymntdev_mutex.
Synchronisation primitives:
pthread_mutex_t mntdev_mutex— serialises access to the mount device.atomic_int mountstatus— cached mount status visible from any thread.atomic_int emul_status— emulation-mode status.
Known limitations
- The daemon assumes a well-behaved serial link; there is no protocol-level retry for corrupted
responses beyond 3 immediate retries on
mount_status(). - Weather data is read via
libweather; the format is expected to be stable. Iflibweatheris unavailable at build time, the code will not build. - Emulation mode does not model all mount statuses and does not implement true dual-axis motion; it is a kinematic approximation suitable for client development.
mount_corrdata()uses local time for the:SLDTcommand, which matches the 10Micron firmware convention; this is intentional and differs from the rest of the daemon, which works in UTC.- The Stellarium protocol implementation uses JNow coordinates (the DAEMON does not perform the JNow→J2000 conversion that some clients expect). Planetarium programs that assume J2000 output may show a small offset; this is acceptable for visual pointing but should be considered if used for astrometric work.
License
All source files are licensed under GNU General Public License v3.0 unless stated otherwise...