DEV Community

張旭豐
張旭豐

Posted on • Edited on

Stop Buying Random Arduino Modules: A Practical Kit for Interactive Devices

Stop Buying Random Arduino Modules: A Practical Kit for Interactive Devices

The biggest mistake beginners make when starting interactive device projects: buying modules without understanding the interaction pattern behind them. You end up with a drawer full of sensors that each do one thing — and no framework for combining them into something that actually responds to the world.

This guide fixes that. Instead of listing modules by name, we categorize them by interaction pattern — the complete chain from detecting something in the environment to producing an output a human can perceive. Each pattern includes the specific modules that make it work, real code you can copy, and the buying links so you get the right parts the first time.

Affiliate disclosure: As an Amazon Associate, I earn from qualifying purchases.


The Five Interaction Patterns You Need

Before buying anything, understand these five patterns. Every interactive Arduino project is some combination of them:

1. Proximity/Distance → Visual Feedback (HC-SR04 / HC-SR501)
2. Environmental Condition → Scheduled Response (DHT22 / BH1750)
3. Touch/Contact → Physical Action ( capacitive soil moisture / force sensor)
4. Vibration/Shock → Alert Output (SW-420)
5. Light Level → Continuous Adjustment (photoresistor / relay)

These five patterns cover 80% of what you actually need for interactive projects. Everything else is a variation.


What a Complete Interactive System Looks Like

Every working interactive device has the same three-part structure:

  • Input: A sensor that reads something from the physical world
  • Controller: Arduino/ESP32 that processes the reading and decides what to do
  • Output: An actuator that produces a perceivable change (light, sound, motion)
[Sensor] → [Arduino] → [Actuator]
  INPUT      PROCESS      OUTPUT
Enter fullscreen mode Exit fullscreen mode

The mistake most beginners make is buying the actuator first (because it looks cool) and then trying to figure out what sensor could possibly connect to it. We do it in the right order: start with the interaction pattern, pick the right sensor, then connect it to whatever output makes sense.


Pattern 1: Proximity Detection → Visual Feedback

Use case: Something detects your presence and responds with light

This is the most common pattern. A distance sensor notices you're there, and the Arduino triggers an LED, a display, or a motor.

The Modules

HC-SR04 ultrasonic sensor — measures distance by sending a 40kHz sound pulse and timing how long until the echo returns. Range: 2cm to 400cm. Works on any surface (unlike IR which struggles with black surfaces). Consumes about 15mA during measurement.

HC-SR501 PIR motion sensor — detects infrared radiation changes (body heat) in a room. Returns digital HIGH/LOW, no distance reading, but uses almost no power and works through non-glass obstacles. Range: up to 7 meters, 120° detection angle.

Smart trash can with HC-SR04 ultrasonic sensor detecting hand distance and triggering automatic lid opening

Project: Smart Trash Can

You approach the trash can. The HC-SR04 detects your hand at 15cm. Arduino triggers a servo to open the lid. You drop your trash. The lid closes after 3 seconds.

#include <NewPing.h>
#include <Servo.h>

#define TRIG_PIN    7
#define ECHO_PIN    6
#define SERVO_PIN   9
#define OPEN_DIST   15   // cm — hand detected
#define CLOSE_DELAY 3000 // ms

NewPing sonar(TRIG_PIN, ECHO_PIN, 400);
Servo lidServo;

bool isOpen = false;
unsigned long closeTime = 0;

void setup() {
  Serial.begin(115200);
  lidServo.attach(SERVO_PIN);
  lidServo.write(0); // closed
}

void loop() {
  int distance = sonar.ping_cm();

  if (!isOpen && distance > 0 && distance < OPEN_DIST) {
    lidServo.write(90); // open
    isOpen = true;
    closeTime = millis() + CLOSE_DELAY;
    Serial.println("Lid opened");
  }

  if (isOpen && millis() > closeTime) {
    lidServo.write(0); // close
    isOpen = false;
    Serial.println("Lid closed");
  }

  delay(50);
}
Enter fullscreen mode Exit fullscreen mode

The Electronics

