/*
 * THIS FILE IS AUTOMATICALLY GENERATED
 *
 * Generator:     sensirion-driver-generator 1.1.2
 * Product:       sen63c
 * Model-Version: 1.2.0
 */
/*
 * Copyright (c) 2025, 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.
 */

#ifndef SENSIRIONI2CSEN63C_H
#define SENSIRIONI2CSEN63C_H

#include <SensirionCore.h>
#include <Wire.h>

#define SEN63C_I2C_ADDR_6B 0x6b

typedef enum {
    SEN63C_START_CONTINUOUS_MEASUREMENT_CMD_ID = 0x21,
    SEN63C_STOP_MEASUREMENT_CMD_ID = 0x104,
    SEN63C_GET_DATA_READY_CMD_ID = 0x202,
    SEN63C_READ_MEASURED_VALUES_AS_INTEGERS_CMD_ID = 0x471,
    SEN63C_READ_NUMBER_CONCENTRATION_VALUES_AS_INTEGERS_CMD_ID = 0x316,
    SEN63C_READ_MEASURED_RAW_VALUES_CMD_ID = 0x492,
    SEN63C_START_FAN_CLEANING_CMD_ID = 0x5607,
    SEN63C_SET_TEMPERATURE_OFFSET_PARAMETERS_CMD_ID = 0x60b2,
    SEN63C_SET_TEMPERATURE_ACCELERATION_PARAMETERS_CMD_ID = 0x6100,
    SEN63C_PERFORM_FORCED_CO2_RECALIBRATION_CMD_ID = 0x6707,
    SEN63C_SET_CO2_SENSOR_AUTOMATIC_SELF_CALIBRATION_CMD_ID = 0x6711,
    SEN63C_GET_CO2_SENSOR_AUTOMATIC_SELF_CALIBRATION_CMD_ID = 0x6711,
    SEN63C_SET_AMBIENT_PRESSURE_CMD_ID = 0x6720,
    SEN63C_GET_AMBIENT_PRESSURE_CMD_ID = 0x6720,
    SEN63C_SET_SENSOR_ALTITUDE_CMD_ID = 0x6736,
    SEN63C_GET_SENSOR_ALTITUDE_CMD_ID = 0x6736,
    SEN63C_ACTIVATE_SHT_HEATER_CMD_ID = 0x6765,
    SEN63C_GET_SHT_HEATER_MEASUREMENTS_CMD_ID = 0x6790,
    SEN63C_GET_PRODUCT_NAME_CMD_ID = 0xd014,
    SEN63C_GET_SERIAL_NUMBER_CMD_ID = 0xd033,
    SEN63C_GET_VERSION_CMD_ID = 0xd100,
    SEN63C_READ_DEVICE_STATUS_CMD_ID = 0xd206,
    SEN63C_READ_AND_CLEAR_DEVICE_STATUS_CMD_ID = 0xd210,
    SEN63C_DEVICE_RESET_CMD_ID = 0xd304,
} SEN63CCmdId;

typedef union {
    struct {
        uint32_t reserved1 : 4;
        uint32_t fanError : 1;
        uint32_t reserved2 : 1;
        uint32_t rhtError : 1;
        uint32_t reserved3 : 1;
        uint32_t reserved4 : 1;
        uint32_t reserved5 : 1;
        uint32_t reserved6 : 1;
        uint32_t pmError : 1;
        uint32_t co21Error : 1;
        uint32_t reserved7 : 8;
        uint32_t fanSpeedWarning : 1;
    };
    uint32_t value;
} SEN63CDeviceStatus;

class SensirionI2cSen63c {
  public:
    SensirionI2cSen63c();
    /**
     * @brief Initializes the SEN63C class.
     *
     * @param i2cBus Arduino stream object to be used for communication.
     */
    void begin(TwoWire& i2cBus, uint8_t i2cAddress);

