diff --git a/Tools/scripts/build_ci.sh b/Tools/scripts/build_ci.sh index d4f04f1fa5782..4f3c797a77ce9 100755 --- a/Tools/scripts/build_ci.sh +++ b/Tools/scripts/build_ci.sh @@ -591,6 +591,7 @@ for t in $CI_BUILD_TARGET; do for v in Rover Tracker Copter Plane Sub Blimp; do python3 Tools/autotest/logger_metadata/parse.py --vehicle $v done + python3 Tools/scripts/decode_devid.py --dump-json /dev/null --dump-json5 /dev/null continue fi diff --git a/Tools/scripts/build_log_message_documentation.sh b/Tools/scripts/build_log_message_documentation.sh index fffd10f0bbf77..3f0fa50f4afaf 100755 --- a/Tools/scripts/build_log_message_documentation.sh +++ b/Tools/scripts/build_log_message_documentation.sh @@ -30,3 +30,8 @@ generate_log_message_documentation() { for vehicle in Rover Plane Copter Tracker Blimp Sub; do generate_log_message_documentation "$vehicle" done + +# device ID bus/device type tables (vehicle-independent) for decoding *_DEV_ID parameters and log fields: +./Tools/scripts/decode_devid.py --dump-json "$DIR/devid.json.new" --dump-json5 "$DIR/devid.json5.new" +mv "$DIR/devid.json.new" "$DIR/devid.json" +mv "$DIR/devid.json5.new" "$DIR/devid.json5" diff --git a/Tools/scripts/decode_devid.py b/Tools/scripts/decode_devid.py index eec4fcc862787..da897ff653ad2 100755 --- a/Tools/scripts/decode_devid.py +++ b/Tools/scripts/decode_devid.py @@ -3,8 +3,13 @@ """ Decode a device ID such as used for COMPASS_DEV_ID, INS_ACC_ID etc -To understand the devtype you should look at the backend headers for -the sensor library, such as libraries/AP_Compass/AP_Compass_Backend.h +Bus type and devtype names are parsed live from the C++ enums in the +sensor backend headers (such as libraries/AP_Compass/AP_Compass_Backend.h) +using Tools/autotest/logger_metadata/enum_parse.py. --dump-json and +--dump-json5 write those tables out for use by tools which do not have an +ArduPilot source tree; --json reads such a file back instead of parsing +the headers. A copy of this script outside the ArduPilot source tree reads +devid.json from the directory it is in. AP_FLAKE8_CLEAN @@ -15,147 +20,331 @@ """ import argparse +import functools +import hashlib +import importlib +import json +import os import sys +from typing import Any from typing import Dict +from typing import List from typing import Literal +from typing import NamedTuple from typing import Optional +from typing import TextIO from typing import Tuple from typing import TypedDict +from typing import get_args + +__all__ = [ # noqa: F822 (the *_TYPES tables are provided by __getattr__) + "AIRSPEED_TYPES", + "BARO_TYPES", + "BUSTYPES", + "COMPASS_TYPES", + "DEVICE_CATEGORIES", + "DEVID_JSON_FORMAT_VERSION", + "DeviceCategory", + "DeviceInfo", + "IMU_TYPES", + "MAVLINK_TYPES", + "decode_device_id", + "devid_data_version", + "dump_devid_json", + "dump_devid_json5", + "format_device_info", + "get_bus_type_name", + "get_device_type_description", + "get_device_type_name", + "get_devid_tables", + "load_devid_json", + "parse_device_id", + "parse_devid_sources", + "use_devid_json", +] + +# realpath so that running through a symlink still finds the source tree +SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +REPO_ROOT = os.path.realpath(os.path.join(SCRIPT_DIR, "..", "..")) +ENUM_PARSE_DIR = os.path.join(REPO_ROOT, "Tools", "autotest", "logger_metadata") + +# used when this script has been copied out of the ArduPilot source tree +DEVID_JSON_FALLBACK_PATH = os.path.join(SCRIPT_DIR, "devid.json") + +# version of the structure of the files written by --dump-json/--dump-json5 +DEVID_JSON_FORMAT_VERSION = 1 -# Device type lookup tables -BUSTYPES: Dict[int, str] = { - 1: "I2C", - 2: "SPI", - 3: "DRONECAN", - 4: "SITL", - 5: "MSP", - 6: "SERIAL", - 7: "WSPI", +DeviceCategory = Literal["compass", "imu", "baro", "airspeed", "mavlink"] +DEVICE_CATEGORIES: Tuple[DeviceCategory, ...] = get_args(DeviceCategory) + + +class EnumSource(NamedTuple): + header: str # relative to REPO_ROOT + enum_name: str # fully-qualified name as reported by enum_parse + strip_prefix: str = "" + add_prefix: str = "" + + +BUS_TYPE_SOURCE = EnumSource("libraries/AP_HAL/Device.h", "AP_HAL::Device::BusType", strip_prefix="BUS_TYPE_") + +DEVICE_TYPE_SOURCES: Dict[str, EnumSource] = { + "compass": EnumSource("libraries/AP_Compass/AP_Compass_Backend.h", "AP_Compass_Backend::DevTypes"), + "imu": EnumSource("libraries/AP_InertialSensor/AP_InertialSensor_Backend.h", "AP_InertialSensor_Backend::DevTypes"), + "baro": EnumSource("libraries/AP_Baro/AP_Baro_Backend.h", "AP_Baro_Backend::DevTypes"), + "airspeed": EnumSource( + "libraries/AP_Airspeed/AP_Airspeed_Backend.h", "AP_Airspeed_Backend::DevType", add_prefix="DEVTYPE_AIRSPEED_" + ), + "mavlink": EnumSource( + "libraries/AP_SerialManager/AP_SerialManager.h", + "AP_SerialManager::DeviceType", + add_prefix="DEVTYPE_MAVLINK_", + ), } -COMPASS_TYPES: Dict[int, str] = { - 0x01: "DEVTYPE_HMC5883_OLD", - 0x07: "DEVTYPE_HMC5883", - 0x02: "DEVTYPE_LSM303D", - 0x04: "DEVTYPE_AK8963 ", - 0x05: "DEVTYPE_BMM150 ", - 0x06: "DEVTYPE_LSM9DS1", - 0x08: "DEVTYPE_LIS3MDL", - 0x09: "DEVTYPE_AK0991x", - 0x0A: "DEVTYPE_IST8310", - 0x0B: "DEVTYPE_ICM20948", - 0x0C: "DEVTYPE_MMC3416", - 0x0D: "DEVTYPE_QMC5883L", - 0x0E: "DEVTYPE_MAG3110", - 0x0F: "DEVTYPE_SITL", - 0x10: "DEVTYPE_IST8308", - 0x11: "DEVTYPE_RM3100_OLD", - 0x12: "DEVTYPE_RM3100", - 0x13: "DEVTYPE_MMC5883", - 0x14: "DEVTYPE_AK09918", - 0x15: "DEVTYPE_AK09915", - 0x16: "DEVTYPE_QMC5883P", - 0x17: "DEVTYPE_BMM350", - 0x18: "DEVTYPE_IIS2MDC", - 0x19: "DEVTYPE_LIS2MDL", # unused except on pre-release firmware - 0x1A: "DEVTYPE_AF9838", +# Names we present differently from the C++ enumerator (after prefix +# handling). Keys are "bus" or a device category. Every rename must +# match an enumerator, so stale entries are caught. +RENAMES: Dict[str, Dict[str, str]] = { + "bus": {"UAVCAN": "DRONECAN"}, + "compass": { + # the driver supports several similarly-named AK0991x compasses + "DEVTYPE_AK09916": "DEVTYPE_AK0991x", + }, + "baro": {"DEVTYPE_BARO_UAVCAN": "DEVTYPE_BARO_DRONECAN"}, + "airspeed": {"DEVTYPE_AIRSPEED_UAVCAN": "DEVTYPE_AIRSPEED_DRONECAN"}, + "mavlink": {"DEVTYPE_MAVLINK_CANBUS": "DEVTYPE_MAVLINK_CAN"}, } -IMU_TYPES: Dict[int, str] = { - 0x09: "DEVTYPE_BMI160", - 0x10: "DEVTYPE_L3G4200D", - 0x11: "DEVTYPE_ACC_LSM303D", - 0x12: "DEVTYPE_ACC_BMA180", - 0x13: "DEVTYPE_ACC_MPU6000", - 0x16: "DEVTYPE_ACC_MPU9250", - 0x17: "DEVTYPE_ACC_IIS328DQ", - 0x21: "DEVTYPE_GYR_MPU6000", - 0x22: "DEVTYPE_GYR_L3GD20", - 0x24: "DEVTYPE_GYR_MPU9250", - 0x25: "DEVTYPE_GYR_I3G4250D", - 0x26: "DEVTYPE_GYR_LSM9DS1", - 0x27: "DEVTYPE_INS_ICM20789", - 0x28: "DEVTYPE_INS_ICM20689", - 0x29: "DEVTYPE_INS_BMI055", - 0x2A: "DEVTYPE_SITL", - 0x2B: "DEVTYPE_INS_BMI088", - 0x2C: "DEVTYPE_INS_ICM20948", - 0x2D: "DEVTYPE_INS_ICM20648", - 0x2E: "DEVTYPE_INS_ICM20649", - 0x2F: "DEVTYPE_INS_ICM20602", - 0x30: "DEVTYPE_INS_ICM20601", - 0x31: "DEVTYPE_INS_ADIS1647x", - 0x32: "DEVTYPE_INS_SERIAL", - 0x33: "DEVTYPE_INS_ICM40609", - 0x34: "DEVTYPE_INS_ICM42688", - 0x35: "DEVTYPE_INS_ICM42605", - 0x36: "DEVTYPE_INS_ICM40605", - 0x37: "DEVTYPE_INS_IIM42652", - 0x38: "DEVTYPE_INS_BMI270", - 0x39: "DEVTYPE_INS_BMI085", - 0x3A: "DEVTYPE_INS_ICM42670", - 0x3B: "DEVTYPE_INS_ICM45686", - 0x3C: "DEVTYPE_INS_SCHA63T", - 0x3D: "DEVTYPE_INS_IIM42653", - 0x3E: "DEVTYPE_INS_LSM6DSV16X", - 0x3F: "DEVTYPE_INS_ASM330", - 0x40: "DEVTYPE_INS_ADIS16607", - 0x42: "DEVTYPE_INS_LSM6DSV32X", - 0x43: "DEVTYPE_INS_LSM6DSK320X", - 0x44: "DEVTYPE_INS_ICM56686", -} -BARO_TYPES: Dict[int, str] = { - 0x01: "DEVTYPE_BARO_SITL", - 0x02: "DEVTYPE_BARO_BMP085", - 0x03: "DEVTYPE_BARO_BMP280", - 0x04: "DEVTYPE_BARO_BMP388", - 0x05: "DEVTYPE_BARO_DPS280", - 0x06: "DEVTYPE_BARO_DPS310", - 0x07: "DEVTYPE_BARO_FBM320", - 0x08: "DEVTYPE_BARO_ICM20789", - 0x09: "DEVTYPE_BARO_KELLERLD", - 0x0A: "DEVTYPE_BARO_LPS2XH", - 0x0B: "DEVTYPE_BARO_MS5611", - 0x0C: "DEVTYPE_BARO_SPL06", - 0x0D: "DEVTYPE_BARO_DRONECAN", - 0x0E: "DEVTYPE_BARO_MSP", - 0x0F: "DEVTYPE_BARO_ICP101XX", - 0x10: "DEVTYPE_BARO_ICP201XX", - 0x11: "DEVTYPE_BARO_MS5607", - 0x12: "DEVTYPE_BARO_MS5837_30BA", - 0x13: "DEVTYPE_BARO_MS5637", - 0x14: "DEVTYPE_BARO_BMP390", - 0x15: "DEVTYPE_BARO_BMP581", - 0x16: "DEVTYPE_BARO_SPA06", - 0x17: "DEVTYPE_BARO_AUAV", - 0x18: "DEVTYPE_BARO_MS5837_02BA", -} +class DevTypeEntry(TypedDict, total=False): + value: int + name: str + description: str + -AIRSPEED_TYPES: Dict[int, str] = { - 0x01: "DEVTYPE_AIRSPEED_SITL", - 0x02: "DEVTYPE_AIRSPEED_MS4525", - 0x03: "DEVTYPE_AIRSPEED_MS5525", - 0x04: "DEVTYPE_AIRSPEED_DLVR", - 0x05: "DEVTYPE_AIRSPEED_MSP", - 0x06: "DEVTYPE_AIRSPEED_SDP3X", - 0x07: "DEVTYPE_AIRSPEED_DRONECAN", - 0x08: "DEVTYPE_AIRSPEED_ANALOG", - 0x09: "DEVTYPE_AIRSPEED_NMEA", - 0x0A: "DEVTYPE_AIRSPEED_ASP5033", - 0x0B: "DEVTYPE_AIRSPEED_AUAV", - 0x0C: "DEVTYPE_AIRSPEED_SCRIPTING", +# Entries which are no longer in any C++ enum but may still be found in +# parameters and logs. Values must not collide with the enums. +EXTRA_ENTRIES: Dict[str, List[DevTypeEntry]] = { + "compass": [ + { + "value": 0x19, + "name": "DEVTYPE_LIS2MDL", + "description": "retired; same sensor as IIS2MDC, only used on pre-release firmware", + }, + ], } -MAVLINK_TYPES: Dict[int, str] = { - 0x01: "DEVTYPE_MAVLINK_UART", - 0x02: "DEVTYPE_MAVLINK_NETWORKING", - 0x03: "DEVTYPE_MAVLINK_CAN", - 0x04: "DEVTYPE_MAVLINK_SCRIPTING", + +class DevIdTables(TypedDict): + bus_types: List[DevTypeEntry] + device_types: Dict[str, List[DevTypeEntry]] + + +def _entries_from_enum(enum_parse: Any, source: EnumSource, table: str) -> List[DevTypeEntry]: + """Extract the entries of one C++ enum, applying prefix changes and RENAMES for table.""" + path = os.path.join(REPO_ROOT, source.header) + enums = [e for e in enum_parse.EnumDocco(None).enumerations_from_file(path) if e.name == source.enum_name] + if len(enums) != 1 or len(enums[0].entries) == 0: + # enum_parse silently skips enums containing entries it cannot evaluate + raise ValueError(f"{source.header}: could not parse enum {source.enum_name}") + + renames = RENAMES.get(table, {}) + unused_renames = set(renames) + entries: List[DevTypeEntry] = [] + for enum_entry in enums[0].entries: + name = enum_entry.name + if source.strip_prefix: + if not name.startswith(source.strip_prefix): + raise ValueError(f"{source.header}: {name} lacks prefix {source.strip_prefix}") + name = name[len(source.strip_prefix):] + name = source.add_prefix + name + if name in renames: + unused_renames.discard(name) + name = renames[name] + entry: DevTypeEntry = {"value": enum_entry.value, "name": name} + if enum_entry.comment: + entry["description"] = enum_entry.comment.strip() + entries.append(entry) + if unused_renames: + raise ValueError(f"{source.header}: renames for unknown enumerators {sorted(unused_renames)}") + return entries + + +def _validate_entries(entries: List[DevTypeEntry], table_name: str, max_value: int) -> None: + seen = set() + for entry in entries: + value = entry.get("value") + if not isinstance(value, int) or isinstance(value, bool) or not isinstance(entry.get("name"), str): + raise ValueError(f"{table_name}: malformed entry {entry!r}") + if not 0 <= value <= max_value: + raise ValueError(f"{table_name}: value {value} out of range 0..{max_value}") + if value in seen: + raise ValueError(f"{table_name}: duplicate value {value}") + seen.add(value) + + +def parse_devid_sources() -> DevIdTables: + """Build the device ID tables by parsing the C++ headers in this source tree.""" + sys.path.insert(0, ENUM_PARSE_DIR) + try: + enum_parse = importlib.import_module("enum_parse") + finally: + sys.path.pop(0) + + bus_types = _entries_from_enum(enum_parse, BUS_TYPE_SOURCE, "bus") + _validate_entries(bus_types, "bus_types", 0x07) + + device_types: Dict[str, List[DevTypeEntry]] = {} + for category in DEVICE_CATEGORIES: + entries = _entries_from_enum(enum_parse, DEVICE_TYPE_SOURCES[category], category) + entries += EXTRA_ENTRIES.get(category, []) + _validate_entries(entries, f"device_types.{category}", 0xFF) + device_types[category] = sorted(entries, key=lambda e: e["value"]) + + return {"bus_types": sorted(bus_types, key=lambda e: e["value"]), "device_types": device_types} + + +def load_devid_json(path: str) -> DevIdTables: + """Load tables from a file written by --dump-json, checking its format_version.""" + with open(path, encoding="utf-8") as f: + raw = json.load(f) + + if not isinstance(raw, dict): + raise ValueError(f"{path}: expected a JSON object") + + format_version = raw.get("format_version") + if format_version != DEVID_JSON_FORMAT_VERSION: + raise ValueError( + f"{path}: unsupported format_version {format_version!r} (expected {DEVID_JSON_FORMAT_VERSION})" + ) + if not isinstance(raw.get("data_version"), str): + raise ValueError(f"{path}: missing or invalid data_version") + + bus_types = raw.get("bus_types") + device_types = raw.get("device_types") + if not isinstance(bus_types, list) or not isinstance(device_types, dict): + raise ValueError(f"{path}: missing bus_types or device_types") + _validate_entries(bus_types, "bus_types", 0x07) + for category in DEVICE_CATEGORIES: + if not isinstance(device_types.get(category), list): + raise ValueError(f"{path}: device_types missing category {category}") + _validate_entries(device_types[category], f"device_types.{category}", 0xFF) + return {"bus_types": bus_types, "device_types": device_types} + + +def devid_data_version(tables: DevIdTables) -> str: + """Content hash of the tables; changes whenever any entry changes.""" + canonical = json.dumps(tables, sort_keys=True, separators=(",", ":")) + return "sha256:" + hashlib.sha256(canonical.encode("utf-8")).hexdigest() + + +def _write_devid_file(tables: DevIdTables, f: TextIO, json5: bool) -> None: + """ + Write tables as JSON or JSON5, one entry per line. + + Both formats carry identical data; JSON5 uses hexadecimal values and + unquoted keys. + """ + def key(k: str) -> str: + return k if json5 else json.dumps(k) + + def entry_line(entry: DevTypeEntry) -> str: + value = f"0x{entry['value']:02X}" if json5 else str(entry["value"]) + fields = [f"{key('value')}: {value}", f"{key('name')}: {json.dumps(entry['name'])}"] + if "description" in entry: + fields.append(f"{key('description')}: {json.dumps(entry['description'])}") + return "{" + ", ".join(fields) + "}" + + def table(entries: List[DevTypeEntry], indent: str) -> str: + rows = [indent + " " + entry_line(e) for e in entries] + return "[\n" + ",\n".join(rows) + "\n" + indent + "]" + + if json5: + f.write("// ArduPilot device ID bus types and device types.\n") + f.write("// Generated by Tools/scripts/decode_devid.py --dump-json5 from the C++ headers; do not edit.\n") + f.write("{\n") + f.write(f" {key('format_version')}: {DEVID_JSON_FORMAT_VERSION},\n") + f.write(f" {key('data_version')}: {json.dumps(devid_data_version(tables))},\n") + f.write(f" {key('bus_types')}: {table(tables['bus_types'], ' ')},\n") + f.write(f" {key('device_types')}: {{\n") + categories = [ + f" {key(category)}: {table(entries, ' ')}" + for category, entries in tables["device_types"].items() + ] + f.write(",\n".join(categories) + "\n") + f.write(" }\n") + f.write("}\n") + + +def dump_devid_json(tables: DevIdTables, path: str) -> None: + with open(path, "w", encoding="utf-8") as f: + _write_devid_file(tables, f, json5=False) + + +def dump_devid_json5(tables: DevIdTables, path: str) -> None: + with open(path, "w", encoding="utf-8") as f: + _write_devid_file(tables, f, json5=True) + + +@functools.lru_cache(maxsize=None) +def get_devid_tables(json_path: Optional[str] = None) -> DevIdTables: + """ + Tables from json_path if given, otherwise parsed from the C++ headers. + + If this script is not in an ArduPilot source tree, DEVID_JSON_FALLBACK_PATH + (devid.json beside the script) is read instead. + """ + if json_path is not None: + return load_devid_json(json_path) + in_tree = os.path.exists(os.path.join(REPO_ROOT, BUS_TYPE_SOURCE.header)) and os.path.isdir(ENUM_PARSE_DIR) + if in_tree: + return parse_devid_sources() + if os.path.exists(DEVID_JSON_FALLBACK_PATH): + return load_devid_json(DEVID_JSON_FALLBACK_PATH) + raise FileNotFoundError( + f"ArduPilot C++ headers not found under {REPO_ROOT} and no {DEVID_JSON_FALLBACK_PATH}; " + "use --json with a file written by --dump-json" + ) + + +def _lookup(entries: List[DevTypeEntry]) -> Dict[int, str]: + return {e["value"]: e["name"] for e in entries} + + +_json_path: Optional[str] = None + + +def use_devid_json(path: Optional[str]) -> None: + """Make lookups read path (from --dump-json) rather than parsing the C++ headers.""" + global _json_path + _json_path = path + + +def get_bus_type_name(bus_type: int) -> str: + return _lookup(get_devid_tables(_json_path)["bus_types"]).get(bus_type, "UNKNOWN") + + +_LEGACY_TABLES = { + "COMPASS_TYPES": "compass", + "IMU_TYPES": "imu", + "BARO_TYPES": "baro", + "AIRSPEED_TYPES": "airspeed", + "MAVLINK_TYPES": "mavlink", } +def __getattr__(name: str) -> Dict[int, str]: + """Lazily provide the lookup dicts this module used to define statically.""" + # check the name before loading: the import machinery probes for + # attributes such as __path__ and must get AttributeError + if name == "BUSTYPES": + return _lookup(get_devid_tables(_json_path)["bus_types"]) + if name in _LEGACY_TABLES: + return _lookup(get_devid_tables(_json_path)["device_types"][_LEGACY_TABLES[name]]) + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + class DeviceInfo(TypedDict): bus_type_value: int bus_type_name: str @@ -165,9 +354,6 @@ class DeviceInfo(TypedDict): is_dronecan: bool -DeviceCategory = Literal["compass", "imu", "baro", "airspeed", "mavlink"] - - def parse_device_id(device_id_str: str) -> Tuple[Optional[int], Optional[str]]: """Parse a device ID and return a diagnostic message, if applicable.""" # backwards compatibility: @@ -223,7 +409,7 @@ def decode_device_id(device_id: int) -> Tuple[Optional[DeviceInfo], Optional[str decoded: DeviceInfo = { "bus_type_value": bus_type, - "bus_type_name": BUSTYPES.get(bus_type, "UNKNOWN"), + "bus_type_name": get_bus_type_name(bus_type), "bus": bus, "address": address, "devtype": devtype, @@ -232,6 +418,20 @@ def decode_device_id(device_id: int) -> Tuple[Optional[DeviceInfo], Optional[str return decoded, None +def _device_type_entry(devtype: int, device_category: DeviceCategory) -> Optional[DevTypeEntry]: + try: + entries = get_devid_tables(_json_path)["device_types"][device_category.lower()] + except KeyError: + raise ValueError(f"Unknown device category: {device_category}") from None + return next((e for e in entries if e["value"] == devtype), None) + + +def get_device_type_description(devtype: int, device_category: DeviceCategory) -> Optional[str]: + """Return the device type's comment from the C++ enum (or EXTRA_ENTRIES), if any.""" + entry = _device_type_entry(devtype, device_category) + return None if entry is None else entry.get("description") + + def get_device_type_name(devtype: int, device_category: DeviceCategory) -> str: """ Look up device type name based on device category. @@ -244,20 +444,8 @@ def get_device_type_name(devtype: int, device_category: DeviceCategory) -> str: Device type name string, or 'UNKNOWN' if not found """ - category_map = { - "compass": COMPASS_TYPES, - "imu": IMU_TYPES, - "baro": BARO_TYPES, - "airspeed": AIRSPEED_TYPES, - "mavlink": MAVLINK_TYPES, - } - - try: - type_dict = category_map[device_category.lower()] - except KeyError: - raise ValueError(f"Unknown device category: {device_category}") from None - - return type_dict.get(devtype, "UNKNOWN") + entry = _device_type_entry(devtype, device_category) + return "UNKNOWN" if entry is None else entry["name"] def format_device_info(decode_info: DeviceInfo, device_type_name: str = "") -> str: @@ -308,10 +496,35 @@ def main() -> None: category_group.add_argument("-B", "--baro", action="store_true", help="decode barometer IDs") category_group.add_argument("-A", "--airspeed", action="store_true", help="decode airspeed IDs") category_group.add_argument("-M", "--mavlink", action="store_true", help="decode MAVLink channel IDs") - parser.add_argument("device_id", help="decimal or hexadecimal device ID") + parser.add_argument("--json", metavar="FILE", help="read tables from a file written by --dump-json") + parser.add_argument("--dump-json", metavar="FILE", help="write the device ID tables as JSON") + parser.add_argument("--dump-json5", metavar="FILE", help="write the device ID tables as JSON5") + parser.add_argument("device_id", nargs="?", help="decimal or hexadecimal device ID") opts = parser.parse_args() + dumping = opts.dump_json is not None or opts.dump_json5 is not None + if opts.device_id is None and not dumping: + parser.error("a device ID is required unless --dump-json or --dump-json5 is given") + + use_devid_json(opts.json) + try: + tables = get_devid_tables(opts.json) + except (OSError, ValueError) as ex: + print(f"Error: {ex}", file=sys.stderr) + sys.exit(1) + + try: + if opts.dump_json is not None: + dump_devid_json(tables, opts.dump_json) + if opts.dump_json5 is not None: + dump_devid_json5(tables, opts.dump_json5) + except OSError as ex: + print(f"Error: {ex}", file=sys.stderr) + sys.exit(1) + if opts.device_id is None: + return + device_id, err_msg = parse_device_id(opts.device_id) if err_msg is not None: print(err_msg, file=sys.stderr) @@ -328,16 +541,16 @@ def main() -> None: device_type_name = "" if not decoded["is_dronecan"]: - if opts.compass: - device_type_name = get_device_type_name(decoded["devtype"], "compass") - elif opts.imu: - device_type_name = get_device_type_name(decoded["devtype"], "imu") - elif opts.baro: - device_type_name = get_device_type_name(decoded["devtype"], "baro") - elif opts.airspeed: - device_type_name = get_device_type_name(decoded["devtype"], "airspeed") - elif opts.mavlink: - device_type_name = get_device_type_name(decoded["devtype"], "mavlink") + for category in DEVICE_CATEGORIES: + if getattr(opts, category): + device_type_name = get_device_type_name(decoded["devtype"], category) + # enum comments flag IDs needing care, e.g. retired or mistaken IDs + description = get_device_type_description(decoded["devtype"], category) + if description is not None: + print( + f"Warning: devtype 0x{decoded['devtype']:x} ({device_type_name}): {description}", + file=sys.stderr, + ) print(format_device_info(decoded, device_type_name)) diff --git a/libraries/AP_Compass/AP_Compass_Backend.h b/libraries/AP_Compass/AP_Compass_Backend.h index 2c086882f5850..746a7892bc33a 100644 --- a/libraries/AP_Compass/AP_Compass_Backend.h +++ b/libraries/AP_Compass/AP_Compass_Backend.h @@ -71,7 +71,7 @@ class AP_Compass_Backend DEVTYPE_SITL = 0x0F, DEVTYPE_IST8308 = 0x10, DEVTYPE_RM3100 = 0x11, - DEVTYPE_RM3100_2 = 0x12, // unused, past mistake + DEVTYPE_RM3100_2 = 0x12, // unused, past mistake; this RM3100 ID was only used by master firmware from 2020-03-26 to 2020-05-23 DEVTYPE_MMC5983 = 0x13, DEVTYPE_AK09918 = 0x14, DEVTYPE_AK09915 = 0x15,