px4-firmware/Tools/serial/generate_config.py

334 lines
11 KiB
Python
Executable File

#!/usr/bin/env python3
""" Script to generate Serial (UART) parameters and the ROMFS startup script """
import argparse
import os
import sys
try:
from jinja2 import Environment, FileSystemLoader
except ImportError as e:
print("Failed to import jinja2: " + str(e))
print("")
print("You may need to install it using:")
print(" pip3 install --user jinja2")
print("")
sys.exit(1)
try:
import yaml
except ImportError as e:
print("Failed to import yaml: " + str(e))
print("")
print("You may need to install it using:")
print(" pip3 install --user pyyaml")
print("")
sys.exit(1)
## Configuration
# All possible Serial ports
# Note: do not re-use or change indexes. When adding a port, always use an
# index that has never been used before. This is important for compatibility
# with QGC (parameter metadata)
serial_ports = {
# index 0 means disabled
# index 1000 means ethernet condiguration
# Generic
# "URT1": {
# "label": "UART 1",
# "index": 1,
# "default_baudrate": 57600,
# },
# "URT2": {
# "label": "UART 2",
# "index": 2,
# "default_baudrate": 57600,
# },
# "URT3": {
# "label": "UART 3",
# "index": 3,
# "default_baudrate": 57600,
# },
# "URT4": {
# "label": "UART 4",
# "index": 4,
# "default_baudrate": 57600,
# },
# "URT5": {
# "label": "UART 5",
# "index": 5,
# "default_baudrate": 57600,
# },
"URT6": {
"label": "UART 6",
"index": 6,
"default_baudrate": 57600,
},
# "URT7": {
# "label": "UART 7",
# "index": 7,
# "default_baudrate": 57600,
# },
# "URT8": {
# "label": "UART 8",
# "index": 8,
# "default_baudrate": 57600,
# },
# "URT9": {
# "label": "UART 9",
# "index": 9,
# "default_baudrate": 57600,
# },
# Telemetry Ports
"TEL1": { # telemetry link
"label": "TELEM 1",
"index": 101,
"default_baudrate": 57600,
},
"TEL2": { # companion port
"label": "TELEM 2",
"index": 102,
"default_baudrate": 921600,
},
"TEL3": {
"label": "TELEM 3",
"index": 103,
"default_baudrate": 57600,
},
"TEL4": {
"label": "TELEM/SERIAL 4",
"index": 104,
"default_baudrate": 57600,
},
# GPS Ports
"GPS1": {
"label": "GPS 1",
"index": 201,
"default_baudrate": 0,
},
"GPS2": {
"label": "GPS 2",
"index": 202,
"default_baudrate": 0,
},
"GPS3": {
"label": "GPS 3",
"index": 203,
"default_baudrate": 0,
},
# RC Port
"RC": {
"label": "Radio Controller",
"index": 300,
"default_baudrate": 0,
},
# WIFI Port (PixRacer)
"WIFI": {
"label": "Wifi Port",
"index": 301,
"default_baudrate": 1, # set default to an unusable value to detect that this serial port has not been configured
},
# EXT2
"EXT2": {
"label": "EXT2",
"index": 401,
"default_baudrate": 57600,
},
}
parser = argparse.ArgumentParser(description='Generate Serial params & startup script')
parser.add_argument('--serial-ports', type=str, nargs='*', metavar="TAG:DEVICE",
default=[],
help='Serial ports: mappings from the tag name to the device (e.g. GPS1:/dev/ttyS1)')
parser.add_argument('--config-files', type=str, nargs='*', default=[],
help='YAML module config file(s)')
parser.add_argument('--all-ports', action='store_true',
help='Generate output for all known ports (params file only)')
parser.add_argument('--constrained-flash', action='store_true',
help='Reduce verbosity in ROMFS scripts to reduce flash size')
parser.add_argument('--rc-dir', type=str, action='store',
help='ROMFS output directory', default=None)
parser.add_argument('--params-file', type=str, action='store',
help='Parameter output file', default=None)
parser.add_argument('--ethernet', action='store_true',
help='Ethernet support')
parser.add_argument('-v', '--verbose', dest='verbose', action='store_true',
help='Verbose Output')
args = parser.parse_args()
arg_board_serial_ports = args.serial_ports
verbose = args.verbose
rc_serial_output_dir = args.rc_dir
rc_serial_template = 'rc.serial.jinja'
rc_serial_port_template = 'rc.serial_port.jinja'
serial_params_output_file = args.params_file
serial_params_template = 'serial_params.c.jinja'
generate_for_all_ports = args.all_ports
constrained_flash = args.constrained_flash
ethernet_supported = args.ethernet
if generate_for_all_ports:
board_ports = [(key, "") for key in serial_ports]
else:
# convert arg_board_serial_ports list [ "TAG:DEVICE" ] into [ ("TAG", "DEVICE") ]
board_ports = [tuple(port.split(":")) for port in arg_board_serial_ports]
if rc_serial_output_dir is None and serial_params_output_file is None:
raise Exception("At least one of --rc-dir or --params-file "
"(e.g. serial_params.c) needs to be specified")
# parse the YAML files
serial_commands = []
ethernet_configuration = []
if ethernet_supported:
ethernet_configuration.append({
'tag': "ETH",
'label': "Ethernet",
'index': 1000
})
def parse_yaml_serial_config(yaml_config):
""" parse the serial_config section from the yaml config file """
if 'serial_config' not in yaml_config:
return []
ret = []
module_name = yaml_config['module_name']
for serial_config in yaml_config['serial_config']:
if 'label' not in serial_config:
serial_config['label'] = module_name
ret.append(serial_config)
return ret
for yaml_file in args.config_files:
with open(yaml_file, 'r') as stream:
try:
yaml_config = yaml.load(stream, Loader=yaml.Loader)
serial_commands.extend(parse_yaml_serial_config(yaml_config))
except yaml.YAMLError as exc:
print(exc)
raise
# sanity check (makes sure the param names don't exceed the max length of 16 chars)
for key in serial_ports:
if len(key) > 4:
raise Exception("Serial tag {:} is too long (max length=4)".format(key))
serial_devices = []
for tag, device in board_ports:
if tag not in serial_ports:
raise Exception("Unknown serial port {:}. "
"You might have to add it to serial_ports in\n {:}".format(tag,
os.path.realpath(__file__)))
serial_devices.append({
'tag': tag,
'device': device,
'label': serial_ports[tag]["label"],
'index': serial_ports[tag]["index"],
'default_baudrate': serial_ports[tag]["default_baudrate"]
})
# construct commands based on selected board
commands = []
for serial_command in serial_commands:
num_instances = serial_command.get('num_instances', 1)
# TODO: use a loop in the script instead of explicitly enumerating all instances
for i in range(num_instances):
port_config = serial_command['port_config_param']
port_param_name = port_config['name'].replace('${i}', str(i))
# check if a port dependency is specified
if 'depends_on_port' in port_config:
depends_on_port = port_config['depends_on_port']
if not any(p['tag'] == depends_on_port for p in serial_devices):
if verbose:
print("Skipping {:} (missing dependent port)".format(port_param_name))
continue
default_port = 0 # disabled
if 'default' in port_config:
if type(port_config['default']) == list:
assert len(port_config['default']) == num_instances
default_port_str = port_config['default'][i]
else:
default_port_str = port_config['default']
if default_port_str != "":
if default_port_str not in serial_ports:
raise Exception("Default Port {:} not found for {:}".format(default_port_str, serial_command['label']))
if default_port_str in dict(board_ports).keys():
default_port = serial_ports[default_port_str]['index']
commands.append({
'command': serial_command['command'],
'label': serial_command['label'],
'instance': i,
'multi_instance': num_instances > 1,
'port_param_name': port_param_name,
'default_port': default_port,
'param_group': port_config['group'],
'description_extended': port_config.get('description_extended', ''),
'supports_networking': serial_command.get('supports_networking', False)
})
if verbose:
print("Serial Devices: {:}".format(serial_devices))
#print("Commands: {:}".format(commands))
jinja_env = Environment(loader=FileSystemLoader(
os.path.dirname(os.path.realpath(__file__))))
# generate the ROMFS script using a jinja template
if rc_serial_output_dir is not None:
if generate_for_all_ports:
raise Exception("Cannot create rc file for --all-ports")
rc_serial_output_file = os.path.join(rc_serial_output_dir, "rc.serial")
rc_serial_port_output_file = os.path.join(rc_serial_output_dir, "rc.serial_port")
if verbose: print("Generating {:}".format(rc_serial_output_file))
if len(serial_devices) == 0:
# if the board has no UARTs, create an empty rc file
open(rc_serial_output_file, 'w').close()
else:
template = jinja_env.get_template(rc_serial_template)
with open(rc_serial_output_file, 'w') as fid:
fid.write(template.render(serial_devices=serial_devices,
commands=commands,
constrained_flash=constrained_flash))
if verbose: print("Generating {:}".format(rc_serial_port_output_file))
template = jinja_env.get_template(rc_serial_port_template)
with open(rc_serial_port_output_file, 'w') as fid:
fid.write(template.render(serial_devices=serial_devices,
ethernet_configuration=ethernet_configuration,
constrained_flash=constrained_flash))
# parameter definitions
if serial_params_output_file is not None:
if verbose: print("Generating {:}".format(serial_params_output_file))
template = jinja_env.get_template(serial_params_template)
with open(serial_params_output_file, 'w') as fid:
fid.write(template.render(serial_devices=serial_devices,
ethernet_configuration=ethernet_configuration,
commands=commands, serial_ports=serial_ports))