    /**
     * @brief Read measured values and apply scaling as defined in datasheet.
     *
     * @param[out] massConcentrationPm1p0 Mass concentration in μg/m³ for
     * particles smaller than 1.0 μm.
     * @param[out] massConcentrationPm2p5 Mass concentration in μg/m³ for
     * particles smaller than 2.5 μm.
     * @param[out] massConcentrationPm4p0 Mass concentration in μg/m³ for
     * particles smaller than 4.0 μm.
     * @param[out] massConcentrationPm10p0 Mass concentration in μg/m³ for
     * particles smaller than 10.0 μm.
     * @param[out] humidity Measured humidity in %RH.
     * @param[out] temperature Measured temperature in degrees celsius.
     * @param[out] co2 Measured CO2 concentration in ppm.
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t readMeasuredValues(float& massConcentrationPm1p0,
                               float& massConcentrationPm2p5,
                               float& massConcentrationPm4p0,
                               float& massConcentrationPm10p0, float& humidity,
                               float& temperature, uint16_t& co2);

    /**
     * @brief Read measured number concentration values and apply scaling as
     * defined in datasheet.
     *
     * @param[out] numberConcentrationPm0p5 Number concentration in
     * particles/cm³ for particles smaller than 0.5 μm.
     * @param[out] numberConcentrationPm1p0 Number concentration in
     * particles/cm³ for particles smaller than 1.0 μm.
     * @param[out] numberConcentrationPm2p5 Number concentration in
     * particles/cm³ for particles smaller than 2.5 μm.
     * @param[out] numberConcentrationPm4p0 Number concentration in
     * particles/cm³ for particles smaller than 4.0 μm.
     * @param[out] numberConcentrationPm10p0 Number concentration in
     * particles/cm³ for particles smaller than 10.0 μm.
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t readNumberConcentrationValues(float& numberConcentrationPm0p5,
                                          float& numberConcentrationPm1p0,
                                          float& numberConcentrationPm2p5,
                                          float& numberConcentrationPm4p0,
                                          float& numberConcentrationPm10p0);

    /**
     * @brief Start a continuous measurement (interval 1s)
     *
     * Starts a continuous measurement. After starting the measurement, it takes
     * some time (~1.1s) until the first measurement results are available. You
     * could poll with the command "Get Data Ready" to check when the results
     * are ready to read. This command is only available in idle mode. If the
     * device is already in any measure mode, this command has no effect.
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t startContinuousMeasurement();

    /**
     * @brief Stop the continuous measurement
     *
     * Stops the measurement and returns to idle mode. After sending this
     * command, wait at least 1000 ms before starting a new measurement. If the
     * device is already in idle mode, this command has no effect.
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t stopMeasurement();

    /**
     * @brief Check if data is ready to be read out from the sensor
     *
     * This command can be used to check if new measurement results are ready to
     * read. The data ready flag is automatically reset after reading the
     * measurement values.
     *
     * @param[out] padding Padding byte, always 0x00.
     * @param[out] dataReady True (0x01) if data is ready, False (0x00) if not.
     * When no measurement is running, False will be returned.
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t getDataReady(uint8_t& padding, bool& dataReady);

    /**
     * @brief read measured values as integers.
     *
     * Returns the measured values. The command "Get Data Ready" can be used to
     * check if new data is available since the last read operation. If no new
     * data is available, the previous values will be returned again. If no data
     * is available at all (e.g. measurement not running for at least one
     * second), all values will be at their upper limit (0xFFFF for uint16,
     * 0x7FFF for int16).
     *
     * @param[out] massConcentrationPm1p0 Value is scaled with factor 10: PM1.0
     * [µg/m³] = value / 10 *Note: If this value is unknown, 0xFFFF is
     * returned.*
     * @param[out] massConcentrationPm2p5 Value is scaled with factor 10: PM2.5
     * [µg/m³] = value / 10 *Note: If this value is unknown, 0xFFFF is
     * returned.*
     * @param[out] massConcentrationPm4p0 Value is scaled with factor 10: PM4.0
     * [µg/m³] = value / 10 *Note: If this value is unknown, 0xFFFF is
     * returned.*
     * @param[out] massConcentrationPm10p0 Value is scaled with factor 10:
     * PM10.0 [µg/m³] = value / 10 *Note: If this value is unknown, 0xFFFF is
     * returned.*
     * @param[out] ambientHumidity Value is scaled with factor 100: RH [%] =
     * value / 100 *Note: If this value is unknown, 0x7FFF is returned.*
     * @param[out] ambientTemperature Value is scaled with factor 200: T [°C] =
     * value / 200 *Note: If this value is unknown, 0x7FFF is returned.*
     * @param[out] cO2 CO₂ concentration [ppm] *Note: If this value is unknown,
     * 0xFFFF is returned. During the first 5..6 seconds after power-on or
     * device reset, this value will be 0xFFFF as well.*
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t readMeasuredValuesAsIntegers(
        uint16_t& massConcentrationPm1p0, uint16_t& massConcentrationPm2p5,
        uint16_t& massConcentrationPm4p0, uint16_t& massConcentrationPm10p0,
        int16_t& ambientHumidity, int16_t& ambientTemperature, uint16_t& cO2);

    /**
     * @brief readNumberConcentrationValuesAsIntegers
     *
     * Returns the measured number concentration values. The command "Get Data
     * Ready" can be used to check if new data is available since the last read
     * operation. If no new data is available, the previous values will be
     * returned again. If no data is available at all (e.g. measurement not
     * running for at least one second), all values will be at their upper limit
     * (0xFFFF for uint16).
     *
     * @param[out] numberConcentrationPm0p5 Value is scaled with factor 10:
     * PM0.5 [particles/cm³] = value / 10 *Note: If this value is unknown,
     * 0xFFFF is returned.*
     * @param[out] numberConcentrationPm1p0 Value is scaled with factor 10:
     * PM1.0 [particles/cm³] = value / 10 *Note: If this value is unknown,
     * 0xFFFF is returned.*
     * @param[out] numberConcentrationPm2p5 Value is scaled with factor 10:
     * PM2.5 [particles/cm³] = value / 10 *Note: If this value is unknown,
     * 0xFFFF is returned.*
     * @param[out] numberConcentrationPm4p0 Value is scaled with factor 10:
     * PM4.0 [particles/cm³] = value / 10 *Note: If this value is unknown,
     * 0xFFFF is returned.*
     * @param[out] numberConcentrationPm10p0 Value is scaled with factor 10:
     * PM10.0 [particles/cm³] = value / 10
     *   * field:
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t readNumberConcentrationValuesAsIntegers(
        uint16_t& numberConcentrationPm0p5, uint16_t& numberConcentrationPm1p0,
        uint16_t& numberConcentrationPm2p5, uint16_t& numberConcentrationPm4p0,
        uint16_t& numberConcentrationPm10p0);

    /**
     * @brief readMeasuredRawValues
     *
     * Returns the measured raw values. The command "Get Data Ready" can be used
     * to check if new data is available since the last read operation. If no
     * new data is available, the previous values will be returned again. If no
     * data is available at all (e.g. measurement not running for at least one
     * second), all values will be at their upper limit (0xFFFF for uint16,
     * 0x7FFF for int16).
     *
     * @param[out] rawHumidity Value is scaled with factor 100: RH [%] = value /
     * 100 *Note: If this value is unknown, 0x7FFF is returned.*
     * @param[out] rawTemperature Value is scaled with factor 200: T [°C] =
     * value / 200 *Note: If this value is unknown, 0x7FFF is returned.*
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t readMeasuredRawValues(int16_t& rawHumidity,
                                  int16_t& rawTemperature);

    /**
     * @brief startFanCleaning
     *
     * This command triggers fan cleaning. The fan is set to the maximum speed
     * for 10 seconds and then automatically stopped. Wait at least 10s after
     * this command before starting a measurement.
     *
     * @note This command is only available in idle mode.
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t startFanCleaning();

    /**
     * @brief setTemperatureOffsetParameters
     *
     * This command allows to compensate temperature effects of the design-in at
     * customer side by applying custom temperature offsets to the ambient
     * temperature. The compensated ambient temperature is calculated as
     * follows: T_Ambient_Compensated = T_Ambient + (slope * T_Ambient) + offset
     * Where slope and offset are the values set with this command, smoothed
     * with the specified time constant. All temperatures
     * (T_Ambient_Compensated, T_Ambient and offset) are represented in °C.
     * There are 5 temperature offset slots available that all contribute
     * additively to T_Ambient_Compensated. The default values for the
     * temperature offset parameters are all zero, meaning that
     * T_Ambient_Compensated is equal to T_Ambient by default. The parameters
     * can be changed in any state of the device, i.e. both in idle mode and in
     * measure mode.
     *
     * @param[in] offset Constant temperature offset scaled with factor 200 (T
     * [°C] = value / 200).
     * @param[in] slope Normalized temperature offset slope scaled with factor
     * 10000 (applied factor = value / 10000).
     * @param[in] timeConstant The time constant determines how fast the new
     * slope and offset will be applied. After the specified value in seconds,
     * 63% of the new slope and offset are applied. A time constant of zero
     * means the new values will be applied immediately (within the next measure
     * interval of 1 second).
     * @param[in] slot The temperature offset slot to be modified. Valid values
     * are 0 .. 4. If the value is outside this range, the parameters will not
     * be applied.
     *
     * @note This configuration is volatile, i.e. the parameters will be
     * reverted to their default value of zero after a device reset.
     *
     * @return error_code 0 on success, an error code otherwise.
     *
     * Example:
     * --------
     *
     * @code{.cpp}
     *
     *     int16_t localError = 0;
     *     localError = sensor.setTemperatureOffsetParameters(1, 10, 1, 0);
     *     if (localError != NO_ERROR) {
     *         return;
     *     }
     *
     * @endcode
     *
     */
    int16_t setTemperatureOffsetParameters(int16_t offset, int16_t slope,
                                           uint16_t timeConstant,
                                           uint16_t slot);

