Tutorial 04 of 05

Make it move.

Drive the servo from a subscribed garden value.

Success looks likeYour device reacts when another team's value changes.

You will make the wings react whenever the visitor counter publishes. The example gives you one restrained gesture. Its angles, speed, and character are starting points you must change.

Power matters. Connect servo red to 5V/VIN, brown to GND, and signal to GPIO 18. Keep a common ground. Do not power the servo from the ESP32 3V3 pin.

Four names control the example. SERVO_PIN says where the signal wire goes. The two angles say where the wings rest and open. GESTURE_MS says how long they stay open.

Make one non-blocking gesture

Install the ESP32Servo library. Disconnect USB power before wiring the servo.

  1. Test the safe angle range. Start with 25° and 70°. Hold the servo loose before attaching the wings.

    done when: the horn reaches both positions without buzzing or pushing against the enclosure.

  2. Add movement to the arrival function. The helper calls it only for the topic you subscribed to.

    done when: a visitor message opens the wings.

  3. Return without blocking. Record when the wings should fold. Let loop() return them later instead of using delay().

    done when: climate readings continue publishing during repeated gestures.

  4. Change the choreography. Adjust angle, hold time, easing, or the number of motions. Keep the mechanism within its safe endpoints.

    done when: your movement is recognisably different from the reference twitch.

Add these declarations above showVisitorMessage():

// Load the servo tool.
#include <ESP32Servo.h>

// CHANGE THESE to fit your safe movement.
const int SERVO_PIN = 18;       // signal wire
const int REST_ANGLE = 25;      // wings folded
const int VISITOR_ANGLE = 70;   // wings open
const unsigned long GESTURE_MS = 700; // open time

Servo wings;
unsigned long foldAt = 0;

Add this to setup():

// Attach the servo and move to the safe resting angle.
wings.attach(SERVO_PIN);
wings.write(REST_ANGLE);

Extend showVisitorMessage() from tutorial 03. Keep its Serial output, then add these three lines before its closing brace:

// Open now and remember when to fold.
wings.write(VISITOR_ANGLE);
foldAt = millis() + GESTURE_MS;
Serial.println("gesture: visitor wings open");

Use a loop that services MQTT, the gesture timer, and your one-minute climate timer:

void loop() {
  // Keep Garden Spine receiving and sending.
  spine.loop();

  // Fold the wings when their open time is over.
  bool timeToFold =
    foldAt != 0 && millis() >= foldAt;
  if (timeToFold) {
    wings.write(REST_ANGLE);
    foldAt = 0;
    Serial.println("gesture: wings at rest");
  }

  // Publish climate data on your chosen interval.
  bool timeToPublish =
    millis() - lastReading > SAMPLE_MS;
  if (timeToPublish) {
    lastReading = millis();

    float temperature = dht.readTemperature();
    float humidity = dht.readHumidity();

    spine.publish(
      "temperature",
      temperature,
      "celsius"
    );
    spine.publish(
      "humidity",
      humidity,
      "percent-rh"
    );
  }
}
Expected Serial Monitor output
received garden/entrance/counter/gk-01/count
payload  {"device_id":"gk-01","ts":null,"value":185,"unit":"events","fw":"0.4.0"}
gesture: visitor wings open
gesture: wings at rest
Expected physical result

The wings open when a new visitor-count message arrives, then return after roughly 700 milliseconds. Climate messages continue to appear on the developer view.

If it did not work

Next: break one message on purpose and recover.