Files
templates/tools/dsview/tests/test_pwm_waveform.py
T

118 lines
4.8 KiB
Python

"""PWM reconstruction vectors and sigrok wait/annotation boundary tests."""
import importlib.util
import math
from pathlib import Path
import sys
import types
import unittest
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[3]
spec = importlib.util.spec_from_file_location('pwm_core', ROOT / 'python/logic_analyzer/decoders/pwm_waveform.py')
core = importlib.util.module_from_spec(spec)
spec.loader.exec_module(core)
class PwmTests(unittest.TestCase):
def test_sine_three_phases_and_polarity(self):
for phase in (0, 2 * math.pi / 3, 4 * math.pi / 3):
for active in (True, False):
state = core.PwmChannel(1000000, active)
result = []
state.update(0, active) # Initial partial cycle must be ignored.
state.update(100, not active)
expected = []
for i in range(101):
width = round(500 + 400 * math.sin(2 * math.pi * i / 100 + phase))
result.extend(state.update(1000 + i * 1000, active))
result.extend(state.update(1000 + i * 1000 + width, not active))
expected.append(2 * width / 1000 - 1)
self.assertEqual(len(result), 100)
for e, value in zip(result, expected):
self.assertAlmostEqual(e['value'], value)
self.assertEqual(e['frequency'], 1000)
def test_fixed_windows_include_zero_and_full_duty(self):
state = core.PwmChannel(1000, carrier_hz=100)
state.update(0, 0)
events = state.update(30, 1) + state.update(60, 0)
self.assertEqual([e['value'] for e in events], [-1]*3 + [1]*3)
self.assertEqual(state.next_boundary(), 70)
def test_fractional_windows_do_not_accumulate_rounding_error(self):
state = core.PwmChannel(1000, carrier_hz=60)
state.update(0, 1)
events = state.update(100000, 1)
self.assertEqual(len(events), 6000)
self.assertEqual(events[-1]['end'], 100000)
self.assertTrue(all(e['duty'] == 1 for e in events))
def test_smoothing_step_response(self):
state = core.PwmChannel(1000, carrier_hz=100, smoothing_ms=10)
state.update(0, 0)
state.update(10, 1)
value = state.update(20, 1)[0]['value']
self.assertAlmostEqual(value, 1 - 2 * math.exp(-1))
def test_partial_capture_has_no_invented_cycle(self):
state = core.PwmChannel(1000)
self.assertEqual(state.update(0, 1) + state.update(5, 0)
+ state.update(10, 1) + state.update(12, 0), [])
def test_invalid_settings(self):
for args in ((0,), (1000, True, -1), (1000, True, 501),
(1000, True, 0, -1), (float('nan'),)):
with self.assertRaises(ValueError):
core.PwmChannel(*args)
def test_adapter_optional_channels_and_skip(self):
class StopCapture(Exception):
pass
class FakeDecoder:
def register(self, kind):
return kind
def has_channel(self, ch):
return ch in (0, 2)
def put(self, ss, es, output, data):
self.emitted.append((ss, es, data))
def wait(self, conditions):
if conditions == {}:
self.samplenum = 0
else:
self.test.assertEqual(conditions, [{0: 'e'}, {2: 'e'}, {'skip': 10}])
self.samplenum += 10
if self.samplenum > 30:
self.samplenum = 30
raise StopCapture()
return (0, 255, 1)
stub = types.ModuleType('sigrokdecode')
stub.Decoder, stub.OUTPUT_ANN, stub.SRD_CONF_SAMPLERATE = FakeDecoder, 0, 1
with patch.dict(sys.modules, {'sigrokdecode': stub, 'common.setgui_decoders.pwm_waveform': core}):
spec = importlib.util.spec_from_file_location('pwm_adapter', ROOT / 'tools/dsview/decoders/pwm_waveform/pd.py')
adapter = importlib.util.module_from_spec(spec)
spec.loader.exec_module(adapter)
decoder = adapter.Decoder()
decoder.test = self
decoder.options = {o['id']: o['default'] for o in decoder.options}
decoder.options['carrier_hz'] = 100
decoder.emitted = []
decoder.metadata(1, 1000)
decoder.start()
with self.assertRaises(StopCapture):
decoder.decode()
decoder.end()
self.assertEqual(len(decoder.emitted), 10)
self.assertEqual({e[2][0] for e in decoder.emitted}, {0, 2, 3, 5})
curves = [e for e in decoder.emitted if e[2][0] < 3]
self.assertEqual(curves[0][:2], (5, 15))
self.assertEqual(curves[0][2][1][-1], 'PWM1 -1.000000000 -1.000000000')
if __name__ == '__main__':
unittest.main()