Notice: Future versions of this firmware are released as ready-to-flash web installer in the Printed Droid Firmware Flasher. The documentation (commands, usage, settings) will continue to be updated here and in the wiki; the source code in this repository remains available in its current state but will no longer be updated.
Why: Printed Droid has shared its source code openly for years. In the meantime, more and more projects in the community build on openly shared work but release their own results as closed source only. Open source works in both directions – this one-way street is not something we will keep supplying.
ESP8266/ESP32/ESP32-S3 based animatronics controller for Star Wars Huyang droid builds (DroidDivision)
This animatronics controller brings your Huyang droid to life with animated TFT eyes, servo-driven neck and body movement, a web interface, serial CLI, and voice assistant integration. Designed for the DroidDivision Huyang build with the Printed-Droid PCB.
- Multi-Board Support - ESP8266, ESP32, ESP32-S3 with automatic pin selection
- Animated TFT Eyes - Two GC9A01 round displays with 6 eye expressions (open, closed, blink, focus, sad, angry)
- 9-Servo Motion Control - Head tilt/rotation + body tilt/rotation via PCA9685 PWM driver
- WiFi Web Interface - Complete control via responsive web UI from any browser
- Voice Assistant Control - Google Home, Alexa & Siri integration with 11 trigger commands
- Serial CLI - Complete command-line interface for configuration and control
- EEPROM Configuration - Persistent settings with CRC-32 checksum validation and wear leveling
- Automatic Animations - Random eye expressions and head movement
- Chest Lights - NeoPixel LED strip for torso illumination
- Audio System - DFPlayer Mini for sound playback (prepared)
- WiFi Fallback - STA mode with automatic AP hotspot fallback
- mDNS Support - Access via
http://huyang.local
- Per-channel servo calibration in EEPROM — every PCA9685 channel (0-15) gets its own
min/center/maxPWM. Asymmetric mapping (0° → minPWM, 90° → centerPWM, 180° → maxPWM) lets you handle linkages whose mechanical centre is not midway between end-stops. - New Calibration Page with live PWM sliders per channel, Go Min / Center / Max buttons, Copy from channel, plus Export / Import JSON for backup.
- New CLI commands:
cal show,cal set <ch> <min> <ctr> <max>(replaces oldcal neck rotation/tiltforward/tiltsideways). - Web Basic Auth, MQTT, and AP Hotspot credentials are now runtime-editable on the Settings page — no re-flash needed. Compile-time defines in
config.hare only first-boot defaults.- Passwords are never returned by
/config/get(only an*PasswordSetboolean). - Empty password fields in
/config/setpreserve the existing value (so editing one setting doesn't wipe others).
- Passwords are never returned by
- Persistent appearance + audio settings — eye color, closed-eye color, pupil enabled / color / size, servo speed preset, volume now survive reboot.
- Monocle as a sequence action — JSON action name
"monocle",param1 = 0..180 deg. - HuyangBody cleanup — removed hardcoded calibration shifts; full -100..100 → 0..180° mapping with per-channel EEPROM calibration.
- EEPROM schema bumped to v6 — old config is discarded on first boot of v2.9.2.
- Bug fix: custom-sequence Save no longer scrolls to top (the editor JS was moved out of the inline
<script>block).
- Per-channel servo calibration introduced — 16 channels ×
centerPWM/minPWM/maxPWM(96 bytes) in EEPROM, replacing the global SERVOMIN/SERVOMAX constants. Schema bumped to v4. Default values match the old globals, so behavior is unchanged until you edit them. - Asymmetric mapping in
HuyangBody::rotateServo/HuyangNeck::rotateServo: 0° →minPWM, 90° →centerPWM, 180° →maxPWM(handles linkages whose neutral isn't midway). - Calibration page rewritten — per-channel card with three sliders, live drag-to-test (PWM hits the servo immediately), per-channel Save button. All 16 channels shown; the 10 used channels get function labels, the 6 unused ones are dimmed.
- New endpoints:
GET /cal/get,GET /cal/set?ch=N&min=¢er=&max=. - Monocle servo (CH 4) wired in:
HuyangNeck::setMonocle(degree)+GET /monocle?deg=0-180. Previously declared in firmware but not driven. - Bug fix: clicking Save in the custom-sequence editor scrolled to top instead of saving. Root cause:
innerHTMLdoesn't execute inline<script>blocks in lazy-loaded fragments, so the handler wasundefined. Functions moved intojavascript.js.
- UI assets embedded in PROGMEM (WebAssets.h, auto-generated via
gen-webassets.ps1) - LittleFS dependency completely removed
- Custom-sequence storage moved from LittleFS to EEPROM
- HuyangConfig: schema version 3, EEPROM size 1024, customSeqBlob[224]
- End-user workflow: flash the sketch only, no LittleFS plugin needed
- Flash grows by ~67 KB (UI assets), no extra RAM usage
- Eye-color presets (Huyang, Sith, Jedi, K-2SO, White, Toxic)
- Pupil size as a slider (6-80 px)
- Closed-eye color configurable
- Pupil idle drift (pupil wanders in automatic mode)
- Volume mute button
- Servo speed presets (slow/normal/fast)
- Self-test sequence + /test/run endpoint
- Sequence progress bar in the status bar
- Custom-sequence editor (JSON editor, moved to EEPROM in v2.9.2)
- Fullscreen / kiosk mode
- Heap monitor in the status bar (warns when <8 KB)
- Settings page fully functional (feature flags, backup/restore, reboot, factory reset)
- Calibration page functional (PCA9685 channel sliders 150-595)
- API documentation page at /api
- WiFi scan + selector in the web UI
- MQTT Home Assistant auto-discovery
- Simple CSRF protection on state-changing GETs
- Eye color only tints the eye preview circles, not the UI accent
- Light theme as an option (toggle button, persisted in localStorage)
- Pupil as an optional feature (on/off via checkbox, color via color picker, default black)
- HuyangFace::setPupil* APIs
- Endpoint /eye/pupil
- Footer: "Made with heart by Jeanette Mueller — Enhanced by Printed-Droid"
- CSS rewritten from scratch (mobile-first responsive, max-width 720px)
- Card-based layout, compact fonts (14-15 px)
- Modern dark theme via CSS custom properties
- User-configurable eye color via color picker
- HuyangFace::setEyeColor APIs + /eye/color endpoint
Web-UI Polish & Multi-Client
- Joystick bug fixed (JoyNeck was calling sendBodyUpdate - present since v1.9)
- Throttle on joystick/slider (80 ms, instead of >20 POSTs/s)
- New audio UI (track input, play, stop, volume slider)
- New sequence UI (greeting/surprised/sad/angry buttons)
- Status bar fixed at the top: WiFi mode, IP, MQTT, sequence playback
- Error handler with visual feedback (instead of silent hang on network problems)
- Multi-client servo state shown below joysticks (text display)
- Settings link made visible, fetch mode corrected
Reliability, Features & APIs
- DFPlayer init enabled (was commented out)
- WiFi: auto-retry back from AP mode to STA every 60s
- WiFi: boot STA timeout 10s -> 5s
- NeoPixel status LEDs: Boot/Connect/STA/AP/Error/OTA
- OTA updates active (hostname "huyang")
- Optional HTTP basic auth for the web interface
- Audio Web API (/audio/play, /audio/stop, /audio/volume)
- HuyangSequence class with 4 pre-defined animations
- MQTT integration (optional, via #define HUYANG_MQTT_ENABLED)
Printed-Droid PCB Pinout
- ESP32 block switched to Lolin/WeMos ESP32 D1 Mini on the Printed-Droid PCB
- I2C/SPI now use ESP32 default pins
- DFPlayer direction corrected (TX instead of RX)
- TFT RST set to -1 (PCB ties display RST directly to ESP RST)
- NeoPixel moved to GPIO 17 (no boot-strap, no conflict)
- ESP8266 block fixed accordingly (was an original v1.9 bug)
Multi-Board Support & Bug Fixes
- Multi-board support: automatic pin selection via
pins.h - ESP32/S3: Arduino_ESP32SPI, HardwareSerial for DFPlayer, Wire.begin(SDA, SCL)
- HuyangAudio: public playTrack(), setVolume(), stop() methods
- HuyangFace: millis overflow fix, yield() in animations, balanced random moods
- HuyangConfig: CRC-32 checksum, packed struct, static_assert validation
- HuyangCLI: buffer limit, reset confirmation, audio stop command
- HuyangWifi: mDNS leak fix, softAPConfig order, auto-reconnect mDNS
- WebServer: chunked POST body, JSON error checking, LittleFS guard
- F() macros across all files for SRAM savings
- Many additional bugfixes (see changelog.md)
Major Refactoring & New Features
- Replaced JxWifiManager with native WiFi management (STA with AP fallback)
- New EEPROM configuration system with checksum validation and wear leveling
- New Serial CLI for runtime configuration and control
- 11 voice assistant trigger endpoints
- mDNS support (
http://huyang.local) - Feature flags stored in EEPROM, configurable via CLI
- Calibration values stored in EEPROM instead of calibration.h
- Consolidated source files (one .cpp per class)
- Flattened file structure (removed src/ subdirectories)
- Multiple bugfixes and compatibility improvements
- Added board settings and pin documentation
- Changed pins to match Printed-Droid PCB
- Audio system prepared (DFPlayer Mini)
- UI improvements with titled buttons
- Config-driven interface (show only enabled features)
- ESP8266 (NodeMCU, Wemos D1 Mini, Generic ESP8266 Module) or ESP32-S3 (Waveshare ESP32-S3 Zero, Lolin S3 Mini - recommended) or ESP32 (ESP32 Dev Module, Waveshare ESP32-Zero)
- PCA9685 PWM Servo Driver - 16-channel I2C servo controller (address 0x40)
- 2x GC9A01 Round TFT Displays - 240x240 pixel, SPI interface (for eyes)
- Up to 9x Standard Servos (depending on build configuration)
- 5V Power Supply - Adequate for servos (minimum 3A recommended)
- DFPlayer Mini - MP3 player module
- MicroSD Card - FAT32 format
- Speaker - 4-8 ohm
- NeoPixel LED Strip - WS2812B for chest/torso lights
- Monacle Servo - For monacle lens movement
- IR Receiver (TSOP38238) - ESP32-S3 only
- PIR Motion Sensor (HC-SR501) - ESP32-S3 only
- Huyang Board from Printed-Droid.com
Pin assignments are automatically selected based on the board chosen in Arduino IDE.
All pin definitions are in pins.h. No manual pin changes needed.
| Function | ESP8266 | ESP32-S3 (S3 Zero / S3 Mini) | ESP32 (ESP32-Zero / Dev Module) |
|---|---|---|---|
| TFT DC | GPIO 2 | GPIO 4 | GPIO 4 |
| TFT CS Left Eye | GPIO 16 | GPIO 5 | GPIO 5 |
| TFT CS Right Eye | GPIO 15 | GPIO 3 | GPIO 3 |
| TFT RST | GPIO 0 | GPIO 6 | GPIO 6 |
| SPI SCK | GPIO 14 (default) | GPIO 10 | GPIO 18 |
| SPI MOSI | GPIO 13 (default) | GPIO 11 | GPIO 23 |
| I2C SDA | GPIO 4 (default) | GPIO 8 | GPIO 21 |
| I2C SCL | GPIO 5 (default) | GPIO 9 | GPIO 22 |
| Audio RX | GPIO 12 | GPIO 1 | GPIO 16 |
| Audio TX | -1 (unused) | GPIO 7 | GPIO 17 |
| NeoPixel | GPIO 0 | GPIO 2 | GPIO 2 |
| IR Receiver | - | GPIO 12 | - |
| PIR Sensor | - | GPIO 13 | - |
| Channel | Function |
|---|---|
| 4 | Monacle servo |
| 5 | Head left servo |
| 6 | Head right servo |
| 8 | Head rotation servo |
| 9 | Neck tilt servo |
| 11 | Body rotation servo |
| 12 | Body forward left servo |
| 13 | Body forward right servo |
| 14 | Body sideways left servo |
| 15 | Body sideways right servo |
- Main Supply: 5V / 3A minimum
- Servos: 5-6V via PCA9685 V+ (external supply, current depends on servo count)
- TFT Displays: 3.3V (from ESP regulator)
- NeoPixel LEDs: 5V with current limiting
- ESP: 3.3V internal regulation
- ESP32-S3 (S3 Zero / S3 Mini): 330uF + 100nF capacitors between 3V3 and GND recommended
Warning: Ensure adequate current capacity for your servo configuration.
- Open Arduino IDE and go to File -> Preferences
- In Additional boards manager URLs, add:
- ESP8266:
http://arduino.esp8266.com/stable/package_esp8266com_index.json - ESP32:
https://espressif.github.io/arduino-esp32/package_esp32_index.json
- ESP8266:
- Go to Tools -> Board -> Board Manager
- Search for
espand install the appropriate package - Select your board under Tools -> Board
These settings are important — especially MMU + lwIP for the full feature set:
| Setting | Value | Why |
|---|---|---|
| Board | LOLIN(WEMOS) D1 mini (clone) or Generic ESP8266 Module |
|
| Upload Speed | 921600 |
fast |
| Debug port | Disabled |
saves RAM |
| Debug Level | None |
saves flash |
| Flash Size | 4MB (FS:none OTA:~1019KB) |
no LittleFS needed anymore! |
| C++ Exceptions | Disabled (new aborts on oom) |
default |
| Flash Frequency | 40MHz |
stable |
| Flash Mode | DOUT (compatible) |
compatible with all D1 Mini clones |
| lwIP Variant | v2 Lower Memory |
saves IRAM |
| MMU | 16KB cache + 48KB IRAM |
raises IRAM from 32 → 48 KB - REQUIRED for full feature set |
| Non-32-Bit Access | Use pgm_read macros for IRAM/PROGMEM |
correct for PROGMEM UI |
| SSL Support | Basic SSL ciphers (lower ROM use) |
saves ~50 KB flash |
| Stack Protection | Disabled |
saves flash |
| VTables | Flash |
saves RAM |
| Erase Flash | Only Sketch |
EEPROM is preserved |
| CPU Frequency | 160 MHz |
double the performance, smoother animations |
| Setting | Value |
|---|---|
| Board | WEMOS D1 MINI ESP32 or ESP32 Dev Module |
| Upload Speed | 921600 |
| CPU Frequency | 240MHz (WiFi/BT) |
| Flash Frequency | 80MHz |
| Flash Mode | QIO |
| Flash Size | 4MB (32Mb) |
| Partition Scheme | Default 4MB with spiffs (SPIFFS is not used) |
| PSRAM | Disabled |
| Erase All Flash | Disabled |
Only if you use the standalone S3 Zero sketch — see Huyang_Remote_Control_S3Zero_V1/.
Open Tools -> Manage Libraries and install each of the following:
| Search for | Install | Notes |
|---|---|---|
ESPAsyncWebServer |
ESPAsyncWebServer by lacamera | or the ESP32Async fork |
ESPAsyncTCP (ESP8266) / AsyncTCP (ESP32) |
standard build | automatically pulled in as a dependency |
PWM Servo Driver |
Adafruit PWM Servo Driver Library | |
Adafruit NeoPixel |
Adafruit NeoPixel | |
Arduino GFX Library |
GFX Library for Arduino by Moon On Our Nation | |
DFRobotDFPlayerMini |
DFRobotDFPlayerMini | |
ArduinoJson |
ArduinoJson by Benoit Blanchon | v7.x required |
PubSubClient |
PubSubClient by Nick O'Leary | only when HUYANG_MQTT_ENABLED=true in config.h |
Edit config.h to set your default values (used on first boot or after factory reset):
- WiFi SSID + password (or leave empty for the AP-mode fallback "HuyangWifiControl")
- Choose WiFi mode:
1= STA (connect to network),0= AP (hotspot) - Set hotspot name/password if using AP mode (also editable in the Settings page after boot)
- Optional: Web Basic Auth user/password and MQTT host/port/user/password — all of these are also runtime-editable in the Settings page (
config.honly provides the first-boot defaults) - Enable or disable hardware features to match your build
After first upload, all settings can be changed via Serial CLI or the Web Settings page and are stored in EEPROM with CRC-32 checksum. Passwords are stored in EEPROM as plain text (no hashing on an MCU) and are never returned over HTTP — make sure to enable Web Basic Auth in production deployments.
As of v2.9.2, only the sketch is uploaded. Web-UI assets are embedded in PROGMEM, custom sequences are stored in EEPROM. No LittleFS plugin needed.
- Select your board in Arduino IDE (pins are auto-selected via
pins.h) - Apply the settings shown above (especially MMU + lwIP + SSL!)
- Upload (
Ctrl+U) - Open Serial Monitor (115200 baud)
- Browser →
http://huyang.local
Done. No second upload, no plugin, no confusion.
If you modify any data/ files, regenerate before compiling:
powershell -ExecutionPolicy Bypass -File gen-webassets.ps1This rebuilds WebAssets.h from the data/ directory. Then recompile and upload the sketch.
Huyang_Remote_Control_v2/
├── Huyang_Remote_Control_v2.ino # Main program
├── pins.h # Board-specific pin definitions (auto-select)
├── config.h # Default configuration (compile-time)
├── HuyangConfig.h/.cpp # EEPROM configuration system (CRC-32)
├── HuyangWifi.h/.cpp # WiFi management (STA + AP)
├── HuyangCLI.h/.cpp # Serial command interface
├── HuyangFace.h/.cpp # TFT eye animations (6 moods)
├── HuyangNeck.h/.cpp # Head/neck servo control (easing)
├── HuyangBody.h/.cpp # Body servo control
├── HuyangAudio.h/.cpp # DFPlayer Mini audio
├── EasingServo.h/.cpp # Servo easing functions
├── WebServer.h/.cpp # Web interface + trigger endpoints
├── HuyangSequence.h/.cpp # Animation sequencer (eyes + body + neck + audio in sync)
├── HuyangMqtt.h/.cpp # MQTT integration (optional via #define)
├── WebAssets.h # PROGMEM-embedded UI (auto-generated)
├── gen-webassets.ps1 # Generator script for WebAssets.h
├── data/ # UI source files (input for gen-webassets.ps1)
├── changelog.md # Version history
└── README.md # This file
Note: data/ is no longer uploaded to the ESP. Its contents are embedded as PROGMEM constants in WebAssets.h via gen-webassets.ps1 and shipped with the sketch. If you edit HTML/CSS/JS → run the generator script, then re-upload the sketch.
Responsive web UI accessible from any browser
- Direct IP:
http://[ESP_IP_ADDRESS](shown in Serial Monitor) - mDNS:
http://huyang.local - AP Mode:
http://192.168.10.1
- Eye expression control (open, closed, blink, focus, sad, angry)
- Joystick for head movement (rotation + tilt)
- Slider for sideways neck tilt
- Body movement controls
- Automatic animation toggle
- Configurable UI (only shows enabled features)
Connect via Serial Monitor. Type help for all commands.
help Show all available commands
status Show WiFi, memory, and uptime
config Show full configuration
save Save current config to EEPROM
reset confirm Reset config to defaults (from config.h)
reboot Restart ESP
wifi status Show WiFi connection info
wifi ssid <name> Set WiFi network name
wifi password <pass> Set WiFi password
wifi mode <ap|sta> Set WiFi mode
wifi reconnect Reconnect with current settings
After changing WiFi settings, use save then wifi reconnect.
neck rotate <-100..100> Rotate head left/right
neck tilt <-100..100> Tilt neck forward/backward
neck sideways <-100..100> Tilt neck left/right
body rotate <-100..100> Rotate body left/right
body tilt <-100..100> Tilt body forward/backward
body sideways <-100..100> Tilt body left/right
eyes open Open both eyes
eyes close Close both eyes
eyes blink Blink both eyes
eyes focus Focus expression
eyes sad Sad expression
eyes angry Angry expression
eyes auto Enable automatic animations
audio volume <0..30> Set playback volume
audio play <track> Play track by number
audio stop Stop playback
cal show List all 16 PCA9685 channels with min / center / max + function label
cal set <ch> <min> <ctr> <max> Set + persist calibration for one channel (0-15, PWM 0-4095)
Calibration writes commit to EEPROM immediately — no separate save needed.
For graphical editing use the Calibration page in the web UI (live PWM sliders, Copy-from-channel, Export/Import JSON).
auto on Enable automatic animations
auto off Disable automatic animations
HTTP GET endpoints for integration with Google Home, Alexa (via IFTTT), Siri (via Shortcuts), or any HTTP client.
| Endpoint | Action |
|---|---|
/trigger/wakeup |
Open eyes, enable automatic mode |
/trigger/sleep |
Close eyes, center head, disable automatic |
/trigger/nod |
Tilt head forward (nod) |
/trigger/shake |
Rotate head left (head shake) |
/trigger/look |
Focus eyes |
/trigger/sad |
Sad eyes + head tilt down |
/trigger/angry |
Angry eyes |
/trigger/blink |
Blink eyes |
/trigger/random |
Enable automatic random animations |
/trigger/reset |
Reset all to default positions |
/trigger/sound?id=<n> |
Play audio track number n |
All endpoints return JSON: {"status":"ok","action":"<name>"}
- Create account at https://ifttt.com
- Create Applet: If This -> Google Assistant / Alexa -> "Say a simple phrase"
- Trigger phrase:
Huyang wake up - Then That -> Webhooks -> Make a web request
- URL:
http://YOUR_HUYANG_IP/trigger/wakeup, Method: GET - Save and test: "Hey Google, Huyang wake up"
- Open Shortcuts app
- Tap + -> Add Action -> search "Get Contents of URL"
- Enter URL:
http://YOUR_HUYANG_IP/trigger/wakeup - Name shortcut: "Huyang wake up"
- Tap (i) -> Add to Siri -> record phrase
- Test: "Hey Siri, Huyang wake up"
Replace 192.168.1.100 with your Huyang's IP address:
http://192.168.1.100/trigger/wakeup
http://192.168.1.100/trigger/sleep
http://192.168.1.100/trigger/nod
http://192.168.1.100/trigger/shake
http://192.168.1.100/trigger/look
http://192.168.1.100/trigger/sad
http://192.168.1.100/trigger/angry
http://192.168.1.100/trigger/blink
http://192.168.1.100/trigger/random
http://192.168.1.100/trigger/reset
http://192.168.1.100/trigger/sound?id=1
You can test triggers directly in your browser or with curl:
curl http://huyang.local/trigger/wakeup
curl http://192.168.1.100/trigger/sad
curl http://192.168.10.1/trigger/sound?id=3
Notes:
- Triggers work on local network without additional hardware
- For external access, use port forwarding or VPN
- IFTTT free plan supports unlimited applets
- Siri Shortcuts work locally without cloud latency
| Problem | Solution |
|---|---|
| Web interface shows blank page | WebAssets.h must exist — run gen-webassets.ps1, then re-upload the sketch |
IRAM overflow (section .iram1 will not fit) |
Set MMU to 16KB cache + 48KB IRAM, lwIP to v2 Lower Memory, SSL to Basic |
| Custom sequence disappears after reboot | EEPROM migration. On first flash of v2.9.2 the old EEPROM config is discarded. Save the sequence again. |
| Cannot connect to WiFi | Check SSID/password via CLI: wifi ssid YourNetwork, wifi password YourPass, save, wifi reconnect |
| Servos not moving | Verify PCA9685 wiring and I2C address (0x40). Check feature flags with config |
| Serial Monitor shows gibberish | Set baud rate to 115200 |
| EEPROM config lost | Use reset confirm to restore defaults, then save |
| mDNS not working | Not all networks support mDNS. Use IP address instead |
| WiFi keeps disconnecting | Huyang auto-reconnects every 30s in STA mode. Check signal strength |
| Eyes not displaying | Check SPI wiring. See pin table above for your board |
| ESP32-S3 displays not working | Verify Display SCL/SDA on GPIO 10/11 (SPI), not GPIO 8/9 (I2C)! |
| Wrong pins after board switch | Pins are auto-selected via pins.h. Verify correct board is selected in Arduino IDE |
Huyang Droid Remote Control v2.9.2 by Printed-Droid.com Original v1.x by Jeanette Mueller
