iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 24
0
IoT

制霸IoT 30Day!系列 第 24

Day 24 實際案例 空氣盒子專案(三)

空氣盒子說明

今天來介紹這個空氣盒子專案,細部運作原理。

程式碼介紹

我們這理用到很多模組是 UART 通訊介面,但是實際硬體上只有一個 UART 介面。
那我們這邊使用SoftwareSerial來克服這個問題。
那我們先來逐一介紹程式邏輯。

#include <FS.h>
#include <SPI.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>

//needed for library
#include <DNSServer.h>
#include <ESPAsyncWebServer.h>
#include <ESPAsyncWiFiManager.h>         //https://github.com/alanswx/ESPAsyncWiFiManager
AsyncWebServer server(80);
DNSServer dns;
AsyncWiFiManager wifiManager(&server,&dns);

#include <WebSocketsClient.h>
#include <Hash.h>

那這邊可以看到使用到的是WiFiManager to ESP Async Server
WiFiManager

WiFiManager 方便的介面程式庫。

WiFiManager 是一個方便的 WiFi 管理介面,並且可以儲存額外資訊在硬體內。
程式碼再來是一些網路協定的程式庫。

程式碼上半部硬體部分

這邊就是 PMS5003 空氣偵測 是用 SoftwareSerial,程式庫且只用到 RX 接收的功能使用 D4 接腳。

#include <SoftwareSerial.h>
//RX ,TX,false,buffer
//D4=2  PMS5003
SoftwareSerial swSer(2, SW_SERIAL_UNUSED_PIN, false, 64);

這邊是藍牙部分也是用 SoftwareSerial,程式庫且使用 RX => D7 TX=> D8 接腳。


//RX ,TX,false,buffer
//D7=13,D8=15 BLE
SoftwareSerial ble(13, 15, false, 128);
int BLE_stat_PIN=10;

這邊是 HMI 顯示模組部分也是用 SoftwareSerial,程式庫且使用 RX => D6 TX=> D5 接腳。

//RX ,TX,false,buffer
//D6=12,D5=14 HMI
SoftwareSerial swSer3(12, 14, false, 128);
#include <Nextion.h>
Nextion myNextion(swSer3, 9600);

這邊是 HTU21D 溫絲度部分。

int Ghostflag=1;

float dht_h = 0;
float dht_t = 0;
float dht_f = 0;
float temperature[10];
float humidity[10];

#include <Wire.h>
#include "SparkFunHTU21D.h"
HTU21D myHumidity;

這邊就是一些基本變數與設定值 的定義部分。

#define LENG 31   //0x42 + 31 bytes equal to 32 bytes
unsigned char buf[LENG];

int cfPM1_0Value=0;
int cfPM2_5Value=0;
int cfPM10Value=0;
int atPM1_0Value=0;
int atPM2_5Value=0;
int atPM10Value=0;

int PN03Value=0; //0.1升空氣中直徑在 0.3um 的顆粒物個數
int PN05Value=0; //0.1升空氣中直徑在 0.5um 的顆粒物個數
int PN10Value=0; //0.1升空氣中直徑在 1.0um 的顆粒物個數
int PN25Value=0; //0.1升空氣中直徑在 2.5um 的顆粒物個數
int PN50Value=0; //0.1升空氣中直徑在 5.0um 的顆粒物個數
int PN100Value=0; //0.1升空氣中直徑在 10um 的顆粒物個數
int pm1_0[10];
int pm2_5[10];
int pm10[10];
int pm_index=0;


#define SWITCH_PIN 0//switch
char password[32];
char domain[30] = "zzzzzz";
char productId[17] = "zzzzzzzz";
WebSocketsClient webSocket;
bool wsAuthCheck = false;
bool wsAuthStatus = false;
char rid[41];
#define WS_NONE 9999
#define WS_GET_RID 100
int wsProcess = WS_NONE;
char SendData[384];
unsigned long error;

const int SENSOR_INTERVAL = 1000;
const int BLE_INTERVAL = 2000;
long unsigned int prevBleTime = 0;
long unsigned int prevSensorTime = 0;

//flag for saving data
bool shouldSaveConfig = false;

