Ze08-CH2O

#include <SoftwareSerial.h>

// Activate debug messages output
#define DEBUG_ON

#define WINSEN_ZE08_CH2O_UART_TX_PIN                            (2)
#define WINSEN_ZE08_CH2O_UART_SPEED                             (9600)
#define WINSEN_ZE08_CH2O_UART_DEFAULT_READ_TIMEOUT              (2500UL)
#define WINSEN_ZE08_CH2O_UART_START_BYTE                        (0xFF)
#define WINSEN_ZE08_CH2O_UART_GAS_NAME                          (0x17)
#define WINSEN_ZE08_CH2O_UART_GAS_UNIT                          (0x04)

// We can use 0xFFFF as error code because sensor's ppb range is 0x00...0x1388 (0...5000)
#define WINSEN_ZE08_CH2O_READ_ERROR_CODE                        (0xFFFF)

// Store all metric's value in the pretty struct
typedef struct {
  uint8_t gasName;
  uint8_t gasUnit;
  uint8_t noDecimalByte;
  uint8_t concentrationHighByte, concentrationLowByte;
  uint8_t fullRangeHighByte, fullRangeLowByte;
  uint8_t checkSum;
} Ze08Ch2OData_t;


uint16_t getZe08Ch2OMetric(const uint8_t _rxPin, const uint8_t _txPin) {
  uint8_t headerDetected = false,
          writePos = 0x00,
          checkSum = 0x00;
  uint16_t rc = WINSEN_ZE08_CH2O_READ_ERROR_CODE;
  uint32_t readStartTime;;

  Ze08Ch2OData_t Ze08Ch2OData;
  uint8_t* ptrRawBuffer = (uint8_t*) &Ze08Ch2OData;

  SoftwareSerial swSerial(_rxPin, _txPin);
  swSerial.begin(WINSEN_ZE08_CH2O_UART_SPEED);
  readStartTime = millis();
  while ((sizeof(Ze08Ch2OData) > writePos) && (WINSEN_ZE08_CH2O_UART_DEFAULT_READ_TIMEOUT > millis() - readStartTime)) {
    if (!swSerial.available()) {
      continue;
    }
    ptrRawBuffer[writePos] = swSerial.read();
#if defined(DEBUG_ON)
    //Serial.print("currentChar (# "); Serial.print(writePos);  Serial.print(") => 0x"); Serial.println((byte) ptrRawBuffer[writePos], HEX);
#endif
    if (!headerDetected) {
      headerDetected = (WINSEN_ZE08_CH2O_UART_START_BYTE == ptrRawBuffer[0x00]);
    } else {
      writePos++;
    }
  }
  // Reading is not finished sucessfully: not all bytes recieved or wrong Gas ID / Unit ID contained in the packet
  if (writePos < sizeof(Ze08Ch2OData) ||
      WINSEN_ZE08_CH2O_UART_GAS_NAME != Ze08Ch2OData.gasName ||
      WINSEN_ZE08_CH2O_UART_GAS_UNIT != Ze08Ch2OData.gasUnit) {
    goto finish;
  }
  // Calculate checksum.
  //Start byte & recieved checksum is not taken in account. The first one is dropped in the read procedure and the second one just will skipped in calculation
  for (uint8_t i = 0x00; i < sizeof(Ze08Ch2OData) - 1; i++) {
    checkSum += ptrRawBuffer[i];
#if defined(DEBUG_ON)
    Serial.print(" 0x"); Serial.print(ptrRawBuffer[i], HEX);
#endif
  }
  checkSum = (~checkSum) + 1;
#if defined(DEBUG_ON)
  Serial.print("\nRecieved / calculated checksum: 0x");
  Serial.print(Ze08Ch2OData.checkSum, HEX); Serial.print(" / 0x"); Serial.println(checkSum, HEX);
#endif
  if (checkSum == Ze08Ch2OData.checkSum) {
#if defined(DEBUG_ON)
    // rc = (Ze08Ch2OData.fullRangeHighByte << 0x08) + Ze08Ch2OData.fullRangeLowByte; Serial.print("Full range (ppb): "); Serial.println(rc);
#endif
    rc = (Ze08Ch2OData.concentrationHighByte << 0x08) + Ze08Ch2OData.concentrationLowByte;
  }

finish:
  swSerial.~SoftwareSerial();
  return rc;
}

void setup() {
  Serial.begin(115200);
  Serial.println("Winsen ZE08-CH2O sensor reading demo (UART, active mode)"); ;
}

void loop() {
  delay(2000);
  uint16_t ch2O = getZe08Ch2OMetric(WINSEN_ZE08_CH2O_UART_TX_PIN, WINSEN_ZE08_CH2O_UART_TX_PIN);
  Serial.print("["); Serial.print(millis()); Serial.print("] CH2O: ");

  if (WINSEN_ZE08_CH2O_READ_ERROR_CODE != ch2O) {
    Serial.print(ch2O); Serial.print(" ppb, \t"); Serial.print(ch2O / 1000.0, 3); Serial.println(" ppm");
  } else {
    Serial.println("read error");
  }
}