Skip to content

Magical Revolving Lantern

When millennium-old tradition meets digital manufacturing.

Project Overview: Perfect fusion of traditional aesthetics and modern technology

Project Origins

Do you remember the first time you saw a revolving lantern as a child? With flickering candlelight, paper-cut shadows danced gracefully on the lantern cover, as if they had come to life. This ancient toy, dating back to the Han Dynasty, used the simplest physical principle—rising hot air—to create poetic visual experiences.

But as a "digital craftsman" who has been working in Fab Lab for 18 weeks, I couldn't help but wonder: what sparks would fly if this thousand-year-old ancient lamp were given wings of modern technology?

Thus, the Magical Revolving Lantern was born.

Drawing Board

Left: Poetry of traditional revolving lantern; Right: Technological aesthetics of the Magical Revolving Lantern

What Exactly Is It?

Simply put, it's a smart lantern that can "read minds."

Core Features Overview

🎯Precise Rotation Control: Say goodbye to uncontrollable hot air flows, use stepper motors to achieve precise speed control. Want to meditate slowly or party at a fast pace? A gesture does it all.

🌈Programmable Light Magic: 28 WS2812B RGB LEDs arranged in two rows, 360° illumination with no dead angles. From warm candlelight yellow to dreamy rainbow gradients, endless variations at your fingertips.

👋Gesture Recognition: Supports up to three APDS-9960 sensors distributed at 120°, no matter which angle you "interact" with it, it instantly understands your intentions. Up, down, left, right—each gesture has its dedicated light response.

📱Web Remote Control: Phones, computers, tablets—anything that can connect to the internet can control it. During gatherings with friends, everyone can "conduct" the lantern performance together, absolutely becoming the center of attention.

🔗Multi-device Synchronization: Based on MQTT protocol, multiple Magical Revolving Lanterns can form a "lantern array" with synchronized changes—quite a spectacular sight.

Feature Demo: Gesture control, Web interface, synchronization effects

Structural Design

Physical Structure Design

The lantern structure took up a lot of time, as the lantern needs an external frame, an internal rotating structure, and gear mechanisms driven by motors. Integrating these systems together took considerable time.

I used Fusion 360 to design the entire lantern structure, including complex gear systems and assembly design:

The rendered effect based on the design file is shown below:

Drawing Board

Final design version rendering, designed in Fusion 360

Then I copied a file in Fusion and expanded all structural components as shown in the figure below:

Drawing Board

Expanding all components in Fusion 360 and rendering to get the illustration, then I numbered all the structural component files that need to be manufactured

Structural Component List

Component No.Part NameQuantityFunction DescriptionManufacturing MethodMaterial
Gesture Sensor3Supports installation of up to 3 gesture sensors to sense gestures from various directions, directly controlling lighting effects and motor rotationPurchase
C4Top Cover1Cylindrical, located at the very top of the lantern, with 3 gesture sensor brackets distributed at 120 degrees, nested and fixed with PCB compartment3D PrintingPLC/PETG
XIAO ESP32C31System core controllerPurchase
Semicircle PCB1Electronic hardware development board, positioned in PCB compartment through positioning fixing holesMilling/JLC ProductionCopper Clad Board/FR4
C3PCB Compartment1Cylindrical structure carrying PCB board, nested and fixed with gear compartment3D PrintingPLC/PETG
C2AGear A m1.1x161Module 1.1mm, 16 teeth, bore diameter 4.25mm, fixed by 4mm steel shaft; Gear B drives rotating cage shaft gear through Gear A3D PrintingPLC
C2BGear B m1.1x301Module 1.1mm, 30 teeth, bore diameter 4.25mm, fixed by 4mm steel shaft; Gear C drives Gear A through Gear B3D PrintingPLC
C2CGear C m1.1x81Module 1.1mm, 8 teeth, connected to motor shaft, motor rotation also drives Gear C3D PrintingPLC
4mm Diameter Steel Shaft2Supporting gearsPurchase
Motor1Drives gear group, then drives lantern rotating cagePurchase
Rechargeable Battery1Powers the systemPurchase
C1Gear Compartment1Supports 3 gears, contains motor box and battery compartment; center has round hole for lantern rotating shaft gear to pass through and connect with gears inside gear compartment; fixed with outer rail through slot holes3D PrintingPLC/PETG
B6Rotating Cage Top Cover and Drive Shaft1Top cover central shaft with gear, driven by Gear A in gear compartment to rotate entire rotating cage3D PrintingPLC
B5Rotating Cage Outer Frame2Top and bottom of rotating cage each have one, fixing rotating cage top cover, bottom cover and 6 rotating cage support columns3D PrintingPLC/PETG
LED Light Strip2Each light strip consists of 14 RGB LEDsPurchase
B4Rotating Cage Support Column6Supports rotating cage and fixes lantern cover panels3D PrintingPLC
B3Lantern Cover Panel61mm thick semi-thin panels, 6 panels can have vinyl-cut sticker patterns, customizable DIY according to preferred themes3D PrintingPLC/PETG
B2Rotating Cage Bottom Plate1Bottom has a conical protrusion that can be placed in cylindrical constraint of lower support base3D PrintingPLC
B1Bottom Bracket of the Cage1Top has cylindrical fixing hole, fixed with outer rail through slot holes3D PrintingPLC
A1Lantern Outer Side Rail6Obtained by laser cuttingLaser Cutting3mm Wood Board
A2Lantern Outer Perimeter Rail2Top and bottom 2 pieces constrain lantern outer side railsLaser Cutting3mm Wood Board

Component Manufacturing

Manufacturing File Export

According to the numbering, I exported all files needed for manufacturing the structural parts, with the directory structure as follows.

Drawing Board

All component files needed for the structural part

Download structure file archive: Magical Revolving Lantern Structure.zip

A1-A2 Laser Cutting

Import A1-Lantern Outer Side Rail.dxf (clone to 6 pieces) and A2-Lantern Outer Perimeter Rail.dxf (clone to 2 pieces) into laser cutting software. For 3mm basswood board, I set 90% laser intensity and processing speed of 15m/s.

Import A1 and A2 files into laser cutting machine software, clone required quantities, adjust laser energy and speed

Then proceed with cutting.

A1-A2 cutting completed

B1-C4 3D Printing

There's a Magical Revolving Lantern.3mf file in the archive, which is a Bambu printer project archive. When opened, you can see the plate distribution effects of all 3D printed parts, convenient for slicing and printing plate by plate.

Bambu printer project multi-plate archive

Printing plate by plate, using Bambu LAB A1 printer, I printed all components plate by plate. For B3 lantern cover panels, I used translucent PETE material. For gears, to distinguish them from the gear compartment, I used golden PLA Silk material. Everything else was white PLA material.

Using Bambu LAB A1 printer, printed all components plate by plate

When printing gears, it's best to use highest quality printing (0.08mm High Quality). I tried it and found that the default 0.2mm makes the holes in the middle of gears smaller, unable to rotate when fitted on 4mm steel shafts.

When printing gears, recommend selecting highest quality

Lantern Rotating Cage Cover Sticker Design

For the lantern rotating cage covers, I used light blue translucent PETG material for printing, with 1mm thickness. Single piece dimensions are 48×151 mm, with a total of 6 pieces. I wanted to use vinyl cutting for some Chinese-style paper-cut patterns to stick on the lantern cover surface.

So I tried using the online tool https://www.lovart.ai/, through prompts and Chinese paper-cut reference images, I made requests. After several rounds of communication, I got the Journey to the West paper-cut style patterns I needed.

The online tool https://www.lovart.ai/ can generate series of high-quality Journey to the West paper-cut illustrations through the paper-cut reference images I provided and prompts

Then I planned the required patterns according to the dimensions of the 6 rotating cage lantern covers, as shown in the figure below.

Readjusted the size of AI-generated paper-cut patterns to fit the size of rotating cage cover panels

Obtained the patterns needed for the vinyl cutting machine, as shown in the figure below.

Patterns needed for vinyl cutting machine

Electronic Hardware Design

The PCB of the Magical Revolving Lantern went through 4 major iterations.

Version 1: Weekly Course Electronic Project Verification

In my Fab Academy course weekly projects, I designed an initial version PCB with KiCad and tried both CNC cutting and commercial manufacturing (JLC production). These 2 boards from the first generation helped me complete various electronic projects in the first 15 weeks, verifying various electronic functions required for the lantern.

Generation 1 PCB designed for Magical Revolving Lantern. To facilitate light effect testing, I placed the LED array diameter on the PCB and tried both CNC milling and commercial manufacturing

Version 2: Milled Semicircular PCB

The entire PCB of the Magical Revolving Lantern began to be planned as circular to fit exactly on top of the gear compartment, but this left insufficient space for the battery, so according to the structure, I adopted a semicircular ring PCB integrated design to make room for the battery.

From Version 2, I started trying EasyEDA, feeling that its library is much richer and convenient for manufacturing. I first designed a milled PCB aiming for basic functional verification, but found that wiring was troublesome.

Version 2 PCB schematic

Version 2: Left is the milled PCB design from EasyEDA, middle is PNG converted from nc file for circuit cutting output by CNC, right is the milled PCB

Testing the size of the milled PCB, it fits well with the PCB compartment.

Testing Version 2 milled PCB dimensions

I used this PCB to initially verify XIAO ESP32 C3, battery, switch, motor, LED strips and gesture sensors, but the main problem was that wiring was complex and not very stable.

Version 3: Double-sided PCB Attempt - Various Failure Lessons

Version 2 attempted more complex PCB design (I wrote this process into Week 17 individual assignment), including double-sided and adding wire-to-board connector support. Because the board was quite complex, I had it manufactured through JLC. The board came out beautifully, and I confidently soldered all components.

The soldered board looked great, but reality was that this version's new design had many bugs, causing this board to be completely scrapped. Main problems included:

  • Incorrect spacing of XIAO dual-row pin sockets (about 3mm too wide), preventing XIAO from fitting into sockets;
  • Reversed wire order for LED strips, gesture sensors and motor connector interfaces, causing all wire orders to be reversed when plugged in;
  • AI-suggested Schottky diodes cause voltage drop, preventing battery power supply to XIAO;

I addressed these issues and modified the PCB design a third time.

Version 4: Double-sided PCB Revision Attempt - Finally Successful

Addressing the problems encountered in actual soldering and testing of the PCB designed and manufactured in Week 17, I made a round of improvements. The final completed circuit schematic is shown below.

Version 4 PCB design circuit schematic

In EasyEDA, I readjusted components and wiring according to the schematic and remanufactured it. This time the component soldering and testing went very smoothly.

Using EasyEDA for routing and displaying 3D effects

The final module connection diagram is shown below:

Electronic hardware connection diagram used by the lantern

Control Core: XIAO ESP32C3 I chose this little guy for three reasons: compact size, built-in WiFi, Arduino compatibility. On a coin-sized PCB, it serves as the brain of the entire system.

Sensing System: Three-Eye Detective Three APDS-9960 sensors distributed at 120°, theoretically covering all directions on the horizontal plane. Each sensor can not only recognize gestures but also detect distance and ambient light intensity, leaving expansion space for future intelligence.

Actuator: Gear Transmission + LED Array The mechanical part uses a precisely calculated gear system with optimized transmission ratio, ensuring sufficient torque while controlling speed within reasonable range. The LED part consists of 2 light strips (each strip has 14 RGB LEDs), creating rich light and shadow effects.

Testing Version 4 PCB to ensure all connected devices work properly.

Version 4 PCB testing successful

Assembly Process

With structural components and PCB ready, we can proceed with assembly. Below are all the parts and components of a Magical Revolving Lantern.

All parts and components of the Magical Revolving Lantern

Assembling Outer Frame Structure A1, A2 and B1

When assembling A1 and A2, you can use a hot glue gun for initial fixing (small amount only, to prevent A1 from falling during inversion). Once assembly is complete, the entire structure becomes very tight and solid. Note that B1 has a cylindrical protrusion that must face upward.

First assemble laser-cut wooden boards A1 and A2, then embed B1 into bottom slots, noting that B1's cylindrical groove should face upward

Assembling Lantern Rotating Cage

This step requires patience. Note that B2 needs the conical rotating shaft face downward so the bottom conical shaft of the rotating cage can be placed into B1's cylindrical groove. The assembled rotating cage is shown below. You can try placing the assembled rotating cage into the outer frame assembled in step 1, ensuring the bottom cone of the rotating cage fits into the lower base's cylindrical groove, allowing easy hand rotation of the rotating cage.

Assemble B2, B4, B5 and B6 to get a solid rotating cage, can be placed into the outer frame assembled in step 1 for testing

Adding Cover Panels and Placing LED Light Strips to Rotating Cage

Carefully embed the rotating cage cover panels (B3) into the grooves on the sides of the rotating cage support columns. When 2 panels remain, remember to thread the thinner plug end of the LED strips from inside through the shaft hole of B6 at the top of the rotating cage, then seal the last two cover panels.

Install lantern cover panels (B3), remember when 2 panels remain, thread the LED strip connectors through the shaft hole of B6 at the top of the rotating cage, then install the remaining 2 panels to seal the lantern

Assembling Gear Compartment (C1) and C2A & C2B Gears

Insert 4mm diameter steel shafts into the 2 shaft holes of B3, and place C2A and C2B gears respectively, then place B3 into the revolving lantern structure, let the rotating cage's geared shaft pass through B3's center hole. Push B3 down into the wooden lantern frame's slots, and the rotating cage truly becomes stable. At this point, you can manually rotate the rotating cage's central shaft gear, and you should see the rotating cage rotate very flexibly while driving C2A and C2B gears to rotate.