//callback notifying us of the need to save config
void saveConfigCallback () {
  Serial.println("Should save config");
  shouldSaveConfig = true;
}

起始設定區塊,包含各個硬體的起始與 wifiManager 區塊。

void setup()
{
  Serial.begin(115200);
  swSer.begin(9600);
  myNextion.init();
  pinMode(BLE_stat_PIN, INPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(SWITCH_PIN, INPUT);

  myHumidity.begin();
  BLESetup();
  FSread();
  Serial.println("\nStart Up: PAQman");
  wificonnect(60);
  prevSensorTime = millis();
  webSocket.begin(String(domain), 80, "/ws");
  webSocket.onEvent(webSocketEvent);
}


void FSread()
{
  //clean FS, for testing
//  SPIFFS.format();delay(100);

  //read configuration from FS json
  Serial.println("mounting FS...");

  if (SPIFFS.begin()) {
    Serial.println("mounted file system");
    if (SPIFFS.exists("/config.json")) {
      //file exists, reading and loading
      Serial.println("reading config file");
      File configFile = SPIFFS.open("/config.json", "r");
      if (configFile) {
        Serial.println("opened config file");
        size_t size = configFile.size();
        // Allocate a buffer to store contents of the file.
        std::unique_ptr<char[]> buf(new char[size]);

        configFile.readBytes(buf.get(), size);
        DynamicJsonBuffer jsonBuffer;
        JsonObject& json = jsonBuffer.parseObject(buf.get());
        json.printTo(Serial);
        if (json.success()) {
          Serial.println("\nparsed json");
          strcpy(cik, json["cik"]);
          strcpy(domain, json["domain"]);
          strcpy(productId, json["productId"]);
        } else {
          Serial.println("failed to load json config");
        }
      }
    }
  } else {
    Serial.println("failed to mount FS");
  }
}

void saveConfig()
{
  Serial.println("saving config");
  DynamicJsonBuffer jsonBuffer;
  JsonObject& json = jsonBuffer.createObject();
  json["cik"] = cik;
  json["domain"] = domain;
  json["productId"] = productId;

  File configFile = SPIFFS.open("/config.json", "w");
  if (!configFile) {
    Serial.println("failed to open config file for writing");
  }

  json.printTo(Serial);
  json.printTo(configFile);
  configFile.close();
  Serial.println("");
}

void wificonnect(int timeOut)
{
  AsyncWiFiManagerParameter custom_cik("cik", "cik", cik, 41);
  AsyncWiFiManagerParameter custom_product_id("productId", "productId", productId, 17);
  AsyncWiFiManagerParameter custom_domain("domain", "domain", domain, 31);

  //set config save notify callback
  wifiManager.setSaveConfigCallback(saveConfigCallback);

  //add all your parameters here
  wifiManager.addParameter(&custom_cik);
  wifiManager.addParameter(&custom_product_id);
  wifiManager.addParameter(&custom_domain);

  wifiManager.setTimeout(timeOut);

  String ssid = "PAQ-MAN" + String(ESP.getChipId());
  Serial.println("Please connected to ssid:" + ssid);
  if (!wifiManager.autoConnect(ssid.c_str(), "88888888")) {
    Serial.println("failed to connect and hit timeout");
    delay(3000);
    //reset and try again, or maybe put it to deep sleep
//    ESP.reset();
//    delay(5000);
  } else {
    Serial.println("connected...yeey :)");
    Serial.print("local ip:");
    Serial.println(WiFi.localIP());
    strcpy(cik, custom_cik.getValue());
    strcpy(productId, custom_product_id.getValue());
    strcpy(domain, custom_domain.getValue());

    //save the custom parameters to FS
    if (shouldSaveConfig) {
      saveConfig();
    }
  }
}

這部分就是藍芽起始設定部分,這部分會主要是在開機時先定義藍芽裝置名稱。

void BLESetup()
{
  ble.begin(9600);
  if (digitalRead(BLE_stat_PIN) == 0) {
    Serial.println("BLE Setup");
    delay(200);
    String BLEname = "PAQ-MAN" + String(ESP.getChipId());
    ble.println("AT+NAME");
    delay(200);
    if (ble.available()) {
      String inData = ble.readStringUntil('\n');
      Serial.print(inData);
      if (inData != String("+NAME=" + BLEname)) {
        ble.println("AT+NAME" + BLEname );
        delay(200);
        if (ble.available()) {
          inData = ble.readStringUntil('\n');
          Serial.print(inData);
        }
      }
    }
  }
}

這邊定義一個供輸入的按鈕偵測,如果有按下就會重置 Wifi。


//=================
void CheckWifiSwitch(void)
//=================
{
    if(!digitalRead(SWITCH_PIN))
    {
      int i=0,j=0;
      for(i=0;i<30;i++)
      {
        delay(100);
        if(!digitalRead(SWITCH_PIN))
          j++;
        else
          i=30;
      }
      if(j>25)
      {
        Serial.println("RESETING WiFi SSID/PASSWORD/DEVICE NAME");
        digitalWrite(LED_BUILTIN, LOW);
        wifiManager.resetSettings();
        wificonnect(120);
      }
    }
}

這部分是 讀取溫濕度感應器與空氣品質數值,並且更新資訊到 HMI 去做顯示。

void getSensorData()
{
  dht_h = myHumidity.readHumidity();
  // Read temperature as Celsius (the default)
  dht_t = myHumidity.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
//  dht_f = myHumidity.readTemperature(true);
  if (!isnan(dht_h) && !isnan(dht_t) && !isnan(dht_f)) {
    myNextion.setComponentText("temperature",String(dht_t));
    myNextion.setComponentText("humidity",String(dht_h));
  }
  if(swSer.find(0x42)){    //start to read when detect 0x42
    swSer.readBytes(buf,LENG);

    if(buf[0] == 0x4d){
      if(checkValue(buf,LENG)){
        cfPM1_0Value=transmitData(buf,3,4);//標準顆粒物PM01
        cfPM2_5Value=transmitData(buf,5,6);//標準顆粒物PM2.5
        cfPM10Value=transmitData(buf,7,8);//標準顆粒物PM10
        atPM1_0Value=transmitData(buf,9,10);//大氣環境下PM01
        atPM2_5Value=transmitData(buf,11,12);//大氣環境下PM2.5
        atPM10Value=transmitData(buf,13,14);//大氣環境下PM10
        PN03Value=transmitData(buf,15,16);//0.1升空氣中直徑在 0.3um 的顆粒物個數
        PN05Value=transmitData(buf,17,18);//0.1升空氣中直徑在 0.5um 的顆粒物個數
        PN10Value=transmitData(buf,19,20);//0.1升空氣中直徑在 1.0um 的顆粒物個數
        PN25Value=transmitData(buf,21,22);//0.1升空氣中直徑在 2.5um 的顆粒物個數
        PN50Value=transmitData(buf,23,24);//0.1升空氣中直徑在 5.0um 的顆粒物個數
        PN100Value=transmitData(buf,25,26);//0.1升空氣中直徑在 10um 的顆粒物個數
      }
    }
    myNextion.setComponentValue("pm1_0",cfPM1_0Value);
    myNextion.setComponentValue("pm2_5",cfPM2_5Value);
    myNextion.setComponentValue("pm10",cfPM10Value);
    pm1_0[pm_index]=cfPM1_0Value;
    pm2_5[pm_index]=cfPM2_5Value;
    pm10[pm_index]=cfPM10Value;
    if (!isnan(dht_h) && !isnan(dht_t) && !isnan(dht_f)) {
      temperature[pm_index] = dht_t;
      humidity[pm_index] = dht_h;
    } else {
      temperature[pm_index] = 0;
      humidity[pm_index] = 0;
    }
    pm_index++;
  }
}

這部分就是將收集到的全部資訊,以 JSON 格式送到藍芽。


void sendToBLE()
{
  DynamicJsonBuffer jsonBuffer;
  JsonObject& root = jsonBuffer.createObject();
  root["pm1_0"] = pm1_0[pm_index];
  root["pm2_5"] = pm2_5[pm_index];
  root["pm10"] = pm10[pm_index];
  root["temperature"] = dht_t;
  root["humidity"] = dht_h;
  root["PN03Value"] = PN03Value;
  root["PN05Value"] = PN05Value;
  root["PN10Value"] = PN10Value;
  root["PN25Value"] = PN25Value;
  root["PN50Value"] = PN50Value;
  root["PN100Value"] = PN100Value;
  char dataBuffer[256];
  root.printTo(dataBuffer, sizeof(dataBuffer));
  Serial.println(dataBuffer);
  ble.println(dataBuffer);
}

這部分是將收到的全部資訊以 websocket 送到公司平台,可以將這部分改成 MQTT 送到 LASS。


void getDataJsonString() {
  DynamicJsonBuffer jsonBuffer;
  JsonObject& root = jsonBuffer.createObject();
  JsonArray& data1_0 = root.createNestedArray("pm1_0");
  JsonArray& data2_5 = root.createNestedArray("pm2_5");
  JsonArray& data10 = root.createNestedArray("pm10");
  JsonArray& dataTemperature = root.createNestedArray("temperature");
  JsonArray& dataHumidity = root.createNestedArray("humidity");
  for ( int j = 0; j < 10; ++j ){
      data1_0.add(pm1_0[j]);
      data2_5.add(pm2_5[j]);
      data10.add(pm10[j]);
      dataTemperature.add(temperature[j]);
      dataHumidity.add(humidity[j]);
  }
  char dataBuffer[256];
  root.printTo(dataBuffer, sizeof(dataBuffer));
  Serial.println(dataBuffer);
  pm_index=0;

  DynamicJsonBuffer jsonBuffer2;
  JsonObject& root2 = jsonBuffer2.createObject();
  JsonArray& calls = root2.createNestedArray("calls");
  JsonObject& call = calls.createNestedObject();
  call["id"] = 999;
  call["procedure"] = "write";
  JsonArray& arguments = call.createNestedArray("arguments");
  JsonObject& alias = arguments.createNestedObject();
  alias["alias"] = "raw_data";
  arguments.add(dataBuffer);
  root2.printTo(SendData, sizeof(SendData));
  if (!digitalRead(BLE_stat_PIN)) {
    Serial.println(SendData);
    webSocket.sendTXT(SendData);
  }
}

這就是下面主要邏輯運行的部分。



void loop() {
  CheckWifiSwitch();
  if (prevSensorTime + SENSOR_INTERVAL < millis() || millis() < prevSensorTime){
    digitalWrite(LED_BUILTIN, LOW);
    getSensorData();
    if (pm_index >= 10) {
      getDataJsonString();
    }
    if (Ghostflag == 1)
    {
      myNextion.sendCommand(String("p1.pic=2").c_str());
      Ghostflag = 2;
    } else {
      myNextion.sendCommand(String("p1.pic=1").c_str());
      Ghostflag = 1;
    }
    prevSensorTime = millis();
  }
  digitalWrite(LED_BUILTIN, HIGH);
  //====================================
  if (prevBleTime + BLE_INTERVAL < millis() || millis() < prevBleTime){
    if (digitalRead(BLE_stat_PIN)) {
      sendToBLE();
    }
    prevBleTime = millis();
  }
  //====================================
  if (!digitalRead(BLE_stat_PIN)) {
    if (WiFi.status() == WL_CONNECTED) {
      webSocket.loop();
    }
  }
  delay(100);
}

char checkValue(unsigned char *thebuf, char leng)
{
  char receiveflag=0;
  int receiveSum=0;

  for(int i=0; i<(leng-2); i++){
  receiveSum=receiveSum+thebuf[i];
  }
  receiveSum=receiveSum + 0x42;

  if(receiveSum == ((thebuf[leng-2]<<8)+thebuf[leng-1]))  //check the serial data
  {
    receiveSum = 0;
    receiveflag = 1;
  }
  return receiveflag;
}
int transmitData(unsigned char *thebuf,int HighB,int LowB)
{
  int result;
  result=((thebuf[HighB]<<8) + thebuf[LowB]); //count PM1.0 value of the air detector module
  return result;
}

結語

今天介紹空氣盒子專案實程式碼部分說明與講解。

Blog 同步刊登


上一篇
Day 23 實際案例 空氣盒子專案(二)
下一篇
Day 25 資料應用
系列文
制霸IoT 30Day!30

尚未有邦友留言

立即登入留言