今天小編給大家?guī)淼氖莵碜訫IT的Jason的基于XIAO ESP32C6的HA空氣質(zhì)量檢測(cè)儀項(xiàng)目,作為一名程序員,Jason經(jīng)常自己連續(xù)幾個(gè)小時(shí)坐在辦公桌前,沉浸在一行行代碼中。后來意識(shí)到周圍的空氣質(zhì)量,尤其是二氧化碳水平不斷上升。
制作背景
保持健康的工作空間至關(guān)重要,但我們需要一個(gè)既實(shí)用又美觀的解決方案。如果有一種緊湊型設(shè)備,不僅可以監(jiān)測(cè)空氣質(zhì)量,還可以作為美觀的辦公桌裝飾品,那不是很棒嗎?有了這個(gè)想法,Jason就開始著手實(shí)現(xiàn)它。Zigbee 是一種出色的智能家居低功耗通信協(xié)議。
使用 Seeed Studio 的 XIAO ESP32 C6 模塊作為主控。它擁有小巧的外形和全面的 Arduino Zigbee 教程為開發(fā)者節(jié)省了大量開發(fā)時(shí)間。此外,還增加了 XIAO 擴(kuò)展板和 Grove VOC 和 eCO2 氣體傳感器 (SGP30) 以獲得準(zhǔn)確的讀數(shù)。
由于不喜歡傳統(tǒng)的方形桌面擺件,所以Jason設(shè)計(jì)了一個(gè)小型站立機(jī)器人造型。至于細(xì)節(jié),對(duì) XIAO 擴(kuò)展板的引腳排列進(jìn)行了布線,以便在 XIAO 系列內(nèi)的不同 MCU 之間輕松切換。這讓Jason在組件選擇上有了更大的靈活性。在結(jié)構(gòu)件內(nèi),我將傳感器放在右側(cè),用打印材料與MCU 隔開,確保最佳性能和時(shí)尚的設(shè)計(jì)。
材料清單
硬件列表
Grove-VOC and CO2 Gas Sensor SGP30
Seeed Studio XIAO ESP32C6Seeed Studio
XIAO Expansion Board
軟件列表
Arduino IDE
Autodesk Fusion
Home Assistant
項(xiàng)目演示
1.連接帶 OLED 顯示屏的設(shè)備
在 OLED 屏幕上,我們將顯示連接狀態(tài),可以輕松查看 Zigbee 與 Home Assistant 的連接是否成功。此外,UI 設(shè)計(jì)中還會(huì)有一些小驚喜!
OLED 顯示屏將包含三個(gè)內(nèi)容區(qū)域:
啟動(dòng) 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 帶二氧化碳傳感器界面效果截圖
將購(gòu)買的 Home Assistant Connect ZBT-1 插入我的 HA 設(shè)置后,我通過 Zigbee Home Automation 添加了我們的 Zigbee 終端設(shè)備。隨后,在對(duì) XIAO ESP32 C6 進(jìn)行編程后,設(shè)備名稱出現(xiàn)在 OLED 顯示屏上。

HomeAssistant 查找 Zigbee 集成

HomeAssistant 連接 Zigbee 設(shè)備
訪問 Homeassistant 后,我們可以看到顯示的數(shù)據(jù)隨時(shí)間的變化
成功連接到 HomeAssistant 后,我們?cè)?Zigbee 中找到了我們的設(shè)備,它提供了兩個(gè)主要功能:
實(shí)時(shí)數(shù)據(jù)顯示

查看歷史二氧化碳趨勢(shì)。值得注意的是,我的設(shè)備在此期間并未持續(xù)運(yùn)行。
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(); } } } }
改進(jìn)計(jì)劃
得益于 Seeed Studio XIAO 擴(kuò)展板提供的眾多 Grove 接口,Jason計(jì)劃將其他傳感器集成到這個(gè)機(jī)器人中來收集更多數(shù)據(jù)。這將可以通過自動(dòng)化設(shè)備來控制其他的智能家居設(shè)備,例如當(dāng)空氣太干燥時(shí)啟動(dòng)除濕機(jī),或者當(dāng)二氧化碳水平上升時(shí)運(yùn)行空氣循環(huán)裝置。總而言之,Jason說這個(gè)智能二氧化碳監(jiān)測(cè)器項(xiàng)目是一次受益匪淺的創(chuàng)新和學(xué)習(xí)之旅。通過將技術(shù)與設(shè)計(jì)相結(jié)合,他創(chuàng)造了一種不僅可以跟蹤空氣質(zhì)量還可以增強(qiáng)工作空間美感的設(shè)備。接下來他會(huì)逐步利用 Zigbee 連接不同的傳感器,打造智能家居生態(tài)系統(tǒng)。
-
傳感器
+關(guān)注
關(guān)注
2576文章
55028瀏覽量
791226 -
mcu
+關(guān)注
關(guān)注
147文章
18924瀏覽量
397987 -
機(jī)器人
+關(guān)注
關(guān)注
213文章
31073瀏覽量
222162 -
空氣質(zhì)量檢測(cè)儀
+關(guān)注
關(guān)注
0文章
14瀏覽量
1353
原文標(biāo)題:創(chuàng)客項(xiàng)目秀|基于XIAO ESP32C6的HA空氣質(zhì)量檢測(cè)儀
文章出處:【微信號(hào):ChaiHuoMakerSpace,微信公眾號(hào):柴火創(chuàng)客空間】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
【GoKit申請(qǐng)】空氣質(zhì)量檢測(cè)儀
Pm2.5空氣質(zhì)量檢測(cè)的小問題
空氣質(zhì)量測(cè)試儀的原理
什么是壓縮空氣質(zhì)量檢測(cè)儀?
基于STM32空氣質(zhì)量檢測(cè)儀原理圖
德爾格壓縮空氣質(zhì)量檢測(cè)儀6種型號(hào)的詳細(xì)介紹
空氣質(zhì)量檢測(cè)儀的簡(jiǎn)單介紹
空氣質(zhì)量檢測(cè)儀的特點(diǎn)介紹
網(wǎng)格化空氣質(zhì)量監(jiān)測(cè)站的特點(diǎn)
空氣質(zhì)量檢測(cè)儀使用方法是怎樣的
空氣質(zhì)量檢測(cè)儀優(yōu)勢(shì)特點(diǎn)介紹
空氣質(zhì)量檢測(cè)儀原理與應(yīng)用介紹
ONETEST-100AQL空氣質(zhì)量檢測(cè)儀工作原理介紹
基于XIAO ESP32C6的HA空氣質(zhì)量檢測(cè)儀設(shè)計(jì)
評(píng)論