2021-08-05 21:05:55 +08:00
|
|
|
#!/usr/bin/python3
|
|
|
|
"""
|
|
|
|
Author: Vertigo Designs, Ryan Summers
|
|
|
|
|
|
|
|
Description: Loop-back integration tests for Stabilizer hardware
|
|
|
|
"""
|
|
|
|
import argparse
|
|
|
|
import asyncio
|
|
|
|
import json
|
|
|
|
import sys
|
|
|
|
|
|
|
|
from gmqtt import Client as MqttClient
|
|
|
|
from miniconf import Miniconf
|
|
|
|
|
2021-08-09 18:50:58 +08:00
|
|
|
# The minimum allowable loopback voltage error (difference between output set point and input
|
2021-08-05 21:05:55 +08:00
|
|
|
# measured value).
|
|
|
|
MINIMUM_VOLTAGE_ERROR = 0.010
|
|
|
|
|
|
|
|
def _voltage_to_machine_units(voltage):
|
|
|
|
""" Convert a voltage to IIR machine units.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
voltage: The voltage to convert
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The IIR machine-units associated with the voltage.
|
|
|
|
"""
|
|
|
|
dac_range = 4.096 * 2.5
|
|
|
|
assert abs(voltage) <= dac_range, 'Voltage out-of-range'
|
|
|
|
return voltage / dac_range * 0x7FFF
|
|
|
|
|
|
|
|
|
|
|
|
def static_iir_output(output_voltage):
|
|
|
|
""" Generate IIR configuration for a static output voltage.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
output_voltage: The desired static IIR output voltage.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
The IIR configuration to send over Miniconf.
|
|
|
|
"""
|
|
|
|
machine_units = _voltage_to_machine_units(output_voltage)
|
|
|
|
return {
|
|
|
|
'y_min': machine_units,
|
|
|
|
'y_max': machine_units,
|
|
|
|
'y_offset': 0,
|
|
|
|
'ba': [1, 0, 0, 0, 0],
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class TelemetryReader:
|
|
|
|
""" Helper utility to read Stabilizer telemetry. """
|
|
|
|
|
|
|
|
@classmethod
|
2021-08-09 18:50:58 +08:00
|
|
|
async def create(cls, prefix, broker, queue):
|
2021-08-05 21:05:55 +08:00
|
|
|
"""Create a connection to the broker and an MQTT device using it."""
|
|
|
|
client = MqttClient(client_id='')
|
|
|
|
await client.connect(broker)
|
2021-08-09 18:50:58 +08:00
|
|
|
return cls(client, prefix, queue)
|
2021-08-05 21:05:55 +08:00
|
|
|
|
|
|
|
|
2021-08-09 18:50:58 +08:00
|
|
|
def __init__(self, client, prefix, queue):
|
2021-08-05 21:05:55 +08:00
|
|
|
""" Constructor. """
|
|
|
|
self.client = client
|
|
|
|
self._telemetry = []
|
|
|
|
self.client.on_message = self.handle_telemetry
|
|
|
|
self._telemetry_topic = f'{prefix}/telemetry'
|
|
|
|
self.client.subscribe(self._telemetry_topic)
|
2021-08-09 18:50:58 +08:00
|
|
|
self.queue = queue
|
2021-08-05 21:05:55 +08:00
|
|
|
|
|
|
|
|
|
|
|
def handle_telemetry(self, _client, topic, payload, _qos, _properties):
|
|
|
|
""" Handle incoming telemetry messages over MQTT. """
|
|
|
|
assert topic == self._telemetry_topic
|
2021-08-09 18:50:58 +08:00
|
|
|
self.queue.put_nowait(json.loads(payload))
|
2021-08-05 21:05:55 +08:00
|
|
|
|
|
|
|
|
2021-08-09 18:50:58 +08:00
|
|
|
async def test_loopback(miniconf, telemetry_queue, set_point, gain=1, channel=0):
|
2021-08-05 21:05:55 +08:00
|
|
|
""" Test loopback operation of Stabilizer.
|
|
|
|
|
|
|
|
Note:
|
|
|
|
Loopback is tested by configuring DACs for static output and verifying telemetry reports the
|
|
|
|
ADCs are measuring those values. Output OUTx should be connected in a loopback configuration
|
|
|
|
to INx on the device.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
miniconf: The miniconf configuration interface.
|
|
|
|
telemetry: a helper utility to read inbound telemetry.
|
|
|
|
set_point: The desired output voltage to test.
|
2021-08-09 18:50:58 +08:00
|
|
|
channel: The loopback channel to test on. Either 0 or 1.
|
|
|
|
gain: The desired AFE gain.
|
2021-08-05 21:05:55 +08:00
|
|
|
"""
|
2021-08-09 18:50:58 +08:00
|
|
|
print(f'Testing loopback for Vout = {set_point:.2f}, Gain = x{gain}')
|
2021-08-05 21:05:55 +08:00
|
|
|
print('---------------------------------')
|
2021-08-09 18:50:58 +08:00
|
|
|
# Configure the AFE and IIRs to output at the set point
|
|
|
|
await miniconf.command(f'afe/{channel}', f'G{gain}', retain=False)
|
|
|
|
await miniconf.command(f'iir_ch/{channel}/0', static_iir_output(set_point), retain=False)
|
|
|
|
|
|
|
|
# Configure signal generators to not affect the test.
|
|
|
|
await miniconf.command('signal_generator/0/amplitude', 0, retain=False)
|
2021-08-05 21:05:55 +08:00
|
|
|
|
2021-08-09 18:50:58 +08:00
|
|
|
# Wait for telemetry to update.
|
2021-08-06 19:41:53 +08:00
|
|
|
await asyncio.sleep(5.0)
|
2021-08-05 21:05:55 +08:00
|
|
|
|
|
|
|
# Verify the ADCs are receiving the setpoint voltage.
|
|
|
|
tolerance = max(0.05 * set_point, MINIMUM_VOLTAGE_ERROR)
|
2021-08-09 18:50:58 +08:00
|
|
|
latest_values = await telemetry_queue.get()
|
2021-08-05 21:05:55 +08:00
|
|
|
print(f'Latest telemtry: {latest_values}')
|
|
|
|
|
2021-08-09 18:50:58 +08:00
|
|
|
assert abs(latest_values['adcs'][channel] - set_point) < tolerance
|
2021-08-05 21:05:55 +08:00
|
|
|
print('PASS')
|
|
|
|
print('')
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
""" Main program entry point. """
|
|
|
|
parser = argparse.ArgumentParser(description='Loopback tests for Stabilizer HITL testing',)
|
|
|
|
parser.add_argument('prefix', type=str,
|
|
|
|
help='The MQTT topic prefix of the target')
|
|
|
|
parser.add_argument('--broker', '-b', default='mqtt', type=str,
|
|
|
|
help='The MQTT broker address')
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
2021-08-09 18:50:58 +08:00
|
|
|
telemetry_queue = asyncio.LifoQueue()
|
|
|
|
|
|
|
|
async def telemetry():
|
|
|
|
await TelemetryReader.create(args.prefix, args.broker, telemetry_queue)
|
|
|
|
try:
|
|
|
|
while True:
|
|
|
|
await asyncio.sleep(1)
|
|
|
|
except asyncio.CancelledError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
telemetry_task = asyncio.Task(telemetry())
|
|
|
|
|
2021-08-05 21:05:55 +08:00
|
|
|
async def test():
|
|
|
|
""" The actual testing being completed. """
|
|
|
|
interface = await Miniconf.create(args.prefix, args.broker)
|
2021-08-09 18:50:58 +08:00
|
|
|
|
|
|
|
# Disable IIR holds and configure the telemetry rate.
|
|
|
|
await interface.command('allow_hold', False, retain=False)
|
|
|
|
await interface.command('force_hold', False, retain=False)
|
|
|
|
await interface.command('telemetry_period', 1, retain=False)
|
2021-08-05 21:05:55 +08:00
|
|
|
|
|
|
|
# Test loopback with a static 1V output of the DACs.
|
2021-08-09 18:50:58 +08:00
|
|
|
await test_loopback(interface, telemetry_queue, 1.0)
|
2021-08-05 21:05:55 +08:00
|
|
|
|
|
|
|
# Repeat test with AFE = 2x
|
2021-08-09 18:50:58 +08:00
|
|
|
await test_loopback(interface, telemetry_queue, 1.0, gain=2)
|
2021-08-05 21:05:55 +08:00
|
|
|
|
|
|
|
# Test with 0V output
|
2021-08-09 18:50:58 +08:00
|
|
|
await test_loopback(interface, telemetry_queue, 0.0)
|
|
|
|
|
|
|
|
telemetry_task.cancel()
|
2021-08-05 21:05:55 +08:00
|
|
|
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
sys.exit(loop.run_until_complete(test()))
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|