    /**
     * @brief setTemperatureAccelerationParameters
     *
     * This command allows to set custom temperature acceleration parameters of
     * the RH/T engine. It overwrites the default temperature acceleration
     * parameters of the RH/T engine with custom values. This configuration is
     * volatile, i.e. the parameters will be reverted to their default values
     * after a device reset.
     *
     * @param[in] k Filter constant K scaled with factor 10 (K = value / 10).
     * @param[in] p Filter constant P scaled with factor 10 (P = value / 10).
     * @param[in] t1 Time constant T1 scaled with factor 10 (T1 [s] = value /
     * 10).
     * @param[in] t2 Time constant T2 scaled with factor 10 (T2 [s] = value /
     * 10).
     *
     * @note The command is only available in idle mode.
     *
     * @return error_code 0 on success, an error code otherwise.
     *
     * Example:
     * --------
     *
     * @code{.cpp}
     *
     *     int16_t localError = 0;
     *     localError = sensor.setTemperatureAccelerationParameters(1, 1, 1, 1);
     *     if (localError != NO_ERROR) {
     *         return;
     *     }
     *
     * @endcode
     *
     */
    int16_t setTemperatureAccelerationParameters(uint16_t k, uint16_t p,
                                                 uint16_t t1, uint16_t t2);

    /**
     * @brief Perform Forced CO₂ Recalibration
     *
     * Execute the forced recalibration (FRC) of the CO₂. See the datasheet of
     * the SCD4x sensor for details how the forced recalibration shall be used.
     *
     * @param[in] targetCo2Concentration Target CO₂ concentration [ppm] of the
     * test setup.
     * @param[out] correction Correction value as received from the SCD [ppm
     * CO₂]. FRC correction [ppm CO₂] is calculated as follows: FRC =
     * return_value - 0x8000 If the recalibration has failed this returned value
     * is 0xFFFF.
     *
     * @note After power-on wait at least 1000 ms and after stopping a
     * measurement 600 ms before sending this command. This command is not
     * available in measure mode. The recalibration procedure will take about
     * 500 ms to complete, during which time no other functions can be executed.
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t performForcedCo2Recalibration(uint16_t targetCo2Concentration,
                                          uint16_t& correction);

    /**
     * @brief setCo2SensorAutomaticSelfCalibration
     *
     * Sets the status of the CO₂ sensor automatic self-calibration (ASC). The
     * CO₂ sensor supports automatic self calibration (ASC) for long-term
     * stability of the CO₂ output. This feature can be enabled or disabled. By
     * default it is enabled. This configuration is volatile, i.e. the parameter
     * will be reverted to its default value after a device restart.
     *
     * @param[in] status Set to true (0x0001) to enable or false (0x0000) to
     * disable the automatic CO₂ measurement self calibration feature. High byte
     * of uint16 is padding and always 0x00.
     *
     * @note This command is only available in idle mode.
     *
     * @return error_code 0 on success, an error code otherwise.
     *
     * Example:
     * --------
     *
     * @code{.cpp}
     *
     *     int16_t localError = 0;
     *     localError = sensor.setCo2SensorAutomaticSelfCalibration(0);
     *     if (localError != NO_ERROR) {
     *         return;
     *     }
     *
     * @endcode
     *
     */
    int16_t setCo2SensorAutomaticSelfCalibration(uint16_t status);

