diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 4f03581..d990a98 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -8,6 +8,4 @@ on: jobs: build_and_deploy: uses: sensirion/.github/.github/workflows/driver.python.pypi_publish.yml@main - secrets: - PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }} - + secrets: inherit diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index d200c56..d2d0f78 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -2,9 +2,11 @@ name: Publish Documentation on: workflow_dispatch: push: - tags: - - "[0-9]+.[0-9]+.[0-9]+" + branches: + - main jobs: documentation: uses: sensirion/.github/.github/workflows/driver.python.documentation.yml@main + with: + use-project-requirements: true diff --git a/.github/workflows/gh_workflow_metadata_update.yml b/.github/workflows/gh_workflow_metadata_update.yml new file mode 100644 index 0000000..7dd70ed --- /dev/null +++ b/.github/workflows/gh_workflow_metadata_update.yml @@ -0,0 +1,11 @@ +name: Driver Index Metadata Update + +on: + push: + branches: + - main + +jobs: + driver-index-metadata-update: + uses: sensirion/.github/.github/workflows/driver.common.di_metadata_update.yml@main + secrets: inherit diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c15f72f..b1cebfa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,9 +8,5 @@ on: - main jobs: - dg-metadata-validation: - if: github.event_name == 'push' && github.ref != 'refs/head/main' - uses: sensirion/.github/.github/workflows/driver.generated.metadata_check.yml@main - test: uses: sensirion/.github/.github/workflows/driver.python.test.yml@main diff --git a/CHANGELOG.md b/CHANGELOG.md index 9670de3..aa28b7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.4.0] - 2026-6-5 + +### Added + +- Added get_product_type command. +## [1.0.1] - 2026-2-13 + +### Fixed + +- Version mismatch in library.properties +## [1.0.0] - 2025-3-13 + ### Added - Support for all I2C commands for the SEN65 as described in the datasheet. +[Unreleased]: https://github.com/Sensirion/python-i2c-sen65/compare/1.4.0...HEAD +[1.4.0]: https://github.com/Sensirion/python-i2c-sen65/compare/1.0.1...1.4.0 +[1.0.1]: https://github.com/Sensirion/python-i2c-sen65/compare/1.0.0...1.0.1 +[1.0.0]: https://github.com/Sensirion/python-i2c-sen65/releases/tag/1.0.0 \ No newline at end of file diff --git a/LICENSE b/LICENSE index 2062766..8fe04ed 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2025, Sensirion AG +Copyright (c) 2026, Sensirion AG All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index 7efac04..cbc1445 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This repository contains the Python driver to communicate with a Sensirion SEN65 sensor over I2C. -SEN65 picture @@ -10,18 +10,18 @@ Click [here](https://sensirion.com/sen6x-air-quality-sensor-platform) to learn m -The default I²C address of [SEN65](https://www.sensirion.com/products/catalog/SEN65) is **0x6B**. +The default I²C address of [SEN65](https://www.sensirion.com/products/catalog/SEN65) is **0x6b**. ## Connect the sensor -You can connect your sensor over a [SEK-SensorBridge](https://developer.sensirion.com/sensirion-products/sek-sensorbridge/). +You can connect your sensor over a [SEK-SensorBridge](https://developer.sensirion.com/product-support/sek-sensorbridge/). For special setups you find the sensor pinout in the section below.
Sensor pinout

