Saturday, 24 October 2026

AI Smart Mountain Road Safety System with Fog Detection

AI Smart Mountain Road Safety System with Fog Detection

Complete IoT + ESP32 + AI Agent + n8n Automation + Telegram Voice Alerts + Google Sheets + ThingSpeak Project Documentation

The maze sign and curve road up or down the mountain with trick fog or mist in front.This scene is many dangerous for driving in this time.
 
 
 
15 IoT-based Projects for Beginners: Easy Step-by-Step Guide
 
 
 
How does an HC-SR04 Ultrasonic Range Sensor Works and How to Interface with ESP32
 
 
 

1. Project overview

The AI Smart Mountain Road Safety System is an IoT-based prototype designed to detect dangerous fog and poor visibility on mountain roads, monitor environmental conditions, identify potential road hazards, and automatically send warnings to drivers or road monitoring personnel.

The system uses an ESP32 microcontroller to collect sensor readings and send them over Wi-Fi to an automation and cloud platform. An n8n workflow processes the readings, applies decision rules and AI-assisted analysis, logs the results to Google Sheets, updates ThingSpeak charts, and triggers Telegram text or voice notifications.

The project combines embedded electronics, IoT communications, cloud data storage, AI-assisted decision-making, and automated alerts into one system.

Mikrobot | Mikrokontroler ESP-WROOM-32 USB-C CH340 WiFi BT
 
 
 

ESP32 — sensor and control unit

Reads fog-related optical measurements, temperature, humidity, and optional road hazard sensors. Publishes readings to the cloud.

 
Infrared Sensor, photo beam
 – advancedoorsandwindows
 
 
 

Fog detection and environmental sensing

Measures optical transmission or scattering, together with temperature and humidity, to estimate hazardous low-visibility conditions.

 
Mastering n8n: Step-by-Step Beginner’s Guide | Data Science Collective
 
 
 

n8n — automation and AI agent

Receives telemetry, classifies risk, runs an AI analysis when useful, and coordinates logging and notifications.

 
В России впервые оштрафовали за голосовое сообщение / Хабр
 
 
 

Telegram — warning delivery

Sends road safety alerts, with optional generated audio announcements for a monitoring group or control room.

 
ThingSpeak - SparkFun DataLoggerIoT Documentation
 
 
 

Google Sheets and ThingSpeak — monitoring

Google Sheets stores event history and AI predictions; ThingSpeak provides time-series sensor charts and remote monitoring.

Important safety note: This is a prototype design. A low-cost optical sensor can detect changes associated with fog, but it does not automatically provide a calibrated road-visibility distance. Real mountain-road deployment requires suitable visibility instrumentation, weatherproof enclosures, field calibration, reliable communications, and independently validated warning logic. The prototype must not be the sole safety mechanism for a public road.

2. System objectives

  • Detect increasing fog and potentially hazardous visibility conditions.

  • Measure temperature and relative humidity.

  • Monitor optional vehicle approach or obstacle distance.

  • Classify the road condition as Normal, Caution, Danger, or Sensor Fault.

  • Send automated Telegram notifications to road operators.

  • Generate spoken warning messages for remote monitoring.

  • Record sensor data, alerts, and energy predictions in Google Sheets.

  • Display historical sensor readings on ThingSpeak.

  • Use AI to summarize conditions and forecast energy consumption.

  • Provide a web dashboard for live monitoring and system status.

3. System architecture

Mountain road sensor station

Optical visibility sensor · DHT22 · Optional distance sensor · Power monitor

GPIO / I²C / ADC

ESP32 firmware

Read · Validate · Filter · Classify · Publish HTTPS/HTTP telemetry

Wi-Fi or local gateway

n8n automation server

Webhook → validation → rule engine → AI analysis → alert routing

Telegram

Text + voice alerts

Google Sheets

Events + predictions

ThingSpeak

Sensor history

Web dashboard

Current status + trends

End-to-end data flow

  1. Sensors collect environmental and optical data.

  2. ESP32 validates the readings and calculates a filtered fog indicator.

  3. The firmware assigns an initial safety level.

  4. Telemetry is transmitted to an n8n webhook.

  5. n8n checks the payload and evaluates thresholds and recent readings.

  6. If enabled, an AI agent creates a structured explanation or energy estimate.

  7. n8n records the event in Google Sheets and sends sensor values to ThingSpeak.

  8. If a warning condition is confirmed, Telegram sends a text and optionally an audio file.

  9. The web dashboard displays current status and historical trends.

4. Components list

The following is a practical prototype bill of materials. The optical visibility sensor is the most important design choice; a generic rain sensor or ordinary IR obstacle sensor is not a calibrated fog sensor.

Indicative prototype quantities and costs in India; prices vary by supplier and sensor specification.

Component

Qty.

Purpose

Indicative cost

ESP32 DevKit V1

1

Main controller and Wi-Fi

₹400–800

DHT22 / AM2302

1

Temperature and humidity

₹250–500

Optical fog/visibility sensor

1

Fog or optical transmission measurement

₹1,500–15,000+

Optical emitter and receiver, if building a sensor

1 set

Optical attenuation measurement

₹300–1,500

HC-SR04 ultrasonic sensor

1 optional

Nearby obstacle demonstration

₹100–250

INA219 current/voltage sensor

1 optional

Power consumption measurement

₹150–350

OLED I²C display

1 optional

Local status and readings

₹150–350

LEDs, resistors, buzzer

1 set

Local warning indication

₹100–250

Breadboard and jumper wires

1 set

Prototype wiring

₹150–300

Regulated 5 V supply

1

Power source

₹200–500

Weather-resistant enclosure

1

Outdoor protection

₹500–2,000

A prototype using a basic optical detector may cost approximately ₹3,000–8,000. A properly calibrated visibility sensor, outdoor enclosure, and dependable power and communication hardware can increase the total considerably.

Recommended sensor strategy

  • Optical visibility: Use a calibrated visibility sensor, preferably one that reports meteorological optical range (MOR) in metres. If using a homemade optical sensor, treat its reading as a relative fog index until calibrated.

  • Temperature/humidity: DHT22 for a basic prototype; a better outdoor-rated sensor can be used for deployment.

  • Obstacle distance: HC-SR04 is for controlled demonstrations, not a reliable outdoor vehicle detection system in heavy rain or fog.

  • Power monitoring: INA219 measures voltage and current on a suitable DC power rail. It does not directly measure every power source or AC mains consumption.

  • Optional location: GPS can associate alerts with a road segment or station.

5. Circuit schematic diagram

This is a functional wiring schematic for an ESP32 DevKit, DHT22, analog optical sensor module, INA219, LEDs and buzzer.

GitHub - aridoutofrange/ESP32_With_DHT22_and_LDR_Sensor: ESP32 board that's connected to DHT22 and LDR sensor, packed with an OLED display to monitor temperature, humidity, and illuminance of a room. · GitHub
 
 
 

5.1 Pin connection table

Module

Module pin

ESP32 connection

DHT22

VCC

3.3 V

DHT22

GND

GND

DHT22

DATA

GPIO 4

Optical sensor with analog output

VCC

According to module specification

Optical sensor

GND

GND

Optical sensor

Analog OUT

GPIO 34 through a safe voltage interface

INA219

VCC

3.3 V

INA219

GND

GND

INA219

SDA

GPIO 21

INA219

SCL

GPIO 22

Green LED

Anode through 220–330 Ω

GPIO 25

Red LED

Anode through 220–330 Ω

GPIO 26

Buzzer driver

Input

GPIO 27

Optional HC-SR04

TRIG

GPIO 18

Optional HC-SR04

ECHO

GPIO 19 through a voltage divider

Electrical precautions

  • ESP32 GPIOs are not 5 V tolerant. Verify the optical sensor's output voltage and use a divider or suitable level shifter when necessary.

  • Some analog sensor modules have an adjustable digital output as well as an analog output. Use the analog output only if it provides a safe voltage range for the ESP32 ADC.

  • The HC-SR04 Echo pin is typically 5 V; use a resistor divider before connecting it to the ESP32.

  • Use a transistor or MOSFET driver for buzzers that draw more current than a GPIO can supply.

  • Keep outdoor sensor wiring protected from moisture, lightning-induced surges, and electromagnetic interference.

5.2 Optical visibility sensor concept

CS120A: Atmospheric Visibility Sensor
 
 
 
De transmissometer of zichtmeter
 
 
 
Photodiode Infrared Led at James Kaiser blog
 
 
 

A simple transmissive sensor places an emitter and receiver at a known distance. As fog droplets scatter and absorb light, the received optical intensity changes.

For an ideal transmissometer, the extinction coefficient can be estimated as

β=−ln⁡(T)Lβ=−Lln(T)

where:

  • TT is the received-to-reference optical transmission ratio.

  • LL is the optical path length in metres.

  • ββ is the extinction coefficient in m−1m−1.

A commonly used meteorological visibility relationship is

V≈3.912βV≈β3.912

where VV is visibility in metres, under the assumptions associated with the selected contrast threshold.

This calculation requires a suitable optical system, reference measurement, alignment, contamination control, and calibration. A single uncalibrated analog reading cannot be converted directly into trustworthy visibility distance.

6. Software and account setup

Install or create the following:

  1. Arduino IDE with ESP32 board support.

  2. Arduino libraries: DHT sensor library, Adafruit Unified Sensor, and ArduinoJson.

  3. An n8n instance, hosted or self-hosted, with HTTPS for production.

  4. A Telegram bot created through BotFather.

  5. A Google Sheet with a configured Google Sheets credential in n8n.

  6. A ThingSpeak account with a channel and write API key.

  7. An AI provider credential, if using an AI agent or language model.

  8. Optional text-to-speech (TTS) service for generating spoken audio files.

For a first prototype, use one sensor station and one Telegram destination. Add multiple stations after the entire data pipeline works reliably.

7. ESP32 firmware

This example sends sensor telemetry to n8n. It uses an analog optical sensor as a relative indicator, not a calibrated visibility instrument. The threshold values below are starting points for testing and must be tuned to the sensor.

7.1 Configure the firmware

Replace these example settings with your own:

  • Wi-Fi SSID and password.

  • HTTPS webhook URL from your n8n instance.

  • Optical sensor calibration constants.

  • Your selected fog and humidity thresholds.

For deployment, do not publish actual credentials or leave the webhook unprotected.

7.2 ESP32 source code

Install the libraries listed above through Arduino Library Manager.

cpp

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <DHT.h>
#include <math.h>

// ---------- User configuration ----------
const char* WIFI_SSID = "YOUR_WIFI_NAME";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";

const char* N8N_WEBHOOK =
  "https://YOUR_N8N_HOST/webhook/mountain-road";

// Pins
constexpr int DHT_PIN = 4;
constexpr int OPTICAL_PIN = 34;
constexpr int GREEN_LED = 25;
constexpr int RED_LED = 26;
constexpr int BUZZER_PIN = 27;

#define DHT_TYPE DHT22
DHT dht(DHT_PIN, DHT_TYPE);

// Tune after sensor calibration.
// These values are illustrative ADC thresholds, not visibility in metres.
constexpr int FOG_CAUTION_ADC = 1700;
constexpr int FOG_DANGER_ADC = 2300;
constexpr float HIGH_HUMIDITY = 90.0;

constexpr unsigned long SAMPLE_INTERVAL_MS = 10000;
unsigned long lastSample = 0;
uint32_t sequenceNumber = 0;

float filteredOptical = NAN;

void connectWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  Serial.print("Connecting to Wi-Fi");
  unsigned long start = millis();

  while (WiFi.status() != WL_CONNECTED &&
         millis() - start < 20000) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();

  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("Wi-Fi connected: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("Wi-Fi unavailable; will retry.");
  }
}

int readOpticalADC() {
  // Average samples to reduce random noise.
  uint32_t total = 0;
  const int samples = 16;

  for (int i = 0; i < samples; i++) {
    total += analogRead(OPTICAL_PIN);
    delay(5);
  }
  return total / samples;
}

String classifyRisk(int optical, float humidity,
                    bool sensorValid) {
  if (!sensorValid) return "SENSOR_FAULT";

  // Confirm the direction of the ADC response for your sensor.
  // This example assumes higher ADC = greater fog indication.
  if (optical >= FOG_DANGER_ADC &&
      humidity >= HIGH_HUMIDITY) {
    return "DANGER";
  }

  if (optical >= FOG_CAUTION_ADC) {
    return "CAUTION";
  }

  return "NORMAL";
}

void sendTelemetry(int optical, float temperature,
                   float humidity, bool sensorValid,
                   const String& risk) {
  if (WiFi.status() != WL_CONNECTED) {
    connectWiFi();
  }

  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("Skipping upload: Wi-Fi offline");
    return;
  }

  WiFiClientSecure client;

  // For quick testing only. Production must validate the
  // server certificate or use a trusted CA certificate.
  client.setInsecure();

  HTTPClient http;
  http.setTimeout(8000);

  if (!http.begin(client, N8N_WEBHOOK)) {
    Serial.println("Failed to initialize HTTP");
    return;
  }

  http.addHeader("Content-Type", "application/json");

  JsonDocument doc;
  doc["station_id"] = "MOUNTAIN_STATION_01";
  doc["sequence"] = ++sequenceNumber;
  doc["uptime_ms"] = millis();
  doc["optical_adc"] = optical;
  doc["temperature_c"] = temperature;
  doc["humidity_pct"] = humidity;
  doc["sensor_valid"] = sensorValid;
  doc["risk"] = risk;

  // Relative indicator, not calibrated visibility.
  doc["visibility_m"] = nullptr;

  String payload;
  serializeJson(doc, payload);

  int code = http.POST(payload);

  Serial.print("HTTP status: ");
  Serial.println(code);

  if (code > 0) {
    Serial.println(http.getString());
  } else {
    Serial.println(http.errorToString(code));
  }

  http.end();
}

void setup() {
  Serial.begin(115200);

  pinMode(GREEN_LED, OUTPUT);
  pinMode(RED_LED, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);

  digitalWrite(GREEN_LED, LOW);
  digitalWrite(RED_LED, LOW);
  digitalWrite(BUZZER_PIN, LOW);

  analogReadResolution(12);
  dht.begin();
  connectWiFi();
}

void loop() {
  if (millis() - lastSample < SAMPLE_INTERVAL_MS) {
    delay(20);
    return;
  }
  lastSample = millis();

  int optical = readOpticalADC();
  float temperature = dht.readTemperature();
  float humidity = dht.readHumidity();

  bool sensorValid =
      !isnan(temperature) &&
      !isnan(humidity) &&
      temperature >= -40 && temperature <= 80 &&
      humidity >= 0 && humidity <= 100;

  // Low-pass filter optical readings.
  if (isnan(filteredOptical)) {
    filteredOptical = optical;
  } else {
    filteredOptical =
        0.75f * filteredOptical + 0.25f * optical;
  }

  String risk = classifyRisk(
      (int)filteredOptical, humidity, sensorValid);

  bool danger = risk == "DANGER";
  bool normal = risk == "NORMAL";

  digitalWrite(RED_LED, danger ? HIGH : LOW);
  digitalWrite(GREEN_LED, normal ? HIGH : LOW);
  digitalWrite(BUZZER_PIN, danger ? HIGH : LOW);

  Serial.printf(
    "Optical=%d Temp=%.1f C RH=%.1f%% Risk=%s\n",
    optical, temperature, humidity, risk.c_str()
  );

  sendTelemetry(
    optical, temperature, humidity, sensorValid, risk
  );
}

7.3 Firmware implementation notes

  • Some optical modules produce a lower ADC value in fog. Reverse the comparison logic if that is what your hardware measures.

  • The example activates the buzzer while the reading is in the danger category. Add a latching state and hysteresis to avoid repeated on/off transitions near a threshold.

  • The code sends a sample every ten seconds. For real use, consider a different interval, offline buffering, retry limits, and a persistent sequence number.

  • setInsecure() disables certificate verification. Replace it with CA-based TLS verification before exposing the system to real networks.

  • The webhook should require authentication, such as a secret header, and reject malformed or excessively large requests.

8. n8n workflow design

The n8n workflow is the central automation layer. It should validate readings and make deterministic safety decisions before using an AI model.

1. Webhook

Receive JSON from ESP32

2. Validate and normalize

Check fields, ranges and station identity

3. Rule-based risk classification

Normal / Caution / Danger / Sensor Fault

4A. Warning branch

Telegram text and optional voice

4B. Logging branch

Google Sheets + ThingSpeak

5. AI-assisted analysis

Explain the readings and estimate future power use

8.1 Required n8n nodes

Node

Configuration

Webhook

POST path mountain-road, JSON body

Code

Validate input and calculate risk

IF / Switch

Branch by risk level

Google Sheets

Append event or telemetry row

HTTP Request

Send measurements to ThingSpeak

Telegram

Send warning text

TTS HTTP Request

Generate an audio file, if supported by the chosen service

Telegram

Send generated audio or voice message

AI Agent / LLM

Optional analysis and power prediction

Error workflow

Report failures and service outages

Use a production webhook URL, not the temporary test URL, after activating the workflow.

8.2 Example n8n Code node

This code validates the incoming payload and derives a risk category. It assumes the incoming JSON has the fields sent by the ESP32 firmware.

javascript

const input = $json.body ?? $json;

const optical = Number(input.optical_adc);
const temperature = Number(input.temperature_c);
const humidity = Number(input.humidity_pct);

const valid =
  input.station_id === "MOUNTAIN_STATION_01" &&
  input.sensor_valid === true &&
  Number.isFinite(optical) &&
  Number.isFinite(temperature) &&
  Number.isFinite(humidity) &&
  optical >= 0 && optical <= 4095 &&
  temperature >= -40 && temperature <= 80 &&
  humidity >= 0 && humidity <= 100;

let risk = "SENSOR_FAULT";
let message = "Sensor data is invalid or incomplete.";

if (valid) {
  if (optical >= 2300 && humidity >= 90) {
    risk = "DANGER";
    message =
      "Potentially hazardous fog indication. Verify road visibility and activate the approved warning procedure.";
  } else if (optical >= 1700) {
    risk = "CAUTION";
    message =
      "Fog indicator elevated. Increase monitoring and verify visibility.";
  } else {
    risk = "NORMAL";
    message = "No configured fog threshold exceeded.";
  }
}

return [{
  json: {
    station_id: input.station_id ?? "UNKNOWN",
    sequence: input.sequence ?? null,
    received_at: new Date().toISOString(),
    optical_adc: optical,
    temperature_c: temperature,
    humidity_pct: humidity,
    risk,
    message,
    valid,
    alert_required:
      risk === "DANGER" || risk === "SENSOR_FAULT"
  }
}];

This is the minimum classification logic. For a stronger implementation, add rolling averages, sensor-health diagnostics, alert deduplication, a rate limit, and a rule to keep an alert active until the hazard has been cleared for a sustained period.

8.3 Importable n8n workflow JSON

The following starter workflow implements the core telemetry pipeline: receive the ESP32 POST request, classify the readings, and return a JSON response. It is a foundation to import and test before adding credential-dependent integrations.

json

