Mithun Das
Published © GPL3+

Buddy - A personal home office assistance

Buddy collects temperature, humidity, light, human presence data, calculates productivity, helps break sedentary, built on AWS Edukit.

IntermediateFull instructions providedOver 1 day499
Buddy - A personal home office assistance

Things used in this project

Story

Read more

Code

M5Stack.ino

Arduino
#include <M5Core2.h>
#include "FastLED.h"
#include <ArduinoECCX08.h>
#include <Wire.h>
#include "Adafruit_BME680.h"
#include "WiFi.h"
#include <bsec.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>
#include "ClosedCube_TCA9548A.h"
//#include "images.h"
#include <driver/i2s.h>
#include "Grove_Human_Presence_Sensor.h"

#define BLE_INTERVAL_IN_SECONDS 120
#define SPRITE_COLOR_DEPTH 16
#define LIGHT_SENSOR 36

TFT_eSprite spr = TFT_eSprite(&M5.Lcd);

//app icons
extern const unsigned char previewR[120264];
extern const unsigned char logo[];
extern const unsigned char temperature_icon[];
extern const unsigned char humidity_icon[];
extern const unsigned char iaq_icon[];
extern const unsigned char bat_icon[];
extern const unsigned char light_icon[];
extern const unsigned char bell_icon[];

#define CONFIG_I2S_BCK_PIN 12 //定义I2S相关端口
#define CONFIG_I2S_LRCK_PIN 0
#define CONFIG_I2S_DATA_PIN 2
#define CONFIG_I2S_DATA_IN_PIN 34

#define Speak_I2S_NUMBER I2S_NUM_0  //定义扬声器端口

#define MODE_MIC 0  //定义工作模式
#define MODE_SPK 1
#define DATA_SIZE 1024

bool InitI2SSpeakOrMic(int mode){  //Init I2S.  初始化I2S
    esp_err_t err = ESP_OK;

    i2s_driver_uninstall(Speak_I2S_NUMBER); // Uninstall the I2S driver.  卸载I2S驱动
    i2s_config_t i2s_config = {
        .mode = (i2s_mode_t)(I2S_MODE_MASTER),  // Set the I2S operating mode.  设置I2S工作模式
        .sample_rate = 44100, // Set the I2S sampling rate.  设置I2S采样率
        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // Fixed 12-bit stereo MSB.  固定为12位立体声MSB
        .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, // Set the channel format.  设置频道格式
        .communication_format = I2S_COMM_FORMAT_I2S,  // Set the format of the communication.  设置通讯格式
        .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // Set the interrupt flag.  设置中断的标志
        .dma_buf_count = 2, //DMA buffer count.  DMA缓冲区计数
        .dma_buf_len = 128, //DMA buffer length.  DMA缓冲区长度
    };
    if (mode == MODE_MIC){
        i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM);
    }else{
        i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
        i2s_config.use_apll = false;  //I2S clock setup.  I2S时钟设置
        i2s_config.tx_desc_auto_clear = true; // Enables auto-cleanup descriptors for understreams.  开启欠流自动清除描述符
    }
    // Install and drive I2S.  安装并驱动I2S
    err += i2s_driver_install(Speak_I2S_NUMBER, &i2s_config, 0, NULL);

    i2s_pin_config_t tx_pin_config;
    tx_pin_config.bck_io_num = CONFIG_I2S_BCK_PIN;  // Link the BCK to the CONFIG_I2S_BCK_PIN pin. 将BCK链接至CONFIG_I2S_BCK_PIN引脚
    tx_pin_config.ws_io_num = CONFIG_I2S_LRCK_PIN;  //          ...
    tx_pin_config.data_out_num = CONFIG_I2S_DATA_PIN;  //       ...
    tx_pin_config.data_in_num = CONFIG_I2S_DATA_IN_PIN; //      ...
    err += i2s_set_pin(Speak_I2S_NUMBER, &tx_pin_config); // Set the I2S pin number.  设置I2S引脚编号
    err += i2s_set_clk(Speak_I2S_NUMBER, 44100, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO); // Set the clock and bitwidth used by I2S Rx and Tx. 设置I2S RX、Tx使用的时钟和位宽
    return true;
}

