Firefighting Robot
This project features an autonomous, Arduino-controlled firefighting rover that utilizes a tri-directional infrared sensor array to locate and extinguish flames while wirelessly syncing real-time telemetry and manual override controls with a cross-platform mobile application interface.
Devices & Components
1
1 relay module 5 Vdc 10A (assembled)
1
Grove - Servo
1
20W Adjustable DC-DC Buck Converter with Digital Display
1
L298 driver for DC/Stepper motors
1
Arduino Uno Rev3
1
40 colored female-female jumper wires
1
40 colored male-female jumper wires
1
Bluetooth Low Energy 4.0 Module - HM-10
3
FlameSensor
2
12V Battery
1
Acrylic Chassis and 4 geared motors
1
WaterPump
Software & Tools
Arduino IDE
Project description
Code
firefighter_robot_final
cpp
Working
1#include <ArduinoJson.h> 2#include <avr/wdt.h> 3 4// ==================== CONFIGURATION ==================== 5#define SERIAL_BAUD 9600 6#define FIRE_DEBOUNCE_MS 200 7#define PUMP_MAX_ON_MS 7000 8#define SPRAY_DURATION_MS 4000 9#define WIGGLE_ON_MS 300 // how long each wiggle direction runs 10#define WIGGLE_OFF_MS 150 // dead stop between direction reversals 11#define MAX_MOVE_DURATION 2000 12#define APPROACH_DURATION_MS 350 13#define TURN_DURATION_MS 60 14#define HEARTBEAT_INTERVAL 1000 15 16// ==================== PIN DEFINITIONS ==================== 17#define L_SENSOR 4 18#define M_SENSOR 3 19#define R_SENSOR 2 20#define IN1 5 21#define IN2 6 22#define IN3 7 23#define IN4 8 24#define PUMP_PIN 9 25 26// ==================== STATE ==================== 27bool robotEnabled = true; 28bool manualMode = false; 29bool emergencyActive = false; 30bool pumpActive = false; 31bool isMoving = false; 32bool wasFireDetected = false; 33bool wiggleLeft = true; 34 35// Non-blocking wiggle sub-state 36enum WigglePhase { WIGGLE_IDLE, WIGGLE_MOVING, WIGGLE_PAUSING }; 37WigglePhase wigglePhase = WIGGLE_IDLE; 38unsigned long wigglePhaseStart = 0; 39 40unsigned long movementStartTime = 0; 41unsigned long pumpStartTime = 0; 42unsigned long lastSensorSend = 0; 43 44int sensorValues[3] = {0, 0, 0}; 45unsigned long lastDebounceTime[3] = {0, 0, 0}; 46int lastStableState[3] = {0, 0, 0}; 47 48enum AutoState { 49 AUTO_IDLE, 50 AUTO_TURNING, 51 AUTO_MOVING, 52 AUTO_SPRAYING, 53 AUTO_COOLDOWN 54}; 55AutoState autoState = AUTO_IDLE; 56unsigned long autoStateStart = 0; 57 58// Static char buffer — no String heap fragmentation on AVR 59char inputBuf[65]; 60uint8_t inputLen = 0; 61 62// ==================== SERIAL ==================== 63// Single send only — no burst. Double-sending at 9600 baud overflows the 64// 64-byte AVR TX buffer and stalls the sketch waiting on UART drain. 65void sendJson(JsonDocument& doc) { 66 serializeJson(doc, Serial); 67 Serial.println(); 68} 69 70void sendAck(const char* cmd, const char* status) { 71 StaticJsonDocument<96> doc; 72 doc["type"] = "ACK"; 73 doc["cmd"] = cmd; 74 doc["status"] = status; 75 doc["ts"] = millis(); 76 sendJson(doc); 77} 78 79void sendEvent(const char* type) { 80 StaticJsonDocument<64> doc; 81 doc["type"] = type; 82 doc["ts"] = millis(); 83 sendJson(doc); 84} 85 86void sendFireAlert(const char* sensor) { 87 StaticJsonDocument<96> doc; 88 doc["type"] = "FIRE_ALERT"; 89 doc["sensor"] = sensor; 90 doc["ts"] = millis(); 91 sendJson(doc); 92} 93 94void sendSensorData() { 95 StaticJsonDocument<256> doc; 96 doc["type"] = "SENSOR_STATUS"; 97 doc["left"] = sensorValues[0]; 98 doc["middle"] = sensorValues[1]; 99 doc["right"] = sensorValues[2]; 100 doc["fire"] = (sensorValues[0] || sensorValues[1] || sensorValues[2]) ? 1 : 0; 101 doc["pump"] = pumpActive ? 1 : 0; 102 doc["mode"] = manualMode ? "manual" : "auto"; 103 doc["emergency"] = emergencyActive ? 1 : 0; 104 doc["moving"] = isMoving ? 1 : 0; 105 doc["enabled"] = robotEnabled ? 1 : 0; 106 doc["state"] = (int)autoState; 107 doc["ts"] = millis(); 108 sendJson(doc); 109} 110 111// ==================== MOVEMENT (navigation) ==================== 112// _motorsOff() — raw pin write, no flag update. 113// Called before every direction change to kill back-EMF on L298N, 114// preventing the 5V brownout that was crashing the AVR. 115void _motorsOff() { 116 digitalWrite(IN1, LOW); digitalWrite(IN2, LOW); 117 digitalWrite(IN3, LOW); digitalWrite(IN4, LOW); 118} 119 120void stopMovement() { 121 _motorsOff(); 122 isMoving = false; 123} 124 125void forward() { 126 if (emergencyActive || !robotEnabled) return; 127 _motorsOff(); 128 digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); 129 digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW); 130 isMoving = true; 131 movementStartTime = millis(); 132} 133 134void back() { 135 if (emergencyActive || !robotEnabled) return; 136 _motorsOff(); 137 digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH); 138 digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH); 139 isMoving = true; 140 movementStartTime = millis(); 141} 142 143void driveLeft() { 144 if (emergencyActive || !robotEnabled) return; 145 _motorsOff(); 146 digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH); // left wheel back 147 digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW); // right wheel forward 148 isMoving = true; 149 movementStartTime = millis(); 150} 151 152void driveRight() { 153 if (emergencyActive || !robotEnabled) return; 154 _motorsOff(); 155 digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); // left wheel forward 156 digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH); // right wheel back 157 isMoving = true; 158 movementStartTime = millis(); 159} 160 161// ==================== WIGGLE MOVEMENT (spray only) ==================== 162// These drive only ONE side — robot rocks gently in place instead of 163// tank-spinning 360. Never call these from navigation code. 164void wiggleMotorLeft() { 165 if (emergencyActive || !robotEnabled) return; 166 _motorsOff(); // kill momentum first 167 digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); // left wheel forward only 168 digitalWrite(IN3, LOW); digitalWrite(IN4, LOW); // right wheel stopped 169 isMoving = true; 170} 171 172void wiggleMotorRight() { 173 if (emergencyActive || !robotEnabled) return; 174 _motorsOff(); 175 digitalWrite(IN1, LOW); digitalWrite(IN2, LOW); // left wheel stopped 176 digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW); // right wheel forward only 177 isMoving = true; 178} 179 180// ==================== PUMP ==================== 181void activatePump() { 182 if (emergencyActive || !robotEnabled) return; 183 // Always write the pin — self-heals stale pumpActive flag from prior crash 184 digitalWrite(PUMP_PIN, LOW); 185 pumpActive = true; 186 pumpStartTime = millis(); 187 sendEvent("PUMP_ON"); 188} 189 190void deactivatePump() { 191 digitalWrite(PUMP_PIN, HIGH); 192 pumpActive = false; 193 wigglePhase = WIGGLE_IDLE; 194 stopMovement(); 195 sendEvent("PUMP_OFF"); 196} 197 198// ==================== SPRAY (non-blocking) ==================== 199void startSpray() { 200 if (emergencyActive || !robotEnabled) return; 201 stopMovement(); 202 activatePump(); 203 wiggleLeft = true; 204 wigglePhase = WIGGLE_MOVING; 205 wigglePhaseStart = millis(); 206 wiggleMotorLeft(); // first wiggle direction 207 autoState = AUTO_SPRAYING; 208 autoStateStart = millis(); 209} 210 211// Called every loop() tick while in AUTO_SPRAYING. 212// Zero delay() — all timing via millis(). 213void updateSpray() { 214 wdt_reset(); // pet WDT here too — serial writes can eat time 215 216 unsigned long now = millis(); 217 218 // Spray duration elapsed — shut down 219 if (now - autoStateStart >= SPRAY_DURATION_MS) { 220 deactivatePump(); 221 autoState = AUTO_COOLDOWN; 222 autoStateStart = millis(); 223 return; 224 } 225 226 // Pump relay failed silently — abort rather than wiggle dry 227 if (!pumpActive) { 228 wigglePhase = WIGGLE_IDLE; 229 stopMovement(); 230 autoState = AUTO_COOLDOWN; 231 autoStateStart = millis(); 232 return; 233 } 234 235 // Non-blocking wiggle state machine 236 switch (wigglePhase) { 237 238 case WIGGLE_MOVING: 239 // Current direction has run long enough — stop 240 if (now - wigglePhaseStart >= WIGGLE_ON_MS) { 241 stopMovement(); 242 wigglePhase = WIGGLE_PAUSING; 243 wigglePhaseStart = now; 244 } 245 break; 246 247 case WIGGLE_PAUSING: 248 // Dead-stop gap elapsed — fire opposite direction 249 if (now - wigglePhaseStart >= WIGGLE_OFF_MS) { 250 wiggleLeft = !wiggleLeft; 251 if (wiggleLeft) wiggleMotorLeft(); else wiggleMotorRight(); 252 wigglePhase = WIGGLE_MOVING; 253 wigglePhaseStart = now; 254 } 255 break; 256 257 case WIGGLE_IDLE: 258 // Recover from unexpected idle during spray 259 wiggleLeft = true; 260 wiggleMotorLeft(); 261 wigglePhase = WIGGLE_MOVING; 262 wigglePhaseStart = now; 263 break; 264 } 265} 266 267// ==================== SENSORS ==================== 268void readSensors() { 269 int raw[3] = { 270 (digitalRead(L_SENSOR) == LOW), 271 (digitalRead(M_SENSOR) == LOW), 272 (digitalRead(R_SENSOR) == LOW) 273 }; 274 unsigned long now = millis(); 275 for (int i = 0; i < 3; i++) { 276 if (raw[i] != lastStableState[i]) { 277 lastDebounceTime[i] = now; 278 lastStableState[i] = raw[i]; 279 } 280 if ((now - lastDebounceTime[i]) >= FIRE_DEBOUNCE_MS) { 281 sensorValues[i] = raw[i]; 282 } 283 } 284} 285 286// ==================== AUTO STATE MACHINE ==================== 287void updateAutoFireFighting() { 288 if (!robotEnabled || manualMode || emergencyActive) return; 289 290 switch (autoState) { 291 292 case AUTO_IDLE: { 293 // Snapshot into locals — prevents mid-switch sensor glitch 294 bool L = sensorValues[0]; 295 bool M = sensorValues[1]; 296 bool R = sensorValues[2]; 297 if (!L && !M && !R) break; 298 299 // Set autoState BEFORE movement call so we always leave AUTO_IDLE 300 // even if a movement guard fires. Prevents double-trigger next loop. 301 if (L && M && R) { autoState = AUTO_MOVING; autoStateStart = millis(); forward(); } 302 else if (L && M) { autoState = AUTO_TURNING; autoStateStart = millis(); driveLeft(); } 303 else if (M && R) { autoState = AUTO_TURNING; autoStateStart = millis(); driveRight(); } 304 else if (M) { autoState = AUTO_MOVING; autoStateStart = millis(); forward(); } 305 else if (L) { autoState = AUTO_TURNING; autoStateStart = millis(); driveLeft(); } 306 else if (R) { autoState = AUTO_TURNING; autoStateStart = millis(); driveRight(); } 307 break; 308 } 309 310 case AUTO_TURNING: 311 if (millis() - autoStateStart >= TURN_DURATION_MS) { 312 stopMovement(); 313 autoState = AUTO_MOVING; // state set before forward() 314 autoStateStart = millis(); 315 forward(); 316 } 317 break; 318 319 case AUTO_MOVING: 320 if (millis() - autoStateStart >= APPROACH_DURATION_MS) { 321 stopMovement(); 322 startSpray(); // sets autoState = AUTO_SPRAYING internally 323 } 324 break; 325 326 case AUTO_SPRAYING: 327 updateSpray(); 328 break; 329 330 case AUTO_COOLDOWN: 331 if (millis() - autoStateStart >= 1500) { 332 autoState = AUTO_IDLE; 333 } 334 break; 335 } 336} 337 338// ==================== COMMANDS ==================== 339void processCommand(const char* cmd) { 340 if (strcmp(cmd, "emergency_stop") == 0) { 341 emergencyActive = true; 342 autoState = AUTO_IDLE; 343 wigglePhase = WIGGLE_IDLE; 344 stopMovement(); 345 if (pumpActive) deactivatePump(); 346 sendAck("emergency_stop", "OK"); 347 return; 348 } 349 if (strcmp(cmd, "reset_emergency") == 0) { 350 emergencyActive = false; 351 robotEnabled = true; 352 autoState = AUTO_IDLE; 353 sendAck("reset_emergency", "OK"); 354 return; 355 } 356 if (emergencyActive) { sendAck(cmd, "LOCKED"); return; } 357 358 if (strcmp(cmd, "robot_on") == 0) { robotEnabled = true; sendAck(cmd, "OK"); } 359 else if (strcmp(cmd, "robot_off") == 0) { robotEnabled = false; stopMovement(); if (pumpActive) deactivatePump(); sendAck(cmd, "OK"); } 360 else if (strcmp(cmd, "manual_on") == 0) { manualMode = true; stopMovement(); autoState = AUTO_IDLE; sendAck(cmd, "OK"); } 361 else if (strcmp(cmd, "manual_off") == 0) { manualMode = false; stopMovement(); autoState = AUTO_IDLE; sendAck(cmd, "OK"); } 362 else if (strcmp(cmd, "pump_on") == 0 && manualMode) { activatePump(); sendAck(cmd, "OK"); } 363 else if (strcmp(cmd, "pump_off") == 0 && manualMode) { deactivatePump(); sendAck(cmd, "OK"); } 364 else if (strcmp(cmd, "f") == 0 && manualMode) { forward(); sendAck(cmd, "OK"); } 365 else if (strcmp(cmd, "b") == 0 && manualMode) { back(); sendAck(cmd, "OK"); } 366 else if (strcmp(cmd, "l") == 0 && manualMode) { driveLeft(); sendAck(cmd, "OK"); } 367 else if (strcmp(cmd, "r") == 0 && manualMode) { driveRight(); sendAck(cmd, "OK"); } 368 else if (strcmp(cmd, "s") == 0) { stopMovement(); sendAck(cmd, "OK"); } 369} 370 371void readBluetoothCommands() { 372 while (Serial.available()) { 373 char c = Serial.read(); 374 if (c == '\n') { 375 // Strip trailing \r — some BT modules send \r\n 376 if (inputLen > 0 && inputBuf[inputLen - 1] == '\r') inputLen--; 377 inputBuf[inputLen] = '\0'; 378 if (inputLen > 0) processCommand(inputBuf); 379 inputLen = 0; 380 } else if (inputLen < 64) { 381 inputBuf[inputLen++] = c; 382 } else { 383 inputLen = 0; // overflow — discard and restart 384 } 385 } 386} 387 388// ==================== SETUP / LOOP ==================== 389void setup() { 390 Serial.begin(SERIAL_BAUD); 391 wdt_enable(WDTO_8S); 392 393 pinMode(L_SENSOR, INPUT_PULLUP); 394 pinMode(M_SENSOR, INPUT_PULLUP); 395 pinMode(R_SENSOR, INPUT_PULLUP); 396 397 pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT); 398 pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT); 399 pinMode(PUMP_PIN, OUTPUT); 400 401 digitalWrite(PUMP_PIN, HIGH); // relay is active-LOW — HIGH = off 402 stopMovement(); 403} 404 405void loop() { 406 wdt_reset(); 407 408 readBluetoothCommands(); 409 410 // Manual move timeout — only when idle and not spraying 411 if (isMoving && !pumpActive && autoState == AUTO_IDLE) { 412 if (millis() - movementStartTime >= MAX_MOVE_DURATION) stopMovement(); 413 } 414 415 // Hard pump safety timeout — enforced regardless of state 416 if (pumpActive && (millis() - pumpStartTime >= PUMP_MAX_ON_MS)) { 417 deactivatePump(); 418 autoState = AUTO_COOLDOWN; 419 autoStateStart = millis(); 420 } 421 422 readSensors(); 423 424 // REPORTING SHIELD — only emit fire events when fully idle. 425 // Sensors are unreliable during spray (water/steam disrupts IR). 426 if (autoState == AUTO_IDLE) { 427 bool fireNow = (sensorValues[0] || sensorValues[1] || sensorValues[2]); 428 if (fireNow && !wasFireDetected) { 429 wasFireDetected = true; 430 if (sensorValues[0]) sendFireAlert("LEFT"); 431 else if (sensorValues[1]) sendFireAlert("MIDDLE"); 432 else sendFireAlert("RIGHT"); 433 } else if (!fireNow && wasFireDetected) { 434 wasFireDetected = false; 435 sendEvent("FIRE_CLEARED"); 436 } 437 } 438 439 updateAutoFireFighting(); 440 441 if (millis() - lastSensorSend >= HEARTBEAT_INTERVAL) { 442 sendSensorData(); 443 lastSensorSend = millis(); 444 } 445}
Downloadable files
firefighting_robot
Android Studio, Flutter mobile application.
firefighting_robot.zip
firerobot
Circuit diagram.
firerobot.jpeg

fireapp
application
fireapp.jpeg

Comments
Only logged in users can leave comments