Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!

From Light to Insight: EdgeML powered by Indoor Solar Cells

Indoor Solar Cells are here to help eliminate the need for constant battery replacements and usher in a new era of self-sustaining IoT.

BeginnerFull instructions provided1 hour1,360
From Light to Insight: EdgeML powered by Indoor Solar Cells

Things used in this project

Hardware components

Seeed XIAO BLE nRF52840 Sense
Seeed Studio Seeed XIAO BLE nRF52840 Sense
×1
Epishine LEH3 Evaluation Kit
×1
Adafruit TSL2561 Luminosity Sensor
×1

Software apps and online services

Arduino IDE
Arduino IDE
Edge Impulse Studio
Edge Impulse Studio

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free
3D Printer (generic)
3D Printer (generic)

Story

Read more

Code

Low Power Arduino Code for Xiao

Arduino
/* Includes ---------------------------------------------------------------- */
#include <LSM6DS3.h>
#include <Wire.h>
#include <Arduino.h>
#include <bluefruit.h>
#include <Adafruit_LittleFS.h>
#include <InternalFileSystem.h>
#include <xiao_test_inferencing.h>

/* Constant defines -------------------------------------------------------- */
#define CONVERT_G_TO_MS2    9.80665f
#define MAX_ACCEPTED_RANGE  2.0f 

/* Private variables ------------------------------------------------------- */
static bool debug_nn = false; // Set this to true to see e.g. features generated from the raw signal
LSM6DS3 myIMU(I2C_MODE, 0x6A);
SoftwareTimer timer;

/* BLE Service ------------------------------------------------------------- */
BLEDfu  bledfu;  // OTA DFU service
BLEDis  bledis;  // device information
BLEUart bleuart; // uart over ble
BLEBas  blebas;  // battery

void initEI() {
  if (!myIMU.begin()) {
    Serial.println("Failed to initialize IMU!");
  }
  if (EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME != 3) {
    ei_printf("ERR: EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME should be equal to 3 (the 3 sensor axes)\n");
    return;
  }
}

float ei_get_sign(float number) {
    return (number >= 0.0) ? 1.0 : -1.0;
}

String label;
float val;
void inference() {
  label = "";
  val = 0;

  // Allocate a buffer here for the values we'll read from the IMU
  float buffer[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE] = { 0 };

  for (size_t ix = 0; ix < EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE; ix += 3) {
    // Determine the next tick (and then sleep later)
    uint64_t next_tick = micros() + (EI_CLASSIFIER_INTERVAL_MS * 1000);

    buffer[ix] = myIMU.readFloatAccelX();
    buffer[ix+1] = myIMU.readFloatAccelY();
    buffer[ix+2] = myIMU.readFloatAccelZ();

    for (int i = 0; i < 3; i++) {
      if (fabs(buffer[ix + i]) > MAX_ACCEPTED_RANGE) {
        buffer[ix + i] = ei_get_sign(buffer[ix + i]) * MAX_ACCEPTED_RANGE;
      }
    }

    buffer[ix + 0] *= CONVERT_G_TO_MS2;
    buffer[ix + 1] *= CONVERT_G_TO_MS2;
    buffer[ix + 2] *= CONVERT_G_TO_MS2;

    delayMicroseconds(next_tick - micros());
  }

  // Turn the raw buffer in a signal which we can the classify
  signal_t signal;
  int err = numpy::signal_from_buffer(buffer, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &signal);
  if (err != 0) {
    //ei_printf("Failed to create signal from buffer (%d)\n", err);
    return;
  }
  
  // Run the classifier
  ei_impulse_result_t result = { 0 };
  err = run_classifier(&signal, &result, debug_nn);
  
  if (err != EI_IMPULSE_OK) {
    //ei_printf("ERR: Failed to run classifier (%d)\n", err);
    return;
  }

  for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
    if (result.classification[ix].value > val) {
      label = result.classification[ix].label;
      val = result.classification[ix].value;
    }
  }
  #if EI_CLASSIFIER_HAS_ANOMALY == 1
      ei_printf("    anomaly score: %.3f\n", result.anomaly);
  #endif
}