void SpeakInit(void){ // 初始化扬声器
  M5.Axp.SetSpkEnable(true);  //启用扬声器电源
  InitI2SSpeakOrMic(MODE_SPK);
}

void DingDong(void){
  size_t bytes_written = 0;
  i2s_write(Speak_I2S_NUMBER, previewR, 120264, &bytes_written, portMAX_DELAY);
}


AK9753 movementSensor;

// need to adjust these sensitivities lower if you want to detect more far
// but will introduce error detection
float sensitivity_presence = 4.0;
float sensitivity_movement = 4.0;
int detect_interval = 30; //milliseconds
PresenceDetector detector(movementSensor, sensitivity_presence, sensitivity_movement, detect_interval);

uint32_t last_time;
uint32_t last_ble_send;
uint8_t last_ble_move = 0;

ClosedCube::Wired::TCA9548A tca9548a;

Bsec iaqSensor;
String output;
String mac;

void checkIaqSensorStatus(void);

int t = 0, h = 0, a = 0, p = 0, s = 0, avg = 0;

#define PaHub_I2C_ADDRESS  0x70
#define BME680_DEFAULT_ADDRESS (0x76)   //for Grove , use 0x77 for Sparkfun
#define BLENAME "M5StackCore2"
#define SERVICE_UUID                  "4D7D1101-BA27-40B2-836C-17505C1044D7"
#define TX_CHAR_UUID                  "4D7D1102-BA27-40B2-836C-17505C1044D7"
#define RX_CHAR_UUID                  "4D7D1103-BA27-40B2-836C-17505C1044D7"

BLEServer* pServer = NULL;
BLECharacteristic* pTxCharacteristic = NULL;
BLECharacteristic* pRxCharacteristic = NULL;
bool bleConnected = false;

WiFiClient client;

#define LEDS_PIN 25
#define LEDS_NUM 10
CRGB ledsBuff[LEDS_NUM];
Adafruit_BME680 bme680; // I2C



void checkIaqSensorStatus(void)
{
  if (iaqSensor.status != BSEC_OK) {
    if (iaqSensor.status < BSEC_OK) {
      output = "BSEC error code : " + String(iaqSensor.status);
      Serial.println(output);
      while (true) {

      }

    } else {
      output = "BSEC warning code : " + String(iaqSensor.status);
      Serial.println(output);
    }
  }

  if (iaqSensor.bme680Status != BME680_OK) {
    if (iaqSensor.bme680Status < BME680_OK) {
      output = "BME680 error code : " + String(iaqSensor.bme680Status);
      Serial.println(output);
      while (true) {

      }
    } else {
      output = "BME680 warning code : " + String(iaqSensor.bme680Status);
      Serial.println(output);
    }
  }
}


class M5StackBLEServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      bleConnected = true;
      setLEDBarColor(0, 0, 225);

    };

    void onDisconnect(BLEServer* pServer) {
      bleConnected = false;
      setLEDBarColor(255, 255, 255);
    }
};

class CharacteristicCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string rxValue = pCharacteristic->getValue();

      if (rxValue.length() > 0) {

        setLEDBarColor(0, 225, 0);
        delay(3000);
        setLEDBarColor(0, 0, 225);
        
        Serial.println("*********");
        Serial.print("Received Value: ");
        for (int i = 0; i < rxValue.length(); i++)
          Serial.print(rxValue[i]);

        Serial.println();
        Serial.println("*********");
      }
    }
};

