You Want Electronic Components for your project. 

For more information, contact us at: 

Develop a Functional Smart System Prototype


This lesson guides you through the complete lifecycle of developing a smart system prototype: requirements analysis, system design, implementation, testing, and iterative refinement.

Prototype Development Lifecycle

Phase Activity Example (Smart Irrigator)
Phase 1: Requirements Define what the system must do. Identify stakeholders, use cases, success criteria. Water when soil < 30%, stop at 70%, log to cloud, alert on pump fault.
Phase 2: System design Select components, define architecture, design state machine, plan MQTT topics. Sensor: capacitive soil. Actuator: solenoid via relay. Protocol: MQTT. Cloud: ThingsBoard.
Phase 3: Implementation Build hardware, write firmware, configure cloud platform, develop dashboards. Wire ESP32 + sensor + relay. Upload firmware. Create ThingsBoard device + dashboard.
Phase 4: Testing Verify each subsystem independently, then test the integrated system. Document results. Unit test: sensor reads correctly. Integration test: pump activates at correct threshold. System test: cloud receives data.
Phase 5: Refinement Analyse test results, identify gaps, improve. Repeat until acceptance criteria are met. Soil sensor gives noisy readings → add rolling average. False pump activations → add hysteresis.

Prototype 1: Smart Environmental Monitor with Auto-Response

System Requirements

  • Monitor temperature (DHT22), humidity (DHT22), and ambient light (LDR) continuously.
  • Automatically activate a fan relay if temperature exceeds 30°C (hysteresis off at 26°C).
  • Activate a buzzer alert if humidity exceeds 80%.
  • Control LED brightness proportionally to inverse light level (auto night light).
  • Publish all sensor readings and system state to ThingsBoard every 15 seconds.
  • Receive RPC commands from ThingsBoard to manually override fan and buzzer.
  • Log all state transitions to Serial Monitor with timestamp (from NTP).
  • Operate correctly even when Wi-Fi or cloud is unavailable (local autonomy).

Hardware Connections

Component GPIO Purpose
DHT22 DATA GPIO 4 Temperature + humidity
LDR GPIO 34 (ADC1) Light level (with 10 kΩ pull-down to GND)
Fan relay IN GPIO 16 HIGH = relay on = fan ON
Buzzer GPIO 17 Active buzzer: HIGH = on
LED (PWM) GPIO 18 Auto brightness (0–255)
Status LED GPIO 2 (onboard) Blinks during cloud publish

Complete Firmware — Smart Environmental Monitor

C/C++ — Unit 7 Smart System Prototype (complete firmware)
#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <ArduinoJson.h>
#include <time.h>

// ── WiFi + Cloud ──────────────────────────────────────────────────
const char* WIFI_SSID = "YourNetwork";
const char* WIFI_PASS = "YourPassword";
const char* TB_HOST   = "mqtt.thingsboard.cloud";
const char* TB_TOKEN  = "YOUR_TOKEN";
const char* TELEM     = "v1/devices/me/telemetry";
const char* ATTR      = "v1/devices/me/attributes";
const char* RPC_SUB   = "v1/devices/me/rpc/request/+";
const char* RPC_RESP  = "v1/devices/me/rpc/response/";

// ── Hardware pins ─────────────────────────────────────────────────
#define DHT_PIN    4
#define LDR_PIN   34
#define FAN_PIN   16
#define BUZZ_PIN  17
#define LED_PIN   18
#define STATUS_LED 2

// ── Thresholds ────────────────────────────────────────────────────
const float FAN_ON   = 30.0,  FAN_OFF   = 26.0;
const float BUZZ_ON  = 80.0,  BUZZ_OFF  = 70.0;

// ── State ─────────────────────────────────────────────────────────
bool fanActive   = false, buzzActive   = false;
bool fanOverride = false, buzzOverride = false;  // RPC override flags

// ── Timing ────────────────────────────────────────────────────────
unsigned long lastRead = 0, lastPub = 0;
const long READ_INT = 3000, PUB_INT = 15000;

DHT          dht(DHT_PIN, DHT22);
WiFiClient   wc;
PubSubClient mqtt(wc);

