docs(docs): Eithernet and voltage ratings

This commit is contained in:
Hamish Willee
2026-05-08 05:56:15 +10:00
parent 96966d2b46
commit 567dadb3b3
17 changed files with 1140 additions and 23 deletions
+16
View File
@@ -14,7 +14,9 @@ inserted into `docs/en/flight_controller/*.md` docs. Sections generated:
- `## Radio Control` — RC input protocols and ports
- `## GPS & Compass` — GPS/safety connector info
- `## Power` — power input ports and monitor type
- `## Voltage Ratings` — per-port operating/absolute voltage ranges, servo rail, voltage monitoring note
- `## Telemetry Radios` — TELEM port listing
- `## Ethernet` — speed, transformerless flag, and link to setup guide (boards with `CONFIG_BOARD_ETHERNET=y`)
- `## SD Card` — presence/absence
## File layout
@@ -144,8 +146,22 @@ Tests in `test_generators.py` use the `snapshot` fixture from `conftest.py`.
hand-written: it is preserved and the proposed content is appended as a
`<!-- section_key-proposed -->` comment, with a console warning.
- **`parse_power_config()` detects INA228** — `CONFIG_DRIVERS_POWER_MONITOR_INA238=y` takes
precedence, then `INA228`, then `INA226`. Old code silently promoted INA228 boards to INA226.
- **Wizard** — `--new-doc` runs an interactive prompts session and caches
answers to `metadata/<stem>_wizard.json` for future re-use.
Key wizard fields and their shapes:
- `ethernet_wizard`: `{"port_label": str|null, "speed_mbps": str, "transformerless": bool}` — only
prompted when `CONFIG_BOARD_ETHERNET=y`; `port_label` is rendered as inline code (backticks) in the
generated section. `apply_sections_to_docs()` bootstraps this from the wizard JSON on first run
when the doc's `<!-- wizard-data -->` block doesn't yet contain it.
- `power_ports_wizard[].normal_min_v` / `normal_max_v` / `absolute_max_v` (str|null) — per-port
voltage ranges for the `## Voltage Ratings` section. Old wizard JSONs without these keys are safe;
missing values produce `TODO` placeholders in the generated section.
- `overview_wizard.servo_rail_absolute_max_v` (str|null) — undamaged absolute maximum for the servo
rail (e.g. `"42"` for Pixhawk-standard boards). Distinct from `servo_rail_max_v` (normal operating
max used in Specifications). Missing → `TODO` placeholder in Voltage Ratings.
## Conventions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,118 @@
{
"manufacturer": "CUAV",
"product": "x25 Super",
"board": "cuav/x25-super",
"fmu_version": null,
"since_version": "v1.18",
"manufacturer_url": "https://store.cuav.net/",
"rc_ports_wizard": [
{
"label": "RC IN",
"side": "FMU"
},
{
"label": "PPMNOTFOUND",
"side": "FMU",
"ppm_only": true
}
],
"gps_ports_wizard": [
{
"port_key": "GPS1",
"label": "GPS&SAFETY",
"pixhawk_standard": true,
"full_port": true
},
{
"port_key": "GPS2",
"label": "GPS2",
"pixhawk_standard": true,
"full_port": false
}
],
"power_ports_wizard": [
{
"label": "POWER C1",
"connector_type": "8-pin JST GH",
"monitor_type": "dronecan",
"normal_min_v": "10",
"normal_max_v": "18",
"absolute_max_v": null
},
{
"label": "POWER C2",
"connector_type": "8-pin JST GH",
"monitor_type": "dronecan",
"normal_min_v": "10",
"normal_max_v": "18",
"absolute_max_v": null
}
],
"overview_wizard": {
"imu": [
"SCH16T",
"IIM42653",
"IIM42652"
],
"baro": [
"BMP581",
"ICP-20100"
],
"mag": [
"RM3100"
],
"osd": null,
"width_mm": "45.45",
"length_mm": "76.5",
"height_mm": "38.7",
"weight_g": null,
"min_voltage": "10",
"max_voltage": "18",
"usb_powers_fc": true,
"usb_pwr_min_v": "4.75",
"usb_pwr_max_v": "5.25",
"has_servo_rail": true,
"servo_rail_max_v": "9.9",
"servo_rail_absolute_max_v": null,
"usb_connectors": [
"USB-C"
],
"usb_labels": [
"USB"
],
"num_additional_adc_inputs": null,
"sensor_variant_labels": null
},
"i2c_buses_wizard": [
{
"bus_num": 1,
"label": "I2C1"
},
{
"bus_num": 2,
"label": "I2C2"
},
{
"bus_num": 3,
"label": "I2C3"
}
],
"ethernet_wizard": {
"port_label": "ETH",
"speed_mbps": "100",
"transformerless": true
},
"extra_port_labels": [
"TELEM1",
"TELEM2",
"CAN1",
"CAN2",
"RSSI",
"SPI6",
"UART4",
"ADC3V3",
"ADC6V6",
"DEBUG (DSU)",
"M1-M16"
]
}
@@ -15,7 +15,10 @@
"power_ports_wizard": [
{
"label": "GPS1",
"connector_type": "n"
"connector_type": "n",
"normal_min_v": null,
"normal_max_v": null,
"absolute_max_v": null
}
],
"overview_wizard": {
@@ -34,6 +37,7 @@
"usb_pwr_max_v": null,
"has_servo_rail": true,
"servo_rail_max_v": null,
"servo_rail_absolute_max_v": null,
"usb_connectors": null,
"usb_labels": [
"USB"
@@ -41,5 +45,7 @@
"num_additional_adc_inputs": null,
"sensor_variant_labels": null
},
"i2c_buses_wizard": null
"i2c_buses_wizard": null,
"ethernet_wizard": null,
"extra_port_labels": null
}
@@ -15,7 +15,10 @@
"power_ports_wizard": [
{
"label": "RC",
"connector_type": "n"
"connector_type": "n",
"normal_min_v": "GPS1",
"normal_max_v": "n",
"absolute_max_v": null
}
],
"overview_wizard": {
@@ -28,7 +31,7 @@
"ICP-20100"
],
"mag": null,
"osd": null,
"osd": "OSD",
"width_mm": null,
"length_mm": null,
"height_mm": null,
@@ -40,6 +43,7 @@
"usb_pwr_max_v": null,
"has_servo_rail": true,
"servo_rail_max_v": null,
"servo_rail_absolute_max_v": null,
"usb_connectors": null,
"usb_labels": [
"USB"
@@ -56,5 +60,7 @@
"bus_num": 2,
"label": "n"
}
]
],
"ethernet_wizard": null,
"extra_port_labels": null
}
@@ -3,3 +3,4 @@ CONFIG_BOARD_SERIAL_TEL2="/dev/ttyS1"
CONFIG_BOARD_SERIAL_GPS1="/dev/ttyS3"
CONFIG_BOARD_SERIAL_RC="/dev/ttyS4"
CONFIG_DRIVERS_RC_INPUT=y
CONFIG_BOARD_ETHERNET=y
@@ -0,0 +1,19 @@
## Ethernet {#ethernet}
The `ETH` port provides 100Mbps wired network connectivity (transformerless application), enabling high-speed communication with companion computers and other network-capable devices.
See [PX4 Ethernet Setup](../advanced_config/ethernet_setup.md) for setup and configuration information.
<!-- ethernet-source-data
{
"board": "test/fixture",
"source": {
"has_ethernet": true
},
"ethernet_wizard": {
"port_label": "ETH",
"speed_mbps": "100",
"transformerless": true
}
}
-->
@@ -0,0 +1,19 @@
## Ethernet {#ethernet}
The `ETH` port provides 1000Mbps wired network connectivity, enabling high-speed communication with companion computers and other network-capable devices.
See [PX4 Ethernet Setup](../advanced_config/ethernet_setup.md) for setup and configuration information.
<!-- ethernet-source-data
{
"board": "test/fixture",
"source": {
"has_ethernet": true
},
"ethernet_wizard": {
"port_label": "ETH",
"speed_mbps": "1000",
"transformerless": false
}
}
-->
@@ -0,0 +1,19 @@
## Ethernet {#ethernet}
The Ethernet port provides 100Mbps wired network connectivity (transformerless application), enabling high-speed communication with companion computers and other network-capable devices.
See [PX4 Ethernet Setup](../advanced_config/ethernet_setup.md) for setup and configuration information.
<!-- ethernet-source-data
{
"board": "test/fixture",
"source": {
"has_ethernet": true
},
"ethernet_wizard": {
"port_label": null,
"speed_mbps": "100",
"transformerless": true
}
}
-->
@@ -0,0 +1,15 @@
## Ethernet {#ethernet}
The Ethernet port provides 100Mbps wired network connectivity (transformerless application), enabling high-speed communication with companion computers and other network-capable devices.
See [PX4 Ethernet Setup](../advanced_config/ethernet_setup.md) for setup and configuration information.
<!-- ethernet-source-data
{
"board": "test/fixture",
"source": {
"has_ethernet": true
},
"ethernet_wizard": {}
}
-->
@@ -2,8 +2,6 @@
The flight controller can be powered from a power module connected to the **POWER** port.
The power module must supply a regulated **5V** at a minimum of **3A continuous**.
Power ports:
- `POWER`: 6-pin JST GH — DroneCAN battery monitoring.
@@ -0,0 +1,48 @@
## Voltage Ratings {#voltage_ratings}
_Durandal_ is 1-way-redundant on the power supply if 1 power sources are supplied.
The 1 power rail is: **POWER**.
**Normal Operation Maximum Ratings**
Under these conditions all power sources will be used in this order to power the system:
1. **POWER** input (4.9V to 5.5V)
**Absolute Maximum Ratings**
Under these conditions the system will not draw any power (will not be operational), but will remain intact.
1. **POWER** input (operational range 4.9V to 5.5V, 0V to 6V undamaged)
1. **Servo input:** `VDD_SERVO` pin of **FMU PWM OUT** (0V to TODO undamaged)
**Voltage monitoring**
Analog battery monitoring via ADC is enabled by default.
<!-- voltage-ratings-source-data
{
"board": "test/fixture",
"source": {
"num_power_inputs": 1,
"has_redundant_power": false,
"power_monitor_type": "analog"
},
"overview_wizard": {
"min_voltage": null,
"max_voltage": null,
"usb_pwr_min_v": null,
"usb_pwr_max_v": null,
"has_servo_rail": true,
"servo_rail_absolute_max_v": null
},
"power_ports_wizard": [
{
"label": "POWER",
"normal_min_v": "4.9",
"normal_max_v": "5.5",
"absolute_max_v": "6"
}
]
}
-->
@@ -0,0 +1,58 @@
## Voltage Ratings {#voltage_ratings}
_X25 Super_ can be triple-redundant on the power supply if 3 power sources are supplied.
The 3 power rails are: **POWER C1**, **POWER C2** and **USB**.
**Normal Operation Maximum Ratings**
Under these conditions all power sources will be used in this order to power the system:
1. **POWER C1** input (10V to 18V)
1. **POWER C2** input (10V to 18V)
1. **USB** input (4.75V to 5.25V)
**Absolute Maximum Ratings**
Under these conditions the system will not draw any power (will not be operational), but will remain intact.
1. **POWER C1** input (operational range 10V to 18V, 0V to TODO undamaged)
1. **POWER C2** input (operational range 10V to 18V, 0V to TODO undamaged)
1. **USB** input (operational range 4.75V to 5.25V, 0V to 6V undamaged)
1. **Servo input:** `VDD_SERVO` pin of **FMU PWM OUT** (0V to TODO undamaged)
**Voltage monitoring**
Digital DroneCAN/UAVCAN battery monitoring is enabled by default.
<!-- voltage-ratings-source-data
{
"board": "test/fixture",
"source": {
"num_power_inputs": 2,
"has_redundant_power": true,
"power_monitor_type": "dronecan"
},
"overview_wizard": {
"min_voltage": null,
"max_voltage": null,
"usb_pwr_min_v": "4.75",
"usb_pwr_max_v": "5.25",
"has_servo_rail": true,
"servo_rail_absolute_max_v": null
},
"power_ports_wizard": [
{
"label": "POWER C1",
"normal_min_v": "10",
"normal_max_v": "18",
"absolute_max_v": null
},
{
"label": "POWER C2",
"normal_min_v": "10",
"normal_max_v": "18",
"absolute_max_v": null
}
]
}
-->
@@ -0,0 +1,58 @@
## Voltage Ratings {#voltage_ratings}
_Pixhawk 5X_ can be triple-redundant on the power supply if 3 power sources are supplied.
The 3 power rails are: **POWER1**, **POWER2** and **USB**.
**Normal Operation Maximum Ratings**
Under these conditions all power sources will be used in this order to power the system:
1. **POWER1** input (4.9V to 5.5V)
1. **POWER2** input (4.9V to 5.5V)
1. **USB** input (4.75V to 5.25V)
**Absolute Maximum Ratings**
Under these conditions the system will not draw any power (will not be operational), but will remain intact.
1. **POWER1** input (operational range 4.9V to 5.5V, 0V to 10V undamaged)
1. **POWER2** input (operational range 4.9V to 5.5V, 0V to 10V undamaged)
1. **USB** input (operational range 4.75V to 5.25V, 0V to 6V undamaged)
1. **Servo input:** `VDD_SERVO` pin of **FMU PWM OUT** (0V to 42V undamaged)
**Voltage monitoring**
Digital I2C battery monitoring is enabled by default.
<!-- voltage-ratings-source-data
{
"board": "test/fixture",
"source": {
"num_power_inputs": 2,
"has_redundant_power": true,
"power_monitor_type": "ina226"
},
"overview_wizard": {
"min_voltage": null,
"max_voltage": null,
"usb_pwr_min_v": "4.75",
"usb_pwr_max_v": "5.25",
"has_servo_rail": true,
"servo_rail_absolute_max_v": "42"
},
"power_ports_wizard": [
{
"label": "POWER1",
"normal_min_v": "4.9",
"normal_max_v": "5.5",
"absolute_max_v": "10"
},
{
"label": "POWER2",
"normal_min_v": "4.9",
"normal_max_v": "5.5",
"absolute_max_v": "10"
}
]
}
-->
@@ -573,6 +573,44 @@ class TestGeneratePowerSection:
# Mixed board should not claim "redundant power inputs" in the intro
assert "redundant power inputs" not in prose.lower()
def test_pure_dronecan_omits_5v_requirement(self):
"""Pure DroneCAN board must NOT say '5V regulated'."""
entry = {
**self._entry_single(),
"power_ports_wizard": [
{"label": "POWER C1", "connector_type": "8-pin JST GH",
"monitor_type": "dronecan"},
],
}
prose = fcdg.generate_power_section(BOARD_KEY, entry).split("<!-- power-source-data")[0]
assert "5V" not in prose
def test_analog_retains_5v_requirement(self):
"""Analog board must keep the 5V/3A line."""
entry = {
**self._entry_single(),
"power_ports_wizard": [
{"label": "POWER", "connector_type": "6-pin JST GH",
"monitor_type": "analog"},
],
}
prose = fcdg.generate_power_section(BOARD_KEY, entry).split("<!-- power-source-data")[0]
assert "5V" in prose
def test_mixed_dronecan_analog_retains_5v_requirement(self):
"""Mixed board (analog + DroneCAN) must keep the 5V/3A line for the analog port."""
entry = {
**self._entry_dual(),
"power_ports_wizard": [
{"label": "POWER 1", "connector_type": "6-pin Molex CLIK-Mate",
"monitor_type": "analog"},
{"label": "POWER C1", "connector_type": "6-pin JST GH",
"monitor_type": "dronecan"},
],
}
prose = fcdg.generate_power_section(BOARD_KEY, entry).split("<!-- power-source-data")[0]
assert "5V" in prose
# ---------------------------------------------------------------------------
# Telemetry section snapshots and structural tests
@@ -1160,3 +1198,259 @@ class TestGenerateSpecSectionVariants:
result = fcdg.generate_specifications_section(BOARD_KEY, entry)
assert "hardware revision Rev 1.0" in result
assert "hardware revision Rev 1.1" in result
# ---------------------------------------------------------------------------
# Ethernet section
# ---------------------------------------------------------------------------
class TestEthernetSection:
def test_ethernet_with_label(self, snapshot):
entry = {
'has_ethernet': True,
'ethernet_wizard': {'port_label': 'ETH', 'speed_mbps': '100', 'transformerless': True},
}
result = fcdg.generate_ethernet_section(BOARD_KEY, entry)
snapshot("ethernet_section.md", result)
def test_ethernet_no_label(self, snapshot):
entry = {
'has_ethernet': True,
'ethernet_wizard': {'port_label': None, 'speed_mbps': '100', 'transformerless': True},
}
result = fcdg.generate_ethernet_section(BOARD_KEY, entry)
snapshot("ethernet_section_no_label.md", result)
def test_ethernet_not_transformerless(self, snapshot):
entry = {
'has_ethernet': True,
'ethernet_wizard': {'port_label': 'ETH', 'speed_mbps': '1000', 'transformerless': False},
}
result = fcdg.generate_ethernet_section(BOARD_KEY, entry)
snapshot("ethernet_section_gigabit.md", result)
def test_ethernet_absent(self):
assert fcdg.generate_ethernet_section(BOARD_KEY, {'has_ethernet': False}) == ''
def test_ethernet_no_wizard(self, snapshot):
"""Falls back to defaults when ethernet_wizard is absent."""
entry = {'has_ethernet': True}
result = fcdg.generate_ethernet_section(BOARD_KEY, entry)
snapshot("ethernet_section_no_wizard.md", result)
def test_label_is_code_style(self):
entry = {
'has_ethernet': True,
'ethernet_wizard': {'port_label': 'ETH', 'speed_mbps': '100', 'transformerless': True},
}
result = fcdg.generate_ethernet_section(BOARD_KEY, entry)
assert '`ETH`' in result
assert '**ETH**' not in result
# ---------------------------------------------------------------------------
# Voltage Ratings section
# ---------------------------------------------------------------------------
_VR_DRONECAN_ENTRY = {
'product': 'X25 Super',
'num_power_inputs': 2,
'has_redundant_power': True,
'has_dronecan_power_input': True,
'power_monitor_type': 'dronecan',
'power_ports_wizard': [
{'label': 'POWER C1', 'connector_type': '8-pin JST GH', 'monitor_type': 'dronecan',
'normal_min_v': '10', 'normal_max_v': '18', 'absolute_max_v': None},
{'label': 'POWER C2', 'connector_type': '8-pin JST GH', 'monitor_type': 'dronecan',
'normal_min_v': '10', 'normal_max_v': '18', 'absolute_max_v': None},
],
'overview_wizard': {
'usb_powers_fc': True, 'usb_pwr_min_v': '4.75', 'usb_pwr_max_v': '5.25',
'has_servo_rail': True, 'servo_rail_max_v': '9.9',
'servo_rail_absolute_max_v': None,
},
}
_VR_SMBUS_ENTRY = {
'product': 'Pixhawk 5X',
'num_power_inputs': 2,
'has_redundant_power': True,
'has_dronecan_power_input': False,
'power_monitor_type': 'ina226',
'power_ports_wizard': [
{'label': 'POWER1', 'connector_type': '6-pin Molex CLIK-Mate',
'normal_min_v': '4.9', 'normal_max_v': '5.5', 'absolute_max_v': '10'},
{'label': 'POWER2', 'connector_type': '6-pin Molex CLIK-Mate',
'normal_min_v': '4.9', 'normal_max_v': '5.5', 'absolute_max_v': '10'},
],
'overview_wizard': {
'usb_powers_fc': True, 'usb_pwr_min_v': '4.75', 'usb_pwr_max_v': '5.25',
'has_servo_rail': True, 'servo_rail_max_v': '10',
'servo_rail_absolute_max_v': '42',
},
}
_VR_ANALOG_ENTRY = {
'product': 'Durandal',
'num_power_inputs': 1,
'has_redundant_power': False,
'has_dronecan_power_input': False,
'power_monitor_type': 'analog',
'power_ports_wizard': [
{'label': 'POWER', 'connector_type': '6-pin JST GH',
'normal_min_v': '4.9', 'normal_max_v': '5.5', 'absolute_max_v': '6'},
],
'overview_wizard': {
'usb_powers_fc': False,
'has_servo_rail': True, 'servo_rail_max_v': '9.9',
'servo_rail_absolute_max_v': None,
},
}
class TestVoltageRatingsSection:
def test_dronecan_dual(self, snapshot):
result = fcdg.generate_voltage_ratings_section(BOARD_KEY, _VR_DRONECAN_ENTRY)
snapshot("voltage_ratings_dronecan.md", result)
def test_smbus_dual(self, snapshot):
result = fcdg.generate_voltage_ratings_section(BOARD_KEY, _VR_SMBUS_ENTRY)
snapshot("voltage_ratings_smbus.md", result)
def test_analog_single(self, snapshot):
result = fcdg.generate_voltage_ratings_section(BOARD_KEY, _VR_ANALOG_ENTRY)
snapshot("voltage_ratings_analog.md", result)
def test_heading_present(self):
result = fcdg.generate_voltage_ratings_section(BOARD_KEY, _VR_SMBUS_ENTRY)
assert '## Voltage Ratings {#voltage_ratings}' in result
def test_normal_operation_section_present(self):
result = fcdg.generate_voltage_ratings_section(BOARD_KEY, _VR_SMBUS_ENTRY)
assert '**Normal Operation Maximum Ratings**' in result
def test_absolute_maximum_section_present(self):
result = fcdg.generate_voltage_ratings_section(BOARD_KEY, _VR_SMBUS_ENTRY)
assert '**Absolute Maximum Ratings**' in result
def test_voltage_monitoring_section_present(self):
result = fcdg.generate_voltage_ratings_section(BOARD_KEY, _VR_SMBUS_ENTRY)
assert '**Voltage monitoring**' in result
def test_dronecan_monitoring_text(self):
result = fcdg.generate_voltage_ratings_section(BOARD_KEY, _VR_DRONECAN_ENTRY)
assert 'DroneCAN/UAVCAN battery monitoring' in result
def test_i2c_monitoring_text(self):
result = fcdg.generate_voltage_ratings_section(BOARD_KEY, _VR_SMBUS_ENTRY)
assert 'Digital I2C battery monitoring' in result
def test_analog_monitoring_text(self):
result = fcdg.generate_voltage_ratings_section(BOARD_KEY, _VR_ANALOG_ENTRY)
assert 'Analog battery monitoring' in result
def test_absolute_max_v_shown(self):
result = fcdg.generate_voltage_ratings_section(BOARD_KEY, _VR_SMBUS_ENTRY)
assert '0V to 10V undamaged' in result
def test_servo_rail_absolute_max_shown(self):
result = fcdg.generate_voltage_ratings_section(BOARD_KEY, _VR_SMBUS_ENTRY)
assert '0V to 42V undamaged' in result
def test_servo_rail_todo_when_absent(self):
result = fcdg.generate_voltage_ratings_section(BOARD_KEY, _VR_DRONECAN_ENTRY)
assert '0V to TODO undamaged' in result
def test_source_comment_present(self):
result = fcdg.generate_voltage_ratings_section(BOARD_KEY, _VR_SMBUS_ENTRY)
assert '<!-- voltage-ratings-source-data' in result
def test_no_power_ports_uses_fallback(self):
entry = {
'product': 'Test Board',
'num_power_inputs': 1,
'has_redundant_power': False,
'power_monitor_type': 'analog',
'power_ports_wizard': None,
'overview_wizard': {'usb_powers_fc': False, 'has_servo_rail': False},
}
result = fcdg.generate_voltage_ratings_section(BOARD_KEY, entry)
assert '## Voltage Ratings' in result
assert 'TODO' in result
def test_mixed_dronecan_i2c_monitoring_note(self):
"""Mixed DroneCAN + I2C ports produce a combined monitoring note."""
entry = {
**_VR_SMBUS_ENTRY,
'power_ports_wizard': [
{'label': 'POWER 1', 'normal_min_v': '4.9', 'normal_max_v': '5.5',
'absolute_max_v': '10', 'monitor_type': 'i2c'},
{'label': 'POWER C1', 'normal_min_v': '10', 'normal_max_v': '18',
'absolute_max_v': None, 'monitor_type': 'dronecan'},
],
}
result = fcdg.generate_voltage_ratings_section(BOARD_KEY, entry)
assert 'DroneCAN/UAVCAN and I2C battery monitoring' in result
def test_per_port_monitor_type_overrides_board_level(self):
"""Per-port monitor_type takes priority over board-level power_monitor_type."""
entry = {
**_VR_SMBUS_ENTRY,
'power_monitor_type': 'ina226', # board-level says I2C
'power_ports_wizard': [
{'label': 'POWER C1', 'normal_min_v': '10', 'normal_max_v': '18',
'absolute_max_v': None, 'monitor_type': 'dronecan'}, # port says DroneCAN
],
}
result = fcdg.generate_voltage_ratings_section(BOARD_KEY, entry)
# Per-port dronecan wins over board-level ina226
assert 'DroneCAN/UAVCAN battery monitoring' in result
assert 'I2C battery monitoring' not in result
# ---------------------------------------------------------------------------
# _monitoring_note helper
# ---------------------------------------------------------------------------
class TestMonitoringNote:
def test_pure_dronecan(self):
ports = [{'monitor_type': 'dronecan'}, {'monitor_type': 'dronecan'}]
assert fcdg._monitoring_note(ports, None) == \
'Digital DroneCAN/UAVCAN battery monitoring is enabled by default.'
def test_pure_i2c_ina226(self):
ports = [{'monitor_type': 'ina226'}]
assert fcdg._monitoring_note(ports, None) == \
'Digital I2C battery monitoring is enabled by default.'
def test_pure_i2c_ltc44xx(self):
ports = [{'monitor_type': 'ltc44xx'}]
assert fcdg._monitoring_note(ports, None) == \
'Digital I2C battery monitoring is enabled by default.'
def test_pure_analog(self):
ports = [{'monitor_type': 'analog'}]
assert fcdg._monitoring_note(ports, None) == \
'Analog battery monitoring via ADC is enabled by default.'
def test_mixed_dronecan_i2c(self):
ports = [{'monitor_type': 'dronecan'}, {'monitor_type': 'ina226'}]
assert fcdg._monitoring_note(ports, None) == \
'Digital DroneCAN/UAVCAN and I2C battery monitoring are enabled by default.'
def test_mixed_dronecan_analog(self):
ports = [{'monitor_type': 'dronecan'}, {'monitor_type': 'analog'}]
assert fcdg._monitoring_note(ports, None) == \
'Digital DroneCAN/UAVCAN and analog ADC battery monitoring are enabled by default.'
def test_fallback_to_board_level_when_no_port_types(self):
ports = [{'label': 'POWER', 'connector_type': '6-pin JST GH'}] # no monitor_type
assert fcdg._monitoring_note(ports, 'ina238') == \
'Digital I2C battery monitoring is enabled by default.'
def test_fallback_dronecan_board_level(self):
assert fcdg._monitoring_note([], 'dronecan') == \
'Digital DroneCAN/UAVCAN battery monitoring is enabled by default.'
def test_no_data_returns_todo(self):
assert 'TODO' in fcdg._monitoring_note([], None)
@@ -379,6 +379,35 @@ class TestParsePowerConfig:
result = fcdg.parse_power_config(board_stm32h7_all_dshot)
assert result["has_dronecan_power_input"] is False
def test_ina228_detected(self, tmp_path):
"""INA228-only board must not fall through to the INA226 fallback."""
src = tmp_path / "src"
src.mkdir()
(src / "board_config.h").write_text(
"#define BOARD_NUMBER_DIGITAL_BRICKS 1\n"
"#define BOARD_HAS_NBAT_V 1d\n"
)
(tmp_path / "default.px4board").write_text(
"CONFIG_DRIVERS_POWER_MONITOR_INA228=y\n"
)
result = fcdg.parse_power_config(tmp_path)
assert result["power_monitor_type"] == "ina228"
def test_ina238_takes_precedence_over_ina228(self, tmp_path):
"""When both INA238 and INA228 are enabled, INA238 wins."""
src = tmp_path / "src"
src.mkdir()
(src / "board_config.h").write_text(
"#define BOARD_NUMBER_DIGITAL_BRICKS 1\n"
"#define BOARD_HAS_NBAT_V 1d\n"
)
(tmp_path / "default.px4board").write_text(
"CONFIG_DRIVERS_POWER_MONITOR_INA238=y\n"
"CONFIG_DRIVERS_POWER_MONITOR_INA228=y\n"
)
result = fcdg.parse_power_config(tmp_path)
assert result["power_monitor_type"] == "ina238"
# ---------------------------------------------------------------------------
# parse_sd_card_config
@@ -16,6 +16,7 @@ def _make_args(**kwargs):
"rc_ports_wizard": None,
"gps_ports_wizard": None,
"power_ports_wizard": None,
"extra_port_labels": None,
}
defaults.update(kwargs)
return argparse.Namespace(**defaults)
@@ -180,6 +181,59 @@ class TestRunWizard:
fcdg._run_wizard(args)
assert args.since_version == "v1.19"
def test_extra_port_labels_stored(self, monkeypatch):
"""Extra port labels entered at the final step are stored in args.extra_port_labels."""
board_data = {
"test/board": {
"has_rc_input": True, "has_common_rc": False,
"rc_serial_device": "/dev/ttyS4",
"has_ppm_pin": False, "ppm_shared_with_rc_serial": False,
"has_pps_capture": False, "has_safety_switch": False, "has_safety_led": False,
"serial_ports": [
{"uart": "USART1", "device": "/dev/ttyS0", "label": "TELEM1"},
],
"num_power_inputs": 1,
}
}
prompts_responses = {
"Manufacturer name": "TestMfr",
"Product name": "TestBoard",
"Board key (e.g. holybro/kakuteh7 or px4/fmu-v6x)": "test/board",
"FMU version suffix (e.g. fmu-v6x, optional)": "",
"PX4 version introduced in": "v1.18",
"Serial RC port label as printed on board": "RC",
" POWER port label as printed on board": "POWER",
" Connector type for POWER (e.g. 6-pin JST GH, 6-pin Molex CLIK-Mate)": "6-pin JST GH",
"Manufacturer URL": "https://example.com/",
}
extra_responses = iter(["CAN1", "CAN2", ""]) # two extra labels then stop
def mock_prompt(label, default="", required=False):
if label.startswith(" extra port label"):
return next(extra_responses, "")
return prompts_responses.get(label, default)
monkeypatch.setattr(fcdg, "_wizard_prompt", mock_prompt)
monkeypatch.setattr(fcdg, "gather_board_data", lambda: board_data)
monkeypatch.setattr(fcdg, "_load_wizard_cache", lambda *a, **kw: None)
monkeypatch.setattr(fcdg, "_save_wizard_cache", lambda *a, **kw: None)
args = _make_args()
fcdg._run_wizard(args)
assert args.extra_port_labels == ["CAN1", "CAN2"]
def test_extra_port_labels_none_when_no_input(self, monkeypatch):
"""extra_port_labels is None when the user enters nothing extra."""
self._patch(monkeypatch, [
"TestMfr", "TestBoard", "",
"",
"v1.18",
"RC", "n",
])
args = _make_args()
fcdg._run_wizard(args)
assert args.extra_port_labels is None
# ---------------------------------------------------------------------------
# Wizard cache: _save_wizard_cache / _load_wizard_cache