# 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](https://ascom-standards.org/api/) interfaces -- drop-in compatible with the original [arduino-st4](https://github.com/kevinferrare/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 ```cpp #include 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: ```bash 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_timer` one-shot with FreeRTOS task handoff, non-blocking microsecond precision - **Dead-reckoning position tracking** -- port of ASCOM `AxisMovementTracker.cs` using `esp_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://:81/ws`. Commands and state are JSON. ### Client to Server ```json {"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) ```json { "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](https://ascom-standards.org/api/) 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 `: ```cpp #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: ```cpp 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](https://arduinojson.org/) | ^7.0.0 | WiFi, Alpaca | | [ESPAsyncWebServer](https://github.com/mathieucarbou/ESPAsyncWebServer) | ^3.6.0 | WiFi, Alpaca | Core library (serial/GPIO/pulse) has no external dependencies beyond the Arduino ESP32 framework. ## Building Requires [PlatformIO](https://platformio.org/). ```bash # 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 ``` ```bash 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](https://github.com/kevinferrare/arduino-st4) by Kevin Ferrare. Sidereal rate constants and ASCOM protocol from the original ArduinoST4 ASCOM driver.