As a programmer, I often find myself glued to my desk for hours, immersed in lines of code. 💻 However, I've become increasingly aware of the air quality around me, particularly the rising levels of CO2. ☁️ It’s essential to maintain a healthy workspace, but I needed a solution that was both functional and aesthetically pleasing. ✨
That's when inspiration struck! 💡 What if I could create a compact device that not only monitors CO2 levels but also serves as an attractive desk ornament? 🎨 With this idea in mind, I set out to make it happen.
Zigbee stood out to me as an excellent low-power communication protocol for smart homes. 🏠 Integrating it with Home Assistant (HA) could extend the operational time of my device, a critical factor for my long coding sessions. ⏳ Excited about this, I began the selection process.
I turned to Seeed Studio and chose the XIAO ESP32 C6 module. Its small form factor and comprehensive Arduino Zigbee tutorials saved me a lot of development time. ⌛ Additionally, I picked the XIAO Expansion Board and the Grove VOC and eCO2 Gas Sensor (SGP30) for accurate readings. 📊
With all the components in hand, I was ready to bring my vision to life. 🚀 I can’t wait to see how this project evolves and to share my progress with the Hackster community! 🌟
I’m not a fan of traditional square desk ornaments, so I designed a small standing robot shape. 🤖 For the details, I routed the XIAO Expansion Board's pinout to allow for easy switching between different MCUs within the XIAO series. 🔄 This gives me more flexibility in component selection. 💡 Inside, I placed the sensor on the right side, separated by a thick wall from the MCU, ensuring optimal performance and a sleek design. ✨
1.Connecting the Device with OLED Display
On the OLED screen, we'll show the connection status, making it easy to see if the Zigbee connection to Home Assistant is successful. Plus, there will be some exciting surprises in the UI design!
The OLED display will feature three content areas:
- Starting Zigbee Connection 🚀
- Connection Success Status ✅
- CO2 and eVOC Data 📊
We can also monitor the Zigbee connection status and data output of the XIAO ESP32 C6 by opening the Arduino serial monitor. 📊
2.HomeAssistant Interface Effect Screenshots with CO2 Sensor
After inserting the purchased Home Assistant Connect ZBT-1 into my HA setup, I added our Zigbee terminal device through Zigbee Home Automation. Subsequently, the device name appears on the OLED display after programming the XIAO ESP32 C6.
We can see the changes in the data displayed over time after accessing Homeassistant
After successfully connecting to HomeAssistant, we found our device in Zigbee, and it offers two key features:
- Real-Time Data Display 📊
- View Historical CO2 Trends 📈. It's worth noting that my device hasn't been running continuously during this period ⚠️.
3.Final Setup on the Desktop
Finally, we can see the sensor data displayed on the OLED screen, as well as the data on our Home Assistant dashboard.
#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 <U8g2lib.h>
#include <Wire.h>
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\n", tvoc_ppb);
Serial.printf("CO2eq Concentration: %d ppm\n", co2_eq_ppm);
carbon_dioxide_value = co2_eq_ppm;
zbCarbonDioxideSensor.setCarbonDioxide(carbon_dioxide_value);
} else {
Serial.println("Error reading IAQ values\n");
}
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();
}
}
}
}
Future ImprovementsThanks to the numerous Grove interfaces provided by the Seeed Studio XIAO Expansion Board, I plan to integrate additional sensors into this robot to collect more data. 📊 This will allow me to enhance my smart home setup by automating devices such as activating my dehumidifier when the air is too dry, or running the air circulation unit when CO2 levels rise. 🌬️ Isn’t that fantastic! 🎉
Final SummaryIn summary, this smart CO2 monitor project has been an exciting journey of innovation and learning. 🚀 By combining technology with design, I've created a device that not only tracks air quality but also enhances the aesthetics of my workspace. 🎨 I invite everyone to share their thoughts on my project, provide feedback, and suggest improvements. 💬 Your insights will be invaluable as I continue to develop this device and explore new features. Let’s work together to create healthier and smarter living environments! 🌱🏡
Next, I will gradually connect different sensors using Zigbee to create a smart home ecosystem. 🏡✨
Comments
Please log in or sign up to comment.