Žiga BeravsDanilo MikovićJožef Jovanovskisamo lotricLuka Mali
Published © GPL3+

PyroGuard

Detecting wildfires before they spread - using AI and IoT to protect forests, lives, and communities.

AdvancedWork in progress5 days272
PyroGuard

Things used in this project

Story

Read more

Code

AI-Powered Sound Detection over LoRaWAN

C/C++
We uploaded this code to our LoRaWAN-enabled Arduino device, allowing it to send data about the detected audio using AI (AIoT).
#include <Arduino.h>
#include <Adafruit_TinyUSB.h>
#include <PDM.h>
#include <FireDetection_inferencing.h>

// Edge Impulse Inference
typedef struct {
    signed short *buffers[2];
    unsigned char buf_select;
    unsigned char buf_ready;
    unsigned int buf_count;
    unsigned int n_samples;
} inference_t;

static inference_t inference;
static bool record_ready = false;
static signed short *sampleBuffer;
static bool debug_nn = false; 
static int print_results = -(EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW);

// LoRaWAN Communication
void setup() {
    // Initialize Serial for communication
    Serial.begin(115200);
    while (!Serial);
    Serial.println("Edge Impulse Inferencing Demo");

    // Initialize LoRaWAN
    pinMode(5, OUTPUT);
    digitalWrite(5, HIGH);
    Serial1.begin(9600);
    while (!Serial1) {}

    Serial1.write("AT+ID=AppEui,\"1581959185195810\"");
    delay(1000);
    Serial1.write("AT+ID=DevEui,\"70B3D57ED0070837\"");
    delay(1000);
    Serial1.write("AT+KEY=AppKey,\"EDDBA5DCBB0CAC01777EE4294B15E1F0\"");
    delay(1000);
    Serial1.write("AT+MODE=LWOTAA");
    delay(1000);
    Serial1.write("AT+JOIN");
    delay(5000);

    // Inferencing settings
    ei_printf("Inferencing settings:\n");
    ei_printf("\tInterval: %.2f ms.\n", (float)EI_CLASSIFIER_INTERVAL_MS);
    ei_printf("\tFrame size: %d\n", EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE);
    ei_printf("\tSample length: %d ms.\n", EI_CLASSIFIER_RAW_SAMPLE_COUNT / 16);
    ei_printf("\tNo. of classes: %d\n", sizeof(ei_classifier_inferencing_categories) /
                                            sizeof(ei_classifier_inferencing_categories[0]));

    run_classifier_init();
    if (microphone_inference_start(EI_CLASSIFIER_SLICE_SIZE) == false) {
        ei_printf("ERR: Could not allocate audio buffer (size %d)\r\n", EI_CLASSIFIER_RAW_SAMPLE_COUNT);
        return;
    }
}

void loop() {
    bool m = microphone_inference_record();
    if (!m) {
        ei_printf("ERR: Failed to record audio...\n");
        return;
    }

    signal_t signal;
    signal.total_length = EI_CLASSIFIER_SLICE_SIZE;
    signal.get_data = &microphone_audio_signal_get_data;
    ei_impulse_result_t result = {0};

    EI_IMPULSE_ERROR r = run_classifier_continuous(&signal, &result, debug_nn);
    if (r != EI_IMPULSE_OK) {
        ei_printf("ERR: Failed to run classifier (%d)\n", r);
        return;
    }

    if (++print_results >= (EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW)) {
        // Print predictions
        ei_printf("Predictions ");
        ei_printf("(DSP: %d ms., Classification: %d ms., Anomaly: %d ms.)",
            result.timing.dsp, result.timing.classification, result.timing.anomaly);
        ei_printf(": \n");
        for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
            ei_printf("    %s: %.5f\n", result.classification[ix].label,
                      result.classification[ix].value);
        }

        // LOGIKA
        if (result.classification[0].value > 0.5) {
          String jsonData = String(result.classification[0].label) + ": " + String(result.classification[0].value) + ", " +
                                 String(result.classification[1].label) + ": " + String(result.classification[1].value) + ", " +
                                 String(result.classification[2].label) + ": " + String(result.classification[2].value) + ", " +
                                 String(result.classification[3].label) + ": " + String(result.classification[3].value);


  
          Serial1.print("AT+MSG=\"");
          Serial1.print(jsonData);
          Serial1.println("\"");
          delay(1000);
        }

        #if EI_CLASSIFIER_HAS_ANOMALY == 1
                ei_printf("    anomaly score: %.3f\n", result.anomaly);
        #endif

        // Send the classification result over LoRaWAN
        String dataToSend = "Predictions: ";
        for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
            dataToSend += String(result.classification[ix].label) + ": " + String(result.classification[ix].value) + ", ";
        }
        // Sending prediction data via LoRaWAN
        //Serial1.print("AT+MSG=\"");
        //Serial1.print(dataToSend);
        //Serial1.println("\"");
        delay(10000); // Wait before sending the next message

        print_results = 0;
    }
}

