vishal soni
Published © MIT

Wild Alert Smart Roadside Reflectors

A smart road reflector and a radar transceiver mesh network, deployed along the jungle roadside to detect animal movement near the road

BeginnerWork in progressOver 1 day99
Wild Alert Smart Roadside Reflectors

Things used in this project

Hardware components

RCWL-0516 Microwave Radar Sensor
×2
SparkFun RGB LED Breakout - WS2812B
SparkFun RGB LED Breakout - WS2812B
×12
Seeed Studio XIAO ESP32S3 Sense
Seeed Studio XIAO ESP32S3 Sense
×1
Arduino Nano R3
Arduino Nano R3
×4
SparkFun Transceiver Breakout - nRF24L01+
SparkFun Transceiver Breakout - nRF24L01+
×5
Li-Ion Battery 1000mAh
Li-Ion Battery 1000mAh
×5
LDR, 5 Mohm
LDR, 5 Mohm
×2
Mini 360 Step-Down Buck Converter
×4
5V Solar Panel
×5

Software apps and online services

Arduino IDE
Arduino IDE
Edge Impulse Studio
Edge Impulse Studio
KiCad
KiCad
Fusion
Autodesk Fusion

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free
Mastech MS8217 Autorange Digital Multimeter
Digilent Mastech MS8217 Autorange Digital Multimeter

Story

Read more

Schematics

schmatic diagram for connection between arduino nano and RCWH radar

Code

Reflector Node

C/C++
// ReflectorNode.ino
// Arduino Nano: RCWL-0516 + WS2812B + nRF24L01 (RF24Mesh)
// Libraries required:
//   - Adafruit_NeoPixel
//   - SPI
//   - RF24
//   - RF24Network (or RF24Mesh if you want automatic addressing)

// Install via Library Manager: "Adafruit NeoPixel", "RF24", "RF24Network", "RF24Mesh"

#include <Adafruit_NeoPixel.h>
#include <SPI.h>
#include <RF24.h>
#include <RF24Network.h>
#include <RF24Mesh.h>

//// CONFIG ////
#define NUM_LEDS 8               // number of LEDs on single reflector unit
#define LED_PIN 6                // WS2812 data pin
#define MOTION_PIN 2             // RCWL-0516 OUT -> D2 (interrupt capable)
#define LDR_PIN A0               // optional: analog LDR read for night-only operation
#define NIGHT_THRESHOLD 500      // analog value below which it's considered NIGHT (tune)
#define NODE_ID  10              // unique numeric node id for RF24Mesh (set per-device)
#define RF_CE_PIN 7
#define RF_CSN_PIN 8
#define CHANNEL 100              // RF24 channel (0..125)
#define SEND_INTERVAL 2000       // ms between status sends

//// Globals ////
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);

RF24 radio(RF_CE_PIN, RF_CSN_PIN);
RF24Network network(radio);
RF24Mesh mesh(radio, network);

volatile bool motionTriggered = false;
unsigned long lastSend = 0;
uint8_t proximityLevel = 0; // 0=clear,1=nearby,2=danger

// packet payload
struct Payload {
  uint8_t nodeId;
  uint8_t level;     // 0,1,2
  uint32_t uptime;   // ms
};

void IRAM_ATTR onMotion() {
  motionTriggered = true;
}

void setLEDsForLevel(uint8_t level) {
  // level 0 -> green, 1 -> cyan, 2 -> red
  uint32_t color;
  if (level == 0) color = strip.Color(0,150,0);        // green
  else if (level == 1) color = strip.Color(0,150,150); // cyan
  else color = strip.Color(200,0,0);                   // red

  for (int i=0;i<NUM_LEDS;i++) strip.setPixelColor(i, color);
  strip.show();
}

void setup() {
  Serial.begin(115200);
  strip.begin();
  strip.show();
  pinMode(MOTION_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(MOTION_PIN), onMotion, RISING);

  // Setup RF24Mesh
  radio.begin();
  radio.setChannel(CHANNEL);
  radio.setDataRate(RF24_250KBPS);
  mesh.setNodeID(NODE_ID);
  mesh.begin();
  Serial.println("Mesh started");

  // initial LED state
  setLEDsForLevel(0);
}

