Add DSLogic capture, signal conversion and DSView decoders

This commit is contained in:
2026-09-27 01:45:08 +03:00
parent cc22c803d1
commit 513e79b127
44 changed files with 5982 additions and 36 deletions

View File

@@ -42,6 +42,7 @@ class DigitalCapture:
start: float
end: float
sample_rate: float = 0
trigger_time: float = None
def _check(cancel):
@@ -54,12 +55,14 @@ def read_capture(path, progress=lambda value: None, cancel=lambda: False):
try:
if path.suffix.lower() == '.csv':
result = _csv(path, progress, cancel)
elif path.suffix.lower() == '.json':
result = _json(path, progress, cancel)
elif path.suffix.lower() in ('.sal', '.dsl'):
with zipfile.ZipFile(path) as archive:
result = (_sal if path.suffix.lower() == '.sal' else _dsl)(
path, archive, progress, cancel)
else:
raise ValueError('Выберите цифровую запись CSV, SAL или DSL.')
raise ValueError('Выберите цифровую запись CSV, JSON, SAL или DSL.')
except (KeyError, struct.error, zipfile.BadZipFile, EOFError,
configparser.Error, UnicodeError) as exc:
raise ValueError('Повреждённый или неподдерживаемый файл: %s' % exc) from exc
@@ -70,6 +73,58 @@ def read_capture(path, progress=lambda value: None, cancel=lambda: False):
return result
def _json(path, progress, cancel):
def unique_object(pairs):
result = {}
for key, value in pairs:
if key in result:
raise ValueError('Повторяющееся поле JSON: %s.' % key)
result[key] = value
return result
_check(cancel)
with path.open(encoding='utf-8-sig') as stream:
rows = json.load(stream, object_pairs_hook=unique_object)
_check(cancel)
if not isinstance(rows, list) or not rows or not isinstance(rows[0], dict):
raise ValueError('Ожидается JSON: непустой массив объектов с Time [s] и каналами 0/1.')
names = list(rows[0])
if ('Time [s]' not in names or not 2 <= len(names) <= 65
or any(not name.strip() for name in names)):
raise ValueError('JSON должен содержать Time [s] и от 1 до 64 цифровых каналов.')
channels = [DigitalChannel(name, 0, array('d')) for name in names if name != 'Time [s]']
fields = set(names)
start = end = None
previous = []
for index, row in enumerate(rows):
if index % 4096 == 0:
_check(cancel)
progress(index * 99 // len(rows))
try:
if not isinstance(row, dict) or set(row) != fields:
raise ValueError('набор полей должен быть одинаковым во всех строках')
time = row['Time [s]']
if (type(time) not in (int, float) or not math.isfinite(time)
or (end is not None and time <= end)):
raise ValueError('время должно быть числом, строго возрастать и быть конечным')
values = [row[c.name] for c in channels]
if any(type(v) not in (int, float, bool) or v not in (0, 1) for v in values):
raise ValueError('цифровые уровни должны быть 0 или 1')
if start is None:
start = time
for channel, value in zip(channels, values):
channel.initial = int(value)
else:
for channel, old, value in zip(channels, previous, values):
if old != value:
channel.edges.append(time)
previous = values
end = time
except (ValueError, OverflowError) as exc:
raise ValueError('JSON, строка %d: %s' % (index + 1, exc)) from exc
return DigitalCapture(str(path), 'JSON', channels, start, end)
def _csv(path, progress, cancel):
size = max(1, path.stat().st_size)
consumed = 0