pyroshield

Low-power forest fire monitoring system

C++C MIT

Pyroshield is a hardware framework for deploying low-power forest fire monitoring systems.

Many modern environmental monitoring solutions rely on high bandwidth wireless communication and energy intensive sensors. Pyroshield is instead built around extreme power efficiency and long-range, low-bandwidth data transmission. A sensor node that knows how to detect environmental anomalies is one that interfaces with the Pyroshield telemetry framework. This avoids the overhead of continuous data streaming, allowing the monitoring nodes to operate autonomously for extended periods. In fact, the interaction between the sensor hardware and the alerting systems can be completely optimized for mesh network topologies, leaving Pyroshield to perform reliable alerting even in austere environments.

The framework relies on an embedded real-time operating system (FreeRTOS) to manage sensor aggregation and telemetry concurrently. Sensor tasks poll data from environmental modules (BMP280, DHT22, MTP40F) without blocking the primary communication channels.

When an anomaly is detected, such as a sharp rise in CO2 levels, the transmission task automatically shifts into an emergency mode. This overrides the standard transmission interval to provide rapid, high-priority updates to the receiver ground station.

[[noreturn]] void task_lora_transmission(void *pvParameters) {
    (void) pvParameters;

    for (;;) {
        const uint16_t co2_ppm = read_mtp40f_gas_concentration();
        const bool emergency = co2_ppm >= EMERGENCY_MODE_CO2_THRESHOLD;
        const uint32_t interval = emergency ? emergency_mode_interval : transmission_interval;

        // collect payload from sensors
        LoRa_Payload payload;
        payload.co2_ppm = co2_ppm;
        payload.pressure = static_cast<uint16_t>(read_bmp280_pressure());
        payload.bmp280_altitude = static_cast<uint16_t>(read_bmp280_altitude());
        payload.dht22_temp = static_cast<uint16_t>(read_dht22_temperature() * 10);
        // ... (remaining fields)

        send_lora_message(payload, emergency);

        vTaskDelay(interval / portTICK_PERIOD_MS);
    }
}

Telemetry metrics

The alerting system relies on a highly packed C struct transmitted as a binary payload over LoRa. This avoids the overhead of text based serialization formats.

The LoRa_Payload structure aggregates environmental, geolocation, power, and system diagnostic metrics:

typedef struct __attribute__((__packed__)) {
    /* Header */
    byte forwarder_id = 0; /// Forwarder device ID.
    byte transmitter_id = 0; /// Transmitter device ID.
    uint16_t message_id = 0; /// The message ID.
    bool allow_forwarding = false; /// Forwarding flag.
    uint8_t ttl = 0; /// TTL value.

    /* Body */
    uint16_t co2_ppm{}; /// The CO2 gas concentration.
    uint16_t pressure{}; /// The pressure reading.
    uint16_t bmp280_altitude{}; /// The altitude reading from BMP280 sensor.
    uint16_t bmp280_temp{}; /// The temperature reading from BMP280 sensor.
    uint16_t dht22_temp{}; /// The temperature reading from DHT22 sensor.
    uint8_t humidity{}; /// The humidity reading.
    uint16_t gps_altitude{}; /// The altitude reading from GPS.
    float gps_lat{}; /// The latitude reading from GPS.
    float gps_lng{}; /// The longitude reading from GPS.
    uint16_t gps_satellites{}; /// The number of GPS satellites in view.

    /* Power */
    uint16_t battery_temp{}; /// The battery temperature reading.
    uint16_t battery_voltage{}; /// The battery voltage reading (scaled by 100).
    uint16_t charger_voltage{}; /// The charger voltage reading (scaled by 100).

    /* System */
    uint16_t memory_usage{}; /// The memory used by heap and stack (in bytes).
} LoRa_Payload;

The payload avoids floating point overhead for most metrics by utilizing fixed point scaling, derived directly from the connected hardware components:

  • Routing header (forwarder_id, transmitter_id, ttl): Manages the mesh network state. The TTL prevents infinite routing loops, while the IDs allow the ground station to trace the packet’s origin and hop path.
  • Gas concentration (co2_ppm): Derived from the MTP40F NDIR sensor. This is the primary trigger for the emergency transmission mode.
  • Environmental data (pressure, bmp280_temp, dht22_temp, humidity): Polled from the BMP280 and DHT22 sensors. To avoid floating point payloads, temperatures are scaled by a factor of 10 (25.4°C is transmitted as 254 inside a uint16_t).
  • Geolocation (gps_lat, gps_lng, gps_altitude): Acquired via a NEO-6M GPS module. These remain as native floats to preserve coordinate precision.
  • Power diagnostics (battery_voltage, charger_voltage): Read via the STM32’s internal ADCs and scaled by 100 (4.15V is transmitted as 415).

Each physical node is equipped with a battery, which is charged by an external power source such as a solar panel.

Mesh routing

To ensure alerts can reach a centralized ground station over vast, dense forested areas, Pyroshield implements a rudimentary mesh forwarding protocol over 433MHz LoRa.

Packets are embedded with a time-to-live (TTL) limit and device identifiers. When a node receives a payload, it verifies the TTL and re-transmits it, extending the range of the entire monitoring network while preventing packet flooding:

void handle_lora_reception() {
    const int packet_size = LoRa.parsePacket();
    if (packet_size == 0) return;

    LoRa_Payload payload;
    LoRa.readBytes(reinterpret_cast<byte *>(&payload), packet_size);

    // prevent routing loops and drop stuck packets
    if (payload.forwarder_id == device_id || payload.transmitter_id == device_id) return;
    if (--payload.ttl == 0) return;

    // fwd the packet deeper into the mesh
    LoRa.beginPacket();
    payload.forwarder_id = device_id;
    LoRa.write(reinterpret_cast<byte *>(&payload), sizeof(payload));
    LoRa.endPacket(true); // non blocking
}

This guarantees that even the most remote nodes can propagate critical alerts to the centralized monitoring group station, where emergency responses can be coordinated efficiently.