{
  "name": "Mountain Road Safety - ESP32 Ingestion",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "mountain-road",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "10000000-0000-4000-8000-000000000001",
      "name": "ESP32 Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [200, 300],
      "webhookId": "mountain-road-ingest"
    },
    {
      "parameters": {
        "jsCode": "const input = $json.body ?? $json;\nconst optical = Number(input.optical_adc);\nconst temperature = Number(input.temperature_c);\nconst humidity = Number(input.humidity_pct);\nconst valid = input.station_id === 'MOUNTAIN_STATION_01' && input.sensor_valid === true && Number.isFinite(optical) && Number.isFinite(temperature) && Number.isFinite(humidity) && optical >= 0 && optical <= 4095 && temperature >= -40 && temperature <= 80 && humidity >= 0 && humidity <= 100;\nlet risk = 'SENSOR_FAULT';\nlet message = 'Invalid or incomplete sensor data.';\nif (valid) {\n  if (optical >= 2300 && humidity >= 90) {\n    risk = 'DANGER';\n    message = 'Potentially hazardous fog indication. Verify visibility and activate the approved warning procedure.';\n  } else if (optical >= 1700) {\n    risk = 'CAUTION';\n    message = 'Elevated fog indicator. Verify visibility and increase monitoring.';\n  } else {\n    risk = 'NORMAL';\n    message = 'No configured fog threshold exceeded.';\n  }\n}\nreturn [{json:{station_id:input.station_id ?? 'UNKNOWN',sequence:input.sequence ?? null,received_at:new Date().toISOString(),optical_adc:optical,temperature_c:temperature,humidity_pct:humidity,risk,message,valid,alert_required:risk === 'DANGER' || risk === 'SENSOR_FAULT'}}];"
      },
      "id": "10000000-0000-4000-8000-000000000002",
      "name": "Validate and Classify",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [440, 300]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ $json }}",
        "options": {
          "responseCode": 200
        }
      },
      "id": "10000000-0000-4000-8000-000000000003",
      "name": "Return Classification",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [700, 300]
    }
  ],
  "connections": {
    "ESP32 Webhook": {
      "main": [
        [
          {
            "node": "Validate and Classify",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate and Classify": {
      "main": [
        [
          {
            "node": "Return Classification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  }
}

How to import it

  1. Copy the complete JSON into a file named mountain-road-workflow.json.

  2. Open n8n and create a workflow.

  3. Choose the workflow import option and select the JSON file.

  4. Save the workflow and test the Webhook node using its test URL.

  5. Activate the workflow and use the production webhook URL in the ESP32 firmware.

This JSON is an ingestion starter, not the complete connected deployment. Telegram, Google Sheets, ThingSpeak and TTS require their respective credentials and nodes. Add those after confirming that the starter correctly classifies incoming sensor data.

8.4 Add the integrations in n8n

After the Validate and Classify node, connect the following branches.

  • Google Sheets: Append one row for each accepted telemetry event.

  • ThingSpeak: Send the sensor fields through an HTTP Request node.

  • Telegram: Use an IF or Switch node to trigger notifications only for the configured alert categories.

  • TTS: Pass a short, controlled warning message to a text-to-speech service, then deliver the returned audio file.

  • AI analysis: Send a summarized set of recent readings to the AI model. Do not allow model output to override the deterministic danger classification.

For production, configure an error workflow and retain a separate log of failed cloud operations.

9. Telegram bot setup

Custom Telegram Bot
 
 
 
Sending Messages and Files to Users via Telegram Bot API with Python | BLOG | *ARGS.TECH
 
 
 
Canali simili, ripubblicazione delle storie e altre 9 funzioni
 
 
 

Step 1 — Create the bot

  1. Open Telegram and search for the verified @BotFather account.

  2. Send /newbot.

  3. Enter a display name, such as Mountain Road Safety.

  4. Choose a unique username ending in bot.

  5. Save the token securely. Anyone with the token can control the bot.

Step 2 — Find the chat ID

  1. Open your new bot and press Start.

  2. Send a test message.

  3. Retrieve the chat ID using the Telegram Bot API getUpdates method, or use a Telegram Trigger node in n8n.

  4. For a group, add the bot and send a message after adding it. Ensure it has the permissions required by the group.

Do not expose your bot token in public source code, screenshots, or a public Git repository.

Step 3 — Configure Telegram in n8n

Create Telegram credentials in n8n using the bot token. Add a Telegram node configured to send a text message.

Example message:

Mountain Road Safety Bot

 
DANGER
 

🚨 MOUNTAIN ROAD FOG WARNING Station: MOUNTAIN_STATION_01 Condition: DANGER Optical indicator: 2450 ADC Humidity: 94% Temperature: 16.8 °C Action: Verify actual visibility and follow the approved road-warning procedure. Time: 20:42 IST

Illustrative notification. The measurements are examples, not live sensor readings.

Use expressions to populate the message from the validated JSON output. Avoid sending a notification every time a danger reading arrives; create a deduplication rule based on station, risk state and time.

10. Google Sheets integration

Google Sheets is useful for historical data, audit records, energy predictions and maintenance reporting.

10.1 Create the spreadsheet

Create a workbook named Mountain Road Safety Monitoring.

Add these tabs:

Tab 1: Telemetry

Column

Field

A

Timestamp

B

Station ID

C

Sequence

D

Optical ADC

E

Visibility metres

F

Temperature °C

G

Humidity %

H

Risk

I

Sensor valid

J

Power watts

K

Energy Wh

Tab 2: Alerts

  • Timestamp

  • Station ID

  • Risk level

  • Alert text

  • Telegram delivery status

  • Voice delivery status

  • Acknowledged by

  • Resolution timestamp

Tab 3: EnergyForecast

  • Timestamp

  • Station ID

  • Measured power (W)

  • Measured energy (Wh)

  • Predicted energy (Wh)

  • Forecast horizon

  • Model version

  • Prediction error

10.2 Connect n8n to Google Sheets

  1. Configure a Google Sheets OAuth credential in n8n.

  2. Authorize access to the intended spreadsheet.

  3. Add a Google Sheets node and select the document and sheet tab.

  4. Choose Append Row.

  5. Map each validated JSON field to its corresponding column.

  6. Test with several samples, including invalid sensor data and a danger event.

Do not store passwords, API keys, Telegram tokens or other secrets in the sheet. Restrict spreadsheet access and define a retention policy for operational data.

11. ThingSpeak cloud dashboard

ThingSpeak provides time-series charts for IoT telemetry and is suitable for viewing trends across fog indicators, temperature, humidity and power consumption.

IoT based Weather Station using ThingSpeak IoT Analytics - Hackster.io
 
 
 

11.1 Create a channel

Create a channel named Mountain Road Safety Station 01 with these fields:

ThingSpeak field

Data

Field 1

Optical ADC

Field 2

Temperature °C

Field 3

Relative humidity %

Field 4

Risk code

Field 5

Measured power W

Field 6

Cumulative energy Wh

Field 7

Visibility metres, only if calibrated

Field 8

Sensor health code

Use numeric values for chart fields. One possible risk encoding is 0 = Normal, 1 = Caution, 2 = Danger, 3 = Sensor Fault.

11.2 Configure the channel

  1. Create a ThingSpeak account and channel.

  2. Enable the fields you plan to record.

  3. Copy the channel's Write API Key.

  4. Store it in an n8n credential or environment variable.

  5. Use an n8n HTTP Request node to publish data.

  6. Create time-series visualizations in the channel's private or public dashboard, as appropriate.

11.3 Example ThingSpeak HTTP request

Configure the HTTP Request node as follows:

  • Method: GET or POST.

  • Endpoint: https://api.thingspeak.com/update.

  • Send the write key as api_key.

  • Map the fields to field1 through field8.

For example, the request body for a POST can contain:

text
api_key=YOUR_WRITE_API_KEY
field1=2450
field2=16.8
field3=94
field4=2
field5=3.6
field6=12.4

Those values are illustrative. Map real measurements using n8n expressions rather than hardcoding them. A successful update returns an entry ID; a zero or failed response should be treated as a logging failure.

Check the current ThingSpeak channel update limits for your account and plan before selecting the upload interval. Avoid sending duplicate updates more frequently than permitted.

12. AI-powered fog analysis

The AI component should explain sensor trends and help forecast energy consumption. The primary hazard classification must remain rule-based and independently testable.

Recommended two-stage design

Stage A — Deterministic safety engine

Validate measurements, check sensor health, apply calibrated thresholds, filter noise, and trigger the required safety response.

 

Stage B — AI analysis agent

Summarize recent trends, explain why a warning was triggered, identify suspicious sensor behaviour, and forecast station energy consumption.

Example AI agent prompt

Use this as the system instruction for an AI node connected to the validated data:

text

You are an assistant for a mountain-road environmental monitoring station.

Use only the supplied measurements and historical data.
Never invent sensor readings or claim that a sensor-derived
optical indicator is a calibrated visibility distance.

Explain trends in temperature, humidity, optical readings,
and power consumption.

If the rule-based system reports DANGER or SENSOR_FAULT,
preserve that status. Do not downgrade or cancel an alert.

Return a concise JSON object with:
- summary
- reported_risk
- observed_trend
- sensor_quality_concerns
- predicted_energy_wh
- prediction_horizon
- recommended_operator_check

Recommendations are advisory. Do not claim that the road is
safe without independently validated visibility measurements.

13. AI power consumption prediction logic

Power forecasting helps estimate the energy required to operate the ESP32, sensors, communications hardware and optional warning devices. It can also help estimate battery life for remote mountain stations.

13.1 Measure actual power

With a suitable INA219 or equivalent monitor, measure voltage VV and current II:

P(t)=V(t)×I(t)P(t)=V(t)×I(t)

Power is measured in watts. Energy consumed over time is measured in watt-hours:

EWh=∑iPiΔti3600EWh=i∑Pi3600Δti

where ΔtiΔti is the interval in seconds between measurements.

For example, if a station draws an average of 1.2 W for 24 hours:

E=1.2×24=28.8 Wh/dayE=1.2×24=28.8 Wh/day

For a battery system, account for regulator losses, battery usable capacity, temperature effects, self-discharge and any solar charging.

13.2 Start with a baseline prediction

Before training an AI model, establish a simple baseline:

E^t+1=Edaily,rollingE^t+1=Edaily,rolling

That is, use the rolling average of daily energy as the next-day forecast. A stronger model can include:

  • Previous hourly and daily energy use.

  • Time of day and day of week.

  • Wi-Fi transmission frequency.

  • Fog warning and buzzer activation duration.

  • Ambient temperature.

  • Solar charging and battery voltage, where applicable.

13.3 Example forecasting code for n8n

This Code node estimates energy use using recent power measurements. The forecast is a baseline projection, not a trained AI model. Feed it a consistent series of samples from Google Sheets or another time-series store.

AI Smart Mountain Road Safety System with Fog Detection

This project builds an AI-powered, IoT-based mountain-road safety system using an ESP32. It detects fog, poor visibility, vehicles, obstacles, gas/smoke, and environmental conditions; activates local road warnings; uploads live data to ThingSpeak; logs events in Google Sheets; and uses an n8n AI Agent to send intelligent Telegram voice/text alerts.

ThingSpeak supports device-to-cloud data through REST or MQTT, and a free account updates a channel at most every 15 seconds, so the ESP32 should normally publish at 15–60 second intervals.[in.mathworks][uk.mathworks]

Project Overview

What the system does

  • Measures visibility/fog using an IR obstacle sensor or laser/IR distance sensor.
  • Measures temperature, humidity, gas/smoke, light level, rain, and vibration.
  • Detects a vehicle or obstacle approaching a dangerous curve using an ultrasonic/IR sensor.
  • Turns on high-intensity LEDs, buzzer, and warning display during fog or danger.
  • Sends sensor data to ThingSpeak for a live cloud dashboard.
  • Sends event data to n8n, where an AI Agent classifies risk and generates a warning.
  • Sends Telegram text + voice alerts to road authorities or drivers.
  • Stores every alert and reading in Google Sheets for historical analysis.
  • Predicts likely power consumption using sensor activity, alert frequency, and weather conditions.

System roles

Layer Technology Purpose
Edge device ESP32 Reads sensors, controls actuators, publishes data
Local warning LEDs, buzzer, relay Warns approaching vehicles immediately
Cloud dashboard ThingSpeak Stores and visualizes road conditions
Automation n8n Receives events, runs AI logic, triggers alerts
AI layer OpenAI/other LLM in n8n Risk classification and alert message generation
Notifications Telegram Bot Text and voice alerts
Records Google Sheets Event history and audit log
Optional control Telegram commands Request status, reset alarm, change mode

n8n provides Telegram message operations including Send Audio, and Google Sheets supports Append Row and Append or Update Row, which are ideal for automated event logging.[docs.n8n][docs.n8n]

Architecture

                 ┌──────────────────────────────┐
                 │        Mountain Road         │
                 └──────────────┬───────────────┘
                                │
        ┌───────────────────────┼───────────────────────┐
        │                       │                       │
┌───────▼────────┐     ┌────────▼────────┐     ┌────────▼────────┐
│ Fog / Visibility│     │ Vehicle /       │     │ Environment     │
│ IR/LDR Sensor   │     │ Obstacle Sensor │     │ DHT22, MQ-2,    │
└───────┬────────┘     └────────┬────────┘     │ Rain, Vibration │
        │                       │              └────────┬────────┘
        └───────────────┬───────┴───────────────────────┘
                        │
              ┌─────────▼─────────┐
              │      ESP32        │
              │  Edge AI Logic    │
              └───┬───────────┬───┘
                  │           │
       ┌──────────▼──┐    ┌───▼─────────────┐
       │ Local Alert │    │ Wi-Fi / HTTPS   │
       │ LED+Buzzer  │    └───┬─────────────┘
       └─────────────┘        │
                              │
             ┌────────────────┼─────────────────┐
             │                │                 │
    ┌────────▼───────┐ ┌──────▼───────┐ ┌───────▼────────┐
    │   ThingSpeak   │ │     n8n      │ │ Google Sheets  │
    │ Cloud Dashboard│ │ AI Agent     │ │ Event Log      │
    └────────────────┘ └──────┬───────┘ └────────────────┘
                              │
                   ┌──────────▼──────────┐
                   │  Telegram Voice +   │
                   │  Text Notifications │
                   └─────────────────────┘

Components List

Component Quantity Purpose
ESP32 DevKit V1 / WROOM-32 1 Main Wi-Fi microcontroller
DHT22 or DHT11 1 Temperature and humidity
MQ-2 gas/smoke sensor 1 Smoke, LPG, combustible gas detection
LDR module 1 Ambient light / night detection
IR obstacle sensor 1–2 Fog/low-visibility and vehicle detection
HC-SR04 ultrasonic sensor 1 Distance to obstacle/vehicle
Rain sensor module 1 Wet-road detection
SW-420 vibration sensor 1 Landslide/vibration detection
PIR motion sensor 1 Optional human/animal movement detection
5V relay module 1 Controls high-power warning lights
Red LEDs / 12V warning lamps 2–4 Fog/danger warning
Yellow LED 1 Caution mode
Green LED 1 Safe mode
Active buzzer 1 Immediate audible alarm
OLED display, 0.96-inch I2C 1 Local status display
Breadboard and jumper wires 1 set Prototyping
5V 2A adapter or 18650 battery + TP4056 1 Power supply
Voltage divider / AMS1117 3.3V As needed Sensor level compatibility
Enclosure, IP65 box 1 Outdoor protection

Pin Connection Plan

Use GPIO pins that are safe during boot. Avoid GPIO0, GPIO2, GPIO12, and GPIO15 for sensors or relays where possible.

ESP32 Pin Connected Device Notes
3V3 DHT22 VCC, LDR VCC, IR VCC, OLED VCC 3.3 V sensors
VIN / 5V MQ-2 VCC, relay VCC, HC-SR04 VCC Use 5 V where required
GND Common ground All modules must share ground
GPIO4 DHT22 DATA Add 10 kΩ pull-up to 3.3 V for DHT22
GPIO34 MQ-2 A0 ADC input
GPIO35 LDR A0 ADC input
GPIO32 Rain sensor A0 ADC input
GPIO33 Vibration sensor DO Digital input
GPIO25 IR fog sensor DO LOW usually means object detected
GPIO26 IR vehicle sensor DO Vehicle/obstacle detection
GPIO27 HC-SR04 TRIG Output
GPIO14 HC-SR04 ECHO Use voltage divider: 5 V to 3.3 V
GPIO18 Green LED Safe condition
GPIO19 Yellow LED Caution
GPIO21 Red LED Danger
GPIO22 Buzzer Alarm
GPIO23 Relay IN High-power warning lamp
GPIO5 / GPIO23 OLED SDA / SCL I2C display; choose available pins

Circuit Schematic Diagram

                         ESP32 DevKit
                 ┌──────────────────────────────┐
                 │ 3V3  GND  GPIO4  GPIO34      │
                 │  5V       GPIO25 GPIO35      │
                 │           GPIO26 GPIO32      │
                 │           GPIO27 GPIO33      │
                 │           GPIO14 GPIO18-23   │
                 └───┬────┬────┬────┬────┬──────┘
                     │    │    │    │    │
      ┌──────────────┘    │    │    │    └────────────────┐
      │                  │    │    │                     │
┌─────▼─────┐   ┌────────▼──┐ │ ┌──▼──────────┐  ┌────────▼──────┐
│   DHT22   │   │ IR Fog    │ │ │ HC-SR04     │  │ Relay Module  │
│ VCC->3V3  │   │ VCC->3V3  │ │ │ VCC->5V     │  │ VCC->5V       │
│ DATA->GPIO4│  │ DO->GPIO25│ │ │ TRIG->GPIO27│  │ IN ->GPIO23   │
│ GND->GND  │   │ GND->GND  │ │ │ ECHO->GPIO14│  │ GND->GND      │
└───────────┘   └───────────┘ │ │ GND->GND    │  └──────┬────────┘
                              │ └─────────────┘         │
┌─────────────┐  ┌──────────┐ │                          │
│ MQ-2 Sensor │  │ LDR      │ │                    ┌─────▼─────┐
│ VCC->5V     │  │ VCC->3V3 │ │                    │ 12V Lamp  │
│ A0 ->GPIO34 │  │ A0->GPIO35│ │                    │ via relay │
│ GND->GND    │  │ GND->GND │ │                    └───────────┘
└─────────────┘  └──────────┘ │
                              │
┌─────────────┐  ┌──────────┐ │
│ Rain Sensor │  │ Vibration│ │
│ A0 ->GPIO32 │  │ DO->GPIO33│ │
└─────────────┘  └──────────┘ │
                              │
                 ┌────────────▼───────────┐
                 │ LEDs: Green GPIO18     │
                 │ Yellow GPIO19          │
                 │ Red GPIO21             │
                 │ Buzzer GPIO22          │
                 └────────────────────────┘

Important: HC-SR04 ECHO outputs 5 V. Connect it to ESP32 through a voltage divider, for example 1 kΩ from ECHO to GPIO14 and 2 kΩ from GPIO14 to GND.

Safety Logic

The ESP32 calculates a Road Risk Score from multiple sensors:

Risk = W_f F + W_v V + W_g G + W_r R + W_l L + W_s S

Where:

  • F = fog/low visibility level
  • V = vehicle/obstacle proximity risk
  • G = gas/smoke risk
  • R = rain risk
  • L = landslide/vibration risk
  • S = night/low-light risk
  • W values are weights

Example weights:

Parameter Weight
Fog 0.35
Obstacle/vehicle 0.25
Rain 0.15
Gas/smoke 0.10
Vibration/landslide 0.10
Night 0.05

Decision levels

Risk Score Status Local Action Cloud Action
0–29 SAFE Green LED Normal data upload
30–59 CAUTION Yellow LED + slow beep n8n caution alert
60–84 DANGER Red LED + buzzer n8n danger alert + Sheets log
85–100 EMERGENCY Red LED + relay lamp + continuous buzzer Emergency Telegram voice alert

ESP32 Source Code

Install these Arduino libraries:

  • WiFi.h
  • HTTPClient.h
  • DHT sensor library by Adafruit
  • Adafruit Unified Sensor
  • Adafruit SSD1306
  • Adafruit GFX
  • ArduinoJson
/*
 * AI Smart Mountain Road Safety System with Fog Detection
 * ESP32 + n8n + ThingSpeak + Google Sheets + Telegram Voice Alerts
 */

#include <WiFi.h>
#include <HTTPClient.h>
#include <DHT.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ArduinoJson.h>

// ---------------- Wi-Fi ----------------
const char* WIFI_SSID = "YOUR_WIFI_NAME";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";

// ---------------- ThingSpeak ----------------
const char* THINGSPEAK_HOST = "api.thingspeak.com";
const char* THINGSPEAK_WRITE_KEY = "YOUR_THINGSPEAK_WRITE_KEY";

// ---------------- n8n Webhook ----------------
const char* N8N_WEBHOOK_URL = "https://YOUR_N8N_DOMAIN/webhook/mountain-road-alert";

// ---------------- Pins ----------------
#define DHT_PIN 4
#define DHT_TYPE DHT22

#define MQ2_PIN 34
#define LDR_PIN 35
#define RAIN_PIN 32
#define VIBRATION_PIN 33

#define FOG_IR_PIN 25
#define VEHICLE_IR_PIN 26

#define TRIG_PIN 27
#define ECHO_PIN 14

#define GREEN_LED 18
#define YELLOW_LED 19
#define RED_LED 21
#define BUZZER_PIN 22
#define RELAY_PIN 23

#define OLED_SDA 5
#define OLED_SCL 23

// ---------------- Thresholds ----------------
int fogThreshold = 500;        // Adjust after calibration
int gasThreshold = 700;        // MQ-2 ADC threshold
int rainThreshold = 2000;      // Lower value may mean wet, depending on module
int vibrationThreshold = 1;    // Digital HIGH means vibration
int obstacleDistanceCm = 100;  // Danger distance

int safeThreshold = 29;
int cautionThreshold = 59;
int dangerThreshold = 84;

// ---------------- Objects ----------------
DHT dht(DHT_PIN, DHT_TYPE);
Adafruit_SSD1306 display(128, 64, &Wire, -1);

unsigned long lastThingSpeak = 0;
unsigned long lastN8NAlert = 0;
const unsigned long THINGSPEAK_INTERVAL = 20000;  // 20 seconds
const unsigned long ALERT_COOLDOWN = 60000;       // 1 minute

float temperature = 0;
float humidity = 0;
int gasValue = 0;
int lightValue = 0;
int rainValue = 0;
bool vibration = false;
bool fogDetected = false;
bool vehicleDetected = false;
long distanceCm = 999;
int riskScore = 0;
String riskLevel = "SAFE";

void setup() {
  Serial.begin(115200);

  pinMode(GREEN_LED, OUTPUT);
  pinMode(YELLOW_LED, OUTPUT);
  pinMode(RED_LED, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(RELAY_PIN, OUTPUT);

  pinMode(FOG_IR_PIN, INPUT);
  pinMode(VEHICLE_IR_PIN, INPUT);
  pinMode(VIBRATION_PIN, INPUT);
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);

  digitalWrite(GREEN_LED, LOW);
  digitalWrite(YELLOW_LED, LOW);
  digitalWrite(RED_LED, LOW);
  digitalWrite(BUZZER_PIN, LOW);
  digitalWrite(RELAY_PIN, LOW);

  dht.begin();

  Wire.begin(OLED_SDA, OLED_SCL);
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println("OLED not found");
  }

  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.println("Mountain Road Safety");
  display.println("Starting...");
  display.display();

  connectWiFi();
}

void loop() {
  readSensors();
  calculateRisk();
  controlLocalAlerts();
  updateDisplay();

  unsigned long now = millis();

  if (now - lastThingSpeak >= THINGSPEAK_INTERVAL) {
    sendToThingSpeak();
    lastThingSpeak = now;
  }

  if (riskScore > cautionThreshold && now - lastN8NAlert >= ALERT_COOLDOWN) {
    sendToN8N();
    lastN8NAlert = now;
  }

  delay(1000);
}

void connectWiFi() {
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to Wi-Fi");

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println();
  Serial.print("Connected. IP: ");
  Serial.println(WiFi.localIP());
}

void readSensors() {
  temperature = dht.readTemperature();
  humidity = dht.readHumidity();

  gasValue = analogRead(MQ2_PIN);
  lightValue = analogRead(LDR_PIN);
  rainValue = analogRead(RAIN_PIN);

  vibration = digitalRead(VIBRATION_PIN) == HIGH;
  fogDetected = digitalRead(FOG_IR_PIN) == LOW;
  vehicleDetected = digitalRead(VEHICLE_IR_PIN) == LOW;

  distanceCm = readUltrasonicDistance();
}

long readUltrasonicDistance() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);

  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  long duration = pulseIn(ECHO_PIN, HIGH, 30000);

  if (duration == 0) {
    return 999;
  }

  return duration * 0.034 / 2;
}

void calculateRisk() {
  int fogRisk = 0;
  int vehicleRisk = 0;
  int gasRisk = 0;
  int rainRisk = 0;
  int vibrationRisk = 0;
  int nightRisk = 0;

  if (fogDetected || lightValue < fogThreshold) {
    fogRisk = 100;
  } else if (lightValue < fogThreshold + 300) {
    fogRisk = 50;
  }

  if (vehicleDetected || distanceCm < obstacleDistanceCm) {
    vehicleRisk = 100;
  } else if (distanceCm < obstacleDistanceCm * 2) {
    vehicleRisk = 50;
  }

  if (gasValue > gasThreshold) {
    gasRisk = 100;
  } else if (gasValue > gasThreshold * 0.7) {
    gasRisk = 50;
  }

  if (rainValue < rainThreshold) {
    rainRisk = 100;
  } else if (rainValue < rainThreshold + 500) {
    rainRisk = 50;
  }

  if (vibration) {
    vibrationRisk = 100;
  }

  if (lightValue < 800) {
    nightRisk = 100;
  }

  riskScore =
    (fogRisk * 0.35) +
    (vehicleRisk * 0.25) +
    (rainRisk * 0.15) +
    (gasRisk * 0.10) +
    (vibrationRisk * 0.10) +
    (nightRisk * 0.05);

  riskScore = constrain(riskScore, 0, 100);

  if (riskScore <= safeThreshold) {
    riskLevel = "SAFE";
  } else if (riskScore <= cautionThreshold) {
    riskLevel = "CAUTION";
  } else if (riskScore <= dangerThreshold) {
    riskLevel = "DANGER";
  } else {
    riskLevel = "EMERGENCY";
  }
}

void controlLocalAlerts() {
  digitalWrite(GREEN_LED, LOW);
  digitalWrite(YELLOW_LED, LOW);
  digitalWrite(RED_LED, LOW);
  digitalWrite(BUZZER_PIN, LOW);
  digitalWrite(RELAY_PIN, LOW);

  if (riskLevel == "SAFE") {
    digitalWrite(GREEN_LED, HIGH);
  } else if (riskLevel == "CAUTION") {
    digitalWrite(YELLOW_LED, HIGH);
    digitalWrite(BUZZER_PIN, HIGH);
    delay(100);
    digitalWrite(BUZZER_PIN, LOW);
  } else if (riskLevel == "DANGER") {
    digitalWrite(RED_LED, HIGH);
    digitalWrite(BUZZER_PIN, HIGH);
    delay(300);
    digitalWrite(BUZZER_PIN, LOW);
  } else {
    digitalWrite(RED_LED, HIGH);
    digitalWrite(BUZZER_PIN, HIGH);
    digitalWrite(RELAY_PIN, HIGH);
  }
}

void updateDisplay() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.println("Mountain Road Safety");
  display.setCursor(0, 16);
  display.print("Risk: ");
  display.println(riskLevel);
  display.print("Score: ");
  display.println(riskScore);
  display.print("Temp: ");
  display.println(temperature);
  display.print("Humidity: ");
  display.println(humidity);
  display.print("Distance: ");
  display.println(distanceCm);
  display.print("Fog: ");
  display.println(fogDetected ? "YES" : "NO");
  display.display();
}

void sendToThingSpeak() {
  if (WiFi.status() != WL_CONNECTED) {
    connectWiFi();
    return;
  }

  HTTPClient http;
  String url = "http://" + String(THINGSPEAK_HOST) + "/update?api_key=" +
               THINGSPEAK_WRITE_KEY +
               "&field1=" + String(temperature, 2) +
               "&field2=" + String(humidity, 2) +
               "&field3=" + String(gasValue) +
               "&field4=" + String(lightValue) +
               "&field5=" + String(rainValue) +
               "&field6=" + String(distanceCm) +
               "&field7=" + String(riskScore) +
               "&field8=" + String(fogDetected ? 1 : 0);

  http.begin(url);
  int httpCode = http.GET();

  if (httpCode > 0) {
    Serial.print("ThingSpeak response: ");
    Serial.println(httpCode);
  } else {
    Serial.print("ThingSpeak error: ");
    Serial.println(httpCode);
  }

  http.end();
}

void sendToN8N() {
  if (WiFi.status() != WL_CONNECTED) {
    connectWiFi();
    return;
  }

  HTTPClient http;
  http.begin(N8N_WEBHOOK_URL);
  http.addHeader("Content-Type", "application/json");

  StaticJsonDocument<768> doc;
  doc["device_id"] = "MOUNTAIN_ROAD_01";
  doc["location"] = "Ghat Road Curve 7";
  doc["risk_level"] = riskLevel;
  doc["risk_score"] = riskScore;
  doc["temperature"] = temperature;
  doc["humidity"] = humidity;
  doc["gas_value"] = gasValue;
  doc["light_value"] = lightValue;
  doc["rain_value"] = rainValue;
  doc["distance_cm"] = distanceCm;
  doc["fog_detected"] = fogDetected;
  doc["vehicle_detected"] = vehicleDetected;
  doc["vibration"] = vibration;

  String payload;
  serializeJson(doc, payload);

  int httpCode = http.POST(payload);

  if (httpCode > 0) {
    Serial.print("n8n response: ");
    Serial.println(httpCode);
  } else {
    Serial.print("n8n error: ");
    Serial.println(httpCode);
  }

  http.end();
}

ThingSpeak Dashboard Setup

ThingSpeak channels store data in fields, and the Write API key is required to update a private channel.[mathworks][se.mathworks]

Create the channel

  1. Sign in at ThingSpeak.
  2. Go to Channels > My Channels > New Channel.
  3. Enter:

    • Name: AI Mountain Road Safety
    • Field 1: Temperature C
    • Field 2: Humidity %
    • Field 3: Gas / Smoke
    • Field 4: Light Level
    • Field 5: Rain Value
    • Field 6: Obstacle Distance cm
    • Field 7: AI Risk Score
    • Field 8: Fog Detected
  4. Enable Make Public if you want a public dashboard.
  5. Save the channel.
  6. Open API Keys and copy the Write API Key.
  7. Paste it into THINGSPEAK_WRITE_KEY in the ESP32 code.

ThingSpeak accepts updates using HTTP GET/POST or MQTT, and its REST API supports writing one entry with multiple fields.[in.mathworks][in.mathworks]

Dashboard widgets

Add these widgets:

  • Temperature: Field 1 line chart
  • Humidity: Field 2 line chart
  • Gas/smoke: Field 3 gauge or chart
  • Visibility/fog: Field 8 status chart
  • Risk score: Field 7 MATLAB visualization
  • Distance: Field 6 line chart

Optional MATLAB risk visualization

readKey = 'YOUR_READ_API_KEY';
data = thingSpeakRead(YOUR_CHANNEL_ID, ...
    'Fields', [1 2 7], ...
    'NumPoints', 50, ...
    'ReadKey', readKey);

temperature = data(:,1);
humidity = data(:,2);
risk = data(:,3);

plot(risk, 'r-', 'LineWidth', 2);
ylabel('AI Risk Score');
title('Mountain Road Risk Trend');
grid on;

ThingSpeak’s MATLAB Analysis app can read, process, and write channel data; it uses channel IDs and API keys for access.[uk.mathworks][in.mathworks]

n8n Workflow

Required credentials

  1. Telegram API credential: Bot token from @BotFather.
  2. Google Sheets OAuth2 credential: Google account authorization.
  3. OpenAI credential: For the AI Agent, if using OpenAI.
  4. Optional: Telegram Chat ID, obtained by messaging the bot and using n8n’s Telegram trigger or Telegram API.

n8n’s Telegram node supports sending audio, and its Google Sheets node supports appending rows to a selected spreadsheet and sheet.[docs.n8n][docs.n8n]

Workflow flow

ESP32 HTTP POST
      │
      ▼
Webhook: /mountain-road-alert
      │
      ▼
IF Risk Level is CAUTION / DANGER / EMERGENCY
      │
      ├── Yes ──▶ Google Sheets: Append Row
      │                │
      │                ▼
      │         AI Agent: Analyze Risk
      │                │
      │                ▼
      │         Generate Voice MP3 / OGG
      │                │
      │                ▼
      │         Telegram: Send Voice Alert
      │                │
      │                ▼
      │         Telegram: Send Text Summary
      │
      └── No ──▶ End

n8n Workflow JSON

Import this JSON in n8n using Workflows > Import from File or paste it into the canvas.

{
  "name": "AI Smart Mountain Road Safety System",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "mountain-road-alert",
        "responseMode": "onReceived",
        "options": {}
      },
      "id": "webhook-node",
      "name": "ESP32 Alert Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [0, 300],
      "webhookId": "mountain-road-alert"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "looseTypeValidation": false,
            "version": 2
          },
          "conditions": [
            {
              "leftValue": "={{ $json.body.risk_level }}",
              "rightValue": "SAFE",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "risk-check",
      "name": "Is Alert Required?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [220, 300]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_GOOGLE_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "RoadSafetyLog",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Timestamp": "={{ $now.format('yyyy-MM-dd HH:mm:ss') }}",
            "DeviceID": "={{ $json.body.device_id }}",
            "Location": "={{ $json.body.location }}",
            "RiskLevel": "={{ $json.body.risk_level }}",
            "RiskScore": "={{ $json.body.risk_score }}",
            "Temperature": "={{ $json.body.temperature }}",
            "Humidity": "={{ $json.body.humidity }}",
            "GasValue": "={{ $json.body.gas_value }}",
            "LightValue": "={{ $json.body.light_value }}",
            "RainValue": "={{ $json.body.rain_value }}",
            "DistanceCm": "={{ $json.body.distance_cm }}",
            "FogDetected": "={{ $json.body.fog_detected }}",
            "VehicleDetected": "={{ $json.body.vehicle_detected }}",
            "Vibration": "={{ $json.body.vibration }}"
          }
        },
        "options": {}
      },
      "id": "google-sheet-log",
      "name": "Log Alert to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [440, 180],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_WITH_CREDENTIAL_ID",
          "name": "Google Sheets account"
        }
      }
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=You are an AI road-safety agent.\n\nAnalyze this mountain-road IoT alert and return JSON only with fields:\n- risk_level\n- recommended_action\n- driver_warning\n- authority_message\n\nSensor data:\nDevice: {{ $json.body.device_id }}\nLocation: {{ $json.body.location }}\nRisk level: {{ $json.body.risk_level }}\nRisk score: {{ $json.body.risk_score }}\nTemperature: {{ $json.body.temperature }} C\nHumidity: {{ $json.body.humidity }}%\nGas value: {{ $json.body.gas_value }}\nLight value: {{ $json.body.light_value }}\nRain value: {{ $json.body.rain_value }}\nObstacle distance: {{ $json.body.distance_cm }} cm\nFog detected: {{ $json.body.fog_detected }}\nVehicle detected: {{ $json.body.vehicle_detected }}\nVibration: {{ $json.body.vibration }}"
      },
      "id": "ai-agent",
      "name": "AI Road Safety Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 1.7,
      "position": [660, 180]
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-4o-mini",
          "mode": "list"
        },
        "options": {}
      },
      "id": "openai-model",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [620, 380],
      "credentials": {
        "openAiApi": {
          "id": "REPLACE_WITH_CREDENTIAL_ID",
          "name": "OpenAI account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const raw = $json.output || $json.text || '';\nlet parsed;\ntry {\n  parsed = JSON.parse(raw);\n} catch (e) {\n  const match = raw.match(/\\{[\\s\\S]*\\}/);\n  parsed = match ? JSON.parse(match[0]) : {\n    risk_level: 'DANGER',\n    recommended_action: 'Activate road warning system.',\n    driver_warning: 'Caution: poor visibility or obstacle ahead.',\n    authority_message: 'Mountain road safety alert detected.'\n  };\n}\n\nconst voiceText =\n  'Attention. Mountain road safety alert. ' +\n  parsed.driver_warning + ' ' +\n  'Recommended action: ' + parsed.recommended_action;\n\nreturn [{ json: { ...parsed, voice_text: voiceText } }];"
      },
      "id": "parse-ai",
      "name": "Parse AI Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [880, 180]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/audio/speech",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openAiApi",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"gpt-4o-mini-tts\",\n  \"voice\": \"alloy\",\n  \"input\": {{ JSON.stringify($json.voice_text) }},\n  \"response_format\": \"mp3\"\n}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "id": "tts-node",
      "name": "Generate AI Voice",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1100, 180],
      "credentials": {
        "openAiApi": {
          "id": "REPLACE_WITH_CREDENTIAL_ID",
          "name": "OpenAI account"
        }
      }
    },
    {
      "parameters": {
        "operation": "sendAudio",
        "chatId": "YOUR_TELEGRAM_CHAT_ID",
        "binaryFile": true,
        "inputBinaryField": "data",
        "additionalFields": {
          "caption": "={{ $('Parse AI Response').item.json.driver_warning }}"
        }
      },
      "id": "telegram-voice",
      "name": "Telegram Voice Alert",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [1320, 180],
      "credentials": {
        "telegramApi": {
          "id": "REPLACE_WITH_CREDENTIAL_ID",
          "name": "Telegram Bot"
        }
      }
    },
    {
      "parameters": {
        "chatId": "YOUR_TELEGRAM_CHAT_ID",
        "text": "=🚨 *Mountain Road Safety Alert*\n\n📍 Location: {{ $('ESP32 Alert Webhook').item.json.body.location }}\n⚠️ Risk: {{ $('Parse AI Response').item.json.risk_level }}\n🧠 AI Advice: {{ $('Parse AI Response').item.json.recommended_action }}\n🚗 Driver Warning: {{ $('Parse AI Response').item.json.driver_warning }}\n\n📊 Risk Score: {{ $('ESP32 Alert Webhook').item.json.body.risk_score }}/100\n🌫️ Fog: {{ $('ESP32 Alert Webhook').item.json.body.fog_detected }}\n🌧️ Rain Value: {{ $('ESP32 Alert Webhook').item.json.body.rain_value }}\n🚗 Distance: {{ $('ESP32 Alert Webhook').item.json.body.distance_cm }} cm",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "id": "telegram-text",
      "name": "Telegram Text Summary",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [1540, 180],
      "credentials": {
        "telegramApi": {
          "id": "REPLACE_WITH_CREDENTIAL_ID",
          "name": "Telegram Bot"
        }
      }
    }
  ],
  "connections": {
    "ESP32 Alert Webhook": {
      "main": [
        [
          {
            "node": "Is Alert Required?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Alert Required?": {
      "main": [
        [
          {
            "node": "Log Alert to Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Alert to Google Sheets": {
      "main": [
        [
          {
            "node": "AI Road Safety Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Road Safety Agent": {
      "main": [
        [
          {
            "node": "Parse AI Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Response": {
      "main": [
        [
          {
            "node": "Generate AI Voice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate AI Voice": {
      "main": [
        [
          {
            "node": "Telegram Voice Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram Voice Alert": {
      "main": [
        [
          {
            "node": "Telegram Text Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}

Telegram Bot Setup

  1. Open Telegram and search for @BotFather.
  2. Send /newbot.
  3. Choose a display name, for example Mountain Road Safety Bot.
  4. Choose a username ending in bot, such as mountain_road_safety_bot.
  5. Copy the bot token.
  6. Add the token to n8n under Credentials > Telegram API.
  7. Message your bot once so Telegram can identify your chat.
  8. Get your chat ID using n8n’s Telegram Trigger or a Telegram API tool.
  9. Replace YOUR_TELEGRAM_CHAT_ID in the workflow.

Telegram Bot API requests use the format https://api.telegram.org/bot<token>/METHOD_NAME. For voice messages, Telegram supports sendVoice; n8n’s Telegram node also provides a Send Audio operation that accepts a binary file.[docs.n8n][core.telegram]

Google Sheets Integration

Create a Google Sheet named Mountain Road Safety System.

Create a tab named RoadSafetyLog with these headers:

Timestamp DeviceID Location RiskLevel RiskScore Temperature Humidity GasValue LightValue RainValue DistanceCm FogDetected VehicleDetected Vibration AIAction

In n8n:

  1. Add the Google Sheets node.
  2. Select Resource: Sheet Within Document.
  3. Select Operation: Append Row.
  4. Choose your spreadsheet and RoadSafetyLog sheet.
  5. Map each incoming ESP32 field to the correct column.
  6. Connect your Google Sheets OAuth2 credential.

n8n identifies a spreadsheet either by list, URL, or spreadsheet ID, and its Append Row operation adds a new row at the end of the selected sheet.[docs.n8n]

AI Power Consumption Prediction Logic

The system predicts expected power use from sensor polling, Wi-Fi transmission, local warnings, and cloud/automation events.

Simple prediction formula

P_{predicted} = P_{base} + P_{wifi}N_w + P_{alert}N_a + P_{lamp}T_l

Where:

  • P_{base} = ESP32 and sensor baseline power
  • N_w = number of Wi-Fi uploads per hour
  • P_{wifi} = energy per upload
  • N_a = number of alerts per hour
  • P_{alert} = energy per alert/buzzer event
  • T_l = warning-lamp ON time in hours
  • P_{lamp} = lamp power

Example calculation

Assume:

  • ESP32 average current: 120 mA
  • Sensors average current: 80 mA
  • Wi-Fi transmission current: 180 mA for 2 seconds
  • Buzzer current: 30 mA
  • 12 V warning lamp: 500 mA
  • Uploads: 180 per hour at 20-second intervals
  • Alerts: 5 per hour
  • Lamp ON: 0.2 hours per hour during fog

Approximate daily energy:

E_{base} = 0.2A \times 5V \times 24h = 24Wh
E_{wifi} = 0.18A \times 5V \times \frac{2}{3600}h \times 180 = 0.09Wh
E_{lamp} = 12V \times 0.5A \times 0.2h = 1.2Wh
E_{total} \approx 25.3Wh/day

For a 12 V 7 Ah battery:

Capacity = 12V \times 7Ah = 84Wh

Estimated backup time:

\frac{84Wh}{25.3Wh/day} \approx 3.3 days

In a real deployment, add a solar panel, battery-management system, low-power sleep mode, and adaptive upload frequency.

Adaptive power-saving rule

Condition Upload Interval Local Warning
Safe, daytime 60 seconds Green LED only
Fog detected 15–20 seconds Yellow/red warning
Emergency 10 seconds Lamp, buzzer, immediate n8n alert
Low battery 120 seconds Reduce Wi-Fi and lamp usage

ThingSpeak’s free tier permits one update every 15 seconds, so use 15 seconds as the fastest normal publishing interval.[uk.mathworks]

Voice Notification Automation

The n8n workflow can convert the AI Agent’s warning into speech using an OpenAI text-to-speech API call, then send the resulting MP3 file through Telegram.

Telegram’s sendVoice method is intended for playable voice notes; n8n’s Telegram node can send an audio binary file using Send Audio.[docs.n8n][core.telegram]

Voice message template

Attention. Mountain road safety alert at Ghat Road Curve 7.
Fog and low visibility detected.
Reduce speed, use low-beam headlights, and maintain a safe distance.
Emergency warning lights are active.

Alert priority

Event Voice Priority Telegram Message
Fog only Medium “Low visibility detected. Drive cautiously.”
Fog + vehicle High “Vehicle approaching low-visibility zone. Reduce speed.”
Smoke/gas High “Possible smoke or gas detected. Avoid the area.”
Vibration/landslide Emergency “Possible ground movement detected. Stop and evacuate the zone.”
Heavy rain + fog Emergency “Dangerous road condition. Avoid travel until conditions improve.”

Testing Procedure

  1. Power the ESP32 and confirm it connects to Wi-Fi.
  2. Open Serial Monitor at 115200 baud.
  3. Verify DHT22 temperature and humidity readings.
  4. Cover the LDR to simulate night/fog.
  5. Place an object before the fog IR sensor.
  6. Move your hand in front of HC-SR04 and verify distance.
  7. Apply heat or gas near MQ-2 carefully and observe the ADC value.
  8. Trigger the vibration sensor.
  9. Confirm LED and buzzer behavior for SAFE, CAUTION, DANGER, and EMERGENCY.
  10. Check ThingSpeak fields update.
  11. Trigger n8n manually using this test request:
curl -X POST https://YOUR_N8N_DOMAIN/webhook/mountain-road-alert \
-H "Content-Type: application/json" \
-d '{
  "device_id": "MOUNTAIN_ROAD_01",
  "location": "Ghat Road Curve 7",
  "risk_level": "EMERGENCY",
  "risk_score": 92,
  "temperature": 18.5,
  "humidity": 94,
  "gas_value": 820,
  "light_value": 320,
  "rain_value": 1200,
  "distance_cm": 55,
  "fog_detected": true,
  "vehicle_detected": true,
  "vibration": false
}'
  1. Confirm Google Sheets receives a new row.
  2. Confirm Telegram receives both text and voice alerts.

Deployment Guide

Hardware protection

  • Use an IP65 or IP66 enclosure.
  • Place the MQ-2 sensor where airflow is available but rain cannot directly enter.
  • Mount ultrasonic and IR sensors away from direct sunlight.
  • Use silicone-sealed cable glands.
  • Add surge protection and fuse protection for 12 V lamps.
  • Mount the enclosure at least 2–3 meters above road level.
  • Use reflective warning signs near the installation.

Network reliability

  • Prefer a 4G Wi-Fi router or LTE modem at remote mountain sites.
  • Configure ESP32 reconnect logic.
  • Store failed alerts locally in SPIFFS/LittleFS and retry later.
  • Use a watchdog timer to recover from hangs.
  • Keep n8n on a reliable cloud VPS or local server with internet access.

Calibration

Sensor Calibration Method
MQ-2 Warm up 24–48 hours initially; record clean-air baseline and smoke threshold
LDR Record values in daylight, dusk, fog, and night
IR fog sensor Test with water mist or fog machine
HC-SR04 Measure known distances and adjust offset
Rain sensor Test dry, light spray, and heavy water conditions
Vibration sensor Adjust sensitivity potentiometer

Future Enhancements

  • Add a camera module for AI-based vehicle, animal, rockfall, and accident detection.
  • Use LoRaWAN for locations without mobile/Wi-Fi coverage.
  • Add solar power with battery-state monitoring.
  • Add variable-message signs showing “FOG AHEAD” or “SLOW DOWN.”
  • Use GPS to send exact coordinates during emergencies.
  • Add automatic gate/barrier control during landslide risk.
  • Add a traffic-light-style system for blind curves.
  • Use TinyML on ESP32 for offline fog and accident-risk classification.
  • Add weather API integration for advance fog and rainfall prediction.
  • Create a mobile app with live map, camera snapshots, and route-risk scoring.
  • Add automatic emergency calls to highway police or local disaster-response teams.

Documentation Summary

This project combines edge sensing, local actuation, cloud monitoring, AI decision-making, and automated communication. The ESP32 provides immediate road warnings even before cloud processing, while n8n adds AI-based interpretation, Google Sheets record keeping, ThingSpeak visualization, and Telegram voice alerts. ThingSpeak is designed for IoT data collection and analysis through REST/MQTT and MATLAB tools, while n8n connects the ESP32 events to Telegram and Google Sheets automation.[docs.n8n][docs.n8n][mathworks]