mirror of
https://github.com/arendst/Tasmota.git
synced 2026-07-27 20:05:46 +00:00
Zigbee co2 sensor (#24337)
* Add CO2 sensor support * Add CO2 sensor support merge with actual development * Merge bug fix
This commit is contained in:
@@ -462,24 +462,28 @@ public:
|
||||
pressure(-0x8000),
|
||||
humidity(0xFFFF),
|
||||
th_setpoint(0xFF),
|
||||
temperature_target(-0x8000)
|
||||
temperature_target(-0x8000),
|
||||
CO2(-1)
|
||||
{}
|
||||
|
||||
inline bool validTemperature(void) const { return -0x8000 != temperature; }
|
||||
inline bool validPressure(void) const { return -0x8000 != pressure; }
|
||||
inline bool validHumidity(void) const { return 0xFFFF != humidity; }
|
||||
inline bool validCO2(void) const { return -1 != CO2; }
|
||||
inline bool validThSetpoint(void) const { return 0xFF != th_setpoint; }
|
||||
inline bool validTempTarget(void) const { return -0x8000 != temperature_target; }
|
||||
|
||||
inline int16_t getTemperature(void) const { return temperature; }
|
||||
inline int16_t getPressure(void) const { return pressure; }
|
||||
inline int16_t getPressure(void) const { return pressure; }
|
||||
inline uint16_t getHumidity(void) const { return humidity; }
|
||||
inline float getCO2(void) const { return CO2; }
|
||||
inline uint8_t getThSetpoint(void) const { return th_setpoint; }
|
||||
inline int16_t getTempTarget(void) const { return temperature_target; }
|
||||
|
||||
inline void setTemperature(int16_t _temperature) { temperature = _temperature; }
|
||||
inline void setPressure(int16_t _pressure) { pressure = _pressure; }
|
||||
inline void setHumidity(uint16_t _humidity) { humidity = _humidity; }
|
||||
inline void setCO2(float _CO2) { CO2 = _CO2; }
|
||||
inline void setThSetpoint(uint8_t _th_setpoint) { th_setpoint = _th_setpoint; }
|
||||
inline void setTempTarget(int16_t _temperature_target){ temperature_target = _temperature_target; }
|
||||
|
||||
@@ -492,6 +496,7 @@ public:
|
||||
// thermostat
|
||||
uint8_t th_setpoint; // percentage of heat/cool in percent
|
||||
int16_t temperature_target; // settings for the temparature
|
||||
float CO2; // CO2 in ppm, 0..10000, -1 if unknown
|
||||
};
|
||||
|
||||
/*********************************************************************************************\
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -190,8 +190,8 @@ enum Cx_cluster_short {
|
||||
Cx0010, Cx0011, Cx0012, Cx0013, Cx0014, Cx001A, Cx0020, Cx0021,
|
||||
Cx0100, Cx0101, Cx0102, Cx0201, Cx0202, Cx0203, Cx0204,
|
||||
Cx0300, Cx0301, Cx0400, Cx0401, Cx0402, Cx0403,
|
||||
Cx0404, Cx0405, Cx0406, Cx0500, Cx0702, Cx0B01, Cx0B04, Cx0B05,
|
||||
CxEF00, CxFC01, CxFC40, CxFCC0, CxFCCC,
|
||||
Cx0404, Cx0405, Cx0406, Cx040D, Cx0500, Cx0702, Cx0B01, Cx0B04,
|
||||
Cx0B05, CxEF00, CxFC01, CxFC40, CxFCC0, CxFCCC,
|
||||
};
|
||||
|
||||
const uint16_t Cx_cluster[] PROGMEM = {
|
||||
@@ -200,8 +200,8 @@ const uint16_t Cx_cluster[] PROGMEM = {
|
||||
0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x001A, 0x0020, 0x0021,
|
||||
0x0100, 0x0101, 0x0102, 0x0201, 0x0202, 0x0203, 0x0204,
|
||||
0x0300, 0x0301, 0x0400, 0x0401, 0x0402, 0x0403,
|
||||
0x0404, 0x0405, 0x0406, 0x0500, 0x0702, 0x0B01, 0x0B04, 0x0B05,
|
||||
0xEF00, 0xFC01, 0xFC40, 0xFCC0, 0xFCCC,
|
||||
0x0404, 0x0405, 0x0406, 0x040D, 0x0500, 0x0702, 0x0B01, 0x0B04,
|
||||
0x0B05, 0xEF00, 0xFC01, 0xFC40, 0xFCC0, 0xFCCC,
|
||||
};
|
||||
|
||||
uint16_t CxToCluster(uint8_t cx) {
|
||||
@@ -223,20 +223,20 @@ uint8_t ClusterToCx(uint16_t cluster) {
|
||||
// Multiplier contains only a limited set of values, so instead of storing the value
|
||||
// we store an index in a table, and reduce it to 4 bits
|
||||
enum Cm_multiplier_nibble {
|
||||
Cm0 = 0, Cm1 = 1, Cm2, Cm5, Cm10, Cm100,
|
||||
Cm0 = 0, Cm1 = 1, Cm2, Cm5, Cm10, Cm100, Cm1000000,
|
||||
// negative numbers
|
||||
Cm_2, Cm_5, Cm_10, Cm_100
|
||||
};
|
||||
|
||||
const int8_t Cm_multiplier[] PROGMEM = {
|
||||
0, 1, 2, 5, 10, 100,
|
||||
-2, -5, -10, -100,
|
||||
const int32_t Cm_multiplier[] PROGMEM = {
|
||||
0, 1, 2, 5, 10, 100, 1000000,
|
||||
-2, -5, -10, -100
|
||||
};
|
||||
|
||||
int8_t CmToMultiplier(uint8_t cm) {
|
||||
int32_t CmToMultiplier(uint8_t cm) {
|
||||
cm = cm & 0x0F; // get only low nibble
|
||||
if (cm < nitems(Cm_multiplier)) {
|
||||
return pgm_read_byte(&Cm_multiplier[cm]);
|
||||
return pgm_read_dword(&Cm_multiplier[cm]);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@@ -1109,6 +1109,13 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
|
||||
{ Zuint8, Cx0406, 0x0012, Z_(PIRUnoccupiedToOccupiedThreshold), Cm1, 0 },
|
||||
// { Zunk, Cx0406, 0xFFFF, Z_(), Cm0, 0 }, // Remove all other values
|
||||
|
||||
// CO2 Concentration Measurement cluster
|
||||
{ Zsingle, Cx040D, 0x0000, Z_(CO2), Cm1000000 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Thermo, CO2) }, // CO2 (ppm)
|
||||
{ Zsingle, Cx040D, 0x0001, Z_(CO2MinMeasuredValue), Cm1000000, 0 }, //
|
||||
{ Zsingle, Cx040D, 0x0002, Z_(CO2MaxMeasuredValue), Cm1000000, 0 }, //
|
||||
{ Zsingle, Cx040D, 0x0003, Z_(CO2Tolerance), Cm1000000, 0 }, //
|
||||
// { Zunk, Cx040D, 0xFFFF, Z_(), Cm0, 0 }, // Remove all other values
|
||||
|
||||
// IAS Cluster (Intruder Alarm System)
|
||||
{ Zenum8, Cx0500, 0x0000, Z_(ZoneState), Cm1, 0 }, // Occupancy (map8)
|
||||
{ Zenum16, Cx0500, 0x0001, Z_(ZoneType), Cm1 + Z_EXPORT_DATA, Z_MAPPING(Z_Data_Alarm, zone_type) }, // Zone type for sensor
|
||||
|
||||
@@ -46,9 +46,9 @@ class Z_attribute_match Z_findAttributeMatcherByName(uint16_t shortaddr, const c
|
||||
matched_attr.cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
|
||||
matched_attr.attribute = pgm_read_word(&converter->attribute);
|
||||
matched_attr.name = (Z_strings + pgm_read_word(&converter->name_offset));
|
||||
int8_t multiplier8 = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
|
||||
if (multiplier8 > 1) { matched_attr.multiplier = multiplier8; }
|
||||
if (multiplier8 < 0) { matched_attr.divider = -multiplier8; }
|
||||
int32_t multiplier32 = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
|
||||
if (multiplier32 > 1) { matched_attr.multiplier = multiplier32; }
|
||||
if (multiplier32 < 0) { matched_attr.divider = -multiplier32; }
|
||||
matched_attr.zigbee_type = pgm_read_byte(&converter->type);
|
||||
uint8_t conv_mapping = pgm_read_byte(&converter->mapping);
|
||||
matched_attr.map_type = (Z_Data_Type) ((conv_mapping & 0xF0)>>4);
|
||||
@@ -79,9 +79,9 @@ class Z_attribute_match Z_findAttributeMatcherById(uint16_t shortaddr, uint16_t
|
||||
matched_attr.cluster = cluster;
|
||||
matched_attr.attribute = attr_id;
|
||||
matched_attr.name = (Z_strings + pgm_read_word(&converter->name_offset));
|
||||
int8_t multiplier8 = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
|
||||
if (multiplier8 > 1) { matched_attr.multiplier = multiplier8; }
|
||||
if (multiplier8 < 0) { matched_attr.divider = -multiplier8; }
|
||||
int32_t multiplier32 = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
|
||||
if (multiplier32 > 1) { matched_attr.multiplier = multiplier32; }
|
||||
if (multiplier32 < 0) { matched_attr.divider = -multiplier32; }
|
||||
matched_attr.zigbee_type = pgm_read_byte(&converter->type);
|
||||
uint8_t conv_mapping = pgm_read_byte(&converter->mapping);
|
||||
matched_attr.map_type = (Z_Data_Type) ((conv_mapping & 0xF0)>>4);
|
||||
@@ -1531,6 +1531,8 @@ void Z_postProcessAttributes(uint16_t shortaddr, uint16_t src_ep, class Z_attrib
|
||||
uint8_t * attr_address = ((uint8_t*)&data) + sizeof(Z_Data) + matched_attr.map_offset;
|
||||
uint32_t uval32 = attr.getUInt(); // call converter to uint only once
|
||||
int32_t ival32 = attr.getInt(); // call converter to int only once
|
||||
float fval = attr.getFloat(); // call converter to int only once
|
||||
|
||||
// AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Mapping type=%d offset=%d zigbee_type=%02X value=%d\n"), (uint8_t) matched_attr.map_type, matched_attr.map_offset, matched_attr.zigbee_type, ival32);
|
||||
switch (ccccaaaa) {
|
||||
case 0xEF000202:
|
||||
@@ -1551,6 +1553,7 @@ void Z_postProcessAttributes(uint16_t shortaddr, uint16_t src_ep, class Z_attrib
|
||||
case Zint8: *(int8_t*)attr_address = ival32; break;
|
||||
case Zint16: *(int16_t*)attr_address = ival32; break;
|
||||
case Zint32: *(int32_t*)attr_address = ival32; break;
|
||||
case Zsingle: *(float*)attr_address = fval; break;
|
||||
}
|
||||
if (Z_Data_Set::updateData(data)) {
|
||||
zigbee_devices.dirty();
|
||||
@@ -1723,11 +1726,11 @@ void Z_Data::toAttributes(Z_attribute_list & attr_list) const {
|
||||
const Z_AttributeConverter *converter = &Z_PostProcess[i];
|
||||
uint8_t conv_export = pgm_read_byte(&converter->multiplier_idx) & Z_EXPORT_DATA;
|
||||
uint8_t conv_mapping = pgm_read_byte(&converter->mapping);
|
||||
uint16_t multiplier = 1;
|
||||
uint16_t divider = 1;
|
||||
int8_t multiplier8 = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
|
||||
if (multiplier8 > 1) { multiplier = multiplier8; }
|
||||
if (multiplier8 < 0) { divider = -multiplier8; }
|
||||
uint32_t multiplier = 1;
|
||||
uint32_t divider = 1;
|
||||
int32_t multiplier32 = CmToMultiplier(pgm_read_byte(&converter->multiplier_idx));
|
||||
if (multiplier32 > 1) { multiplier = multiplier32; }
|
||||
if (multiplier32 < 0) { divider = -multiplier32; }
|
||||
Z_Data_Type map_type = (Z_Data_Type) ((conv_mapping & 0xF0)>>4);
|
||||
uint8_t map_offset = (conv_mapping & 0x0F);
|
||||
|
||||
|
||||
@@ -335,11 +335,11 @@ ZBM(ZBS_AF_REGISTERF2, Z_SREQ | Z_AF, AF_REGISTER, 0xF2 /* endpoint */, Z_B0(Z_P
|
||||
// Z_AF:register profile:104, ep:01 - main clusters for router or device
|
||||
ZBM(ZBS_AF_REGISTER_ALL, Z_SREQ | Z_AF, AF_REGISTER, 0x01 /* endpoint */, Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA), // 24000401050000000000
|
||||
0x05, 0x00 /* AppDeviceId */, 0x00 /* AppDevVer */, 0x00 /* LatencyReq */,
|
||||
0x0E /* AppNumInClusters */, // actually all clusters will be received
|
||||
0x0F /* AppNumInClusters */, // actually all clusters will be received
|
||||
0x00,0x00, 0x04,0x00, 0x05,0x00, 0x06,0x00, // 0x0000, 0x0004, 0x0005, 0x0006
|
||||
0x07,0x00, 0x08,0x00, 0x0A,0x00, 0x02,0x01, // 0x0007, 0x0008, 0x000A, 0X0102
|
||||
0x00,0x03, 0x00,0x04, 0x02,0x04, 0x03,0x04, // 0x0300, 0x0400, 0x0402, 0x0403
|
||||
0x05,0x04, 0x06,0x04, // 0x0405, 0x0406
|
||||
0x05,0x04, 0x06,0x04, 0x0D,0x04, // 0x0405, 0x0406, 0x040D
|
||||
0x00 /* AppNumOutClusters */)
|
||||
|
||||
// Z_ZDO:mgmtPermitJoinReq
|
||||
@@ -724,16 +724,16 @@ ZBR(ZBR_SET_OK2, EZSP_setConfigurationValue, 0x00 /*high*/, 0x00 /*ok*/)
|
||||
// Add Endpoints
|
||||
ZBM(ZBS_ADD_ENDPOINT1, EZSP_addEndpoint, 0x00 /*high*/, 0x01 /*ep*/, Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA),
|
||||
0x05, 0x00 /* AppDeviceId */, 0x00 /* AppDevVer */,
|
||||
0x0F /* inputClusterCount */, // actually all clusters will be received
|
||||
0X0F /* outputClusterCount */, // 02000104010500000F0F0000040005000600070008000A00020100030004020403040504060400050000040005000600070008000A0002010003000402040304050406040005
|
||||
0x10 /* inputClusterCount */, // actually all clusters will be received
|
||||
0X10 /* outputClusterCount */, // 02000104010500000F0F0000040005000600070008000A00020100030004020403040504060400050000040005000600070008000A0002010003000402040304050406040005
|
||||
0x00,0x00, 0x04,0x00, 0x05,0x00, 0x06,0x00, // 0x0000, 0x0004, 0x0005, 0x0006
|
||||
0x07,0x00, 0x08,0x00, 0x0A,0x00, 0x02,0x01, // 0x0007, 0x0008, 0x000A, 0X0102
|
||||
0x00,0x03, 0x00,0x04, 0x02,0x04, 0x03,0x04, // 0x0300, 0x0400, 0x0402, 0x0403
|
||||
0x05,0x04, 0x06,0x04, 0x00,0x05, // 0x0405, 0x0406, 0x0500
|
||||
0x05,0x04, 0x06,0x04, 0x0D,0x04, 0x00,0x05, // 0x0405, 0x0406, 0x040D, 0x0500
|
||||
0x00,0x00, 0x04,0x00, 0x05,0x00, 0x06,0x00, // 0x0000, 0x0004, 0x0005, 0x0006
|
||||
0x07,0x00, 0x08,0x00, 0x0A,0x00, 0x02,0x01, // 0x0007, 0x0008, 0x000A, 0X0102
|
||||
0x00,0x03, 0x00,0x04, 0x02,0x04, 0x03,0x04, // 0x0300, 0x0400, 0x0402, 0x0403
|
||||
0x05,0x04, 0x06,0x04, 0x00,0x05, // 0x0405, 0x0406, 0x0500
|
||||
0x05,0x04, 0x06,0x04, 0x0D,0x04, 0x00,0x05, // 0x0405, 0x0406, 0x040D, 0x0500
|
||||
)
|
||||
ZBM(ZBS_ADD_ENDPOINTB, EZSP_addEndpoint, 0x00 /*high*/, 0x0B /*ep*/, Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA),
|
||||
0x05, 0x00 /* AppDeviceId */, 0x00 /* AppDevVer */,
|
||||
|
||||
@@ -665,7 +665,7 @@ int32_t Z_ReceiveActiveEp(int32_t res, const SBuffer &buf) {
|
||||
// list of clusters that need bindings
|
||||
const uint8_t Z_bindings[] PROGMEM = {
|
||||
Cx0001, Cx0006, Cx0008, Cx0102, Cx0201, Cx0300,
|
||||
Cx0400, Cx0402, Cx0403, Cx0405, Cx0406,
|
||||
Cx0400, Cx0402, Cx0403, Cx0405, Cx0406, Cx040D,
|
||||
Cx0500, Cx0B04,
|
||||
};
|
||||
|
||||
@@ -1564,6 +1564,7 @@ const Z_autoAttributeReporting_t Z_autoAttributeReporting[] PROGMEM = {
|
||||
{ 0x0403, 0x0000, 30, USE_ZIGBEE_MAXTIME_SENSOR, USE_ZIGBEE_AUTOBIND_PRESSURE }, // Pressure (1 hPa)
|
||||
{ 0x0405, 0x0000, 30, USE_ZIGBEE_MAXTIME_SENSOR, USE_ZIGBEE_AUTOBIND_HUMIDITY }, // Humidity (1 %)
|
||||
{ 0x0406, 0x0000, 10, USE_ZIGBEE_MAXTIME_SENSOR, 0 }, // Occupancy
|
||||
{ 0x040D, 0x0000, 60, USE_ZIGBEE_MAXTIME_SENSOR, 0.00005 }, // CO2
|
||||
{ 0x0500, 0x0002, 1, USE_ZIGBEE_MAXTIME_SENSOR, 0 }, // ZoneStatus
|
||||
};
|
||||
|
||||
|
||||
@@ -2285,42 +2285,49 @@ void ZigbeeShow(void)
|
||||
|
||||
WSContentSend_PD(msg[ZB_WEB_END_STATUS], dhm);
|
||||
|
||||
// Sensors
|
||||
const Z_Data_Thermo & thermo = device.data.find<Z_Data_Thermo>();
|
||||
for (uint8_t i = 0; i < endpoints_max; i++) {
|
||||
if (0 == device.endpoints[i]) continue;
|
||||
uint8_t endpoint = device.endpoints[i];
|
||||
// Sensors
|
||||
const Z_Data_Thermo & thermo = device.data.find<Z_Data_Thermo>(endpoint);
|
||||
|
||||
if (&thermo != &z_data_unk) {
|
||||
bool validTemp = thermo.validTemperature();
|
||||
bool validTempTarget = thermo.validTempTarget();
|
||||
bool validThSetpoint = thermo.validThSetpoint();
|
||||
bool validHumidity = thermo.validHumidity();
|
||||
bool validPressure = thermo.validPressure();
|
||||
if (&thermo != &z_data_unk) {
|
||||
bool validTemp = thermo.validTemperature();
|
||||
bool validTempTarget = thermo.validTempTarget();
|
||||
bool validThSetpoint = thermo.validThSetpoint();
|
||||
bool validHumidity = thermo.validHumidity();
|
||||
bool validPressure = thermo.validPressure();
|
||||
bool validCO2 = thermo.validCO2();
|
||||
|
||||
if (validTemp || validTempTarget || validThSetpoint || validHumidity || validPressure) {
|
||||
WSContentSend_P(msg[ZB_WEB_LINE_START]);
|
||||
if (validTemp) {
|
||||
char buf[12];
|
||||
dtostrf(thermo.getTemperature() / 100.0f, 3, 1, buf);
|
||||
WSContentSend_PD(PSTR(" ☀️ %s°C"), buf);
|
||||
}
|
||||
if (validTempTarget) {
|
||||
char buf[12];
|
||||
dtostrf(thermo.getTempTarget() / 100.0f, 3, 1, buf);
|
||||
WSContentSend_PD(PSTR(" 🎯 %s°C"), buf);
|
||||
}
|
||||
if (validThSetpoint) {
|
||||
WSContentSend_PD(PSTR(" ⚙️ %d%%"), thermo.getThSetpoint());
|
||||
}
|
||||
if (validHumidity) {
|
||||
WSContentSend_P(PSTR(" 💧 %d%%"), (uint16_t)(thermo.getHumidity() / 100.0f + 0.5f));
|
||||
}
|
||||
if (validPressure) {
|
||||
WSContentSend_P(PSTR(" ⛅ %d hPa"), thermo.getPressure());
|
||||
}
|
||||
if (validTemp || validTempTarget || validThSetpoint || validHumidity || validPressure || validCO2) {
|
||||
WSContentSend_P(msg[ZB_WEB_LINE_START]);
|
||||
if (validTemp) {
|
||||
char buf[12];
|
||||
dtostrf(thermo.getTemperature() / 100.0f, 3, 1, buf);
|
||||
WSContentSend_PD(PSTR(" ☀️ %s°C"), buf);
|
||||
}
|
||||
if (validTempTarget) {
|
||||
char buf[12];
|
||||
dtostrf(thermo.getTempTarget() / 100.0f, 3, 1, buf);
|
||||
WSContentSend_PD(PSTR(" 🎯 %s°C"), buf);
|
||||
}
|
||||
if (validThSetpoint) {
|
||||
WSContentSend_PD(PSTR(" ⚙️ %d%%"), thermo.getThSetpoint());
|
||||
}
|
||||
if (validHumidity) {
|
||||
WSContentSend_P(PSTR(" 💧 %d%%"), (uint16_t)(thermo.getHumidity() / 100.0f + 0.5f));
|
||||
}
|
||||
if (validPressure) {
|
||||
WSContentSend_P(PSTR(" ⛅ %d hPa"), thermo.getPressure());
|
||||
}
|
||||
if (validCO2) {
|
||||
WSContentSend_P(PSTR(" 🫧 %.0f ppm"), 1000000*thermo.getCO2());
|
||||
}
|
||||
|
||||
WSContentSend_P(PSTR("{e}"));
|
||||
WSContentSend_P(PSTR("{e}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Light, switches and plugs
|
||||
const Z_Data_OnOff & onoff = device.data.find<Z_Data_OnOff>();
|
||||
bool onoff_display = (&onoff != &z_data_unk) ? onoff.validPower() : false;
|
||||
|
||||
Reference in New Issue
Block a user