diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 2adcd6c..e0c60dc 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -7,6 +7,12 @@ on: branches: [ master ] jobs: + metadata: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: Check metadata + run: tools/metadata.py csv_to_ulog: runs-on: ubuntu-latest steps: diff --git a/flix/mavlink.ino b/flix/mavlink.ino index 6c15f1f..fd80fae 100644 --- a/flix/mavlink.ino +++ b/flix/mavlink.ino @@ -268,6 +268,15 @@ int handleMavlinkCommand(const void *_m) { return MAV_RESULT_ACCEPTED; } + if (m.command == MAV_CMD_REQUEST_MESSAGE && m.param1 == MAVLINK_MSG_ID_COMPONENT_METADATA) { + mavlink_message_t response; + mavlink_msg_component_metadata_pack(mavlinkSysId, MAV_COMP_ID_AUTOPILOT1, &response, t * 1000, + 2566517357, // crc + "https://quadcopter.dev/meta/2566517357/general.json"); + sendMessage(&response); + return MAV_RESULT_ACCEPTED; + } + if (m.command == MAV_CMD_COMPONENT_ARM_DISARM) { if (m.param1 == 1 && controlThrottle > 0.05) return MAV_RESULT_DENIED; // don't arm if throttle is not low armed = m.param1 == 1; diff --git a/tools/general.json b/tools/general.json new file mode 100644 index 0000000..eb21de3 --- /dev/null +++ b/tools/general.json @@ -0,0 +1,10 @@ +{ + "version": 1, + "metadataTypes": [ + { + "type": 1, + "uri": "https://quadcopter.dev/meta/3307474483/parameters.json", + "fileCrc": 3307474483 + } + ] +} diff --git a/tools/metadata.py b/tools/metadata.py new file mode 100755 index 0000000..03172b2 --- /dev/null +++ b/tools/metadata.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +# Script for checking the metadata files + +import sys +import re +from pathlib import Path +import json +import zlib +import requests + + +def mavlink_crc323(data: bytes) -> int: + return (zlib.crc32(data, 0xFFFFFFFF) ^ 0xFFFFFFFF) & 0xFFFFFFFF + + +parameter_regexp = re.compile(r'^\s+\{"(?P[^"]+)",\s*&(?P[^,]+)(.*)\},?$', re.MULTILINE) + +parameters_source_file = Path(__file__).parent.parent / 'flix' / 'parameters.ino' +parameters_code = parameters_source_file.read_text() +parameters_meta_file = Path(__file__).parent / 'parameters.json' +parameters_meta = json.loads(parameters_meta_file.read_text()) +assert parameters_meta['version'] == 1 + +parameters = parameter_regexp.finditer(parameters_code) + +for parameter in parameters: + name = parameter.group('name') + variable = parameter.group('variable') + + for meta in parameters_meta['parameters']: + name_meta = meta['name'] + regex = re.sub(r'\{[^\}]+\}', r'.*', name_meta) # handle {n} wildcards + if re.fullmatch(regex, name): + break + else: + raise RuntimeError(f'Parameter {name} is missing in parameters.json') + +general_meta_file = Path(__file__).parent / 'general.json' +general_crc = mavlink_crc323(general_meta_file.read_bytes()) +general_data = json.loads(general_meta_file.read_text()) + +parameters_crc = mavlink_crc323(parameters_meta_file.read_bytes()) + +general_url = f'https://quadcopter.dev/meta/{general_crc}/general.json' +parameters_url = f'https://quadcopter.dev/meta/{parameters_crc}/parameters.json' + +print(f'General CRC: {general_crc}') +print(f'Parameters CRC: {mavlink_crc323(parameters_meta_file.read_bytes())}') +print(f'General URL: {general_url}') +print(f'Parameters URL: {parameters_url}') + +code = 0 + +assert general_data['version'] == 1 +for meta in general_data['metadataTypes']: + if meta['type'] == 1: + if meta['uri'] != parameters_url: + print(f'ERROR: Parameters URI should be {parameters_url}') + code = 1 + if meta['fileCrc'] != parameters_crc: + print(f'ERROR: Parameters CRC shoule be {parameters_crc}') + code = 1 + break +else: + print('ERROR: Parameters metadata not found in general.json') + code = 1 + +resp = requests.get(general_url, allow_redirects=True) +resp.raise_for_status() +assert resp.json() == general_data + +resp = requests.get(parameters_url, allow_redirects=True) +resp.raise_for_status() +assert resp.json() == parameters_meta + +if code == 0: + print('Everything is good') +sys.exit(code) diff --git a/tools/parameters.json b/tools/parameters.json new file mode 100644 index 0000000..6c796a2 --- /dev/null +++ b/tools/parameters.json @@ -0,0 +1,192 @@ +{ + "version": 1, + "parameters": [ + { "name": "CTL_RATE_R_P", "type": "Float", "group": "Control", "shortDesc": "Proportional gain for roll rate" }, + { "name": "CTL_RATE_R_I", "type": "Float", "group": "Control", "shortDesc": "Integral gain for roll rate" }, + { "name": "CTL_RATE_R_D", "type": "Float", "group": "Control", "shortDesc": "Derivative gain for roll rate" }, + { "name": "CTL_RATE_R_WU", "type": "Float", "group": "Control", "shortDesc": "Integral windup limit for roll rate" }, + { "name": "CTL_RATE_R_D_A", "type": "Float", "group": "Control", "shortDesc": "Low-pass filter alpha for roll rate" }, + { "name": "CTL_RATE_P_P", "type": "Float", "group": "Control", "shortDesc": "Proportional gain for pitch rate" }, + { "name": "CTL_RATE_P_I", "type": "Float", "group": "Control", "shortDesc": "Integral gain for pitch rate" }, + { "name": "CTL_RATE_P_D", "type": "Float", "group": "Control", "shortDesc": "Derivative gain for pitch rate" }, + { "name": "CTL_RATE_P_WU", "type": "Float", "group": "Control", "shortDesc": "Integral windup limit for pitch rate" }, + { "name": "CTL_RATE_P_D_A", "type": "Float", "group": "Control", "shortDesc": "Low-pass filter alpha for pitch rate" }, + { "name": "CTL_RATE_Y_P", "type": "Float", "group": "Control", "shortDesc": "Proportional gain for yaw rate" }, + { "name": "CTL_RATE_Y_I", "type": "Float", "group": "Control", "shortDesc": "Integral gain for yaw rate" }, + { "name": "CTL_RATE_Y_D", "type": "Float", "group": "Control", "shortDesc": "Derivative gain for yaw rate" }, + { "name": "CTL_RATE_Y_WU", "type": "Float", "group": "Control", "shortDesc": "Integral windup limit for yaw rate" }, + { "name": "CTL_RATE_Y_D_A", "type": "Float", "group": "Control", "shortDesc": "Low-pass filter alpha for yaw rate" }, + + { "name": "CTL_RATE_P_MAX", "type": "Float", "group": "Control", "units": "rad/s", "shortDesc": "Maximum pitch rate for ACRO mode" }, + { "name": "CTL_RATE_R_MAX", "type": "Float", "group": "Control", "units": "rad/s", "shortDesc": "Maximum roll rate for ACRO mode" }, + { "name": "CTL_RATE_Y_MAX", "type": "Float", "group": "Control", "units": "rad/s", "shortDesc": "Maximum yaw rate for ACRO mode" }, + + { "name": "CTL_ATT_R_P", "type": "Float", "group": "Control", "shortDesc": "Proportional gain for roll angle" }, + { "name": "CTL_ATT_P_P", "type": "Float", "group": "Control", "shortDesc": "Proportional gain for pitch angle" }, + { "name": "CTL_ATT_Y_P", "type": "Float", "group": "Control", "shortDesc": "Proportional gain for yaw angle" }, + { "name": "CTL_ATT_MAX", "type": "Float", "group": "Control", "units": "radians", "shortDesc": "Maximum tilt angle for STAB mode" }, + + { + "name": "CTL_FLT_MODE_{n}", + "type": "Float", + "group": "Control", + "shortDesc": "Flight mode for switch position {n}", + "values": [{ "value": 0, "description": "RAW" }, { "value": 1, "description": "ACRO" }, { "value": 2, "description": "STAB" }, { "value": 3, "description": "AUTO" }, { "value": 4, "description": "POS" }] + }, + + { + "name": "IMU_MODEL", + "type": "Float", + "shortDesc": "IMU sensor model", + "group": "IMU", + "increment": 1, + "decimalPlaces": 0, + "values": [{ "value": -1, "description": "Disabled"}, { "value": 1, "description": "MPU-9250"}, { "value": 2, "description": "ICM-20948"}, { "value": 3, "description": "MPU-6050"}, { "value": 4, "description": "ICM-40609-D"}], + "rebootRequired": true + }, + { + "name": "IMU_BUS", + "type": "Float", + "shortDesc": "IMU bus type", + "group": "IMU", + "values": [ + { "value": 0, "description": "SPI" }, + { "value": 1, "description": "I2C" } + ], + "rebootRequired": true + }, + { "name": "IMU_PIN_SCK", "type": "Float", "group": "IMU", "shortDesc": "IMU SPI SCK pin", "min": 1, "increment": 1, "decimalPlaces": 0 }, + { "name": "IMU_PIN_MISO", "type": "Float", "group": "IMU", "shortDesc": "IMU SPI MISO pin", "min": 1, "increment": 1, "decimalPlaces": 0 }, + { "name": "IMU_PIN_MOSI", "type": "Float", "group": "IMU", "shortDesc": "IMU SPI MOSI pin", "min": 1, "increment": 1, "decimalPlaces": 0 }, + { "name": "IMU_PIN_CS", "type": "Float", "group": "IMU", "shortDesc": "IMU SPI CS pin", "min": 1, "increment": 1, "decimalPlaces": 0 }, + { "name": "IMU_PIN_SDA", "type": "Float", "group": "IMU", "shortDesc": "IMU I2C SDA pin", "min": 1, "increment": 1, "decimalPlaces": 0 }, + { "name": "IMU_PIN_SCL", "type": "Float", "group": "IMU", "shortDesc": "IMU I2C SCL pin", "min": 1, "increment": 1, "decimalPlaces": 0 }, + { "name": "IMU_PIN_INT", "type": "Float", "group": "IMU", "shortDesc": "IMU interrupt pin (-1 for using timer)", "min": -1, "increment": 1, "decimalPlaces": 0 }, + + { "name": "IMU_ROT_ROLL", "type": "Float", "group": "IMU", "units": "radians", "shortDesc": "IMU roll rotation" }, + { "name": "IMU_ROT_PITCH", "type": "Float", "group": "IMU", "units": "radians", "shortDesc": "IMU pitch rotation" }, + { "name": "IMU_ROT_YAW", "type": "Float", "group": "IMU", "units": "radians", "shortDesc": "IMU yaw rotation" }, + + { "name": "IMU_ACC_BIAS_X", "type": "Float", "group": "IMU (Calibration)", "units": "m/s^2", "shortDesc": "Accelerometer bias X", "volatile": true }, + { "name": "IMU_ACC_BIAS_Y", "type": "Float", "group": "IMU (Calibration)", "units": "m/s^2", "shortDesc": "Accelerometer bias Y", "volatile": true }, + { "name": "IMU_ACC_BIAS_Z", "type": "Float", "group": "IMU (Calibration)", "units": "m/s^2", "shortDesc": "Accelerometer bias Z", "volatile": true }, + + { "name": "IMU_ACC_SCALE_X", "type": "Float", "group": "IMU (Calibration)", "shortDesc": "Accelerometer scale X", "volatile": true }, + { "name": "IMU_ACC_SCALE_Y", "type": "Float", "group": "IMU (Calibration)", "shortDesc": "Accelerometer scale Y", "volatile": true }, + { "name": "IMU_ACC_SCALE_Z", "type": "Float", "group": "IMU (Calibration)", "shortDesc": "Accelerometer scale Z", "volatile": true }, + + { "name": "IMU_GYRO_BIAS_A", "type": "Float", "group": "IMU", "shortDesc": "Alpha for gyroscope bias estimation" }, + + { "name": "EST_ACC_WEIGHT", "type": "Float", "group": "Estimation", "shortDesc": "Accelerometer weight" }, + { "name": "EST_LVL_WEIGHT", "type": "Float", "group": "Estimation", "shortDesc": "Level weight", "decimalPlaces": 5 }, + { "name": "EST_RATES_LPF_A", "type": "Float", "group": "Estimation", "shortDesc": "Low-pass filter alpha for rates" }, + { "name": "EST_RATES_NO_F", "type": "Float", "group": "Estimation", "units": "Hz", "shortDesc": "Notch filter center frequency for rates", "min": 0, "decimalPlaces": 0 }, + { "name": "EST_RATES_NO_BW", "type": "Float", "group": "Estimation", "units": "Hz", "shortDesc": "Notch filter bandwidth for rates", "min": 0, "decimalPlaces": 0 }, + + { "name": "MOT_PIN_FL", "type": "Float", "group": "Motors", "shortDesc": "Front-left motor pin (-1 to disabled)", "min": -1, "increment": 1, "decimalPlaces": 0 }, + { "name": "MOT_PIN_FR", "type": "Float", "group": "Motors", "shortDesc": "Front-right motor pin (-1 to disabled)", "min": -1, "increment": 1, "decimalPlaces": 0 }, + { "name": "MOT_PIN_RL", "type": "Float", "group": "Motors", "shortDesc": "Rear-left motor pin (-1 to disabled)", "min": -1, "increment": 1, "decimalPlaces": 0 }, + { "name": "MOT_PIN_RR", "type": "Float", "group": "Motors", "shortDesc": "Rear-right motor pin (-1 to disabled)", "min": -1, "increment": 1, "decimalPlaces": 0 }, + { "name": "MOT_PWM_FREQ", "type": "Float", "group": "Motors", "units": "Hz", "shortDesc": "PWM frequency", "min": 0, "increment": 1, "decimalPlaces": 0 }, + { "name": "MOT_PWM_RES", "type": "Float", "group": "Motors", "units": "bits", "shortDesc": "PWM resolution", "min": 1, "increment": 1, "decimalPlaces": 0 }, + { "name": "MOT_PWM_STOP", "type": "Float", "group": "Motors", "units": "μs", "shortDesc": "PWM for stopping the motors (brushless motors)", "min": 0, "decimalPlaces": 0 }, + { "name": "MOT_PWM_MIN", "type": "Float", "group": "Motors", "units": "μs", "shortDesc": "PWM for minimum throttle (brushless motors)", "min": 0, "decimalPlaces": 0 }, + { "name": "MOT_PWM_MAX", "type": "Float", "group": "Motors", "units": "μs", "shortDesc": "PWM for maximum throttle (-1 for brushed motors)", "min": -1, "decimalPlaces": 0 }, + + { "name": "RC_RX_PIN", "type": "Float", "group": "RC", "shortDesc": "RC receiver RX pin (-1 for disabled)", "min": -1, "increment": 1, "decimalPlaces": 0 }, + { + "name": "RC_ZERO_{n}", + "type": "Float", + "group": "RC (Calibration)", + "shortDesc": "RC channel {n} zero PWM", + "units": "μs", + "min": 0, + "decimalPlaces": 0 + }, + { + "name": "RC_MAX_{n}", + "type": "Float", + "group": "RC (Calibration)", + "shortDesc": "RC channel {n} maximum PWM", + "units": "μs", + "min": 0, + "decimalPlaces": 0 + }, + + { "name": "RC_ROLL", "type": "Float", "group": "RC (Calibration)", "shortDesc": "RC roll channel", "min": 0, "increment": 1, "decimalPlaces": 0 }, + { "name": "RC_PITCH", "type": "Float", "group": "RC (Calibration)", "shortDesc": "RC pitch channel", "min": 0, "increment": 1, "decimalPlaces": 0 }, + { "name": "RC_THROTTLE", "type": "Float", "group": "RC (Calibration)", "shortDesc": "RC throttle channel", "min": 0, "increment": 1, "decimalPlaces": 0 }, + { "name": "RC_YAW", "type": "Float", "group": "RC (Calibration)", "shortDesc": "RC yaw channel", "min": 0, "increment": 1, "decimalPlaces": 0 }, + { "name": "RC_MODE", "type": "Float", "group": "RC (Calibration)", "shortDesc": "RC mode switch channel", "min": 0, "increment": 1, "decimalPlaces": 0 }, + + { + "name": "WIFI_MODE", + "type": "Float", + "group": "Wi-Fi", + "shortDesc": "Wi-Fi mode", + "values": [ + { "value": 0, "description": "Disabled" }, + { "value": 1, "description": "Access point (AP)" }, + { "value": 2, "description": "Client (STA)" }, + { "value": 3, "description": "ESP-NOW" } + ] + }, + { "name": "WIFI_PORT_LOC", "type": "Float", "group": "Wi-Fi", "shortDesc": "Local UDP port", "min": 0, "max": 65535, "increment": 1, "decimalPlaces": 0 }, + { "name": "WIFI_PORT_REM", "type": "Float", "group": "Wi-Fi", "shortDesc": "Remote UDP port", "min": 0, "max": 65535, "increment": 1, "decimalPlaces": 0 }, + { + "name": "WIFI_LONG_RANGE", + "type": "Float", + "group": "Wi-Fi", + "shortDesc": "Long-range WiFi mode", + "values": [ + { "value": 0, "description": "Off" }, + { "value": 1, "description": "On" } + ] + }, + { + "name": "WIFI_BROADCAST", + "type": "Float", + "group": "Wi-Fi", + "shortDesc": "Always broadcast UDP", + "values": [ + { "value": 0, "description": "Off" }, + { "value": 1, "description": "On" } + ] + }, + + { "name": "ESPNOW_CHANNEL", "type": "Float", "group": "ESP-NOW", "shortDesc": "ESP-NOW channel", "min": 1, "max": 13, "increment": 1, "decimalPlaces": 0 }, + + { "name": "MAV_SYS_ID", "type": "Float", "group": "MAVLink", "shortDesc": "MAVLink system ID", "min": 1, "increment": 1, "decimalPlaces": 0 }, + { "name": "MAV_RATE_SLOW", "type": "Float", "group": "MAVLink", "units": "Hz", "shortDesc": "Rate for heartbeats and battery state", "min": 0, "decimalPlaces": 0 }, + { "name": "MAV_RATE_FAST", "type": "Float", "group": "MAVLink", "units": "Hz", "shortDesc": "Rate for attitude, RC channels and motors state", "min": 0, "decimalPlaces": 0 }, + { "name": "MAV_RATE_EXTRA", "type": "Float", "group": "MAVLink", "units": "Hz", "shortDesc": "Rate for IMU data and exposed log topic", "min": 0, "decimalPlaces": 0 }, + { "name": "MAV_RATE_ATT", "type": "Float", "group": "MAVLink", "units": "Hz", "shortDesc": "Rate for attitude", "min": 0, "decimalPlaces": 0 }, + { "name": "MAV_RATE_ATT_TG", "type": "Float", "group": "MAVLink", "units": "Hz", "shortDesc": "Rate for attitude target", "min": 0, "decimalPlaces": 0 }, + { "name": "MAV_RATE_RC", "type": "Float", "group": "MAVLink", "units": "Hz", "shortDesc": "Rate for RC channels", "min": 0, "decimalPlaces": 0 }, + { "name": "MAV_RATE_MOT", "type": "Float", "group": "MAVLink", "units": "Hz", "shortDesc": "Rate for motors state", "min": 0, "decimalPlaces": 0 }, + { "name": "MAV_RATE_IMU", "type": "Float", "group": "MAVLink", "units": "Hz", "shortDesc": "Rate for IMU data", "min": 0, "decimalPlaces": 0 }, + { "name": "MAV_RATE_TOPIC", "type": "Float", "group": "MAVLink", "units": "Hz", "shortDesc": "Rate for exposed log topic", "min": 0, "decimalPlaces": 0 }, + + { + "name": "LOG_MEMORY", + "type": "Float", + "group": "Log", + "shortDesc": "Memory type for logging", + "values": [ + { "value": -1, "description": "Disabled" }, + { "value": 0, "description": "RAM" }, + { "value": 1, "description": "PSRAM" } + ] + }, + { "name": "LOG_USAGE", "type": "Float", "group": "Log", "units": "norm", "shortDesc": "Percentage of memory to use for logging" }, + { "name": "LOG_RATE_{n}", "type": "Float", "group": "Log", "units": "Hz", "shortDesc": "Log topic {n} rate", "min": 0, "decimalPlaces": 0 }, + + { "name": "PWR_VOLT_PIN", "type": "Float", "group": "Power", "shortDesc": "Voltage ADC input pin (-1 for disabled)", "min": -1, "increment": 1, "decimalPlaces": 0 }, + { "name": "PWR_VOLT_SCALE", "type": "Float", "group": "Power", "shortDesc": "Voltage divider scale factor" }, + { "name": "PWR_VOLT_LPF_A", "type": "Float", "group": "Power", "shortDesc": "Low-pass filter alpha for battery voltage" }, + + { "name": "SF_RC_LOSS_TIME", "type": "Float", "group": "Safety", "units": "s", "shortDesc": "RC signal loss timeout in seconds", "min": 0, "decimalPlaces": 1 }, + { "name": "SF_DESCEND_TIME", "type": "Float", "group": "Safety", "units": "s", "shortDesc": "Automatic descent timeout in seconds", "min": 0, "decimalPlaces": 1 }, + { "name": "SF_DISARM_TILT", "type": "Float", "group": "Safety", "units": "radians", "shortDesc": "Tilt angle above which auto-disarm triggers" } + ] +}