// LoRaWAN PDM Data Handling
static void pdm_data_ready_inference_callback(void) {
    int bytesAvailable = PDM.available();
    int bytesRead = PDM.read((char *)&sampleBuffer[0], bytesAvailable);

    if (record_ready == true) {
        for (int i = 0; i < bytesRead >> 1; i++) {
            inference.buffers[inference.buf_select][inference.buf_count++] = sampleBuffer[i];

            if (inference.buf_count >= inference.n_samples) {
                inference.buf_select ^= 1;
                inference.buf_count = 0;
                inference.buf_ready = 1;
            }
        }
    }
}

static bool microphone_inference_start(uint32_t n_samples) {
    inference.buffers[0] = (signed short *)malloc(n_samples * sizeof(signed short));
    if (inference.buffers[0] == NULL) return false;
    
    inference.buffers[1] = (signed short *)malloc(n_samples * sizeof(signed short));
    if (inference.buffers[1] == NULL) {
        free(inference.buffers[0]);
        return false;
    }

    sampleBuffer = (signed short *)malloc((n_samples >> 1) * sizeof(signed short));
    if (sampleBuffer == NULL) {
        free(inference.buffers[0]);
        free(inference.buffers[1]);
        return false;
    }

    inference.buf_select = 0;
    inference.buf_count = 0;
    inference.n_samples = n_samples;
    inference.buf_ready = 0;

    // Set up PDM
    PDM.onReceive(&pdm_data_ready_inference_callback);
    PDM.setBufferSize((n_samples >> 1) * sizeof(int16_t));
    if (!PDM.begin(1, EI_CLASSIFIER_FREQUENCY)) {
        ei_printf("Failed to start PDM!");
    }
    PDM.setGain(127);

    record_ready = true;
    return true;
}

static bool microphone_inference_record(void) {
    bool ret = true;
    if (inference.buf_ready == 1) {
        ei_printf("Error sample buffer overrun. Decrease the number of slices per model window\n");
        ret = false;
    }

    while (inference.buf_ready == 0) {
        delay(1);
    }

    inference.buf_ready = 0;
    return ret;
}

static int microphone_audio_signal_get_data(size_t offset, size_t length, float *out_ptr) {
    numpy::int16_to_float(&inference.buffers[inference.buf_select ^ 1][offset], out_ptr, length);
    return 0;
}

static void microphone_inference_end(void) {
    PDM.end();
    free(inference.buffers[0]);
    free(inference.buffers[1]);
    free(sampleBuffer);
}

IgnisGuard

This Android application receives audio classification data sent from the LoRaWAN-enabled device. It alerts the user in real-time if a fire-related sound is detected using AI-based analysis.

Pyroscope

This Android application receives audio classification data sent from the LoRaWAN-enabled device. It alerts the user in real-time if a fire-related sound is detected using AI-based analysis.

Credits

Žiga Beravs
1 project • 1 follower
Danilo Miković
1 project • 2 followers
Jožef Jovanovski
1 project • 2 followers
samo lotric
1 project • 1 follower
Luka Mali
20 projects • 24 followers
Maker Pro, prototyping enthusiast, head of MakerLab, a lecturer at the University of Ljubljana, founder.

Comments