HC-SR04 VCC  → Arduino 5V
HC-SR04 GND  → Arduino GND
HC-SR04 TRIG → Arduino pin 7
HC-SR04 ECHO → Arduino pin 6 (through 1kΩ resistor)
HC-SR501 VCC → Arduino 5V
HC-SR501 OUT → Arduino pin 2
HC-SR501 GND → Arduino GND
Servo Brown  → Arduino GND
Servo Red    → Arduino 5V (or external 5V if servo is large)
Servo Orange → Arduino pin 9
Enter fullscreen mode Exit fullscreen mode

Testing the HC-SR04

// Standalone test — paste into Arduino IDE
#include <NewPing.h>
#define TRIG_PIN 7
#define ECHO_PIN 6
NewPing sonar(TRIG_PIN, ECHO_PIN, 400);

void setup() { Serial.begin(115200); }
void loop() {
  delay(500);
  Serial.print("Distance: ");
  Serial.print(sonar.ping_cm());
  Serial.println(" cm");
}
Enter fullscreen mode Exit fullscreen mode

Open Serial Monitor at 115200 baud. Hold your hand in front of the sensor. You should see distance readings updating every 500ms. If you get 0, the object is either too close (<2cm) or too far (>400cm) or at an angle the sound can't bounce back from.


Pattern 2: Environmental Condition → Scheduled Response

Use case: The system reads temperature, humidity, or light level and responds based on schedule or threshold

This pattern is about monitoring the environment continuously and triggering outputs at specific times or when conditions exceed limits. Unlike proximity detection (which cares about instant presence), environmental monitoring cares about trends and thresholds.

The Modules

DHT22 temperature and humidity sensor — reads both temperature (0-50°C ±0.5°C) and relative humidity (0-100% ±2%). Slower response time (2 seconds per reading) so it's not suitable for fast events, but it's the standard for room-level monitoring. Uses a single-wire protocol that requires a library.

BH1750 light intensity sensor — measures ambient light in lux (0-65535 lux). More accurate than a simple photoresistor because it gives actual lux readings you can compare against known thresholds (home environments: 50-500 lux, offices: 300-1000 lux). I2C interface, which means you can stack it with other I2C devices.

Bedside table with ESP32, DHT22 temperature sensor, BH1750 light sensor, and sunrise simulation LED strip in modern bedroom

Project: Wake-Up Light System

At 6:00 AM, the DHT22 checks if room temperature is below 18°C. If so, the BH1750 starts gradually increasing LED brightness over 30 minutes to simulate sunrise, compensating for the cold by using warmer light earlier. The LED strip is connected through a relay module.

#include <Wire.h>
#include <BH1750.h>
#include <DHT.h>
#include <RTClib.h>

#define RELAY_PIN   8
#define LED_PIN     9   // PWM pin for MOSFET
#define DHT_PIN     4
#define WAKE_HOUR   6
#define WAKE_MIN    0
#define TEMP_THRESHOLD 18 // °C — cold morning threshold

BH1750 lightMeter;
DHT dht(DHT_PIN, DHT22);
RTC_DS3231 rtc;

int targetLux = 0;    // 0-65535
int currentLux = 0;

void setup() {
  Serial.begin(115200);
  Wire.begin();
  lightMeter.begin();
  dht.begin();
  rtc.begin();

  // Uncomment to set RTC time (run once, then comment out):
  // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

  pinMode(RELAY_PIN, OUTPUT);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW); // relay off initially
}

void loop() {
  DateTime now = rtc.now();

  float temp = dht.readTemperature();
  float hum = dht.readHumidity();
  currentLux = lightMeter.readLightLevel();

  Serial.print("Time: ");
  Serial.print(now.hour());
  Serial.print(":");
  Serial.println(now.minute());
  Serial.print("Temp: ");
  Serial.print(temp);
  Serial.print("°C | Humidity: ");
  Serial.print(hum);
  Serial.println("%");
  Serial.print("Light: ");
  Serial.print(currentLux);
  Serial.println(" lux");

  // Check if it's wake time
  if (now.hour() == WAKE_HOUR && now.minute() == WAKE_MIN) {
    int warmupMinutes = (temp < TEMP_THRESHOLD) ? 45 : 30;
    int warmupSeconds = warmupMinutes * 60;
    int targetBrightness = map(millis() % warmupSeconds, 0, warmupSeconds, 0, 255);

    analogWrite(LED_PIN, targetBrightness);
    digitalWrite(RELAY_PIN, HIGH);  // LED strip on
    Serial.print("Wake light active, brightness: ");
    Serial.println(targetBrightness);
  } else {
    analogWrite(LED_PIN, 0);
    digitalWrite(RELAY_PIN, LOW);
  }

  delay(60000); // Check every minute
}
Enter fullscreen mode Exit fullscreen mode