Insert 4mm diameter steel shafts into the 2 shaft seats of gear compartment (C1), then place gear C2B (the largest one, positioned close to the rotating cage central shaft gear) and C2A on the steel shafts. Push C1 down along the wooden frame, letting the rotating cage's geared central shaft pass through C1's center hole, then get caught by the wooden side rail slots

Integrating PCB Compartment and Top Cover

First place the PCB along the positioning holes in the PCB compartment (C3), can use hot glue gun to fix the PCB, then also fix the Grove Mini FAN control board on the PCB, and connect the PCB and control board with Grove wire. The 2P motor wire from one end of the control board can pass through the hole on the side of the PCB, connecting to the motor. Then fix the head of 1 gesture sensor on the bracket reserved in C4 with hot glue gun, noting that the sensor probe faces outward.

Place the PCB board in PCB compartment (C3), connect the motor control board, the 2P motor wire from one end of the control board can pass through the hole on the side of the PCB; fix the head of 1 gesture sensor on the bracket reserved in C4 with hot glue gun

Install C2C gear on the motor shaft and place in the battery compartment.

First place the motor in the battery compartment

Before installing the PCB compartment, remember to connect the motor's 2P power wire.

Connect the motor 2P wire first, then install the PCB compartment

Carefully insert the PCB compartment on top of the gear compartment, then connect the 2 LED strip connectors.

Install PCB compartment and plug in LED strip connectors

Insert the battery and connect the battery to the PCB socket, being careful not to reverse the red and black wires.

Connect battery

Finally, connect the gesture sensor connector to the PCB socket, then plug in the top cover, turn on the switch, now the motor, light strips and gesture sensors all work.

Vinyl Sticker Cutting and Installation

The printed stickers, with my wife's help, completed the transfer of 1 pattern. The finally cut stickers were applied to the rotating cage cover panels.

Used vinyl cutting machine to cut paper-cut style stickers, then with wife's help transferred them to the rotating cage cover panels

Interaction Design

This part has been verified and tested in previous courses. Control of the revolving lantern is through 3 methods:

  • Lantern gesture sensors
  • Web control via Wi-Fi
  • Multiple lanterns can automatically synchronize via Wi-Fi/MQTT

Lantern can be controlled in 3 ways

Gesture Sensor Control

I integrated ultra-mini APDS-9960 gesture sensors, related programs have been verified in Week 9 assignment. Here I adjusted the gesture control functions:

  • Left/Right gestures: Switch between multiple lighting effect modes (traditional revolving lantern, flame effect, rainbow rotation, breathing sync, meteor chase, festival celebration)
  • Up gesture: Turn on revolving lantern (simultaneously start motor and lighting effects)
  • Down gesture: Turn off revolving lantern (stop motor and lighting effects)

Web Control via Wi-Fi

Related programs have been verified in Week 15 individual assignment. For Web control in the Final Project, I plan to place 3 buttons:

  • Switch lighting effects
  • Turn on revolving lantern
  • Turn off revolving lantern

Multiple Lanterns Can Automatically Synchronize via Wi-Fi/MQTT

Related programs have been verified in Week 11 individual assignment. Multiple lanterns will automatically synchronize via Wi-Fi using MQTT protocol.

Software Architecture

The software part adopts a multi-task concurrent architecture design, fully utilizing the dual-core advantages of ESP32C3.

Drawing Board

Software Architecture: Multi-task concurrent processing diagram

Gesture Recognition Algorithm: Not simple direction judgment, but composite gesture recognition based on timing analysis. For example, "circle drawing" gesture triggers rainbow mode, "upward push" gesture gradually increases brightness.

LED Control Strategy: Implements smooth color transition algorithms, avoiding abrupt jumps. Also supports multiple preset modes: breathing light, flowing light, strobe, gradient, etc.

Communication Protocol Design: Combines WiFi and ESP-NOW communication methods. Daily control uses WiFi (stable, long distance), group synchronization uses ESP-NOW (low latency, no router needed).

Staged Testing Programs

Special Note: All programs in this project were written by Claude Sonnet 4. The AI generated the code based on other relevant course assignment documents and requirements I provided, which were then tested and improved upon.

Motor Switch Test Program

Initially only connecting the motor and testing if the motor can work normally, test program as follows:

cpp
#define MOTOR_PIN 10

void setup() {
  Serial.begin(115200);
  delay(2000);
  Serial.println("Basic Motor Test - ON/OFF only");
  
  pinMode(MOTOR_PIN, OUTPUT);
  digitalWrite(MOTOR_PIN, LOW);
}

void loop() {
  Serial.println("Motor ON");
  digitalWrite(MOTOR_PIN, HIGH);
  delay(3000);
  
  Serial.println("Motor OFF");  
  digitalWrite(MOTOR_PIN, LOW);
  delay(3000);
}

LED Light Effect Test

Then only connecting 1 LED light (GPIO9), testing light effect control program as follows:

cpp
#include <Adafruit_NeoPixel.h>     
#define PIN 9    // Changed to GPIO 9                     
#define MAX_LED 14                   

Adafruit_NeoPixel strip = Adafruit_NeoPixel(MAX_LED, PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  Serial.begin(115200);
  delay(2000);
  
  Serial.println("Testing GPIO 9...");
  
  strip.begin();
  strip.setBrightness(20);
  
  // Stable initialization
  strip.clear();
  strip.show();
  delay(100);
  strip.clear();
  strip.show();
  delay(100);
}

void loop() {
  // Simple red green blue test
  Serial.println("RED");
  for(int i = 0; i < MAX_LED; i++) {
    strip.setPixelColor(i, strip.Color(50, 0, 0));
  }
  strip.show();
  delay(1000);
  
  Serial.println("GREEN");
  for(int i = 0; i < MAX_LED; i++) {
    strip.setPixelColor(i, strip.Color(0, 50, 0));
  }
  strip.show();
  delay(1000);
  
  Serial.println("BLUE");
  for(int i = 0; i < MAX_LED; i++) {
    strip.setPixelColor(i, strip.Color(0, 0, 50));
  }
  strip.show();
  delay(1000);
  
  Serial.println("OFF");
  strip.clear();
  strip.show();
  delay(1000);
}

LED Light Effects and Motor Program

After multiple debugging and revisions, completed the test program for motor and 2 LEDs working synchronously:

cpp
#include <Adafruit_NeoPixel.h>

// Hardware definitions
#define LED_PIN_1 9      // GPIO9 controls first LED strip
#define LED_PIN_2 8      // GPIO8 controls second LED strip
#define MOTOR_PIN 10     // GPIO10 controls motor
#define MAX_LED 14       // 14 LEDs

