Firmware (Arduino Nano ESP32 / PlatformIO): - Native ASCOM Alpaca CoverCalibrator REST API on port 11111 - Alnitak serial protocol on USB at 9600 baud (simultaneous with WiFi) - MG995 servo cover mechanism with non-blocking state machine (D9) - LED brightness PWM via IRLZ44N MOSFET, LEDC channel 0 (D3) - 12V dew heater PWM via IRLZ44N MOSFET, LEDC channel 1 (D5) - mDNS + UDP Alpaca discovery, WiFi watchdog reconnect - SERIAL_DEBUG flag to silence debug output in USB-only mode INDI driver (C++ / libcurl / nlohmann-json): - WiFi mode: HTTP Alpaca via libcurl - USB mode: Alnitak serial via POSIX termios - LightBoxInterface + DustCapInterface + dew heater number property Python controller (PyQt6): - Dark-themed desktop app for direct manual control - AlpacaBackend (requests) + AlnitakBackend (pyserial) - PollWorker QThread; cover, brightness, dew heater panels - QSettings persistence; auto serial port discovery Docs: - system-diagram.svg, wiring-diagram.svg (browser-renderable SVG) - BUILD_NOTES.md with BOM, LM2596 calibration, power-on checklist - WIRING.md quick-reference, README.md, CHANGELOG.md
9.6 KiB
ESP32 FlatPanel
An open-source automated telescope flat-field panel built around the Arduino Nano ESP32.
Controls LED brightness, a motorised cover, and a dew heater — all accessible from ASCOM, INDI, and a standalone desktop application.
Features
| Feature | Detail |
|---|---|
| LED brightness | 8-bit PWM (0–255) via logic-level MOSFET |
| Motorised cover | MG995 servo rotates panel in/out of the telescope light path |
| Dew heater | 12V resistive element, 0–100% PWM — keeps the diffuser clear |
| WiFi mode | Native ASCOM Alpaca REST API on port 11111; auto-discovered via mDNS and UDP broadcast |
| USB mode | Alnitak serial protocol at 9600 baud — plug-and-play with N.I.N.A., SGPro, APT and indi_flipflat |
| Both simultaneously | WiFi and USB are active at the same time in a single firmware build |
| ASCOM | Use ASCOM Remote (free, official) — no custom COM driver required |
| INDI | Custom indi_esp32_flatpanel driver with WiFi and USB connection modes |
| Desktop app | Dark-themed PyQt6 controller for direct manual operation |
Repository layout
flatpanel/
├── firmware/ ESP32 firmware (PlatformIO / Arduino)
│ ├── platformio.ini
│ └── src/
│ ├── config.h ← edit this first
│ └── main.cpp
├── indi-driver/ INDI driver (C++, Linux)
│ ├── CMakeLists.txt
│ ├── indi_esp32_flatpanel.h
│ ├── indi_esp32_flatpanel.cpp
│ └── indi_esp32_flatpanel.xml
├── controller/ Python desktop controller (all platforms)
│ ├── flatpanel_controller.py
│ └── requirements.txt
├── docs/
│ ├── system-diagram.svg Block diagram — open in any browser
│ ├── wiring-diagram.svg Colour-coded wiring — open in any browser
│ └── BUILD_NOTES.md Step-by-step assembly guide
├── WIRING.md Quick-reference wiring + BOM
├── README.md This file
└── CHANGELOG.md
Hardware required
| Component | Role | Pin |
|---|---|---|
| Arduino Nano ESP32 | Main controller | — |
| MG995 servo | Cover open/close | D9 |
| IRLZ44N MOSFET × 2 | LED brightness + dew heater | D3, D5 |
| LM2596 buck module | 12V → 5V for ESP32 + servo | — |
| 12V PSU ≥ 4A | Powers everything | — |
| White LED strip 12V (Ra ≥ 90) | Flat-field illumination | — |
| Frosted acrylic 3mm | Light diffuser | — |
| Dew heater element 12V ~24Ω | Anti-dew resistive heater | — |
| 100Ω resistor × 2 | MOSFET gate series | — |
| 10kΩ resistor × 2 | MOSFET gate pull-down | — |
See docs/BUILD_NOTES.md for the full BOM, dew heater sizing table, and step-by-step assembly instructions.
See docs/wiring-diagram.svg for the colour-coded wiring diagram.
Quick-start
1 Configure the firmware
Edit firmware/src/config.h:
#define WIFI_SSID "your_network"
#define WIFI_PASSWORD "your_password"
// Tune to your mechanical stops after assembly:
#define SERVO_CLOSED 0 // panel in flat position
#define SERVO_OPEN 90 // panel clear of telescope
// Set false when using USB/Alnitak mode to keep the serial line clean:
#define SERIAL_DEBUG true
2 Flash
cd flatpanel
pio run -t upload # PlatformIO CLI
# or use the PlatformIO IDE extension in VS Code
3 Verify
Open a serial monitor at 9600 baud.
You should see the ESP32's IP address printed on boot.
Browse to:
http://esp32-flatpanel.local:11111/management/v1/configureddevices
Expected response contains "DeviceType":"CoverCalibrator".
Connection modes
WiFi — ASCOM Alpaca (no driver needed)
The ESP32 is an Alpaca server. Use ASCOM Remote as the bridge to ASCOM COM:
- Install ASCOM Platform 6.6+ and ASCOM Remote.
- In ASCOM Remote, add a CoverCalibrator device → host
esp32-flatpanel.local, port11111, device0. - In N.I.N.A. / SGPro / APT, choose ASCOM CoverCalibrator and select the ASCOM Remote device.
The ESP32 is also discovered automatically by software that supports Alpaca UDP discovery (N.I.N.A. 3+, Cartes du Ciel).
WiFi — INDI
# Build and install the custom driver (once)
cd flatpanel/indi-driver
cmake -B build -DCMAKE_INSTALL_PREFIX=/usr
cmake --build build -j$(nproc)
sudo cmake --install build
# Start (KStars/Ekos loads it automatically after install)
indiserver indi_esp32_flatpanel
In Ekos: select ESP32 FlatPanel, set Connection Mode to WiFi / Alpaca, enter the host and port.
USB — Alnitak (plug-and-play)
Set SERIAL_DEBUG false in config.h and reflash. Then connect via USB-C.
The firmware speaks the standard Alnitak protocol so existing software works without any driver:
| Software | Built-in support |
|---|---|
| N.I.N.A. | ✅ Alnitak / Flip-Flat |
| Sequence Generator Pro | ✅ Alnitak |
| Astro Photography Tool | ✅ Alnitak |
INDI (indi_flipflat) |
✅ ships with INDI |
| Our INDI driver | ✅ USB mode in Connection tab |
Dew heater via USB: The standard Alnitak protocol has no dew heater concept.
Our INDI driver and the Python controller use custom extension commands>T(set) />U(get) that are silently ignored by other software.
Desktop controller app
Works in both WiFi and USB mode. Dark-themed for night use.
pip install PyQt6 requests pyserial
python flatpanel/controller/flatpanel_controller.py
Alpaca API reference
Base URL: http://<host>:11111/api/v1/covercalibrator/0/
| Method | Endpoint | Parameters | Description |
|---|---|---|---|
| GET | coverstate |
— | 2=Open, 3=Closed, 4=Moving |
| GET | calibratorstate |
— | 1=Off, 3=Ready |
| GET | brightness |
— | Current brightness 0–255 |
| GET | maxbrightness |
— | Always 255 |
| GET | dewheater |
— | Current dew heater 0–100% (custom) |
| PUT | opencover |
— | Rotate panel out of light path |
| PUT | closecover |
— | Rotate panel into flat position |
| PUT | haltcover |
— | Stop cover movement |
| PUT | calibratoron |
Brightness=N |
Turn on LED at brightness N |
| PUT | calibratoroff |
— | Turn off LED |
| PUT | dewheater |
Percentage=N |
Set dew heater 0–100% (custom) |
| PUT | action |
Action=SetDewHeaterParameters=N |
Alpaca-standard alternative to PUT dewheater |
| PUT | action |
Action=GetDewHeater |
Returns current dew % as string Value |
Management endpoints: /management/apiversions, /management/v1/description, /management/v1/configureddevices
Alnitak serial protocol reference
Port: USB-C at 9600 baud, 8N1.
Command format: >CXXX\r · Response format: *CDDVVV\r (DD = device type 19).
| Cmd | Description | Example |
|---|---|---|
>P000 |
Ping | *P19000 |
>S000 |
State (motor + light) | *S19 + motor(1) + light(0) + 00 |
>B128 |
Set brightness 0–255 | *B19128 |
>J000 |
Get brightness | *J19128 |
>L000 |
Light on (keep current brightness) | *L19000 |
>D000 |
Light off | *D19000 |
>O000 |
Open cover | *O19000 |
>C000 |
Close cover | *C19000 |
>H000 |
Halt cover | *H19000 |
>T050 |
Set dew heater 50% (custom) | *T19050 |
>U000 |
Get dew heater % (custom) | *U19050 |
Motor state in >S000 response: 0=unknown/moving · 1=closed · 2=open
Light state: 0=off · 1=on
Pin assignments
| D-pin | GPIO | Function | Config define |
|---|---|---|---|
| D9 | — | MG995 servo signal | SERVO_PIN |
| D3 | — | LED MOSFET gate | LED_PWM_PIN |
| D5 | — | Dew heater MOSFET gate | DEW_HEATER_PIN |
All pins use the Arduino Nano ESP32 D-number scheme.
LEDC channel 0 → LED · channel 1 → dew heater.
Building the INDI driver
Dependencies: libindi-dev, libcurl4-openssl-dev, nlohmann-json3-dev, cmake
sudo apt install libindi-dev libcurl4-openssl-dev nlohmann-json3-dev cmake build-essential
cd flatpanel/indi-driver
cmake -B build -DCMAKE_INSTALL_PREFIX=/usr
cmake --build build -j$(nproc)
sudo cmake --install build
If nlohmann-json3-dev is unavailable, CMake will fetch it automatically via FetchContent.
Troubleshooting
| Symptom | Fix |
|---|---|
| No boot message on Serial | Check VIN wiring; try USB-C direct to PC (bypasses LM2596) |
| Servo twitches constantly | LM2596 sagging; re-measure 5V under servo load |
| LED stays on at 0% | Missing 10kΩ gate pull-down on MOSFET-A |
| Alnitak commands ignored | Set SERIAL_DEBUG false, reflash |
Connection refused on port 11111 |
ESP32 not yet connected to WiFi; check credentials |
| Dew heater not warming | Verify MOSFET-B drain voltage ≈ 0V when active; check element resistance |
| Python app import error | pip install PyQt6 requests pyserial |
| INDI build fails | sudo apt install libindi-dev |
Diagrams
| File | Description |
|---|---|
| docs/system-diagram.svg | Architecture block diagram |
| docs/wiring-diagram.svg | Colour-coded wiring diagram |
Open either file in a web browser — no additional software needed.
Licence
Released under the MIT Licence — see LICENCE file.
ASCOM and Alpaca are trademarks of the ASCOM Initiative.
INDI is maintained by the INDI developers at indilib.org.