Skip to content

LED Status Indicators

LED Status Indicators

This section covers the LED indicator system that provides visual feedback for the RFID-based check-in/check-out operations. Learning this section will enable you to:

  • Design and implement LED status indicators for user feedback
  • Handle different LED states (check-in, check-out, error, ready)
  • Implement MQTT-controlled LED patterns
  • Troubleshoot common LED circuit issues

Before starting this section, ensure you have:

  • LED circuit wired per 05-02
  • MQTT communication between ESP32 and Node-RED working
  • Check-in/check-out flow implemented (05-08)
  • Basic understanding of ESP32 GPIO control

The system uses two LEDs to convey the current state:

LED ColorStateMeaning
Green ONCheck-in successTag registered, time recording started
Red ONCheck-out successTime recorded in TimeTagger
Green blinkSystem readyESP32 connected and waiting
Red blinkErrorConnection issue or API failure
Both OFFDeep sleep or idleNo activity or power saving
User Experience Timeline:
1. User approaches reader → Both LEDs OFF (idle)
2. User taps RFID tag → Momentary pause
3. ✓ Check-in confirmed → GREEN LED for 3 seconds
4. User leaves → LEDs OFF
5. User returns, taps again → Momentary pause
6. ✓ Check-out confirmed → RED LED for 3 seconds
7. Recording saved → Quick blink, then OFF
// LED Pin Definitions
#define LED_GREEN 22
#define LED_RED 21
// LED Timing Constants
const unsigned long LED_INDICATE_TIME = 3000; // 3 seconds
const unsigned long BLINK_INTERVAL = 250; // 250ms for blinking
// LED State Management
enum LEDState {
LED_OFF,
LED_GREEN_ON,
LED_RED_ON,
LED_GREEN_BLINK,
LED_RED_BLINK,
LED_BOTH_BLINK
};
LEDState currentLEDState = LED_OFF;
unsigned long ledStateStartTime = 0;
void setLED(LEDState state) {
currentLEDState = state;
ledStateStartTime = millis();
switch (state) {
case LED_OFF:
digitalWrite(LED_GREEN, LOW);
digitalWrite(LED_RED, LOW);
break;
case LED_GREEN_ON:
digitalWrite(LED_GREEN, HIGH);
digitalWrite(LED_RED, LOW);
break;
case LED_RED_ON:
digitalWrite(LED_GREEN, LOW);
digitalWrite(LED_RED, HIGH);
break;
case LED_GREEN_BLINK:
case LED_RED_BLINK:
case LED_BOTH_BLINK:
// Blinking is handled in the main loop
break;
}
}
void updateLED() {
unsigned long now = millis();
switch (currentLEDState) {
case LED_GREEN_ON:
case LED_RED_ON:
// Auto-turn off after indication time
if (now - ledStateStartTime >= LED_INDICATE_TIME) {
setLED(LED_OFF);
}
break;
case LED_GREEN_BLINK:
// Blink green (system ready indicator)
if ((now / BLINK_INTERVAL) % 2 == 0) {
digitalWrite(LED_GREEN, HIGH);
} else {
digitalWrite(LED_GREEN, LOW);
}
break;
case LED_RED_BLINK:
// Blink red (error indicator)
if ((now / BLINK_INTERVAL) % 2 == 0) {
digitalWrite(LED_RED, HIGH);
} else {
digitalWrite(LED_RED, LOW);
}
break;
case LED_BOTH_BLINK:
// Alternate blink (system starting)
if ((now / BLINK_INTERVAL) % 2 == 0) {
digitalWrite(LED_GREEN, HIGH);
digitalWrite(LED_RED, LOW);
} else {
digitalWrite(LED_GREEN, LOW);
digitalWrite(LED_RED, HIGH);
}
break;
case LED_OFF:
default:
// Do nothing — LEDs stay off
break;
}
}

The ESP32 listens for MQTT feedback from Node-RED and updates LEDs accordingly:

