ST4-ESP32
ESP32 library for controlling telescope mounts via the ST-4 autoguider port. Drives optocoupler-isolated GPIO pins with hardware-timer pulse guiding, microsecond position tracking, and FreeRTOS thread safety.
Supports serial, WebSocket, and ASCOM Alpaca interfaces -- drop-in compatible with the original arduino-st4 protocol while adding pulse guiding, position tracking, and network control.
Wiring
ESP32 TLP521-4 (Quad Optocoupler) RJ12 (ST-4)
┌──────────────────────┐
GPIO 16 (RA+) ──┤1 Anode Collector 8├── Pin 3 (RA+)
GND ──┤2 Cathode Emitter 7├── Pin 2 (GND)
GPIO 17 (RA-) ──┤3 Anode Collector 6├── Pin 5 (RA-)
GND ──┤4 Cathode Emitter 5├── Pin 4 (DEC-)
└──────────────────────┘
┌──────────────────────┐
GPIO 18 (DEC+) ──┤1 Anode Collector 8├── Pin 6 (DEC+)
GND ──┤2 Cathode Emitter 7├── Pin 2 (GND)
GPIO 19 (DEC-) ──┤3 Anode Collector 6├── Pin 4 (DEC-)
GND ──┤4 Cathode Emitter 5├── Pin 2 (GND)
└──────────────────────┘
GPIO 2 (LED) ── 220R ── LED ── GND
Each GPIO drives an optocoupler LED through a current-limiting resistor (220-470 ohm). The optocoupler transistor shorts the corresponding ST-4 signal to ground, simulating a button press on the hand controller. The optical isolation protects the ESP32 from the mount's electrical domain.
See arduino-st4/Hardware/ for reference photos of the original build.
Quick Start
#include <ST4.h>
ST4Controller controller;
ST4Serial serial;
void setup() {
controller.begin(); // Default pins: 16, 17, 18, 19, LED=2
serial.begin(controller); // 57600 baud, extended protocol
}
void loop() {
serial.update();
}
Flash with PlatformIO:
pio run -e serial_compatible -t upload -t monitor
Features
- GPIO safety -- mutual exclusion prevents simultaneous plus/minus activation on the same axis (optocoupler short protection)
- Hardware-timer pulse guiding --
esp_timerone-shot with FreeRTOS task handoff, non-blocking microsecond precision - Dead-reckoning position tracking -- port of ASCOM
AxisMovementTracker.csusingesp_timer_get_time()for microsecond resolution - Configurable sidereal rates -- default 9x/7x RA (accounts for Earth rotation), 8x symmetric DEC
- Serial protocol -- backward compatible with original arduino-st4 ASCOM driver, plus extended commands
- WebSocket server -- JSON command/state protocol with automatic state broadcasting
- ASCOM Alpaca REST API -- standard telescope interface for N.I.N.A., PHD2, and any ASCOM-compatible software
- FreeRTOS thread safety -- layered mutex hierarchy (Controller > Pulse > Axis) prevents deadlocks across ESP32 dual cores
- Conditional compilation -- WiFi and Alpaca are opt-in via
#define, keeping the core lean
Serial Protocol
Connect at 57600 baud, 8N1. Commands are terminated with #.
Basic Commands (arduino-st4 compatible)
| Command | Action |
|---|---|
CONNECT# |
Enable mount control, turn on LED |
DISCONNECT# |
Stop all axes, turn off LED |
RA+# |
Slew RA positive |
RA-# |
Slew RA negative |
RA0# |
Stop RA axis |
DEC+# |
Slew DEC positive |
DEC-# |
Slew DEC negative |
DEC0# |
Stop DEC axis |
Extended Commands (enabled by default)
| Command | Response |
|---|---|
PULSE RA+ 500# |
Pulse guide RA+ for 500 ms |
PULSE DEC- 1000# |
Pulse guide DEC- for 1000 ms |
POS?# |
POS 12.345678 45.678901# |
SYNC 12.345 45.678# |
Set position to given RA/DEC |
STATUS?# |
STATUS CONNECTED RA:+:12.345678 DEC:0:45.678901# |
VERSION?# |
VERSION 2026.02.17# |
WebSocket Protocol
Connect to ws://<ip>:81/ws. Commands and state are JSON.
Client to Server
{"cmd":"move","axis":"ra","dir":"+"}
{"cmd":"move","axis":"dec","dir":"-"}
{"cmd":"pulse","axis":"ra","dir":"+","ms":500}
{"cmd":"stop"}
{"cmd":"sync","ra":12.345,"dec":45.678}
{"cmd":"status"}
Server to Client (broadcast)
{
"type": "state",
"connected": true,
"ra": {"active": false, "dir": "+", "pos": 12.345678},
"dec": {"active": true, "dir": "-", "pos": 45.678901}
}
State broadcasts on direction change and every 250 ms during active slew.
ASCOM Alpaca
The Alpaca interface implements the ASCOM Telescope v3 specification, allowing any compatible software to control the mount over HTTP.
- REST API on port 32323 (configurable)
- UDP discovery on port 32227 -- clients auto-detect the device
- CORS enabled for web-based Alpaca clients
Supported Operations
| Operation | Endpoint | Method |
|---|---|---|
| Connect/Disconnect | /api/v1/telescope/0/connected |
PUT |
| Pulse Guide | /api/v1/telescope/0/pulseguide |
PUT |
| Move Axis | /api/v1/telescope/0/moveaxis |
PUT |
| Abort Slew | /api/v1/telescope/0/abortslew |
PUT |
| Sync Position | /api/v1/telescope/0/synctocoordinates |
PUT |
| Get Position | /api/v1/telescope/0/rightascension, declination |
GET |
| Slewing State | /api/v1/telescope/0/slewing |
GET |
| Pulse Active | /api/v1/telescope/0/ispulseguiding |
GET |
PulseGuide Direction Mapping
| Alpaca Direction | Value | ST-4 Mapping |
|---|---|---|
| North | 0 | DEC+ |
| South | 1 | DEC- |
| East | 2 | RA+ |
| West | 3 | RA- |
Enable with #define ST4_ALPACA_ENABLED before including ST4.h, or use the alpaca_server example.
Pin Configuration
Override defaults by defining before #include <ST4.h>:
#define ST4_PIN_RA_PLUS 16 // GPIO for RA positive
#define ST4_PIN_RA_MINUS 17 // GPIO for RA negative
#define ST4_PIN_DEC_PLUS 18 // GPIO for DEC positive
#define ST4_PIN_DEC_MINUS 19 // GPIO for DEC negative
#define ST4_PIN_LED 2 // Status LED
Active logic is configurable per-axis (ACTIVE_HIGH default, ACTIVE_LOW for inverted drivers).
Rate Configuration
Default sidereal rate multipliers (from the original ASCOM driver):
| Axis | Direction | Multiplier | Effective Rate |
|---|---|---|---|
| RA | Plus | 9x | 8x slew + 1x Earth rotation |
| RA | Minus | 7x | 8x slew - 1x Earth rotation |
| DEC | Plus | 8x | Symmetric |
| DEC | Minus | 8x | Symmetric |
Override at runtime:
ST4RateConfig rates = {9.0, 7.0, 8.0, 8.0};
controller.setRates(rates);
Examples
| Example | Features | Build |
|---|---|---|
basic_gpio |
Raw axis control, wiring verification | make basic |
serial_compatible |
Serial protocol, ASCOM driver compatible | make serial |
pulse_guide |
Hardware-timer pulse guiding, position tracking | make pulse |
wifi_control |
WebSocket server, JSON state broadcasting | make wifi |
alpaca_server |
ASCOM Alpaca REST API, UDP discovery | make alpaca |
Dependencies
| Library | Version | Used By |
|---|---|---|
| ArduinoJson | ^7.0.0 | WiFi, Alpaca |
| ESPAsyncWebServer | ^3.6.0 | WiFi, Alpaca |
Core library (serial/GPIO/pulse) has no external dependencies beyond the Arduino ESP32 framework.
Building
Requires PlatformIO.
# Build all examples
make all
# Build specific example
make serial
make wifi
make alpaca
# Run native unit tests (no hardware needed)
make test
# Upload and monitor
pio run -e serial_compatible -t upload -t monitor
# Clean build artifacts
make clean
Native Tests
61 tests across 5 suites run on the host machine without ESP32 hardware. A thin mock layer in test/mocks/ replaces Arduino, FreeRTOS, and esp_timer APIs:
test_pin 9 tests GPIO logic, active-high/low, pin validation
test_axis 10 tests Mutual exclusion, direction control, safety
test_tracker 11 tests Position accumulation, rate changes, timing
test_serial 16 tests Protocol parsing, all commands, edge cases
test_controller 15 tests Rate calculation, connection guard, state
pio test -e native -v
Project Structure
st-4-esp32/
├── include/ Header files
│ ├── ST4.h Facade (includes everything)
│ ├── ST4Types.h Enums, constants, state structs
│ ├── ST4Config.h Default pin assignments
│ ├── ST4Pin.h Single GPIO abstraction
│ ├── ST4Axis.h Dual-pin axis with mutual exclusion
│ ├── ST4Tracker.h Dead-reckoning position tracker
│ ├── ST4Pulse.h Hardware-timer pulse engine
│ ├── ST4Controller.h High-level mount controller
│ ├── ST4Serial.h Serial protocol handler
│ ├── ST4WiFi.h WebSocket server (optional)
│ └── ST4Alpaca.h ASCOM Alpaca REST API (optional)
├── src/ Implementations
├── examples/ 5 Arduino sketches
├── test/
│ ├── mocks/ Arduino/FreeRTOS mock layer
│ └── test_*/ Unity test suites
├── arduino-st4/ Original project reference
├── platformio.ini
├── Makefile
└── library.json
License
LGPL-3.0-or-later
Credits
Based on arduino-st4 by Kevin Ferrare. Sidereal rate constants and ASCOM protocol from the original ArduinoST4 ASCOM driver.