Skip to content

About

ESP32 Arduino ASCOM Alpaca dome driver for a rolloff observatory roof, with SSD1306 OLED display

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

20 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ESP32 Rolloff Roof — ASCOM Alpaca Driver

An ESP32-based controller for a rolloff observatory roof driven by a garage door opener. Implements the ASCOM Alpaca Dome interface so any Alpaca-compatible astronomy application (N.I.N.A., Cartes du Ciel, etc.) can open, close, and monitor the roof. A built-in web page provides manual control from any browser on the local network.

Roof controller on the bench: round display showing CLOSED, ESP32 on a screw-terminal board, Phidgets SSR


How the roof is driven

This driver is built for a roll-off roof pulled by a standard garage door opener, controlled the same way its wall button controls it: a single momentary contact that both opens and closes. There is no separate open input and close input. Each press cycles the opener through its sequence — move, stop, move back — and the relay imitates one press with a 250 ms pulse on GPIO 18.

Everything unusual about the firmware follows from that one fact:

  • The device cannot command a direction. It can only say "press the button". It infers what the roof is doing from the last command it issued and the two limit switches.
  • AbortSlew pulses only while the roof is actually moving. On a toggle input, pressing the button on a stationary roof would start it, so an abort on a stopped roof deliberately does nothing.
  • Opening an already-open roof does nothing. The commands check the limit switches first, because a stray pulse would close it.
  • The two limit switches are the only ground truth. Between them the roof's position is unknown, which is why a 2-minute movement watchdog exists.

If your opener exposes separate open and close inputs, or accepts a position command, this firmware needs modification rather than configuration.


Features

  • ASCOM Alpaca Dome driver (IDomeV2, interface version 2) on port 11111
  • Alpaca UDP auto-discovery on port 32227 — no manual IP entry needed in N.I.N.A.
  • Browser control/status page on port 80, plus a diagnostics page at /diag
  • 1.28″ GC9A01 round TFT (240×240) showing shutter status, IP address and whether Alpaca is polling
  • Two limit switches (OPEN and CLOSED positions)
  • Relay pulse (250 ms) to toggle the motor controller
  • 2-minute movement watchdog — sets error state if a limit switch is never reached
  • WiFi credentials kept in a gitignored file — safe to push to GitHub

Reliability

The roof has to work unattended overnight, so several failure modes are handled explicitly:

  • Persistent HTTP connections on the Alpaca port. N.I.N.A. polls a dome several times a second. Arduino's WebServer closes the connection after every response, and lwIP holds each closed socket for about 140 seconds, so the device can only sustain roughly 7 new connections a minute. Port 11111 therefore runs on ESP-IDF's esp_http_server, which keeps the connection open — an entire session costs one connection instead of thousands. The status page reports requests per connection so you can confirm it.
  • Hardware task watchdog. The chip resets if loop() stops running.
  • WiFi supervisor. Notices a dropped association from driver events and runs connect passes (see Access point selection and roaming), rebooting after 90 s down, boot included. It never reboots while the roof is moving.
  • Roam watch. If RSSI stays below −72 dBm on every 60 s sample for 5 min, the device scans while still connected and roams only to an AP at least 10 dB stronger, timed just after a client poll, with a 15 min cooldown.
  • Poll-stall supervisor. Catches the failure the WiFi supervisor can't see: the association stays up, but traffic to this one device blackholes and the client's polls time out. (The RG-11 safety monitor lost N.I.N.A. this way twice in one night with nothing in its log.) Once a client has proved itself live — PUT Connected=true or a status poll — 35 s of silence on the Alpaca API logs polls stopped (link up), 70 s forces a re-association, and 10 min disarms so a N.I.N.A. that was simply closed doesn't cause reconnect flapping overnight. PUT Connected=false disarms immediately. Silence is timed from the later of the last poll and the link coming up, so a WiFi recovery doesn't trigger a second forced disconnect. The armed flag and stall count live in RTC memory, so an outage that spans a self-reboot is still logged. The status page shows the last poll age and stall count, and the display's Alpaca:OK turns to Alpaca:-- after 30 s of silence.
  • Diagnostics that survive a reboot. The last 24 events are logged to RTC memory, which outlives a watchdog reset: boot, WiFi lost (802.11 reason code decoded), WiFi connected (BSSID, channel, RSSI), connect failed (BSSID and reason code, or timeout), roamed (from → to), roam skipped (with the reason), self-reboot, roof commands, polls stopped (link up), polls resumed (with the gap length), client presumed gone, and client disconnected (Connected=false). An identical roam skipped entry is logged at most once an hour. A power cycle clears the log deliberately, so the counters always mean "since mains was applied".
  • The display is optional. The SPI link to the panel is write-only, so the firmware simply draws whether or not a display is plugged in. An IPS TFT does not burn in, so unlike the earlier OLED it needs no drift or idle-blank tricks.

Access point selection and roaming

