You Want Electronic Components for your project. 

For more information, contact us at: 

Control Actuators Based on Sensor Inputs to Achieve Desired System Behaviour


Lesson 4.3 — Control Actuators Based on Sensor Inputs to Achieve Desired System Behaviour

The full power of IoT is realised when sensor data drives real-world actions. This lesson builds complete closed-loop systems — systems where the output is automatically determined by the sensor input according to defined logic.

Open Loop vs Closed Loop Control

Property Open Loop (Timer-Based) Closed Loop (Feedback-Based)
Definition Output action is predetermined, not affected by sensor feedback Sensor feedback continuously adjusts the output to reach a desired state
Example Timer-controlled irrigation: water for 10 minutes every day Soil moisture sensor irrigation: water until soil reaches 70% moisture
Stability Drifts over time if conditions change Self-correcting, adapts to changes
Complexity Simple to implement Requires sensor + threshold + actuator + feedback loop
IoT example Time-based street light schedule LDR-based street light that responds to actual darkness
KEY
All control systems in this lesson are closed-loop: the sensor measures a condition, the program compares it to a setpoint, and the actuator corrects the condition. This is the foundational model for smart systems everywhere — from thermostats to self-driving cars.

Project 1: Smart Temperature-Controlled Fan

Component Connection Role
DHT22 DATA Arduino pin 4 Temperature and humidity source
L298N ENA Arduino pin 9 (PWM) Fan speed via PWM (0–255)
L298N IN1 Arduino pin 7 Motor direction
L298N IN2 Arduino pin 8 Motor direction (LOW for forward)
Status LED Arduino pin 13 HIGH when fan running
L298N motor power 12 V external supply Motor supply (separate from Arduino 5 V)
C/C++ — Smart Temperature-Controlled Fan System
#include <DHT.h>
#define DHT_PIN 4
#define ENA_PIN 9   // PWM speed control
#define IN1_PIN 7
#define IN2_PIN 8
#define LED_PIN 13

DHT dht(DHT_PIN, DHT22);

const float FAN_ON_TEMP  = 28.0;  // °C: turn fan ON above this
const float FAN_OFF_TEMP = 25.0;  // °C: turn fan OFF below this (hysteresis)
const int   FAN_SPEED    = 200;   // PWM: 0–255
bool fanRunning   = false;
unsigned long lastRead    = 0;
const long READ_INTERVAL  = 3000;

void setFan(bool on, int speed = FAN_SPEED) {
  if (on) {
    digitalWrite(IN1_PIN, HIGH); digitalWrite(IN2_PIN, LOW);
    analogWrite(ENA_PIN, speed); digitalWrite(LED_PIN, HIGH);
  } else {
    analogWrite(ENA_PIN, 0);     digitalWrite(LED_PIN, LOW);
  }
  fanRunning = on;
}

void setup() {
  Serial.begin(9600); dht.begin();
  pinMode(IN1_PIN, OUTPUT); pinMode(IN2_PIN, OUTPUT); pinMode(LED_PIN, OUTPUT);
  setFan(false);
  Serial.println("Fan controller ready.");
}

void loop() {
  if (millis() - lastRead >= READ_INTERVAL) {
    lastRead = millis();
    float temp = dht.readTemperature();
    float hum  = dht.readHumidity();
    if (isnan(temp)) { Serial.println("Sensor error — maintaining last fan state"); return; }
    Serial.print("T:"); Serial.print(temp,1); Serial.print("C H:"); Serial.print(hum,1);
    Serial.print("% Fan:"); Serial.println(fanRunning ? "ON" : "OFF");
    // Hysteresis control
    if (!fanRunning && temp >= FAN_ON_TEMP)  { setFan(true);  Serial.println(">>> Fan ON"); }
    else if (fanRunning && temp <= FAN_OFF_TEMP) { setFan(false); Serial.println(">>> Fan OFF"); }
  }
}

Project 2: Automatic Night Light (LDR + PWM LED)

An LDR measures ambient light. The LED brightness is continuously and inversely proportional to the light level — the darker the environment, the brighter the LED. This is a proportional (P) controller, the simplest form of closed-loop feedback control.

C/C++ — Automatic Night Light with Smooth Dimming
#define LDR_PIN A0
#define LED_PIN 9   // PWM pin

int ledBrightness = 0;
int ldrSmooth     = 0;
unsigned long lastUpdate = 0;

void setup() { Serial.begin(9600); pinMode(LED_PIN, OUTPUT); }

void loop() {
  if (millis() - lastUpdate >= 100) {
    lastUpdate = millis();
    int rawLDR = analogRead(LDR_PIN);
    ldrSmooth  = (ldrSmooth * 7 + rawLDR) / 8;  // Exponential moving average
    // high light (bright ~800) → dim LED | low light (dark ~100) → bright LED
    int targetBrightness = constrain(map(ldrSmooth, 100, 800, 255, 0), 0, 255);
    // Gradual transition (avoid sudden jumps)
    if (ledBrightness < targetBrightness) ledBrightness++;
    else if (ledBrightness > targetBrightness) ledBrightness--;
    analogWrite(LED_PIN, ledBrightness);
    Serial.print("LDR:"); Serial.print(ldrSmooth);
    Serial.print(" LED:"); Serial.println(ledBrightness);
  }
}