    /**
     * @brief getCo2SensorAutomaticSelfCalibration
     *
     * The CO₂ sensor supports automatic self calibration (ASC) for long-term
     * stability of the CO₂ output. This feature can be enabled or disabled. By
     * default it is enabled. This configuration is volatile, i.e. the parameter
     * will be reverted to its default value after a device restart.
     *
     * @param[out] padding Padding byte, always 0x00.
     * @param[out] status Is set true (0x01) if the automatic self calibration
     * is enabled or false (0x00) if the automatic self calibration is disabled.
     *
     * @note This command is only available in idle mode.
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t getCo2SensorAutomaticSelfCalibration(uint8_t& padding,
                                                 bool& status);

    /**
     * @brief setAmbientPressure
     *
     * The ambient pressure can be used for pressure compensation in the CO₂
     * sensor. Setting an ambient pressure overrides any pressure compensation
     * based on a previously set sensor altitude. Use of this command is
     * recommended for applications experiencing significant ambient pressure
     * changes to ensure CO₂ sensor accuracy. Valid input values are between 700
     * to 1'200 hPa. The default value is 1013 hPa. This configuration is
     * volatile, i.e. the parameter will be reverted to its default value after
     * a device restart.
     *
     * @param[in] ambientPressure Ambient pressure [hPa] to be used for pressure
     * compensation.
     *
     * @note This command can be used in any state of the device, i.e. both in
     * idle mode and in measure mode.
     *
     * @return error_code 0 on success, an error code otherwise.
     *
     * Example:
     * --------
     *
     * @code{.cpp}
     *
     *     int16_t localError = 0;
     *     localError = sensor.setAmbientPressure(1013);
     *     if (localError != NO_ERROR) {
     *         return;
     *     }
     *
     * @endcode
     *
     */
    int16_t setAmbientPressure(uint16_t ambientPressure);