// Dual LED strip initialization
Adafruit_NeoPixel strip1 = Adafruit_NeoPixel(MAX_LED, LED_PIN_1, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip2 = Adafruit_NeoPixel(MAX_LED, LED_PIN_2, NEO_GRB + NEO_KHZ800);

// PWM parameters
const int PWM_FREQ = 5000;
const int PWM_RESOLUTION = 8;

// Color definitions (low brightness)
#define RED    strip1.Color(50, 0, 0)
#define GREEN  strip1.Color(0, 50, 0)
#define BLUE   strip1.Color(0, 0, 50)
#define YELLOW strip1.Color(50, 50, 0)
#define PURPLE strip1.Color(50, 0, 50)
#define CYAN   strip1.Color(0, 50, 50)
#define WHITE  strip1.Color(30, 30, 30)
#define ORANGE strip1.Color(60, 20, 0)
#define OFF    strip1.Color(0, 0, 0)

// Current mode
int currentMode = 0;
const int TOTAL_MODES = 6;

// Helper function: Set both LED strips' pixel colors simultaneously
void setDualPixelColor(int pixel, uint32_t color) {
  strip1.setPixelColor(pixel, color);
  strip2.setPixelColor(pixel, color);
}

// Helper function: Display both LED strips simultaneously
void showDualStrips() {
  strip1.show();
  strip2.show();
}

// Helper function: Clear both LED strips simultaneously
void clearDualStrips() {
  strip1.clear();
  strip2.clear();
}

// Helper function: Create color (compatible with both strips)
uint32_t createColor(int r, int g, int b) {
  return strip1.Color(r, g, b);
}

void setup() {
  Serial.begin(115200);
  delay(2000);
  
  Serial.println("=== Magical Revolving Lantern Dual LED Version Startup ===");
  Serial.println("Smart Lantern - Dual LED & Motor Integration");
  
  // Initialize both LED strips
  strip1.begin();
  strip1.setBrightness(20);
  strip1.clear();
  strip1.show();
  
  strip2.begin();
  strip2.setBrightness(20);
  strip2.clear();
  strip2.show();
  
  delay(100);
  
  clearDualStrips();
  showDualStrips();
  
  // Initialize motor PWM
  ledcAttach(MOTOR_PIN, PWM_FREQ, PWM_RESOLUTION);
  ledcWrite(MOTOR_PIN, 0);
  
  // Startup animation
  startupSequence();
  
  Serial.println("\nDual LED system ready! Starting effect operations...\n");
}

void loop() {
  switch(currentMode) {
    case 0:
      Serial.println("Mode 1: Traditional Lantern (Dual LED)");
      traditionalLanternMode();
      break;
      
    case 1:
      Serial.println("Mode 2: Fire Effect (Dual LED)");
      fireMode();
      break;
      
    case 2:
      Serial.println("Mode 3: Rainbow Spin (Dual LED)");
      rainbowSpinMode();
      break;
      
    case 3:
      Serial.println("Mode 4: Breathing Sync (Dual LED)");
      breathingSyncMode();
      break;
      
    case 4:
      Serial.println("Mode 5: Meteor Chase (Dual LED)");
      meteorChaseMode();
      break;
      
    case 5:
      Serial.println("Mode 6: Festival Mode (Dual LED)");
      festivalMode();
      break;
  }
  
  // Switch to next mode
  currentMode = (currentMode + 1) % TOTAL_MODES;
  
  // Inter-mode transition
  transitionEffect();
}

// Startup animation
void startupSequence() {
  Serial.println("Startup sequence - Dual LED sync...");
  
  // Motor slow startup
  for(int speed = 0; speed <= 100; speed += 5) {
    ledcWrite(MOTOR_PIN, speed);
    delay(30);
  }
  
  // Both LED strips light up one by one synchronously
  for(int i = 0; i < MAX_LED; i++) {
    setDualPixelColor(i, WHITE);
    showDualStrips();
    delay(50);
  }
  
  delay(500);
  
  // Turn off all
  clearDualStrips();
  showDualStrips();
  ledcWrite(MOTOR_PIN, 0);
  delay(500);
}

// Mode 1: Traditional lantern effect
void traditionalLanternMode() {
  uint32_t colors[] = {RED, ORANGE, YELLOW, GREEN, CYAN, BLUE};
  int numColors = 6;
  
  // Motor medium speed rotation
  ledcWrite(MOTOR_PIN, 120);
  
  // Run for 30 seconds
  unsigned long startTime = millis();
  int offset = 0;
  
  while(millis() - startTime < 30000) {
    // Dual LED color flow
    for(int i = 0; i < MAX_LED; i++) {
      int colorIndex = (i + offset) % numColors;
      setDualPixelColor(i, colors[colorIndex]);
    }
    showDualStrips();
    
    offset++;
    delay(200);  // Match motor speed
  }
  
  // Gradually stop
  for(int speed = 120; speed >= 0; speed -= 5) {
    ledcWrite(MOTOR_PIN, speed);
    delay(50);
  }
}

// Mode 2: Fire effect
void fireMode() {
  // Motor slow rotation, simulating flame flickering
  ledcWrite(MOTOR_PIN, 80);
  
  unsigned long startTime = millis();
  
  while(millis() - startTime < 30000) {
    // Flame flicker effect
    for(int i = 0; i < MAX_LED; i++) {
      int flicker = random(20, 80);
      int r = flicker;
      int g = flicker * 0.3;
      int b = 0;
      setDualPixelColor(i, createColor(r, g, b));
    }
    showDualStrips();
    
    // Motor speed random variation
    int motorSpeed = 60 + random(-20, 20);
    ledcWrite(MOTOR_PIN, motorSpeed);
    
    delay(50);
  }
  
  ledcWrite(MOTOR_PIN, 0);
}

// Mode 3: Rainbow spin
void rainbowSpinMode() {
  unsigned long startTime = millis();
  int colorOffset = 0;
  
  int loopCount = 0;
  
  while(millis() - startTime < 30000) {
    // Dual LED rainbow display - using simple RGB cycle
    for(int i = 0; i < MAX_LED; i++) {
      // Create rainbow effect
      int phase = ((i * 255 / MAX_LED) + (colorOffset * 10)) % 255;
      int r = 0, g = 0, b = 0;
      
      if(phase < 85) {
        // Red to green
        r = (255 - phase * 3) / 5;  // Directly divide by 5 to reduce brightness
        g = (phase * 3) / 5;
        b = 0;
      } else if(phase < 170) {
        // Green to blue
        phase -= 85;
        r = 0;
        g = (255 - phase * 3) / 5;
        b = (phase * 3) / 5;
      } else {
        // Blue to red
        phase -= 170;
        r = (phase * 3) / 5;
        g = 0;
        b = (255 - phase * 3) / 5;
      }
      
      setDualPixelColor(i, createColor(r, g, b));
    }
    showDualStrips();
    
    // Motor speed varies with rainbow
    int motorSpeed = 120;  // Fixed speed
    ledcWrite(MOTOR_PIN, motorSpeed);
    
    colorOffset = (colorOffset + 1) % 26;  // Cycle range
    delay(100);  // Delay
    
    // Debug output
    loopCount++;
    if(loopCount % 10 == 0) {
      Serial.print(".");
    }
  }
  
  ledcWrite(MOTOR_PIN, 0);
}

// Mode 4: Breathing sync
void breathingSyncMode() {
  unsigned long startTime = millis();
  
  while(millis() - startTime < 30000) {
    // Breathing cycle
    // Brighten + accelerate
    for(int level = 0; level <= 100; level += 2) {
      // Dual LED brightness
      int brightness = level / 2;
      for(int i = 0; i < MAX_LED; i++) {
        setDualPixelColor(i, createColor(brightness, brightness/2, 0));
      }
      showDualStrips();
      
      // Motor speed
      ledcWrite(MOTOR_PIN, 50 + level);
      delay(30);
    }
    
    // Dim + decelerate
    for(int level = 100; level >= 0; level -= 2) {
      // Dual LED brightness
      int brightness = level / 2;
      for(int i = 0; i < MAX_LED; i++) {
        setDualPixelColor(i, createColor(brightness, brightness/2, 0));
      }
      showDualStrips();
      
      // Motor speed
      ledcWrite(MOTOR_PIN, 50 + level);
      delay(30);
    }
  }
  
  ledcWrite(MOTOR_PIN, 0);
}

// Mode 5: Meteor chase
void meteorChaseMode() {
  // Motor fast rotation
  ledcWrite(MOTOR_PIN, 180);
  
  unsigned long startTime = millis();
  int position = 0;
  
  while(millis() - startTime < 30000) {
    clearDualStrips();
    
    // Main meteor
    setDualPixelColor(position, WHITE);
    
    // Trail
    if(position > 0) {
      setDualPixelColor((position - 1) % MAX_LED, createColor(20, 20, 20));
    }
    if(position > 1) {
      setDualPixelColor((position - 2) % MAX_LED, createColor(10, 10, 10));
    }
    if(position > 2) {
      setDualPixelColor((position - 3) % MAX_LED, createColor(5, 5, 5));
    }
    
    showDualStrips();
    
    position = (position + 1) % MAX_LED;
    delay(50);
  }
  
  // Decelerate stop
  for(int speed = 180; speed >= 0; speed -= 5) {
    ledcWrite(MOTOR_PIN, speed);
    delay(30);
  }
}

// Mode 6: Festival celebration
void festivalMode() {
  uint32_t festiveColors[] = {RED, GREEN, YELLOW, BLUE, PURPLE};
  
  // Motor medium speed rotation
  ledcWrite(MOTOR_PIN, 130);
  
  unsigned long startTime = millis();
  
  while(millis() - startTime < 30000) {
    // Random flashing
    for(int i = 0; i < MAX_LED; i++) {
      if(random(10) > 7) {  // 30% chance to flash
        setDualPixelColor(i, festiveColors[random(5)]);
      } else {
        setDualPixelColor(i, OFF);
      }
    }
    showDualStrips();
    
    // Occasional motor speed change
    if(random(100) > 90) {
      ledcWrite(MOTOR_PIN, 100 + random(60));
    }
    
    delay(100);
  }
  
  ledcWrite(MOTOR_PIN, 0);
}

// Inter-mode transition effect
void transitionEffect() {
  Serial.println("Mode switching - Dual LED sync...");
  
  // Stop motor
  ledcWrite(MOTOR_PIN, 0);
  
  // Gradually dim all LEDs
  for(int brightness = 20; brightness >= 0; brightness--) {
    for(int i = 0; i < MAX_LED; i++) {
      setDualPixelColor(i, createColor(brightness, brightness, brightness));
    }
    showDualStrips();
    delay(50);
  }
  
  clearDualStrips();
  showDualStrips();
  delay(1000);
}

Adding Multi-Gesture Sensor Test Program

cpp
#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include "XLOT_APDS9960AD.h"

// Hardware definitions
#define LED_PIN_1 9      // GPIO9 controls first LED strip
#define LED_PIN_2 8      // GPIO8 controls second LED strip
#define MOTOR_PIN 10     // GPIO10 controls motor
#define MAX_LED 14       // 14 LEDs

// I2C pin definitions (for gesture sensors)
#define SDA_PIN 6        // GPIO6 (D4) - I2C data line
#define SCL_PIN 7        // GPIO7 (D5) - I2C clock line

// Dual LED strip initialization
Adafruit_NeoPixel strip1 = Adafruit_NeoPixel(MAX_LED, LED_PIN_1, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip2 = Adafruit_NeoPixel(MAX_LED, LED_PIN_2, NEO_GRB + NEO_KHZ800);

// Gesture sensor initialization
XLOT_APDS9960AD apds;

// PWM parameters
const int PWM_FREQ = 5000;
const int PWM_RESOLUTION = 8;

// Color definitions (low brightness)
#define RED    strip1.Color(50, 0, 0)
#define GREEN  strip1.Color(0, 50, 0)
#define BLUE   strip1.Color(0, 0, 50)
#define YELLOW strip1.Color(50, 50, 0)
#define PURPLE strip1.Color(50, 0, 50)
#define CYAN   strip1.Color(0, 50, 50)
#define WHITE  strip1.Color(30, 30, 30)
#define ORANGE strip1.Color(60, 20, 0)
#define OFF    strip1.Color(0, 0, 0)

// System state control
bool lanternOn = false;          // Lantern on/off state
int currentMode = 0;             // Current light effect mode
const int TOTAL_MODES = 6;       // Total number of modes
int currentBrightness = 20;      // Current brightness

// Mode control variables
unsigned long modeStartTime = 0; // Mode start time
int modeOffset = 0;              // Mode internal offset

// Gesture control debounce
unsigned long lastGestureTime = 0;
const unsigned long GESTURE_DELAY = 800; // Gesture interval 800ms

// Mode name array (for debug output)
const char* modeNames[TOTAL_MODES] = {
  "Traditional Lantern",
  "Fire Effect", 
  "Rainbow Spin",
  "Breathing Sync",
  "Meteor Chase",
  "Festival Mode"
};

// Helper function: Set both LED strips' pixel colors simultaneously
void setDualPixelColor(int pixel, uint32_t color) {
  strip1.setPixelColor(pixel, color);
  strip2.setPixelColor(pixel, color);
}

// Helper function: Display both LED strips simultaneously
void showDualStrips() {
  strip1.show();
  strip2.show();
}

// Helper function: Clear both LED strips simultaneously
void clearDualStrips() {
  strip1.clear();
  strip2.clear();
}

// Helper function: Create color (compatible with both strips)
uint32_t createColor(int r, int g, int b) {
  return strip1.Color(r, g, b);
}

// Gesture handling function
void handleGestures() {
  uint8_t gesture = apds.readGesture();
  
  if (gesture != 0) {
    // Gesture debounce processing
    if (millis() - lastGestureTime < GESTURE_DELAY) {
      return;
    }
    lastGestureTime = millis();
    
    switch (gesture) {
      case APDS9960_LEFT:
        Serial.println("🔄 Gesture: ← (Switch to previous mode)");
        switchMode(-1);
        break;
        
      case APDS9960_RIGHT:
        Serial.println("🔄 Gesture: → (Switch to next mode)");
        switchMode(1);
        break;
        
      case APDS9960_UP:
        Serial.println("🔆 Gesture: ↑ (Turn on lantern)");
        turnOnLantern();
        break;
        
      case APDS9960_DOWN:
        Serial.println("🔅 Gesture: ↓ (Turn off lantern)");
        turnOffLantern();
        break;
    }
  }
}

// Mode switching function
void switchMode(int direction) {
  // Can only switch modes when lantern is on
  if (!lanternOn) {
    Serial.println("   ⚠️ Lantern not on, please swipe up first to turn on");
    return;
  }
  
  // Calculate new mode
  currentMode += direction;
  if (currentMode < 0) {
    currentMode = TOTAL_MODES - 1;
  } else if (currentMode >= TOTAL_MODES) {
    currentMode = 0;
  }
  
  // Reset mode time and offset
  modeStartTime = millis();
  modeOffset = 0;
  
  Serial.print("   ✅ Switched to mode ");
  Serial.print(currentMode + 1);
  Serial.print(": ");
  Serial.println(modeNames[currentMode]);
  
  // Show mode switch indicator
  showModeIndicator();
}

// Turn on lantern
void turnOnLantern() {
  if (lanternOn) {
    Serial.println("   ℹ️ Lantern already on");
    return;
  }
  
  lanternOn = true;
  modeStartTime = millis();
  modeOffset = 0;
  
  Serial.println("   ✅ Lantern turned on");
  Serial.print("   🎨 Current mode: ");
  Serial.println(modeNames[currentMode]);
  
  // Show startup animation
  showStartupAnimation();
}

// Turn off lantern
void turnOffLantern() {
  if (!lanternOn) {
    Serial.println("   ℹ️ Lantern already off");
    return;
  }
  
  lanternOn = false;
  
  Serial.println("   ✅ Lantern turned off");
  
  // Show shutdown animation
  showShutdownAnimation();
  
  // Stop motor and clear LEDs
  ledcWrite(MOTOR_PIN, 0);
  clearDualStrips();
  showDualStrips();
}

// Show mode switch indicator
void showModeIndicator() {
  clearDualStrips();
  
  // Show different colors based on mode
  uint32_t indicatorColor;
  switch(currentMode) {
    case 0: indicatorColor = ORANGE; break;  // Traditional lantern - orange
    case 1: indicatorColor = RED; break;     // Fire effect - red
    case 2: indicatorColor = createColor(25, 25, 25); break; // Rainbow spin - white
    case 3: indicatorColor = YELLOW; break;  // Breathing sync - yellow
    case 4: indicatorColor = CYAN; break;    // Meteor chase - cyan
    case 5: indicatorColor = PURPLE; break;  // Festival mode - purple
  }
  
  // Show LED quantity corresponding to mode number
  for (int i = 0; i <= currentMode; i++) {
    setDualPixelColor(i, indicatorColor);
  }
  showDualStrips();
  delay(800);
  
  clearDualStrips();
  showDualStrips();
  delay(200);
}

// Show startup animation
void showStartupAnimation() {
  Serial.println("   🎬 Playing startup animation...");
  
  // LEDs spread out from center
  for (int i = 0; i < MAX_LED; i++) {
    setDualPixelColor(i, GREEN);
    showDualStrips();
    delay(80);
  }
  
  delay(300);
  
  // Motor slow startup
  for (int speed = 0; speed <= 120; speed += 10) {
    ledcWrite(MOTOR_PIN, speed);
    delay(50);
  }
  
  clearDualStrips();
  showDualStrips();
}

// Show shutdown animation
void showShutdownAnimation() {
  Serial.println("   🎬 Playing shutdown animation...");
  
  // Motor gradual stop
  int currentSpeed = 120;
  while (currentSpeed > 0) {
    ledcWrite(MOTOR_PIN, currentSpeed);
    currentSpeed -= 10;
    delay(100);
  }
  ledcWrite(MOTOR_PIN, 0);
  
  // LEDs turn off from outside to inside
  for (int i = MAX_LED - 1; i >= 0; i--) {
    setDualPixelColor(i, OFF);
    showDualStrips();
    delay(80);
  }
}

// Run current mode light effects
void runCurrentMode() {
  if (!lanternOn) {
    return; // Don't run any effects when lantern is off
  }
  
  switch(currentMode) {
    case 0:
      traditionalLanternMode();
      break;
    case 1:
      fireMode();
      break;
    case 2:
      rainbowSpinMode();
      break;
    case 3:
      breathingSyncMode();
      break;
    case 4:
      meteorChaseMode();
      break;
    case 5:
      festivalMode();
      break;
  }
}

// Mode 1: Traditional lantern effect
void traditionalLanternMode() {
  uint32_t colors[] = {RED, ORANGE, YELLOW, GREEN, CYAN, BLUE};
  int numColors = 6;
  
  // Motor medium speed rotation
  ledcWrite(MOTOR_PIN, 120);
  
  // Dual LED color flow
  for(int i = 0; i < MAX_LED; i++) {
    int colorIndex = (i + modeOffset) % numColors;
    setDualPixelColor(i, colors[colorIndex]);
  }
  showDualStrips();
  
  // Update offset every 200ms
  if (millis() - modeStartTime > (modeOffset + 1) * 200) {
    modeOffset++;
  }
}

// Mode 2: Fire effect
void fireMode() {
  // Motor slow rotation, simulating flame flickering
  int motorSpeed = 80 + random(-15, 15);
  ledcWrite(MOTOR_PIN, motorSpeed);
  
  // Flame flicker effect
  for(int i = 0; i < MAX_LED; i++) {
    int flicker = random(20, 80);
    int r = flicker;
    int g = flicker * 0.3;
    int b = 0;
    setDualPixelColor(i, createColor(r, g, b));
  }
  showDualStrips();
}

// Mode 3: Rainbow spin
void rainbowSpinMode() {
  // Motor fixed speed
  ledcWrite(MOTOR_PIN, 120);
  
  // Dual LED rainbow display
  for(int i = 0; i < MAX_LED; i++) {
    int phase = ((i * 255 / MAX_LED) + (modeOffset * 10)) % 255;
    int r = 0, g = 0, b = 0;
    
    if(phase < 85) {
      r = (255 - phase * 3) / 5;
      g = (phase * 3) / 5;
      b = 0;
    } else if(phase < 170) {
      phase -= 85;
      r = 0;
      g = (255 - phase * 3) / 5;
      b = (phase * 3) / 5;
    } else {
      phase -= 170;
      r = (phase * 3) / 5;
      g = 0;
      b = (255 - phase * 3) / 5;
    }
    
    setDualPixelColor(i, createColor(r, g, b));
  }
  showDualStrips();
  
  // Update offset every 100ms
  if (millis() - modeStartTime > (modeOffset + 1) * 100) {
    modeOffset++;
    if (modeOffset > 25) modeOffset = 0;
  }
}

// Mode 4: Breathing sync
void breathingSyncMode() {
  // Calculate breathing cycle
  unsigned long cycleTime = (millis() - modeStartTime) % 4000; // 4 second cycle
  int breathLevel = 0;
  
  if (cycleTime < 2000) {
    // Brightening process
    breathLevel = map(cycleTime, 0, 2000, 0, 100);
  } else {
    // Dimming process
    breathLevel = map(cycleTime, 2000, 4000, 100, 0);
  }
  
  // Dual LED brightness
  int brightness = breathLevel / 2;
  for(int i = 0; i < MAX_LED; i++) {
    setDualPixelColor(i, createColor(brightness, brightness/2, 0));
  }
  showDualStrips();
  
  // Motor speed varies with breathing
  ledcWrite(MOTOR_PIN, 50 + breathLevel);
}

// Mode 5: Meteor chase
void meteorChaseMode() {
  // Motor fast rotation
  ledcWrite(MOTOR_PIN, 180);
  
  // Calculate meteor position
  int position = (modeOffset) % MAX_LED;
  
  clearDualStrips();
  
  // Main meteor
  setDualPixelColor(position, WHITE);
  
  // Trail
  for (int i = 1; i <= 3; i++) {
    int tailPos = (position - i + MAX_LED) % MAX_LED;
    int brightness = 30 - (i * 8);
    if (brightness > 0) {
      setDualPixelColor(tailPos, createColor(brightness, brightness, brightness));
    }
  }
  
  showDualStrips();
  
  // Update position every 50ms
  if (millis() - modeStartTime > (modeOffset + 1) * 50) {
    modeOffset++;
  }
}

// Mode 6: Festival celebration
void festivalMode() {
  uint32_t festiveColors[] = {RED, GREEN, YELLOW, BLUE, PURPLE};
  
  // Motor medium speed rotation
  ledcWrite(MOTOR_PIN, 130);
  
  // Random flashing
  for(int i = 0; i < MAX_LED; i++) {
    if(random(10) > 7) {  // 30% chance to flash
      setDualPixelColor(i, festiveColors[random(5)]);
    } else {
      setDualPixelColor(i, OFF);
    }
  }
  showDualStrips();
  
  // Occasional motor speed change
  if(random(100) > 95) {
    ledcWrite(MOTOR_PIN, 100 + random(60));
  }
}

void setup() {
  Serial.begin(115200);
  delay(2000);
  
  Serial.println("=== Magical Revolving Lantern Gesture Control System Startup ===");
  Serial.println("Smart Lantern - Gesture Control System");
  
  // Initialize I2C bus (for gesture sensors)
  Wire.begin(SDA_PIN, SCL_PIN);
  
  // Initialize gesture sensor
  if(!apds.begin()){
    Serial.println("❌ Gesture sensor initialization failed! Please check wiring.");
    Serial.println("   Running in non-gesture control mode...");
  } else {
    Serial.println("✅ Gesture sensor initialization successful!");
    // Configure gesture sensor
    apds.enableProximity(true);
    apds.enableGesture(true);
    apds.setProxGain(APDS9960_PGAIN_8X);
    apds.setGestureGain(APDS9960_PGAIN_8X);
    apds.setGestureGain(APDS9960_AGAIN_64X);
    apds.setGestureGain(APDS9960_GGAIN_8);
  }
  
  // Initialize both LED strips
  strip1.begin();
  strip1.setBrightness(currentBrightness);
  strip1.clear();
  strip1.show();
  
  strip2.begin();
  strip2.setBrightness(currentBrightness);
  strip2.clear();
  strip2.show();
  
  // Initialize motor PWM
  ledcAttach(MOTOR_PIN, PWM_FREQ, PWM_RESOLUTION);
  ledcWrite(MOTOR_PIN, 0);
  
  Serial.println("\n🎮 Gesture control instructions:");
  Serial.println("   ↑ : Turn on lantern");
  Serial.println("   ↓ : Turn off lantern");
  Serial.println("   ← : Switch to previous light effect mode");
  Serial.println("   → : Switch to next light effect mode");
  Serial.println("\n💡 System ready, waiting for gesture control...");
  
  // Show ready indicator
  for(int i = 0; i < 3; i++) {
    setDualPixelColor(0, GREEN);
    showDualStrips();
    delay(200);
    clearDualStrips();
    showDualStrips();
    delay(200);
  }
}

void loop() {
  // Detect gestures
  handleGestures();
  
  // Run current mode (only when on)
  runCurrentMode();
  
  // Short delay
  delay(50);
}

Now gestures, light strips and motor all respond.

Adding Web Module Test Program

  1. Web Interface Features

Modern design: gradient backgrounds, frosted glass effects, shadow animations.
Responsive layout: perfect display on phones, tablets and computers.
Real-time status display: current on/off status and mode names.
Three core buttons:

🔆 Turn on lantern
🔅 Turn off lantern
🎨 Switch light effects

  1. API Interfaces

GET /api/status: Get current status (on/off status, mode information).
POST /api/control: Send control commands (on/off/switch).

  1. Smart Interaction

Status sync: Web interface automatically updates status every 3 seconds.
Operation feedback: Every operation has success/failure prompts.
Prevent misoperation: Cannot switch modes when off (consistent with gestures and web).

🔧 Usage

Modify WiFi information:

cpp
const char* ssid = "YourWiFiName";        // Change to your WiFi name
const char* password = "YourWiFiPassword"; // Change to your WiFi password

After uploading program:

Serial monitor will display IP address
Use browser to access that IP address to see control interface

Dual control:

Gesture control: up to turn on, down to turn off, left/right to switch modes
Web control: click buttons to achieve same functions

cpp
#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#include "XLOT_APDS9960AD.h"

// WiFi settings - Please modify to your WiFi information
const char* ssid = "YourWiFiName";
const char* password = "YourWiFiPassword";

// Web server
AsyncWebServer server(80);

// Hardware definitions
#define LED_PIN_1 9      // GPIO9 controls first LED strip
#define LED_PIN_2 8      // GPIO8 controls second LED strip
#define MOTOR_PIN 10     // GPIO10 controls motor
#define MAX_LED 14       // 14 LEDs

// I2C pin definitions
#define SDA_PIN 6        // GPIO6 - I2C data line
#define SCL_PIN 7        // GPIO7 - I2C clock line

// LED strips
Adafruit_NeoPixel strip1 = Adafruit_NeoPixel(MAX_LED, LED_PIN_1, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip2 = Adafruit_NeoPixel(MAX_LED, LED_PIN_2, NEO_GRB + NEO_KHZ800);

// Gesture sensor
XLOT_APDS9960AD apds;

// PWM parameters
const int PWM_FREQ = 5000;
const int PWM_RESOLUTION = 8;

// System state
bool lanternOn = false;
int currentMode = 0;
const int TOTAL_MODES = 6;
int currentBrightness = 20;

// Gesture control debounce
unsigned long lastGestureTime = 0;
const unsigned long GESTURE_DELAY = 800;

// Mode names
const char* modeNames[TOTAL_MODES] = {
  "Traditional Lantern", "Fire Effect", "Rainbow Spin", 
  "Breathing Sync", "Meteor Chase", "Festival Mode"
};

// Mode control variables
unsigned long modeStartTime = 0;
int modeOffset = 0;

// Helper functions
void setDualPixelColor(int pixel, uint32_t color) {
  strip1.setPixelColor(pixel, color);
  strip2.setPixelColor(pixel, color);
}

void showDualStrips() {
  strip1.show();
  strip2.show();
}

void clearDualStrips() {
  strip1.clear();
  strip2.clear();
}

uint32_t createColor(int r, int g, int b) {
  return strip1.Color(r, g, b);
}

// This function is not needed in current program as we use LED strips instead of individual LEDs
// void updateLEDs() {
//   // This function is for individual LED control, current program uses LED strips
// }

// WiFi connection
void connectToWiFi() {
  Serial.println("Connecting WiFi...");
  WiFi.begin(ssid, password);
  
  int retries = 0;
  while (WiFi.status() != WL_CONNECTED && retries < 20) {
    delay(500);
    Serial.print(".");
    retries++;
  }
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("");
    Serial.println("WiFi connected successfully!");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("");
    Serial.println("WiFi connection failed!");
  }
}

// Web server setup
void setupWebServer() {
  // Homepage
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    String html = "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
    html += "<title>Magical Revolving Lantern Control</title>";
    html += "<style>body{font-family:Arial;text-align:center;background:#667eea;color:white;padding:20px}";
    html += ".container{background:rgba(255,255,255,0.1);padding:30px;border-radius:15px;max-width:400px;margin:0 auto}";
    html += "button{width:100%;padding:15px;margin:10px 0;border:none;border-radius:10px;font-size:16px;cursor:pointer}";
    html += ".btn-on{background:#4CAF50;color:white}";
    html += ".btn-off{background:#f44336;color:white}";
    html += ".btn-switch{background:#2196F3;color:white}";
    html += ".status{background:rgba(255,255,255,0.2);padding:10px;border-radius:8px;margin:20px 0}";
    html += "</style></head><body>";
    html += "<div class='container'>";
    html += "<h1>Magical Revolving Lantern</h1>";
    html += "<div class='status'>";
    html += "<p>Status: <span id='status'>Loading</span></p>";
    html += "<p>Mode: <span id='mode'>Loading</span></p>";
    html += "</div>";
    html += "<button class='btn-on' onclick='sendCmd(\"on\")'>Turn On Lantern</button>";
    html += "<button class='btn-off' onclick='sendCmd(\"off\")'>Turn Off Lantern</button>";
    html += "<button class='btn-switch' onclick='sendCmd(\"switch\")'>Switch Light Effect</button>";
    html += "</div>";
    html += "<script>";
    html += "var modes=['Traditional Lantern','Fire Effect','Rainbow Spin','Breathing Sync','Meteor Chase','Festival Mode'];";
    html += "function updateStatus(){";
    html += "fetch('/api/status').then(r=>r.json()).then(d=>{";
    html += "document.getElementById('status').innerText=d.on?'On':'Off';";
    html += "document.getElementById('mode').innerText=modes[d.mode]||'Unknown';";
    html += "}).catch(e=>document.getElementById('status').innerText='Connection Failed');}";
    html += "function sendCmd(cmd){";
    html += "fetch('/api/control',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},";
    html += "body:'command='+cmd}).then(r=>r.json()).then(d=>{";
    html += "if(d.success)setTimeout(updateStatus,200);else alert('Operation failed:'+d.message);";
    html += "}).catch(e=>alert('Network error'));}";
    html += "updateStatus();setInterval(updateStatus,3000);";
    html += "</script></body></html>";
    
    request->send(200, "text/html", html);
  });
  
  // API: Get status
  server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request) {
    DynamicJsonDocument doc(128);
    doc["on"] = lanternOn;
    doc["mode"] = currentMode;
    
    String response;
    serializeJson(doc, response);
    request->send(200, "application/json", response);
  });
  
  // API: Control commands
  server.on("/api/control", HTTP_POST, [](AsyncWebServerRequest *request) {
    DynamicJsonDocument responseDoc(128);
    responseDoc["success"] = false;
    responseDoc["message"] = "Invalid request";
    
    if (request->hasParam("command", true)) {
      String command = request->getParam("command", true)->value();
      
      if (command == "on") {
        if (!lanternOn) {
          turnOnLantern();
          responseDoc["success"] = true;
          responseDoc["message"] = "Lantern turned on";
        } else {
          responseDoc["message"] = "Lantern already on";
        }
      } 
      else if (command == "off") {
        if (lanternOn) {
          turnOffLantern();
          responseDoc["success"] = true;
          responseDoc["message"] = "Lantern turned off";
        } else {
          responseDoc["message"] = "Lantern already off";
        }
      }
      else if (command == "switch") {
        if (lanternOn) {
          switchMode(1);
          responseDoc["success"] = true;
          responseDoc["message"] = String("Switched to: ") + modeNames[currentMode];
        } else {
          responseDoc["message"] = "Please turn on lantern first";
        }
      }
    }
    
    String response;
    serializeJson(responseDoc, response);
    request->send(200, "application/json", response);
  });
  
  server.begin();
  Serial.println("Web server started");
}