TRY
Open the Serial Plotter (Tools → Serial Plotter) while this code is running and cover/uncover the LDR with your hand. You will see the LDR and LED lines move inversely in real time — closed-loop feedback visualised.

Project 3: Smart Security System — State Machine with Multi-Actuator Response

State Behaviour
IDLE No motion, no close object. All outputs off. Check PIR every loop, HC-SR04 every 200 ms.
ALERT PIR triggered. LED blinks slowly (500 ms), buzzer beeps once, relay stays off. If HC-SR04 detects object within 50 cm: upgrade to ALARM.
ALARM Close intrusion confirmed. LED fast-blink (100 ms), continuous buzzer tone, relay ON (simulates siren/camera). Return to IDLE only after 10 s with no further motion.
C/C++ — Security System State Machine
#define PIR_PIN    5
#define TRIG_PIN   9
#define ECHO_PIN   10
#define LED_PIN    6
#define BUZZER_PIN 8
#define RELAY_PIN  7

enum State { IDLE, ALERT, ALARM };
State currentState    = IDLE;
unsigned long stateEnteredAt = 0;
unsigned long lastBlinkTime  = 0;
bool ledState = false;

long measureDistance() {
  digitalWrite(TRIG_PIN, LOW);  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
  long d = pulseIn(ECHO_PIN, HIGH, 20000);
  return d == 0 ? 999 : d * 0.0343 / 2;
}

void setState(State s) {
  currentState = s; stateEnteredAt = millis();
  noTone(BUZZER_PIN); digitalWrite(RELAY_PIN, LOW); digitalWrite(LED_PIN, LOW);
  Serial.print("State → ");
  Serial.println(s == IDLE ? "IDLE" : s == ALERT ? "ALERT" : "ALARM");
}

void setup() {
  Serial.begin(9600);
  pinMode(PIR_PIN, INPUT);
  pinMode(TRIG_PIN, OUTPUT); pinMode(ECHO_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);  pinMode(BUZZER_PIN, OUTPUT); pinMode(RELAY_PIN, OUTPUT);
  setState(IDLE);
}

void loop() {
  unsigned long now = millis();
  bool motion  = digitalRead(PIR_PIN) == HIGH;
  long distCm  = measureDistance();

  switch (currentState) {
    case IDLE:
      if (motion) setState(ALERT);
      break;

    case ALERT:
      // Slow LED blink
      if (now - lastBlinkTime >= 500) {
        lastBlinkTime = now; ledState = !ledState; digitalWrite(LED_PIN, ledState);
      }
      if (distCm < 50) setState(ALARM);
      if (!motion && (now - stateEnteredAt > 10000)) setState(IDLE);
      break;

    case ALARM:
      // Fast LED blink + continuous alarm
      if (now - lastBlinkTime >= 100) {
        lastBlinkTime = now; ledState = !ledState; digitalWrite(LED_PIN, ledState);
      }
      tone(BUZZER_PIN, 2000);
      digitalWrite(RELAY_PIN, HIGH);
      if (!motion && (now - stateEnteredAt > 10000)) setState(IDLE);
      break;
  }
}

KEY
A state machine is a fundamental design pattern for IoT control systems. Instead of a tangle of nested if/else statements, the system exists in one well-defined state at a time, and transitions between states based on sensor events. This makes the system easier to understand, test, and extend.

Actuator Safety Guidelines

Safety rule Explanation
Never exceed GPIO current limits Arduino/ESP32 GPIO pins source/sink max 40 mA each, 200 mA total. Always use driver transistors or relay modules for loads above 20 mA.
Flyback diodes on inductive loads Motors, relays, and solenoids produce back-EMF spikes when switched off. Always place a flyback diode (1N4007) across the coil: cathode toward +V, anode toward GND.
Separate power supplies for motors DC motors draw large transient currents at startup (3–5× running current). Use a separate supply — powering a motor from the MCU supply causes voltage sags that reset the MCU.
Galvanic isolation for mains Relay modules provide galvanic isolation between the MCU (5 V) and mains (230 V). Optocoupler-based relay modules are safer — they optically isolate the control and power sides.
Heat dissipation High-power actuators and driver ICs generate heat. L298N running a motor may need a heatsink on the IC package.
Test without loads first Verify your control logic works (LED as placeholder) before connecting real loads like pumps or motors. This prevents accidents when code has bugs.

Check Your Understanding — Lesson 4.3

?
1. In the smart fan controller, why is hysteresis used (28°C to turn on, 25°C to turn off) rather than a single threshold (e.g. 27°C for both)? What practical problem does this solve?

?
2. Draw the state diagram for the security system (IDLE, ALERT, ALARM states). Label each state with its active outputs and each arrow with its transition condition.

?
3. A student connects a 12 V solenoid valve directly to an ESP32 GPIO pin set HIGH. List THREE problems this will cause and explain the correct circuit topology to safely control the solenoid from the ESP32.

I'm an Embedded & AIoT Systems Engineer and the Founder & CEO of Nyereka Tech, a Rwanda-based startup enabling access to STEM tools, training, and innovation. I'm passionate about empowering young innovators through practical technology education and local manufacturing solutions.