    /**
     * @brief getAmbientPressure
     *
     * Gets the ambient pressure value. The ambient pressure can be used for
     * pressure compensation in the CO₂ sensor.
     *
     * @param[out] ambientPressure Currently used ambient pressure [hPa] for
     * pressure compensation.
     *
     * @note This command can be used in any state of the device, i.e. both in
     * idle mode and in measure mode.
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t getAmbientPressure(uint16_t& ambientPressure);

    /**
     * @brief setSensorAltitude
     *
     * The sensor altitude can be used for pressure compensation in the CO₂
     * sensor. The default sensor altitude value is set to 0 meters above sea
     * level. Valid input values are between 0 and 3000m. This configuration is
     * volatile, i.e. the parameter will be reverted to its default value after
     * a device reset.
     *
     * @param[in] altitude Sensor altitude [m], valid input between 0 and 3000m.
     *
     * @return error_code 0 on success, an error code otherwise.
     *
     * Example:
     * --------
     *
     * @code{.cpp}
     *
     *     int16_t localError = 0;
     *     localError = sensor.setSensorAltitude(0);
     *     if (localError != NO_ERROR) {
     *         return;
     *     }
     *
     * @endcode
     *
     */
    int16_t setSensorAltitude(uint16_t altitude);

    /**
     * @brief getSensorAltitude
     *
     * Gets the current sensor altitude. The sensor altitude can be used for
     * pressure compensation in the CO₂ sensor.
     *
     * @param[out] altitude Current sensor altitude [m].
     *
     * @note This command is only available in idle mode.
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t getSensorAltitude(uint16_t& altitude);

    /**
     * @brief Activate SHT Heater
     *
     * This command allows to use the inbuilt heater in SHT sensor to reverse
     * creep at high humidity. This command activates the SHT sensor heater with
     * 200mW for 1s. The heater is then automatically deactivated again. The
     * "get_sht_heater_measurements" command can be used to check if the heater
     * has finished. Wait at least 20s after this command before starting a
     * measurement to get coherent temperature values (heating consequence to
     * disappear).
     *
     * @note This command is only available in idle mode.
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t activateShtHeater();

    /**
     * @brief Get the measurement values when the SHT sensor heating is
     * finished.
     *
     * Get the measured values when the SHT sensor heating is triggerd. If the
     * heating is not finished, the returned humidity and temperature values are
     * 0x7FFF.
     *
     * @param[out] humidity Value is scaled with factor 100: RH [%] = value /
     * 100 *Note: If this value is not available, 0x7FFF is returned.*
     * @param[out] temperature Value is scaled with factor 200: T [°C] = value /
     * 200 *Note: If this value is not available, 0x7FFF is returned.*
     *
     * @note This command is only availble in idle mode. This command must be
     * used after the "activate_sht_heater" command. The
     * get_sht_heater_measurements command can be queried every 0.05s to get the
     * measurements.
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t getShtHeaterMeasurements(int16_t& humidity, int16_t& temperature);

    /**
     * @brief getProductName
     *
     * Gets the product name from the device.
     *
     * @param[out] productName Null-terminated ASCII string containing the
     * product name. Up to 32 characters can be read from the device.
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t getProductName(int8_t productName[], uint16_t productNameSize);

    /**
     * @brief getSerialNumber
     *
     * Gets the serial number from the device.
     *
     * @param[out] serialNumber Null-terminated ASCII string containing the
     * serial number. Up to 32 characters can be read from the device.
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t getSerialNumber(int8_t serialNumber[], uint16_t serialNumberSize);

    /**
     * @brief getVersion
     *
     * Gets the version information for the hardware, firmware and communication
     * protocol.
     *
     * @param[out] firmwareMajor Firmware major version number.
     * @param[out] firmwareMinor Firmware minor version number.
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t getVersion(uint8_t& firmwareMajor, uint8_t& firmwareMinor);

    /**
     * @brief readDeviceStatus
     *
     * Reads the current device status. Use this command to get detailed
     * information about the device status. The device status is encoded in
     * flags. Each device status flag represents a single bit in a 32-bit
     * integer value. If more than one error is present, the device status
     * register value is the sum of the corresponding flag values. For details
     * about the available flags, refer to the device status flags documentation
     * in the data sheet.
     *
     * @param[out] deviceStatus Device status (32 flags as an integer value).
     * For details, please refer to the device status flags documentation in the
     * datasheet.
     *
     * @note The status flags of type "Error" are sticky, i.e. they are not
     * cleared automatically even if the error condition no longer exists. So
     * they can only be cleared manually with the command "Read And Clear Device
     * Status" or with a device reset. All other flags are not sticky, i.e. they
     * are cleared automatically if the trigger condition disappears.
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t readDeviceStatus(SEN63CDeviceStatus& deviceStatus);

    /**
     * @brief readAndClearDeviceStatus
     *
     * Reads the current device status (like command 0xD206 "Read Device
     * Status") and afterwards clears all flags.
     *
     * @param[out] deviceStatus Device status (32 flags as an integer value)
     * **before** clearing it. For details, please refer to the device status
     * flags documentation.
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t readAndClearDeviceStatus(SEN63CDeviceStatus& deviceStatus);

    /**
     * @brief deviceReset
     *
     * Executes a reset on the device. This has the same effect as a power
     * cycle.
     *
     * @return error_code 0 on success, an error code otherwise.
     */
    int16_t deviceReset();

