Files
ardupilot/Tools/scripts/decode_devid.py
T
Dr.-Ing. Amilcar do Carmo Lucas 032a861cc0 Tools: address robot issues in decode_devid.py
Some of the issues were pre-existing.
Behavior is preserved and warnings issued.
2026-09-02 17:28:56 +10:00

346 lines
10 KiB
Python
Executable File

#!/usr/bin/env python3
"""
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
AP_FLAKE8_CLEAN
SPDX-FileCopyrightText: 2017-2026 ArduPilot developers
SPDX-License-Identifier: GPL-3.0-or-later
"""
import argparse
import sys
from typing import Dict
from typing import Literal
from typing import Optional
from typing import Tuple
from typing import TypedDict
# Device type lookup tables
BUSTYPES: Dict[int, str] = {
1: "I2C",
2: "SPI",
3: "DRONECAN",
4: "SITL",
5: "MSP",
6: "SERIAL",
7: "WSPI",
}
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",
}
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",
}
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",
}
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",
}
MAVLINK_TYPES: Dict[int, str] = {
0x01: "DEVTYPE_MAVLINK_UART",
0x02: "DEVTYPE_MAVLINK_NETWORKING",
0x03: "DEVTYPE_MAVLINK_CAN",
0x04: "DEVTYPE_MAVLINK_SCRIPTING",
}
class DeviceInfo(TypedDict):
bus_type_value: int
bus_type_name: str
bus: int
address: int
devtype: int
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:
# - 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": BUSTYPES.get(bus_type, "UNKNOWN"),
"bus": bus,
"address": address,
"devtype": devtype,
"is_dronecan": bus_type == 3,
}
return decoded, None
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
"""
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")
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("device_id", help="decimal or hexadecimal device ID")
opts = parser.parse_args()
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"]:
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")
print(format_device_info(decoded, device_type_name))
if __name__ == "__main__":
main()