GrouseGuard - Bioacoustic monitoring of Wood grouse

Machine Learning for Conservation: Detecting the voice of the Wood Grouse

BeginnerWork in progress12 hours290
GrouseGuard - Bioacoustic monitoring of Wood grouse

Things used in this project

Hardware components

USB-A to USB-C cable
×1
Colibri IoT - NatureGuard
×1

Software apps and online services

The Things Stack
The Things Industries The Things Stack
Node-RED
Node-RED
Edge Impulse Studio
Edge Impulse Studio

Story

Read more

Schematics

NodeRED dashboard flow

dashboard flow for NodeRED

Code

NatureGuard module code

Arduino
Upload to Arduino or NatureGuard module
// Final version of Wood Grouse detection firmware using Edge Impulse model

// Configuration macros
#define EIDSP_QUANTIZE_FILTERBANK   0
#define EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW 4
#define NUM_MEASUREMENTS 4                       // Number of recent detections to average
#define THRESHOLD 0.85                            // Detection confidence threshold
#define PDM_gain 200                              // Microphone gain

// Include necessary libraries
#include <Arduino.h>
#include <Adafruit_TinyUSB.h>
#include <PDM.h>                                  // For using PDM microphone
#include <Diviji_petelin_inferencing.h>           // Edge Impulse generated model

// Struct to manage inference buffer and state
typedef struct {
    signed short *buffers[2];     // Double buffer for audio samples
    unsigned char buf_select;     // Active buffer index
    unsigned char buf_ready;      // Flag to indicate if a buffer is ready
    unsigned int buf_count;       // Number of samples written
    unsigned int n_samples;       // Total samples expected
} inference_t;

// Global variables
static inference_t inference;
static bool record_ready = false;
static signed short *sampleBuffer;
static bool debug_nn = false;

float measurements[NUM_MEASUREMENTS];             // Ring buffer for confidence values
int measurement_index = 0;
unsigned long last_detection_time = 0;            // Timestamp of last detection

void setup() {
    // Configure pin 5 as output and pull HIGH (likely power or enable line)
    pinMode(5, OUTPUT);
    digitalWrite(5, HIGH);

    // Initialize Serial interfaces
    Serial.begin(9600);
    while (!Serial) delay(10);                   // Wait for serial to initialize

    Serial1.begin(9600);                          // Possibly used for LoRa or external communication
    delay(2000);

    // Initialize the Edge Impulse classifier
    run_classifier_init();

    // Initialize microphone for inference
    if (!microphone_inference_start(EI_CLASSIFIER_SLICE_SIZE)) {
        ei_printf("ERR: audio inference setup failed\n");
        return;
    }

    ei_printf("Microphone ready.\n");

    // LoRa init
    Serial1.println("AT+ADR");
    delay(200);
    Serial1.println("AT+JOIN");
    ei_printf("Poiljam AT+JOIN...\n");

    delay(7000); // delay for join

    // connection test
    Serial1.println("AT+MSG=\"test bober, povezani smo in poslalo je\"");
}

void loop() {
    // Check if new audio slice is ready
    if (!microphone_inference_record()) {
        ei_printf("Ni podatkov, PDM overflow?\n");
        return;
    }

    // Run classification on the recorded slice
    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: classifier failed (%d)\n", r);
        return;
    }

    // Extract the prediction score for label 'Petelin'
    float petelin_score = 0.0f;
    for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
        if (strcmp(result.classification[ix].label, "Petelin") == 0) {
            petelin_score = result.classification[ix].value;
            break;
        }
    }

// Store the score in a circular buffer
    measurements[measurement_index++] = petelin_score;

// If enough measurements collected, calculate average
    if (measurement_index >= NUM_MEASUREMENTS) {
        float avg = 0.0f;
        for (int i = 0; i < NUM_MEASUREMENTS; i++) {
            avg += measurements[i];
        }
        avg /= NUM_MEASUREMENTS;

// Print average detection confidence
        Serial.print("Povpreje: ");
        Serial.println(avg, 5);

// If average confidence is above threshold, detection confirmed
        if (avg >= THRESHOLD) {
            Serial.println("Petelin zaznan!");
            Serial1.println("AT+MSG=\"1\"");
            last_detection_time = millis();
        } else {
            Serial.println("Ni petelina.");
        }

        measurement_index = 0;
    }
// Echo any incoming messages from Serial1 to Serial
    while (Serial1.available()) {
        Serial.write(Serial1.read());
    }
}
// Microphone PDM data callback - fills inference buffers
static void pdm_data_ready_inference_callback(void) {
    // Get number of bytes available from microphone
    int bytesAvailable = PDM.available();
    // Read the audio samples into sampleBuffer
    int bytesRead = PDM.read((char *)&sampleBuffer[0], bytesAvailable);

    // Only fill inference buffer if recording is active
    if (record_ready) {
        for (int i = 0; i < bytesRead >> 1; i++) {
            // Copy audio samples into the inference buffer
            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;
            }
        }
    }
}

//  Dodan timeout + zaita pred zmrzovanjem
// Wait until buffer is ready or timeout occurs
static bool microphone_inference_record(void) {
    const unsigned long start = millis();
    while (inference.buf_ready == 0) {
        delay(1);
        // Timeout protection in case buffer never fills
        if (millis() - start > 1000) { // max 1s
            ei_printf("Timeout akanja na PDM buffer\n");
            return false;
        }
    }

    inference.buf_ready = 0;
    return true;
}

// Allocate and initialize inference buffers and PDM mic
static bool microphone_inference_start(uint32_t n_samples) {
    inference.buffers[0] = (signed short *)malloc(n_samples * sizeof(signed short));
    if (!inference.buffers[0]) return false;

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

    sampleBuffer = (signed short *)malloc((n_samples >> 1) * sizeof(signed short));
    if (!sampleBuffer) {
        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;

    PDM.onReceive(&pdm_data_ready_inference_callback);
    PDM.setBufferSize((n_samples >> 1) * sizeof(int16_t));
    // Start microphone with 1 channel at required frequency
    if (!PDM.begin(1, EI_CLASSIFIER_FREQUENCY)) {
        ei_printf("Napaka: PDM zaetek spodletel\n");
        return false;
    }

    PDM.setGain(PDM_gain);
    // Set flag indicating that inference is ready to record
    record_ready = true;

    return true;
}

// Provide audio sample data to Edge Impulse classifier
static int microphone_audio_signal_get_data(size_t offset, size_t length, float *out_ptr) {
    // Convert raw audio to float format for classification
    numpy::int16_to_float(&inference.buffers[inference.buf_select ^ 1][offset], out_ptr, length);
    return 0;
}

// Clean up and release microphone and buffers
static void microphone_inference_end(void) {
    PDM.end();
    free(inference.buffers[0]);
    free(inference.buffers[1]);
    free(sampleBuffer);
}

#if !defined(EI_CLASSIFIER_SENSOR) || EI_CLASSIFIER_SENSOR != EI_CLASSIFIER_SENSOR_MICROPHONE
// Compilation error if model is not microphone-compatible
#error "Model ni ustrezen za mikrofon"
#endif

petelinV2-1.0.5.zip

Arduino
This includes ML library
No preview (download only).

Credits

Tilen Potokar
1 project • 0 followers
Jaka Urh
0 projects • 0 followers
Luka Gumilar
0 projects • 0 followers
Klemen Flegar
0 projects • 0 followers
Luka Mali
20 projects • 24 followers
Maker Pro, prototyping enthusiast, head of MakerLab, a lecturer at the University of Ljubljana, founder.

Comments