void setup() {
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_RED, OUTPUT);
// Startup indication
setLED(LED_BOTH_BLINK);
// ... rest of setup
// Ready indication
setLED(LED_GREEN_BLINK); // Blinking green = system ready
}
// MQTT callback for feedback
void callback(char* topic, byte* payload, unsigned int length) {
// Parse the message
String message;
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.print("Feedback received: ");
Serial.println(message);
// Parse JSON
DynamicJsonDocument doc(256);
DeserializationError error = deserializeJson(doc, message);
if (error) {
Serial.println("JSON parse error");
setLED(LED_RED_BLINK);
return;
}
String action = doc["action"] | "";
bool success = doc["success"] | true;
if (success) {
if (action == "CHECK_IN") {
// Green = checked in
setLED(LED_GREEN_ON);
Serial.println("✓ Check-in successful");
}
else if (action == "CHECK_OUT") {
// Red = checked out
setLED(LED_RED_ON);
Serial.println("✓ Check-out successful");
}
} else {
// Error state
setLED(LED_RED_BLINK);
Serial.print("✗ Operation failed: ");
Serial.println(doc["error"].as<String>());
}
}

Indicate WiFi and MQTT connection status through LED patterns:

void setup() {
// Startup sequence
setLED(LED_BOTH_BLINK);
// Connect to WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
// Rapid blink during connection attempt
digitalWrite(LED_GREEN, !digitalRead(LED_GREEN));
}
// WiFi connected → blink green
setLED(LED_GREEN_BLINK);
// Connect to MQTT
client.setServer(mqtt_server, 1883);
while (!client.connected()) {
if (client.connect("ESP32_RFID")) {
// MQTT connected → solid brief green
setLED(LED_GREEN_ON);
delay(500);
} else {
delay(2000);
}
}
// System ready
setLED(LED_GREEN_BLINK); // Slow blink = waiting for tags
}

For cleaner code organization:

class LEDManager {
private:
int greenPin;
int redPin;
LEDState currentState;
unsigned long stateStartTime;
unsigned long indicationDuration;
public:
LEDManager(int gp, int rp) : greenPin(gp), redPin(rp) {
pinMode(greenPin, OUTPUT);
pinMode(redPin, OUTPUT);
currentState = LED_OFF;
stateStartTime = 0;
indicationDuration = 3000;
}
void indicateCheckIn() {
setState(LED_GREEN_ON);
}
void indicateCheckOut() {
setState(LED_RED_ON);
}
void indicateError() {
setState(LED_RED_BLINK);
}
void indicateReady() {
setState(LED_GREEN_BLINK);
}
void indicateStartup() {
setState(LED_BOTH_BLINK);
}
void turnOff() {
setState(LED_OFF);
}
void update() {
unsigned long now = millis();
switch (currentState) {
case LED_GREEN_ON:
case LED_RED_ON:
if (now - stateStartTime >= indicationDuration) {
turnOff();
}
break;
case LED_GREEN_BLINK:
if ((now / 250) % 2 == 0) {
digitalWrite(greenPin, HIGH);
digitalWrite(redPin, LOW);
} else {
digitalWrite(greenPin, LOW);
}
break;
case LED_RED_BLINK:
if ((now / 250) % 2 == 0) {
digitalWrite(redPin, HIGH);
digitalWrite(greenPin, LOW);
} else {
digitalWrite(redPin, LOW);
}
break;
case LED_BOTH_BLINK:
if ((now / 150) % 2 == 0) {
digitalWrite(greenPin, HIGH);
digitalWrite(redPin, LOW);
} else {
digitalWrite(greenPin, LOW);
digitalWrite(redPin, HIGH);
}
break;
case LED_OFF:
default:
digitalWrite(greenPin, LOW);
digitalWrite(redPin, LOW);
break;
}
}
private:
void setState(LEDState state) {
currentState = state;
stateStartTime = millis();
}
};
// Global instance
LEDManager leds(LED_GREEN, LED_RED);

The Node-RED flow sends MQTT feedback messages to control the LEDs:

