最近家装,各种材料堆砌,又想急着搬进去住,发现室内总是有味道,实在放心不下,于是网上搜了一大圈,都是甲醛检测治理,一次性也比较贵,具体效果怎么样,也无从验证。正好有朋友在做负氧离子睡眠机方面的研究,交流了一下,他强力推鉴我用负氧离子发生器来除醛,效果杠杠的。闲来无事,说干就干。
还是利用我自己搭建的阿里云平台架构,EMQ+TDEngine+NGINX
硬件照常祭出我的多协议网关控制器
这次主要是利用控制器的串口采集各种传感器信号,根据预设浓度参数实现IO口控制220V继电器,实现负氧离子发生器的自动工作,和新风系统的工作(后一步考虑找一下新风系统的协议,美国百朗的,哪位大侠有的话可以提供一个)
传感器的选择
PMSA003-C PM2.5攀藤传感器A0 激光 高精度 测雾霾粉尘
CO2传感器用了比较便宜的CCS811
甲醛传感器达特 DART WZ-S
继电器板不想重新画板在淘宝上采购了硬石科技的3.3V控制输入220V 5A的光电隔离继电器模块
一切准备就绪,开始撸代码
//ccs811 ECO2 tvoc
bool ccs811_get_results (ccs811_sensor_t* dev,
uint16_t* iaq_tvoc,
uint16_t* iaq_eco2,
uint8_t* raw_i,
uint16_t* raw_v)
{
if (!dev) return false;
dev->error_code = CCS811_OK;
if (dev->mode == ccs811_mode_idle)
{
error_dev ("Sensor is in idle mode and not performing measurements.",
__FUNCTION__, dev);
dev->error_code = CCS811_DRV_WRONG_MODE;
return false;
}
if (dev->mode == ccs811_mode_250ms && (iaq_tvoc || iaq_eco2))
{
error_dev ("Sensor is in constant power mode, only raw data "
"are available every 250ms",
__FUNCTION__, dev);
dev->error_code = CCS811_DRV_NO_IAQ_DATA;
return false;
}
uint8_t data[8];
// read IAQ sensor values and RAW sensor data including status and error id
if (!ccs811_reg_read(dev, CCS811_REG_ALG_RESULT_DATA, data, 8))
{
error_dev ("Could not read sensor data.", __FUNCTION__, dev);
dev->error_code |= CCS811_DRV_RD_DATA_FAILED;
return false;
}
// check for errors
if (data[CCS811_ALG_DATA_STATUS] & CCS811_STATUS_ERROR)
{
return ccs811_check_error_status (dev);
}
// check whether new data are ready, if not, latest values are read from sensor
// and error_code is set
if (!(data[CCS811_ALG_DATA_STATUS] & CCS811_STATUS_DATA_RDY))
{
debug_dev ("No new data.", __FUNCTION__, dev);
dev->error_code = CCS811_DRV_NO_NEW_DATA;
}
// if *iaq* is not NULL return IAQ sensor values
if (iaq_tvoc) *iaq_tvoc = data[CCS811_ALG_DATA_TVOC_HB] << 8 | data[CCS811_ALG_DATA_TVOC_LB];
if (iaq_eco2) *iaq_eco2 = data[CCS811_ALG_DATA_ECO2_HB] << 8 | data[CCS811_ALG_DATA_ECO2_LB];
// if *raw* is not NULL return RAW sensor data
if (raw_i) *raw_i = data[CCS811_ALG_DATA_RAW_HB] >> 2;
if (raw_v) *raw_v = (data[CCS811_ALG_DATA_RAW_HB] & 0x03) << 8 | data[CCS811_ALG_DATA_RAW_LB];
return true;
}
//pasma003 pm2.5 pm10
void Pasm_Packet_Handle(uint8_t* pdatabuf, uint16_t datalen)
{
uint8_t temp, tmp1;
uint16_t a;
int i;
for (i = 0; i < datalen; i++) {
temp = pdatabuf[i];
if (temp == 0x42)
{
package_index = 0;
PMSA_start = 1;
whole_package[package_index++] = temp;
continue;
}
if (PMSA_start == 1)
{
if (temp == 0x4d)
PMSA_start = 2;
else
{
package_index = 0;
PMSA_start = 0;
whole_package[0] = 0;
whole_package[1] = 0;
continue;
}
}
if (PMSA_start == 2)
{
whole_package[package_index] = temp;
package_index++;
}
if ((package_index >= PACKAGE_LEN) && (whole_package[0] == 0x42) && (whole_package[1] == 0x4d))
{
pasmhandle_package(whole_package);
PMSA_start = 0;
package_index = 0;
whole_package[0] = 0;
whole_package[1] = 0;
continue;
}
if (package_index >= PACKAGE_LEN)
{
PMSA_start = 0;
package_index = 0;
whole_package[0] = 0;
}
}
return;
}
//dart HCHO
void Dart_Packet_Handle(uint8_t *pdatabuf,uint16_t datalen)
{
uint8_t temp,tmp1;
uint16_t a;
int i;
for (i = 0; i < datalen; i++) {
temp = pdatabuf[i];
if (temp == 0xff)
{
DART_RX_NUM = 0;
Dart_start = 1;
}
if (Dart_start == 1)
{
Dart_Data[DART_RX_NUM] = temp;
DART_RX_NUM++;
}
if ((DART_RX_NUM >= 9) && (Dart_Data[0] == 0xff) && (Dart_Data[1] == 0x86))
{
a = (Dart_Data[2] * 256 + Dart_Data[3]);
if (a >= 5000)a = 5000;
Dart_hcho_rev = a;
ESP_LOGI(DART_TAG, "HCHO:%d\n", Dart_hcho_rev);
SetDartDataValue(Dart_hcho_rev);
Dart_start = 0;
DART_RX_NUM = 0;
Dart_Data[0] = 0;
Dart_Data[1] = 0;
continue;
}
if ((DART_RX_NUM >= 9) && (Dart_Data[0] == 0xff) && (Dart_Data[1] == 0x17))
{
a = (Dart_Data[4] * 256 + Dart_Data[5]);
if (a >= 5000)a = 5000;
Dart_hcho_rev = a;
printf("HCHO:%d\n", Dart_hcho_rev);
SetDartDataValue(Dart_hcho_rev);
Dart_start = 0;
DART_RX_NUM = 0;
Dart_Data[0] = 0;
Dart_Data[1] = 0;
continue;
}
if (DART_RX_NUM >= 9)
{
Dart_start = 0;
DART_RX_NUM = 0;
Dart_Data[0] = 0;
}
}
return;
}
//mqtt publish
void air_sense_mqtt(void* pvParameters)
{
int ret1 = 0,ret2=0;
uint16_t hcho;
uint16_t pm25,pm10,pm1;
uint16_t deviceid[3];
char date_str[20];
char time_str[20];
cJSON* root = NULL;
cJSON* subroot = NULL;
char* pp;
// wait for time to be set
time_t now = 0;
//struct tm timeinfo = { 0 };
struct tm now_time = { 0 };
TickType_t last_wakeup = xTaskGetTickCount();
GetChipID();
deviceid[0] = (mac_addr[1] << 8) | mac_addr[0];
deviceid[1] = (mac_addr[3] << 8) | mac_addr[2];
deviceid[2] = (mac_addr[5] << 8) | mac_addr[4];
while (1)
{
// get environmental data from another sensor and set them
// ccs811_set_environmental_data (sensor, 25.3, 47.8);
// get the results and do something with them
ret1=GetDartDataValue(&hcho);
ret2=GetPMSA003Value(&pm25, &pm10, &pm1);
if(ret1&&ret2)
{
printf("%.3f ESP32AIR Sensor periodic: hcho %d ppb, pm25 %dμg/m3 pm10 %dμg/m3 pm1 %dμg/m3 \n",
(double)sdk_system_get_time() * 1e-3, hcho, pm25,pm10,pm1);
#if 1
time(&now);
//localtime_r(&now, &timeinfo);
localtime_r(&now, &now_time);
sprintf(date_str, "%d-%d-%d", now_time.tm_year + 1900, now_time.tm_mon + 1, now_time.tm_mday);
sprintf(time_str, "%d:%d:%d", now_time.tm_hour, now_time.tm_min, now_time.tm_sec);
root = cJSON_CreateObject();//创建一个机器人状态的JSON对象
subroot = cJSON_CreateObject();//subroot是下面的一个嵌入对象
cJSON_AddStringToObject(subroot, "SensorType", "ESP32_AIR");
cJSON_AddStringToObject(subroot, "DATE", date_str);
cJSON_AddStringToObject(subroot, "TIME", time_str);
cJSON_AddNumberToObject(subroot, "DEVICEID0", deviceid[0]);
cJSON_AddNumberToObject(subroot, "DEVICEID1", deviceid[1]);
cJSON_AddNumberToObject(subroot, "DEVICEID2", deviceid[2]);
cJSON_AddNumberToObject(subroot, "Addr", 1);
cJSON_AddNumberToObject(subroot, "HCHO", hcho);
cJSON_AddNumberToObject(subroot, "PM25", pm25);
cJSON_AddNumberToObject(subroot, "PM10", pm10);
cJSON_AddNumberToObject(subroot, "PM1", pm1);
cJSON_AddItemToObject(root, "RealAIR", subroot);
pp = cJSON_Print(root);
if (NULL == pp)
{
printf("cjson create fail\r\n");
cJSON_Delete(root);
return;
}
printf("real sensordata ESP_AIR:%s", pp);
esp_mqtt_client_publish(g_mqtt_client, SENSOR_ESP32_AIR_REAL_DATA_TOPIC, pp, 0, 0, 0);
//strcpy(csensorSOILPayloadStr,pp);
cJSON_Delete(root);//最后将root根节点删除
cJSON_free(pp);//释放result的空间,必须要有,要不然内存里会失去一段空间,最后系统崩溃
pp = NULL;
#endif
}
// passive waiting until 1 second is over
vTaskDelayUntil(&last_wakeup, 30000 / portTICK_PERIOD_MS);
}
}
监控界面采用了Grafana直接读取TDEngine数据,实现了极少的编程,界面效果还不错
作为理科生,多动手DIY可以防止老年痴呆,Fighting!