// Gesture handling
void handleGestures() {
  uint8_t gesture = apds.readGesture();
  
  if (gesture != 0) {
    if (millis() - lastGestureTime < GESTURE_DELAY) {
      return;
    }
    lastGestureTime = millis();
    
    switch (gesture) {
      case APDS9960_LEFT:
        Serial.println("Gesture: ← (Previous mode)");
        switchMode(-1);
        break;
        
      case APDS9960_RIGHT:
        Serial.println("Gesture: → (Next mode)");
        switchMode(1);
        break;
        
      case APDS9960_UP:
        Serial.println("Gesture: ↑ (Turn on lantern)");
        turnOnLantern();
        break;
        
      case APDS9960_DOWN:
        Serial.println("Gesture: ↓ (Turn off lantern)");
        turnOffLantern();
        break;
    }
  }
}

// Switch mode
void switchMode(int direction) {
  if (!lanternOn) {
    Serial.println("Lantern not on, please swipe up first to turn on");
    return;
  }
  
  currentMode += direction;
  if (currentMode < 0) {
    currentMode = TOTAL_MODES - 1;
  } else if (currentMode >= TOTAL_MODES) {
    currentMode = 0;
  }
  
  modeStartTime = millis();
  modeOffset = 0;
  
  Serial.print("Switched to mode: ");
  Serial.println(modeNames[currentMode]);
  
  showModeIndicator();
}

