22# (c) Copyright 2023 Sensirion AG, Switzerland
33
44import time
5- from typing import Optional , Tuple
5+ from datetime import datetime , timezone
6+ from functools import partial
7+ from typing import Optional , Tuple , Iterator , List
68
79from sensirion_i2c_driver import I2cConnection
10+ from sensirion_i2c_driver .errors import I2cChecksumError
811from sensirion_shdlc_driver import ShdlcSerialPort , ShdlcConnection
912from sensirion_shdlc_sensorbridge import (SensorBridgePort ,
1013 SensorBridgeShdlcDevice ,
1114 SensorBridgeI2cProxy )
15+ from sensirion_shdlc_sensorbridge .device import ReadBufferResponse , RepeatedTransceiveHandle
1216
13- from sensirion_driver_adapters .channel import TxRxChannel
17+ from sensirion_driver_adapters .channel import TxRxChannel , MeasurementStream , Measurement
1418from sensirion_driver_adapters .channel_provider import I2cChannelProvider
15- from sensirion_driver_adapters .i2c_adapter .i2c_channel import I2cChannel
19+ from sensirion_driver_adapters .i2c_adapter .i2c_channel import I2cChannel , RxData
20+ from sensirion_driver_adapters .i2c_adapter .i2c_streaming_channel import I2cStreamingChannel
21+
22+
23+ class SensorBridgeMeasurementStream (MeasurementStream ):
24+ def __init__ (self , sensor_bridge : SensorBridgeShdlcDevice ,
25+ sensor_bridge_port : SensorBridgePort ,
26+ channel : I2cChannel ,
27+ tx_bytes : bytes ,
28+ payload_offset : int ,
29+ response : RxData ,
30+ measurement_interval_us : int ,
31+ buffered_samples : int = 100 ,
32+ sensor_busy_delay_us : int = 10 ):
33+ self ._tx_bytes = tx_bytes
34+ self ._payload_offset = payload_offset
35+ self ._response_descriptor = response
36+ self ._measurement_interval_us = measurement_interval_us
37+ self ._buffered_samples = buffered_samples
38+ self ._sensor_busy_delay_us = sensor_busy_delay_us
39+ self ._channel = channel
40+ self ._i2c_address = channel ._slave_address # noqa: protected-access
41+ self ._sensor_bridge_port = sensor_bridge_port
42+ self ._sensor_bridge = sensor_bridge
43+ self ._rx_length = response .rx_length * 3 // 2
44+ self ._handle : Optional [RepeatedTransceiveHandle ] = None
45+ self ._start_time = datetime .now (tz = timezone .utc ).timestamp ()
46+ self ._stream_iterator = None
47+
48+ def open (self ) -> MeasurementStream :
49+ self ._handle : RepeatedTransceiveHandle = self ._sensor_bridge .start_repeated_i2c_transceive (
50+ self ._sensor_bridge_port ,
51+ self ._measurement_interval_us ,
52+ self ._i2c_address ,
53+ self ._tx_bytes ,
54+ self ._rx_length ,
55+ timeout_us = 1000 ,
56+ read_delay_us = self ._sensor_busy_delay_us )
57+ self ._start_time = datetime .now (tz = timezone .utc ).timestamp ()
58+ time .sleep (self ._buffered_samples * self ._measurement_interval_us / 1000000 )
59+ return self
60+
61+ def close (self ) -> None :
62+ print ("closing stream" )
63+ if self ._handle is not None :
64+ self ._sensor_bridge .stop_repeated_i2c_transceive (self ._handle )
65+ self ._handle = None
66+ self ._stream_iterator = None
67+
68+ def __enter__ (self ) -> MeasurementStream :
69+ return self .open ()
70+
71+ def __exit__ (self , exc_type , exc_val , exc_tb ):
72+ try :
73+ self .close ()
74+ self ._stream_iterator = None
75+ return False
76+ except Exception : # noqa
77+ return True
78+
79+ def __iter__ (self ) -> Iterator [Measurement ]:
80+ self ._stream_iterator = self .get_next_measurement ()
81+ return self
82+
83+ def __next__ (self ) -> Measurement :
84+ assert self ._stream_iterator is not None , "Illegal state: Stream iterator is None!"
85+ return next (self ._stream_iterator )
86+
87+ def get_next_measurement (self ) -> Iterator [Measurement ]:
88+ def handle_buffer_response (response : ReadBufferResponse ) -> List [Optional [bytes ]]:
89+ lost_packets = response .lost_bytes % self ._rx_length
90+ measurement_data = [None for _ in range (lost_packets )]
91+ measurement_data += [None if d .error else d .data for d in response .values ]
92+ return measurement_data
93+
94+ interval_s = self ._measurement_interval_us / 1_000_000
95+ while self ._handle is not None :
96+ buffer_response : ReadBufferResponse = self ._sensor_bridge .read_buffer (self ._handle )
97+ receive_time = datetime .now (tz = timezone .utc ).timestamp ()
98+ measurements = handle_buffer_response (buffer_response )
99+ computed_interval_s = interval_s
100+ if len (measurements ) > 0 :
101+ computed_interval_s = (receive_time - self ._start_time ) / len (measurements )
102+ effective_measurement_interval = min (interval_s ,
103+ computed_interval_s ) # shrink but to not extend the interval
104+ start_time = self ._start_time
105+ for data in measurements :
106+ unpacked_data = None
107+ if data is not None :
108+ try :
109+ unpacked_data = self ._channel .strip_protocol (data )
110+ except I2cChecksumError : # errors are reported the same way as lost packets
111+ ...
112+ yield Measurement (stream_id = self ._handle .raw_handle ,
113+ timestamp = start_time ,
114+ data = None if data is None else self ._response_descriptor .unpack (unpacked_data ))
115+ start_time += effective_measurement_interval
116+ new_start_time = datetime .now (tz = timezone .utc ).timestamp ()
117+ start_time = min (start_time , new_start_time ) # measurements must not be in the future
118+ next_receive_time = receive_time + effective_measurement_interval * self ._buffered_samples
119+ sleep_time = max (0.0 , (next_receive_time - new_start_time ))
120+ if sleep_time > 1.0 :
121+ time .sleep (sleep_time )
122+ self ._start_time = start_time
16123
17124
18125class SensorBridgeI2cChannelProvider (I2cChannelProvider ):
@@ -40,6 +147,7 @@ def __init__(self, sensor_bridge_port: SensorBridgePort,
40147 self ._shdlc_port : Optional [ShdlcSerialPort ] = None
41148 self ._sensor_bridge : Optional [SensorBridgeShdlcDevice ] = None
42149 self ._i2c_transceiver : Optional [SensorBridgeI2cProxy ] = None
150+ self ._streams : List [SensorBridgeMeasurementStream ] = []
43151
44152 def release_channel_resources (self ):
45153 """
@@ -49,6 +157,9 @@ def release_channel_resources(self):
49157 """
50158 if self ._sensor_bridge is None :
51159 return
160+ for stream in self ._streams :
161+ stream .close ()
162+ self ._streams .clear ()
52163 assert self ._sensor_bridge_port is not None , "Illegal state: SensorBridgePort is None!"
53164 assert self ._shdlc_port is not None , "Illegal state: Shdlc port is None!"
54165 self ._sensor_bridge .switch_supply_off (self ._sensor_bridge_port )
@@ -80,6 +191,31 @@ def get_channel(self, slave_address: int,
80191 The crc calculator that can compute the crc checksum of the byte stream
81192 """
82193
83- return I2cChannel (I2cConnection (self ._i2c_transceiver ),
84- slave_address = slave_address ,
85- crc = self .try_create_crc_calculator (crc_parameters ))
194+ i2c_channel = I2cChannel (I2cConnection (self ._i2c_transceiver ),
195+ slave_address = slave_address ,
196+ crc = self .try_create_crc_calculator (crc_parameters ))
197+ channel = I2cStreamingChannel (
198+ partial (self ._create_measurement_stream , i2c_channel ),
199+ i2c_channel = i2c_channel )
200+ return channel
201+
202+ def _create_measurement_stream (self , i2c_channel : I2cChannel ,
203+ tx_bytes : bytes ,
204+ payload_offset : int ,
205+ response : RxData ,
206+ measurement_interval_us : int ,
207+ buffered_samples : int = 100 ,
208+ sensor_busy_delay_us : int = 10 ,
209+ _ : bool = False
210+ ) -> MeasurementStream :
211+ stream = SensorBridgeMeasurementStream (self ._sensor_bridge ,
212+ self ._sensor_bridge_port ,
213+ i2c_channel ,
214+ tx_bytes ,
215+ payload_offset ,
216+ response ,
217+ measurement_interval_us ,
218+ buffered_samples ,
219+ sensor_busy_delay_us )
220+ self ._streams .append (stream )
221+ return stream
0 commit comments