    /**
     * @brief signalMassConcentrationPm1p0
     *
     * @param[in] massConcentrationPm1p0Raw
     *
     * @return Mass concentration in μg/m³ for particles smaller than 1.0 μm
     */
    static float
    signalMassConcentrationPm1p0(uint16_t massConcentrationPm1p0Raw);

    /**
     * @brief signalMassConcentrationPm2p5
     *
     * @param[in] massConcentrationPm2p5Raw
     *
     * @return Mass concentration in μg/m³ for particles smaller than 2.5 μm
     */
    static float
    signalMassConcentrationPm2p5(uint16_t massConcentrationPm2p5Raw);

    /**
     * @brief signalMassConcentrationPm4p0
     *
     * @param[in] massConcentrationPm4p0Raw
     *
     * @return Mass concentration in μg/m³ for particles smaller than 4.0 μm
     */
    static float
    signalMassConcentrationPm4p0(uint16_t massConcentrationPm4p0Raw);

    /**
     * @brief signalMassConcentrationPm10p0
     *
     * @param[in] massConcentrationPm10p0Raw
     *
     * @return Mass concentration in μg/m³ for particles smaller than 10.0 μm
     */
    static float
    signalMassConcentrationPm10p0(uint16_t massConcentrationPm10p0Raw);

