Files
templates/python/tests/test_logic_analyzer_analysis.py

187 lines
9.2 KiB
Python

from array import array
from collections import Counter
from pathlib import Path
import struct
import unittest
from logic_analyzer.files import DigitalChannel, DigitalCapture, read_capture, ImportCancelled
from logic_analyzer.analysis import analyze_capture, can_crc, pulse_measurements, interval_edges
from logic_analyzer.decoders.set_uart import crc16
PING = bytes.fromhex('A5 5A 02 08 01 00 00 00 2A 00 34 12 00 00 33 EC 33 04')
def bits_of(value, count):
return [(value >> bit) & 1 for bit in range(count - 1, -1, -1)]
def capture_bits(bits, rate, invert=False, origin=0):
edges = array('d')
for i in range(1, len(bits)):
if bits[i] != bits[i - 1]:
edges.append(origin + i / rate)
channel = DigitalChannel('bus', bits[0] ^ invert, edges)
return DigitalCapture('test', 'test', [channel], origin, origin + len(bits) / rate)
def uart_capture(data, baud=115200, parity='none', stops=1, invert=False, bad_parity=False, bad_stop=False):
bits = [1] * 5
for byte in data:
bits += [0] + [(byte >> i) & 1 for i in range(8)]
if parity != 'none':
bits += [((sum(bits[-8:]) + (parity == 'odd')) & 1) ^ bad_parity]
bits += [0 if bad_stop else 1] * stops
bits += [1] * 5
return capture_bits(bits, baud, invert)
def can_packet(ident, data, extended=True, remote=False, corrupt_crc=False, ack=True):
if extended:
bits = [0] + bits_of(ident >> 18, 11) + [1, 1] + bits_of(ident & 0x3ffff, 18) + [int(remote), 0, 0]
else:
bits = [0] + bits_of(ident, 11) + [int(remote), 0, 0]
bits += bits_of(len(data), 4)
if not remote:
for byte in data:
bits += bits_of(byte, 8)
bits += bits_of(can_crc(bits) ^ int(corrupt_crc), 15)
stuffed, last, run = [], None, 0
for value in bits:
stuffed.append(value)
run = run + 1 if value == last else 1
last = value
if run == 5:
stuffed.append(1 - value)
last, run = 1 - value, 1
return stuffed + [1, 0 if ack else 1, 1] + [1] * 7
class LogicAnalysisTests(unittest.TestCase):
def test_uart_byte_values_inverted_and_parity(self):
for invert in (False, True):
for parity in ('none', 'even', 'odd'):
cap = uart_capture(b'\x00\xff\xa5\x5a', parity=parity, stops=2, invert=invert)
result = analyze_capture(cap, dict(mode='UART', baudrate=115200, parity=parity, stops=2, inverted=invert))
self.assertEqual([e.text for e in result.events], ['UART 0x00', 'UART 0xFF', 'UART 0xA5', 'UART 0x5A'])
def test_uart_errors_are_not_accepted_as_set_frames(self):
cap = uart_capture(PING, parity='even', bad_parity=True)
result = analyze_capture(cap, dict(mode='SET UART', parity='even'))
self.assertTrue(result.events)
self.assertTrue(all(e.kind == 'error' for e in result.events))
result = analyze_capture(uart_capture(b'\xa5', bad_stop=True), dict(mode='UART'))
self.assertTrue(any('стопового' in e.text for e in result.events))
def test_set_ping_bridge_crc_and_resynchronization(self):
damaged = bytearray(PING)
damaged[-1] ^= 1
data = b'noise' + damaged + PING + bytes.fromhex('AA 55 08 01 01 67 45 23 01 AA BB FE 14')
events = analyze_capture(uart_capture(data), dict(mode='SET UART')).events
self.assertTrue(any('CRC' in e.text and e.kind == 'error' for e in events))
self.assertEqual(sum('SET v2 PING' in e.text for e in events), 1)
self.assertTrue(any('ProtoCAN seq=' in e.text for e in events))
self.assertTrue(any('ProtoCAN GAS' in e.text for e in events))
def test_pm35_request_and_special_response(self):
raw = bytes.fromhex('100300000002')
raw += struct.pack('<H', crc16(raw, True))
events = analyze_capture(uart_capture(raw), dict(mode='PM35 UART', role='request')).events
self.assertEqual(len(events), 1)
self.assertIn('READ R0 count=2', events[0].text)
raw = b'\x10\x03\x00' + bytes(256)
raw += struct.pack('<H', crc16(raw, True))
events = analyze_capture(uart_capture(raw), dict(mode='PM35 UART', role='response')).events
self.assertEqual(len(events), 1)
self.assertIn('128 words', events[0].text)
def test_uart_end_flush_and_gap(self):
result = analyze_capture(uart_capture(PING[:9]), dict(mode='SET UART'))
self.assertTrue(any('Incomplete' in e.text for e in result.events))
cap = uart_capture(PING)
result = analyze_capture(cap, dict(mode='SET UART'), end=cap.end / 2)
self.assertFalse(any(e.kind == 'frame' for e in result.events))
def test_crc15_known_check_value(self):
bits = [bit for byte in b'123456789' for bit in bits_of(byte, 8)]
self.assertEqual(can_crc(bits), 0x059e)
def test_can_standard_extended_remote_and_nack(self):
bits = [1] * 10
bits += can_packet(0x123, b'\x01\x02', extended=False) + [1] * 5
bits += can_packet(0x1b530010, b'\x34\x12\xfe\xff') + [1] * 5
bits += can_packet(0x123, b'\x00\x00', extended=False, remote=True, ack=False) + [1] * 5
result = analyze_capture(capture_bits(bits, 500000), dict(mode='CAN', baudrate=500000))
self.assertEqual(len(result.events), 3, [e.text for e in result.events])
self.assertTrue(all(e.kind == 'can' for e in result.events))
self.assertIn('STD ID=00000123', result.events[0].text)
self.assertIn('EXT ID=1B530010', result.events[1].text)
self.assertEqual(result.events[1].details, '34 12 fe ff')
self.assertIn('RTR NACK', result.events[2].text)
def test_can_crc_error_recovery_and_application(self):
bits = [1] * 10 + can_packet(0x1b530010, b'\x34\x12', corrupt_crc=True) + [1] * 5
bits += can_packet(0x1b530010, b'\x34\x12') + [1] * 5
events = analyze_capture(capture_bits(bits, 1000000), dict(mode='SET CAN')).events
self.assertTrue(any(e.kind == 'error' and 'CRC15' in e.text for e in events))
self.assertEqual(sum('R0010=1234' in e.text for e in events), 1)
def test_can_stuff_error(self):
bits = [1] * 10 + [0] * 20 + [1] * 10
events = analyze_capture(capture_bits(bits, 1000000), dict(mode='CAN')).events
self.assertTrue(any('stuffing' in e.text for e in events))
def test_can_set_reassembly_and_balsam(self):
parts = [bytes([0x10]) + struct.pack('<H', len(PING)) + PING[:5]]
parts += [bytes([0x20 | i]) + PING[offset:offset+7] for i, offset in enumerate(range(5, len(PING), 7), 1)]
bits = [1] * 10
for part in parts:
bits += can_packet(0x122a0000, part) + [1] * 5
events = analyze_capture(capture_bits(bits, 1000000), dict(mode='SET CAN', protocol='set-v2')).events
self.assertTrue(any('SET v2 PING' in e.text for e in events), [e.text for e in events])
bits = [1] * 10 + can_packet(0xba0010, bytes.fromhex('a01012340000ffff')) + [1] * 5
events = analyze_capture(capture_bits(bits, 1000000), dict(mode='SET CAN', protocol='balsam')).events
self.assertTrue(any('R0010=1234 R0012=FFFF' in e.text for e in events))
def test_can_incomplete_end(self):
bits = [1] * 10 + can_packet(0x123, b'\x55', extended=False)[:-8]
events = analyze_capture(capture_bits(bits, 1000000), dict(mode='CAN')).events
self.assertTrue(any('Неполный' in e.text for e in events))
self.assertFalse(any(e.kind == 'can' for e in events))
def test_gate_profiles_and_truncated_window(self):
vin = DigitalChannel('Vin', 0, array('d', [1e-6, 4e-6]))
status = DigitalChannel('Vstat', 0, array('d', [1.25e-6, 1.95e-6]))
cap = DigitalCapture('test', 'test', [vin, status], 0, 6e-6)
events = analyze_capture(cap, dict(mode='1SP0635')).events
self.assertEqual(Counter(e.kind for e in events), dict(control=2, delay_ok=1, width_ok=1, missing=1))
self.assertAlmostEqual(next(e for e in events if e.kind == 'delay_ok').end - 1e-6, 250e-9)
events = analyze_capture(cap, dict(mode='1SP0635'), end=4.2e-6).events
self.assertTrue(any(e.kind == 'incomplete' for e in events))
self.assertFalse(any(e.kind == 'missing' for e in events))
with self.assertRaises(ValueError):
analyze_capture(cap, dict(mode='1SP0635', status_channel=0))
def test_measurements_cycle_and_clipped_pulse(self):
ch = DigitalChannel('D0', 0, array('d', [1e-6, 2e-6, 5e-6, 6e-6]))
measured = pulse_measurements(ch, 1.5e-6, 0, 8e-6)
self.assertAlmostEqual(measured['period'], 4e-6)
self.assertAlmostEqual(measured['duty'], 25)
self.assertEqual(interval_edges(ch, 1e-6, 6e-6), (1, 2))
self.assertNotIn('width', pulse_measurements(ch, 0.5e-6, 0, 8e-6))
def test_result_limit_cancellation_and_progress(self):
cap = uart_capture(bytes(range(100)))
progress = []
result = analyze_capture(cap, dict(mode='UART', max_events=3), progress=progress.append)
self.assertTrue(result.truncated)
self.assertEqual(len(result.events), 3)
self.assertLessEqual(len(progress), 101)
with self.assertRaises(ImportCancelled):
analyze_capture(cap, dict(mode='UART'), cancel=lambda: True)
if __name__ == '__main__':
unittest.main()