Skip to content

Commit 5a7b5be

Browse files
Add tests for signal generator
1 parent a10ba22 commit 5a7b5be

File tree

5 files changed

+157
-5
lines changed

5 files changed

+157
-5
lines changed

examples/configs/signal-generator/signal-generator.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
- type: tickit.devices.signal_generator.EpicsSignalGenerator
1+
- type: tickit_devices.signal_generator.signal_generator.EpicsSignalGenerator
22
name: gen
33
inputs: {}
44
- type: tickit.devices.sink.Sink

src/tickit_devices/signal_generator/signal_generator.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import pydantic.v1.dataclasses
77
from pydantic.v1 import Field
88
from softioc import builder
9-
109
from tickit.adapters.epics import EpicsAdapter
1110
from tickit.adapters.io import EpicsIo
1211
from tickit.core.adapter import AdapterContainer
@@ -82,7 +81,7 @@ def set_amplitude(self, amplitude: float) -> None:
8281
self._wave.amplitude = amplitude
8382

8483
def get_amplitude_offset(self) -> float:
85-
return self._wave.amplitude
84+
return self._wave.amplitude_offset
8685

8786
def set_amplitude_offset(self, amplitude_offset: float) -> None:
8887
self._wave.amplitude_offset = amplitude_offset
@@ -119,10 +118,10 @@ def _compute_wave(self, time: SimTime) -> float:
119118

120119
def _sine(self, time: SimTime) -> float:
121120
return self._wave.amplitude_offset + (
122-
self._wave.amplitude * self._sinosoid(time)
121+
self._wave.amplitude * self._sinusoid(time)
123122
)
124123

125-
def _sinosoid(self, time: SimTime) -> float:
124+
def _sinusoid(self, time: SimTime) -> float:
126125
time_seconds = time * 1e-9
127126
return math.sin(2 * math.pi * self._wave.frequency * time_seconds)
128127

tests/signal_generator/__init__.py