// Function Node: "Generate LED Feedback"
// Creates appropriate feedback message based on operation result
const uid = msg.payload?.uid || "UNKNOWN";
const operation = msg.action || "UNKNOWN";
const success = msg.success !== false;
if (success) {
if (operation === "CHECK_IN") {
msg.payload = {
action: "CHECK_IN",
success: true,
name: msg.record?.ds || uid,
message: "Time recording started"
};
} else if (operation === "CHECK_OUT") {
msg.payload = {
action: "CHECK_OUT",
success: true,
name: msg.record?.ds || uid,
duration: (msg.record?.t2 - msg.record?.t1) || 0,
message: "Time recorded successfully"
};
}
} else {
msg.payload = {
action: "ERROR",
success: false,
uid: uid,
error: msg.error || "Unknown error",
message: "Operation failed"
};
}
msg.topic = "asset/tracking/feedback";
msg.retain = false;
return msg;

Test the LED system:

// 1. Power on the ESP32
// Expected: Both LEDs alternate blink during startup
// Then: Green LED blinks slowly (system ready)
// 2. Scan a registered RFID tag (first time)
// Expected: Green LED ON for 3 seconds (check-in)
// 3. Scan the same tag again
// Expected: Red LED ON for 3 seconds (check-out)
// 4. Disconnect WiFi temporarily
// Expected: Red LED blinks (connection error)
// 5. Reconnect WiFi
// Expected: Green LED blinks (system ready)

Verification Checklist:

  • Startup sequence: both LEDs blink
  • System ready: green LED slow blink
  • Check-in: green LED solid for 3 seconds
  • Check-out: red LED solid for 3 seconds
  • Error: red LED blink
  • WiFi connected: green LED pattern
  • WiFi lost: red LED pattern

Symptom: LEDs barely visible even in indoor lighting

Cause: Resistor value too high

Solution:

// Try lower resistor values:
// For 10mA current:
// R = (3.3V - 2.0V) / 0.01A = 130Ω → Use 150Ω
// For 15mA (max recommended):
// R = (3.3V - 2.0V) / 0.015A = 87Ω → Use 100Ω

Symptom: LEDs are uncomfortably bright

Solution: Increase resistor value or use PWM for software brightness control:

// Software brightness control using PWM
void setLEDBrightness(int pin, int brightness) {
// brightness: 0-255
analogWrite(pin, brightness);
}
// Use in setup:
// ledcSetup(0, 5000, 8); // Channel 0, 5kHz, 8-bit
// ledcAttachPin(LED_GREEN, 0);
// ledcWrite(0, 128); // 50% brightness

Symptom: Green LED lights up for check-out instead of check-in

Cause: LED wiring reversed (cathode/anode swapped) or code logic error

Solution:

// Verify wiring: LED anode (+) → GPIO via resistor → LED cathode (-) → GND
// Check with multimeter: GPIO HIGH should light the LED
// If reversed:
// digitalWrite(pin, HIGH) → OFF (if LED wired reversed)
// digitalWrite(pin, LOW) → ON
// Swap in code:
// #define LED_ON LOW
// #define LED_OFF HIGH
  • Recommended: Use consistent LED meanings (green = good, red = bad)
  • Recommended: Include startup test sequence so users know both LEDs work
  • Recommended: Use non-blocking LED control (millis() instead of delay())
  • Avoid: Using delay() in the LED control code (blocks other operations)
  • Avoid: Driving LEDs at maximum current — 10mA is sufficient for indication
  1. Green LED: System ready (blink), check-in success (3s solid)
  2. Red LED: Error (blink), check-out success (3s solid)
  3. Non-blocking control: Use millis() timers, not delay()
  4. MQTT feedback: Node-RED publishes LED commands to asset/tracking/feedback
  5. Startup sequence: verify both LEDs are functional at power-on

Writing Date: 2026-05-17
Based on Source File: 校正版/10 Time recording witht RFID und TimeTagger.md
Target Audience: Alibaba.com IoT Pre-sales Engineer
Status: ✅ Completed