ASCOM Alpaca compatible roll-off roof controller for remote observatory automation, built on ESP32-S3 with MQTT integration, weather safety interlock, and a browser-based control interface.
Relays on this board carry low-voltage control signals only. They trigger external contactors -- never connect motors, inverters, or other high-current loads directly to the relay outputs. Use appropriately rated external contactors for all high-power switching.
- Non-blocking state machine sequences relay operations without interrupting WiFi/MQTT
- Open, close, and stop via ASCOM, MQTT, or web interface
- Limit switch detection with configurable trigger state and swap option
- Two independent configurable timeouts: limit switch response (default 5s) and total movement (default 90s)
- Park sensor: blocks roof movement when telescope is unparked. Supports physical GPIO, UDP network sensors, or both (AND logic)
- Weather/sky safety: receives UDP broadcasts from safety sensor devices; blocks roof opening when any enabled sensor reports unsafe conditions. Fail-safe: an enabled sensor that goes offline is treated as unsafe
- Auto-close: optionally closes the roof automatically when weather becomes unsafe (requires telescope parked)
- All safety interlocks have bypass toggles accessible from both the Setup and Roof Control pages
- K1: 12V power relay (triggers external contactor)
- K3: Soft-power button relay (500ms pulse)
- AC power detection via optocoupler distinguishes relay state from actual inverter output
- Manual controls available from Roof Control page
- Dark theme, responsive layout, 2-second live status polling
- Consistent navigation on all pages: Home, Roof Control, Setup, WiFi Config, Console, Update
- Console page: live ring-buffer debug log (50 entries), accessible remotely without serial monitor
- Park sensor and safety sensor management: per-sensor enable, bypass, and remove
- OTA firmware updates via ElegantOTA (auto-reboot after upload)
- Publishes status every 30 seconds and on state change
- Accepts open/close/stop commands
- LWT availability topic
- Status payload includes weather safety state and safety bypass flag
- Optional GPS module for time sync (configurable pins)
- DS3231 RTC backup (I2C)
- NTP server mode: serves time to local network devices
- PPS signal support for sub-second NTP accuracy
- Configurable timezone offset and DST
Microcontroller: ESP32-S3 44-pin
Power: 12V DC input, onboard 5V regulator
| Pin | Function |
|---|---|
| GPIO4 | K1 - Inverter power relay |
| GPIO5 | K2 - Roof button relay |
| GPIO6 | K3 - Inverter soft-power relay |
| GPIO7 | AC power detection (optocoupler) |
| GPIO8 | I2C SDA (RTC) |
| GPIO9 | I2C SCL (RTC) |
| GPIO35 | Open limit switch |
| GPIO36 | Closed limit switch |
| GPIO37 | Rain sensor (RG9) |
| GPIO38-41 | Snow sensor (digital + RS485) |
| GPIO42 | Physical park sensor |
GPS pins are configurable via the web interface (default TX: GPIO14).
- Arduino IDE 2.0+
- Board: ESP32-S3 Dev Module
- Libraries (via Library Manager): ArduinoJson 6.x, PubSubClient, ElegantOTA
| Setting | Value |
|---|---|
| USB CDC On Boot | Enabled |
| Flash Size | 4MB |
| Partition Scheme | Default 4MB with SPIFFS |
| Upload Speed | 921600 |
| USB Mode | Hardware CDC and JTAG |
- Device starts in AP mode: SSID
RoofController, passwordRoofController - Connect and open
http://192.168.4.1 - Enter WiFi credentials under WiFi Config
- Device reboots and connects to your network
All settings are saved to NVS (survives reboots) and configurable via the web interface.
Setup page (/setup): pin assignments, trigger states, timeout values, MQTT broker, park sensor type, safety sensor enable/bypass, GPS/NTP settings, timezone
WiFi Config (/wificonfig): SSID and password
Roof Control (/control): park sensor bypass and safety sensor bypass toggles accessible here for quick override without navigating to setup
Default topic prefix: observatory/roof
| Topic | Direction | Description |
|---|---|---|
<prefix>/status |
Publish | JSON status (30s interval + on change) |
<prefix>/command |
Subscribe | {"command":"open|close|stop"} |
<prefix>/availability |
Publish | online / offline (LWT) |
{
"status": "Closed",
"telescope_parked": true,
"bypass_enabled": false,
"weather_safe": true,
"safety_bypass": false,
"inverter_relay_state": false,
"inverter_ac_power_state": false,
"limit_open": false,
"limit_closed": true,
"device_id": "ESP32-XXXXXX",
"ip_address": "192.168.1.100",
"version": "3.3.0"
}mqtt:
cover:
- name: "Observatory Roof"
command_topic: "observatory/roof/command"
state_topic: "observatory/roof/status"
payload_open: '{"command":"open"}'
payload_close: '{"command":"close"}'
payload_stop: '{"command":"stop"}'
value_template: "{{ value_json.status }}"
state_open: "Open"
state_closed: "Closed"
availability_topic: "observatory/roof/availability"
binary_sensor:
- name: "Weather Safe"
state_topic: "observatory/roof/status"
value_template: "{{ value_json.weather_safe }}"
payload_on: true
payload_off: false- Discovery: UDP port 32227, responds to
alpacadiscovery1 - API base:
http://<device-ip>:11111/api/v1/dome/0/ - Device type: Dome (RollOffRoof)
| Method | Description |
|---|---|
GET shutterstatus |
0=Open, 1=Closed, 2=Opening, 3=Closing, 4=Error |
PUT openshutter |
Open roof (checks park + weather safety) |
PUT closeshutter |
Close roof |
PUT abortslew |
Stop movement |
GET athome |
True when closed |
GET atpark |
True when open |
| Endpoint | Method | Description |
|---|---|---|
/ |
GET | Home / status page |
/control |
GET | Roof control page |
/setup |
GET/POST | Configuration page |
/console |
GET | Debug log viewer |
/console.json |
GET | Raw log JSON |
/api/status |
GET | Live status JSON |
/open |
POST | Open roof |
/close |
POST | Close roof |
/stop |
POST | Stop movement |
/toggle_bypass |
POST | Toggle park sensor bypass |
/toggle_safety_bypass |
POST | Toggle safety sensor bypass |
/safety_sensor_enabled |
POST | Enable/disable a safety sensor |
/safety_sensor_bypass |
POST | Bypass a safety sensor |
/safety_sensor_remove |
POST | Remove a safety sensor |
/inverter_toggle |
POST | Toggle K1 power relay |
/inverter_button |
POST | K3 soft-power button press |
/update |
GET | OTA firmware update |
/restart |
POST | Reboot device |
The PCB Files/ directory contains v3.0 manufacturing files: schematic, layout, Gerbers, BOM, pick-and-place, and 3D model. Designed for JLCPCB, PCBWay, or equivalent.
- Safety sensor integration: weather/sky condition interlock via UDP broadcasts
- Auto-close on unsafe weather (configurable, requires telescope parked)
- Safety sensor bypass controls on Setup and Roof Control pages
- Weather safe status and safety bypass state added to MQTT and
/api/status - Unified navigation bar across all pages with Console link
- Web-based debug console at
/console(ring buffer, no serial monitor needed) - Toggle switches replace checkboxes in safety sensor table
- Per-sensor remove and remove-all for both park and safety sensors
- Park sensor type saves immediately on selection (no Apply button)
- ElegantOTA auto-reboot fix
- HTML entity encoding for all non-ASCII characters
- GPS/RTC/NTP support with PPS signal for sub-second accuracy
- DS3231 RTC backup with I2C detection
- NTP server mode for local network devices
- Timezone and DST configuration
- Migrated to ESP32-S3 (44-pin)
- Triple relay system (K1, K2, K3)
- AC power detection via optocoupler
- Snow sensor input (digital + RS485)
- Dual inverter state tracking (relay vs AC power)
- Non-blocking roof state machine
- Dual movement timeout system
- Initial release: ESP32, ASCOM Alpaca, MQTT, UDP park sensors
MIT License. See LICENSE for details.
Author: Corey Smart