# -*- coding: utf-8 -*-
# (c) Copyright 2021 Sensirion AG, Switzerland
# flake8: noqa
from __future__ import absolute_import, division, print_function
from struct import pack, unpack
from sensirion_i2c_driver import SensirionI2cCommand, CrcCalculator
from sensirion_i2c_scd.scd4x.response_types import Scd4xHumidity, Scd4xCarbonDioxide, Scd4xTemperature
from sensirion_i2c_scd.scd4x.data_types import Scd4xTemperatureOffsetDegC
from sensirion_i2c_scd.scd4x.response_types import Scd4xTemperatureOffset
[docs]class Scd4xI2cCmdBase(SensirionI2cCommand):
"""
SCD4x I²C base command.
"""
[docs] def __init__(self, command, tx_data, rx_length, read_delay, timeout,
post_processing_time=0.0):
"""
Constructs a new SCD4x I²C command.
:param int/None command:
The command ID to be sent to the device. None means that no
command will be sent, i.e. only ``tx_data`` (if not None) will
be sent. No CRC is added to these bytes since the command ID
usually already contains a CRC.
:param bytes-like/list/None tx_data:
Bytes to be extended with CRCs and then sent to the I²C device.
None means that no write header will be sent at all (if ``command``
is None too). An empty list means to send the write header (even if
``command`` is None), but without data following it.
:param int/None rx_length:
Number of bytes to be read from the I²C device, including CRC
bytes. None means that no read header is sent at all. Zero means
to send the read header, but without reading any data.
:param float read_delay:
Delay (in Seconds) to be inserted between the end of the write
operation and the beginning of the read operation. This is needed
if the device needs some time to prepare the RX data, e.g. if it
has to perform a measurement. Set to 0.0 to indicate that no delay
is needed, i.e. the device does not need any processing time.
:param float timeout:
Timeout (in Seconds) to be used in case of clock stretching. If the
device stretches the clock longer than this value, the transceive
operation will be aborted with a timeout error. Set to 0.0 to
indicate that the device will not stretch the clock for this
command.
:param float post_processing_time:
Maximum time in seconds the device needs for post processing of
this command until it is ready to receive the next command. For
example after a device reset command, the device might need some
time until it is ready again. Usually this is 0.0s, i.e. no post
processing is needed.
"""
super(Scd4xI2cCmdBase, self).__init__(
command=command,
tx_data=tx_data,
rx_length=rx_length,
read_delay=read_delay,
timeout=timeout,
crc=CrcCalculator(8, 0x31, 0xFF, 0x00),
command_bytes=2,
post_processing_time=post_processing_time,
)
[docs]class Scd4xI2cCmdStartPeriodicMeasurement(Scd4xI2cCmdBase):
"""
Start Periodic Measurement I²C Command
start periodic measurement, signal update interval is 5 seconds.
.. note:: This command is only available in idle mode.
"""
[docs] def __init__(self):
"""
Constructor.
"""
super(Scd4xI2cCmdStartPeriodicMeasurement, self).__init__(
command=0x21B1,
tx_data=None,
rx_length=None,
read_delay=0.0,
timeout=0,
post_processing_time=0.001,
)
[docs]class Scd4xI2cCmdReadMeasurement(Scd4xI2cCmdBase):
"""
Read Measurement I²C Command
read sensor output. The measurement data can only be read out once per
signal update interval as the buffer is emptied upon read-out. If no data
is available in the buffer, the sensor returns a NACK. To avoid a NACK
response the get_data_ready_status can be issued to check data status. The
I2C master can abort the read transfer with a NACK followed by a STOP
condition after any data byte if the user is not interested in subsequent
data.
.. note:: This command is only available in measurement mode. The firmware
updates the measurement values depending on the measurement mode.
"""
[docs] def __init__(self):
"""
Constructor.
"""
super(Scd4xI2cCmdReadMeasurement, self).__init__(
command=0xEC05,
tx_data=None,
rx_length=9,
read_delay=0.001,
timeout=0,
post_processing_time=0.0,
)
[docs] def interpret_response(self, data):
"""
Validates the CRCs of the received data from the device and returns
the interpreted data.
:param bytes data:
Received raw bytes from the read operation.
:return:
- co2 (:py:class:`~sensirion_i2c_scd.scd4x.response_types.Scd4xCarbonDioxid`) -
CO₂ response object
- temperature (:py:class:`~sensirion_i2c_scd.scd4x.response_types.Scd4xTemperature`) -
Temperature response object.
- humidity (:py:class:`~sensirion_i2c_scd.scd4x.response_types.Scd4xHumidity`) -
Humidity response object
:rtype: tuple
:raise ~sensirion_i2c_driver.errors.I2cChecksumError:
If a received CRC was wrong.
"""
# check and remove CRCs
checked_data = Scd4xI2cCmdBase.interpret_response(self, data)
# convert raw received data into proper data types
co2 = int(unpack(">H", checked_data[0:2])[0]) # uint16
temperature = int(unpack(">H", checked_data[2:4])[0]) # uint16
humidity = int(unpack(">H", checked_data[4:6])[0]) # uint16
return Scd4xCarbonDioxide(co2), Scd4xTemperature(temperature), Scd4xHumidity(humidity)
[docs]class Scd4xI2cCmdStopPeriodicMeasurement(Scd4xI2cCmdBase):
"""
Stop Periodic Measurement I²C Command
Stop periodic measurement and return to idle mode for sensor configuration
or to safe energy.
.. note:: This command is only available in measurement mode.
"""
[docs] def __init__(self):
"""
Constructor.
"""
super(Scd4xI2cCmdStopPeriodicMeasurement, self).__init__(
command=0x3F86,
tx_data=None,
rx_length=None,
read_delay=0.0,
timeout=0,
post_processing_time=0.5,
)
[docs]class Scd4xI2cCmdGetTemperatureOffset(Scd4xI2cCmdBase):
"""
Get Temperature Offset I²C Command
The temperature offset represents the difference between the measured
temperature by the SCD4x and the actual ambient temperature. Per default,
the temperature offset is set to 4°C.
.. note:: Only available in idle mode.
"""
[docs] def __init__(self):
"""
Constructor.
"""
super(Scd4xI2cCmdGetTemperatureOffset, self).__init__(
command=0x2318,
tx_data=None,
rx_length=3,
read_delay=0.001,
timeout=0,
post_processing_time=0.0,
)
[docs] def interpret_response(self, data):
"""
Validates the CRCs of the received data from the device and returns
the interpreted data.
:param bytes data:
Received raw bytes from the read operation.
:return:
- temperature offset (:py:class:`~sensirion_i2c_scd.scd4x.response_types.Scd4xTemperatureOffset`) -
TemperatureOffset response object.
:raise ~sensirion_i2c_driver.errors.I2cChecksumError:
If a received CRC was wrong.
"""
# check and remove CRCs
checked_data = Scd4xI2cCmdBase.interpret_response(self, data)
# convert raw received data into proper data types
t_offset = int(unpack(">H", checked_data[0:2])[0]) # uint16
return Scd4xTemperatureOffset(t_offset)
[docs]class Scd4xI2cCmdSetTemperatureOffset(Scd4xI2cCmdBase):
"""
Set Temperature Offset I²C Command
Setting the temperature offset of the SCD4x inside the customer device
correctly allows the user to leverage the RH and T output signal. Note that
the temperature offset can depend on various factors such as the SCD4x
measurement mode, self-heating of close components, the ambient temperature
and air flow. Thus, the SCD4x temperature offset should be determined
inside the customer device under its typical operation and in thermal
equilibrium.
.. note:: Only available in idle mode.
"""
[docs] def __init__(self, t_offset):
"""
Constructor.
:param int t_offset:
Temperature offset in degree celsius
"""
super(Scd4xI2cCmdSetTemperatureOffset, self).__init__(
command=0x241D,
tx_data=b"".join([pack(">H", Scd4xTemperatureOffsetDegC(t_offset).ticks)]),
rx_length=None,
read_delay=0.0,
timeout=0,
post_processing_time=0.001,
)
[docs]class Scd4xI2cCmdGetSensorAltitude(Scd4xI2cCmdBase):
"""
Get Sensor Altitude I²C Command
Get configured sensor altitude in meters above sea level. Per default, the
sensor altitude is set to 0 meter above sea-level.
.. note:: Only available in idle mode.
"""
[docs] def __init__(self):
"""
Constructor.
"""
super(Scd4xI2cCmdGetSensorAltitude, self).__init__(
command=0x2322,
tx_data=None,
rx_length=3,
read_delay=0.001,
timeout=0,
post_processing_time=0.0,
)
[docs] def interpret_response(self, data):
"""
Validates the CRCs of the received data from the device and returns
the interpreted data.
:param bytes data:
Received raw bytes from the read operation.
:return: Sensor altitude in meters.
:rtype: int
:raise ~sensirion_i2c_driver.errors.I2cChecksumError:
If a received CRC was wrong.
"""
# check and remove CRCs
checked_data = Scd4xI2cCmdBase.interpret_response(self, data)
# convert raw received data into proper data types
sensor_altitude = int(unpack(">H", checked_data[0:2])[0]) # uint16
return sensor_altitude
[docs]class Scd4xI2cCmdSetSensorAltitude(Scd4xI2cCmdBase):
"""
Set Sensor Altitude I²C Command
Set sensor altitude in meters above sea level. Note that setting a sensor
altitude to the sensor overrides any pressure compensation based on a
previously set ambient pressure.
.. note:: Only available in idle mode.
"""
[docs] def __init__(self, sensor_altitude):
"""
Constructor.
:param int sensor_altitude:
Sensor altitude in meters.
"""
super(Scd4xI2cCmdSetSensorAltitude, self).__init__(
command=0x2427,
tx_data=b"".join([pack(">H", sensor_altitude)]),
rx_length=None,
read_delay=0.0,
timeout=0,
post_processing_time=0.001,
)
[docs]class Scd4xI2cCmdSetAmbientPressure(Scd4xI2cCmdBase):
"""
Set Ambient Pressure I²C Command
The set_ambient_pressure command can be sent during periodic measurements
to enable continuous pressure compensation. Note that setting an ambient
pressure to the sensor overrides any pressure compensation based on a
previously set sensor altitude.
.. note:: Available during measurements.
"""
[docs] def __init__(self, ambient_pressure):
"""
Constructor.
:param int ambient_pressure:
Ambient pressure in hPa. Convert value to Pa by: value * 100.
"""
super(Scd4xI2cCmdSetAmbientPressure, self).__init__(
command=0xE000,
tx_data=b"".join([pack(">H", ambient_pressure)]),
rx_length=None,
read_delay=0.0,
timeout=0,
post_processing_time=0.001,
)
[docs]class Scd4xI2cCmdGetAutomaticSelfCalibration(Scd4xI2cCmdBase):
"""
Get Automatic Self Calibration I²C Command
By default, the ASC is enabled.
"""
[docs] def __init__(self):
"""
Constructor.
"""
super(Scd4xI2cCmdGetAutomaticSelfCalibration, self).__init__(
command=0x2313,
tx_data=None,
rx_length=3,
read_delay=0.001,
timeout=0,
post_processing_time=0.0,
)
[docs] def interpret_response(self, data):
"""
Validates the CRCs of the received data from the device and returns
the interpreted data.
:param bytes data:
Received raw bytes from the read operation.
:return: 1 if ASC is enabled, 0 if ASC is disabled
:rtype: int
:raise ~sensirion_i2c_driver.errors.I2cChecksumError:
If a received CRC was wrong.
"""
# check and remove CRCs
checked_data = Scd4xI2cCmdBase.interpret_response(self, data)
# convert raw received data into proper data types
asc_enabled = int(unpack(">H", checked_data[0:2])[0]) # uint16
return asc_enabled
[docs]class Scd4xI2cCmdSetAutomaticSelfCalibration(Scd4xI2cCmdBase):
"""
Set Automatic Self Calibration I²C Command
By default, the ASC is enabled.
"""
[docs] def __init__(self, asc_enabled):
"""
Constructor.
:param int asc_enabled:
1 to enable ASC, 0 to disable ASC
"""
super(Scd4xI2cCmdSetAutomaticSelfCalibration, self).__init__(
command=0x2416,
tx_data=b"".join([pack(">H", asc_enabled)]),
rx_length=None,
read_delay=0.0,
timeout=0,
post_processing_time=0.001,
)
[docs]class Scd4xI2cCmdStartLowPowerPeriodicMeasurement(Scd4xI2cCmdBase):
"""
Start Low Power Periodic Measurement I²C Command
Start low power periodic measurement, signal update interval is 30 seconds.
.. note:: This command is only available in idle mode.
"""
[docs] def __init__(self):
"""
Constructor.
"""
super(Scd4xI2cCmdStartLowPowerPeriodicMeasurement, self).__init__(
command=0x21AC,
tx_data=None,
rx_length=None,
read_delay=0.0,
timeout=0,
post_processing_time=0.0,
)
[docs]class Scd4xI2cCmdGetDataReadyStatus(Scd4xI2cCmdBase):
"""
Get Data Ready Status I²C Command
Check whether new measurement data is available for read-out.
"""
[docs] def __init__(self):
"""
Constructor.
"""
super(Scd4xI2cCmdGetDataReadyStatus, self).__init__(
command=0xE4B8,
tx_data=None,
rx_length=3,
read_delay=0.001,
timeout=0,
post_processing_time=0.0,
)
[docs] def interpret_response(self, data):
"""
Validates the CRCs of the received data from the device and returns
the interpreted data.
:param bytes data:
Received raw bytes from the read operation.
:return: If last 11 bits are 0 data not ready, else data ready
:rtype: int
:raise ~sensirion_i2c_driver.errors.I2cChecksumError:
If a received CRC was wrong.
"""
# check and remove CRCs
checked_data = Scd4xI2cCmdBase.interpret_response(self, data)
# convert raw received data into proper data types
data_ready = int(unpack(">H", checked_data[0:2])[0]) # uint16
return data_ready
[docs]class Scd4xI2cCmdPersistSettings(Scd4xI2cCmdBase):
"""
Persist Settings I²C Command
Configuration settings such as the temperature offset, sensor altitude and
the ASC enabled/disabled parameter are by default stored in the volatile
memory (RAM) only and will be lost after a power-cycle. This command stores
the current configuration in the EEPROM of the SCD4x, making them
persistent across power-cycling.
.. note:: To avoid unnecessary wear of the EEPROM, this command should only
be sent when persistence is required and if actual changes to the
configuration have been made (The EEPROM is guaranteed to endure
at least 2000 write cycles before failure). Note that field
calibration history (i.e. FRC and ASC) is automatically stored in
a separate EEPROM dimensioned for specified sensor lifetime.
"""
[docs] def __init__(self):
"""
Constructor.
"""
super(Scd4xI2cCmdPersistSettings, self).__init__(
command=0x3615,
tx_data=None,
rx_length=None,
read_delay=0.0,
timeout=0,
post_processing_time=0.8,
)
[docs]class Scd4xI2cCmdGetSerialNumber(Scd4xI2cCmdBase):
"""
Get Serial Number I²C Command
Reading out the serial number can be used to identify the chip and to
verify the presence of the sensor. The get serial number command returns 3
words. Together, the 3 words constitute a unique serial number with a
length of 48 bits (big endian format).
"""
[docs] def __init__(self):
"""
Constructor.
"""
super(Scd4xI2cCmdGetSerialNumber, self).__init__(
command=0x3682,
tx_data=None,
rx_length=9,
read_delay=0.001,
timeout=0,
post_processing_time=0.0,
)
[docs] def interpret_response(self, data):
"""
Validates the CRCs of the received data from the device and returns
the interpreted data.
:param bytes data:
Received raw bytes from the read operation.
:return:
- serial_number (int) - 48 bit serial number
:rtype: tuple
:raise ~sensirion_i2c_driver.errors.I2cChecksumError:
If a received CRC was wrong.
"""
# check and remove CRCs
checked_data = Scd4xI2cCmdBase.interpret_response(self, data)
# convert raw received data into proper data types
serial_0 = int(unpack(">H", checked_data[0:2])[0]) # uint16
serial_1 = int(unpack(">H", checked_data[2:4])[0]) # uint16
serial_2 = int(unpack(">H", checked_data[4:6])[0]) # uint16
return serial_0 << 32 | serial_1 << 16 | serial_2
[docs]class Scd4xI2cCmdReinit(Scd4xI2cCmdBase):
"""
Reinit I²C Command
The reinit command reinitializes the sensor by reloading user settings from
EEPROM. Before sending the reinit command, the stop measurement command
must be issued. If reinit command does not trigger the desired
re-initialization, a power-cycle should be applied to the SCD4x.
.. note:: Only available in idle mode.
"""
[docs] def __init__(self):
"""
Constructor.
"""
super(Scd4xI2cCmdReinit, self).__init__(
command=0x3646,
tx_data=None,
rx_length=None,
read_delay=0.0,
timeout=0,
post_processing_time=0.02,
)
[docs]class Scd4xI2cCmdMeasureSingleShot(Scd4xI2cCmdBase):
"""
Measure Single Shot I²C Command
On-demand measurement of CO₂ concentration, relative humidity and
temperature. The sensor output is read with the read_measurement command.
.. note:: Only available in idle mode.
"""
[docs] def __init__(self):
"""
Constructor.
"""
super(Scd4xI2cCmdMeasureSingleShot, self).__init__(
command=0x219D,
tx_data=None,
rx_length=None,
read_delay=0.0,
timeout=0,
post_processing_time=5.0,
)
[docs]class Scd4xI2cCmdMeasureSingleShotRhtOnly(Scd4xI2cCmdBase):
"""
Measure Single Shot Rht Only I²C Command
On-demand measurement of relative humidity and temperature only.
.. note:: Only available in idle mode.
"""
[docs] def __init__(self):
"""
Constructor.
"""
super(Scd4xI2cCmdMeasureSingleShotRhtOnly, self).__init__(
command=0x2196,
tx_data=None,
rx_length=None,
read_delay=0.0,
timeout=0,
post_processing_time=0.05,
)
[docs]class Scd4xI2cCmdPowerDown(Scd4xI2cCmdBase):
"""
Power Down I²C Command
Put the sensor from idle to sleep mode to reduce current consumption.
.. note:: Only available in idle mode.
"""
[docs] def __init__(self):
"""
Constructor.
"""
super(Scd4xI2cCmdPowerDown, self).__init__(
command=0x36E0,
tx_data=None,
rx_length=None,
read_delay=0.0,
timeout=0,
post_processing_time=0.001,
)
[docs]class Scd4xI2cCmdWakeUp(Scd4xI2cCmdBase):
"""
Wake Up I²C Command
Wake up sensor from sleep mode to idle mode.
.. note:: Only available in sleep mode.
"""
[docs] def __init__(self):
"""
Constructor.
"""
super(Scd4xI2cCmdWakeUp, self).__init__(
command=0x36F6,
tx_data=None,
rx_length=None,
read_delay=0.0,
timeout=0,
post_processing_time=0.02,
)