Bark Beetle Sentry

Bark Beetle Sentry detects bark beetles early using smart sensors to protect forests from infestation.

IntermediateWork in progressOver 1 day201
Bark Beetle Sentry

Things used in this project

Hardware components

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

Software apps and online services

Arduino IDE
Arduino IDE
Edge Impulse Studio
Edge Impulse Studio
ThingsBoard
The Things Network

Story

Read more

Code

Arduino code

Arduino
#include <Arduino.h>
#include <PDM.h>
#include "secrets.h"
#include <mp3_prvi_model_inferencing.h>

#define MODULE_EN_PIN 5
#define DEVICE_ID "001"

#ifndef EIDSP_QUANTIZE_FILTERBANK
#define EIDSP_QUANTIZE_FILTERBANK 0
#endif
#define EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW 4

const unsigned long MIN_SEND_INTERVAL = 1 * 60 * 1000;  // 1 minute
const unsigned long HEARTBEAT_INTERVAL = 10 * 60 * 1000; // 10 minutes
const float CLASSIFY_THRESHOLD = 0.8;
const unsigned long CLASSIFY_INTERVAL = 10000; // 10 seconds

unsigned long lastSentTime = 0;
unsigned long lastHeartbeatTime = 0;
unsigned long lastClassifyTime = 0;

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);

void sendATCommand(String command);
void sendStatusMessage(bool lubadarDetected);
float readBatteryPercent();

void setup() {
    pinMode(MODULE_EN_PIN, OUTPUT);
    digitalWrite(MODULE_EN_PIN, HIGH);
    delay(1000);

    Serial.begin(9600);
    Serial1.begin(9600);
    delay(3000);

    Serial.println("\n\nInitializing module...\n\n");
    sendATCommand("AT");
    sendATCommand("AT+ID=DevEui,\"" + String(DEV_EUI) + "\"");
    sendATCommand("AT+ID=AppEui,\"" + String(APP_EUI) + "\"");
    sendATCommand("AT+KEY=APPKEY,\"" + String(APP_KEY) + "\"");
    sendATCommand("AT+MODE=LWOTAA");
    sendATCommand("AT+JOIN");

    Serial.println("\n\nModule initialization completed!\n\n");
    delay(15000);

    run_classifier_init();
    lastSentTime = millis();
    lastHeartbeatTime = millis();
    lastClassifyTime = millis();
}

void loop() {
    if ((millis() - lastClassifyTime) < CLASSIFY_INTERVAL) return;
    lastClassifyTime = millis();

    microphone_inference_end();
    if (!microphone_inference_start(EI_CLASSIFIER_SLICE_SIZE)) {
        ei_printf("Error: buffer not allocated\n");
        return;
    }

    // Collect all required slices
    for (int i = 0; i < EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW; i++) {
        if (!microphone_inference_record()) {
            ei_printf("Error while capturing slice\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 };

    if (run_classifier_continuous(&signal, &result, debug_nn) != EI_IMPULSE_OK) return;

    Serial.println("\nClassification result:");
    float prob = 0.0;
    for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
        Serial.print("  ");
        Serial.print(result.classification[ix].label);
        Serial.print(": ");
        Serial.println(result.classification[ix].value, 5);

        if (String(result.classification[ix].label) == "Lubadar") {
            prob = result.classification[ix].value;
        }
    }

    bool lubadarDetected = prob > CLASSIFY_THRESHOLD;
    bool heartbeatDue = (millis() - lastHeartbeatTime) > HEARTBEAT_INTERVAL;
    bool minIntervalPassed = (millis() - lastSentTime) > MIN_SEND_INTERVAL;

    if ((lubadarDetected && minIntervalPassed) || (heartbeatDue && minIntervalPassed)) {
        sendStatusMessage(lubadarDetected);
        lastSentTime = millis();
        if (heartbeatDue) lastHeartbeatTime = millis();
    }
}


void sendATCommand(String command) {
    Serial1.print(command + "\r\n");
    Serial.print("Sending: ");
    Serial.println(command);

    unsigned long timeout = millis() + 3000;
    while (millis() < timeout) {
        if (Serial1.available()) {
            char c = Serial1.read();
            Serial.write(c);
        }
    }
}

void sendStatusMessage(bool lubadarDetected) {
    int battery = (int)readBatteryPercent();
    String batteryStr = String(battery);
    while (batteryStr.length() < 3) batteryStr = "0" + batteryStr;

    String payload = String(DEVICE_ID) + batteryStr + (lubadarDetected ? "1" : "0");
    sendATCommand("AT+MSG=\"" + payload + "\"");
    Serial.println("[LoRa] Sent: " + payload);
}

float readBatteryPercent() {
    return 77; // Placeholder for testing
}

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

    if (!record_ready) return;

    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));
    inference.buffers[1] = (signed short *)malloc(n_samples * sizeof(signed short));
    sampleBuffer = (signed short *)malloc((n_samples >> 1) * sizeof(signed short));

    if (!inference.buffers[0] || !inference.buffers[1] || !sampleBuffer) return false;

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

    PDM.onReceive(pdm_data_ready_inference_callback);
    PDM.setBufferSize((n_samples >> 1) * sizeof(int16_t));
    if (!PDM.begin(1, EI_CLASSIFIER_FREQUENCY)) return false;

    PDM.setGain(127);
    record_ready = true;
    return true;
}

static bool microphone_inference_record(void) {
    if (inference.buf_ready == 1) {
        ei_printf("Buffer overrun!\n");
        return false;
    }
    while (inference.buf_ready == 0) delay(1);
    inference.buf_ready = 0;
    return true;
}

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);
}

TTN uplink

JavaScript
function decodeUplink(input) {
  try {
    const decoded = String.fromCharCode.apply(null, input.bytes);
    
    const id = decoded.substring(0, 3);
    const battery = decoded.substring(3, 6);
    const alert = decoded.substring(6, 7);

    return {
      data: {
        id: parseInt(id, 10),
        battery: parseInt(battery, 10),
        alert: parseInt(alert, 10)
      },
      warnings: [],
      errors: []
    };
  } catch (error) {
    return {
      data: {},
      warnings: [],
      errors: ['Decoding error: ' + error.message]
    };
  }
}

ei-mp3_prvi_model-arduino-1.0.6.zip

Arduino
Arduino ML library
No preview (download only).

Credits

Matej Brezner
1 project • 2 followers
Matej Turk
1 project • 2 followers
Uroš Sobotič Verdnik
1 project • 1 follower
Mihael Zeme
1 project • 2 followers
Luka Mali
20 projects • 24 followers
Maker Pro, prototyping enthusiast, head of MakerLab, a lecturer at the University of Ljubljana, founder.

Comments