Oluwatobi Ademosu
Published © MIT

WildGuard: Off-Grid Edge AI Wildlife Monitoring

WildGuard is an advanced edge-AI early-warning camera designed to combat illegal poaching and monitor wildlife corridors.

AdvancedFull instructions providedOver 1 day27
WildGuard: Off-Grid Edge AI Wildlife Monitoring

Things used in this project

Hardware components

Arduino UNO Q
×1
Buzzer
Buzzer
×1
5 mm LED: Red
5 mm LED: Red
×1
5 mm LED: Green
5 mm LED: Green
×1
5 mm LED: Yellow
5 mm LED: Yellow
×1

Software apps and online services

Arduino App Lab

Story

Read more

Custom parts and enclosures

Circuit diagram

File missing, please reupload.

Circuit diagram

Additional Images

Others

Schematics

Schematics

File missing, please reupload.

Circuit diagram

Circuit diagram

Code

Code

C/C++
No preview (download only).

Code

Python
# SPDX-License-Identifier: MPL-2.0
# WildGuard Conservation & Ranger System - Python Backend
# Programmed for the Qualcomm Dragonwing QRB2210 eMMC MPU

import os
import json
import time
import secrets
import string
import threading
from datetime import datetime, timezone
from arduino.app_utils import App, Bridge
from arduino.app_bricks.web_ui import WebUI
from arduino.app_utils.camera import WebSocketCamera, resized

# ----------------------------------------------------------------------
#  CONFIGURATION & STATE
# ----------------------------------------------------------------------
LOG_FILE = "/home/arduino/ArduinoApps/wildlife_sys/events.jsonl"
MOCK_NODE_ID = "WG-SERENGETI-09"
MOCK_LAT = -2.1540
MOCK_LON = 34.6857

# Alert state suppression configurations
SUPPRESSION_WINDOW = 60.0  # seconds
last_alert_timestamps = {
    "human_intrusion": 0.0,
    "vehicle_intrusion": 0.0,
    "animal_sighting": 0.0
}

# Anomaly threshold: Trigger warning if no wildlife activity for 5 minutes
WILDLIFE_ANOMALY_TIMEOUT = 300.0  # seconds
last_wildlife_sighting_time = time.time()
anomaly_alert_fired = False

# Class translation dictionary
CLASS_MAPPING = {
    "person": {
        "event_type": "human_intrusion",
        "label": "Poacher / Human Intrusion",
        "alert": True,
        "confidence_threshold": 0.80
    },
    "car": {
        "event_type": "vehicle_intrusion",
        "label": "Vehicle Intrusion",
        "alert": True,
        "confidence_threshold": 0.80
    },
    "truck": {
        "event_type": "vehicle_intrusion",
        "label": "Vehicle Intrusion",
        "alert": True,
        "confidence_threshold": 0.80
    },
    "dog": {
        "event_type": "animal_sighting",
        "label": "Wild Dog Sighting",
        "alert": False,
        "confidence_threshold": 0.60
    },
    "cat": {
        "event_type": "animal_sighting",
        "label": "Leopard / Cat Sighting",
        "alert": False,
        "confidence_threshold": 0.60
    }
}

# ----------------------------------------------------------------------
#  CONNECTION PARING SETUP
# ----------------------------------------------------------------------
def generate_secret() -> str:
    return ''.join(secrets.choice(string.digits) for _ in range(6))

secret = generate_secret()
ui = WebUI()
resolution = (480, 640)
camera = WebSocketCamera(
    resolution=resolution, 
    secret=secret, 
    encrypt=True, 
    adjustments=resized(resolution, maintain_ratio=True)
)

# Send credentials on frontend connection
ui.on_connect(lambda sid: ui.send_message("welcome", {
    "client_name": camera.name,
    "secret": secret,
    "status": camera.status,
    "protocol": camera.protocol,
    "ip": camera.ip,
    "port": camera.port
}))

# ----------------------------------------------------------------------
#  AI CLASSIFICATION & DECISION PIPELINE
# ----------------------------------------------------------------------
def log_event_to_jsonl(event):
    """Saves threat evidence to local files, emulating an SD logging module."""
    try:
        with open(LOG_FILE, "a") as f:
            f.write(json.dumps(event) + "\n")
    except Exception as e:
        print(f"[BACKEND ERROR] Logging failed: {e}")