    /**
     * @brief signalNumberConcentrationPm0p5
     *
     * @param[in] numberConcentrationPm0p5Raw
     *
     * @return Number concentration in particles/cm³ for particles smaller than
     * 0.5 μm
     */
    static float
    signalNumberConcentrationPm0p5(uint16_t numberConcentrationPm0p5Raw);

    /**
     * @brief signalNumberConcentrationPm1p0
     *
     * @param[in] numberConcentrationPm1p0Raw
     *
     * @return Number concentration in particles/cm³ for particles smaller
     * than 1.0 μm
     */
    static float
    signalNumberConcentrationPm1p0(uint16_t numberConcentrationPm1p0Raw);

    /**
     * @brief signalNumberConcentrationPm2p5
     *
     * @param[in] numberConcentrationPm2p5Raw
     *
     * @return Number concentration in particles/cm³ for particles smaller
     * than 2.5 μm
     */
    static float
    signalNumberConcentrationPm2p5(uint16_t numberConcentrationPm2p5Raw);

    /**
     * @brief signalNumberConcentrationPm4p0
     *
     * @param[in] numberConcentrationPm4p0Raw
     *
     * @return Number concentration in particles/cm³ for particles smaller
     * than 4.0 μm
     */
    static float
    signalNumberConcentrationPm4p0(uint16_t numberConcentrationPm4p0Raw);

    /**
     * @brief signalNumberConcentrationPm10p0
     *
     * @param[in] numberConcentrationPm10p0Raw
     *
     * @return Number concentration in particles/cm³ for particles smaller
     * than 10.0 μm
     */
    static float
    signalNumberConcentrationPm10p0(uint16_t numberConcentrationPm10p0Raw);

    /**
     * @brief signalTemperature
     *
     * @param[in] temperatureRaw
     *
     * @return Measured temperature in degrees celsius. The raw value is scaled
     * appropriately.
     */
    static float signalTemperature(int16_t temperatureRaw);

    /**
     * @brief signalHumidity
     *
     * @param[in] humidityRaw
     *
     * @return Measured humidity in %RH. The raw value is scaled appropriately.
     */
    static float signalHumidity(int16_t humidityRaw);

    /**
     * @brief signalCo2
     *
     * @param[in] co2Raw
     *
     * @return Measured CO2 in ppm.
     */
    static uint16_t signalCo2(uint16_t co2Raw);

  private:
    TwoWire* _i2cBus = nullptr;
    uint8_t _i2cAddress = 0;
};

#endif  // SENSIRIONI2CSEN63C_H