// ── RPC Handler ───────────────────────────────────────────────────
void onRPC(char* topic, byte* payload, unsigned int len) {
  StaticJsonDocument<128> req;
  deserializeJson(req, payload, len);
  String method = req["method"].as<String>();
  String tid = String(topic);
  String rid = tid.substring(tid.lastIndexOf('/') + 1);

  if (method == "setFan") {
    fanOverride = fanActive = req["params"]["state"];
    digitalWrite(FAN_PIN, fanActive ? HIGH : LOW);
    Serial.println(fanActive ? "RPC: Fan ON" : "RPC: Fan OFF");
  } else if (method == "setBuzzer") {
    buzzOverride = buzzActive = req["params"]["state"];
    digitalWrite(BUZZ_PIN, buzzActive ? HIGH : LOW);
  } else if (method == "clearOverrides") {
    fanOverride = buzzOverride = false;
    Serial.println("RPC: Overrides cleared — auto mode restored");
  }
  mqtt.publish((String(RPC_RESP) + rid).c_str(), "{\"result\":\"ok\"}");
}

// ── Connect helpers ───────────────────────────────────────────────
void connectWiFi() {
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) delay(500);
  configTime(7200, 0, "pool.ntp.org");   // Rwanda: UTC+2
  Serial.println("Wi-Fi: " + WiFi.localIP().toString());
}

void connectMQTT() {
  while (!mqtt.connected()) {
    if (mqtt.connect("smart-env-01", TB_TOKEN, "")) {
      mqtt.subscribe(RPC_SUB);
      mqtt.publish(ATTR, "{\"firmware\":\"v1.0\",\"location\":\"NEDU-Lab\"}");
    } else { delay(3000); }
  }
}

// ── Automation logic (local — no cloud dependency) ────────────────
void applyAutomation(float temp, float hum) {
  if (fanOverride || buzzOverride) return;   // Manual override active

  // Fan hysteresis control
  if      (!fanActive  && temp >= FAN_ON)  { fanActive  = true;  digitalWrite(FAN_PIN,  HIGH); Serial.println("Auto: Fan ON  (temp=" + String(temp,1) + ")"); }
  else if ( fanActive  && temp <= FAN_OFF) { fanActive  = false; digitalWrite(FAN_PIN,  LOW);  Serial.println("Auto: Fan OFF"); }

  // Buzzer humidity alert
  if      (!buzzActive && hum  >= BUZZ_ON) { buzzActive = true;  digitalWrite(BUZZ_PIN, HIGH); Serial.println("Auto: Buzzer ON (hum=" + String(hum,1) + ")"); }
  else if ( buzzActive && hum  <= BUZZ_OFF){ buzzActive = false; digitalWrite(BUZZ_PIN, LOW);  }
}

// ── Setup ─────────────────────────────────────────────────────────
void setup() {
  Serial.begin(115200);
  dht.begin();
  analogSetAttenuation(ADC_11DB);
  for (int p : {FAN_PIN, BUZZ_PIN, LED_PIN, STATUS_LED}) pinMode(p, OUTPUT);
  connectWiFi();
  mqtt.setServer(TB_HOST, 1883);
  mqtt.setCallback(onRPC);
  connectMQTT();
}

// ── Loop ──────────────────────────────────────────────────────────
void loop() {
  if (!mqtt.connected()) connectMQTT();
  mqtt.loop();
  unsigned long now = millis();

  if (now - lastRead >= READ_INT) {
    lastRead = now;
    float temp = dht.readTemperature();
    float hum  = dht.readHumidity();
    int   ldr  = analogRead(LDR_PIN);

    if (!isnan(temp) && !isnan(hum)) {
      // Auto-brightness: dark room → bright LED
      int brightness = constrain(map(ldr, 100, 3900, 255, 0), 0, 255);
      analogWrite(LED_PIN, brightness);
      applyAutomation(temp, hum);

      if (now - lastPub >= PUB_INT) {
        lastPub = now;
        digitalWrite(STATUS_LED, HIGH);
        StaticJsonDocument<256> doc;
        doc["temperature"] = round(temp * 10) / 10.0;
        doc["humidity"]    = round(hum  * 10) / 10.0;
        doc["light_raw"]   = ldr;
        doc["brightness"]  = brightness;
        doc["fan_state"]   = fanActive  ? 1 : 0;
        doc["buzz_state"]  = buzzActive ? 1 : 0;
        doc["override"]    = (fanOverride || buzzOverride) ? 1 : 0;
        doc["rssi"]        = WiFi.RSSI();
        char payload[256];
        serializeJson(doc, payload);
        mqtt.publish(TELEM, payload);
        Serial.println(payload);
        digitalWrite(STATUS_LED, LOW);
      }
    } else {
      Serial.println("Sensor error — maintaining last state");
    }
  }
}

