RockyTracker - Real Time Rockfall Detection & Alert System

A device that detects nearby rockfalls in real time and alerts subscribed users instantly when larger sized rocks are detected.

IntermediateFull instructions providedOver 6 days277

Things used in this project

Hardware components

Arduino Nano 33 BLE Sense
Arduino Nano 33 BLE Sense
×1
Seeed Studio XIAO nRF52840 Sense (XIAO BLE Sense)
Seeed Studio XIAO nRF52840 Sense (XIAO BLE Sense)
×1

Software apps and online services

The Things Network
Edge Impulse Studio
Edge Impulse Studio
Arduino IDE
Arduino IDE
Leaflet - an open-source JavaScript library for mobile-friendly interactive maps
Render
PostgreSQL

Story

Read more

Custom parts and enclosures

ei-mis--final-project-arduino-1_0_19_C4cPGz9unq.zip

Schematics

Workflow

Code

Arduino

Arduino
This code runs on a Seeed Studio XIAO nRF52840 Sense.
#define EIDSP_QUANTIZE_FILTERBANK   0
#define EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW 1
#define EI_CLASSIFIER_INTERVAL_MS 100

#include <PDM.h>
#include <MIS_-_final_project_inferencing.h>
#include <ArduinoBLE.h>
#include <Arduino.h>

/** Audio buffers, pointers and selectors */
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; // Set this to true to see e.g. features generated from the raw signal
static int print_results = -(EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW);
String Bmsg;

int lastClassification = -1; // to track last classification
unsigned long lastBatteryReportTime = 0;
const unsigned long batteryReportInterval = 20000; // 1 hour in milliseconds 3600000

int classificationCounts[2] = {0, 0}; // 0 = small, 1 = big
unsigned long classificationWindowStart = 0;
const unsigned long classificationWindowDuration = 5000; // 5 seconds classification duration
int threshold = 5; // detected big rocks to classify to big rocks
int majorityClassification;

static bool microphone_inference_start(uint32_t n_samples);
static bool microphone_inference_record(void);
static int microphone_audio_signal_get_data(size_t offset, size_t length, float *out_ptr);

/**
 * @brief      Arduino setup function
 */
void setup()
{
    Serial.begin(115200);
    while (!Serial);
    Serial.println("Edge Impulse Inferencing Demo");

    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), this could be due to the window length of your model\r\n", EI_CLASSIFIER_RAW_SAMPLE_COUNT);
        return;
    }

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

    Serial1.write("AT+ID=AppEui,\"13197332FFACFDAA\"");
    delay(1000);
    Serial1.write("AT+ID=DevEui,\"70B3D57ED0070835\"");
    delay(1000);
    Serial1.write("AT+KEY=AppKey,\"6FE3858DAA75C86D48C5A4B6DBCDED12\"");
    delay(1000);
    Serial1.write("AT+MODE=LWOTAA");
    delay(1000);
    Serial1.write("AT+JOIN");
    delay(5000);
}

/**
 * @brief      Arduino main function. Runs the inferencing loop.
 */
void loop()
{
    
    // battery
    if (millis() - lastBatteryReportTime >= batteryReportInterval) {
        lastBatteryReportTime = millis();
        //int batteryPercentage = analogRead(A0) * 100 / 1023;
        int battery = analogRead(A0);
        int batteryPercentage = map(battery, 0, 1023, 0, 100);
        //ei_printf("Battery percentage: %d%%\n", batteryPercentage);
        Bmsg = String(",battery=") + String(batteryPercentage);
    }
    
    // mic interface
    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_results = 0;

        //Only continue if the model thinks there's less than 50% chance the sound is an anomaly.
        //If the anomaly score is high ( 0.5), the rest of the classification logic is skipped.
        if (result.classification[1].value < 0.5) {
            int currentClassification = (result.classification[2].value > result.classification[0].value) ? 0 : 1;

            // Count this classification
            classificationCounts[currentClassification]++;

            // Start timer if not already started
            if (classificationWindowStart == 0) {
                classificationWindowStart = millis();
            }

            // Check if 10 seconds have passed
            if (millis() - classificationWindowStart >= classificationWindowDuration) {
                // Decide the majority classification
                if (classificationCounts[1] >= threshold) {
                    majorityClassification = 1;
                } else {
                    majorityClassification = 0;
                }

                // Only send if different from last sent
                if (majorityClassification != lastClassification) {
                    lastClassification = majorityClassification;

                    ei_printf("ID: %08X, ", NRF_FICR->DEVICEID[0]);
                    ei_printf("size: %d\n", majorityClassification);

                    char hexID[9];
                    snprintf(hexID, sizeof(hexID), "%08X", NRF_FICR->DEVICEID[0]);

                    uint32_t deviceID = NRF_FICR->DEVICEID[0];
                    int shortID = deviceID % 10000000;  // Range: [0, 9999999]
                    // Expected Collisions (n=1000): ~0.05
                    // Collision Probability: ~0.25%


                    String msg = String("AT+MSG=\"id=") + String(shortID) + 
                                 String(",size=") + String(majorityClassification) + 
                                 Bmsg + "\"";

                    char ch_msg[msg.length() + 1];
                    msg.toCharArray(ch_msg, sizeof(ch_msg));
                    Serial1.write((const uint8_t*)ch_msg, strlen(ch_msg));
                }

                // Reset for next window
                classificationCounts[0] = 0;
                classificationCounts[1] = 0;
                classificationWindowStart = 0;
            }
        }
    }

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

/**
 * @brief      PDM buffer full callback
 *             Get data and call audio thread callback
 */
static void pdm_data_ready_inference_callback(void)
{
    int bytesAvailable = PDM.available();

    // read into the sample buffer
    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;
            }
        }
    }
}

/**
 * @brief      Init inferencing struct and setup/start PDM
 *
 * @param[in]  n_samples  The n samples
 *
 * @return     { description_of_the_return_value }
 */
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;

    // configure the data receive callback
    PDM.onReceive(&pdm_data_ready_inference_callback);

    PDM.setBufferSize((n_samples >> 1) * sizeof(int16_t));

    // initialize PDM with:
    // - one channel (mono mode)
    // - a 16 kHz sample rate
    if (!PDM.begin(1, EI_CLASSIFIER_FREQUENCY)) {
        ei_printf("Failed to start PDM!");
    }

    PDM.setGain(127);

    record_ready = true;

    return true;
}

/**
 * @brief      Wait on new data
 *
 * @return     True when finished
 */
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 "
            "(EI_CLASSIFIER_SLICES_PER_MODEL_WINDOW)\n");
        ret = false;
    }

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

    inference.buf_ready = 0;

    return ret;
}

/**
 * Get raw audio signal data
 */
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;
}

/**
 * @brief      Stop PDM and release 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
#error "Invalid model for current sensor."
#endif

Edge impulse library

Arduino
This zip contains ML library
No preview (download only).

RockyTracker Repository

Github public repository

Credits

Matea Velichkovska
1 project • 5 followers
mateja ferk
1 project • 5 followers
Taja
1 project • 4 followers
Milan Mandic
1 project • 4 followers
Luka Mali
20 projects • 24 followers
Maker Pro, prototyping enthusiast, head of MakerLab, a lecturer at the University of Ljubljana, founder.

Comments