void setup()
{
  Serial.begin(115200);
  while (!Serial && millis() < 5000);
  initBLE();
  initEI();
  pinMode(LED_BUILTIN, OUTPUT);
  sd_power_mode_set(NRF_POWER_MODE_LOWPWR);   // Enters low power mode
  timer.begin(1000, timer_callback);          // Configure the timer with 1000 ms interval, with our callback
  timer.start();                              // Start the timer 
}

char buf[128];
void loop()
{
  sd_app_evt_wait();
  delay(1 * 60 * 1000);
  inference();
  label.toCharArray(buf,127);
  bleuart.write( buf, 127 );
}

void timer_callback(TimerHandle_t xTimerID)
{
  digitalToggle(LED_BUILTIN);   // Toggles the LED on and off to signal if it still has power
  sd_app_evt_wait();            // Puts the nrf52 to sleep when there is nothing to do
}

/* Initialize BLE ---------------------------------------------------------- */
void initBLE() {
  // Setup the BLE LED to be enabled on CONNECT
  // Note: This is actually the default behavior, but provided
  // here in case you want to control this LED manually via PIN 19
  Bluefruit.autoConnLed(false);

  // Config the peripheral connection with maximum bandwidth 
  // more SRAM required by SoftDevice
  // Note: All config***() function must be called before begin()
  Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);

  Bluefruit.begin();
  Bluefruit.setTxPower(4);    // Check bluefruit.h for supported values
  //Bluefruit.setName(getMcuUniqueID()); // useful testing with multiple central connections
  Bluefruit.Periph.setConnectCallback(connect_callback);
  Bluefruit.Periph.setDisconnectCallback(disconnect_callback);

  // To be consistent OTA DFU should be added first if it exists
  bledfu.begin();

  // Configure and Start Device Information Service
  bledis.setManufacturer("Zalmotek");
  bledis.setModel("Xiao");
  bledis.begin();

  // Configure and Start BLE Uart Service
  bleuart.begin();

  // Start BLE Battery Service
  blebas.begin();
  blebas.write(100);

  // Set up and start advertising
  startAdv();
}

void startAdv(void)
{
  // Advertising packet
  Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
  Bluefruit.Advertising.addTxPower();

  // Include bleuart 128-bit uuid
  Bluefruit.Advertising.addService(bleuart);

  // Secondary Scan Response packet (optional)
  // Since there is no room for 'Name' in Advertising packet
  Bluefruit.ScanResponse.addName();
  
  /* Start Advertising
   * - Enable auto advertising if disconnected
   * - Interval:  fast mode = 20 ms, slow mode = 152.5 ms
   * - Timeout for fast mode is 30 seconds
   * - Start(timeout) with timeout = 0 will advertise forever (until connected)
   * 
   * For recommended advertising interval
   * https://developer.apple.com/library/content/qa/qa1931/_index.html   
   */
  Bluefruit.Advertising.restartOnDisconnect(true);
  Bluefruit.Advertising.setInterval(32, 244);    // in unit of 0.625 ms
  Bluefruit.Advertising.setFastTimeout(30);      // number of seconds in fast mode
  Bluefruit.Advertising.start(0);                // 0 = Don't stop advertising after n seconds  
}

// callback invoked when central connects
void connect_callback(uint16_t conn_handle)
{
  // Get the reference to current connection
  BLEConnection* connection = Bluefruit.Connection(conn_handle);

  char central_name[32] = { 0 };
  connection->getPeerName(central_name, sizeof(central_name));

  //Serial.print("Connected to ");
  //Serial.println(central_name);
}

// callback invoked when central disconnects
void disconnect_callback(uint16_t conn_handle, uint8_t reason)
{
  (void) conn_handle;
  (void) reason;

  //Serial.println();
  //Serial.print("Disconnected, reason = 0x"); Serial.println(reason, HEX);
}

Credits

Mihnea Stoica

Mihnea Stoica

23 projects β€’ 14 followers
I love me some Prototyping, Product Design, CAD and Leatherworking.
Alexandra Covor

Alexandra Covor

34 projects β€’ 137 followers
πŸͺ„ I'm an embedded engineer who loves creating things.
Constantin Craciun

Constantin Craciun

22 projects β€’ 55 followers
Passionate about hardware development and IoT.
Zalmotek

Zalmotek

26 projects β€’ 48 followers
#totallynotrobots doing prototyping
Andrei Nicula

Andrei Nicula

6 projects β€’ 3 followers

Comments