ThingsBoard Dashboard for the Smart System

Widget Configuration Layout note
Temperature gauge Radial gauge, key: temperature, range 0–50°C. Colour zones: green 0–28, yellow 28–32, red 32+. Row 1 (most important)
Humidity gauge Radial gauge, key: humidity, range 0–100%. Colour zones: green 30–70, yellow 70–80, red 80+. Row 1
Light trend chart Time-series chart, keys: light_raw + brightness. Last 30 minutes. Dual-axis if needed. Row 2
System status table Latest Values table showing: fan_state, buzz_state, override, rssi. Row 2
Alarm widget Alarms table: configure alarm rules for temp > 32 and hum > 80. Row 3
Fan switch Control switch widget: calls setFan RPC with {state: true/false}. Row 3 — control panel
Buzzer button RPC button widget: calls setBuzzer RPC. Row 3 — control panel
Clear overrides RPC button: calls clearOverrides to return to automatic mode. Row 3 — control panel

Testing the Prototype

Unit Testing — Each Component Separately

Test Verification steps
Sensor test Verify DHT22 reads make sense. Compare to reference thermometer. Apply isnan() check. Verify LDR responds to light changes.
Actuator test Manually drive FAN_PIN, BUZZ_PIN, LED_PIN via Serial commands. Confirm correct electrical response. Check fan relay clicks audibly.
Wi-Fi test Verify connection, NTP sync, and IP address in Serial Monitor.
MQTT test Verify MQTT connect succeeds. Use MQTT Explorer to confirm telemetry arrives at broker. Confirm RPC subscription active.
Cloud test Verify ThingsBoard Latest Telemetry updates with correct values. Confirm all 8 telemetry keys appear.

Integration Testing — Full System

Test scenario Expected result
Temperature trigger Cover DHT22 with your hand to raise temperature above 30°C. Verify fan relay activates within one read cycle (3 s). Verify ThingsBoard fan_state changes to 1.
Humidity trigger Breathe on the DHT22 to raise humidity above 80%. Verify buzzer activates. Verify ThingsBoard logs the buzz_state change.
Manual override Click Fan ON button in dashboard. Verify fan activates regardless of temperature. Cover DHT22 — fan should stay on despite any automation logic.
Clear override Click Clear Overrides. Verify automation logic resumes. Fan should turn off if temperature is below threshold.
Offline test Disable Wi-Fi router. Verify device continues reading sensors and activating actuators based on local thresholds. Re-enable Wi-Fi. Verify reconnection and data resumes.
Sensor failure test Disconnect DHT22 DATA pin. Verify device prints ‘Sensor error’ but does not crash. Verify actuators maintain last state (not reset to off).

Performance Metrics and Refinement

Metric What to measure Targets and fixes
Response latency Time from threshold breach to actuator activation. Target: < 10 s. If > 30 s: reduce READ_INTERVAL.
Publish success rate Count of MQTT publishes vs successful deliveries. Target: > 95%. If lower: check Wi-Fi RSSI and broker connection.
Sensor accuracy Compare DHT22 readings to a calibrated reference. Offset > 2°C: apply calibration constant in firmware.
False activations Count fan or buzzer activations that do not correspond to real events. If high: widen hysteresis gap or add rolling average to sensor reads.
Cloud data gaps Time periods with no telemetry in ThingsBoard history. If gaps > 30 s: investigate connection drops. Add offline buffering.
Power consumption Measure current draw in normal operation vs peak (fan + buzzer). If > expected: add ESP32 deep sleep between readings for battery-powered versions.

Check Your Understanding — Lesson 7.3

?
1. During integration testing, you find that the fan activates and deactivates 4–5 times per minute when temperature is near 30°C. Which two firmware changes would solve this, and what are the specific values you would apply?
?
2. The offline test reveals that when Wi-Fi drops, the device continues local automation correctly but when Wi-Fi reconnects, no buffered data is sent. Which Unit 5 pattern addresses this, and outline the key code structure needed.
?
3. A colleague suggests simplifying the prototype by removing the RPC override functionality and controlling the fan only through automatic sensor thresholds. Provide TWO operational reasons why manual override capability is essential in a deployed smart system.

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.