void loop() {
  mesh.update();
  mesh.DHCP(); // optional if using RF24DHCP

  // check night condition
  bool isNight = true;
  int ldrVal = analogRead(LDR_PIN);
  if (ldrVal > NIGHT_THRESHOLD) isNight = false; // bright -> day

  // if daytime, keep green and low-power
  if (!isNight) {
    proximityLevel = 0;
    setLEDsForLevel(0);
    delay(500);
    return;
  }

  // handle motion trigger
  if (motionTriggered) {
    motionTriggered = false;
    // on first trigger, mark as nearby (1), if repeated within short time escalate to danger (2)
    static unsigned long lastTriggerTime = 0;
    unsigned long now = millis();
    if (now - lastTriggerTime < 3000) {
      proximityLevel = 2; // immediate danger if double-triggered
    } else {
      proximityLevel = 1; // nearby
    }
    lastTriggerTime = now;
    setLEDsForLevel(proximityLevel);
  }

  // degrade level over time (simulate moving away)
  static unsigned long levelSetTime = 0;
  if (proximityLevel != 0) {
    if (levelSetTime == 0) levelSetTime = millis();
    if (millis() - levelSetTime > 8000) { // after 8s reduce
      if (proximityLevel == 2) proximityLevel = 1;
      else proximityLevel = 0;
      setLEDsForLevel(proximityLevel);
      if (proximityLevel == 0) levelSetTime = 0;
      else levelSetTime = millis();
    }
  }

  // periodically broadcast status to mesh (so upstream or other reflectors know)
  if (millis() - lastSend > SEND_INTERVAL) {
    Payload p;
    p.nodeId = NODE_ID;
    p.level = proximityLevel;
    p.uptime = millis();
    if (mesh.write(&p, sizeof(p))) {
      // success
    } else {
      // you can retry or log
    }
    lastSend = millis();
  }

  // process incoming packets
  while (network.available()) {
    RF24NetworkHeader header;
    Payload recv;
    network.peek(header); // peek optional
    if (network.read(header, &recv, sizeof(recv))) {
      // For example: if we receive a danger alert from a camera node, set all LEDs to red
      if (recv.level == 2) {
        proximityLevel = 2;
        setLEDsForLevel(2);
      } else if (recv.level == 1 && proximityLevel < 2) {
        // escalate to at least cyan
        proximityLevel = max<uint8_t>(proximityLevel, 1);
        setLEDsForLevel(proximityLevel);
      }
    }
  }

  // small sleep to lower cpu usage
  delay(50);
}

Radar Node

C/C++
#include <SPI.h>
#include <RF24.h>
#include <RF24Network.h>
#include <RF24Mesh.h>

#define MOTION_PIN 2        // RCWL-0516 OUT -> D2
#define NODE_ID  15         // unique per radar node
#define RF_CE_PIN 7
#define RF_CSN_PIN 8
#define CHANNEL 100
#define SEND_INTERVAL 2000  // ms

RF24 radio(RF_CE_PIN, RF_CSN_PIN);
RF24Network network(radio);
RF24Mesh mesh(radio, network);

volatile bool motionTriggered = false;
uint8_t proximityLevel = 0; // 0=clear, 1=nearby, 2=danger
unsigned long lastSend = 0;

struct Payload {
  uint8_t nodeId;
  uint8_t level;
  uint32_t uptime;
};

void IRAM_ATTR onMotion() {
  motionTriggered = true;
}

void setup() {
  Serial.begin(115200);
  pinMode(MOTION_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(MOTION_PIN), onMotion, RISING);

  radio.begin();
  radio.setChannel(CHANNEL);
  radio.setDataRate(RF24_250KBPS);
  mesh.setNodeID(NODE_ID);
  mesh.begin();
  Serial.println("Radar node started");
}

void loop() {
  mesh.update();
  mesh.DHCP();

  // simple trigger logic
  if (motionTriggered) {
    motionTriggered = false;
    static unsigned long lastTrig = 0;
    unsigned long now = millis();
    if (now - lastTrig < 3000) {
      proximityLevel = 2; // repeated trigger quickly = danger
    } else {
      proximityLevel = 1; // single trigger = nearby
    }
    lastTrig = now;
  }

  // degrade after a while
  static unsigned long lastLevelTime = 0;
  if (proximityLevel > 0) {
    if (lastLevelTime == 0) lastLevelTime = millis();
    if (millis() - lastLevelTime > 8000) {
      proximityLevel = 0;
      lastLevelTime = 0;
    }
  }

  // send status
  if (millis() - lastSend > SEND_INTERVAL) {
    Payload p;
    p.nodeId = NODE_ID;
    p.level = proximityLevel;
    p.uptime = millis();
    mesh.write(&p, sizeof(p));
    lastSend = millis();
    Serial.printf("Sent level %d\n", proximityLevel);
  }

  delay(50);
}