// Turn on lantern
void turnOnLantern() {
  if (lanternOn) {
    Serial.println("Lantern already on");
    return;
  }
  
  lanternOn = true;
  modeStartTime = millis();
  modeOffset = 0;
  
  Serial.println("Lantern turned on");
  Serial.print("Current mode: ");
  Serial.println(modeNames[currentMode]);
  
  showStartupAnimation();
}

// Turn off lantern
void turnOffLantern() {
  if (!lanternOn) {
    Serial.println("Lantern already off");
    return;
  }
  
  lanternOn = false;
  Serial.println("Lantern turned off");
  
  showShutdownAnimation();
  
  ledcWrite(MOTOR_PIN, 0);
  clearDualStrips();
  showDualStrips();
}

// Show mode indicator
void showModeIndicator() {
  clearDualStrips();
  
  uint32_t indicatorColor;
  switch(currentMode) {
    case 0: indicatorColor = createColor(60, 20, 0); break;  // Orange
    case 1: indicatorColor = createColor(50, 0, 0); break;   // Red
    case 2: indicatorColor = createColor(25, 25, 25); break; // White
    case 3: indicatorColor = createColor(50, 50, 0); break;  // Yellow
    case 4: indicatorColor = createColor(0, 50, 50); break;  // Cyan
    case 5: indicatorColor = createColor(50, 0, 50); break;  // Purple
  }
  
  for (int i = 0; i <= currentMode; i++) {
    setDualPixelColor(i, indicatorColor);
  }
  showDualStrips();
  delay(800);
  
  clearDualStrips();
  showDualStrips();
  delay(200);
}

// Startup animation
void showStartupAnimation() {
  Serial.println("Playing startup animation...");
  
  for (int i = 0; i < MAX_LED; i++) {
    setDualPixelColor(i, createColor(0, 50, 0));
    showDualStrips();
    delay(80);
  }
  
  delay(300);
  
  for (int speed = 0; speed <= 120; speed += 10) {
    ledcWrite(MOTOR_PIN, speed);
    delay(50);
  }
  
  clearDualStrips();
  showDualStrips();
}

// Shutdown animation
void showShutdownAnimation() {
  Serial.println("Playing shutdown animation...");
  
  int currentSpeed = 120;
  while (currentSpeed > 0) {
    ledcWrite(MOTOR_PIN, currentSpeed);
    currentSpeed -= 10;
    delay(100);
  }
  ledcWrite(MOTOR_PIN, 0);
  
  for (int i = MAX_LED - 1; i >= 0; i--) {
    setDualPixelColor(i, createColor(0, 0, 0));
    showDualStrips();
    delay(80);
  }
}

// Run current mode
void runCurrentMode() {
  if (!lanternOn) {
    return;
  }
  
  switch(currentMode) {
    case 0:
      traditionalLanternMode();
      break;
    case 1:
      fireMode();
      break;
    case 2:
      rainbowSpinMode();
      break;
    case 3:
      breathingSyncMode();
      break;
    case 4:
      meteorChaseMode();
      break;
    case 5:
      festivalMode();
      break;
  }
}

// Mode 1: Traditional lantern
void traditionalLanternMode() {
  uint32_t colors[] = {
    createColor(50, 0, 0),   // Red
    createColor(60, 20, 0),  // Orange
    createColor(50, 50, 0),  // Yellow
    createColor(0, 50, 0),   // Green
    createColor(0, 50, 50),  // Cyan
    createColor(0, 0, 50)    // Blue
  };
  int numColors = 6;
  
  ledcWrite(MOTOR_PIN, 120);
  
  for(int i = 0; i < MAX_LED; i++) {
    int colorIndex = (i + modeOffset) % numColors;
    setDualPixelColor(i, colors[colorIndex]);
  }
  showDualStrips();
  
  if (millis() - modeStartTime > (modeOffset + 1) * 200) {
    modeOffset++;
  }
}

// Mode 2: Fire effect
void fireMode() {
  int motorSpeed = 80 + random(-15, 15);
  ledcWrite(MOTOR_PIN, motorSpeed);
  
  for(int i = 0; i < MAX_LED; i++) {
    int flicker = random(20, 80);
    int r = flicker;
    int g = flicker * 0.3;
    int b = 0;
    setDualPixelColor(i, createColor(r, g, b));
  }
  showDualStrips();
}

// Mode 3: Rainbow spin
void rainbowSpinMode() {
  ledcWrite(MOTOR_PIN, 120);
  
  for(int i = 0; i < MAX_LED; i++) {
    int phase = ((i * 255 / MAX_LED) + (modeOffset * 10)) % 255;
    int r = 0, g = 0, b = 0;
    
    if(phase < 85) {
      r = (255 - phase * 3) / 5;
      g = (phase * 3) / 5;
      b = 0;
    } else if(phase < 170) {
      phase -= 85;
      r = 0;
      g = (255 - phase * 3) / 5;
      b = (phase * 3) / 5;
    } else {
      phase -= 170;
      r = (phase * 3) / 5;
      g = 0;
      b = (255 - phase * 3) / 5;
    }
    
    setDualPixelColor(i, createColor(r, g, b));
  }
  showDualStrips();
  
  if (millis() - modeStartTime > (modeOffset + 1) * 100) {
    modeOffset++;
    if (modeOffset > 25) modeOffset = 0;
  }
}

// Mode 4: Breathing sync
void breathingSyncMode() {
  unsigned long cycleTime = (millis() - modeStartTime) % 4000;
  int breathLevel = 0;
  
  if (cycleTime < 2000) {
    breathLevel = map(cycleTime, 0, 2000, 0, 100);
  } else {
    breathLevel = map(cycleTime, 2000, 4000, 100, 0);
  }
  
  int brightness = breathLevel / 2;
  for(int i = 0; i < MAX_LED; i++) {
    setDualPixelColor(i, createColor(brightness, brightness/2, 0));
  }
  showDualStrips();
  
  ledcWrite(MOTOR_PIN, 50 + breathLevel);
}

// Mode 5: Meteor chase
void meteorChaseMode() {
  ledcWrite(MOTOR_PIN, 180);
  
  int position = (modeOffset) % MAX_LED;
  
  clearDualStrips();
  
  setDualPixelColor(position, createColor(30, 30, 30));
  
  for (int i = 1; i <= 3; i++) {
    int tailPos = (position - i + MAX_LED) % MAX_LED;
    int brightness = 30 - (i * 8);
    if (brightness > 0) {
      setDualPixelColor(tailPos, createColor(brightness, brightness, brightness));
    }
  }
  
  showDualStrips();
  
  if (millis() - modeStartTime > (modeOffset + 1) * 50) {
    modeOffset++;
  }
}

// Mode 6: Festival celebration
void festivalMode() {
  uint32_t festiveColors[] = {
    createColor(50, 0, 0),   // Red
    createColor(0, 50, 0),   // Green
    createColor(50, 50, 0),  // Yellow
    createColor(0, 0, 50),   // Blue
    createColor(50, 0, 50)   // Purple
  };
  
  ledcWrite(MOTOR_PIN, 130);
  
  for(int i = 0; i < MAX_LED; i++) {
    if(random(10) > 7) {
      setDualPixelColor(i, festiveColors[random(5)]);
    } else {
      setDualPixelColor(i, createColor(0, 0, 0));
    }
  }
  showDualStrips();
  
  if(random(100) > 95) {
    ledcWrite(MOTOR_PIN, 100 + random(60));
  }
}

void setup() {
  Serial.begin(115200);
  delay(2000);
  
  Serial.println("=== Magical Revolving Lantern Gesture+Web Control System Startup ===");
  
  // Initialize I2C
  Wire.begin(SDA_PIN, SCL_PIN);
  
  // Initialize gesture sensor
  if(!apds.begin()){
    Serial.println("Gesture sensor initialization failed!");
  } else {
    Serial.println("Gesture sensor initialization successful!");
    apds.enableProximity(true);
    apds.enableGesture(true);
    apds.setProxGain(APDS9960_PGAIN_8X);
    apds.setGestureGain(APDS9960_PGAIN_8X);
    apds.setGestureGain(APDS9960_AGAIN_64X);
    apds.setGestureGain(APDS9960_GGAIN_8);
  }
  
  // Initialize LEDs
  strip1.begin();
  strip1.setBrightness(currentBrightness);
  strip1.clear();
  strip1.show();
  
  strip2.begin();
  strip2.setBrightness(currentBrightness);
  strip2.clear();
  strip2.show();
  
  // Initialize motor
  ledcAttach(MOTOR_PIN, PWM_FREQ, PWM_RESOLUTION);
  ledcWrite(MOTOR_PIN, 0);
  
  // Connect WiFi
  connectToWiFi();
  
  // Setup web server
  if (WiFi.status() == WL_CONNECTED) {
    setupWebServer();
  }
  
  Serial.println("System ready!");
  Serial.println("Gesture control:");
  Serial.println("  ↑ : Turn on lantern");
  Serial.println("  ↓ : Turn off lantern");
  Serial.println("  ← → : Switch light effect modes");
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("Web control:");
    Serial.print("  Access URL: http://");
    Serial.println(WiFi.localIP());
  }
}

void loop() {
  // Detect gestures
  handleGestures();
  
  // Run current mode
  runCurrentMode();
  
  // Check WiFi connection
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("WiFi connection lost, attempting reconnection...");
    WiFi.reconnect();
  }
  
  delay(50);
}

Successfully compiled and uploaded. In the serial monitor, you can see the IP address provided by XIAO ESP32C3 as shown below. If you need Web control, access: http://192.168.91.124. Note that to access this IP address, your phone or computer needs to use the same Wi-Fi as XIAO ESP32C3.

After successful program compilation and upload, it will show the IP address available in Web mode

I connected my PC to the same Wi-Fi as XIAO and then used browser to access http://192.168.91.124

Final Version Program: Gesture+Web+MQTT Sync

Complete Feature List

Gesture Control

  • Four-direction gesture recognition (up, down, left, right)
  • Multi-retry initialization mechanism
  • Time-division detection to avoid resource conflicts

Web Control Interface

  • Modern responsive design
  • Real-time status display
  • Three control buttons + sync switch

MQTT Multi-device Synchronization

  • Automatic device discovery
  • Real-time status synchronization
  • Anti-loop message mechanism

Six Light Effect Modes

  • Traditional lantern
  • Fire effect
  • Rainbow spin
  • Breathing sync
  • Meteor chase
  • Festival celebration
cpp
#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#include <PubSubClient.h>
#include "XLOT_APDS9960AD.h"

// WiFi settings - Please modify to your WiFi information
const char* ssid = "xiao";
const char* password = "12345678";

// MQTT settings
const char* mqtt_broker = "broker.hivemq.com";
const int mqtt_port = 1883;
const char* mqtt_topic_command = "smart_lantern/command";
const char* mqtt_topic_status = "smart_lantern/status";
const char* mqtt_topic_heartbeat = "smart_lantern/heartbeat";

// Web server and MQTT client
AsyncWebServer server(80);
WiFiClient espClient;
PubSubClient mqttClient(espClient);

// Hardware definitions
#define LED_PIN_1 9      // GPIO9 controls first LED strip
#define LED_PIN_2 8      // GPIO8 controls second LED strip
#define MOTOR_PIN 10     // GPIO10 controls motor
#define MAX_LED 14       // 14 LEDs

// I2C pin definitions
#define SDA_PIN 6        // GPIO6 - I2C data line
#define SCL_PIN 7        // GPIO7 - I2C clock line