The Electronics

DHT22 VCC  → Arduino 3.3V or 5V
DHT22 DATA → Arduino pin 4
DHT22 GND  → Arduino GND
BH1750 VCC → Arduino 3.3V
BH1750 GND → Arduino GND
BH1750 SDA → Arduino A4 (SDA)
BH1750 SCL → Arduino A5 (SCL)
Relay VCC  → Arduino 5V
Relay IN   → Arduino pin 8
Relay GND  → Arduino GND
LED strip + → external 12V power (shared ground with Arduino)
LED strip - → MOSFET drain → MOSFET source → GND
Enter fullscreen mode Exit fullscreen mode

Testing the BH1750 Separately

#include <Wire.h>
#include <BH1750.h>
BH1750 lightMeter;

void setup() {
  Serial.begin(115200);
  Wire.begin();
  lightMeter.begin();
  Serial.println("BH1750 Test — move toward/away from light source");
}

void loop() {
  uint16_t lux = lightMeter.readLightLevel();
  Serial.print("Lux: ");
  Serial.println(lux);
  delay(500);
}
Enter fullscreen mode Exit fullscreen mode

Pattern 3: Soil Moisture → Pump Action

Use case: A capacitive sensor detects soil moisture level and triggers a water pump when dry

This pattern differs from the others because it involves higher current (a water pump needs more than Arduino can supply directly). You need a MOSFET or relay to switch the pump on and off.

The Module

Capacitive soil moisture sensor (FC-37 / v1.2) — measures soil moisture via capacitance change, not resistance. The key advantage over resistive sensors: the electrodes don't corrode because no current flows through the soil. Analog output (0-1023), where dry air reads ~400 and fully submerged in water reads ~700-800 depending on calibration.

Balcony garden with Arduino, capacitive soil moisture sensor FC-37 in plant pot, water pump visible with tubing, LCD display showing moisture percentage

Project: Smart Plant Watering

The FC-37 sensor reads soil moisture every hour. When moisture drops below 30% (dry), a 5V water pump activates for 10 seconds. An LCD shows current moisture percentage and last watering time.

#include <LiquidCrystal_I2C.h>
#include <Servo.h>

#define MOISTURE_PIN   A0
#define PUMP_RELAY_PIN 8
#define PUMP_DURATION  10000 // 10 seconds
#define DRY_THRESHOLD  30   // percent
#define CHECK_INTERVAL 3600000 // 1 hour in ms

LiquidCrystal_I2C lcd(0x27, 16, 2);
Servo waterPump;

int moistureRaw = 0;
int moisturePercent = 0;
unsigned long lastCheck = 0;
bool pumpActive = false;
unsigned long pumpStartTime = 0;

void setup() {
  Serial.begin(115200);
  lcd.init();
  lcd.backlight();
  lcd.print("Plant Monitor");
  delay(2000);
  lcd.clear();

  pinMode(MOISTURE_PIN, INPUT);
  pinMode(PUMP_RELAY_PIN, OUTPUT);
  digitalWrite(PUMP_RELAY_PIN, LOW); // pump off
}

int readMoisture() {
  // Take average of 10 readings to reduce noise
  long sum = 0;
  for (int i = 0; i < 10; i++) {
    sum += analogRead(MOISTURE_PIN);
    delay(50);
  }
  int raw = sum / 10;
  // Map: 400 (dry) = 0%, 800 (wet) = 100%
  int percent = map(raw, 400, 800, 0, 100);
  percent = constrain(percent, 0, 100);
  return percent;
}