Several UniFi APs broadcast the same SSID. The ESP32 default is a fast scan that joins the first match in channel order and never roams. That left the RG-11 safety monitor in the same building on a distant AP at −74 to −81 dBm while the observatory AP was at −49 dBm, until N.I.N.A.'s polls timed out. This firmware uses the same fix as the safety monitor:

  • Connect: every connect (boot, drop, stall-forced, roam) scans all channels, then tries each BSSID strongest-first, directly on its channel, then falls back to a plain connect. APs below the minimum RSSI are only tried after every stronger one has refused, so the device still gets online if they are all that's left. An AP that refuses a connect (for example, UniFi Lock to AP pinned elsewhere) is tried last for the next hour and isn't used as a roam target.
  • Roam: a link only counts as weak after staying below the trigger on every sample for the sustain period. The scan runs while still connected, and the switch waits for the gap just after a client poll, so N.I.N.A.'s next poll lands on the new AP.
  • Never while the roof moves: no roam scan, roam, stall-forced re-association or reboot starts while the roof is opening or closing. A roam that was ready when the roof started is dropped (logged as roam skipped — roof started moving) and re-evaluated once the roof stops. A drop that happens mid-travel still reconnects, and none of this blocks loop(), so the relay, limit switches and movement timeout are unaffected.

The core's auto-reconnect is off: it reuses the last config, which may pin a BSSID that is refusing the device.

All thresholds are in one block near the top of src/main.cpp, and each can be overridden with a -D build flag:

Setting Default Meaning
WIFI_MIN_RSSI −75 dBm Weaker APs are tried only after every stronger one refused
ROAM_TRIGGER_RSSI −72 dBm Consider roaming only below this
ROAM_SUSTAIN_MS 300000 (5 min) ...on every sample for this long
ROAM_HYSTERESIS_DB 10 dB Target must beat the current AP by this much
ROAM_COOLDOWN_MS 900000 (15 min) No roam attempt this soon after the last one
ROAM_CHECK_INTERVAL_MS 60000 (60 s) RSSI sample period

[env:roamtest] in platformio.ini sets test values that force a roam on the bench: pio run -e roamtest -t upload. Flash esp32dev again afterwards. A plain pio run -t upload only ever builds esp32dev.


Hardware

Part Description
ESP32 DevKit-C 38-pin, USB-C (ESP32-WROOM-32)
Display 1.28″ GC9A01 round TFT, 240×240, SPI, 7-pin header (VCC GND SCL SDA DC CS RST)
Limit switch ×2 Normally-open (NO) momentary switches
Relay module Phidgets 3054 SSR (500 mA, 40 VDC / 28 VAC), triggered by a logic-HIGH pulse. Its output wires across the garage door opener's wall-button terminals, in parallel with the existing button
Garage door opener Any opener driven by a single momentary contact that opens, stops, and closes in sequence

Phidgets 3054 solid-state relay: Control terminals from the ESP32, Load terminals to the opener's wall-button input


Wiring

ESP32 DevKit-C pin layout (USB-C end at top)

LEFT SIDE                              RIGHT SIDE
─────────────────────────────────      ─────────────────────────────
3V3  ── TFT VCC ┐                      VIN
GND  ── TFT GND │                      GND  Limit sw common (both)
D15  ── TFT SCL │ display header       D13
D2   ── TFT SDA │ seats directly on    D12
D4   ── TFT DC  │ these seven pins     D14
RX2  ── TFT CS  │                      D27
TX2  ── TFT RST ┘                      D26
D5                                     D25
D18 (GPIO18) ── Relay signal           D33 (GPIO33) ── Limit sw OPEN
D19                                    D32 (GPIO32) ── Limit sw CLOSED
D21                                    D35
RX0                                    D34
TX0                                    VN
D22                                    VP
D23                                    EN
GND  ── Relay GND

Wiring – GC9A01 display

The display seats directly onto the first seven pins of the left rail — no jumpers. Its header order matches the board silkscreen one-to-one, the same arrangement used on the RG-11 safety monitor.

The module labels its SPI pins with I²C names: SCL is SCK and SDA is MOSI. It is SPI, not I²C. There is no BLK pin, so the backlight is permanently on.

Display pin  →  ESP32 header pin  (GPIO)
──────────────────────────────────────────
VCC          →  3V3
GND          →  GND
SCL          →  D15              (GPIO 15, SCK)
SDA          →  D2               (GPIO 2,  MOSI)
DC           →  D4               (GPIO 4)
CS           →  RX2              (GPIO 16)
RST          →  TX2              (GPIO 17)

Pin numbers are set in platformio.ini as TFT_eSPI build flags, so no User_Setup.h edit is needed and the config travels with the repo. GPIO 2 also drives the DevKit's onboard LED, so it flickers with SPI traffic — harmless. GPIO 2 and 15 are strapping pins, but the display is a high-impedance input on those lines, so flashing and booting are unaffected.

Connection summary