// LED strips
Adafruit_NeoPixel strip1 = Adafruit_NeoPixel(MAX_LED, LED_PIN_1, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip2 = Adafruit_NeoPixel(MAX_LED, LED_PIN_2, NEO_GRB + NEO_KHZ800);

// Gesture sensor
XLOT_APDS9960AD apds;

// PWM parameters
const int PWM_FREQ = 5000;
const int PWM_RESOLUTION = 8;

// System state
bool lanternOn = false;
int currentMode = 0;
const int TOTAL_MODES = 6;
int currentBrightness = 20;
bool gestureEnabled = false;

// Device unique identifier
String deviceId;
bool syncEnabled = true;

// Task time management
unsigned long lastGestureCheck = 0;
unsigned long lastMqttCheck = 0;
unsigned long lastHeartbeat = 0;
unsigned long lastMqttAttempt = 0;

// Task interval control
const unsigned long GESTURE_CHECK_INTERVAL = 50;    // Check gestures every 50ms
const unsigned long MQTT_CHECK_INTERVAL = 100;      // Check MQTT every 100ms
const unsigned long HEARTBEAT_INTERVAL = 30000;     // 30 second heartbeat
const unsigned long MQTT_RETRY_INTERVAL = 5000;     // 5 second reconnection interval

// Gesture control debounce
unsigned long lastGestureTime = 0;
const unsigned long GESTURE_DELAY = 800;

// Mode names
const char* modeNames[TOTAL_MODES] = {
  "Traditional Lantern", "Fire Effect", "Rainbow Spin", 
  "Breathing Sync", "Meteor Chase", "Festival Mode"
};

// Mode control variables
unsigned long modeStartTime = 0;
int modeOffset = 0;

// Initialize device ID
void initDeviceId() {
  uint64_t chipId = ESP.getEfuseMac();
  char deviceIdBuffer[20];
  snprintf(deviceIdBuffer, sizeof(deviceIdBuffer), "Lantern%llX", chipId & 0xFFFFFF);
  deviceId = String(deviceIdBuffer);
  Serial.print("Device ID: ");
  Serial.println(deviceId);
}

// Helper functions
void setDualPixelColor(int pixel, uint32_t color) {
  strip1.setPixelColor(pixel, color);
  strip2.setPixelColor(pixel, color);
}

void showDualStrips() {
  strip1.show();
  strip2.show();
}

void clearDualStrips() {
  strip1.clear();
  strip2.clear();
}

uint32_t createColor(int r, int g, int b) {
  return strip1.Color(r, g, b);
}

// I2C device scanner
void scanI2CDevices() {
  Serial.println("\n=== I2C Device Scanner ===");
  byte error, address;
  int deviceCount = 0;
  
  for(address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    
    if (error == 0) {
      Serial.print("Device found at 0x");
      if (address < 16) {
        Serial.print("0");
      }
      Serial.print(address, HEX);
      
      if (address == 0x39) {
        Serial.print(" <- APDS-9960 Gesture Sensor!");
      }
      
      Serial.println();
      deviceCount++;
    }
  }
  
  if (deviceCount == 0) {
    Serial.println("No I2C devices found");
  } else {
    Serial.print("Found ");
    Serial.print(deviceCount);
    Serial.println(" I2C device(s)");
  }
  Serial.println("==========================");
}

// Enhanced gesture sensor initialization
bool initGestureSensor() {
  Serial.println("\n=== Enhanced Gesture Sensor Init ===");
  
  // First scan I2C devices
  scanI2CDevices();
  
  // Reset I2C bus
  Serial.println("Resetting I2C bus...");
  Wire.end();
  delay(100);
  Wire.begin(SDA_PIN, SCL_PIN);
  delay(500);
  
  // Multiple initialization attempts, reset before each attempt
  for(int attempt = 1; attempt <= 5; attempt++) {
    Serial.print("Gesture sensor init attempt ");
    Serial.print(attempt);
    Serial.print("/5: ");
    
    // Slight delay before each attempt
    delay(attempt * 200);
    
    if(apds.begin()) {
      Serial.println("SUCCESS!");
      
      // Verify sensor is actually usable
      delay(100);
      
      // Configure sensor
      Serial.println("Configuring sensor...");
      apds.enableProximity(true);
      delay(50);
      apds.enableGesture(true);
      delay(50);
      apds.setProxGain(APDS9960_PGAIN_8X);
      delay(50);
      apds.setGestureGain(APDS9960_PGAIN_8X);
      delay(50);
      apds.setGestureGain(APDS9960_AGAIN_64X);
      delay(50);
      apds.setGestureGain(APDS9960_GGAIN_8);
      delay(50);
      
      Serial.println("Sensor configured successfully!");
      
      // Test read function
      Serial.println("Testing sensor read...");
      for(int test = 0; test < 5; test++) {
        uint8_t testGesture = apds.readGesture();
        delay(100);
      }
      Serial.println("Sensor test completed!");
      
      return true;
    } else {
      Serial.println("FAILED");
      
      // Re-scan I2C after failures
      if(attempt == 3) {
        Serial.println("Re-scanning I2C after failures...");
        scanI2CDevices();
      }
    }
  }
  
  Serial.println("All gesture sensor init attempts failed!");
  return false;
}

// WiFi connection
void connectToWiFi() {
  Serial.println("Connecting to WiFi...");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  
  int retries = 0;
  while (WiFi.status() != WL_CONNECTED && retries < 20) {
    delay(500);
    Serial.print(".");
    retries++;
  }
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("");
    Serial.println("WiFi connected successfully!");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("");
    Serial.println("WiFi connection failed!");
  }
}

// MQTT connection
void connectToMQTT() {
  if (!mqttClient.connected() && WiFi.status() == WL_CONNECTED) {
    if (millis() - lastMqttAttempt > MQTT_RETRY_INTERVAL) {
      lastMqttAttempt = millis();
      
      if (mqttClient.connect(deviceId.c_str())) {
        Serial.println("MQTT connected successfully!");
        
        // Subscribe to topics
        mqttClient.subscribe(mqtt_topic_command);
        mqttClient.subscribe(mqtt_topic_status);
        mqttClient.subscribe(mqtt_topic_heartbeat);
        
        // Send online notification
        publishHeartbeat();
        publishStatus();
      } else {
        Serial.print("MQTT connection failed, error code: ");
        Serial.println(mqttClient.state());
      }
    }
  }
}

// Publish heartbeat message
void publishHeartbeat() {
  if (mqttClient.connected()) {
    DynamicJsonDocument doc(256);
    doc["deviceId"] = deviceId;
    doc["timestamp"] = millis();
    doc["ip"] = WiFi.localIP().toString();
    doc["status"] = "online";
    
    String message;
    serializeJson(doc, message);
    
    mqttClient.publish(mqtt_topic_heartbeat, message.c_str());
  }
}

// Publish status message
void publishStatus() {
  if (mqttClient.connected()) {
    DynamicJsonDocument doc(256);
    doc["deviceId"] = deviceId;
    doc["lanternOn"] = lanternOn;
    doc["currentMode"] = currentMode;
    doc["modeName"] = modeNames[currentMode];
    doc["timestamp"] = millis();
    
    String message;
    serializeJson(doc, message);
    
    mqttClient.publish(mqtt_topic_status, message.c_str());
  }
}

// Publish control command
void publishCommand(const String& command, const String& source = "gesture") {
  if (mqttClient.connected() && syncEnabled) {
    DynamicJsonDocument doc(256);
    doc["deviceId"] = deviceId;
    doc["command"] = command;
    doc["source"] = source;
    doc["lanternOn"] = lanternOn;
    doc["currentMode"] = currentMode;
    doc["timestamp"] = millis();
    
    String message;
    serializeJson(doc, message);
    
    bool published = mqttClient.publish(mqtt_topic_command, message.c_str());
    if (published) {
      Serial.print("Published command: ");
      Serial.println(command);
    }
  }
}

// MQTT message callback
void onMqttMessage(char* topic, byte* payload, unsigned int length) {
  String message;
  for (int i = 0; i < length; i++) {
    message += (char)payload[i];
  }
  
  // Parse JSON message
  DynamicJsonDocument doc(512);
  DeserializationError error = deserializeJson(doc, message);
  
  if (error) {
    return; // Silently ignore parsing errors
  }
  
  String senderId = doc["deviceId"];
  
  // Ignore messages from self
  if (senderId == deviceId) {
    return;
  }
  
  // Handle different types of messages
  String topicStr = String(topic);
  
  if (topicStr == mqtt_topic_command) {
    String command = doc["command"];
    
    Serial.print("Received sync command: ");
    Serial.print(command);
    Serial.print(" (from: ");
    Serial.print(senderId);
    Serial.println(")");
    
    // Execute sync commands
    if (command == "turn_on") {
      if (!lanternOn) {
        lanternOn = true;
        modeStartTime = millis();
        modeOffset = 0;
        showStartupAnimation();
      }
    }
    else if (command == "turn_off") {
      if (lanternOn) {
        lanternOn = false;
        showShutdownAnimation();
        ledcWrite(MOTOR_PIN, 0);
        clearDualStrips();
        showDualStrips();
      }
    }
    else if (command == "switch_mode") {
      if (lanternOn) {
        int newMode = doc["currentMode"];
        if (newMode >= 0 && newMode < TOTAL_MODES) {
          currentMode = newMode;
          modeStartTime = millis();
          modeOffset = 0;
          showModeIndicator();
        }
      }
    }
  }
  else if (topicStr == mqtt_topic_heartbeat) {
    String status = doc["status"];
    if (status == "online") {
      Serial.print("Device online: ");
      Serial.println(senderId);
    }
  }
}

// Web server setup
void setupWebServer() {
  // Homepage
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    String html = "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
    html += "<title>Smart Lantern Control</title>";
    html += "<style>body{font-family:Arial;text-align:center;background:#667eea;color:white;padding:20px}";
    html += ".container{background:rgba(255,255,255,0.1);padding:30px;border-radius:15px;max-width:400px;margin:0 auto}";
    html += "button{width:100%;padding:15px;margin:10px 0;border:none;border-radius:10px;font-size:16px;cursor:pointer}";
    html += ".btn-on{background:#4CAF50;color:white}";
    html += ".btn-off{background:#f44336;color:white}";
    html += ".btn-switch{background:#2196F3;color:white}";
    html += ".btn-sync{background:#FF9800;color:white}";
    html += ".status{background:rgba(255,255,255,0.2);padding:10px;border-radius:8px;margin:20px 0}";
    html += "</style></head><body>";
    html += "<div class='container'>";
    html += "<h1>🏮 Smart Lantern</h1>";
    html += "<div class='status'>";
    html += "<p>Status: <span id='status'>Loading...</span></p>";
    html += "<p>Mode: <span id='mode'>Loading...</span></p>";
    html += "<p>Device: <span id='deviceId'>Loading...</span></p>";
    html += "<p>MQTT: <span id='mqttStatus'>Loading...</span></p>";
    html += "<p>Gesture: <span id='gestureStatus'>Loading...</span></p>";
    html += "</div>";
    html += "<button class='btn-on' onclick='sendCmd(\"on\")'>🔆 Turn On</button>";
    html += "<button class='btn-off' onclick='sendCmd(\"off\")'>🔅 Turn Off</button>";
    html += "<button class='btn-switch' onclick='sendCmd(\"switch\")'>🎨 Switch Mode</button>";
    html += "<button class='btn-sync' onclick='sendCmd(\"toggle_sync\")'>🔄 Sync: <span id='syncStatus'>Loading...</span></button>";
    html += "</div>";
    html += "<script>";
    html += "var modes=['Traditional Lantern','Fire Effect','Rainbow Spin','Breathing Sync','Meteor Chase','Festival Mode'];";
    html += "function updateStatus(){";
    html += "fetch('/api/status').then(r=>r.json()).then(d=>{";
    html += "document.getElementById('status').innerText=d.on?'On':'Off';";
    html += "document.getElementById('mode').innerText=modes[d.mode]||'Unknown';";
    html += "document.getElementById('deviceId').innerText=d.deviceId||'Unknown';";
    html += "document.getElementById('mqttStatus').innerText=d.mqttConnected?'Connected':'Disconnected';";
    html += "document.getElementById('gestureStatus').innerText=d.gestureEnabled?'Available':'Unavailable';";
    html += "document.getElementById('syncStatus').innerText=d.syncEnabled?'On':'Off';";
    html += "}).catch(e=>console.error(e));}";
    html += "function sendCmd(cmd){";
    html += "fetch('/api/control',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},";
    html += "body:'command='+cmd}).then(r=>r.json()).then(d=>{";
    html += "if(d.success)setTimeout(updateStatus,200);";
    html += "}).catch(e=>console.error(e));}";
    html += "updateStatus();setInterval(updateStatus,3000);";
    html += "</script></body></html>";
    
    request->send(200, "text/html", html);
  });
  
  // API: Get status
  server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request) {
    DynamicJsonDocument doc(256);
    doc["on"] = lanternOn;
    doc["mode"] = currentMode;
    doc["deviceId"] = deviceId;
    doc["mqttConnected"] = mqttClient.connected();
    doc["syncEnabled"] = syncEnabled;
    doc["gestureEnabled"] = gestureEnabled;
    
    String response;
    serializeJson(doc, response);
    request->send(200, "application/json", response);
  });
  
  // API: Control commands
  server.on("/api/control", HTTP_POST, [](AsyncWebServerRequest *request) {
    DynamicJsonDocument responseDoc(128);
    responseDoc["success"] = false;
    responseDoc["message"] = "Invalid request";
    
    if (request->hasParam("command", true)) {
      String command = request->getParam("command", true)->value();
      
      if (command == "on") {
        if (!lanternOn) {
          turnOnLantern();
          publishCommand("turn_on", "web");
          responseDoc["success"] = true;
          responseDoc["message"] = "Lantern turned on";
        }
      } 
      else if (command == "off") {
        if (lanternOn) {
          turnOffLantern();
          publishCommand("turn_off", "web");
          responseDoc["success"] = true;
          responseDoc["message"] = "Lantern turned off";
        }
      }
      else if (command == "switch") {
        if (lanternOn) {
          switchMode(1);
          publishCommand("switch_mode", "web");
          responseDoc["success"] = true;
          responseDoc["message"] = String("Switched to: ") + modeNames[currentMode];
        }
      }
      else if (command == "toggle_sync") {
        syncEnabled = !syncEnabled;
        responseDoc["success"] = true;
        responseDoc["message"] = String("Sync ") + (syncEnabled ? "enabled" : "disabled");
      }
    }
    
    String response;
    serializeJson(responseDoc, response);
    request->send(200, "application/json", response);
  });
  
  server.begin();
  Serial.println("Web server started");
}