Camera Node

C/C++
// ESP32CamAlert.ino
// Libraries: esp_camera (builtin), RF24, RF24Network, RF24Mesh
// Board: AI-Thinker ESP32-CAM

#include "esp_camera.h"
#include <WiFi.h> // for prints only
#include <SPI.h>
#include <RF24.h>
#include <RF24Network.h>
#include <RF24Mesh.h>

#define RF_CE 26
#define RF_CSN 27
#define NODE_ID 20
#define SEND_INTERVAL 1500

// RF setup
RF24 radio(RF_CE, RF_CSN);
RF24Network network(radio);
RF24Mesh mesh(radio, network);

struct Payload {
  uint8_t nodeId;
  uint8_t level;
  uint32_t uptime;
};

unsigned long lastSend = 0;
int dangerCounter = 0;

// Minimal camera init for AI thinker
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

void setup() {
  Serial.begin(115200);
  // camera config
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer   = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_GRAYSCALE;
  config.frame_size = FRAMESIZE_QQVGA; // small for fast processing
  config.jpeg_quality = 12;
  config.fb_count = 1;

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed: %d\n", err);
  } else {
    Serial.println("Camera OK");
  }

  // RF init
  radio.begin();
  radio.setDataRate(RF24_250KBPS);
  mesh.setNodeID(NODE_ID);
  mesh.begin();
  Serial.println("Mesh started on ESP32-CAM");
}

uint8_t detectMotionSimple() {
  // Very simple pixel-diff method: capture two frames and compare sum difference.
  // Returns 0 no-motion, 1 nearby, 2 danger.
  camera_fb_t * fb1 = esp_camera_fb_get();
  if (!fb1) return 0;
  delay(100);
  camera_fb_t * fb2 = esp_camera_fb_get();
  if (!fb2) {
    esp_camera_fb_return(fb1);
    return 0;
  }

  // compare a few bytes to estimate difference
  size_t len = min((size_t)2000, min(fb1->len, fb2->len));
  uint32_t diff = 0;
  for (size_t i=0;i<len;i+=20) { // sample step to speed up
    diff += abs((int)fb1->buf[i] - (int)fb2->buf[i]);
  }

  esp_camera_fb_return(fb1);
  esp_camera_fb_return(fb2);

  // thresholds (tune in field)
  if (diff > 1500) {
    // large movement
    return 2;
  } else if (diff > 400) {
    return 1;
  } else return 0;
}

void loop() {
  mesh.update();
  mesh.DHCP();

  uint8_t motionLevel = detectMotionSimple();

  if (motionLevel > 0) {
    // aggregate consecutive detections to decide danger
    if (motionLevel == 2) dangerCounter++;
    else dangerCounter = max(0, dangerCounter - 1);
  } else {
    dangerCounter = max(0, dangerCounter - 1);
  }

  uint8_t sendLevel = 0;
  if (dangerCounter >= 2) sendLevel = 2;
  else if (motionLevel == 1) sendLevel = 1;

  if (millis() - lastSend > SEND_INTERVAL && sendLevel > 0) {
    Payload p;
    p.nodeId = NODE_ID;
    p.level = sendLevel;
    p.uptime = millis();
    mesh.write(&p, sizeof(p));
    lastSend = millis();
    Serial.printf("Sent alert level %d\n", sendLevel);
  }

  // handle incoming messages similar to reflector node (optionally)
  while (network.available()) {
    RF24NetworkHeader header;
    Payload recv;
    network.peek(header);
    if (network.read(header, &recv, sizeof(recv))) {
      Serial.printf("Recv from %d level %d\n", recv.nodeId, recv.level);
    }
  }

  delay(200);
}

Credits

vishal soni
10 projects • 12 followers
Engineer ,Electronic Enthusiast

Comments