Whitespace-only changes.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
from dataclasses import dataclass
2+
from typing import List
3+
4+
import pytest
5+
from tickit.core.typedefs import SimTime
6+
7+
from tickit_devices.signal_generator.signal_generator import (
8+
SignalGeneratorDevice,
9+
WaveConfig,
10+
)
11+
12+
13+
@pytest.fixture
14+
def signal_generator() -> SignalGeneratorDevice:
15+
config = WaveConfig()
16+
return SignalGeneratorDevice(config)
17+
18+
19+
TIMES = [
20+
SimTime(0),
21+
SimTime(0.25e9),
22+
SimTime(0.5e9),
23+
SimTime(0.75e9),
24+
SimTime(1e9),
25+
SimTime(1.25e9),
26+
]
27+
28+
EXPECTED_SINE_VALUES = [
29+
1.0,
30+
2.0,
31+
1.0,
32+
0.0,
33+
1.0,
34+
2.0,
35+
]
36+
37+
EXPECTED_GATE_STATES = [
38+
True,
39+
True,
40+
True,
41+
False,
42+
True,
43+
True,
44+
]
45+
46+
EXPECTED_OUTPUTS = [
47+
SignalGeneratorDevice.Outputs(value=1.0, gate=True),
48+
SignalGeneratorDevice.Outputs(value=2.0, gate=True),
49+
SignalGeneratorDevice.Outputs(value=1.0, gate=True),
50+
SignalGeneratorDevice.Outputs(value=0.0, gate=False),
51+
SignalGeneratorDevice.Outputs(value=1.0, gate=True),
52+
SignalGeneratorDevice.Outputs(value=2.0, gate=True),
53+
]
54+
55+
56+
def test_signal_generator_produces_sine_wave(
57+
signal_generator: SignalGeneratorDevice,
58+
):
59+
sine_values = []
60+
for sim_time in TIMES:
61+
signal_generator.update(sim_time, {})
62+
sine_values.append(signal_generator.get_value())
63+
assert sine_values == pytest.approx(EXPECTED_SINE_VALUES)
64+
65+
66+
def test_signal_generator_can_be_disabled(
67+
signal_generator: SignalGeneratorDevice,
68+
):
69+
sine_values = []
70+
signal_generator.set_enabled(False)
71+
for sim_time in TIMES:
72+
signal_generator.update(sim_time, {})
73+
sine_values.append(signal_generator.get_value())
74+
assert sine_values == [0.0] * 6
75+
76+
77+
def test_signal_generator_trips_gate(
78+
signal_generator: SignalGeneratorDevice,
79+
):
80+
gate_states = []
81+
for sim_time in TIMES:
82+
signal_generator.update(sim_time, {})
83+
gate_states.append(signal_generator.is_gate_open())
84+
assert gate_states == EXPECTED_GATE_STATES
85+
86+
87+
def test_signal_generator_produces_outputs(
88+
signal_generator: SignalGeneratorDevice,
89+
):
90+
outputs = []
91+
for sim_time, expected_output in zip(TIMES, outputs):
92+
output = signal_generator.update(sim_time, {})
93+
assert output["gate"] == expected_output["gate"]
94+
assert output["value"] == pytest.approx(expected_output["value"])
95+
96+
97+
def test_config_passes(signal_generator: SignalGeneratorDevice):
98+
default_wave_config = WaveConfig()
99+
signal_generator.get_amplitude() == default_wave_config.amplitude
100+
signal_generator.get_amplitude_offset() == default_wave_config.amplitude_offset
101+
signal_generator.get_frequency() == default_wave_config.frequency
102+
signal_generator.get_gate_threshold() == 0.5
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import asyncio
2+
from dataclasses import dataclass
3+
from typing import Any
4+
5+
import aioca
6+
import pytest
7+
8+
PV_PREFIX = "SIGNALGEN"
9+
AMPLITUDE = f"{PV_PREFIX}:Amplitude"
10+
AMPLITUDE_RBV = f"{AMPLITUDE}_RBV"
11+
OFFSET = f"{PV_PREFIX}:Offset"
12+
OFFSET_RBV = f"{OFFSET}_RBV"
13+
FREQUENCY = f"{PV_PREFIX}:Frequency"
14+
FREQUENCY_RBV = f"{FREQUENCY}_RBV"
15+
GATE_THRESHOLD = f"{PV_PREFIX}:GateThreshold"
16+
GATE_THRESHOLD_RBV = f"{GATE_THRESHOLD}_RBV"
17+
ENABLED = f"{PV_PREFIX}:Enabled"
18+
ENABLED_RBV = f"{ENABLED}_RBV"
19+
20+
21+
@pytest.mark.asyncio
22+
@pytest.mark.parametrize(
23+
"tickit_process",
24+
["examples/configs/signal-generator/signal-generator.yaml"],
25+
indirect=True,
26+
)
27+
async def test_signal_generator_system(tickit_process):
28+
await set_and_test(AMPLITUDE, AMPLITUDE_RBV, 1.0, 2.0)
29+
await set_and_test(OFFSET, OFFSET_RBV, 1.0, 2.0)
30+
await set_and_test(FREQUENCY, FREQUENCY, 1.0, 2.0)
31+
await set_and_test(GATE_THRESHOLD, GATE_THRESHOLD_RBV, 0.5, 0.25)
32+
await set_and_test(ENABLED, ENABLED_RBV, True, False)
33+
34+
35+
async def set_and_test(pv: str, rbv: str, expected_initial_value: Any, new_value: Any):
36+
initial_value = await aioca.caget(rbv)
37+
await aioca.caput(pv, new_value, wait=True)
38+
await asyncio.sleep(1.0)
39+
actual_demand_value, actual_new_value = await asyncio.gather(
40+
*[aioca.caget(pv), aioca.caget(rbv)]
41+
)
42+
43+
assert initial_value == expected_initial_value, (
44+
f"Initially caget({pv}) was {initial_value}, expected {expected_initial_value}",
45+
)
46+
assert actual_demand_value == new_value, (
47+
f"After caput({pv}, {new_value}), {pv} was actually {actual_new_value}"
48+
)
49+
assert actual_new_value == new_value, (
50+
f"After caput({pv}, {new_value}), {rbv} was actually {actual_new_value}"
51+
)

0 commit comments

Comments
 (0)