// Enhanced gesture handling function
void handleGestures() {
  // Time-division checking to avoid conflicts with MQTT
  if (millis() - lastGestureCheck < GESTURE_CHECK_INTERVAL) {
    return;
  }
  lastGestureCheck = millis();
  
  if (!gestureEnabled) {
    return;
  }
  
  // Add sensor health check
  static unsigned long lastHealthCheck = 0;
  if (millis() - lastHealthCheck > 30000) { // Check every 30 seconds
    lastHealthCheck = millis();
    Serial.println("Gesture sensor health check...");
    
    // Try simple I2C communication
    Wire.beginTransmission(0x39);
    byte error = Wire.endTransmission();
    
    if (error != 0) {
      Serial.println("Gesture sensor I2C communication failed!");
      Serial.print("Error code: ");
      Serial.println(error);
      
      // Try reinitialize
      Serial.println("Attempting to reinitialize sensor...");
      gestureEnabled = initGestureSensor();
    } else {
      Serial.println("Gesture sensor I2C OK");
    }
  }
  
  uint8_t gesture = apds.readGesture();
  
  if (gesture != 0) {
    if (millis() - lastGestureTime < GESTURE_DELAY) {
      return;
    }
    lastGestureTime = millis();
    
    Serial.println("=== GESTURE DETECTED ===");
    Serial.print("Timestamp: ");
    Serial.println(millis());
    Serial.print("Gesture code: ");
    Serial.println(gesture);
    
    switch (gesture) {
      case APDS9960_LEFT:
        Serial.println("Action: LEFT (previous mode)");
        switchMode(-1);
        publishCommand("switch_mode", "gesture");
        break;
        
      case APDS9960_RIGHT:
        Serial.println("Action: RIGHT (next mode)");
        switchMode(1);
        publishCommand("switch_mode", "gesture");
        break;
        
      case APDS9960_UP:
        Serial.println("Action: UP (turn on lantern)");
        turnOnLantern();
        publishCommand("turn_on", "gesture");
        break;
        
      case APDS9960_DOWN:
        Serial.println("Action: DOWN (turn off lantern)");
        turnOffLantern();
        publishCommand("turn_off", "gesture");
        break;
        
      default:
        Serial.print("Unknown gesture code: ");
        Serial.println(gesture);
        break;
    }
    Serial.println("========================");
  }
  
  // Debug info: periodically display gesture detection status
  static unsigned long lastStatusReport = 0;
  if (millis() - lastStatusReport > 60000) { // Report every minute
    lastStatusReport = millis();
    Serial.print("Gesture system status - Enabled: ");
    Serial.print(gestureEnabled ? "YES" : "NO");
    Serial.print(", Check interval: ");
    Serial.print(GESTURE_CHECK_INTERVAL);
    Serial.println("ms");
  }
}

// Switch mode
void switchMode(int direction) {
  if (!lanternOn) {
    return;
  }
  
  currentMode += direction;
  if (currentMode < 0) {
    currentMode = TOTAL_MODES - 1;
  } else if (currentMode >= TOTAL_MODES) {
    currentMode = 0;
  }
  
  modeStartTime = millis();
  modeOffset = 0;
  
  showModeIndicator();
  publishStatus();
}

// Turn on lantern
void turnOnLantern() {
  if (lanternOn) {
    return;
  }
  
  lanternOn = true;
  modeStartTime = millis();
  modeOffset = 0;
  
  showStartupAnimation();
  publishStatus();
}

// Turn off lantern
void turnOffLantern() {
  if (!lanternOn) {
    return;
  }
  
  lanternOn = false;
  showShutdownAnimation();
  
  ledcWrite(MOTOR_PIN, 0);
  clearDualStrips();
  showDualStrips();
  publishStatus();
}

// Show mode indicator
void showModeIndicator() {
  clearDualStrips();
  
  uint32_t indicatorColor;
  switch(currentMode) {
    case 0: indicatorColor = createColor(60, 20, 0); break;  // Orange
    case 1: indicatorColor = createColor(50, 0, 0); break;   // Red
    case 2: indicatorColor = createColor(25, 25, 25); break; // White
    case 3: indicatorColor = createColor(50, 50, 0); break;  // Yellow
    case 4: indicatorColor = createColor(0, 50, 50); break;  // Cyan
    case 5: indicatorColor = createColor(50, 0, 50); break;  // Purple
  }
  
  for (int i = 0; i <= currentMode; i++) {
    setDualPixelColor(i, indicatorColor);
  }
  showDualStrips();
  delay(500);
  
  clearDualStrips();
  showDualStrips();
}

// Startup animation
void showStartupAnimation() {
  for (int i = 0; i < MAX_LED; i++) {
    setDualPixelColor(i, createColor(0, 50, 0));
    showDualStrips();
    delay(50);
  }
  
  for (int speed = 0; speed <= 120; speed += 10) {
    ledcWrite(MOTOR_PIN, speed);
    delay(30);
  }
  
  clearDualStrips();
  showDualStrips();
}

// Shutdown animation
void showShutdownAnimation() {
  int currentSpeed = 120;
  while (currentSpeed > 0) {
    ledcWrite(MOTOR_PIN, currentSpeed);
    currentSpeed -= 10;
    delay(50);
  }
  ledcWrite(MOTOR_PIN, 0);
  
  for (int i = MAX_LED - 1; i >= 0; i--) {
    setDualPixelColor(i, createColor(0, 0, 0));
    showDualStrips();
    delay(50);
  }
}

// Run current mode
void runCurrentMode() {
  if (!lanternOn) {
    return;
  }
  
  switch(currentMode) {
    case 0: traditionalLanternMode(); break;
    case 1: fireMode(); break;
    case 2: rainbowSpinMode(); break;
    case 3: breathingSyncMode(); break;
    case 4: meteorChaseMode(); break;
    case 5: festivalMode(); break;
  }
}

// Mode 1: Traditional lantern
void traditionalLanternMode() {
  uint32_t colors[] = {
    createColor(50, 0, 0),   // Red
    createColor(60, 20, 0),  // Orange
    createColor(50, 50, 0),  // Yellow
    createColor(0, 50, 0),   // Green
    createColor(0, 50, 50),  // Cyan
    createColor(0, 0, 50)    // Blue
  };
  int numColors = 6;
  
  ledcWrite(MOTOR_PIN, 120);
  
  for(int i = 0; i < MAX_LED; i++) {
    int colorIndex = (i + modeOffset) % numColors;
    setDualPixelColor(i, colors[colorIndex]);
  }
  showDualStrips();
  
  if (millis() - modeStartTime > (modeOffset + 1) * 200) {
    modeOffset++;
  }
}

// Mode 2: Fire effect
void fireMode() {
  int motorSpeed = 80 + random(-15, 15);
  ledcWrite(MOTOR_PIN, motorSpeed);
  
  for(int i = 0; i < MAX_LED; i++) {
    int flicker = random(20, 80);
    int r = flicker;
    int g = flicker * 0.3;
    int b = 0;
    setDualPixelColor(i, createColor(r, g, b));
  }
  showDualStrips();
}

// Mode 3: Rainbow spin
void rainbowSpinMode() {
  ledcWrite(MOTOR_PIN, 120);
  
  for(int i = 0; i < MAX_LED; i++) {
    int phase = ((i * 255 / MAX_LED) + (modeOffset * 10)) % 255;
    int r = 0, g = 0, b = 0;
    
    if(phase < 85) {
      r = (255 - phase * 3) / 5;
      g = (phase * 3) / 5;
      b = 0;
    } else if(phase < 170) {
      phase -= 85;
      r = 0;
      g = (255 - phase * 3) / 5;
      b = (phase * 3) / 5;
    } else {
      phase -= 170;
      r = (phase * 3) / 5;
      g = 0;
      b = (255 - phase * 3) / 5;
    }
    
    setDualPixelColor(i, createColor(r, g, b));
  }
  showDualStrips();
  
  if (millis() - modeStartTime > (modeOffset + 1) * 100) {
    modeOffset++;
    if (modeOffset > 25) modeOffset = 0;
  }
}

// Mode 4: Breathing sync - simplified version
void breathingSyncMode() {
  unsigned long cycleTime = (millis() - modeStartTime) % 4000;
  int breathLevel = (cycleTime < 2000) ? 
    map(cycleTime, 0, 2000, 0, 50) : map(cycleTime, 2000, 4000, 50, 0);
  
  for(int i = 0; i < MAX_LED; i++) {
    setDualPixelColor(i, createColor(breathLevel, breathLevel/2, 0));
  }
  showDualStrips();
  
  ledcWrite(MOTOR_PIN, 50 + breathLevel * 2);
}

// Mode 5: Meteor chase
void meteorChaseMode() {
  ledcWrite(MOTOR_PIN, 180);
  
  int position = (modeOffset) % MAX_LED;
  
  clearDualStrips();
  
  setDualPixelColor(position, createColor(30, 30, 30));
  
  for (int i = 1; i <= 3; i++) {
    int tailPos = (position - i + MAX_LED) % MAX_LED;
    int brightness = 30 - (i * 8);
    if (brightness > 0) {
      setDualPixelColor(tailPos, createColor(brightness, brightness, brightness));
    }
  }
  
  showDualStrips();
  
  if (millis() - modeStartTime > (modeOffset + 1) * 50) {
    modeOffset++;
  }
}

// Mode 6: Festival celebration
void festivalMode() {
  uint32_t festiveColors[] = {
    createColor(50, 0, 0),   // Red
    createColor(0, 50, 0),   // Green
    createColor(50, 50, 0),  // Yellow
    createColor(0, 0, 50),   // Blue
    createColor(50, 0, 50)   // Purple
  };
  
  ledcWrite(MOTOR_PIN, 130);
  
  for(int i = 0; i < MAX_LED; i++) {
    if(random(10) > 7) {
      setDualPixelColor(i, festiveColors[random(5)]);
    } else {
      setDualPixelColor(i, createColor(0, 0, 0));
    }
  }
  showDualStrips();
  
  if(random(100) > 95) {
    ledcWrite(MOTOR_PIN, 100 + random(60));
  }
}

// MQTT maintenance task
void maintainMQTT() {
  if (millis() - lastMqttCheck < MQTT_CHECK_INTERVAL) {
    return;
  }
  lastMqttCheck = millis();
  
  if (!mqttClient.connected()) {
    connectToMQTT();
  } else {
    mqttClient.loop();
    
    // Send heartbeat
    if (millis() - lastHeartbeat > HEARTBEAT_INTERVAL) {
      publishHeartbeat();
      lastHeartbeat = millis();
    }
  }
}

void setup() {
  Serial.begin(115200);
  delay(3000);
  
  Serial.println("=== Smart Lantern Debug Enhanced Version ===");
  Serial.println("Version: Complete Debug v1.0");
  
  // Initialize device ID
  initDeviceId();
  
  // First initialize LED and motor (before WiFi)
  Serial.println("Initializing LED strips...");
  strip1.begin();
  strip1.setBrightness(currentBrightness);
  strip1.clear();
  strip1.show();
  
  strip2.begin();
  strip2.setBrightness(currentBrightness);
  strip2.clear();
  strip2.show();
  
  Serial.println("Initializing motor...");
  ledcAttach(MOTOR_PIN, PWM_FREQ, PWM_RESOLUTION);
  ledcWrite(MOTOR_PIN, 0);
  
  // Early gesture sensor initialization (before WiFi)
  Serial.println("Early gesture sensor initialization...");
  Wire.begin(SDA_PIN, SCL_PIN);
  delay(1000);  // Give sensor more startup time
  
  gestureEnabled = initGestureSensor();
  
  // Connect WiFi
  connectToWiFi();
  
  // Setup MQTT and web server (after gesture sensor)
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("Setting up MQTT...");
    mqttClient.setServer(mqtt_broker, mqtt_port);
    mqttClient.setCallback(onMqttMessage);
    
    // Delayed MQTT startup to avoid conflicts with gesture sensor
    delay(1000);
    connectToMQTT();
    
    Serial.println("Setting up Web server...");
    setupWebServer();
  }
  
  Serial.println("\n=== System Status ===");
  Serial.println("Control Methods:");
  if (gestureEnabled) {
    Serial.println("  ✅ Gesture Control: Available");
    Serial.println("     UP: Turn on lantern");
    Serial.println("     DOWN: Turn off lantern");
    Serial.println("     LEFT/RIGHT: Switch light modes");
  } else {
    Serial.println("  ❌ Gesture Control: Unavailable");
    Serial.println("     Check I2C connections and sensor wiring");
  }
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("  ✅ Web Control: Available");
    Serial.print("     Access: http://");
    Serial.println(WiFi.localIP());
    
    Serial.println("  ✅ MQTT Sync: Available");
    Serial.print("     Device ID: ");
    Serial.println(deviceId);
    Serial.print("     MQTT Status: ");
    Serial.println(mqttClient.connected() ? "Connected" : "Connecting...");
  } else {
    Serial.println("  ❌ WiFi: Not connected");
  }
  
  Serial.println("\nDebug Features:");
  Serial.println("  • Enhanced I2C scanning");
  Serial.println("  • Multi-retry gesture sensor init");
  Serial.println("  • Periodic health checks");
  Serial.println("  • Detailed gesture detection logs");
  
  Serial.println("\n🚀 System Ready!");
  Serial.println("Watch serial monitor for gesture detection and health reports");
  
  // Startup indicator animation
  for(int i = 0; i < 3; i++) {
    setDualPixelColor(0, createColor(0, 50, 0));
    showDualStrips();
    delay(200);
    clearDualStrips();
    showDualStrips();
    delay(200);
  }
  
  // Final health check report
  Serial.println("\n=== Initial Health Check ===");
  Serial.print("Gesture sensor: ");
  Serial.println(gestureEnabled ? "READY" : "FAILED");
  Serial.print("WiFi: ");
  Serial.println(WiFi.status() == WL_CONNECTED ? "CONNECTED" : "FAILED");
  Serial.print("MQTT: ");
  Serial.println(mqttClient.connected() ? "CONNECTED" : "PENDING");
  Serial.println("=============================");
}

