While on the search for an alternative to directly buying the fairly expensive MH-Z19 CO2 sensor, [spezifisch] came across a ‘BreeRainz’ branded gadget (also found under other brands) that claimed to use an NDIR (Non-Dispersive Infrared) sensor for measuring CO2 levels, while costing only €25. This type of sensor allows for CO2 levels to be measured directly, rather than inferred, making them significantly more precise.
After cracking the gadget open (literally, due to the hidden screws), the CO2 sensor is clearly visible. While superficially identical to an MH-Z19, the NDIR sensor is actually called ‘HC8’, is produced by 广州海谷电子科技有限公司 (Guangzhou Haigu Electronic Technology Co., Ltd.). While being pin-compatible with the MH-Z19, its UART protocol is not the same. Fortunately there is a datasheet to help with implementing it, which is what [spezifisch] did.
This raises the question of whether harvesting NDIR CO2 sensors like this is worth it to save a few Euros. A quick look on German Amazon shows that the device in question currently costs €35, while a genuine MH-Z19 can be bought for €25 or less. There are also many MH-Z19 models (B, C and D), which cover an even wider price range. All of which points to finding an NDIR sensor-containing device can be interesting when it’s on sale, but if all you care about is the sensor itself, it’s probably best to just buy them directly.
I still have some trouble finding hidden screws, but over the decades I’ve learned a few of the tricks that are implemented. But, I’m still surprised to read that someone doesn’t know about them.
Perhaps you’ll share these “tricks” with the rest of us?
He found the mysterious device specs by googling the command’s byte sequence “64 69 03 5E 4E” that he sniffed on its RX line ! Five bytes and Google gets it all, bang! This era is amazing.
Or you could just buy a much better sensor from a German company for the same amount of money — search your favorite distributor for “pasco2” :)
Interesting tip, thanks! Including VAT it costs around 34€ at Farnell (Germany) and Digikey though (product name: EVALPASCO2MINIBOARDTOBO1).
Any suggestions for something that runs in a high humidity environment where there is a chance of water exposure?
/**
HC8 sensor code for arduino Uno. I wrote this with help from OG python script,
from translated chinese docs, with some help from GPT.
By default in active output mode, sensor will produce CO2 value every second.
This script will store last 60 values and each minute will calculate the average.
Data format in “active output” mode:
The output format is 16BYTE.
Data header: BYTE0 = 0X42; BYTE1=4D
BYTE6 data is high, BYTE7 data is low, indicating CO2 concentration.
BYTE15, data checksum. BYTE15= BYTE0+BYTE1+…….+BYTE13;
Example: 42 4D 0C 51 09 A2 07 2B 01 35 05 81 20 08 20 AD;
CO2 concentration = BYTE6 X 256 + BYTE7 = 07X256 + 2B = 1853;
**/
#include
// Define the CO2 sensor serial interface
SoftwareSerial sensorSerial(2, 3); // RX, TX
// Define the number of bytes in a data packet
#define DATA_PACKET_SIZE 16
// Define the size of the CO2 buffer in “element-seconds”
// The CO2 sensor sends data every second,
// and the CO2_BUFFER_SIZE determines the number of measurements stored.
// It is also used as the interval (in seconds) for printing the average value.
#define CO2_BUFFER_SIZE 60
unsigned long intervalPrintTime = CO2_BUFFER_SIZE * 1000UL;
// Initialize an array to store CO2 values
uint16_t co2Values[CO2_BUFFER_SIZE];
// Variable to keep track of the current index in the buffer
int co2Index = 0;
// Variable to keep track of the number of valid measurements in the buffer
int validMeasurements = 0;
// Variable to keep track of the last print time
unsigned long lastPrintTime = 0;
void setup() {
// Start the serial communication with the CO2 sensor
sensorSerial.begin(9600);
Serial.begin(9600); // You can change this baud rate based on your needs
}
void printAverage() {
// Calculate the average of the valid CO2 values in the buffer
uint32_t sum = 0;
for (int i = 0; i < validMeasurements; i++) {
sum += co2Values[i];
}
uint16_t average = static_cast(sum / validMeasurements);
// Print the average CO2 concentration
Serial.print(“Average CO2 Concentration: “);
Serial.println(average);
}
void loop() {
if (sensorSerial.available() >= DATA_PACKET_SIZE) {
// Read the data packet from the CO2 sensor
uint8_t dataPacket[DATA_PACKET_SIZE];
sensorSerial.readBytes(dataPacket, DATA_PACKET_SIZE);
// Check if the data packet has a valid header
if (dataPacket[0] == 0x42 && dataPacket[1] == 0x4D) {
// Extract CO2 concentration from the data packet
uint16_t co2Concentration = (dataPacket[6] << 8) | dataPacket[7];
// Verify the checksum
uint8_t checksum = 0;
for (int i = 0; i = intervalPrintTime) || (millis() < lastPrintTime)) {
printAverage();
lastPrintTime = millis();
}
} else {
// Checksum error
Serial.println("Checksum error!");
}
} else {
// Invalid header
Serial.println("Invalid data packet header!");
}
}
}