ESP32 pin Signal Notes
GPIO 32 (D32) Limit switch — CLOSED Pull-up enabled; switch other terminal → GND
GPIO 33 (D33) Limit switch — OPEN Pull-up enabled; switch other terminal → GND
GPIO 18 (D18) Relay signal Pulses HIGH for 250 ms, imitating one press of the opener's wall button
GPIO 15 (D15) TFT SCL (SPI clock) Display seats on the first seven left-rail pins
GPIO 2 (D2) TFT SDA (SPI MOSI)
GPIO 4 (D4) TFT DC
GPIO 16 (RX2) TFT CS
GPIO 17 (TX2) TFT RST
3V3 TFT VCC
GND TFT GND, relay GND, switch common

The two limit-switch GPIOs (D32/D33) are adjacent on the right rail.


Software setup

1. Clone the repository

git clone https://github.com/exploded/ESP32-Rolloff.git
cd ESP32-Rolloff

2. Create your WiFi credentials file

cp include/wifi_credentials.h.example include/wifi_credentials.h

Edit include/wifi_credentials.h and fill in your SSID and password:

#define WIFI_SSID "YourSSID"
#define WIFI_PASS "YourPassword"

wifi_credentials.h is listed in .gitignore and will never be committed.

3. Build and flash

Open the project in PlatformIO (VS Code extension or CLI) and upload:

pio run --target upload

Required libraries (installed automatically by PlatformIO via platformio.ini):

  • bodmer/TFT_eSPI (configured by the build_flags block in platformio.ini)

Usage

Browser

Open http://<ESP32-IP>/ in any browser on the same network. The page shows shutter status, switch states, link health (including the access point's BSSID, channel and RSSI, and the roam watch state), and Open / Close buttons. It polls a small JSON endpoint every 10 seconds rather than reloading itself, so leaving a tab open costs the device very little.

http://<ESP32-IP>/diag shows the reset reason, boot count, and the event log described under Reliability.

Path Port Purpose
/ 80 Status and control page
/status.json 80 Status as JSON, polled by the page
/cmd?a=open|close|abort 80 Same-origin roof control used by the buttons
/diag 80 Diagnostics and event log
/api/v1/dome/0/… 11111 ASCOM Alpaca Dome API
/management/… 11111 Alpaca management API

Monitoring

scripts/monitor-rolloff.ps1 logs availability and the device's own counters to a CSV. It is read-only and cannot move the roof. Run it on the observatory PC and leave it going:

.\scripts\monitor-rolloff.ps1 -Ip <ESP32-IP>

It probes once a minute by design. Do not speed it up — a faster prober becomes a significant part of the load it is trying to measure.

ASCOM Alpaca (N.I.N.A., Cartes du Ciel, etc.)

The device advertises itself via Alpaca UDP discovery — most clients will find it automatically. If you need to enter it manually:

Setting Value
IP address assigned by your router (shown on the display)
Alpaca port 11111
Device type Dome
Device number 0

Serial monitor

Connect at 115200 baud to see startup messages including the assigned IP address.


Display

The round panel uses the same scheme as the RG-11 safety monitor, so the two read alike side by side: a coloured ring at the rim around a black centre.

        ╭───────────────╮
      ╭─┤    coloured   ├─╮      ring + headline colour:
     ╱  ╰───────────────╯  ╲       green  OPEN
    │         OPEN          │      blue   CLOSED
    │     192.168.1.38      │      amber  OPENING / CLOSING
    │   Alpaca:OK  T:1234   │      red    ERROR
     ╲ -62dBm ch6  up 318m ╱
      ╰───────────────────╯
Line Meaning
Headline Shutter status: OPEN / CLOSED / OPENING / CLOSING / ERROR
IP address Or WiFi lost 42s while the supervisor is reconnecting
Alpaca:OK / Alpaca:-- A client polled port 11111 within the last 30 s — shows on the wall when N.I.N.A.'s polls stop reaching the device. T: is the Alpaca request count since boot
Footer WiFi RSSI, channel and uptime in minutes

The whole frame is drawn into an off-screen sprite and pushed in one go, so it refreshes twice a second without flicker. Boot progress (WiFi connecting..., Restarting) is shown as a two-line message.


Ports

Port Protocol Purpose
80 TCP/HTTP Browser status + control page
11111 TCP/HTTP ASCOM Alpaca Dome API
32227 UDP Alpaca auto-discovery

GPIO summary

GPIO Direction Function
2 Output TFT SDA (SPI MOSI) — also the onboard LED
4 Output TFT DC
15 Output TFT SCL (SPI clock)
16 Output TFT CS
17 Output TFT RST
18 Output Relay pulse (HIGH = active)
32 Input Limit switch CLOSED (active LOW)
33 Input Limit switch OPEN (active LOW)

About

ESP32 Arduino ASCOM Alpaca dome driver for a rolloff observatory roof, with SSD1306 OLED display

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages