mirror of
https://github.com/ArduPilot/ardupilot.git
synced 2026-10-02 10:23:25 +08:00
pre-commit / ci (push) Canceled after 0s
test Renode / cubeorangeplus-quadplane (push) Canceled after 0s
test scripts / build (astyle-cleanliness) (push) Canceled after 0s
test scripts / build (check_autotest_options) (push) Canceled after 0s
test scripts / build (logger_metadata) (push) Canceled after 0s
test scripts / build (param-file-validation) (push) Canceled after 0s
test scripts / build (param_parse) (push) Canceled after 0s
test scripts / build (python-cleanliness) (push) Canceled after 0s
test scripts / build (shellcheck) (push) Canceled after 0s
test scripts / build (validate_board_list) (push) Canceled after 0s
Rather than maintaining copies of the bus and device type tables, decode_devid.py now parses the enums in AP_HAL/Device.h, AP_SerialManager.h and the compass, IMU, baro and airspeed backend headers using logger_metadata/enum_parse.py. A small rename map keeps established display names (DRONECAN, AK0991x), and the retired LIS2MDL ID, which is in no enum, is listed explicitly. Comments on enum entries are shown as warnings when decoding. --dump-json and --dump-json5 write the tables, with a format_version and a content-hash data_version, for tools without a source tree; --json reads such a file back. A copy of the script outside an ArduPilot tree (as synced by MethodicConfigurator) reads devid.json from its own directory. The files are published alongside LogMessages.* by build_log_message_documentation.sh, and CI dumps them in the logger_metadata step so that header changes which break parsing are caught. The tables had drifted from the headers: ACC_LSM9DS1, INS_ZEROONE_FPGA_SCH16T and INS_ICM56686 were missing, MMC5883 is now MMC5983, AK8963/BMM150 had trailing spaces, and several names now follow the headers.
560 lines
20 KiB
Python
Executable File
560 lines
20 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
"""
|
|
Decode a device ID such as used for COMPASS_DEV_ID, INS_ACC_ID etc
|
|
|
|
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
|
|
|
|
SPDX-FileCopyrightText: 2017-2026 ArduPilot developers
|
|
|
|
SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
"""
|
|
|
|
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
|
|
|
|
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_",
|
|
),
|
|
}
|
|
|
|
# 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"},
|
|
}
|
|
|
|
|
|
class DevTypeEntry(TypedDict, total=False):
|
|
value: int
|
|
name: str
|
|
description: str
|
|
|
|
|
|
# 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",
|
|
},
|
|
],
|
|
}
|
|
|
|
|
|
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
|
|
bus: int
|
|
address: int
|
|
devtype: int
|
|
is_dronecan: bool
|
|
|
|
|
|
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:
|
|
# - assume all digit strings are decimal,
|
|
# - assume strings without "0x" prefix but with characters A-F are hexadecimal
|
|
try:
|
|
device_id = int(device_id_str)
|
|
except ValueError:
|
|
try:
|
|
device_id = int(device_id_str, 16)
|
|
except ValueError:
|
|
return None, f"Invalid device ID '{device_id_str}'"
|
|
|
|
if device_id < 0:
|
|
return None, "device ID must not be negative"
|
|
|
|
# Warn that ambiguous digits-only input is interpreted as decimal.
|
|
if device_id_str.isdigit():
|
|
return (
|
|
device_id,
|
|
f"Warning: device ID '{device_id_str}' is interpreted as decimal; prefix hexadecimal IDs with 0x.",
|
|
)
|
|
|
|
return device_id, None
|
|
|
|
|
|
def decode_device_id(device_id: int) -> Tuple[Optional[DeviceInfo], Optional[str]]:
|
|
"""
|
|
Decode an ArduPilot device ID into its components.
|
|
|
|
Args:
|
|
device_id: Device ID as integer
|
|
|
|
Returns:
|
|
A tuple containing the decoded dictionary and an error message. If
|
|
device_id does not fit in the 24-bit device ID format, the decoded
|
|
dictionary is None. Otherwise, it contains these keys:
|
|
- bus_type_value: Bus type numeric value
|
|
- bus_type_name: Bus type name (I2C, SPI, etc.)
|
|
- bus: Bus number
|
|
- address: Device address
|
|
- devtype: Device type numeric value
|
|
- is_dronecan: Whether this is DRONECAN bus type
|
|
|
|
"""
|
|
if device_id < 0 or device_id > 0x00FFFFFF:
|
|
return None, f"Error: device ID 0x{device_id:x} is outside the 24-bit range."
|
|
|
|
bus_type = device_id & 0x07
|
|
bus = (device_id >> 3) & 0x1F
|
|
address = (device_id >> 8) & 0xFF
|
|
devtype = (device_id >> 16) & 0xFF
|
|
|
|
decoded: DeviceInfo = {
|
|
"bus_type_value": bus_type,
|
|
"bus_type_name": get_bus_type_name(bus_type),
|
|
"bus": bus,
|
|
"address": address,
|
|
"devtype": devtype,
|
|
"is_dronecan": bus_type == 3,
|
|
}
|
|
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.
|
|
|
|
Args:
|
|
devtype: Device type numeric value
|
|
device_category: One of 'compass', 'imu', 'baro', 'airspeed', 'mavlink'
|
|
|
|
Returns:
|
|
Device type name string, or 'UNKNOWN' if not found
|
|
|
|
"""
|
|
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:
|
|
"""
|
|
Format decoded device info as a readable string.
|
|
|
|
Args:
|
|
decode_info: Dictionary returned by decode_device_id()
|
|
device_type_name: Device type name (optional; not applicable to DroneCAN IDs)
|
|
|
|
Returns:
|
|
Formatted string with device information
|
|
|
|
"""
|
|
bus_type = decode_info["bus_type_value"]
|
|
bus_type_name = decode_info["bus_type_name"]
|
|
bus = decode_info["bus"]
|
|
address = decode_info["address"]
|
|
devtype = decode_info["devtype"]
|
|
is_dronecan = decode_info["is_dronecan"]
|
|
|
|
if is_dronecan:
|
|
# Compass DroneCAN IDs store sensor_id + 1; other DroneCAN IDs use 0.
|
|
sensor_id = devtype - 1 if devtype > 0 else None
|
|
sensor_id_info = (
|
|
f"sensor_id:{sensor_id}(0x{sensor_id:x})"
|
|
if sensor_id is not None
|
|
else "sensor_id:not encoded"
|
|
)
|
|
return (
|
|
f"bus_type:{bus_type_name}({bus_type}) bus:{bus} "
|
|
f"address:{address}(0x{address:x}) {sensor_id_info}"
|
|
)
|
|
device_type_suffix = f" {device_type_name}" if device_type_name else ""
|
|
return (
|
|
f"bus_type:{bus_type_name}({bus_type}) bus:{bus} "
|
|
f"address:{address}(0x{address:x}) devtype:{devtype}(0x{devtype:x})"
|
|
f"{device_type_suffix}"
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
"""Command-line interface."""
|
|
parser = argparse.ArgumentParser(description="DEVICE_ID must be decimal or hexadecimal with a 0x prefix")
|
|
category_group = parser.add_mutually_exclusive_group()
|
|
category_group.add_argument("-C", "--compass", action="store_true", help="decode compass IDs")
|
|
category_group.add_argument("-I", "--imu", action="store_true", help="decode IMU IDs")
|
|
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("--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)
|
|
if device_id is None:
|
|
sys.exit(1)
|
|
|
|
decoded, err_msg = decode_device_id(device_id)
|
|
if err_msg is not None:
|
|
print(err_msg, file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
if decoded is None:
|
|
sys.exit(1)
|
|
|
|
device_type_name = ""
|
|
if not decoded["is_dronecan"]:
|
|
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))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|