From 0d35230d9978748c8b54018ae2d776973d7bfd02 Mon Sep 17 00:00:00 2001 From: Armin Date: Mon, 31 Oct 2022 16:06:56 +0100 Subject: [PATCH] Major refactoring --- src/MHZ19.cpp | 722 +++++++++++++++++++++++++++++--------------------- src/MHZ19.h | 298 +++++++++++---------- 2 files changed, 581 insertions(+), 439 deletions(-) diff --git a/src/MHZ19.cpp b/src/MHZ19.cpp index a15aed7..8a7e799 100644 --- a/src/MHZ19.cpp +++ b/src/MHZ19.cpp @@ -2,65 +2,271 @@ #include "MHZ19.h" -/*#########################-Commands-##############################*/ - -// see https://revspace.nl/MH-Z19B -// Must have the same order as the COMMAND_TYPE enum -byte Commands[14] = { - 0x78, // 0 Recovery Reset Changes operation mode and performs MCU reset - 0x79, // 1 ABC (Automatic Baseline Correction) Mode ON/OFF - Turns ABC logic on or off (b[3] == 0xA0 - on, 0x00 - off) - 0x7D, // 2 Get ABC logic status (1 - enabled, 0 - disabled) - 0x84, // 3 Raw CO2 - 0x85, // 4 Temperature float, CO2 Unlimited - 0x86, // 5 Temperature integer, CO2 limited / clipped - 0x87, // 6 Zero Calibration - 0x88, // 7 Span Calibration - 0x99, // 8 Range - 0x9B, // 9 Get Range - 0x9C, // 10 Get Background CO2 - 0xA0, // 11 Get Firmware Version - 0xA2, // 12 Get Last Response - 0xA3 // 13 Get Temperature Calibration -}; - -/*#####################-Initiation Functions-#####################*/ - -void MHZ19::begin(Stream &serial) -{ - mySerial = &serial; - - /* establish connection */ - verify(); - - /* check if successful */ - if (this->errorCode != RESULT_OK) - { - #if defined (ESP32) && (MHZ19_ERRORS) - ESP_LOGE(TAG_MHZ19, "Initial communication errorCode recieved"); - #elif MHZ19_ERRORS - Serial.println("!ERROR: Initial communication errorCode recieved"); - #endif - } - - /* What FW version is the sensor running? */ - char myVersion[4]; - this->getVersion(myVersion); - - /* Store the major version number (assumed to be less than 10) */ - this->storage.settings.fw_ver = myVersion[1]; +//#define DEBUG_LOCAL +/*#####################-Initialization Functions-#####################*/ + +/* + * Initializes some variables and read version string + */ +void MHZ19::begin(Stream &aSerial) { + mySerial = &aSerial; + mySerial->setTimeout(MHZ19_RESPONSE_TIMEOUT_MILLIS); + + // preset send array once + CommandToSend[0] = 0xFF; + CommandToSend[1] = 0x01; + CommandToSend[3] = 0; + CommandToSend[4] = 0; + CommandToSend[6] = 0; + CommandToSend[7] = 0; + + VersionString[4] = '\0'; + +#if defined(DEBUG_LOCAL) + if (readCO2AndTemperature()) { + printErrorCode(&Serial); // print error to standard Serial, not to MHZ19 connection :-) + } +#else + readCO2AndTemperature(); // Test connection with standard command +#endif + + this->readVersion(); +} + +/** + * Sends the 9 byte command stream, and receives the 9 byte response into ReceivedResponse + * 9 bytes at 9600 baud takes 10 ms. + * Checks for checksum + * Timeout of MHZ19_RESPONSE_TIMEOUT_MILLIS (500) is specified in begin(); + * @return true if error happened during response receiving + */ +bool MHZ19::processCommand(MHZ19_command_t aCommand, bool aDoNotWaitForResponse) { +// Flush response buffer + while (mySerial->available()) { + mySerial->read(); + } + + CommandToSend[2] = aCommand; + + /* + * Datasheet: Checksum = NOT (Byte1+Byte2+Byte3+Byte4+Byte5+Byte6+Byte7))+1, Byte0 is start byte + * Set checksum. We have constant Byte1 0x01 and Bytes4 to 7 are also zero. + */ + CommandToSend[8] = (~(0x01 + aCommand + CommandToSend[3])) + 1; + + mySerial->write(CommandToSend, MHZ19_DATA_LEN); // Start sending + mySerial->flush(); // wait to be sent + + if (!aDoNotWaitForResponse) { + delay(12); + return readResponse(); + } + + return false; +} + +/** + * Receives the 9 byte response into ReceivedResponse + * Response starts 2 ms after end of request (23 ms for setAutocalibration()) + * Checks for checksum + * Timeout of MHZ19_RESPONSE_TIMEOUT_MILLIS (500) is specified in begin(); + * @return true if error happened during response receiving + */ +bool MHZ19::readResponse() { + /* response received, read buffer */ + if (mySerial->readBytes(ReceivedResponse, MHZ19_DATA_LEN) != MHZ19_DATA_LEN) { + this->errorCode = RESULT_TIMEOUT; +#if defined(DEBUG_LOCAL) + Serial.print(F("Timeout error. Available=")); + Serial.println(mySerial->available()); +#endif + return true; + } + + uint8_t tChecksum = 0; + for (uint_fast8_t i = 1; i < 8; i++) { + tChecksum += ReceivedResponse[i]; + } + tChecksum = ~tChecksum + 1; + +#if defined(DEBUG_LOCAL) + Serial.print(F(" Received cmd=0x")); + Serial.print(ReceivedResponse[1], HEX); + Serial.print(F("|")); + printCommand((MHZ19_command_t)ReceivedResponse[1], &Serial); + + for (uint_fast8_t i = 2; i < 8; i += 2) { + Serial.print(F(" 0x")); + Serial.print(ReceivedResponse[i], HEX); + Serial.print(F(",0x")); + Serial.print(ReceivedResponse[i + 1], HEX); + Serial.print(F(" =")); + Serial.print((uint16_t) (ReceivedResponse[i] << 8 | ReceivedResponse[i + 1])); // Unsigned decimal word + } + + Serial.println(); +#endif + + if (tChecksum != ReceivedResponse[8]) { + this->errorCode = RESULT_CHECKSUM; +#if defined(DEBUG_LOCAL) + Serial.print(F("Checksum error. Received=")); + Serial.print(ReceivedResponse[8]); + Serial.print(F(" expected=")); + Serial.println(tChecksum); +#endif + return true; + } + +#if defined(DEBUG_LOCAL) + if (CommandToSend[2] != ReceivedResponse[1]) { + Serial.print(F("Command mismatch error. Sent=0x")); + Serial.print(CommandToSend[2], HEX); + Serial.print(F(" received=0x")); + Serial.println(ReceivedResponse[1], HEX); + this->errorCode = RESULT_MATCH; + } +#endif + this->errorCode = RESULT_OK; + return false; +} + +void MHZ19::printErrorCode(Print *aSerial) { + aSerial->print(F("Response error code=")); + aSerial->println(errorCode); +} + +void MHZ19::printCommand(MHZ19_command_t aCommand, Print *aSerial) { + switch (aCommand) { + case GETABC: + aSerial->print(F("getABC ")); + break; + case RAWCO2: + aSerial->print(F("getRawCO2")); + break; + case CO2UNLIM: + aSerial->print(F("getCO2Unl")); + break; + case CO2LIM: + aSerial->print(F("getCO2 ")); + break; + case GETRANGE: + aSerial->print(F("getRange")); + break; + case GETCALPPM: + aSerial->print(F("getMinPPM")); + break; + case GETFIRMWARE: + aSerial->print(F("getFW ")); + break; + default: + break; + } + +} + +bool MHZ19::readCO2AndTemperature() { + if (processCommand(CO2LIM)) { // 0x86 + return true; + } + this->CO2 = ReceivedResponse[2] << 8 | ReceivedResponse[3]; + this->Temperature = ReceivedResponse[4] - TEMP_ADJUST; + this->ABCCounter = ReceivedResponse[6]; + return false; +} + +/** + * Fills TemperatureFloat, CO2Unmasked and MinimumLightADC + * @return true if error happened during response receiving + */ +bool MHZ19::readCO2AndTemperatureRaw() { + if (processCommand(CO2UNLIM)) { // 0x85 + return true; + } + this->TemperatureFloat = (ReceivedResponse[2] << 8 | ReceivedResponse[3]) / 100.0; + this->CO2Unmasked = ReceivedResponse[4] << 8 | ReceivedResponse[5]; + this->MinimumLightADC = ReceivedResponse[6] << 8 | ReceivedResponse[7]; // Observed 1013 to 1044 + return false; +} + +/** + * Fills CO2RawADC + * @return true if error happened during response receiving + */ +bool MHZ19::readCO2Raw() { + if (processCommand(RAWCO2)) { // 0x84 + return true; + } + this->CO2RawADC = ReceivedResponse[2] << 8 | ReceivedResponse[3]; + this->Unknown1 = ReceivedResponse[4] << 8 | ReceivedResponse[5]; // Observed 0xA3C8 to 0xA438 + this->Unknown2 = ReceivedResponse[6] << 8 | ReceivedResponse[7]; // Observed 0x0B57 to 0x0BEB + return false; +} + +/** + * fills VersionString + * @return true if error happened during response receiving + */ +bool MHZ19::readVersion() { + if (processCommand(GETFIRMWARE)) { + return true; + } + for (uint_fast8_t i = 0; i < 4; i++) { + this->VersionString[i] = char(this->ReceivedResponse[i + 2]); + } + this->VersionMajor = this->ReceivedResponse[3] - '0'; + return false; +} + +/** + * fills SensorRange + * @return true if error happened during response receiving + */ +bool MHZ19::readRange() { + if (processCommand(GETRANGE)) { + return true; + } + this->SensorRange = ReceivedResponse[4] << 8 | ReceivedResponse[5]; + return false; +} + +// TODO or better getBackgroundCO2 ??? +bool MHZ19::readBackgroundCO2() { + if (processCommand(GETCALPPM)) { + return true; + } + this->BackgroundCO2 = ReceivedResponse[4] << 8 | ReceivedResponse[5]; + return false; +} + +/* + * @return true if ABC enabled + */ +bool MHZ19::readABC() { + if (processCommand(GETABC)) { // 0x7D + return true; + } + this->AutoBaselineCorrectionEnabled = this->ReceivedResponse[7]; // (1 - enabled, 0 - disabled) + return false; +} + +void MHZ19::setAutoCalibration(bool aSwitchOn) { + if (aSwitchOn) { + this->CommandToSend[3] = 0xA0; // set parameter to command buffer + } + processCommand(ABC); // 0x79 + this->CommandToSend[3] = 0x00; // clear parameter from command buffer } /*########################-Set Functions-##########################*/ -void MHZ19::setRange(int range) -{ - if(range < 500 || range > 20000) - { - #if defined (ESP32) && (MHZ19_ERRORS) +void MHZ19::setRange(int range) { + if (range < 500 || range > 20000) { +#if defined (ESP32) && (MHZ19_ERRORS) ESP_LOGE(TAG_MHZ19, "Invalid Range value (500 - 20000)"); - #elif MHZ19_ERRORS +#elif MHZ19_ERRORS Serial.println("!ERROR: Invalid Range value (500 - 20000)"); - #endif +#endif return; } @@ -69,65 +275,53 @@ void MHZ19::setRange(int range) provisioning(RANGE, range); } -void MHZ19::zeroSpan(int span) -{ - if (span > 10000) - { - #if defined (ESP32) && (MHZ19_ERRORS) - ESP_LOGE(TAG_MHZ19, "Invalid Span value (0 - 10000)"); - #elif MHZ19_ERRORS +void MHZ19::zeroSpan(int span) { + if (span > 10000) { +#if defined (ESP32) && (MHZ19_ERRORS) + ESP_LOGE(TAG_MHZ19, "Invalid Span value (0 - 10000)"); +#elif MHZ19_ERRORS Serial.println("!ERROR: Invalid Span value (0 - 10000)"); - #endif - } - else +#endif + } else provisioning(SPANCAL, span); return; } -void MHZ19::setFilter(bool isON, bool isCleared) -{ +void MHZ19::setFilter(bool isON, bool isCleared) { this->storage.settings.filterMode = isON; this->storage.settings.filterCleared = isCleared; } /*########################-Get Functions-##########################*/ -int MHZ19::getCO2(bool isunLimited, bool force) -{ - if (force == true) - { - if(isunLimited) +unsigned int MHZ19::getCO2(bool isunLimited, bool force) { + if (force == true) { + if (isunLimited) { provisioning(CO2UNLIM); - else + } else { provisioning(CO2LIM); - } - - if (this->errorCode == RESULT_OK || force == false) - { - if (!this->storage.settings.filterMode) - { - unsigned int validRead = 0; + } + } - if(isunLimited) - validRead = makeInt(this->storage.responses.CO2UNLIM[4], this->storage.responses.CO2UNLIM[5]); - else - validRead = makeInt(this->storage.responses.CO2LIM[2], this->storage.responses.CO2LIM[3]); + if (this->errorCode == RESULT_OK || force == false) { + if (!this->storage.settings.filterMode) { + unsigned int validRead; - if(validRead > 32767) - validRead = 32767; // Set to maximum to stop negative values being return due to overflow + if (isunLimited) { + this->CO2Unmasked = storage.responses.CO2UNLIM[4] << 8 | storage.responses.CO2UNLIM[5]; + } else { + this->CO2 = storage.responses.CO2LIM[2] << 8 | storage.responses.CO2LIM[3]; + } + return validRead; - else - return validRead; - } - else - { - /* FILTER BEGIN ----------------------------------------------------------- */ + } else { + /* FILTER BEGIN ----------------------------------------------------------- */ unsigned int checkVal[2]; bool trigFilter = false; // Filter must call the opposest unlimited/limited command to work - if(!isunLimited) + if (!isunLimited) provisioning(CO2UNLIM); else provisioning(CO2LIM); @@ -139,36 +333,29 @@ int MHZ19::getCO2(bool isunLimited, bool force) // shows an abnormal value, reset duration can be found. Limited CO2 ppm returns to "normal" // after reset. - if(this->storage.settings.filterCleared) - { - if(checkVal[0] > 32767 || checkVal[1] > 32767 || (((checkVal[0] - checkVal[1]) >= 10) && checkVal[1] == 410)) - { + if (this->storage.settings.filterCleared) { + if (checkVal[0] > 32767 || checkVal[1] > 32767 || (((checkVal[0] - checkVal[1]) >= 10) && checkVal[1] == 410)) { this->errorCode = RESULT_FILTER; return 0; } - } - else - { - if(checkVal[0] > 32767) - { + } else { + if (checkVal[0] > 32767) { checkVal[0] = 32767; trigFilter = true; } - if(checkVal[1] > 32767) - { + if (checkVal[1] > 32767) { checkVal[1] = 32767; trigFilter = true; } - if(((checkVal[0] - checkVal[1]) >= 10) && checkVal[1] == 410) + if (((checkVal[0] - checkVal[1]) >= 10) && checkVal[1] == 410) trigFilter = true; - if(trigFilter) - { + if (trigFilter) { this->errorCode = RESULT_FILTER; } } - if(isunLimited) + if (isunLimited) return checkVal[0]; else return checkVal[1]; @@ -178,26 +365,22 @@ int MHZ19::getCO2(bool isunLimited, bool force) return 0; } -unsigned int MHZ19::getCO2Raw(bool force) -{ - if (force == true) +unsigned int MHZ19::getCO2Raw(bool force) { + if (force == true) { provisioning(RAWCO2); - - if (this->errorCode == RESULT_OK || force == false) + } + if (this->errorCode == RESULT_OK || force == false) { return makeInt(this->storage.responses.RAW[2], this->storage.responses.RAW[3]); - - else - return 0; + } + return 0; } -float MHZ19::getTransmittance(bool force) -{ +float MHZ19::getTransmittance(bool force) { if (force == true) provisioning(RAWCO2); - if (this->errorCode == RESULT_OK || force == false) - { - float calc = (float)makeInt((this->storage.responses.RAW[2]), this->storage.responses.RAW[3]); + if (this->errorCode == RESULT_OK || force == false) { + float calc = (float) makeInt((this->storage.responses.RAW[2]), this->storage.responses.RAW[3]); return (calc * 100 / 35000); // (calc * to percent / x(raw) zero) } @@ -206,43 +389,37 @@ float MHZ19::getTransmittance(bool force) return 0; } -float MHZ19::getTemperature(bool force) -{ - if(this->storage.settings.fw_ver < 5) - { +float MHZ19::getTemperature(bool force) { + if (this->storage.settings.fw_ver < 5) { if (force == true) provisioning(CO2LIM); if (this->errorCode == RESULT_OK || force == false) return (this->storage.responses.CO2LIM[4] - TEMP_ADJUST); - } - else - { + } else { if (force == true) provisioning(CO2UNLIM); if (this->errorCode == RESULT_OK) - return (float)(((int)this->storage.responses.CO2UNLIM[2] << 8) | this->storage.responses.CO2UNLIM[3]) / 100; + return (float) (((int) this->storage.responses.CO2UNLIM[2] << 8) | this->storage.responses.CO2UNLIM[3]) / 100; } return -273.15; } -int MHZ19::getRange() -{ - /* check get range was recieved */ +unsigned int MHZ19::getRange() { + /* check get range was received */ provisioning(GETRANGE); - if (this->errorCode == RESULT_OK) + if (this->errorCode == RESULT_OK) { /* convert MH-Z19 memory value and return */ - return (int)makeInt(this->storage.responses.STAT[4], this->storage.responses.STAT[5]); + return (int) makeInt(this->storage.responses.STAT[4], this->storage.responses.STAT[5]); + } - else - return 0; + return 0; } -byte MHZ19::getAccuracy(bool force) -{ +byte MHZ19::getAccuracy(bool force) { if (force == true) provisioning(CO2LIM); @@ -252,22 +429,19 @@ byte MHZ19::getAccuracy(bool force) else return 0; - //GetRange byte 7 +//GetRange byte 7 } -byte MHZ19::getPWMStatus() -{ - //255 156 byte 4; +byte MHZ19::getPWMStatus() { +//255 156 byte 4; return 0; } -void MHZ19::getVersion(char rVersion[]) -{ +void MHZ19::getVersion(char rVersion[]) { provisioning(GETFIRMWARE); if (this->errorCode == RESULT_OK) - for (byte i = 0; i < 4; i++) - { + for (byte i = 0; i < 4; i++) { rVersion[i] = char(this->storage.responses.STAT[i + 2]); } @@ -275,34 +449,29 @@ void MHZ19::getVersion(char rVersion[]) memset(rVersion, 0, 4); } -int MHZ19::getBackgroundCO2() -{ - provisioning(GETCALPPM); +unsigned int MHZ19::getBackgroundCO2() { + processCommand(GETCALPPM); - if (this->errorCode == RESULT_OK) - return (int)makeInt(this->storage.responses.STAT[4], this->storage.responses.STAT[5]); - - else - return 0; + if (this->errorCode == RESULT_OK) { + return (int) makeInt(this->ReceivedResponse[4], this->ReceivedResponse[5]); + } + return 0; } -byte MHZ19::getTempAdjustment() -{ +byte MHZ19::getTempAdjustment() { provisioning(GETEMPCAL); - /* 40 is returned here, however this library uses TEMP_ADJUST + /* Constant 40 is returned here, however this library uses TEMP_ADJUST when using temperature function as it appears inaccurate, - */ + */ - if (this->errorCode == RESULT_OK) + if (this->errorCode == RESULT_OK) { return (this->storage.responses.STAT[3]); - - else - return 0; + } + return 0; } -byte MHZ19::getLastResponse(byte bytenum) -{ +byte MHZ19::getLastResponse(byte bytenum) { provisioning(GETLASTRESP); if (this->errorCode == RESULT_OK) @@ -312,22 +481,9 @@ byte MHZ19::getLastResponse(byte bytenum) return 0; } -bool MHZ19::getABC() -{ - /* check get ABC logic status (1 - enabled, 0 - disabled) */ - provisioning(GETABC); - - if (this->errorCode == RESULT_OK) - /* convert MH-Z19 memory value and return */ - return this->storage.responses.STAT[7]; - else - return 1; -} - /*######################-Utility Functions-########################*/ -void MHZ19::verify() -{ +void MHZ19::verify() { unsigned long timeStamp = millis(); /* construct common command (133) */ @@ -335,15 +491,13 @@ void MHZ19::verify() write(this->storage.constructedCommand); - while (read(this->storage.responses.CO2UNLIM, CO2UNLIM) != RESULT_OK) - { - if (millis() - timeStamp >= TIMEOUT_PERIOD) - { - #if defined (ESP32) && (MHZ19_ERRORS) - ESP_LOGE(TAG_MHZ19, "Failed to verify connection(1) to sensor."); - #elif MHZ19_ERRORS + while (read(this->storage.responses.CO2UNLIM) != RESULT_OK) { + if (millis() - timeStamp >= MHZ19_RESPONSE_TIMEOUT_MILLIS) { +#if defined (ESP32) && (MHZ19_ERRORS) + ESP_LOGE(TAG_MHZ19, "Failed to verify connection(1) to sensor."); +#elif MHZ19_ERRORS Serial.println("!ERROR: Failed to verify connection(1) to sensor."); - #endif +#endif return; } @@ -356,30 +510,26 @@ void MHZ19::verify() /* update timeStamp for next comms iteration */ timeStamp = millis(); - while (read(this->storage.responses.STAT, GETLASTRESP) != RESULT_OK) - { - if (millis() - timeStamp >= TIMEOUT_PERIOD) - { - #if defined (ESP32) && (MHZ19_ERRORS) - ESP_LOGE(TAG_MHZ19, "Failed to verify connection(2) to sensor."); - #elif MHZ19_ERRORS + while (read(this->storage.responses.STAT) != RESULT_OK) { + if (millis() - timeStamp >= MHZ19_RESPONSE_TIMEOUT_MILLIS) { +#if defined (ESP32) && (MHZ19_ERRORS) + ESP_LOGE(TAG_MHZ19, "Failed to verify connection(2) to sensor."); +#elif MHZ19_ERRORS Serial.println("!ERROR: Failed to verify connection(2) to sensor."); - #endif +#endif return; } } /* compare CO2 & temp bytes, command(133), against last response bytes, command (162)*/ - for (byte i = 2; i < 6; i++) - { - if (this->storage.responses.CO2UNLIM[i] != this->storage.responses.STAT[i]) - { - #if defined (ESP32) && (MHZ19_ERRORS) - ESP_LOGE(TAG_MHZ19, "Last response is not as expected, verification failed."); - #elif MHZ19_ERRORS + for (byte i = 2; i < 6; i++) { + if (this->storage.responses.CO2UNLIM[i] != this->storage.responses.STAT[i]) { +#if defined (ESP32) && (MHZ19_ERRORS) + ESP_LOGE(TAG_MHZ19, "Last response is not as expected, verification failed."); +#elif MHZ19_ERRORS Serial.println("!ERROR: Last response is not as expected, verification failed."); - #endif +#endif return; } @@ -387,20 +537,17 @@ void MHZ19::verify() return; } -void MHZ19::autoCalibration(bool isON, byte ABCPeriod) -{ +void MHZ19::autoCalibration(bool isON, byte ABCPeriod) { /* If ABC is ON */ - if(isON) - { + if (isON) { /* If a period was defined */ - if (ABCPeriod) - { + if (ABCPeriod) { /* Catch values out of range */ - if(ABCPeriod >= 24) + if (ABCPeriod >= 24) ABCPeriod = 24; /* Convert to bytes */ - ABCPeriod *= 6.7; + ABCPeriod *= 6.7; } /* If no period was defined (for safety, even though default argument is given)*/ else @@ -416,26 +563,22 @@ void MHZ19::autoCalibration(bool isON, byte ABCPeriod) provisioning(ABC, ABCPeriod); } -void MHZ19::calibrate() -{ +void MHZ19::calibrate() { provisioning(ZEROCAL); } -void MHZ19::recoveryReset() -{ +void MHZ19::recoveryReset() { provisioning(RECOVER); } -void MHZ19::printCommunication(bool isDec, bool isPrintComm) -{ +void MHZ19::printCommunication(bool isDec, bool isPrintComm) { this->storage.settings._isDec = isDec; this->storage.settings.printcomm = isPrintComm; } -/*######################-Inernal Functions-########################*/ +/*######################-Internal Functions-########################*/ -void MHZ19::provisioning(Command_Type commandtype, int inData) -{ +void MHZ19::provisioning(MHZ19_command_t commandtype, int inData) { /* construct command */ constructCommand(commandtype, inData); @@ -443,14 +586,13 @@ void MHZ19::provisioning(Command_Type commandtype, int inData) write(this->storage.constructedCommand); /*return response */ - handleResponse(commandtype); + handleResponse(); /* Check if ABC_OFF needs to run */ ABCCheck(); } -void MHZ19::constructCommand(Command_Type commandtype, int inData) -{ +void MHZ19::constructCommand(MHZ19_command_t commandtype, int inData) { /* values for conversions */ byte High; byte Low; @@ -463,16 +605,15 @@ void MHZ19::constructCommand(Command_Type commandtype, int inData) memset(this->storage.constructedCommand, 0, MHZ19_DATA_LEN); /* set address to 'any' */ - asemblecommand[0] = 255; ///(0xFF) 255/FF means 'any' address (where the sensor is located) + asemblecommand[0] = 0xFF; // Start byte /* set register */ - asemblecommand[1] = 1; //(0x01) arbitrary byte number + asemblecommand[1] = 1; // Sensor # /* set command */ - asemblecommand[2] = Commands[commandtype]; // assign command value + asemblecommand[2] = commandtype; // Command - switch (commandtype) - { + switch (commandtype) { case RECOVER: break; case ABC: @@ -520,8 +661,7 @@ void MHZ19::constructCommand(Command_Type commandtype, int inData) memcpy(this->storage.constructedCommand, asemblecommand, MHZ19_DATA_LEN); } -void MHZ19::write(byte toSend[]) -{ +void MHZ19::write(byte toSend[]) { /* for print communications */ if (this->storage.settings.printcomm == true) printstream(toSend, true, this->errorCode); @@ -533,8 +673,7 @@ void MHZ19::write(byte toSend[]) mySerial->flush(); } -byte MHZ19::read(byte inBytes[MHZ19_DATA_LEN], Command_Type commandnumber) -{ +byte MHZ19::read(byte inBytes[MHZ19_DATA_LEN]) { /* loop escape */ unsigned long timeStamp = millis(); @@ -545,15 +684,13 @@ byte MHZ19::read(byte inBytes[MHZ19_DATA_LEN], Command_Type commandnumber) this->errorCode = RESULT_NULL; /* wait until we have exactly the 9 bytes reply (certain controllers call read() too fast) */ - while (mySerial->available() < MHZ19_DATA_LEN) - { - if (millis() - timeStamp >= TIMEOUT_PERIOD) - { - #if defined (ESP32) && (MHZ19_ERRORS) - ESP_LOGW(TAG_MHZ19, "Timed out waiting for response"); - #elif MHZ19_ERRORS + while (mySerial->available() < MHZ19_DATA_LEN) { + if (millis() - timeStamp >= MHZ19_RESPONSE_TIMEOUT_MILLIS) { +#if defined (ESP32) && (MHZ19_ERRORS) + ESP_LOGW(TAG_MHZ19, "Timed out waiting for response"); +#elif MHZ19_ERRORS Serial.println("!Error: Timed out waiting for response"); - #endif +#endif this->errorCode = RESULT_TIMEOUT; @@ -575,12 +712,11 @@ byte MHZ19::read(byte inBytes[MHZ19_DATA_LEN], Command_Type commandnumber) /* CRC error will not override match error */ if (inBytes[8] != crc) - this->errorCode = RESULT_CRC; + this->errorCode = RESULT_CHECKSUM; /* construct error code */ - if (inBytes[0] != this->storage.constructedCommand[0] || inBytes[1] != this->storage.constructedCommand[2]) - { - /* clear rx buffer for desync correction */ + if (inBytes[0] != this->storage.constructedCommand[0] || inBytes[1] != this->storage.constructedCommand[2]) { + /* clear rx buffer for desync correction */ cleanUp(mySerial->available()); this->errorCode = RESULT_MATCH; } @@ -596,17 +732,15 @@ byte MHZ19::read(byte inBytes[MHZ19_DATA_LEN], Command_Type commandnumber) return this->errorCode; } -void MHZ19::cleanUp(uint8_t cnt) -{ - for(uint8_t x = 0; x < cnt; x++) - { +void MHZ19::cleanUp(uint8_t cnt) { + for (uint8_t x = 0; x < cnt; x++) { #if (MHZ19_ERRORS) // to avoid nasty ESP32 compiler error. uint8_t eject = mySerial->read(); #else - mySerial->read(); + mySerial->read(); #endif #if defined (ESP32) && (MHZ19_ERRORS) - ESP_LOGW(TAG_MHZ19, "Clearing Byte: %d", eject); + ESP_LOGW(TAG_MHZ19, "Clearing Byte: %d", eject); #elif MHZ19_ERRORS Serial.print("!Warning: Clearing Byte: "); Serial.println(eject); @@ -614,39 +748,29 @@ void MHZ19::cleanUp(uint8_t cnt) } } -void MHZ19::handleResponse(Command_Type commandtype) -{ - if (this->storage.constructedCommand[2] == Commands[RAWCO2]) // compare commands byte - read(this->storage.responses.RAW, commandtype); // returns error number, passes back response and inputs command - - else if (this->storage.constructedCommand[2] == Commands[CO2UNLIM]) - read(this->storage.responses.CO2UNLIM, commandtype); - - else if (this->storage.constructedCommand[2] == Commands[CO2LIM]) - read(this->storage.responses.CO2LIM, commandtype); - - else - read(this->storage.responses.STAT, commandtype); +void MHZ19::handleResponse() { + if (this->storage.constructedCommand[2] == RAWCO2) { // compare commands byte + read(this->storage.responses.RAW); // returns error number, passes back response and inputs command + } else if (this->storage.constructedCommand[2] == CO2UNLIM) { + read(this->storage.responses.CO2UNLIM); + } else if (this->storage.constructedCommand[2] == CO2LIM) { + read(this->storage.responses.CO2LIM); + } else { + read(this->storage.responses.STAT); + } } -void MHZ19::printstream(byte inBytes[MHZ19_DATA_LEN], bool isSent, byte pserrorCode) -{ - if (pserrorCode != RESULT_OK && isSent == false) - { +void MHZ19::printstream(byte inBytes[MHZ19_DATA_LEN], bool isSent, byte pserrorCode) { + if (pserrorCode != RESULT_OK && isSent == false) { Serial.print("Received >> "); - if (this->storage.settings._isDec) - { + if (this->storage.settings._isDec) { Serial.print("DEC: "); - for (uint8_t i = 0; i < MHZ19_DATA_LEN; i++) - { + for (uint8_t i = 0; i < MHZ19_DATA_LEN; i++) { Serial.print(inBytes[i]); Serial.print(" "); } - } - else - { - for (uint8_t i = 0; i < MHZ19_DATA_LEN; i++) - { + } else { + for (uint8_t i = 0; i < MHZ19_DATA_LEN; i++) { Serial.print("0x"); if (inBytes[i] < 16) Serial.print("0"); @@ -658,23 +782,17 @@ void MHZ19::printstream(byte inBytes[MHZ19_DATA_LEN], bool isSent, byte pserrorC Serial.println(pserrorCode); } - else - { + else { isSent ? Serial.print("Sent << ") : Serial.print("Received >> "); - if (this->storage.settings._isDec) - { + if (this->storage.settings._isDec) { Serial.print("DEC: "); - for (uint8_t i = 0; i < MHZ19_DATA_LEN; i++) - { + for (uint8_t i = 0; i < MHZ19_DATA_LEN; i++) { Serial.print(inBytes[i]); Serial.print(" "); } - } - else - { - for (uint8_t i = 0; i < MHZ19_DATA_LEN; i++) - { + } else { + for (uint8_t i = 0; i < MHZ19_DATA_LEN; i++) { Serial.print("0x"); if (inBytes[i] < 16) Serial.print("0"); @@ -686,13 +804,11 @@ void MHZ19::printstream(byte inBytes[MHZ19_DATA_LEN], bool isSent, byte pserrorC } } -byte MHZ19::getCRC(byte inBytes[]) -{ +byte MHZ19::getCRC(byte inBytes[]) { /* as shown in datasheet */ byte x = 0, crc = 0; - for (x = 1; x < 8; x++) - { + for (x = 1; x < 8; x++) { crc += inBytes[x]; } @@ -702,30 +818,26 @@ byte MHZ19::getCRC(byte inBytes[]) return crc; } -void MHZ19::ABCCheck() -{ - /* check timer interval if dynamic hours have passed and if ABC_OFF was set to true */ - if (((millis() - ABCRepeatTimer) >= 4.32e7) && (this->storage.settings.ABCRepeat == true)) - { - /* update timer inerval */ - ABCRepeatTimer = millis(); +void MHZ19::ABCCheck() { + /* check timer interval if dynamic hours have passed and if ABC_OFF was set to true */ + if (((millis() - ABCRepeatTimer) >= 4.32e7) && (this->storage.settings.ABCRepeat == true)) { + /* update timer inerval */ + ABCRepeatTimer = millis(); - /* construct command to skip next ABC cycle */ - provisioning(ABC, MHZ19_ABC_PERIOD_OFF); - } + /* construct command to skip next ABC cycle */ + provisioning(ABC, MHZ19_ABC_PERIOD_OFF); + } } -void MHZ19::makeByte(int inInt, byte *high, byte *low) -{ - *high = (byte)(inInt / 256); - *low = (byte)(inInt % 256); +void MHZ19::makeByte(int inInt, byte *high, byte *low) { + *high = (byte) (inInt / 256); + *low = (byte) (inInt % 256); return; } -unsigned int MHZ19::makeInt(byte high, byte low) -{ - unsigned int calc = ((unsigned int)high * 256) + (unsigned int)low; +unsigned int MHZ19::makeInt(byte high, byte low) { + unsigned int calc = ((unsigned int) high * 256) + (unsigned int) low; return calc; } diff --git a/src/MHZ19.h b/src/MHZ19.h index e437e1e..0ebce77 100644 --- a/src/MHZ19.h +++ b/src/MHZ19.h @@ -10,194 +10,224 @@ #define TAG_MHZ19 "MH-Z19" #endif -#define MHZ19_ERRORS 1 // Set to 0 to disable error prints -#define TEMP_ADJUST 40 // This is the value used to adjust the temperature. -#define TIMEOUT_PERIOD 500 // Time out period for response (ms) -#define DEFAULT_RANGE 2000 // For range function (sensor works best in this range) -#define MHZ19_DATA_LEN 9 // Data protocol length +#define MHZ19_ERRORS 1 // Set to 0 to disable error prints +#define TEMP_ADJUST 40 // This is the value used to adjust the temperature. +#define MHZ19_RESPONSE_TIMEOUT_MILLIS 500 // Time out period for response (ms) +#define DEFAULT_RANGE 2000 // For range function (sensor works best in this range) +#define MHZ19_DATA_LEN 9 // Data protocol length // Command bytes -------------------------- // #define MHZ19_ABC_PERIOD_OFF 0x00 #define MHZ19_ABC_PERIOD_DEF 0xA0 /* enum alias for error code definitions */ -enum ERRORCODE -{ - RESULT_NULL = 0, - RESULT_OK = 1, - RESULT_TIMEOUT = 2, - RESULT_MATCH = 3, - RESULT_CRC = 4, - RESULT_FILTER = 5 +enum ERRORCODE { + RESULT_NULL = 0, RESULT_OK = 1, RESULT_TIMEOUT = 2, RESULT_MATCH = 3, RESULT_CHECKSUM = 4, RESULT_FILTER = 5 }; -class MHZ19 -{ - public: - /*###########################-Variables-##########################*/ +class MHZ19 { +public: + /*###########################-Variables-##########################*/ - /* Holds last received error code from recieveResponse() */ - byte errorCode; + /* enum for command types */ + typedef enum COMMAND_TYPE { + RECOVER = 0x78, // Recovery Reset - Changes operation mode and performs MCU reset + ABC = 0x79, // Turns ABC (Automatic Baseline Correction) logic on or off (b[3] == 0xA0 - on, 0x00 - off) + GETABC = 0x7D, // Get ABC logic status (1 - enabled, 0 - disabled) + RAWCO2 = 0x84, // Raw CO2 ADC value + CO2UNLIM = 0x85, // Smoothed temperature ADC value, CO2 level + CO2LIM = 0x86, // CO2 masked at 500 in the first minute, Temperature integer + ZEROCAL = 0x87, // Zero Calibration + SPANCAL = 0x88, // Span Calibration + RANGE = 0x99, // Sets sensor range. Note that parameter bytes differ from those in the datasheet. + GETRANGE = 0x9B, // Get Range + GETCALPPM = 0x9C, // Get Background CO2 / lowest ppm value to show + GETFIRMWARE = 0xA0, // Get Firmware Version + GETLASTRESP = 0xA2, // Get Last Response + GETEMPCAL = 0xA3 // Get temperature offset - returns constant 40 + } MHZ19_command_t; - /* for keeping track of the ABC run interval */ - unsigned long ABCRepeatTimer; + /* pointer for Stream class to accept reference for hardware and software ports */ + Stream *mySerial; - /*#####################-Initiation Functions-#####################*/ + /* Holds last received error code from recieveResponse() */ + byte errorCode; - /* essential begin */ - void begin(Stream &stream); + /* for keeping track of the ABC run interval */ + unsigned long ABCRepeatTimer; + char VersionString[5]; + uint8_t VersionMajor; - /*########################-Set Functions-##########################*/ + uint16_t CO2RawADC; + uint16_t Unknown1; + uint16_t Unknown2; - /* Sets Range to desired value*/ - void setRange(int range = 2000); + uint16_t CO2; // Is displayed / masked as 500 for the first minute. Values are clipped between 405 and 5000. + int8_t Temperature; + uint8_t ABCCounter; // Is incremented by MHZ19 every 10 minutes - /* Sets Span to desired value below 10,000*/ - void zeroSpan(int span = 2000); + float TemperatureFloat; + uint16_t CO2Unmasked; // Is displayed even at first minute. Values are also clipped between 405 and 5000. + uint16_t MinimumLightADC; - /* Sets "filter mode" to ON or OFF & mode type (see example) */ - void setFilter(bool isON = true, bool isCleared = true); + uint16_t SensorRange; + bool AutoBaselineCorrectionEnabled; + uint16_t BackgroundCO2; + + + uint8_t CommandToSend[MHZ19_DATA_LEN]; // Array for commands to be sent + uint8_t ReceivedResponse[MHZ19_DATA_LEN]; // Array for response + + bool processCommand(MHZ19_command_t aCommand, bool aDoNotWaitForResponse = false); + bool readResponse(); + bool readCO2AndTemperature(); + bool readCO2AndTemperatureRaw(); + bool readVersion(); + bool readABC(); // Reads ABC-Status using command 125 / 0x7D + bool readRange(); + bool readBackgroundCO2(); + bool readCO2Raw(); +// bool readRange(); + + + + void setAutoCalibration(bool aSwitchOn); - /*########################-Get Functions-##########################*/ + void printErrorCode(Print *aSerial); + void printCommand(MHZ19_command_t aCommand, Print *aSerial); + + /*#####################-Initiation Functions-#####################*/ + + /* essential begin */ + void begin(Stream &aStream); + + /*########################-Set Functions-##########################*/ + + /* Sets Range to desired value*/ + void setRange(int range = 2000); + + /* Sets Span to desired value below 10,000*/ + void zeroSpan(int span = 2000); + + /* Sets "filter mode" to ON or OFF & mode type (see example) */ + void setFilter(bool isON = true, bool isCleared = true); - /* request CO2 values, 2 types of CO2 can be returned, isLimted = true (command 134) and is Limited = false (command 133) */ - int getCO2(bool isunLimited = true, bool force = true); + /*########################-Get Functions-##########################*/ - /* returns the "raw" CO2 value of unknown units */ - unsigned int getCO2Raw(bool force = true); + /* request CO2 values, 2 types of CO2 can be returned, isLimted = true (command 134) and is Limited = false (command 133) */ + unsigned int getCO2(bool isunLimited = true, bool force = true); - /* returns Raw CO2 value as a % of transmittance */ //<--- needs work to understand - float getTransmittance(bool force = true); + /* returns the "raw" CO2 value of unknown units */ + unsigned int getCO2Raw(bool force = true); - /* returns temperature using command 133 or 134 */ - float getTemperature(bool force = true); + /* returns Raw CO2 value as a % of transmittance */ //<--- needs work to understand + float getTransmittance(bool force = true); - /* reads range using command 153 */ - int getRange(); + /* returns temperature using command 133 or 134 */ + float getTemperature(bool force = true); - /* reads ABC-Status using command 125 / 0x7D */ - bool getABC(); + /* reads range using command 153 */ + unsigned int getRange(); - /* Returns accuracy value if available */ - byte getAccuracy(bool force = true); - /* not yet implemented */ - byte getPWMStatus(); - /* returns MH-Z19 version using command 160, to the entered array */ - void getVersion(char rVersion[]); + /* Returns accuracy value if available */ + byte getAccuracy(bool force = true); - /* returns background CO2 used by sensor using command 156 */ - int getBackgroundCO2(); + /* not yet implemented */ + byte getPWMStatus(); - /* returns temperature using command 163 (Note: this library deducts -2 when the value is used) */ - byte getTempAdjustment(); + /* returns MH-Z19 version using command 160, to the entered array */ + void getVersion(char rVersion[]); - /* returns last recorded response from device using command 162 */ - byte getLastResponse(byte bytenum); + /* returns background CO2 used by sensor using command 156 */ + unsigned int getBackgroundCO2(); - /*######################-Utility Functions-########################*/ + /* returns temperature using command 163 (Note: this library deducts -2 when the value is used) */ + byte getTempAdjustment(); - /* ensure communication is working (included in begin())*/ - void verify(); + /* returns last recorded response from device using command 162 */ + byte getLastResponse(byte bytenum); - /* disables calibration or sets ABCPeriod */ - void autoCalibration(bool isON = true, byte ABCPeriod = 24); + /*######################-Utility Functions-########################*/ - /* Calibrates "Zero" (Note: Zero refers to 400ppm for this sensor)*/ - void calibrate(); + /* ensure communication is working (included in begin())*/ + void verify(); - /* Calibrate Backwards compatibility */ - void inline calibrateZero(){ calibrate(); }; + /* disables calibration or sets ABCPeriod */ + void autoCalibration(bool isON = true, byte ABCPeriod = 24); - /* requests a reset */ - void recoveryReset(); + /* Calibrates "Zero" (Note: Zero refers to 400ppm for this sensor)*/ + void calibrate(); - /* use to show communication between MHZ19 and Device */ - void printCommunication(bool isDec = true, bool isPrintComm = true); + /* Calibrate Backwards compatibility */ + void inline calibrateZero() { + calibrate(); + } - private: - /*###########################-Variables-##########################*/ + /* requests a reset */ + void recoveryReset(); - /* pointer for Stream class to accept reference for hardware and software ports */ - Stream* mySerial; + /* use to show communication between MHZ19 and Device */ + void printCommunication(bool isDec = true, bool isPrintComm = true); - /* alias for command types */ - typedef enum COMMAND_TYPE - { - RECOVER = 0, // 0 Recovery Reset - ABC = 1, // 1 ABC (Automatic Baseline Correction) Mode ON/OFF - GETABC = 2, // 2 Get ABC - Status 0x79 - RAWCO2 = 3, // 3 Raw CO2 - CO2UNLIM = 4, // 4 Temperature for unsigned, CO2 Unlimited - CO2LIM = 5, // 5 Temperature for signed, CO2 limited - ZEROCAL = 6, // 6 Zero Calibration - SPANCAL = 7, // 7 Span Calibration - RANGE = 8, // 8 Range - GETRANGE = 9, // 9 Get Range - GETCALPPM = 10, // 10 Get Background CO2 - GETFIRMWARE = 11, // 11 Get Firmware Version - GETLASTRESP = 12, // 12 Get Last Response - GETEMPCAL = 13 // 13 Get Temperature Calibration - } Command_Type; +private: + /*###########################-Variables-##########################*/ - /* Memory Pool */ - struct mempool - { - struct config - { - bool ABCRepeat = false; // A flag which represents whether auto calibration ABC period was checked - bool filterMode = false; // Flag set by setFilter() to signify is "filter mode" was made active - bool filterCleared = true; // Additional flag set by setFilter() to store which mode was selected - bool printcomm = false; // Communication print options - bool _isDec = true; // Holds preference for communication printing - uint8_t fw_ver = 0; // holds the major version of the firmware - } settings; + /* Memory Pool */ + struct mempool { + struct config { + bool ABCRepeat = false; // A flag which represents whether auto calibration ABC period was checked + bool filterMode = false; // Flag set by setFilter() to signify is "filter mode" was made active + bool filterCleared = true; // Additional flag set by setFilter() to store which mode was selected + bool printcomm = false; // Communication print options + bool _isDec = true; // Holds preference for communication printing + uint8_t fw_ver = 0; // Holds the major version of the firmware + } settings; - byte constructedCommand[MHZ19_DATA_LEN]; // holder for new commands which are to be sent + byte constructedCommand[MHZ19_DATA_LEN]; // holder for new commands which are to be sent - struct indata - { - byte CO2UNLIM[MHZ19_DATA_LEN]; // Holds command 133 response values "CO2 unlimited and temperature for unsigned" - byte CO2LIM[MHZ19_DATA_LEN]; // Holds command 134 response values "CO2 limited and temperature for signed" - byte RAW[MHZ19_DATA_LEN]; // Holds command 132 response values "CO2 Raw" - byte STAT[MHZ19_DATA_LEN]; // Holds other command response values such as range, background CO2 etc - } responses; + struct indata { + byte CO2UNLIM[MHZ19_DATA_LEN]; // Holds command 133 response values "CO2 unlimited and temperature for unsigned" + byte CO2LIM[MHZ19_DATA_LEN]; // Holds command 134 response values "CO2 limited and temperature for signed" + byte RAW[MHZ19_DATA_LEN]; // Holds command 132 response values "CO2 Raw" + byte STAT[MHZ19_DATA_LEN]; // Holds other command response values such as range, background CO2 etc + } responses; - } storage; + } storage; - /*######################-Internal Functions-########################*/ + /*######################-Internal Functions-########################*/ - /* Coordinates sending, constructing and receiving commands */ - void provisioning(Command_Type commandtype, int inData = 0); + /* Coordinates sending, constructing and receiving commands */ + void provisioning(MHZ19_command_t commandtype, int inData = 0); - /* Constructs commands using command array and entered values */ - void constructCommand(Command_Type commandtype, int inData = 0); + /* Constructs commands using command array and entered values */ + void constructCommand(MHZ19_command_t commandtype, int inData = 0); - /* generates a checksum for sending and verifying incoming data */ - byte getCRC(byte inBytes[]); + /* generates a checksum for sending and verifying incoming data */ + byte getCRC(byte inBytes[]); - /* Sends commands to the sensor */ - void write(byte toSend[]); + /* Sends commands to the sensor */ + void write(byte toSend[]); - /* Call retrieveData to retrieve values from the sensor and check return code */ - byte read(byte inBytes[9], Command_Type commandnumber); + /* Call retrieveData to retrieve values from the sensor and check return code */ + byte read(byte inBytes[9]); - /* Assigns response to the correct communication arrays */ - void handleResponse(Command_Type commandtype); + /* Assigns response to the correct communication arrays */ + void handleResponse(); - /* prints sending / receiving messages if enabled */ - void printstream(byte inbytes[9], bool isSent, byte pserrorCode); + /* prints sending / receiving messages if enabled */ + void printstream(byte inbytes[9], bool isSent, byte pserrorCode); - /* Checks whether time elapse for next ABC OFF cycle has occurred */ - void ABCCheck(); + /* Checks whether time elapse for next ABC OFF cycle has occurred */ + void ABCCheck(); - /* converts integers to bytes according to /256 and %256 */ - void makeByte(int inInt, byte *high, byte *low); + /* converts integers to bytes according to /256 and %256 */ + void makeByte(int inInt, byte *high, byte *low); - /* converts bytes to integers according to *256 and + value */ - unsigned int makeInt(byte high, byte low); + /* converts bytes to integers according to *256 and + value */ + unsigned int makeInt(byte high, byte low); - void cleanUp(uint8_t cnt); + void cleanUp(uint8_t cnt); }; #endif