Skip to content

Commit 955c268

Browse files
authored
Fix embedded graph time axis possible wrap with Measured Time (#224)
1 parent 59ba306 commit 955c268

File tree

2 files changed

+56
-12
lines changed

2 files changed

+56
-12
lines changed

scrutiny/server/datalogging/datalogging_manager.py

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,37 @@ def request_acquisition(self, request: api_datalogging.AcquisitionRequest, callb
110110
entry_signal_map=entry_signal_map,
111111
callback=callback), block=False)
112112

113+
@classmethod
114+
def make_xaxis_indexed(cls, nb_points:int) -> List[float]:
115+
return [i for i in range(nb_points)]
116+
117+
@classmethod
118+
def make_xaxis_ideal_time(cls, nb_points:int, sampling_rate:api_datalogging.SamplingRate, decimation:int) -> List[float]:
119+
if sampling_rate.frequency is None:
120+
raise ValueError('Ideal time X-Axis is not possible with variable frequency loops')
121+
timestep = 1 / sampling_rate.frequency
122+
timestep *= decimation
123+
return [round(i * timestep, cls.TIME_PRECISION_DIGIT) for i in range(nb_points)]
124+
125+
@classmethod
126+
def make_xaxis_measured_time(cls, time_data:List[bytes]) -> List[float]:
127+
if (len(time_data)) < 1:
128+
raise ValueError('Bad measured time')
129+
time_codec = Codecs.get(EmbeddedDataType.uint32, endianness=Endianness.Big)
130+
first_sample = time_codec.decode(time_data[0])
131+
previous_sample = first_sample
132+
offset = 0
133+
out_data:List[float] = [0]
134+
for sample in time_data[1:]:
135+
v = time_codec.decode(sample)
136+
if v < 0x80000000 and previous_sample > 0x80000000:
137+
offset += 0x100000000
138+
previous_sample = v
139+
v_sec = (v - first_sample + offset) * 1e-7
140+
out_data.append(v_sec)
141+
142+
return out_data
143+
113144
def acquisition_complete_callback(self, success: bool, detail_msg: str, data: Optional[List[List[bytes]]], metadata: Optional[device_datalogging.AcquisitionMetadata]) -> None:
114145
"""Callback called by the device handler when the acquisition finally gets triggered and data has finished downloaded."""
115146
if self.active_request is None:
@@ -174,27 +205,20 @@ def acquisition_complete_callback(self, success: bool, detail_msg: str, data: Op
174205
# Add the X-Axis. Either use a measured signal or use a generated one of the user wants IdealTime
175206
xaxis = DataSeries()
176207
if self.active_request.api_request.x_axis_type == api_datalogging.XAxisType.Indexed:
177-
xaxis.set_data([i for i in range(nb_points)])
208+
xaxis.set_data(self.make_xaxis_indexed(nb_points))
178209
xaxis.name = 'Index'
179210
xaxis.logged_watchable = None
180211
elif self.active_request.api_request.x_axis_type == api_datalogging.XAxisType.IdealTime:
181212
# Ideal time : Generate a time X-Axis based on the sampling rate. Assume the device is running the loop at a reliable fixed rate
182213
sampling_rate = self.get_sampling_rate(self.active_request.api_request.rate_identifier)
183-
if sampling_rate.frequency is None:
184-
raise ValueError('Ideal time X-Axis is not possible with variable frequency loops')
185-
timestep = 1 / sampling_rate.frequency
186-
timestep *= self.active_request.api_request.decimation
187-
xaxis.set_data([round(i * timestep, self.TIME_PRECISION_DIGIT) for i in range(nb_points)])
214+
xaxis_data = self.make_xaxis_ideal_time(nb_points, sampling_rate, self.active_request.api_request.decimation)
215+
xaxis.set_data(xaxis_data)
188216
xaxis.name = 'Time (ideal)'
189217
xaxis.logged_watchable = None
190218
elif self.active_request.api_request.x_axis_type == api_datalogging.XAxisType.MeasuredTime:
191219
# Measured time is appended at the end of the signal list. See make_device_config_from_request
192-
time_data = data[-1]
193-
if (len(time_data)) < 1:
194-
raise ValueError('Bad measured time')
195-
time_codec = Codecs.get(EmbeddedDataType.uint32, endianness=Endianness.Big)
196-
first_sample = time_codec.decode(time_data[0])
197-
xaxis.set_data([(time_codec.decode(sample) - first_sample) * 1e-7 for sample in time_data])
220+
xaxis_data=self.make_xaxis_measured_time(data[-1])
221+
xaxis.set_data(xaxis_data)
198222
xaxis.name = 'Time (measured)'
199223
xaxis.logged_watchable = None
200224
elif self.active_request.api_request.x_axis_type == api_datalogging.XAxisType.Signal:

test/server/datalogging/test_datalogging_manager.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#
77
# Copyright (c) 2023 Scrutiny Debugger
88

9+
import struct
10+
911
import scrutiny.server.datalogging.definitions.api as api_datalogging
1012
import scrutiny.server.datalogging.definitions.device as device_datalogging
1113
from scrutiny.core import datalogging as core_datalogging
@@ -632,6 +634,24 @@ def assert_state_change_and_clear(state: api_datalogging.DataloggingState, compl
632634
self.datalogging_manager.process()
633635
assert_state_change_and_clear(api_datalogging.DataloggingState.Standby, None)
634636

637+
def test_make_xaxis(self):
638+
639+
data = DataloggingManager.make_xaxis_indexed(10)
640+
self.assertEqual(data, [0,1,2,3,4,5,6,7,8,9])
641+
642+
rate = api_datalogging.SamplingRate("test", 1000, api_datalogging.ExecLoopType.FIXED_FREQ, 0)
643+
data = DataloggingManager.make_xaxis_ideal_time(10, rate, 2)
644+
self.assertEqual(data, [0, 0.002, 0.004, 0.006, 0.008, 0.010, 0.012, 0.014, 0.016, 0.018])
645+
646+
raw_data = [ struct.pack('>L', v) for v in [0, 10000, 20000, 30000]]
647+
data = DataloggingManager.make_xaxis_measured_time(raw_data)
648+
self.assertEqual(data, [0, 0.001, 0.002, 0.003])
649+
650+
limit = 0xFFFFFFFF
651+
raw_data = [ struct.pack('>L', v & 0xFFFFFFFF) for v in [limit-20000, limit-10000, limit, limit+10000, limit+20000]]
652+
data = DataloggingManager.make_xaxis_measured_time(raw_data)
653+
self.assertEqual(data, [0, 0.001, 0.002, 0.003, 0.004])
654+
635655

636656
if __name__ == '__main__':
637657
import unittest

0 commit comments

Comments
 (0)