From cbf5de76a17e5fc84d95ab4216f86d26fbdeddd0 Mon Sep 17 00:00:00 2001 From: Pascal Sachs Date: Mon, 13 Jul 2026 10:39:48 +0200 Subject: [PATCH] Improve usage examples Add totalizer example Improve usage examples to be more stable. Print out the product name instead of only the id. For SLF3x example, print the actual flags for air in line and high flow instead of the whole flag buffer. --- CHANGELOG.md | 3 +- README.md | 7 +++ .../slf3x_totalizer_usage.py | 45 +++++++++++++++++++ examples/scc1_slf3x_example/slf3x_usage.py | 24 ++++++---- .../scc1_usb_to_i2c/scc1_usb_2_i2c_usage.py | 14 ++++-- sensirion_uart_scc1/drivers/slf_common.py | 18 ++++++-- 6 files changed, 95 insertions(+), 16 deletions(-) create mode 100644 examples/scc1_slf3x_example/slf3x_totalizer_usage.py diff --git a/CHANGELOG.md b/CHANGELOG.md index bef5502..8e04e42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add interface for totalizer - Ensure linux file endings across the package - Fix several typos -- Update GitHub actions to latest versions +- Update GitHub actions to the latest versions - Update dependencies +- Improve usage examples ## [1.1.1] - 2024-4-10 diff --git a/README.md b/README.md index 1cd6f21..97c68ed 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,13 @@ We provide two examples to show two basic usage scenarios of the driver python ./examples/scc1_slf3x_example/slf3x_usage.py --serial-port ``` +- **SLF3x Totalizer Usage** + This example enables the totalizer, resets it before starting a measurement, and prints the accumulated totalizer flow together with the sensor's configured unit. + + ```bash + python ./examples/scc1_slf3x_example/slf3x_totalizer_usage.py --serial-port + ``` + - **USB-I2c-Bridge** This example shows how a public python driver can be used with the SCC1-USB cable. The example uses the public driver `sensirion_i2c_sf06_lf`. Before you run the example you need to install this driver. diff --git a/examples/scc1_slf3x_example/slf3x_totalizer_usage.py b/examples/scc1_slf3x_example/slf3x_totalizer_usage.py new file mode 100644 index 0000000..b9062f9 --- /dev/null +++ b/examples/scc1_slf3x_example/slf3x_totalizer_usage.py @@ -0,0 +1,45 @@ +import argparse +import time + +from sensirion_shdlc_driver import ShdlcSerialPort, ShdlcConnection + +from sensirion_uart_scc1.drivers.scc1_slf3x import Scc1Slf3x +from sensirion_uart_scc1.drivers.slf_common import get_volume_unit_label, SlfProductName +from sensirion_uart_scc1.scc1_shdlc_device import Scc1ShdlcDevice + +parser = argparse.ArgumentParser() +parser.add_argument('--serial-port', '-p', default='COM5') +args = parser.parse_args() + +with ShdlcSerialPort(port=args.serial_port, baudrate=115200) as port: + device = Scc1ShdlcDevice(ShdlcConnection(port), target_address=0) + device.sensor_reset() + device.set_sensor_type(Scc1Slf3x.SENSOR_TYPE) + sensor = Scc1Slf3x(device) + + print(f'Product: {SlfProductName.from_product_id(sensor.product_id)}') + print(f"product id: 0x{sensor.product_id:08X}") + print(f"Serial number: {sensor.serial_number}") + + flow_scale, unit = sensor.get_flow_unit_and_scale() + if flow_scale is None or unit is None: + raise RuntimeError("Could not determine the sensor flow unit and scale") + volume_unit_label = get_volume_unit_label(unit) + + sensor.set_totalizator_status(True) + sensor.reset_totalizator() + # Measure as fast as possible + sensor.start_continuous_measurement(interval_ms=0) + try: + for _ in range(10): + time.sleep(1) + totalizer_value = sensor.get_totalizator_value() + if totalizer_value is None: + print("Totalizer value not available") + continue + + totalized_flow = totalizer_value / flow_scale + print(f"Totalizer volume: {totalized_flow} {volume_unit_label}") + + finally: + sensor.stop_continuous_measurement() diff --git a/examples/scc1_slf3x_example/slf3x_usage.py b/examples/scc1_slf3x_example/slf3x_usage.py index adcce3d..017681e 100644 --- a/examples/scc1_slf3x_example/slf3x_usage.py +++ b/examples/scc1_slf3x_example/slf3x_usage.py @@ -3,7 +3,7 @@ from sensirion_shdlc_driver import ShdlcSerialPort, ShdlcConnection from sensirion_uart_scc1.drivers.scc1_slf3x import Scc1Slf3x -from sensirion_uart_scc1.drivers.slf_common import get_flow_unit_label +from sensirion_uart_scc1.drivers.slf_common import get_flow_unit_label, SlfProductName from sensirion_uart_scc1.scc1_shdlc_device import Scc1ShdlcDevice parser = argparse.ArgumentParser() @@ -12,19 +12,27 @@ with ShdlcSerialPort(port=args.serial_port, baudrate=115200) as port: device = Scc1ShdlcDevice(ShdlcConnection(port), target_address=0) + device.sensor_reset() device.set_sensor_type(Scc1Slf3x.SENSOR_TYPE) sensor = Scc1Slf3x(device) - print("serial_number:", sensor.serial_number) - print("product id:", sensor.product_id) - print("Flow;\tTemperature;\t Flag") + + print(f'Product: {SlfProductName.from_product_id(sensor.product_id)}') + print(f"product id: 0x{sensor.product_id:08X}") + print(f"Serial number: {sensor.serial_number}") + + print("Flow;\tTemperature;\tAir In Line;\tHigh Flow") flow_scale, unit = sensor.get_flow_unit_and_scale() + unit_label = get_flow_unit_label(unit) sensor.start_continuous_measurement(interval_ms=2) try: - for _ in range(1000): + for _ in range(50): remaining, lost, data = sensor.read_extended_buffer() - print(f"Remaining bytes {remaining} and {lost}-sample lost") - print() for flow, temperature, flag in data: - print(f'{flow / flow_scale} {get_flow_unit_label(unit)};\t{temperature / 200} C;\t {flag}') + air_in_line = bool(flag & 0x01) + high_flow = bool((flag >> 1) & 0x01) + flow_scaled = flow / flow_scale + temperature_scaled = temperature / 200 + + print(f'{flow_scaled:.3f} {unit_label};\t{temperature_scaled:.3f} C;\t{air_in_line};\t{high_flow}') finally: sensor.stop_continuous_measurement() diff --git a/examples/scc1_usb_to_i2c/scc1_usb_2_i2c_usage.py b/examples/scc1_usb_to_i2c/scc1_usb_2_i2c_usage.py index 0b2af90..2c0311e 100644 --- a/examples/scc1_usb_to_i2c/scc1_usb_2_i2c_usage.py +++ b/examples/scc1_usb_to_i2c/scc1_usb_2_i2c_usage.py @@ -8,10 +8,12 @@ from sensirion_i2c_sf06_lf.device import Sf06LfDevice from sensirion_shdlc_driver import ShdlcSerialPort, ShdlcConnection +from sensirion_uart_scc1.drivers.slf_common import SlfProductName from sensirion_uart_scc1.scc1_shdlc_device import Scc1ShdlcDevice with ShdlcSerialPort(port='COM5', baudrate=115200) as port: bridge = Scc1ShdlcDevice(ShdlcConnection(port), target_address=0) + bridge.sensor_reset() bridge.set_sensor_type(3) # SF06 devices channel = I2cChannel(bridge.get_i2c_transceiver(), @@ -25,11 +27,15 @@ time.sleep(0.1) except BaseException: ... - (product_identifier, serial_number - ) = sensor.read_product_identifier() - print(f"product_identifier: {product_identifier}; " f"serial_number: {serial_number}; ") + + product_identifier, serial_number = sensor.read_product_identifier() + + print(f'Product: {SlfProductName.from_product_id(sensor.product_id)}') + print(f"product id: 0x{sensor.product_id:08X}") + print(f"Serial number: {sensor.serial_number}") + sensor.start_h2o_continuous_measurement() - for i in range(500): + for i in range(50): try: time.sleep(0.02) (a_flow, a_temperature, a_signaling_flags diff --git a/sensirion_uart_scc1/drivers/slf_common.py b/sensirion_uart_scc1/drivers/slf_common.py index 4b2e7aa..42d26cc 100644 --- a/sensirion_uart_scc1/drivers/slf_common.py +++ b/sensirion_uart_scc1/drivers/slf_common.py @@ -7,6 +7,7 @@ from collections import OrderedDict from enum import Enum +from typing import Optional from sensirion_uart_scc1.scc1_exceptions import Scc1InvalidProductId @@ -23,7 +24,7 @@ class SlfMode(Enum): LIQUI_8 = 'Liquid 8' -class SlfMeasurementCommand(object): +class SlfMeasurementCommand: MEASUREMENT_COMMANDS = { SlfMode.LIQUI_0: 0x3603, SlfMode.LIQUI_1: 0x3608, @@ -79,7 +80,9 @@ class SlfProduct(Enum): SF06 = 'SF06' @staticmethod - def from_product_id(product_id: int) -> SlfProduct: + def from_product_id(product_id: Optional[int]) -> SlfProduct: + # remove last byte of the product id for detection + product_id = product_id >> 8 if product_id in SLF3x_PRODUCT_IDS: return SlfProduct.SLF3x elif product_id in LD20_2600B_PRODUCT_IDS: @@ -91,7 +94,10 @@ def from_product_id(product_id: int) -> SlfProduct: class SlfProductName: @staticmethod - def from_product(product: SlfProduct, product_id: int) -> str: + def from_product_id(product_id: Optional[int]) -> str: + # remove last byte of the product id for detection + product = SlfProduct.from_product_id(product_id) + product_id = product_id >> 8 if product is SlfProduct.SLF3x: return SLF3x_PRODUCT_NAME.get(product_id, 'SLF3x') elif product is SlfProduct.LD20: @@ -169,3 +175,9 @@ def get_flow_unit_label(flow_unit_raw: int) -> str: time_base = FLOW_UNIT_TIME_BASE.get((flow_unit_raw >> 4) & 0xF, '') volume = FLOW_UNIT_VOLUME.get((flow_unit_raw >> 8) & 0xF, '') return f'{prefix}{volume}/{time_base}' + + +def get_volume_unit_label(flow_unit_raw: int) -> str: + prefix = FLOW_UNIT_PREFIX.get(flow_unit_raw & 0xF, '') + volume = FLOW_UNIT_VOLUME.get((flow_unit_raw >> 8) & 0xF, '') + return f'{prefix}{volume}'