-sensor wiring picture | *Pin* | *Cable Color* | *Name* | *Description* | *Comments* | diff --git a/ci/checkin_doc.sh b/ci/checkin_doc.sh deleted file mode 100644 index 1031751..0000000 --- a/ci/checkin_doc.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# set shell settings (see https://sipb.mit.edu/doc/safe-shell/) -set -euv -o pipefail - -# clone the repo into a subfolder html, checkout the gh-pages into this folder and commit the freshly generated html -git clone "git@gitlab:${CI_PROJECT_PATH}.git" html -cd html -git checkout gh-pages -rm -f empty.txt -rm -f *.html -rm -f *.js -rm -rf _* -cd .. -# make sure to copy .nojekyll -cp -rf public/.[!.]* public/* html -cd html -git add . - -if git diff-index --quiet HEAD -- -then - exit 0 -fi - -git commit -m"Automatic doc update: ${CI_COMMIT_SHORT_SHA}" -git push \ No newline at end of file diff --git a/ci/set_git_config.sh b/ci/set_git_config.sh deleted file mode 100644 index 860c6aa..0000000 --- a/ci/set_git_config.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -# set shell settings (see https://sipb.mit.edu/doc/safe-shell/) -set -eufv -o pipefail - -# install ssh private key (set as CI variable in GitLab project settings) -mkdir -p ~/.ssh -echo "$SSH_DEPLOY_KEY" > ~/.ssh/id_rsa -chmod 400 ~/.ssh/id_rsa - -# change remote URL to SSH to allow pushing with SSH -git remote set-url --push origin "git@gitlab:${CI_PROJECT_PATH}.git" - -# set git author -git config --global user.name "GitLab-CI" -git config --global user.email "<>" diff --git a/docs/conf.py b/docs/conf.py index a154d77..881091e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,11 +4,12 @@ # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html + import os import sys from datetime import datetime -import pkg_resources +import importlib.metadata as metadata import sphinx.ext.autodoc import sensirion_i2c_sen65 @@ -17,16 +18,16 @@ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) # -- Project information ----------------------------------------------------- -distribution = pkg_resources.get_distribution('sensirion_i2c_sen65') +distribution = metadata.distribution("sensirion_i2c_sen65") -project = u'sensirion_i2c_sen65' +project = distribution.name copyright = u'{} Sensirion AG, Switzerland'.format(datetime.now().year) author = 'Sensirion AG' # The short X.Y version -version = sensirion_i2c_sen65.__version__ +version = distribution.version # The full version, including alpha/beta/rc tags -release = sensirion_i2c_sen65.__version__ +release = distribution.version # -- General configuration --------------------------------------------------- diff --git a/docs/execute-measurements.rst b/docs/execute-measurements.rst index deb86d1..e9b665f 100644 --- a/docs/execute-measurements.rst +++ b/docs/execute-measurements.rst @@ -26,7 +26,7 @@ execute a simple measurement. python examples/example_usage_sensorbridge_sen65.py --serial-port -.. _Sensirion SEK-SensorBridge: https://developer.sensirion.com/sensirion-products/sek-sensorbridge/ +.. _Sensirion SEK-SensorBridge: https://developer.sensirion.com/product-support/sek-sensorbridge/ .. _SensorBridge FTDI Driver Installation: https://sensirion.github.io/python-shdlc-sensorbridge/sensor-bridge-installation.html Example script diff --git a/examples/example_usage_linux_sen65.py b/examples/example_usage_linux_sen65.py index ece4e5c..a4dfb2e 100644 --- a/examples/example_usage_linux_sen65.py +++ b/examples/example_usage_linux_sen65.py @@ -1,13 +1,13 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# (c) Copyright 2025 Sensirion AG, Switzerland +# (c) Copyright 2026 Sensirion AG, Switzerland # # THIS FILE IS AUTOMATICALLY GENERATED! # -# Generator: sensirion-driver-generator 1.1.2 +# Generator: sensirion-driver-generator 1.7.0 # Product: sen65 -# Model-Version: 1.3.0 +# Model-Version: 1.4.0 # import argparse diff --git a/examples/example_usage_sensorbridge_sen65.py b/examples/example_usage_sensorbridge_sen65.py index 9e72978..dade178 100644 --- a/examples/example_usage_sensorbridge_sen65.py +++ b/examples/example_usage_sensorbridge_sen65.py @@ -1,13 +1,13 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# (c) Copyright 2025 Sensirion AG, Switzerland +# (c) Copyright 2026 Sensirion AG, Switzerland # # THIS FILE IS AUTOMATICALLY GENERATED! # -# Generator: sensirion-driver-generator 1.1.2 +# Generator: sensirion-driver-generator 1.7.0 # Product: sen65 -# Model-Version: 1.3.0 +# Model-Version: 1.4.0 # import argparse diff --git a/images/product-image-sen6x.png b/images/product-image-sen6x.png new file mode 100644 index 0000000..b97bde3 Binary files /dev/null and b/images/product-image-sen6x.png differ diff --git a/images/product-pinout-sen6x.png b/images/product-pinout-sen6x.png new file mode 100644 index 0000000..a940971 Binary files /dev/null and b/images/product-pinout-sen6x.png differ diff --git a/metadata.yml b/metadata.yml index f5643fb..5e670ff 100644 --- a/metadata.yml +++ b/metadata.yml @@ -1,7 +1,7 @@ # driver generation metadata -generator_version: 1.1.2 -model_version: 1.3.0 +generator_version: 1.7.0 +model_version: 1.4.0 dg_status: released is_manually_modified: false first_generated: '2025-02-17 07:46' -last_generated: '2025-03-13 10:28' +last_generated: '2026-06-05 09:32' diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2f89f98 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,68 @@ +[build-system] +requires = ["hatchling >= 1.26", "wheel >= 0.45.0"] +build-backend = "hatchling.build" + +[project] +name = "sensirion_i2c_sen65" +description = "I2C driver for the Sensirion SEN65 sensor family" + +readme = "README.md" +version = "1.4.0" + +requires-python = ">=3.8.4,<4.0" + +authors = [ + { name = "Sensirion", email = "info@sensirion.com" }, +] + +license = "BSD-3-Clause" +license-files = ["LICENSE"] + +keywords = [ + "Sensirion SEN65", + "I2C", + "SEN65", + ] + +classifiers = [ + "Intended Audience :: Developers", + "Topic :: System :: Hardware :: Hardware Drivers", + "Topic :: Software Development :: Libraries :: Python Modules", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +] + +dependencies = [ + "sensirion-driver-adapters>=2.3.0,<3.0", + "sensirion-driver-support-types>=1.2.0,<2.0", + "sensirion-i2c-driver>=1.0,<2.0", + "sensirion-shdlc-sensorbridge>=0.1.0,<2.0" + ] + +[project.optional-dependencies] + +docs=[ + "jinja2~=3.1.6", + "sphinx-rtd-theme==3.0.2", + "sphinx>=7.0,<8.0;python_version < '3.11'", + "sphinx==8.2.3;python_version >= '3.11'", + "lazy-object-proxy ~=1.7.1", + "sphinx-autoapi~=3.0.0", +] + +test= [ + "flake8>=7.1.0", + "mock~=5.2.0", + "pytest>=8.3.5", + "pytest-cov>=5.0.0", + "mypy~=1.13.0", + "setuptools>=73.2.0" +] + +[project.urls] +Changelog = "https://github.com/Sensirion/python-i2c-sen65/blob/master/CHANGELOG.md" +Repository = "https://github.com/Sensirion/python-i2c-sen65" +Documentation = "https://sensirion.github.io/python-i2c-sen65" + diff --git a/sensirion_i2c_sen65/commands.py b/sensirion_i2c_sen65/commands.py index 3c40b10..91b2b7f 100644 --- a/sensirion_i2c_sen65/commands.py +++ b/sensirion_i2c_sen65/commands.py @@ -1,13 +1,13 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# (c) Copyright 2025 Sensirion AG, Switzerland +# (c) Copyright 2026 Sensirion AG, Switzerland # # THIS FILE IS AUTOMATICALLY GENERATED! # -# Generator: sensirion-driver-generator 1.1.2 +# Generator: sensirion-driver-generator 1.7.0 # Product: sen65 -# Model-Version: 1.3.0 +# Model-Version: 1.4.0 # """ The transfer classes specify the data that is transferred between host and sensor. The generated transfer classes @@ -26,241 +26,228 @@ class DeviceStatus(BitfieldContainer): rht_error = BitField(offset=6, width=1) gas_error = BitField(offset=7, width=1) reserved3 = BitField(offset=8, width=1) - reserved4 = BitField(offset=9, width=1) - reserved5 = BitField(offset=10, width=1) + co2_2_error = BitField(offset=9, width=1) + hcho_error = BitField(offset=10, width=1) pm_error = BitField(offset=11, width=1) - reserved6 = BitField(offset=12, width=1) - reserved7 = BitField(offset=13, width=8) + co2_1_error = BitField(offset=12, width=1) + reserved4 = BitField(offset=13, width=8) fan_speed_warning = BitField(offset=21, width=1) + reserved5 = BitField(offset=22, width=10) -class StartContinuousMeasurement(Transfer): - """ - 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. - """ +class GetVocAlgorithmTuningParameters(Transfer): + """Gets the parameters to customize the VOC algorithm.""" - CMD_ID = 0x21 + CMD_ID = 0x60d0 def pack(self): return self.tx_data.pack([]) - tx = TxData(CMD_ID, '>H', device_busy_delay=0.05, slave_address=None, ignore_ack=False) + tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) + rx = RxData('>hhhhhh') -class StopMeasurement(Transfer): +class SetVocAlgorithmTuningParameters(Transfer): """ - 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. + Sets the parameters to customize the VOC algorithm. + This configuration is volatile, i.e. the parameters will be + reverted to their default values after a device reset. """ - CMD_ID = 0x104 + CMD_ID = 0x60d0 + + def __init__(self, index_offset, learning_time_offset_hours, learning_time_gain_hours, gating_max_duration_minutes, + std_initial, gain_factor): + self._index_offset = index_offset + self._learning_time_offset_hours = learning_time_offset_hours + self._learning_time_gain_hours = learning_time_gain_hours + self._gating_max_duration_minutes = gating_max_duration_minutes + self._std_initial = std_initial + self._gain_factor = gain_factor def pack(self): - return self.tx_data.pack([]) + return self.tx_data.pack([self._index_offset, self._learning_time_offset_hours, self._learning_time_gain_hours, + self._gating_max_duration_minutes, self._std_initial, self._gain_factor]) - tx = TxData(CMD_ID, '>H', device_busy_delay=1.0, slave_address=None, ignore_ack=False) + tx = TxData(CMD_ID, '>Hhhhhhh', device_busy_delay=0.02, slave_address=None, ignore_ack=False) -class GetDataReady(Transfer): +class GetVocAlgorithmState(Transfer): """ - 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. + Gets the current VOC algorithm state. + Allows to backup and restore the VOC algorithm state to resume operation after a power cycle or + device reset, skipping initial learning phase. By default, the VOC Engine is reset and the algorithm + state is retained if a measurement is stopped and started again. If the VOC algorithm + state shall be reset, a device reset or a power cycle can be executed. """ - CMD_ID = 0x202 + CMD_ID = 0x6181 def pack(self): return self.tx_data.pack([]) tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) - rx = RxData('>B?') + rx = RxData('>8B') -class ReadMeasuredValuesAsIntegers(Transfer): +class SetVocAlgorithmState(Transfer): """ - 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). + Sets the VOC algorithm state previously received with "Get VOC Algorithm State" command. + Allows restoration of the VOC algorithm state to resume operation after a power cycle or device + reset, skipping initial learning phase. By default, the VOC Engine is reset, and the algorithm state is retained if + a measurement is stopped and started again. If the VOC algorithm state shall be reset, a device reset, or a + power cycle can be executed. """ - CMD_ID = 0x446 + CMD_ID = 0x6181 + + def __init__(self, state): + self._state = state def pack(self): - return self.tx_data.pack([]) + return self.tx_data.pack([self._state]) - tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) - rx = RxData('>HHHHhhhh') + tx = TxData(CMD_ID, '>H8B') -class ReadNumberConcentrationValuesAsIntegers(Transfer): - """ - 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). - """ +class GetNoxAlgorithmTuningParameters(Transfer): + """Gets the parameters to customize the NOx algorithm.""" - CMD_ID = 0x316 + CMD_ID = 0x60e1 def pack(self): return self.tx_data.pack([]) tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) - rx = RxData('>HHHHH') + rx = RxData('>hhhhhh') -class ReadMeasuredRawValues(Transfer): +class SetNoxAlgorithmTuningParameters(Transfer): """ - 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). + Sets the parameters to customize the NOx algorithm. + This configuration is volatile, i.e. the parameters will be + reverted to their default values after a device reset. """ - CMD_ID = 0x455 + CMD_ID = 0x60e1 + + def __init__(self, index_offset, learning_time_offset_hours, learning_time_gain_hours, gating_max_duration_minutes, + std_initial, gain_factor): + self._index_offset = index_offset + self._learning_time_offset_hours = learning_time_offset_hours + self._learning_time_gain_hours = learning_time_gain_hours + self._gating_max_duration_minutes = gating_max_duration_minutes + self._std_initial = std_initial + self._gain_factor = gain_factor def pack(self): - return self.tx_data.pack([]) + return self.tx_data.pack([self._index_offset, self._learning_time_offset_hours, self._learning_time_gain_hours, + self._gating_max_duration_minutes, self._std_initial, self._gain_factor]) - tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) - rx = RxData('>hhHH') + tx = TxData(CMD_ID, '>Hhhhhhh', device_busy_delay=0.02, slave_address=None, ignore_ack=False) -class StartFanCleaning(Transfer): +class StartContinuousMeasurement(Transfer): """ - 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. + 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. """ - CMD_ID = 0x5607 + CMD_ID = 0x21 def pack(self): return self.tx_data.pack([]) - tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) + tx = TxData(CMD_ID, '>H', device_busy_delay=0.05, slave_address=None, ignore_ack=False) -class SetTemperatureOffsetParameters(Transfer): +class StopMeasurement(Transfer): """ - 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. + 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. """ - CMD_ID = 0x60b2 - - def __init__(self, offset, slope, time_constant, slot): - self._offset = offset - self._slope = slope - self._time_constant = time_constant - self._slot = slot + CMD_ID = 0x104 def pack(self): - return self.tx_data.pack([self._offset, self._slope, self._time_constant, self._slot]) + return self.tx_data.pack([]) - tx = TxData(CMD_ID, '>HhhHH', device_busy_delay=0.02, slave_address=None, ignore_ack=False) + tx = TxData(CMD_ID, '>H', device_busy_delay=1.4, slave_address=None, ignore_ack=False) -class SetVocAlgorithmTuningParameters(Transfer): +class GetDataReady(Transfer): """ - Sets the parameters to customize the VOC algorithm. - This configuration is volatile, i.e. the parameters will be - reverted to their default values after a device reset. + 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. """ - CMD_ID = 0x60d0 - - def __init__(self, index_offset, learning_time_offset_hours, learning_time_gain_hours, gating_max_duration_minutes, - std_initial, gain_factor): - self._index_offset = index_offset - self._learning_time_offset_hours = learning_time_offset_hours - self._learning_time_gain_hours = learning_time_gain_hours - self._gating_max_duration_minutes = gating_max_duration_minutes - self._std_initial = std_initial - self._gain_factor = gain_factor + CMD_ID = 0x202 def pack(self): - return self.tx_data.pack([self._index_offset, self._learning_time_offset_hours, self._learning_time_gain_hours, - self._gating_max_duration_minutes, self._std_initial, self._gain_factor]) + return self.tx_data.pack([]) - tx = TxData(CMD_ID, '>Hhhhhhh', device_busy_delay=0.02, slave_address=None, ignore_ack=False) + tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) + rx = RxData('>B?') -class GetVocAlgorithmTuningParameters(Transfer): - """Gets the parameters to customize the VOC algorithm.""" +class ReadNumberConcentrationValuesAsIntegers(Transfer): + """ + Returns the measured number concentration values. + The command 0x0202 "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). + """ - CMD_ID = 0x60d0 + CMD_ID = 0x316 def pack(self): return self.tx_data.pack([]) tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) - rx = RxData('>hhhhhh') + rx = RxData('>HHHHH') -class SetNoxAlgorithmTuningParameters(Transfer): - """ - Sets the parameters to customize the NOx algorithm. - This configuration is volatile, i.e. the parameters will be - reverted to their default values after a device reset. +class SetTemperatureOffsetParameters(Transfer): """ + This command allows to compensate temperature effects of the + design-in at customer side by applying custom temperature offsets + to the ambient temperature. - CMD_ID = 0x60e1 - - def __init__(self, index_offset, learning_time_offset_hours, learning_time_gain_hours, gating_max_duration_minutes, - std_initial, gain_factor): - self._index_offset = index_offset - self._learning_time_offset_hours = learning_time_offset_hours - self._learning_time_gain_hours = learning_time_gain_hours - self._gating_max_duration_minutes = gating_max_duration_minutes - self._std_initial = std_initial - self._gain_factor = gain_factor - - def pack(self): - return self.tx_data.pack([self._index_offset, self._learning_time_offset_hours, self._learning_time_gain_hours, - self._gating_max_duration_minutes, self._std_initial, self._gain_factor]) + The compensated ambient temperature is calculated as follows: - tx = TxData(CMD_ID, '>Hhhhhhh', device_busy_delay=0.02, slave_address=None, ignore_ack=False) + * 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. + """ -class GetNoxAlgorithmTuningParameters(Transfer): - """Gets the parameters to customize the NOx algorithm.""" + CMD_ID = 0x60b2 - CMD_ID = 0x60e1 + def __init__(self, offset, slope, time_constant, slot): + self._offset = offset + self._slope = slope + self._time_constant = time_constant + self._slot = slot def pack(self): - return self.tx_data.pack([]) + return self.tx_data.pack([self._offset, self._slope, self._time_constant, self._slot]) - tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) - rx = RxData('>hhhhhh') + tx = TxData(CMD_ID, '>HhhHH', device_busy_delay=0.02, slave_address=None, ignore_ack=False) class SetTemperatureAccelerationParameters(Transfer): @@ -284,157 +271,197 @@ def pack(self): tx = TxData(CMD_ID, '>HHHHH', device_busy_delay=0.02, slave_address=None, ignore_ack=False) -class SetVocAlgorithmState(Transfer): +class GetProductType(Transfer): """ - Sets the VOC algorithm state previously received with "Get VOC Algorithm State" command. - Allows restoration of the VOC algorithm state to resume operation after a power cycle or device - reset, skipping initial learning phase. By default, the VOC Engine is reset, and the algorithm state is retained if - a measurement is stopped and started again. If the VOC algorithm state shall be reset, a device reset, or a - power cycle can be executed. + Gets the product type from the device. + + The following product types are expected: + - SEN62: '00085800' + - SEN63C: '00085700' + - SEN65: '00085200' + - SEN66: '00085300' + - SEN68: '00085400' + - SEN69C: '00085900' """ - CMD_ID = 0x6181 + CMD_ID = 0xd002 - def __init__(self, state): - self._state = state + def pack(self): + return self.tx_data.pack([]) + + tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) + rx = RxData('>32s') + + +class GetProductName(Transfer): + """Gets the product name from the device.""" + + CMD_ID = 0xd014 def pack(self): - return self.tx_data.pack([self._state]) + return self.tx_data.pack([]) - tx = TxData(CMD_ID, '>H8B') + tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) + rx = RxData('>32s') -class GetVocAlgorithmState(Transfer): - """ - Gets the current VOC algorithm state. - Allows to backup and restore the VOC algorithm state to resume operation after a power cycle or - device reset, skipping initial learning phase. By default, the VOC Engine is reset and the algorithm - state is retained if a measurement is stopped and started again. If the VOC algorithm - state shall be reset, a device reset or a power cycle can be executed. - """ +class GetSerialNumber(Transfer): + """Gets the serial number from the device.""" - CMD_ID = 0x6181 + CMD_ID = 0xd033 def pack(self): return self.tx_data.pack([]) tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) - rx = RxData('>8B') + rx = RxData('>32s') -class ActivateShtHeater(Transfer): +class ReadDeviceStatus(Transfer): """ - 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). + 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. """ - CMD_ID = 0x6765 + CMD_ID = 0xd206 def pack(self): return self.tx_data.pack([]) tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) + rx = RxData('>I') -class GetShtHeaterMeasurements(Transfer): +class ReadAndClearDeviceStatus(Transfer): """ - 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. + Reads the current device status (like command 0xD206 "Read Device Status") and afterwards clears + all flags. """ - CMD_ID = 0x6790 + CMD_ID = 0xd210 def pack(self): return self.tx_data.pack([]) tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) - rx = RxData('>hh') + rx = RxData('>I') -class GetProductName(Transfer): - """Gets the product name from the device.""" +class GetVersion(Transfer): + """Gets the version information for the firmware.""" - CMD_ID = 0xd014 + CMD_ID = 0xd100 def pack(self): return self.tx_data.pack([]) tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) - rx = RxData('>32s') + rx = RxData('>BB') -class GetSerialNumber(Transfer): - """Gets the serial number from the device.""" +class DeviceReset(Transfer): + """Executes a reset on the device. This has the same effect as a power cycle.""" - CMD_ID = 0xd033 + CMD_ID = 0xd304 + + def pack(self): + return self.tx_data.pack([]) + + tx = TxData(CMD_ID, '>H', device_busy_delay=1.2, slave_address=None, ignore_ack=False) + + +class StartFanCleaning(Transfer): + """ + 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. + """ + + CMD_ID = 0x5607 def pack(self): return self.tx_data.pack([]) tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) - rx = RxData('>32s') -class GetVersion(Transfer): - """Gets the version information for the hardware, firmware and communication protocol.""" +class ActivateShtHeater(Transfer): + """ + Activate the heater feature of the SHT4x sensor. + This command allows to use the inbuilt heater in SHT sensor + to decontaminate and reverse creep at high humidity. + This command activates the SHT sensor heater with 200mW for 1s. + The SHT heater measurement done just before deactivation can be + read using the command \"Get SHT Heater Measurements\" after + the duration of the heating feature as specified in the SHT4x + datasheet. + Wait at least 20s after this command before starting a measurement + to get coherent temperature values (heating consequence to disappear). + """ - CMD_ID = 0xd100 + CMD_ID = 0x6765 def pack(self): return self.tx_data.pack([]) tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) - rx = RxData('>BB') -class ReadDeviceStatus(Transfer): +class GetShtHeaterMeasurements(Transfer): """ - 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. + Get the measurement values when the SHT sensor heating + is finished. """ - CMD_ID = 0xd206 + CMD_ID = 0x6790 def pack(self): return self.tx_data.pack([]) tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) - rx = RxData('>I') + rx = RxData('>hh') -class ReadAndClearDeviceStatus(Transfer): +class ReadMeasuredValuesAsIntegers(Transfer): """ - Reads the current device status (like command 0xD206 "Read Device Status") and afterwards clears - all flags. + 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). """ - CMD_ID = 0xd210 + CMD_ID = 0x446 def pack(self): return self.tx_data.pack([]) tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) - rx = RxData('>I') + rx = RxData('>HHHHhhhh') -class DeviceReset(Transfer): - """Executes a reset on the device. This has the same effect as a power cycle.""" +class ReadMeasuredRawValues(Transfer): + """ + 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). + """ - CMD_ID = 0xd304 + CMD_ID = 0x455 def pack(self): return self.tx_data.pack([]) - tx = TxData(CMD_ID, '>H', device_busy_delay=1.2, slave_address=None, ignore_ack=False) + tx = TxData(CMD_ID, '>H', device_busy_delay=0.02, slave_address=None, ignore_ack=False) + rx = RxData('>hhHH') diff --git a/sensirion_i2c_sen65/device.py b/sensirion_i2c_sen65/device.py index e959db1..b7ff22d 100644 --- a/sensirion_i2c_sen65/device.py +++ b/sensirion_i2c_sen65/device.py @@ -1,13 +1,13 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# (c) Copyright 2025 Sensirion AG, Switzerland +# (c) Copyright 2026 Sensirion AG, Switzerland # # THIS FILE IS AUTOMATICALLY GENERATED! # -# Generator: sensirion-driver-generator 1.1.2 +# Generator: sensirion-driver-generator 1.7.0 # Product: sen65 -# Model-Version: 1.3.0 +# Model-Version: 1.4.0 # """ The class Sen65DeviceBase implements the low level interface of the sensor. @@ -18,8 +18,8 @@ from sensirion_driver_adapters.transfer import execute_transfer from sensirion_driver_support_types.mixin_access import MixinAccess from sensirion_i2c_sen65.commands import (ActivateShtHeater, DeviceReset, DeviceStatus, GetDataReady, - GetNoxAlgorithmTuningParameters, GetProductName, GetSerialNumber, - GetShtHeaterMeasurements, GetVersion, GetVocAlgorithmState, + GetNoxAlgorithmTuningParameters, GetProductName, GetProductType, + GetSerialNumber, GetShtHeaterMeasurements, GetVersion, GetVocAlgorithmState, GetVocAlgorithmTuningParameters, ReadAndClearDeviceStatus, ReadDeviceStatus, ReadMeasuredRawValues, ReadMeasuredValuesAsIntegers, ReadNumberConcentrationValuesAsIntegers, SetNoxAlgorithmTuningParameters, @@ -27,12 +27,8 @@ SetVocAlgorithmState, SetVocAlgorithmTuningParameters, StartContinuousMeasurement, StartFanCleaning, StopMeasurement) -from sensirion_i2c_sen65.result_types import (SignalHumidity, SignalMassConcentrationPm10p0, - SignalMassConcentrationPm1p0, SignalMassConcentrationPm2p5, - SignalMassConcentrationPm4p0, SignalNoxIndex, - SignalNumberConcentrationPm0p5, SignalNumberConcentrationPm10p0, - SignalNumberConcentrationPm1p0, SignalNumberConcentrationPm2p5, - SignalNumberConcentrationPm4p0, SignalTemperature, SignalVocIndex) +from sensirion_i2c_sen65.result_types import (SignalDividedBy10Int16, SignalDividedBy10Uint16, SignalHumidity, + SignalTemperature) class Sen65DeviceBase: @@ -45,192 +41,31 @@ def __init__(self, channel): def channel(self): return self._channel - def start_continuous_measurement(self): - """ - 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. - """ - transfer = StartContinuousMeasurement() - return execute_transfer(self._channel, transfer) - - def stop_measurement(self): - """ - 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. - """ - transfer = StopMeasurement() - return execute_transfer(self._channel, transfer) - - def get_data_ready(self): - """ - 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. - - :return padding: - Padding byte, always 0x00. - :return data_ready: - True (0x01) if data is ready, False (0x00) if not. When no measurement is running, False will be - returned. - """ - transfer = GetDataReady() - return execute_transfer(self._channel, transfer) - - def read_measured_values_as_integers(self): - """ - 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). - - :return mass_concentration_pm1p0: - Value is scaled with factor 10: PM1.0 [µg/m³] = value / 10 - *Note: If this value is unknown, 0xFFFF is returned.* - :return mass_concentration_pm2p5: - Value is scaled with factor 10: PM2.5 [µg/m³] = value / 10 - *Note: If this value is unknown, 0xFFFF is returned.* - :return mass_concentration_pm4p0: - Value is scaled with factor 10: PM4.0 [µg/m³] = value / 10 - *Note: If this value is unknown, 0xFFFF is returned.* - :return mass_concentration_pm10p0: - Value is scaled with factor 10: PM10.0 [µg/m³] = value / 10 - *Note: If this value is unknown, 0xFFFF is returned.* - :return ambient_humidity: - Value is scaled with factor 100: RH [%] = value / 100 - *Note: If this value is unknown, 0x7FFF is returned.* - :return ambient_temperature: - Value is scaled with factor 200: T [°C] = value / 200 - *Note: If this value is unknown, 0x7FFF is returned.* - :return voc_index: - Value is scaled with factor 10: VOC Index = value / 10 - *Note: If this value is unknown, 0x7FFF is returned.* - :return nox_index: - Value is scaled with factor 10: NOx Index = value / 10 - *Note: If this value is unknown, 0x7FFF is returned. During - the first 10..11 seconds after power-on or device reset, this - value will be 0x7FFF as well.* - """ - transfer = ReadMeasuredValuesAsIntegers() - return execute_transfer(self._channel, transfer) - - def read_number_concentration_values_as_integers(self): - """ - 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). - - :return number_concentration_pm0p5: - Value is scaled with factor 10: PM0.5 [particles/cm³] = value / 10 - *Note: If this value is unknown, 0xFFFF is returned.* - :return number_concentration_pm1p0: - Value is scaled with factor 10: PM1.0 [particles/cm³] = value / 10 - *Note: If this value is unknown, 0xFFFF is returned.* - :return number_concentration_pm2p5: - Value is scaled with factor 10: PM2.5 [particles/cm³] = value / 10 - *Note: If this value is unknown, 0xFFFF is returned.* - :return number_concentration_pm4p0: - Value is scaled with factor 10: PM4.0 [particles/cm³] = value / 10 - *Note: If this value is unknown, 0xFFFF is returned.* - :return number_concentration_pm10p0: - Value is scaled with factor 10: PM10.0 [particles/cm³] = value / 10 - *Note: If this value is unknown, 0xFFFF is returned.* - """ - transfer = ReadNumberConcentrationValuesAsIntegers() - return execute_transfer(self._channel, transfer) - - def read_measured_raw_values(self): - """ - 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). - - :return raw_humidity: - Value is scaled with factor 100: RH [%] = value / 100 - *Note: If this value is unknown, 0x7FFF is returned.* - :return raw_temperature: - Value is scaled with factor 200: T [°C] = value / 200 - *Note: If this value is unknown, 0x7FFF is returned.* - :return raw_voc: - Raw measured VOC ticks without scale factor. - *Note: If this value is unknown, 0xFFFF is returned.* - :return raw_nox: - Raw measured NOx ticks without scale factor. - *Note: If this value is unknown, 0xFFFF is returned. During - the first 10..11 seconds after power-on or device reset, this - value will be 0xFFFF as well.* + def get_voc_algorithm_tuning_parameters(self): """ - transfer = ReadMeasuredRawValues() - return execute_transfer(self._channel, transfer) + Gets the parameters to customize the VOC algorithm. - def start_fan_cleaning(self): - """ - 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. + :return index_offset: + VOC index representing typical (average) conditions. + :return learning_time_offset_hours: + Time constant to estimate the VOC algorithm offset from the history in hours. Past events will be + forgotten after about twice the learning time. + :return learning_time_gain_hours: + Time constant to estimate the VOC algorithm gain from the history in hours. Past events will be + forgotten after about twice the learning time. + :return gating_max_duration_minutes: + Maximum duration of gating in minutes (freeze of estimator during high VOC index signal). Zero + disables the gating. + :return std_initial: + Initial estimate for standard deviation. Lower value boosts events during initial learning period, + but may result in larger device-to-device variations. + :return gain_factor: + Gain factor to amplify or to attenuate the VOC index output. .. note:: This command is only available in idle mode. """ - transfer = StartFanCleaning() - return execute_transfer(self._channel, transfer) - - def set_temperature_offset_parameters(self, offset, slope, time_constant, slot): - """ - 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 offset: - Constant temperature offset scaled with factor 200 (T [°C] = value / 200). - :param slope: - Normalized temperature offset slope scaled with factor 10000 (applied factor = value / 10000). - :param time_constant: - 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 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. - - :Example: - .. code-block:: python - - sensor.set_temperature_offset_parameters(1, 10, 1, 0) - - """ - transfer = SetTemperatureOffsetParameters(offset, slope, time_constant, slot) + transfer = GetVocAlgorithmTuningParameters() return execute_transfer(self._channel, transfer) def set_voc_algorithm_tuning_parameters(self, index_offset, learning_time_offset_hours, learning_time_gain_hours, @@ -276,31 +111,68 @@ def set_voc_algorithm_tuning_parameters(self, index_offset, learning_time_offset gating_max_duration_minutes, std_initial, gain_factor) return execute_transfer(self._channel, transfer) - def get_voc_algorithm_tuning_parameters(self): + def get_voc_algorithm_state(self): """ - Gets the parameters to customize the VOC algorithm. + Gets the current VOC algorithm state. + Allows to backup and restore the VOC algorithm state to resume operation after a power cycle or + device reset, skipping initial learning phase. By default, the VOC Engine is reset and the algorithm + state is retained if a measurement is stopped and started again. If the VOC algorithm + state shall be reset, a device reset or a power cycle can be executed. + + :return state: + Current VOC algorithm state. + + .. note:: + This command can be used either in measure mode or in idle mode (which will then return the state at the + time when the measurement was stopped). In measure mode, the state can be read each measure interval to + always have the latest state available, even in case of a sudden power loss. + """ + transfer = GetVocAlgorithmState() + return execute_transfer(self._channel, transfer)[0] + + def set_voc_algorithm_state(self, state): + """ + Sets the VOC algorithm state previously received with "Get VOC Algorithm State" command. + Allows restoration of the VOC algorithm state to resume operation after a power cycle or device + reset, skipping initial learning phase. By default, the VOC Engine is reset, and the algorithm state is retained if + a measurement is stopped and started again. If the VOC algorithm state shall be reset, a device reset, or a + power cycle can be executed. + + :param state: + VOC algorithm state to restore. + + .. note:: + This command is only available in idle mode and the state will be applied only once when starting + the next measurement. In measure mode, this command has no effect. + """ + transfer = SetVocAlgorithmState(state) + return execute_transfer(self._channel, transfer) + + def get_nox_algorithm_tuning_parameters(self): + """ + Gets the parameters to customize the NOx algorithm. :return index_offset: - VOC index representing typical (average) conditions. + NOx index representing typical (average) conditions. :return learning_time_offset_hours: - Time constant to estimate the VOC algorithm offset from the history in hours. Past events will be + Time constant to estimate the NOx algorithm offset from the history in hours. Past events will be forgotten after about twice the learning time. :return learning_time_gain_hours: - Time constant to estimate the VOC algorithm gain from the history in hours. Past events will be - forgotten after about twice the learning time. + The time constant to estimate the NOx algorithm gain from the history has no impact for NOx. This + parameter is still in place for consistency reasons with the VOC tuning parameters command. :return gating_max_duration_minutes: - Maximum duration of gating in minutes (freeze of estimator during high VOC index signal). Zero + Maximum duration of gating in minutes (freeze of estimator during high NOx index signal). Zero disables the gating. :return std_initial: - Initial estimate for standard deviation. Lower value boosts events during initial learning period, - but may result in larger device-to-device variations. + The initial estimate for standard deviation has no impact for NOx. This parameter is still in place + for consistency reasons with the VOC tuning parameters command. :return gain_factor: - Gain factor to amplify or to attenuate the VOC index output. + Gain factor to amplify or to attenuate the NOx index output. .. note:: - This command is only available in idle mode. + This command is available only in idle mode. """ - transfer = GetVocAlgorithmTuningParameters() + transfer = GetNoxAlgorithmTuningParameters() return execute_transfer(self._channel, transfer) def set_nox_algorithm_tuning_parameters(self, index_offset, learning_time_offset_hours, learning_time_gain_hours, @@ -346,31 +218,115 @@ def set_nox_algorithm_tuning_parameters(self, index_offset, learning_time_offset gating_max_duration_minutes, std_initial, gain_factor) return execute_transfer(self._channel, transfer) - def get_nox_algorithm_tuning_parameters(self): + def start_continuous_measurement(self): """ - Gets the parameters to customize the NOx algorithm. - - :return index_offset: - NOx index representing typical (average) conditions. - :return learning_time_offset_hours: - Time constant to estimate the NOx algorithm offset from the history in hours. Past events will be - forgotten after about twice the learning time. - :return learning_time_gain_hours: - The time constant to estimate the NOx algorithm gain from the history has no impact for NOx. This - parameter is still in place for consistency reasons with the VOC tuning parameters command. - :return gating_max_duration_minutes: - Maximum duration of gating in minutes (freeze of estimator during high NOx index signal). Zero - disables the gating. - :return std_initial: - The initial estimate for standard deviation has no impact for NOx. This parameter is still in place - for consistency reasons with the VOC tuning parameters command. - :return gain_factor: - Gain factor to amplify or to attenuate the NOx index output. + 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. + """ + transfer = StartContinuousMeasurement() + return execute_transfer(self._channel, transfer) + + def stop_measurement(self): + """ + 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. + """ + transfer = StopMeasurement() + return execute_transfer(self._channel, transfer) + + def get_data_ready(self): + """ + 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. + + :return padding: + Padding byte, always 0x00. + :return data_ready: + True (0x01) if data is ready, False (0x00) if not. When no measurement is running, False will be + returned. + """ + transfer = GetDataReady() + return execute_transfer(self._channel, transfer) + + def read_number_concentration_values_as_integers(self): + """ + Returns the measured number concentration values. + The command 0x0202 "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). + + :return number_concentration_pm0p5: + Value is scaled with factor 10: PM0.5 [particles/cm³] = value / 10. + *Note: If this value is unknown, 0xFFFF is returned.* + :return number_concentration_pm1p0: + Value is scaled with factor 10: PM1.0 [particles/cm³] = value / 10. + *Note: If this value is unknown, 0xFFFF is returned.* + :return number_concentration_pm2p5: + Value is scaled with factor 10: PM2.5 [particles/cm³] = value / 10. + *Note: If this value is unknown, 0xFFFF is returned.* + :return number_concentration_pm4p0: + Value is scaled with factor 10: PM4.0 [particles/cm³] = value / 10. + *Note: If this value is unknown, 0xFFFF is returned.* + :return number_concentration_pm10p0: + Value is scaled with factor 10: PM10.0 [particles/cm³] = value / 10. + *Note: If this value is unknown, 0xFFFF is returned.* + """ + transfer = ReadNumberConcentrationValuesAsIntegers() + return execute_transfer(self._channel, transfer) + + def set_temperature_offset_parameters(self, offset, slope, time_constant, slot): + """ + 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 offset: + Constant temperature offset scaled with factor 200 (T [°C] = value / 200). + :param slope: + Normalized temperature offset slope scaled with factor 10000 (applied factor = value / 10000). + :param time_constant: + 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 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 command is available only in idle mode. + This configuration is volatile, i.e. the parameters will be reverted to their default value of zero + after a device reset. + + :Example: + .. code-block:: python + + sensor.set_temperature_offset_parameters(1, 10, 1, 0) + """ - transfer = GetNoxAlgorithmTuningParameters() + transfer = SetTemperatureOffsetParameters(offset, slope, time_constant, slot) return execute_transfer(self._channel, transfer) def set_temperature_acceleration_parameters(self, k, p, t1, t2): @@ -400,82 +356,25 @@ def set_temperature_acceleration_parameters(self, k, p, t1, t2): transfer = SetTemperatureAccelerationParameters(k, p, t1, t2) return execute_transfer(self._channel, transfer) - def set_voc_algorithm_state(self, state): - """ - Sets the VOC algorithm state previously received with "Get VOC Algorithm State" command. - Allows restoration of the VOC algorithm state to resume operation after a power cycle or device - reset, skipping initial learning phase. By default, the VOC Engine is reset, and the algorithm state is retained if - a measurement is stopped and started again. If the VOC algorithm state shall be reset, a device reset, or a - power cycle can be executed. - - :param state: - VOC algorithm state to restore. - - .. note:: - This command is only available in idle mode and the state will be applied only once when starting - the next measurement. In measure mode, this command has no effect. - """ - transfer = SetVocAlgorithmState(state) - return execute_transfer(self._channel, transfer) - - def get_voc_algorithm_state(self): + def get_product_type(self): """ - Gets the current VOC algorithm state. - Allows to backup and restore the VOC algorithm state to resume operation after a power cycle or - device reset, skipping initial learning phase. By default, the VOC Engine is reset and the algorithm - state is retained if a measurement is stopped and started again. If the VOC algorithm - state shall be reset, a device reset or a power cycle can be executed. + Gets the product type from the device. - :return state: - Current VOC algorithm state. + The following product types are expected: + - SEN62: '00085800' + - SEN63C: '00085700' + - SEN65: '00085200' + - SEN66: '00085300' + - SEN68: '00085400' + - SEN69C: '00085900' - .. note:: - This command can be used either in measure mode or in idle mode (which will then return the state at the - time when the measurement was stopped). In measure mode, the state can be read each measure interval to - always have the latest state available, even in case of a sudden power loss. + :return product_type: + Null-terminated ASCII string containing the product type. + Up to 32 characters can be read from the device. """ - transfer = GetVocAlgorithmState() + transfer = GetProductType() return execute_transfer(self._channel, transfer)[0] - def activate_sht_heater(self): - """ - 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. - """ - transfer = ActivateShtHeater() - return execute_transfer(self._channel, transfer) - - def get_sht_heater_measurements(self): - """ - 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. - - :return humidity: - Value is scaled with factor 100: RH [%] = value / 100 - *Note: If this value is not available, 0x7FFF is returned.* - :return 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. - """ - transfer = GetShtHeaterMeasurements() - return execute_transfer(self._channel, transfer) - def get_product_name(self): """ Gets the product name from the device. @@ -498,18 +397,6 @@ def get_serial_number(self): transfer = GetSerialNumber() return execute_transfer(self._channel, transfer)[0] - def get_version(self): - """ - Gets the version information for the hardware, firmware and communication protocol. - - :return firmware_major: - Firmware major version number. - :return firmware_minor: - Firmware minor version number. - """ - transfer = GetVersion() - return execute_transfer(self._channel, transfer) - def read_device_status(self): """ Reads the current device status. @@ -547,11 +434,146 @@ def read_and_clear_device_status(self): res_0 = execute_transfer(self._channel, transfer)[0] return DeviceStatus(res_0) + def get_version(self): + """ + Gets the version information for the firmware. + + :return firmware_major: + Firmware major version number. + :return firmware_minor: + Firmware minor version number. + """ + transfer = GetVersion() + return execute_transfer(self._channel, transfer) + def device_reset(self): """Executes a reset on the device. This has the same effect as a power cycle.""" transfer = DeviceReset() return execute_transfer(self._channel, transfer) + def start_fan_cleaning(self): + """ + 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. + """ + transfer = StartFanCleaning() + return execute_transfer(self._channel, transfer) + + def activate_sht_heater(self): + """ + Activate the heater feature of the SHT4x sensor. + This command allows to use the inbuilt heater in SHT sensor + to decontaminate and reverse creep at high humidity. + This command activates the SHT sensor heater with 200mW for 1s. + The SHT heater measurement done just before deactivation can be + read using the command \"Get SHT Heater Measurements\" after + the duration of the heating feature as specified in the SHT4x + datasheet. + 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. + """ + transfer = ActivateShtHeater() + return execute_transfer(self._channel, transfer) + + def get_sht_heater_measurements(self): + """ + Get the measurement values when the SHT sensor heating + is finished. + + :return sht_relative_humidity: + If the sht heating is completed, this value indicates + the scaled relative humidity of the SHT4x sensor. + Value is scaled with factor 100: RH [%] = value / 100 + *Note: If this value is not available, 0x7FFF is returned.* + :return sht_temperature: + If the sht heating is completed, this value indicates + the scaled temperature of the SHT4x sensor. + Value is scaled with factor 200: T [°C] = value / 200 + *Note: If this value is not available, 0x7FFF is returned.* + + .. note:: + 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. This command is only available in idle + mode. + """ + transfer = GetShtHeaterMeasurements() + return execute_transfer(self._channel, transfer) + + def read_measured_values_as_integers(self): + """ + 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). + + :return mass_concentration_pm1p0: + Value is scaled with factor 10: PM1.0 [µg/m³] = value / 10 + *Note: If this value is unknown, 0xFFFF is returned.* + :return mass_concentration_pm2p5: + Value is scaled with factor 10: PM2.5 [µg/m³] = value / 10 + *Note: If this value is unknown, 0xFFFF is returned.* + :return mass_concentration_pm4p0: + Value is scaled with factor 10: PM4.0 [µg/m³] = value / 10 + *Note: If this value is unknown, 0xFFFF is returned.* + :return mass_concentration_pm10p0: + Value is scaled with factor 10: PM10.0 [µg/m³] = value / 10 + *Note: If this value is unknown, 0xFFFF is returned.* + :return ambient_humidity: + Value is scaled with factor 100: RH [%] = value / 100 + *Note: If this value is unknown, 0x7FFF is returned.* + :return ambient_temperature: + Value is scaled with factor 200: T [°C] = value / 200 + *Note: If this value is unknown, 0x7FFF is returned.* + :return voc_index: + Value is scaled with factor 10: VOC Index = value / 10 + *Note: If this value is unknown, 0x7FFF is returned.* + :return nox_index: + Value is scaled with factor 10: NOx Index = value / 10 + *Note: If this value is unknown, 0x7FFF is returned. During + the first 10..11 seconds after power-on or device reset, this + value will be 0x7FFF as well.* + """ + transfer = ReadMeasuredValuesAsIntegers() + return execute_transfer(self._channel, transfer) + + def read_measured_raw_values(self): + """ + 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). + + :return raw_humidity: + Value is scaled with factor 100: RH [%] = value / 100 + *Note: If this value is unknown, 0x7FFF is returned.* + :return raw_temperature: + Value is scaled with factor 200: T [°C] = value / 200 + *Note: If this value is unknown, 0x7FFF is returned.* + :return raw_voc: + Raw measured VOC ticks without scale factor. + *Note: If this value is unknown, 0xFFFF is returned.* + :return raw_nox: + Raw measured NOx ticks without scale factor. + *Note: If this value is unknown, 0xFFFF is returned. During + the first 10..11 seconds after power-on or device reset, this + value will be 0xFFFF as well.* + """ + transfer = ReadMeasuredRawValues() + return execute_transfer(self._channel, transfer) + class Sen65Device(Sen65DeviceBase): """Driver class implementation of SEN65""" @@ -577,20 +599,21 @@ def read_measured_values(self): :return humidity: Measured humidity in %RH. :return temperature: - Measured temperature in degrees celsius. + Measured temperature in degrees Celsius. :return voc_index: - Measured VOC Index between 0 and 500. + VOC Index :return nox_index: - Measured NOx Index between 0 and 500. + NOx Index """ (mass_concentration_pm1p0_raw, mass_concentration_pm2p5_raw, mass_concentration_pm4p0_raw, mass_concentration_pm10p0_raw, humidity_raw, temperature_raw, voc_index_raw, nox_index_raw ) = self.read_measured_values_as_integers() - return (SignalMassConcentrationPm1p0(mass_concentration_pm1p0_raw), - SignalMassConcentrationPm2p5(mass_concentration_pm2p5_raw), - SignalMassConcentrationPm4p0(mass_concentration_pm4p0_raw), - SignalMassConcentrationPm10p0(mass_concentration_pm10p0_raw), SignalHumidity(humidity_raw), - SignalTemperature(temperature_raw), SignalVocIndex(voc_index_raw), SignalNoxIndex(nox_index_raw)) + return (SignalDividedBy10Uint16(mass_concentration_pm1p0_raw), + SignalDividedBy10Uint16(mass_concentration_pm2p5_raw), + SignalDividedBy10Uint16(mass_concentration_pm4p0_raw), + SignalDividedBy10Uint16(mass_concentration_pm10p0_raw), SignalHumidity(humidity_raw), + SignalTemperature(temperature_raw), SignalDividedBy10Int16(voc_index_raw), + SignalDividedBy10Int16(nox_index_raw)) def read_number_concentration_values(self): """ @@ -610,8 +633,8 @@ def read_number_concentration_values(self): (number_concentration_pm0p5_raw, number_concentration_pm1p0_raw, number_concentration_pm2p5_raw, number_concentration_pm4p0_raw, number_concentration_pm10p0_raw ) = self.read_number_concentration_values_as_integers() - return (SignalNumberConcentrationPm0p5(number_concentration_pm0p5_raw), - SignalNumberConcentrationPm1p0(number_concentration_pm1p0_raw), - SignalNumberConcentrationPm2p5(number_concentration_pm2p5_raw), - SignalNumberConcentrationPm4p0(number_concentration_pm4p0_raw), - SignalNumberConcentrationPm10p0(number_concentration_pm10p0_raw)) + return (SignalDividedBy10Uint16(number_concentration_pm0p5_raw), + SignalDividedBy10Uint16(number_concentration_pm1p0_raw), + SignalDividedBy10Uint16(number_concentration_pm2p5_raw), + SignalDividedBy10Uint16(number_concentration_pm4p0_raw), + SignalDividedBy10Uint16(number_concentration_pm10p0_raw)) diff --git a/sensirion_i2c_sen65/response_provider.py b/sensirion_i2c_sen65/response_provider.py index c03eaaa..3f9e6d6 100644 --- a/sensirion_i2c_sen65/response_provider.py +++ b/sensirion_i2c_sen65/response_provider.py @@ -4,7 +4,8 @@ class Sen65ResponseProvider(rp.ResponseProvider): - RESPONSE_MAP = {0xd014: struct.pack('>32s', rp.random_ascii_string(32)), + RESPONSE_MAP = {0xd002: struct.pack('>32s', rp.random_ascii_string(32)), + 0xd014: struct.pack('>32s', rp.random_ascii_string(32)), 0xd033: struct.pack('>32s', rp.random_ascii_string(32))} def get_id(self) -> str: diff --git a/sensirion_i2c_sen65/result_types.py b/sensirion_i2c_sen65/result_types.py index bbf1815..dd22fba 100644 --- a/sensirion_i2c_sen65/result_types.py +++ b/sensirion_i2c_sen65/result_types.py @@ -1,13 +1,13 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# (c) Copyright 2025 Sensirion AG, Switzerland +# (c) Copyright 2026 Sensirion AG, Switzerland # # THIS FILE IS AUTOMATICALLY GENERATED! # -# Generator: sensirion-driver-generator 1.1.2 +# Generator: sensirion-driver-generator 1.7.0 # Product: sen65 -# Model-Version: 1.3.0 +# Model-Version: 1.4.0 # """ The signal classes specify transformations of the raw sensor signals into a meaningful units. @@ -17,134 +17,42 @@ from sensirion_driver_support_types.signals import AbstractSignal -class SignalMassConcentrationPm1p0(AbstractSignal): - """Mass concentration in μg/m³ for particles smaller than 1.0 μm""" +class SignalDividedBy10Uint16(AbstractSignal): + """ + All mass and number concentrations are scaled by a factor 10 to avoid + floating point operations on the communication interface. + """ - def __init__(self, mass_concentration_pm1p0_raw): - self._mass_concentration_pm1p0 = mass_concentration_pm1p0_raw / 10.0 + def __init__(self, scaled_integer_value): + self._divided_by_10_uint16 = scaled_integer_value / 10.0 @property def value(self): - return self._mass_concentration_pm1p0 + return self._divided_by_10_uint16 def __str__(self): return '{0:.2f}'.format(self.value) -class SignalMassConcentrationPm2p5(AbstractSignal): - """Mass concentration in μg/m³ for particles smaller than 2.5 μm""" +class SignalDividedBy10Int16(AbstractSignal): + """ + The VOC and NOx indices are scaled by a factor 10 to avoid + floating point operations on the communication interface. + """ - def __init__(self, mass_concentration_pm2p5_raw): - self._mass_concentration_pm2p5 = mass_concentration_pm2p5_raw / 10.0 + def __init__(self, scaled_integer_value): + self._divided_by_10_int16 = scaled_integer_value / 10.0 @property def value(self): - return self._mass_concentration_pm2p5 - - def __str__(self): - return '{0:.2f}'.format(self.value) - - -class SignalMassConcentrationPm4p0(AbstractSignal): - """Mass concentration in μg/m³ for particles smaller than 4.0 μm""" - - def __init__(self, mass_concentration_pm4p0_raw): - self._mass_concentration_pm4p0 = mass_concentration_pm4p0_raw / 10.0 - - @property - def value(self): - return self._mass_concentration_pm4p0 - - def __str__(self): - return '{0:.2f}'.format(self.value) - - -class SignalMassConcentrationPm10p0(AbstractSignal): - """Mass concentration in μg/m³ for particles smaller than 10.0 μm""" - - def __init__(self, mass_concentration_pm10p0_raw): - self._mass_concentration_pm10p0 = mass_concentration_pm10p0_raw / 10.0 - - @property - def value(self): - return self._mass_concentration_pm10p0 - - def __str__(self): - return '{0:.2f}'.format(self.value) - - -class SignalNumberConcentrationPm0p5(AbstractSignal): - """Number concentration in particles/cm³ for particles smaller than 0.5 μm""" - - def __init__(self, number_concentration_pm0p5_raw): - self._number_concentration_pm0p5 = number_concentration_pm0p5_raw / 10.0 - - @property - def value(self): - return self._number_concentration_pm0p5 - - def __str__(self): - return '{0:.2f}'.format(self.value) - - -class SignalNumberConcentrationPm1p0(AbstractSignal): - """Number concentration in particles/cm³ for particles smaller than 1.0 μm""" - - def __init__(self, number_concentration_pm1p0_raw): - self._number_concentration_pm1p0 = number_concentration_pm1p0_raw / 10.0 - - @property - def value(self): - return self._number_concentration_pm1p0 - - def __str__(self): - return '{0:.2f}'.format(self.value) - - -class SignalNumberConcentrationPm2p5(AbstractSignal): - """Number concentration in particles/cm³ for particles smaller than 2.5 μm""" - - def __init__(self, number_concentration_pm2p5_raw): - self._number_concentration_pm2p5 = number_concentration_pm2p5_raw / 10.0 - - @property - def value(self): - return self._number_concentration_pm2p5 - - def __str__(self): - return '{0:.2f}'.format(self.value) - - -class SignalNumberConcentrationPm4p0(AbstractSignal): - """Number concentration in particles/cm³ for particles smaller than 4.0 μm""" - - def __init__(self, number_concentration_pm4p0_raw): - self._number_concentration_pm4p0 = number_concentration_pm4p0_raw / 10.0 - - @property - def value(self): - return self._number_concentration_pm4p0 - - def __str__(self): - return '{0:.2f}'.format(self.value) - - -class SignalNumberConcentrationPm10p0(AbstractSignal): - """Number concentration in particles/cm³ for particles smaller than 10.0 μm""" - - def __init__(self, number_concentration_pm10p0_raw): - self._number_concentration_pm10p0 = number_concentration_pm10p0_raw / 10.0 - - @property - def value(self): - return self._number_concentration_pm10p0 + return self._divided_by_10_int16 def __str__(self): return '{0:.2f}'.format(self.value) class SignalTemperature(AbstractSignal): - """Measured temperature in degrees celsius. The raw value is scaled appropriately.""" + """Measured temperature in degrees Celsius. The raw value is scaled appropriately.""" def __init__(self, temperature_raw): self._temperature = temperature_raw / 200.0 @@ -170,31 +78,3 @@ def value(self): def __str__(self): return '{0:.2f}'.format(self.value) - -class SignalVocIndex(AbstractSignal): - """Measured VOC Index ticks.""" - - def __init__(self, voc_index_raw): - self._voc_index = voc_index_raw / 10.0 - - @property - def value(self): - return self._voc_index - - def __str__(self): - return '{0:.2f}'.format(self.value) - - -class SignalNoxIndex(AbstractSignal): - """Measured NOx Index ticks.""" - - def __init__(self, nox_index_raw): - self._nox_index = nox_index_raw / 10.0 - - @property - def value(self): - return self._nox_index - - def __str__(self): - return '{0:.2f}'.format(self.value) - diff --git a/sensirion_i2c_sen65/version.py b/sensirion_i2c_sen65/version.py index 338edf5..b42242e 100644 --- a/sensirion_i2c_sen65/version.py +++ b/sensirion_i2c_sen65/version.py @@ -1,5 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function +import importlib.metadata as metadata +from typing import Final -version = "0.1.0" +version: Final[str] = metadata.version("sensirion_i2c_sen65") diff --git a/setup.py b/setup.py index 561f260..bfae448 100644 --- a/setup.py +++ b/setup.py @@ -1,70 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +from setuptools import setup -import os -import re - -from setuptools import setup, find_packages - -# Python versions this package is compatible with -python_requires = '>=3.6, <4' - -# Packages that this package imports. List everything apart from standard lib packages. -install_requires = [ - 'sensirion-i2c-driver>=1.0.0,<2.0', - 'sensirion-driver-adapters>=2.1.9,<3.0', - 'sensirion-driver-support-types~=0.2.0', - 'sensirion-shdlc-sensorbridge>=0.1.0,<0.3.0' -] - -# Packages required for tests and docs -extras_require = { - 'test': [ - 'flake8~=3.7.8', - 'pytest~=6.2.5', - 'pytest-cov~=3.0.0', - ] -} - -# Read version number from version.py -version_line = open("sensirion_i2c_sen65/version.py", "rt").read() -result = re.search(r"^version = ['\"]([^'\"]*)['\"]", version_line, re.M) -if result: - version_string = result.group(1) -else: - raise RuntimeError("Unable to find version string") - -# Use README.rst and CHANGELOG.md as package description -root_path = os.path.dirname(__file__) -long_description = open(os.path.join(root_path, 'README.md')).read() - -setup( - name='sensirion_i2c_sen65', - version=version_string, - author='Sensirion', - author_email='info@sensirion.com', - description='I2C driver for the Sensirion SEN65 sensor family', - license='BSD', - keywords="""Sensirion SEN65 - I2C - SEN65""", - project_urls={ - "Documentation": "https://sensirion.github.io/python-i2c-sen65", - "Repository": "https://github.com/Sensirion/python-i2c-sen65", - "Changelog": "https://github.com/Sensirion/python-i2c-sen65/blob/master/CHANGELOG.md", - }, - packages=find_packages(exclude=['tests', 'tests.*']), - long_description=long_description, - long_description_content_type='text/markdown', - python_requires=python_requires, - install_requires=install_requires, - extras_require=extras_require, - classifiers=[ - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.11', - 'Topic :: Software Development :: Libraries :: Python Modules' - ] -) +setup() \ No newline at end of file diff --git a/tests/test_sen65.py b/tests/test_sen65.py index 612c406..5bf3e60 100644 --- a/tests/test_sen65.py +++ b/tests/test_sen65.py @@ -1,13 +1,13 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# (c) Copyright 2025 Sensirion AG, Switzerland +# (c) Copyright 2026 Sensirion AG, Switzerland # # THIS FILE IS AUTOMATICALLY GENERATED! # -# Generator: sensirion-driver-generator 1.1.2 +# Generator: sensirion-driver-generator 1.7.0 # Product: sen65 -# Model-Version: 1.3.0 +# Model-Version: 1.4.0 # import pytest @@ -24,6 +24,12 @@ def sensor(channel_provider): yield Sen65Device(channel) +def test_get_product_type1(sensor): + product_type = sensor.get_product_type() + print(f"product_type: {product_type}; " + ) + + def test_get_product_name1(sensor): product_name = sensor.get_product_name() print(f"product_name: {product_name}; " @@ -48,8 +54,24 @@ def test_set_temperature_offset_parameters1(sensor): sensor.set_temperature_offset_parameters(1, 10, 1, 0) -def test_set_voc_algorithm_tuning_parameters1(sensor): - sensor.set_voc_algorithm_tuning_parameters(100, 12, 12, 180, 50, 230) +def test_set_temperature_acceleration_parameters1(sensor): + sensor.set_temperature_acceleration_parameters(1, 1, 1, 1) + + +def test_activate_sht_heater1(sensor): + sensor.activate_sht_heater() + + +def test_read_device_status1(sensor): + device_status = sensor.read_device_status() + print(f"device_status: {device_status}; " + ) + + +def test_read_and_clear_device_status1(sensor): + device_status = sensor.read_and_clear_device_status() + print(f"device_status: {device_status}; " + ) def test_get_voc_algorithm_tuning_parameters1(sensor): @@ -65,8 +87,8 @@ def test_get_voc_algorithm_tuning_parameters1(sensor): ) -def test_set_nox_algorithm_tuning_parameters1(sensor): - sensor.set_nox_algorithm_tuning_parameters(1, 12, 12, 720, 50, 230) +def test_set_voc_algorithm_tuning_parameters1(sensor): + sensor.set_voc_algorithm_tuning_parameters(100, 12, 12, 180, 50, 230) def test_get_nox_algorithm_tuning_parameters1(sensor): @@ -82,8 +104,8 @@ def test_get_nox_algorithm_tuning_parameters1(sensor): ) -def test_set_temperature_acceleration_parameters1(sensor): - sensor.set_temperature_acceleration_parameters(1, 1, 1, 1) +def test_set_nox_algorithm_tuning_parameters1(sensor): + sensor.set_nox_algorithm_tuning_parameters(1, 12, 12, 720, 50, 230) def test_get_voc_algorithm_state1(sensor): @@ -92,22 +114,6 @@ def test_get_voc_algorithm_state1(sensor): ) -def test_activate_sht_heater1(sensor): - sensor.activate_sht_heater() - - -def test_read_device_status1(sensor): - device_status = sensor.read_device_status() - print(f"device_status: {device_status}; " - ) - - -def test_read_and_clear_device_status1(sensor): - device_status = sensor.read_and_clear_device_status() - print(f"device_status: {device_status}; " - ) - - def test_start_continuous_measurement1(sensor): sensor.start_continuous_measurement() (padding, data_ready @@ -165,8 +171,5 @@ def test_start_continuous_measurement1(sensor): f"raw_nox: {raw_nox}; " ) sensor.set_temperature_offset_parameters(1, 10, 1, 0) - state = sensor.get_voc_algorithm_state() - print(f"state: {state}; " - ) sensor.stop_measurement()