void loop() {
  if (millis() - lastCheck > CHECK_INTERVAL || lastCheck == 0) {
    moisturePercent = readMoisture();
    lastCheck = millis();

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Moisture: ");
    lcd.print(moisturePercent);
    lcd.print("%");

    Serial.print("Moisture: ");
    Serial.print(moisturePercent);
    Serial.println("%");

    if (moisturePercent < DRY_THRESHOLD && !pumpActive) {
      pumpActive = true;
      pumpStartTime = millis();
      digitalWrite(PUMP_RELAY_PIN, HIGH);
      lcd.setCursor(0, 1);
      lcd.print("Watering...");
      Serial.println("Pump activated");
    }
  }

  if (pumpActive && millis() - pumpStartTime > PUMP_DURATION) {
    pumpActive = false;
    digitalWrite(PUMP_RELAY_PIN, LOW);
    lcd.setCursor(0, 1);
    lcd.print("Done!        ");
    Serial.println("Pump deactivated");
  }

  delay(100);
}
Enter fullscreen mode Exit fullscreen mode

Pattern 4: Vibration Detection → Alert Output

Use case: A vibration sensor detects machine movement or impacts and triggers an alarm

Industrial and maker-space applications: monitor 3D printers, CNC machines, or any equipment that shouldn't be running outside hours. The SW-420 outputs digital HIGH when vibration is detected.

The Module

SW-420 vibration sensor — a spring-based normally-closed switch that opens when vibration is detected. Simpler than accelerometers, cheaper, and works well for threshold-based detection (is something moving or not?). The module includes a comparator chip that outputs digital HIGH for 2 seconds after each vibration event before resetting.

Workshop bench with Arduino Nano, SW-420 vibration sensor on 3D printer motor, LED warning light mounted above, maker space environment

Project: Machine Vibration Monitor

Monitors a 3D printer or CNC machine. When vibration is detected outside normal hours (weekdays after 10 PM, weekends after 8 PM), a red LED warning light turns on and stays on for 5 minutes after the last vibration event. If it keeps triggering, something might be wrong with the machine.

#define VIB_PIN       2  // Digital pin for SW-420
#define LED_RED_PIN   9  // PWM for red warning LED
#define LED_GREEN_PIN 10 // PWM for green status LED
#define ALERT_HANG    300000 // 5 min in ms after last vibration
#define WEEKDAY_START 22 // 10 PM
#define WEEKEND_START 20 // 8 PM

bool alertActive = false;
unsigned long lastVibration = 0;

bool isOffHours() {
  // Uses millis() for time-of-day — works without RTC module
  // Note: millis() resets after ~49 days. For production, use RTC.
  unsigned long ms = millis();
  // Simplified: assume 5-second cycle, estimate hour
  // In real use: attach RTC DS3231 for accurate time
  return true; // Always on for demo — replace with real RTC check
}

void setup() {
  Serial.begin(115200);
  pinMode(VIB_PIN, INPUT);
  pinMode(LED_RED_PIN, OUTPUT);
  pinMode(LED_GREEN_PIN, OUTPUT);
  digitalWrite(LED_GREEN_PIN, HIGH); // Green = system OK
}

void loop() {
  int vibration = digitalRead(VIB_PIN);

  if (vibration == HIGH) {
    lastVibration = millis();
    Serial.println("Vibration detected!");
    if (isOffHours()) {
      alertActive = true;
      digitalWrite(LED_GREEN_PIN, LOW);
    }
  }

  if (alertActive && millis() - lastVibration > ALERT_HANG) {
    alertActive = false;
    digitalWrite(LED_GREEN_PIN, HIGH);
    digitalWrite(LED_RED_PIN, LOW);
    Serial.println("Alert cleared");
  }

  if (alertActive) {
    // Pulse red LED
    int pulse = (millis() / 500) % 2;
    digitalWrite(LED_RED_PIN, pulse ? HIGH : LOW);
  }

  delay(100);
}
Enter fullscreen mode Exit fullscreen mode

Pattern 5: Light Level → Continuous Adjustment

Use case: Ambient light changes continuously and the system adjusts output accordingly

Unlike a simple on/off light sensor, this pattern uses dimming — the output changes proportionally to the input. Street lights that get brighter as it gets darkerer, camera aperture that adjusts automatically.

The Modules

