今天小編給大家?guī)淼氖莵碜訫IT的Jason的基于XIAO ESP32C6的HA空氣質(zhì)量檢測儀項目,作為一名程序員,Jason經(jīng)常自己連續(xù)幾個小時坐在辦公桌前,沉浸在一行行代碼中。后來意識到周圍的空氣質(zhì)量,尤其是二氧化碳水平不斷上升。
制作背景
保持健康的工作空間至關(guān)重要,但我們需要一個既實用又美觀的解決方案。如果有一種緊湊型設(shè)備,不僅可以監(jiān)測空氣質(zhì)量,還可以作為美觀的辦公桌裝飾品,那不是很棒嗎?有了這個想法,Jason就開始著手實現(xiàn)它。Zigbee 是一種出色的智能家居低功耗通信協(xié)議。
使用 Seeed Studio 的 XIAO ESP32 C6 模塊作為主控。它擁有小巧的外形和全面的 Arduino Zigbee 教程為開發(fā)者節(jié)省了大量開發(fā)時間。此外,還增加了 XIAO 擴展板和 Grove VOC 和 eCO2 氣體傳感器 (SGP30) 以獲得準確的讀數(shù)。
由于不喜歡傳統(tǒng)的方形桌面擺件,所以Jason設(shè)計了一個小型站立機器人造型。至于細節(jié),對 XIAO 擴展板的引腳排列進行了布線,以便在 XIAO 系列內(nèi)的不同 MCU 之間輕松切換。這讓Jason在組件選擇上有了更大的靈活性。在結(jié)構(gòu)件內(nèi),我將傳感器放在右側(cè),用打印材料與MCU 隔開,確保最佳性能和時尚的設(shè)計。
材料清單
硬件列表
Grove-VOC and CO2 Gas Sensor SGP30
Seeed Studio XIAO ESP32C6Seeed Studio
XIAO Expansion Board
軟件列表
Arduino IDE
Autodesk Fusion
Home Assistant
項目演示
1.連接帶 OLED 顯示屏的設(shè)備
在 OLED 屏幕上,我們將顯示連接狀態(tài),可以輕松查看 Zigbee 與 Home Assistant 的連接是否成功。此外,UI 設(shè)計中還會有一些小驚喜!
OLED 顯示屏將包含三個內(nèi)容區(qū)域:
啟動 Zigbee 連接
連接成功狀態(tài)
CO2 和 eVOC 數(shù)據(jù)
我們還可以通過打開 Arduino 串行監(jiān)視器來監(jiān)視 XIAO ESP32 C6 的 Zigbee 連接狀態(tài)和數(shù)據(jù)輸出。
Arduino 串行監(jiān)視器
2.HomeAssistant 帶二氧化碳傳感器界面效果截圖
將購買的 Home Assistant Connect ZBT-1 插入我的 HA 設(shè)置后,我通過 Zigbee Home Automation 添加了我們的 Zigbee 終端設(shè)備。隨后,在對 XIAO ESP32 C6 進行編程后,設(shè)備名稱出現(xiàn)在 OLED 顯示屏上。
HomeAssistant 查找 Zigbee 集成
HomeAssistant 連接 Zigbee 設(shè)備
訪問 Homeassistant 后,我們可以看到顯示的數(shù)據(jù)隨時間的變化
成功連接到 HomeAssistant 后,我們在 Zigbee 中找到了我們的設(shè)備,它提供了兩個主要功能:
實時數(shù)據(jù)顯示
查看歷史二氧化碳趨勢。值得注意的是,我的設(shè)備在此期間并未持續(xù)運行。
3.桌面上的最終設(shè)置
最后,我們可以看到 OLED 屏幕上顯示的傳感器數(shù)據(jù),以及 Home Assistant 儀表板上的數(shù)據(jù)。
最終展示效果
#ifndef ZIGBEE_MODE_ED #error "Zigbee end device mode is not selected in Tools->Zigbee mode" #endif #include "Zigbee.h" #include "sensirion_common.h" #include "sgp30.h" #include#include U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE); uint8_t HugoUI_Animation_EasyOut(float *a, float *a_trg, uint16_t n) { if (*a == *a_trg) return 0; float cz = fabs(*a - *a_trg); if (cz <= 1) *a = *a_trg; else { if (cz < 10) n = n * cz * 0.1f; if (n < 10) n = 10; *a += (*a_trg - *a) / (n * 0.1f); } return 1; } uint8_t HugoUI_Animation_EasyIn(float *a, float *a_trg, uint16_t n) { if (*a == *a_trg) return 0; float cz = fabs(*a - *a_trg); if (cz <= 1) *a = *a_trg; else if (cz > 20) n = n * 3; else if (cz > 15) n = n * 2; else if (cz > 5) n = n * 1; if (*a != *a_trg) *a += (*a_trg - *a) / (n * 0.1f); else return 0; return 1; } void Oled_DrawSlowBitmapResize(int x, int y, const uint8_t *bitmap, int w1, int h1, int w2, int h2) { uint8_t color = u8g2.getDrawColor(); float mw = (float)w2 / w1; float mh = (float)h2 / h1; uint8_t cmw = ceil(mw); uint8_t cmh = ceil(mh); int xi, yi, byteWidth = (w1 + 7) / 8; for (yi = 0; yi < h1; yi++) { for (xi = 0; xi < w1; xi++) { if (*(uint8_t *)(bitmap + yi * byteWidth + xi / 8) & (1 << (xi & 7))) { u8g2.drawBox(x + xi * mw, y + yi * mh, cmw, cmh); } else if (color != 2) { u8g2.setDrawColor(0); u8g2.drawBox(x + xi * mw, y + yi * mh, cmw, cmh); u8g2.setDrawColor(color); } } } } const unsigned char gImage_humidity[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xF0, 0x07, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0x7C, 0x3F, 0x00, 0x00, 0x3E, 0x7C, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x80, 0x07, 0xF0, 0x01, 0xC0, 0x07, 0xE0, 0x01, 0xC0, 0x03, 0xC0, 0x03, 0xE0, 0x81, 0x81, 0x07, 0xE0, 0xC1, 0x83, 0x07, 0xE0, 0xD0, 0x03, 0x07, 0xF0, 0xF8, 0x03, 0x0F, 0xF0, 0xF8, 0x0B, 0x0F, 0xF0, 0xF0, 0x1F, 0x0F, 0xF0, 0xE0, 0x1F, 0x0F, 0xE0, 0xC0, 0x0F, 0x07, 0xE0, 0xC0, 0x07, 0x07, 0xE0, 0xC1, 0x83, 0x07, 0xC0, 0xC3, 0xC3, 0x03, 0xC0, 0xC7, 0xE3, 0x03, 0x80, 0x8F, 0xF3, 0x01, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFE, 0x7F, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }; const unsigned char gImage_homeassistant[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0xFC, 0xBF, 0x07, 0x00, 0xFE, 0xFF, 0x07, 0x00, 0xFF, 0xFF, 0x07, 0x80, 0xFF, 0xFF, 0x07, 0xC0, 0xFF, 0xFF, 0x07, 0xE0, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0x1F, 0xFC, 0xFF, 0xFF, 0x3F, 0xFE, 0xF8, 0x1F, 0x7F, 0x7F, 0xF7, 0xEF, 0xFE, 0x7F, 0xF7, 0xEF, 0xFE, 0xF8, 0xFF, 0xFF, 0x1F, 0xF8, 0xFF, 0xFF, 0x1F, 0xF8, 0xEF, 0xF7, 0x1F, 0xF8, 0xCF, 0xF3, 0x1F, 0xF8, 0x9F, 0xF9, 0x1F, 0xF8, 0x7F, 0xFE, 0x1F, 0xF8, 0xFF, 0xFF, 0x1F, 0xF8, 0xFF, 0xFF, 0x1F, 0xF8, 0xFF, 0xFF, 0x1F, 0xF8, 0xFF, 0xFF, 0x1F, 0xF8, 0xFF, 0xFF, 0x1F, 0xF8, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // 32x32 #define CARBON_DIOXIDE_SENSOR_ENDPOINT_NUMBER 10 uint8_t button = BOOT_PIN; ZigbeeCarbonDioxideSensor zbCarbonDioxideSensor = ZigbeeCarbonDioxideSensor(CARBON_DIOXIDE_SENSOR_ENDPOINT_NUMBER); int16_t err = 0; uint16_t tvoc_ppb, co2_eq_ppm; uint16_t carbon_dioxide_value; static uint32_t timeCounter = 0; static float img_a = 4, img_a_trg = 24; static float img_b = -2, img_b_trg = 24; static float img_c = -10, img_c_trg = 13; static float img_d = 5, img_d_trg = 90; static void carbon_sensor_update(void *arg) { for (;;) { if (!(timeCounter++ % 20)) { err = sgp_measure_iaq_blocking_read(&tvoc_ppb, &co2_eq_ppm); if (err == STATUS_OK) { Serial.printf("tVOC Concentration: %d ppb ", tvoc_ppb); Serial.printf("CO2eq Concentration: %d ppm ", co2_eq_ppm); carbon_dioxide_value = co2_eq_ppm; zbCarbonDioxideSensor.setCarbonDioxide(carbon_dioxide_value); } else { Serial.println("Error reading IAQ values "); } zbCarbonDioxideSensor.report(); delay(6000); } } } void setup() { int16_t err; uint16_t scaled_ethanol_signal, scaled_h2_signal; Serial.begin(115200); u8g2.begin(); // Init RF pinMode(WIFI_ENABLE, OUTPUT); digitalWrite(WIFI_ENABLE, LOW); delay(100); pinMode(WIFI_ANT_CONFIG, OUTPUT); digitalWrite(WIFI_ANT_CONFIG, LOW); // Init button switch pinMode(button, INPUT_PULLUP); // Init SGP30 while (sgp_probe() != STATUS_OK) { Serial.println("SGP failed"); while (1) ; } err = sgp_measure_signals_blocking_read(&scaled_ethanol_signal, &scaled_h2_signal); if (err == STATUS_OK) { Serial.println("get ram signal!"); } else { Serial.println("error reading signals"); } err = sgp_iaq_init(); zbCarbonDioxideSensor.setManufacturerAndModel("Espressif", "ZigbeeCarbonDioxideSensor"); zbCarbonDioxideSensor.setMinMaxValue(0, 1500); Zigbee.addEndpoint(&zbCarbonDioxideSensor); Serial.println("Starting Zigbee..."); u8g2.clearBuffer(); u8g2.setFont(u8g2_font_ncenB08_tr); u8g2.drawStr(0, 30, "Connecting to Zigbee..."); u8g2.sendBuffer(); if (!Zigbee.begin()) { Serial.println("Zigbee failed to start!"); Serial.println("Rebooting..."); ESP.restart(); } else { Serial.println("Zigbee started successfully!"); } Serial.println("Connecting to network"); while (!Zigbee.connected()) { Serial.print("."); delay(100); } u8g2.clearBuffer(); u8g2.drawStr(0, 30, "Successfully connect"); u8g2.drawStr(0, 50, "Zigbee network!"); u8g2.sendBuffer(); Serial.println(); delay(5000); // Start carbon sensor reading task xTaskCreate(carbon_sensor_update, "carbon_sensor_update", 2048, NULL, 10, NULL); zbCarbonDioxideSensor.setReporting(0, 30, 0); } void loop() { u8g2.clearBuffer(); u8g2.setFont(u8g2_font_ncenB08_tr); u8g2.drawXBM(0, 0, 32, 32, gImage_homeassistant); u8g2.drawStr(43, img_b, " Air Monitor"); u8g2.setDrawColor(2); u8g2.drawRBox(36, img_c, img_d, 15, 1); u8g2.setDrawColor(1); u8g2.drawStr(0, 45, "CO2: "); u8g2.setCursor(30, 45); u8g2.print(carbon_dioxide_value); u8g2.drawStr(55, 45, "ppb"); u8g2.drawStr(0, 60, "TVOC: "); u8g2.setCursor(38, 60); u8g2.print(tvoc_ppb); u8g2.drawStr(55, 60, "ppm"); if (img_a == img_a_trg) { if (img_a == 4) { img_a_trg = 24; } else if (img_a == 24) img_a_trg = 4; } HugoUI_Animation_EasyOut(&img_b, &img_b_trg, 100); HugoUI_Animation_EasyIn(&img_a, &img_a_trg, 115); HugoUI_Animation_EasyOut(&img_c, &img_c_trg, 100); HugoUI_Animation_EasyOut(&img_d, &img_d_trg, 100); Oled_DrawSlowBitmapResize(118 - img_a / 2, 50 - img_a / 4, gImage_humidity, 32, 32, img_a, img_a); u8g2.sendBuffer(); if (digitalRead(button) == LOW) { delay(100); int startTime = millis(); while (digitalRead(button) == LOW) { delay(50); if ((millis() - startTime) > 3000) { Serial.println("Resetting Zigbee to factory and rebooting in 1s."); delay(1000); Zigbee.factoryReset(); } } } }
改進計劃
得益于 Seeed Studio XIAO 擴展板提供的眾多 Grove 接口,Jason計劃將其他傳感器集成到這個機器人中來收集更多數(shù)據(jù)。這將可以通過自動化設(shè)備來控制其他的智能家居設(shè)備,例如當空氣太干燥時啟動除濕機,或者當二氧化碳水平上升時運行空氣循環(huán)裝置。總而言之,Jason說這個智能二氧化碳監(jiān)測器項目是一次受益匪淺的創(chuàng)新和學習之旅。通過將技術(shù)與設(shè)計相結(jié)合,他創(chuàng)造了一種不僅可以跟蹤空氣質(zhì)量還可以增強工作空間美感的設(shè)備。接下來他會逐步利用 Zigbee 連接不同的傳感器,打造智能家居生態(tài)系統(tǒng)。
-
傳感器
+關(guān)注
關(guān)注
2557文章
51812瀏覽量
759416 -
mcu
+關(guān)注
關(guān)注
146文章
17521瀏覽量
355234 -
機器人
+關(guān)注
關(guān)注
212文章
28990瀏覽量
209920 -
空氣質(zhì)量檢測儀
+關(guān)注
關(guān)注
0文章
11瀏覽量
1271
原文標題:創(chuàng)客項目秀|基于XIAO ESP32C6的HA空氣質(zhì)量檢測儀
文章出處:【微信號:ChaiHuoMakerSpace,微信公眾號:柴火創(chuàng)客空間】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
【GoKit申請】空氣質(zhì)量檢測儀
Pm2.5空氣質(zhì)量檢測的小問題
空氣質(zhì)量測試儀的原理
什么是壓縮空氣質(zhì)量檢測儀?
基于STM32空氣質(zhì)量檢測儀原理圖
德爾格壓縮空氣質(zhì)量檢測儀6種型號的詳細介紹
空氣質(zhì)量檢測儀的簡單介紹
空氣質(zhì)量檢測儀的特點介紹
網(wǎng)格化空氣質(zhì)量監(jiān)測站的特點
空氣質(zhì)量檢測儀使用方法是怎樣的
空氣質(zhì)量檢測儀優(yōu)勢特點介紹
空氣質(zhì)量檢測儀原理與應用介紹
ONETEST-100AQL空氣質(zhì)量檢測儀工作原理介紹

評論