void loop() {
  // Time-division task scheduling to avoid resource conflicts
  
  // Task 1: Gesture detection (50ms interval) - high priority
  handleGestures();
  
  // Task 2: Light effect operation (real-time)
  runCurrentMode();
  
  // Task 3: MQTT maintenance (100ms interval)
  maintainMQTT();
  
  // Task 4: WiFi monitoring (reduced frequency)
  static unsigned long lastWifiCheck = 0;
  if (millis() - lastWifiCheck > 10000) {  // Check WiFi every 10 seconds
    if (WiFi.status() != WL_CONNECTED) {
      Serial.println("WiFi reconnecting...");
      WiFi.reconnect();
    }
    lastWifiCheck = millis();
  }
  
  // Main loop delay (reduce CPU usage)
  delay(10);
}

Testing the final program, it fully implemented 3 control methods. Below is a screenshot of the final version's Web access:

Direct control of XIAO ESP32C3 through web access

Finally manufactured 2 revolving lanterns for multi-lantern synchronization testing.

Successfully completed testing of the final program, fully implementing 3 control methods

The video below demonstrates successful control of the lantern in 3 ways.

Manufacturing Journey: 18 Weeks of Growth

Phase One: Conception and Design (Weeks 1-6)

The initial idea for the project was simple: make something that lights up. But as I delved deeper into the Fab Academy course, this "thing that lights up" gradually gained clear direction and complex functionality.

WeekMilestoneStatusAchievement
Week 1Project Conception & Requirements Analysis
Week 23D Modeling Design
Week 3Laser Cut Shell
Week 6PCB Circuit Design

The biggest gain from this phase was learning how to transform vague ideas into concrete solutions. Initially, I just wanted to make a "smart lamp," but through requirements analysis, technical research, and feasibility assessment, I finally settled on the revolving lantern as a culturally rich carrier.

Phase Two: Core Technology Breakthrough (Weeks 7-11)

Entering the technical implementation phase, every week was a new challenge. PCB production, sensor debugging, motor control—each link required repeated trial and error and optimization.

WeekMilestoneStatusAchievement
Week 8PCB Production & Soldering
Week 9Gesture Sensor Integration
Week 10Motor & LED Control
Week 11Network Communication Implementation

The key breakthrough in this phase was solving the problem of multi-sensor collaborative work. How do three gesture sensors avoid interfering with each other? How to achieve real-time gesture recognition with limited computing power? These all required finding optimal solutions through practice.

Phase Three: System Integration and Optimization (Weeks 12-17)

After technical verification was completed, came the most challenging system integration phase. How to integrate scattered functional modules into a harmonious whole?

WeekMilestoneStatusAchievement
Week 15Web Control Interface
Week 16Mechanical Structure Integration
Week 17Circular PCB Design

Week 17's PCB design had many problems discovered during the first board's soldering and testing. After troubleshooting and revision, the final PCB design and production were completed.

Current Status: Main Body Fully Complete

Completed Parts ✅

Hardware Manufacturing Completion: 100%

  • Laser-cut wooden shell: Perfect ✨
  • 3D-printed gear system: Smooth operation ✨
  • Circular PCB: Manufacturing complete, soldering in place ✨
  • Motor control: Testing passed ✨
  • LED light effects: Brilliant and colorful ✨

Software Development Completion: 100%

  • Basic firmware: Stable operation ✨
  • Gesture recognition: Algorithm verified ✨
  • Web interface: Fully functional ✨
  • WiFi communication: Stable connection ✨

Current status: Main functions implemented, undergoing final integration and debugging

Minor Hiccups Encountered ⚠️

PCB Design Errors

At the final stages of the project, I discovered a basic error: the gesture sensor, LED strip, and motor control board interface pin sequences on Version 3 PCB were all designed in reverse! This was like installing a reverse steering wheel in a car—all directions were backwards.

Problem Analysis: When designing the PCB, I referenced incorrect pin diagrams, causing wire sequence reversal. Although no components were damaged (thankfully there were protection circuits), the sensors, light strips, and motor couldn't work properly.

Solution: Designed Version 4 PCB

Gear Continuous Operation Problem

After assembling the entire Magical Revolving Lantern, testing quickly revealed frequent stuttering. I removed the PCB compartment to observe and found that C2A was being pushed up after running for a while, causing it to lose grip, as shown in the video below.

So I tried modifying the C2B and C2A gears, as shown below. I increased the gear height from 4mm to 5mm, then added another 4mm wrapper on the shaft part to allow the gears to fit more tightly with the steel shaft.

Redesigned and printed C2B and C2A gears

After replacement, the lantern's rotational stability improved significantly.

This minor incident gave me a deep appreciation for the power of "Murphy's Law" in project management: anything that can go wrong will go wrong. Fortunately, I had reserved enough time buffer, so this small problem wouldn't affect the final demonstration.

Detailed Technical Specifications

Materials and Cost Analysis

The entire project's total cost was controlled within 250 RMB, which is quite economical for similar smart hardware products.

Cost breakdown: Electronic components account for the major portion, mechanical material costs are relatively low

BOM

No.NameQuantityCommentDesignatorFootprintPin CountManufacturerLCSC PriceTotal price(RMB)
110uF110uFC1CAP-TH_BD4.0-P1.50-D0.8-FD2HUAWEI(华威集团)0.12920.13
2100nF1100nFC2CAP-TH_L5.0-W2.5-P5.00-D1.02Dersonic(德尔创)0.12160.12
3WAFER-PH2.0-4PZZ4WAFER-PH2.0-4PZZFAN1,GROVE1,GROVE2,GROVE3CONN-TH_4P-P2.00_FWF20001-S04S22W5B4XUNPU(讯普)0.10450.42
41.0T-4P立贴31.0T-4P立贴GESTURE1,GESTURE2,GESTURE3CONN-SMD_1.0T-4P-LT6BOOMELE(博穆精密)0.38021.14
5PZ254V-11-02P1PZ254V-11-02PJBATT1HDR-TH_2P-P2.54-V-M2XFCN(兴飞)0.08510.09
6WAFER-PH2.0-3PZZ2WAFER-PH2.0-3PZZLEDSTRIP1,LEDSTRIP2CONN-TH_PH2.0D-L-3P3XUNPU(讯普)0.07910.16
7SS-12D10-G0901SS-12D10-G090SW1SW-TH_3P-L12.7-W6.6-P4.70_C99000343693G-Switch(品赞)1.8011.80
8XIAOESP32C31XIAOESP32C3XIAOESP32C3CONN-SMD_11399105423Seeed Studio(矽递科技)3535.00
92541FV-7P-B22541FV-7P-BXIAOL1,XIAOR1HDR-TH_7P-P2.54-V-F7HanElectricity(瀚源)0.29220.58
10XLOT APDS-9960 手势传感器3APDS-9960XLOTJST PH2.0mm4XLOT3090.00
11拓竹 N20-10D侧出轴电机-25rpm (1个)- LA008 bambulab(Toy motor)1N20-10D LA008 bambulabN20-10DSH1.0mm-2P2Bambu lab2929.00
12劲爽5V伏大电流锂电池(Powerful 5V high current lithium battery)1JST2.542劲爽电池3030.00
13可编程全彩RGB灯条214灯珠 RGB灯条(14 LED beads RGB light strip)PH2.03亚博智能 Yahboom1326.00
14椴木板(Limestone board)160cm80cm3mm3030.00
153D打印 PLA/PETG 材料 3D Printing PLA/PETG Materials1Bambu2020.00
16Grove - Universal 4 Pin 20cm Unbuckled Cable1Seeed Studio(矽递科技)44.00
174mm直径钢轴(4mm Diameter Steel Shaft)2直径4mm,长度20mm Diameter 4mm, length 20mm劲功0.040.08
SUM268.52

Performance Indicators

Response Performance:

  • Gesture recognition delay: < 200ms
  • LED color switching: < 50ms
  • WiFi connection time: < 3s
  • Motor startup time: < 500ms
  • MQTT synchronization: <2s

Battery Life:

  • Normal usage mode: About 4 hours

Interaction Range:

  • Gesture recognition distance: 5-30cm
  • WiFi control distance: About 50 meters
  • Multi-device sync: Theoretically any Magical Revolving Lanterns subscribing to the same MQTT channel can achieve synchronization

Comprehensive Application of Digital Manufacturing Skills

This project used almost all manufacturing technologies taught in Fab Academy:

📐 2D/3D Design

  • Fusion 360: Entire lantern structure, including complex gear systems and assembly design
  • EasyEDA: Circuit schematics and PCB layout

⚙️ Subtractive Manufacturing

  • Laser cutting: Precision wooden shell cutting
  • CNC milling: PCB circuit board manufacturing
  • Vinyl cutting: Traditional Chinese paper-cut style patterns on lantern cover panels

🖨️ Additive Manufacturing

  • FDM 3D printing: Complex gears and support structures
  • Multi-color printing: Enhanced visual effects

🔌 Electronic Manufacturing

  • Double-sided PCB design: Based on single-sided PCB design, challenged double-layer PCB design, complete process from schematic to finished product
  • Commercial PCB production: Double-layer PCB manufacturing through JLC
  • Electronic component soldering: Various connector and component soldering processes
  • Debugging and testing: Application of oscilloscopes and logic analyzers

From design to finished product

Quality Control and Testing

Each manufacturing link has corresponding quality control standards:

Mechanical Parts:

  • Dimensional accuracy: ±0.1mm
  • Surface quality: No obvious layer lines
  • Assembly clearance: 0.2-0.5mm

Electronic Parts:

  • Soldering quality: Visual inspection + continuity testing
  • Functional testing: Module-by-module step-by-step verification
  • Complete machine testing: Long-term stability verification

Project Evaluation Standards

Technical Implementation Assessment

I conducted an overall evaluation of the project

Core Function Verification (Total 40 points):

  • ✅ Motor and gear transmission control: 7/10 points (rotating cage rotation quite stable after gear improvement)
  • ⚠️ Gesture recognition system: 7/10 points (sometimes fails)
  • ✅ LED light effect control: 8/10 points
  • ✅ Wireless connection function: 8/10 points

User Experience (Total 25 points):

  • 🔄 Interaction response speed: 8/10 points (continuously optimizing)
  • ✅ Interface aesthetics and usability: 6/10 points
  • ✅ Overall completion quality: 4/5 points

Technical Innovation (Total 25 points):

  • ✅ System complexity: 6/10 points
  • ✅ Manufacturing process difficulty: 7/10 points
  • ✅ Creative uniqueness: 3/5 points

Documentation Completeness (Total 10 points):

  • ✅ Detailed process recording: 4/5 points
  • ✅ Complete open-source materials: 4/5 points

Project Total Score: 72/100 points

Project Significance and Value

Digital Expression of Cultural Heritage

This project is not just a technical showcase, but also a new attempt at cultural inheritance. As a carrier of traditional Chinese culture, how can the revolving lantern maintain its cultural core while bursting with new vitality in the digital age? The Magical Revolving Lantern provides a possible answer.

Preservation of Traditional Elements:

  • Classic hexagonal shape
  • Core concept of rotation creating dynamic beauty
  • Light and shadow changes creating poetic atmosphere

Empowerment by Modern Technology:

  • Controllable rotation and light effects
  • Intelligent interaction methods
  • Networked connection capabilities

Practice of Open Source Spirit

All design files, code, and manufacturing tutorials for the project will be released as open source, hoping to:

  • Provide complete reference cases for other Makers
  • Promote digital innovation of traditional culture
  • Facilitate knowledge sharing in the Fab Lab community

Open Source Content List:

  • Complete 3D model files
  • PCB design files (schematics + layout)
  • Firmware source code
  • Manufacturing tutorials and BOM list
  • Video demonstrations and technical analysis

Reflection and Outlook

18-Week Growth Trajectory

Looking back at this 18-week project journey, the biggest gain was not mastering how many new technologies, but learning how to transform complex ideas into achievable solutions, and how to make reasonable trade-offs under time and resource constraints.

Technical Level Gains:

  • Mastered complete digital manufacturing process chain
  • Learned systematic engineering thinking
  • Improved problem-solving abilities

Non-technical Growth:

  • Project management and time planning abilities
  • Team collaboration and communication skills
  • Open source sharing awareness and habits

Future Improvement Directions

There are still many areas where this project can be improved:

Function Expansion:

  • Add sound sensing for music rhythm effects
  • Include temperature and humidity sensors to create smart ambient lighting
  • Develop mobile APP for richer control options

Performance Optimization:

  • More efficient power management system
  • Faster gesture recognition algorithms
  • More stable wireless communication protocols

Product Development Exploration:

  • Modular design for user customization
  • Further cost optimization to improve cost-effectiveness
  • Build user community to promote creative sharing

Acknowledgments

The completion of this project wouldn't have been possible without the help of many people:

  • Mentor Team: Pradnya Shindekar and Yu Jianfeng for their professional technical guidance and advice
  • Seeed Studio: For financial support, with special thanks to Engineer Xu Guoqun for professional CNC operation guidance, and hardware engineer Qu Xiangnan for professional soldering guidance
  • Chaihuo Makerspace: Provided comprehensive manufacturing equipment and environment
  • Fab Lab Community: Rich open-source resources and experience sharing
  • Family and Friends: Thanks to my wife for her care throughout the learning process
  • Claude AI: Provided valuable suggestions in PCB design and technical solution selection

Conclusion

The Magical Revolving Lantern is not just my Fab Academy final project, but also my exploration of the fusion of traditional culture with modern technology. In this rapidly changing era, how can traditional culture burst with new vitality? How can technology be more humane and caring?

Perhaps the answer lies in each of our small creations, in every bold attempt, in every open-source sharing.

I hope this small Magical Revolving Lantern can ignite more people's passion for creation, love for culture, and longing for the future.