Photoresistor (GL5528) — simple analog component, resistance decreases as light increases. Not precise (every unit is different) but cheap and works for threshold-based projects. Reads 0-1023 on Arduino analog pin.

Relay module (1-channel) — switches AC or high-current DC loads. NOT for dimming AC lights (use a triac / dimmer module for that). The relay is for on/off control of higher-power devices from Arduino's 5V logic.

Project: Ambient Light Compensation for Display Case

A museum or gallery display case has LED lighting. As ambient daylight changes throughout the day, a photoresistor detects the change, and the Arduino adjusts LED brightness to keep the light inside the case constant. This prevents UV-sensitive artifacts from being exposed to varying light levels.

#define PHOTORES_PIN   A0
#define RELAY_PIN       8
#define TARGET_LUX     200  // Target internal lux (arbitrary units)
#define SAMPLE_SIZE    10

int lastBrightness = 0;

void setup() {
  Serial.begin(115200);
  pinMode(PHOTORES_PIN, INPUT);
  pinMode(RELAY_PIN, OUTPUT);
  Serial.println("Ambient Light Compensation — Display Case");
}

int readLight() {
  long sum = 0;
  for (int i = 0; i < SAMPLE_SIZE; i++) {
    sum += analogRead(PHOTORES_PIN);
    delay(10);
  }
  return sum / SAMPLE_SIZE;
}

void adjustLight(int current) {
  // current < TARGET_LUX: too dark, increase brightness
  // current > TARGET_LUX: too bright, decrease brightness
  int diff = TARGET_LUX - current;
  int adjust = map(diff, -1023, 1023, -50, 50); // steps of 50ms

  if (abs(diff) > 20) { // Deadband: don't adjust for small changes
    lastBrightness = constrain(lastBrightness + adjust, 0, 255);
    // Simulate PWM dimming by rapid relay flickering (use MOSFET for real dimming)
    // Here we just print the target — real implementation needs MOSFET on PWM
    Serial.print("Adjusting: current=");
    Serial.print(current);
    Serial.print(" | target=");
    Serial.print(TARGET_LUX);
    Serial.print(" | brightness=");
    Serial.println(lastBrightness);
  }
}

void loop() {
  int lightLevel = readLight();
  adjustLight(lightLevel);
  delay(500);
}
Enter fullscreen mode Exit fullscreen mode

The Five-Pattern Starter Kit

Rather than buying modules one at a time and wondering if they'll work together, buy them as a kit with a purpose:

For Proximity + Visual Feedback

For Environmental Monitoring

For Contact + Physical Action

For Vibration Monitoring

For Light + Dimming

Controller

Power + Wiring

Total kit cost: approximately $65-75 for enough modules to build all five interaction patterns.


The Testing Habit

Every module in this guide comes with a standalone test snippet — copy it into a blank Arduino sketch, upload, open Serial Monitor. If the reading looks right, the module works and you understand how to use it. If it doesn't, you know exactly which module to troubleshoot rather than debugging a full project where the problem could be anywhere.

This is the habit that separates people who actually finish projects from people who have a drawer full of sensors they bought and never used.


What to Build First

Start with Pattern 1 (HC-SR04 + servo) — it's the simplest complete loop: sensor detects something, Arduino processes, actuator moves. Once that works, add Pattern 2 (add a DHT22 and log temperature to Serial). Then Pattern 3 (replace the Serial output with a pump relay). Each pattern is a building block.

The modules don't care which order you learn them in. But without the interaction pattern framework, you end up with a box full of parts and no idea how they fit together.



Next Step: From Scene to Sensor, Without Writing Code

If this guide gave you ideas for your own setup — but you're not sure which sensors and outputs work best for your specific space — I can help you map that out.

I offer a personalized interactive device design guide at Fiverr:

👉 https://www.fiverr.com/phd_hfchang/generate-an-arduino-interactive-prototypef

What you get:

  • A custom guide based on your actual scene (not generic recommendations)
  • Sensor selection matched to user behavior and physical constraints
  • Interaction logic without needing to write code from scratch
  • Testing methodology with pass/fail criteria for each output

Tags: arduino, modules, sensors, interactive, beginner, esp32, hc-sr04, dht22, pir-sensor, smart-home

Top comments (0)