Skip to content

Instantly share code, notes, and snippets.

@JasonBSteele
Created July 11, 2020 17:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JasonBSteele/024b05a13ea4387a20f6d65ee6f11dfa to your computer and use it in GitHub Desktop.
Save JasonBSteele/024b05a13ea4387a20f6d65ee6f11dfa to your computer and use it in GitHub Desktop.
/* Edge Impulse Arduino examples
* Copyright (c) 2020 EdgeImpulse Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
// If your target is limited in memory remove this macro to save 10K RAM
#define EIDSP_QUANTIZE_FILTERBANK 0
#define MINIMUM_CERTAINTY 0.75
#define BLE_CONNECTING_DELAY 10000
#define INFERRENCING_DELAY 2000
#define REPEAT_NOTIFY_DELAY 600000 //10 Minutes
/* Includes ---------------------------------------------------------------- */
#include <PDM.h> // Microphone
#include <ArduinoBLE.h> // Bluetooth
#include <kitchen-sounds_inference.h> // Trained model
/** Audio buffers, pointers and selectors */
typedef struct {
int16_t *buffer;
uint8_t buf_ready;
uint32_t buf_count;
uint32_t n_samples;
} inference_t;
enum State {
waitingForConnection,
connecting,
inferrencing
};
static const char NOISE[] = "noise";
static char classification[20];
static char prevClassification[sizeof(classification)];
static unsigned long prevMillis;
static unsigned long currentMillis;
static unsigned long prevNotifyMillis;
static State state = waitingForConnection;
static inference_t inference;
static bool record_ready = false;
static signed short sampleBuffer[2048];
static bool debug_nn = false; // Set this to true to see e.g. features generated from the raw signal
BLEDevice bleCentral;
BLEService soundsService("180C"); // User defined service
BLEStringCharacteristic soundCharacteristic("2A56", // standard 16-bit characteristic UUID
BLERead | BLENotify, 20); // remote clients will only be able to read this
/**
* @brief Arduino setup function
*/
void setup()
{
for (size_t ix = 0; ix < 20; ix++) {
ei_printf("waiting... %lu\n", ix);
delay(1000);
}
//Serial.begin(115200);
Serial.begin(9600);
ei_printf("Edge Impulse Inferencing Demo\n");
// summary of inferencing settings (from model_metadata.h)
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]));
if (microphone_inference_start(EI_CLASSIFIER_RAW_SAMPLE_COUNT) == false) {
ei_printf("ERR: Failed to setup audio sampling\r\n");
return;
}
setupBLE();
}
void setupBLE()
{
if (!BLE.begin()) { // initialize BLE
ei_printf("starting BLE failed!");
while (1);
}
BLE.debug(Serial);
BLE.setLocalName("Kitchen Sounds"); // Set name for connection
BLE.setAdvertisedService(soundsService); // Advertise service
soundsService.addCharacteristic(soundCharacteristic); // Add characteristic to service
BLE.addService(soundsService); // Add service
BLE.advertise(); // Start advertising
ei_printf("Peripheral device MAC: ");
Serial.println(BLE.address());
ei_printf("Waiting for connection...\n");
}
/**
* @brief Arduino main function. Runs the inferencing loop.
*/
void loop()
{
currentMillis = millis();
switch(state) {
case waitingForConnection:
bleCentral = BLE.central(); // Wait for a BLE central to connect
// if a central is connected to the peripheral:
if (bleCentral) {
Serial.print("Connected to central MAC: ");
Serial.println(bleCentral.address());
// turn on the LED to indicate the connection
digitalWrite(LED_BUILTIN, HIGH);
// Transition to connecting state
prevMillis = millis();
state = connecting;
ei_printf("Wait 10 secs to finish BLE exchange\n");
}
break;
case connecting:
if(bleCentral && bleCentral.connected()) {
if (currentMillis - prevMillis > BLE_CONNECTING_DELAY) {
// Transition to inferrencing state
prevMillis = currentMillis;
state = inferrencing;
ei_printf("Starting inferencing in 2 seconds...\n");
}
}
else {
disconnected();
}
break;
case inferrencing:
if (bleCentral && bleCentral.connected()) {
if (currentMillis - prevMillis > INFERRENCING_DELAY) {
do_inferencing();
currentMillis = millis();
if (strlen(classification) > 0) {
ei_printf("Classification: %s\n", classification);
// If 10mins since last notify or classification is different
if (!prevNotifyMillis || currentMillis - prevNotifyMillis > REPEAT_NOTIFY_DELAY
|| strcmp(classification, prevClassification) != 0) {
strcpy(prevClassification, classification);
prevNotifyMillis = currentMillis;
ei_printf("BLE Notify classification\n");
soundCharacteristic.setValue(classification);
}
}
prevMillis = currentMillis;
ei_printf("Starting inferencing in 2 seconds...\n");
}
}
else {
disconnected();
}
break;
}
}
void disconnected()
{
// when the central disconnects, turn off the LED:
digitalWrite(LED_BUILTIN, LOW);
ei_printf("Disconnected from central MAC: ");
Serial.println(bleCentral.address());
ei_printf("Waiting for connection...\n");
//Transition to waitingForConnection state
state = waitingForConnection;
}
void do_inferencing()
{
classification[0] = '\0';
ei_printf("Recording...\n");
bool m = microphone_inference_record();
if (!m) {
ei_printf("ERR: Failed to record audio...\n");
return;
}
ei_printf("Recording done\n");
signal_t signal;
signal.total_length = EI_CLASSIFIER_RAW_SAMPLE_COUNT;
signal.get_data = &microphone_audio_signal_get_data;
ei_impulse_result_t result = { 0 };
EI_IMPULSE_ERROR r = run_classifier(&signal, &result, debug_nn);
if (r != EI_IMPULSE_OK) {
ei_printf("ERR: Failed to run classifier (%d)\n", r);
return;
}
// print the predictions
ei_printf("Predictions (DSP: %d ms., Classification: %d ms., Anomaly: %d ms.): \n",
result.timing.dsp, result.timing.classification, result.timing.anomaly);
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
ei_printf(" %s: %.5f\n", result.classification[ix].label, result.classification[ix].value);
if (strcmp(result.classification[ix].label, NOISE) != 0 && result.classification[ix].value >= MINIMUM_CERTAINTY) {
strncpy(classification, result.classification[ix].label, sizeof(classification)-1);
}
}
#if EI_CLASSIFIER_HAS_ANOMALY == 1
ei_printf(" anomaly score: %.3f\n", result.anomaly);
#endif
}
/**
* @brief Printf function uses vsnprintf and output using Arduino Serial
*
* @param[in] format Variable argument list
*/
void ei_printf(const char *format, ...) {
static char print_buf[1024] = { 0 };
va_list args;
va_start(args, format);
int r = vsnprintf(print_buf, sizeof(print_buf), format, args);
va_end(args);
if (r > 0) {
Serial.write(print_buf);
}
}
/**
* @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 || inference.buf_ready == 1) {
for(int i = 0; i < bytesRead>>1; i++) {
inference.buffer[inference.buf_count++] = sampleBuffer[i];
if(inference.buf_count >= inference.n_samples) {
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.buffer = (int16_t *)malloc(n_samples * sizeof(int16_t));
if(inference.buffer == NULL) {
return false;
}
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);
// optionally set the gain, defaults to 20
PDM.setGain(80);
//ei_printf("Sector size: %d nblocks: %d\r\n", ei_nano_fs_get_block_size(), n_sample_blocks);
PDM.setBufferSize(4096);
// 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!");
}
record_ready = true;
return true;
}
/**
* @brief Wait on new data
*
* @return True when finished
*/
static bool microphone_inference_record(void)
{
inference.buf_ready = 0;
inference.buf_count = 0;
while(inference.buf_ready == 0) {
delay(10);
}
return true;
}
/**
* Get raw audio signal data
*/
static int microphone_audio_signal_get_data(size_t offset, size_t length, float *out_ptr)
{
arm_q15_to_float(&inference.buffer[offset], out_ptr, length);
return 0;
}
/**
* @brief Stop PDM and release buffers
*/
static void microphone_inference_end(void)
{
PDM.end();
free(inference.buffer);
}
#if !defined(EI_CLASSIFIER_SENSOR) || EI_CLASSIFIER_SENSOR != EI_CLASSIFIER_SENSOR_MICROPHONE
#error "Invalid model for current sensor."
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment