129 lines
4.1 KiB
Python
129 lines
4.1 KiB
Python
#!/usr/bin/python3
|
|
"""
|
|
Author: Vertigo Designs, Ryan Summers
|
|
|
|
Description: Implements HITL testing of Stabilizer data livestream capabilities.
|
|
"""
|
|
import asyncio
|
|
import sys
|
|
import argparse
|
|
import logging
|
|
import socket
|
|
import time
|
|
|
|
from miniconf import Miniconf
|
|
from stabilizer.stream import StabilizerStream
|
|
|
|
# The duration to receive frames for.
|
|
STREAM_TEST_DURATION_SECS = 5.0
|
|
|
|
# The minimum efficiency of the stream in frame transfer to pass testing. Represented as
|
|
# (received_frames / transmitted_frames).
|
|
MIN_STREAM_EFFICIENCY = 0.95
|
|
|
|
def _get_ip(broker):
|
|
""" Get the IP of the local device.
|
|
|
|
Args:
|
|
broker: The broker IP of the test. Used to select an interface to get the IP of.
|
|
|
|
Returns:
|
|
The IP as an array of integers.
|
|
"""
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
try:
|
|
sock.connect((broker, 1883))
|
|
address = sock.getsockname()[0]
|
|
finally:
|
|
sock.close()
|
|
|
|
return list(map(int, address.split('.')))
|
|
|
|
|
|
def sequence_delta(previous_sequence, next_sequence):
|
|
""" Check the number of items between two sequence numbers. """
|
|
if previous_sequence is None:
|
|
return 0
|
|
|
|
delta = next_sequence - (previous_sequence + 1)
|
|
return delta & 0xFFFFFFFF
|
|
|
|
|
|
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')
|
|
parser.add_argument('--port', '-p', default=2000, type=int,
|
|
help='The UDP port to use for streaming')
|
|
|
|
args = parser.parse_args()
|
|
|
|
async def test():
|
|
""" The actual testing being completed. """
|
|
local_ip = _get_ip(args.broker)
|
|
interface = await Miniconf.create(args.prefix, args.broker)
|
|
stream = StabilizerStream(args.port, timeout=0.5)
|
|
|
|
# Configure the stream
|
|
print(f'Configuring stream to target {".".join(map(str, local_ip))}:{args.port}')
|
|
print('')
|
|
await interface.command('stream_target', {'ip': local_ip, 'port': args.port}, retain=False)
|
|
await interface.command('telemetry_period', 10, retain=False)
|
|
|
|
# Verify frame reception
|
|
print('Testing stream reception')
|
|
print('')
|
|
last_sequence = None
|
|
|
|
# Sample frames over a set time period and verify that no drops are encountered.
|
|
stop = time.time() + STREAM_TEST_DURATION_SECS
|
|
dropped_batches = 0
|
|
total_batches = 0
|
|
|
|
while time.time() < stop:
|
|
for (seqnum, _data) in stream.read_frame():
|
|
num_dropped = sequence_delta(last_sequence, seqnum)
|
|
total_batches += 1 + num_dropped
|
|
|
|
if num_dropped:
|
|
dropped_batches += num_dropped
|
|
logging.warning('Frame drop detected: 0x%08X -> 0x%08X (%d batches)',
|
|
last_sequence, seqnum, num_dropped)
|
|
|
|
last_sequence = seqnum
|
|
|
|
assert total_batches, 'Stream did not receive any frames'
|
|
stream_efficiency = 1.0 - (dropped_batches / total_batches)
|
|
|
|
print(f'Stream Reception Rate: {stream_efficiency * 100:.2f} %')
|
|
print(f'Received {total_batches} ')
|
|
print(f'Lost {dropped_batches} batches')
|
|
|
|
assert stream_efficiency > MIN_STREAM_EFFICIENCY, \
|
|
f'Stream dropped too many packets. Reception rate: {stream_efficiency * 100:.2f} %'
|
|
|
|
# Disable the stream.
|
|
print('Closing stream')
|
|
print('')
|
|
await interface.command('stream_target', {'ip': [0, 0, 0, 0], 'port': 0}, retain=False)
|
|
stream.clear()
|
|
|
|
print('Verifying no further data is received')
|
|
try:
|
|
for _ in stream.read_frame():
|
|
raise Exception('Unexpected data encountered on stream')
|
|
except socket.timeout:
|
|
pass
|
|
print('PASS')
|
|
|
|
|
|
loop = asyncio.get_event_loop()
|
|
sys.exit(loop.run_until_complete(test()))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|