/*
 * THIS FILE IS AUTOMATICALLY GENERATED
 *
 * Generator:     sensirion-driver-generator 1.7.0
 * Product:       sen65
 * Model-Version: 1.4.1
 */
/*
 * Copyright (c) 2026, Sensirion AG
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * * Neither the name of Sensirion AG nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "SensirionI2cSen65.h"
#include <Arduino.h>

// make sure that we use the proper definition of NO_ERROR
#ifdef NO_ERROR
#undef NO_ERROR
#endif
#define NO_ERROR 0

static uint8_t communication_buffer[48] = {0};

SensirionI2cSen65::SensirionI2cSen65() {
}

float SensirionI2cSen65::signalDividedBy10Uint16(uint16_t scaledIntegerValue) {
    float dividedBy10Uint16 = 0.0;

    dividedBy10Uint16 = scaledIntegerValue / 10.0;
    return dividedBy10Uint16;
}

float SensirionI2cSen65::signalDividedBy10Int16(int16_t scaledIntegerValue) {
    float dividedBy10Int16 = 0.0;

    dividedBy10Int16 = scaledIntegerValue / 10.0;
    return dividedBy10Int16;
}

float SensirionI2cSen65::signalTemperature(int16_t temperatureRaw) {
    float temperature = 0.0;

    temperature = temperatureRaw / 200.0;
    return temperature;
}

float SensirionI2cSen65::signalHumidity(int16_t humidityRaw) {
    float humidity = 0.0;

    humidity = humidityRaw / 100.0;
    return humidity;
}

int16_t SensirionI2cSen65::readMeasuredValues(
    float& massConcentrationPm1p0, float& massConcentrationPm2p5,
    float& massConcentrationPm4p0, float& massConcentrationPm10p0,
    float& humidity, float& temperature, float& vocIndex, float& noxIndex) {
    uint16_t massConcentrationPm1p0Raw = 0;
    uint16_t massConcentrationPm2p5Raw = 0;
    uint16_t massConcentrationPm4p0Raw = 0;
    uint16_t massConcentrationPm10p0Raw = 0;
    int16_t humidityRaw = 0;
    int16_t temperatureRaw = 0;
    int16_t vocIndexRaw = 0;
    int16_t noxIndexRaw = 0;
    int16_t localError = 0;

    localError = readMeasuredValuesAsIntegers(
        massConcentrationPm1p0Raw, massConcentrationPm2p5Raw,
        massConcentrationPm4p0Raw, massConcentrationPm10p0Raw, humidityRaw,
        temperatureRaw, vocIndexRaw, noxIndexRaw);
    if (localError != NO_ERROR) {
        return localError;
    }
    massConcentrationPm1p0 =
        SensirionI2cSen65::signalDividedBy10Uint16(massConcentrationPm1p0Raw);
    massConcentrationPm2p5 =
        SensirionI2cSen65::signalDividedBy10Uint16(massConcentrationPm2p5Raw);
    massConcentrationPm4p0 =
        SensirionI2cSen65::signalDividedBy10Uint16(massConcentrationPm4p0Raw);
    massConcentrationPm10p0 =
        SensirionI2cSen65::signalDividedBy10Uint16(massConcentrationPm10p0Raw);
    humidity = SensirionI2cSen65::signalHumidity(humidityRaw);
    temperature = SensirionI2cSen65::signalTemperature(temperatureRaw);
    vocIndex = SensirionI2cSen65::signalDividedBy10Int16(vocIndexRaw);
    noxIndex = SensirionI2cSen65::signalDividedBy10Int16(noxIndexRaw);
    return localError;
}

int16_t SensirionI2cSen65::readNumberConcentrationValues(
    float& numberConcentrationPm0p5, float& numberConcentrationPm1p0,
    float& numberConcentrationPm2p5, float& numberConcentrationPm4p0,
    float& numberConcentrationPm10p0) {
    uint16_t numberConcentrationPm0p5Raw = 0;
    uint16_t numberConcentrationPm1p0Raw = 0;
    uint16_t numberConcentrationPm2p5Raw = 0;
    uint16_t numberConcentrationPm4p0Raw = 0;
    uint16_t numberConcentrationPm10p0Raw = 0;
    int16_t localError = 0;

    localError = readNumberConcentrationValuesAsIntegers(
        numberConcentrationPm0p5Raw, numberConcentrationPm1p0Raw,
        numberConcentrationPm2p5Raw, numberConcentrationPm4p0Raw,
        numberConcentrationPm10p0Raw);
    if (localError != NO_ERROR) {
        return localError;
    }
    numberConcentrationPm0p5 =
        SensirionI2cSen65::signalDividedBy10Uint16(numberConcentrationPm0p5Raw);
    numberConcentrationPm1p0 =
        SensirionI2cSen65::signalDividedBy10Uint16(numberConcentrationPm1p0Raw);
    numberConcentrationPm2p5 =
        SensirionI2cSen65::signalDividedBy10Uint16(numberConcentrationPm2p5Raw);
    numberConcentrationPm4p0 =
        SensirionI2cSen65::signalDividedBy10Uint16(numberConcentrationPm4p0Raw);
    numberConcentrationPm10p0 = SensirionI2cSen65::signalDividedBy10Uint16(
        numberConcentrationPm10p0Raw);
    return localError;
}

int16_t SensirionI2cSen65::getVocAlgorithmTuningParameters(
    int16_t& indexOffset, int16_t& learningTimeOffsetHours,
    int16_t& learningTimeGainHours, int16_t& gatingMaxDurationMinutes,
    int16_t& stdInitial, int16_t& gainFactor) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_GET_VOC_ALGORITHM_TUNING_PARAMETERS_CMD_ID, buffer_ptr, 18);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    SensirionI2CRxFrame rxFrame(buffer_ptr, 18);
    localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 18,
                                                         rxFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= rxFrame.getInt16(indexOffset);
    localError |= rxFrame.getInt16(learningTimeOffsetHours);
    localError |= rxFrame.getInt16(learningTimeGainHours);
    localError |= rxFrame.getInt16(gatingMaxDurationMinutes);
    localError |= rxFrame.getInt16(stdInitial);
    localError |= rxFrame.getInt16(gainFactor);
    return localError;
}

int16_t SensirionI2cSen65::setVocAlgorithmTuningParameters(
    int16_t indexOffset, int16_t learningTimeOffsetHours,
    int16_t learningTimeGainHours, int16_t gatingMaxDurationMinutes,
    int16_t stdInitial, int16_t gainFactor) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_SET_VOC_ALGORITHM_TUNING_PARAMETERS_CMD_ID, buffer_ptr, 20);
    localError |= txFrame.addInt16(indexOffset);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= txFrame.addInt16(learningTimeOffsetHours);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= txFrame.addInt16(learningTimeGainHours);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= txFrame.addInt16(gatingMaxDurationMinutes);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= txFrame.addInt16(stdInitial);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= txFrame.addInt16(gainFactor);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    return localError;
}

int16_t SensirionI2cSen65::getVocAlgorithmState(uint8_t state[],
                                                uint16_t stateSize) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_GET_VOC_ALGORITHM_STATE_CMD_ID, buffer_ptr, 12);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    SensirionI2CRxFrame rxFrame(buffer_ptr, 12);
    localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 12,
                                                         rxFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= rxFrame.getBytes((uint8_t*)state, stateSize);
    return localError;
}

int16_t SensirionI2cSen65::setVocAlgorithmState(const uint8_t state[],
                                                uint16_t stateSize) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_SET_VOC_ALGORITHM_STATE_CMD_ID, buffer_ptr, 14);
    localError |= txFrame.addBytes(state, stateSize);
    if (localError != NO_ERROR) {
        return localError;
    }

    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    return localError;
}

int16_t SensirionI2cSen65::getNoxAlgorithmTuningParameters(
    int16_t& indexOffset, int16_t& learningTimeOffsetHours,
    int16_t& learningTimeGainHours, int16_t& gatingMaxDurationMinutes,
    int16_t& stdInitial, int16_t& gainFactor) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_GET_NOX_ALGORITHM_TUNING_PARAMETERS_CMD_ID, buffer_ptr, 18);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    SensirionI2CRxFrame rxFrame(buffer_ptr, 18);
    localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 18,
                                                         rxFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= rxFrame.getInt16(indexOffset);
    localError |= rxFrame.getInt16(learningTimeOffsetHours);
    localError |= rxFrame.getInt16(learningTimeGainHours);
    localError |= rxFrame.getInt16(gatingMaxDurationMinutes);
    localError |= rxFrame.getInt16(stdInitial);
    localError |= rxFrame.getInt16(gainFactor);
    return localError;
}

int16_t SensirionI2cSen65::setNoxAlgorithmTuningParameters(
    int16_t indexOffset, int16_t learningTimeOffsetHours,
    int16_t learningTimeGainHours, int16_t gatingMaxDurationMinutes,
    int16_t stdInitial, int16_t gainFactor) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_SET_NOX_ALGORITHM_TUNING_PARAMETERS_CMD_ID, buffer_ptr, 20);
    localError |= txFrame.addInt16(indexOffset);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= txFrame.addInt16(learningTimeOffsetHours);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= txFrame.addInt16(learningTimeGainHours);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= txFrame.addInt16(gatingMaxDurationMinutes);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= txFrame.addInt16(stdInitial);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= txFrame.addInt16(gainFactor);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    return localError;
}

int16_t SensirionI2cSen65::startContinuousMeasurement(void) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_START_CONTINUOUS_MEASUREMENT_CMD_ID, buffer_ptr, 2);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(50);
    return localError;
}

int16_t SensirionI2cSen65::stopMeasurement(void) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_STOP_MEASUREMENT_CMD_ID, buffer_ptr, 2);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(1400);
    return localError;
}

int16_t SensirionI2cSen65::getDataReady(uint8_t& padding, bool& dataReady) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_GET_DATA_READY_CMD_ID, buffer_ptr, 3);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
    localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
                                                         rxFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= rxFrame.getUInt8(padding);
    localError |= rxFrame.getBool(dataReady);
    return localError;
}

int16_t SensirionI2cSen65::readNumberConcentrationValuesAsIntegers(
    uint16_t& numberConcentrationPm0p5, uint16_t& numberConcentrationPm1p0,
    uint16_t& numberConcentrationPm2p5, uint16_t& numberConcentrationPm4p0,
    uint16_t& numberConcentrationPm10p0) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_READ_NUMBER_CONCENTRATION_VALUES_AS_INTEGERS_CMD_ID, buffer_ptr,
        15);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    SensirionI2CRxFrame rxFrame(buffer_ptr, 15);
    localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 15,
                                                         rxFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= rxFrame.getUInt16(numberConcentrationPm0p5);
    localError |= rxFrame.getUInt16(numberConcentrationPm1p0);
    localError |= rxFrame.getUInt16(numberConcentrationPm2p5);
    localError |= rxFrame.getUInt16(numberConcentrationPm4p0);
    localError |= rxFrame.getUInt16(numberConcentrationPm10p0);
    return localError;
}

int16_t SensirionI2cSen65::setTemperatureOffsetParameters(int16_t offset,
                                                          int16_t slope,
                                                          uint16_t timeConstant,
                                                          uint16_t slot) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_SET_TEMPERATURE_OFFSET_PARAMETERS_CMD_ID, buffer_ptr, 14);
    localError |= txFrame.addInt16(offset);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= txFrame.addInt16(slope);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= txFrame.addUInt16(timeConstant);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= txFrame.addUInt16(slot);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    return localError;
}

int16_t SensirionI2cSen65::setTemperatureAccelerationParameters(uint16_t k,
                                                                uint16_t p,
                                                                uint16_t t1,
                                                                uint16_t t2) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_SET_TEMPERATURE_ACCELERATION_PARAMETERS_CMD_ID, buffer_ptr, 14);
    localError |= txFrame.addUInt16(k);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= txFrame.addUInt16(p);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= txFrame.addUInt16(t1);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= txFrame.addUInt16(t2);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    return localError;
}

int16_t SensirionI2cSen65::getProductType(int8_t productType[],
                                          uint16_t productTypeSize) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_GET_PRODUCT_TYPE_CMD_ID, buffer_ptr, 48);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    SensirionI2CRxFrame rxFrame(buffer_ptr, 48);
    localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 48,
                                                         rxFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= rxFrame.getBytes((uint8_t*)productType, productTypeSize);
    return localError;
}

int16_t SensirionI2cSen65::getProductName(int8_t productName[],
                                          uint16_t productNameSize) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_GET_PRODUCT_NAME_CMD_ID, buffer_ptr, 48);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    SensirionI2CRxFrame rxFrame(buffer_ptr, 48);
    localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 48,
                                                         rxFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= rxFrame.getBytes((uint8_t*)productName, productNameSize);
    return localError;
}

int16_t SensirionI2cSen65::getSerialNumber(int8_t serialNumber[],
                                           uint16_t serialNumberSize) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_GET_SERIAL_NUMBER_CMD_ID, buffer_ptr, 48);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    SensirionI2CRxFrame rxFrame(buffer_ptr, 48);
    localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 48,
                                                         rxFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= rxFrame.getBytes((uint8_t*)serialNumber, serialNumberSize);
    return localError;
}

int16_t SensirionI2cSen65::readDeviceStatus(SEN65DeviceStatus& deviceStatus) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_READ_DEVICE_STATUS_CMD_ID, buffer_ptr, 6);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    SensirionI2CRxFrame rxFrame(buffer_ptr, 6);
    localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 6,
                                                         rxFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= rxFrame.getUInt32(deviceStatus.value);
    return localError;
}

int16_t
SensirionI2cSen65::readAndClearDeviceStatus(SEN65DeviceStatus& deviceStatus) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_READ_AND_CLEAR_DEVICE_STATUS_CMD_ID, buffer_ptr, 6);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    SensirionI2CRxFrame rxFrame(buffer_ptr, 6);
    localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 6,
                                                         rxFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= rxFrame.getUInt32(deviceStatus.value);
    return localError;
}

int16_t SensirionI2cSen65::getVersion(uint8_t& firmwareMajor,
                                      uint8_t& firmwareMinor) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_GET_VERSION_CMD_ID, buffer_ptr, 3);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
    localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
                                                         rxFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= rxFrame.getUInt8(firmwareMajor);
    localError |= rxFrame.getUInt8(firmwareMinor);
    return localError;
}

int16_t SensirionI2cSen65::deviceReset(void) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_DEVICE_RESET_CMD_ID, buffer_ptr, 2);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(1200);
    return localError;
}

int16_t SensirionI2cSen65::startFanCleaning(void) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_START_FAN_CLEANING_CMD_ID, buffer_ptr, 2);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    return localError;
}

int16_t SensirionI2cSen65::activateShtHeater(void) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_ACTIVATE_SHT_HEATER_CMD_ID, buffer_ptr, 2);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    return localError;
}

int16_t
SensirionI2cSen65::getShtHeaterMeasurements(int16_t& shtRelativeHumidity,
                                            int16_t& shtTemperature) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_GET_SHT_HEATER_MEASUREMENTS_CMD_ID, buffer_ptr, 6);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    SensirionI2CRxFrame rxFrame(buffer_ptr, 6);
    localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 6,
                                                         rxFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= rxFrame.getInt16(shtRelativeHumidity);
    localError |= rxFrame.getInt16(shtTemperature);
    return localError;
}

int16_t SensirionI2cSen65::readMeasuredValuesAsIntegers(
    uint16_t& massConcentrationPm1p0, uint16_t& massConcentrationPm2p5,
    uint16_t& massConcentrationPm4p0, uint16_t& massConcentrationPm10p0,
    int16_t& ambientHumidity, int16_t& ambientTemperature, int16_t& vocIndex,
    int16_t& noxIndex) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_READ_MEASURED_VALUES_AS_INTEGERS_CMD_ID, buffer_ptr, 24);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    SensirionI2CRxFrame rxFrame(buffer_ptr, 24);
    localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 24,
                                                         rxFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= rxFrame.getUInt16(massConcentrationPm1p0);
    localError |= rxFrame.getUInt16(massConcentrationPm2p5);
    localError |= rxFrame.getUInt16(massConcentrationPm4p0);
    localError |= rxFrame.getUInt16(massConcentrationPm10p0);
    localError |= rxFrame.getInt16(ambientHumidity);
    localError |= rxFrame.getInt16(ambientTemperature);
    localError |= rxFrame.getInt16(vocIndex);
    localError |= rxFrame.getInt16(noxIndex);
    return localError;
}

int16_t SensirionI2cSen65::readMeasuredRawValues(int16_t& rawHumidity,
                                                 int16_t& rawTemperature,
                                                 uint16_t& rawVoc,
                                                 uint16_t& rawNox) {
    int16_t localError = NO_ERROR;
    uint8_t* buffer_ptr = communication_buffer;

    SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
        SEN65_READ_MEASURED_RAW_VALUES_CMD_ID, buffer_ptr, 12);
    localError =
        SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    delay(20);
    SensirionI2CRxFrame rxFrame(buffer_ptr, 12);
    localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 12,
                                                         rxFrame, *_i2cBus);
    if (localError != NO_ERROR) {
        return localError;
    }
    localError |= rxFrame.getInt16(rawHumidity);
    localError |= rxFrame.getInt16(rawTemperature);
    localError |= rxFrame.getUInt16(rawVoc);
    localError |= rxFrame.getUInt16(rawNox);
    return localError;
}

void SensirionI2cSen65::begin(TwoWire& i2cBus, uint8_t i2cAddress) {
    _i2cBus = &i2cBus;
    _i2cAddress = i2cAddress;
}