def process_detections(raw_detections: dict):
    """Processes classifications, coordinates mapping, alerts, and suppression."""
    global last_wildlife_sighting_time, anomaly_alert_fired
    current_time = time.time()
    
    for label, details in raw_detections.items():
        label_lower = label.lower()
        if label_lower not in CLASS_MAPPING:
            continue
            
        map_config = CLASS_MAPPING[label_lower]
        confidence = details.get("confidence", 0.0)
        
        # Check confidence threshold filter
        if confidence < map_config["confidence_threshold"]:
            continue
            
        event_type = map_config["event_type"]
        
        # Apply duplicate suppression per class type
        elapsed = current_time - last_alert_timestamps[event_type]
        if elapsed < SUPPRESSION_WINDOW:
            print(f"[SUPPRESSED] Duplicate '{event_type}' ignored. Cooldown remaining: {SUPPRESSION_WINDOW - elapsed:.1f}s")
            continue
            
        # Update timestamp markers
        last_alert_timestamps[event_type] = current_time
        
        if event_type == "animal_sighting":
            last_wildlife_sighting_time = current_time
            anomaly_alert_fired = False  # Reset anomaly if animal is seen

        # Build formal JSON event packet
        event_data = {
            "event_type": event_type,
            "label": map_config["label"],
            "confidence": float(confidence),
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "node_id": MOCK_NODE_ID,
            "gps_lat": MOCK_LAT,
            "gps_lon": MOCK_LON,
            "alert": map_config["alert"]
        }
        
        print(f"[DECISION ENGINE] Threat verified: {map_config['label']} | Confidence: {confidence*100:.1f}%")
        
        # Write SD replica log
        log_event_to_jsonl(event_data)
        
        # Publish payload to frontend
        ui.send_message("wildguard_event", event_data)
        
        # Trigger real-time alert asynchronously on MCU side via Bridge notify
        if map_config["alert"]:
            # Type 1: Human Intrusion
            Bridge.notify("trigger_alert", 1, int(confidence * 100))
        else:
            # Type 2: Informational Animal Sighting
            Bridge.notify("trigger_alert", 2, int(confidence * 100))

# ----------------------------------------------------------------------
#  SECONDARY ANOMALY TIMEOUT BACKGROUND THREAD
# ----------------------------------------------------------------------
def anomaly_check_loop():
    """Triggers alarms if no animal activity occurs for over 5 minutes."""
    global anomaly_alert_fired
    while True:
        try:
            current_time = time.time()
            elapsed_no_wildlife = current_time - last_wildlife_sighting_time
            
            if elapsed_no_wildlife > WILDLIFE_ANOMALY_TIMEOUT and not anomaly_alert_fired:
                anomaly_alert_fired = True
                anomaly_packet = {
                    "event_type": "low_activity_warning",
                    "label": "Low Wildlife Activity Warning",
                    "timestamp": datetime.now(timezone.utc).isoformat(),
                    "elapsed_sec": int(elapsed_no_wildlife),
                    "node_id": MOCK_NODE_ID
                }
                print(f"[CRITICAL ANOMALY] Alert: No wildlife seen in {elapsed_no_wildlife/60:.1f} minutes!")
                log_event_to_jsonl(anomaly_packet)
                ui.send_message("low_activity_warning", anomaly_packet)
                
            time.sleep(10)
        except Exception as e:
            print(f"[ANOMALY MODULE ERROR] {e}")
            time.sleep(10)

# Instantiate loop hooks
def loop():
    # Keep the MPU thread from locks
    time.sleep(0.5)

def linux_started():
    print("[MPU] MCU sync successful. WildGuard daemon online.")
    return True

# Initialize components
print("[MPU] Initializing WildGuard Systems...")
Bridge.provide("linux_started", linux_started)

# Start background anomaly tracker
threading.Thread(target=anomaly_check_loop, daemon=True).start()

# Bind brick callbacks
from arduino.app_bricks.video_objectdetection import VideoObjectDetection
detection = VideoObjectDetection(camera, confidence=0.5)
detection.on_detect_all(process_detections)

# Final block execution
App.run(user_loop=loop)

Code

YAML
name: WildGuard App Lab Console
description: "A dual-brain edge-AI smart camera and tactical dashboard for wildlife protection."
icon: 
version: "1.0.0"
ports: 
bricks:
  - id: arduino:web_ui
    configuration:
      port: 7000

Credits

Oluwatobi Ademosu
6 projects • 2 followers

Comments