void setupBLEServer() {
  BLEDevice::init(BLENAME);
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new M5StackBLEServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic
  pTxCharacteristic = pService->createCharacteristic(
                        TX_CHAR_UUID,
                        BLECharacteristic::PROPERTY_READ   |
                        BLECharacteristic::PROPERTY_WRITE  |
                        BLECharacteristic::PROPERTY_NOTIFY
                      );
  pTxCharacteristic->addDescriptor(new BLE2902());

  pTxCharacteristic->setValue("00;00;00;00");

  pRxCharacteristic = pService->createCharacteristic(
                        RX_CHAR_UUID,
                        BLECharacteristic::PROPERTY_READ   |
                        BLECharacteristic::PROPERTY_WRITE  |
                        BLECharacteristic::PROPERTY_NOTIFY
                      );
  pRxCharacteristic->setCallbacks(new CharacteristicCallbacks());

  pService->start();

  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
  pAdvertising->setMinPreferred(0x12);

  //pAdvertising->setMinPreferred(0x0);

  BLEAdvertisementData adv;
  adv.setName(BLENAME);
  pAdvertising->setAdvertisementData(adv);

  BLEAdvertisementData adv2;
  adv2.setCompleteServices(BLEUUID(SERVICE_UUID));
  pAdvertising->setScanResponseData(adv2);

  BLEDevice::startAdvertising();
  mac = BLEDevice::getAddress().toString().c_str();
  

}
void setup() {
  M5.begin();
  pinMode(LIGHT_SENSOR, INPUT);
  Wire.begin();
  tca9548a.address(PaHub_I2C_ADDRESS);

  FastLED.addLeds<SK6812, LEDS_PIN>(ledsBuff, LEDS_NUM);
  setLEDBarColor(255, 255, 255);
  M5.Lcd.setTextColor(RED);
  M5.Lcd.setTextSize(3);
  //display full screen logo
  M5.Lcd.pushImage(0, 0, 320, 240, (uint8_t *)logo);

  tca9548a.selectChannel(0);
  iaqSensor.begin(BME680_I2C_ADDR_PRIMARY, Wire);
  checkIaqSensorStatus();
  bsec_virtual_sensor_t sensorList[10] = {
    BSEC_OUTPUT_RAW_TEMPERATURE,
    BSEC_OUTPUT_RAW_PRESSURE,
    BSEC_OUTPUT_RAW_HUMIDITY,
    BSEC_OUTPUT_RAW_GAS,
    BSEC_OUTPUT_IAQ,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
  };

  iaqSensor.updateSubscription(sensorList, 7, BSEC_SAMPLE_RATE_LP);
  checkIaqSensorStatus();
  delay(5000);
  //Initializing AK9753 sensor
  tca9548a.selectChannel(1);

  if (movementSensor.initialize() == false) {
    Serial.println("Device not found. Check wiring.");
    while (1);
  }

  last_time = millis();
  last_ble_send = millis();
  last_ble_move = 0;

  
  delay(1000);

  setupBLEServer();

  //M5.Lcd.pushImage(0, 0, 50, 50, (uint8_t *)logo16_glcd_bmp);

  SpeakInit();
  DingDong();
  delay(100);

  //delay(10000);

  M5.Lcd.clear(BLACK);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.setTextSize(2);

//setup display screen for once
  M5.Lcd.drawFastVLine(105,0,240,TFT_LIGHTGREY);
  M5.Lcd.drawFastVLine(210,0,240,TFT_LIGHTGREY);
  M5.Lcd.drawFastHLine(0,120,320,TFT_LIGHTGREY);

  M5.Lcd.pushImage(10, 10, 32, 32, (uint8_t *)temperature_icon);
  M5.Lcd.pushImage(125, 10, 32, 32, (uint8_t *)humidity_icon);
  M5.Lcd.pushImage(240, 10, 32, 32, (uint8_t *)iaq_icon);
  M5.Lcd.pushImage(10, 130, 32, 32, (uint8_t *)bat_icon);
  M5.Lcd.pushImage(125, 130, 32, 32, (uint8_t *)light_icon);
  M5.Lcd.pushImage(240, 130, 32, 32, (uint8_t *)bell_icon);
  
  delay(1000);
}

