|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | +# (c) Copyright 2026 Sensirion AG, Switzerland |
| 3 | +import datetime |
| 4 | +import logging |
| 5 | +import time |
| 6 | +from typing import Any, Iterator, Optional, Tuple, Callable, Iterable |
| 7 | + |
| 8 | +from sensirion_driver_adapters.channel import StreamingChannel, Measurement, MeasurementPacket, TxRxChannel |
| 9 | +from sensirion_driver_adapters.i2c_adapter.i2c_channel import I2cChannel |
| 10 | +from sensirion_driver_adapters.rx_tx_data import RxData |
| 11 | + |
| 12 | +log = logging.getLogger(__name__) |
| 13 | + |
| 14 | + |
| 15 | +class I2cStreamingChannel(StreamingChannel, TxRxChannel): |
| 16 | + def __init__(self, |
| 17 | + start_repeated_measurement: Callable[[int, int, bytes, bool, int], int], |
| 18 | + get_next_measurement_data: Callable[[int, RxData, float, int], Iterator[MeasurementPacket]], |
| 19 | + stop_repeated_measurement: Callable[[int], None], |
| 20 | + i2c_channel: I2cChannel) -> None: |
| 21 | + |
| 22 | + self._i2_channel = i2c_channel |
| 23 | + self._start_repeated_measurement = start_repeated_measurement |
| 24 | + self._get_next_measurement_data = get_next_measurement_data |
| 25 | + self._stop_repeated_measurement = stop_repeated_measurement |
| 26 | + self._get_next_measurement = self.default_get_next_measurement |
| 27 | + self._stream_id = -1 |
| 28 | + |
| 29 | + def open_measurement_stream(self, tx_bytes: bytes, |
| 30 | + payload_offset: int, |
| 31 | + response: RxData, |
| 32 | + measurement_interval_us: int, |
| 33 | + time_before_first_read_us: int = 1000, |
| 34 | + repeat_tx_data: bool = False, |
| 35 | + slave_address: Optional[int] = None, |
| 36 | + _get_next_measurement: Optional[Callable] = None) -> Iterator[Measurement]: |
| 37 | + |
| 38 | + if _get_next_measurement: |
| 39 | + # allow overriding the default get_next_measurement function |
| 40 | + self._get_next_measurement = _get_next_measurement |
| 41 | + tx_data = self._i2_channel.build_tx_data(tx_bytes, payload_offset, self._i2_channel._crc) |
| 42 | + i2c_address = self._i2_channel._slave_address |
| 43 | + rx_len = response.rx_length * 3 // 2 if self._i2_channel._crc else response.rx_length |
| 44 | + stream_id = self._start_repeated_measurement(measurement_interval_us, |
| 45 | + i2c_address, |
| 46 | + tx_data, |
| 47 | + repeat_tx_data, |
| 48 | + rx_len) |
| 49 | + return self._get_next_measurement(stream_id, self._get_next_measurement_data, response, measurement_interval_us) |
| 50 | + |
| 51 | + def close_measurement_stream(self, stream_id: int) -> None: |
| 52 | + self._stop_repeated_measurement(stream_id) |
| 53 | + |
| 54 | + def default_get_next_measurement(self, stream_id: int, |
| 55 | + get_next_measurement_data: Callable[[int, RxData, float, int], |
| 56 | + Iterator[MeasurementPacket]], |
| 57 | + response: RxData, |
| 58 | + measurement_interval_us: int) -> Iterator[Measurement]: |
| 59 | + |
| 60 | + start_time: datetime.datetime = datetime.datetime.now() |
| 61 | + measurement_interval_s = measurement_interval_us / 1000000.0 |
| 62 | + time.sleep(measurement_interval_s * 2) # wait for two samples before starting |
| 63 | + while True: |
| 64 | + nr_of_packets = 0 |
| 65 | + time_stamp = start_time.timestamp() |
| 66 | + for new_packet in get_next_measurement_data(stream_id, response, time_stamp, measurement_interval_us): |
| 67 | + raw_data = self._i2_channel.strip_protocol(new_packet.data) |
| 68 | + next_measurement = Measurement(stream_id=stream_id, |
| 69 | + timestamp=new_packet.timestamp, |
| 70 | + data=response.unpack(raw_data)) |
| 71 | + time_stamp = new_packet.timestamp |
| 72 | + yield next_measurement |
| 73 | + nr_of_packets += 1 |
| 74 | + if nr_of_packets == 0: |
| 75 | + time.sleep(measurement_interval_s * 2) # wait for approx 50 samples |
| 76 | + start_time = datetime.datetime.now() |
| 77 | + continue |
| 78 | + next_measurement_time = datetime.datetime.fromtimestamp(time_stamp + measurement_interval_s) |
| 79 | + current_time = datetime.datetime.now() |
| 80 | + sleep_time = (next_measurement_time - current_time).total_seconds() |
| 81 | + if sleep_time > 0: |
| 82 | + time.sleep(sleep_time) |
| 83 | + start_time = datetime.datetime.now() |
| 84 | + start_time = next_measurement_time |
| 85 | + |
| 86 | + def write_read(self, tx_bytes: Iterable, payload_offset: int, |
| 87 | + response: RxData, |
| 88 | + device_busy_delay: float = 0.0, |
| 89 | + post_processing_delay: Optional[float] = None, |
| 90 | + slave_address: Optional[int] = None, |
| 91 | + ignore_errors: bool = False) -> Optional[Tuple[Any, ...]]: |
| 92 | + return self._i2_channel.write_read(tx_bytes, payload_offset, response, device_busy_delay, |
| 93 | + post_processing_delay, slave_address, ignore_errors) |
| 94 | + |
| 95 | + def strip_protocol(self, data) -> None: |
| 96 | + """""" |
| 97 | + self._i2_channel.strip_protocol(data) |
| 98 | + |
| 99 | + def timeout(self) -> float: |
| 100 | + return self._i2_channel.timeout |
0 commit comments