void setLEDBarColor(uint32_t r, uint32_t g, uint32_t b) {
  FastLED.setBrightness(10);
  for (int i = 0; i < LEDS_NUM; i++)
  {
    ledsBuff[i].setRGB(g, r, b);
  }
  FastLED.show();
}


void loop() {
  M5.update();

  // if (M5.BtnA.wasReleased() || M5.BtnA.pressedFor(1000, 200)) {
  //   DingDong();
  // }
    if(M5.Touch.ispressed()) {
      TouchPoint_t coordinate = M5.Touch.getPressPoint();
      Serial.printf("x:%d, y:%d \r\n", coordinate.x, coordinate.y);
      if (coordinate.x > 210 && coordinate.y > 180){
        //bell sprite tapped
        DingDong();
      }
      
    }  

  //M5.Axp.SetLed(1);
  tca9548a.selectChannel(1);
  detector.loop();
  uint32_t now = millis();
  if (now - last_time > 100) {
    

    // M5.Lcd.clear(BLACK);
    // M5.Lcd.setCursor(0, 0);
    // M5.Lcd.setTextSize(3);

    int light = analogRead(LIGHT_SENSOR);
    int lightPct = map(light,0,4095,0,100);
    //M5.Lcd.setTextColor(GREEN);
    //M5.Lcd.printf("MAC: %s\n", mac.c_str());
    //M5.Lcd.printf("LIGHT: %d\n", light);
    float batVol = M5.Axp.GetBatVoltage(); // 4.16
    int bVol = (int)(batVol * 100.0);
    //M5.Lcd.printf("BAT: %d\n", bVol);
    int bPct = 100;
    if(batVol >= 4.100){
        bPct = 100;
    } else if(batVol >= 3.95){
        bPct = 75;
    } else if(batVol >= 3.80){
        bPct = 50;
    } else if(batVol >= 3.25){
        bPct = 25;
    } else{
        bPct = 0;
    }

   uint8_t move = 0;  // default value if not movement detected

    if (detector.presentField1()) {
      //M5.Lcd.setTextColor(GREEN);
      Serial.println("IR1: X");
      move = 1;
    } else {
      // M5.Lcd.setTextColor(RED);
      // M5.Lcd.println("IR1: 0");
    }

    if (detector.presentField2()) {
      // M5.Lcd.setTextColor(GREEN);
      Serial.println("IR2: X");
      move = 1;
    } else {
      // M5.Lcd.setTextColor(RED);
      // M5.Lcd.println("IR2: 0");
    }

    if (detector.presentField3()) {
      // M5.Lcd.setTextColor(GREEN);
      Serial.println("IR3: X");
      move = 1;
    } else {
      // M5.Lcd.setTextColor(RED);
      //Serial.println("IR3: 0");
    }

    if (detector.presentField4()) {
      // M5.Lcd.setTextColor(GREEN);
      Serial.println("IR4: X");
      move = 1;
    } else {
      // M5.Lcd.setTextColor(RED);
      // M5.Lcd.println("IR4: 0");
    }
    
    // uint8_t m = detector.getMovement();  //read movement state will clear it
    // //M5.Lcd.setTextColor(WHITE);
    // //plot a pulse for movement between 1-3
    // if (m & MOVEMENT_FROM_1_TO_3) {
    //   //M5.Lcd.println("1 TO 3");
    //   move = 1; //movement left to right
    //   Serial.println("Movement");
      
    // } else if (m & MOVEMENT_FROM_3_TO_1) {
    //   //M5.Lcd.println("3 TO 1");
    //   move = 2; //movement right to left
    //   Serial.println("Movement");
     
    // } else {
    //   //M5.Lcd.println("NA");
    // }

    // //Serial.print(detector.getDerivativeOfDiff24());
    // //Serial.print(" ");

    // //plot a pulse for movement between 2-4
    // if (m & MOVEMENT_FROM_2_TO_4) {
    //   //M5.Lcd.println("2 TO 4");
    //   move = 3;  // up to down
    //   Serial.println("Movement");
     
    // } else if (m & MOVEMENT_FROM_4_TO_2) {
    //   //M5.Lcd.println("4 TO 2");
    //   move = 4; //down to up
    //   Serial.println("Movement");
      
    // } else {
    //   //M5.Lcd.println("NA");
    // }

    if( move != 0 ){
      Serial.printf("Setting move  to 1 for value %d\n", move);
      last_ble_move = 1;
    }

    tca9548a.selectChannel(0);
    if (iaqSensor.run()) { // If new data is available

      t = (int) iaqSensor.temperature * 1.8 + 32;
      h =  iaqSensor.humidity;
      a = iaqSensor.iaq;
      p = (int) (iaqSensor.pressure / 100);

      
      spr.createSprite(100, 50);
      spr.fillSprite(TFT_BLACK);
      spr.setTextColor(TFT_LIGHTGREY);
      spr.setTextSize(3);
      spr.drawNumber(t, 5, 5);
      spr.drawString("F", 45, 5);
      spr.pushSprite(0, 50);
      spr.deleteSprite();

      //humidity 
      spr.setColorDepth(SPRITE_COLOR_DEPTH);
      spr.createSprite(103, 70);
      
      if(h > 70){
        spr.fillSprite(TFT_RED);
      }else{
        spr.fillSprite(TFT_BLACK);
      }
      
      spr.setTextColor(TFT_LIGHTGREY);
      spr.setTextSize(3);
      spr.drawNumber(h, 15, 15);
      spr.drawString("%", 60, 15);
      spr.pushSprite(106, 50);
      spr.deleteSprite();
      

      spr.createSprite(80, 50);
      spr.fillSprite(TFT_BLACK);
      spr.setTextColor(TFT_LIGHTGREY);
      spr.setTextSize(3);
      spr.drawNumber(a, 5, 5);
      //spr.drawString("%", 45, 5);
      spr.pushSprite(230, 50);
      spr.deleteSprite();

      spr.createSprite(80, 50);
      spr.fillSprite(TFT_BLACK);
      spr.setTextColor(TFT_LIGHTGREY);
      spr.setTextSize(3);
      spr.drawNumber(bPct, 5, 5);
      spr.drawString("%", 65, 5);
      spr.pushSprite(0, 170);
      spr.deleteSprite();


      //light 
      spr.createSprite(103, 70);
      
      if(lightPct < 50){
        spr.fillSprite(TFT_RED);
      }else{
        spr.fillSprite(TFT_BLACK);
      }
      
      spr.setTextColor(TFT_LIGHTGREY);
      spr.setTextSize(3);
      spr.drawNumber(lightPct, 15, 15);
      spr.drawString("%", 60, 15);
      spr.pushSprite(106, 170);
      spr.deleteSprite();

      //bell 
      spr.createSprite(103, 70);
      
      if(light > 70){
        spr.fillSprite(TFT_GREEN);
        spr.setTextColor(TFT_BLACK);
      }else{
        spr.fillSprite(TFT_BLACK);
        spr.setTextColor(TFT_LIGHTGREY);
      }
      
      
      spr.setTextSize(5);
      spr.drawNumber(0, 30, 15);
      spr.pushSprite(211, 170);
      spr.deleteSprite();


      delay(1000);


      if ( bleConnected && (now -last_ble_send > BLE_INTERVAL_IN_SECONDS  * 1000 )) {
        char buf[72];
        sprintf(buf, "%s;%d;%d;%d;%d;%d;%d", mac.c_str(), t, h, a, lightPct,last_ble_move,bPct);
        Serial.println(String(buf).c_str());
        pTxCharacteristic->setValue(String(buf).c_str());

        pTxCharacteristic->notify();
        last_ble_send = now;
        last_ble_move = 0;
      }


    } else {
      checkIaqSensorStatus();
    }

    last_time = now;
  }

  delay(1);
  



}

Code

Credits

Mithun Das

Mithun Das

23 projects • 106 followers
Hacker and Maker driven by passion. Ambassador at Edge Impulse and Balena. Follow me on Twitter @tweetmithund

Comments