diff --git a/.github/hardware.py b/.github/hardware.py index 02b6fb1..0fd0082 100644 --- a/.github/hardware.py +++ b/.github/hardware.py @@ -191,6 +191,19 @@ class FieldType(Enum): ] used_pins = {} +ignored_validations = set() + + +def set_ignored_validations(device): + global ignored_validations + ignored_validations = set(device.get('validation_ignores', [])) + + +def report_error(check, message): + if check in ignored_validations: + return False + print(f'ERROR: {message}') + return True def ignore_undef_pins(pair): @@ -210,11 +223,11 @@ def validate(target, layout, device): global used_pins had_error = False used_pins = {} + set_ignored_validations(device) for field in dict(filter(ignore_undef_pins, layout.items())): # Ensure that the layout field is a valid field from the hardware list if field not in hardware_fields.keys(): - print(f'ERROR: device "{target}" has an unknown field name {field}') - had_error = True + had_error |= report_error('unknown_field_name', f'device "{target}" has an unknown field name {field}') else: had_error |= validate_pin_uniqueness(target, layout, field) had_error |= validate_grouping(target, layout, field, device['firmware']) @@ -244,16 +257,19 @@ def validate_field_grouping(target, layout, field, field_group): if field in group[0]: for must in group[0] + group[1]: if must not in layout: - print(f'ERROR: device "{target}" because "{field}" is defined all other related fields must also be defined {must}') - print(f'\t{group[0] + group[1]}') - had_error = True + had_error |= report_error( + 'missing_related_field', + f'device "{target}" because "{field}" is defined all other related fields must also be defined {must}\n\t{group[0] + group[1]}' + ) found = True if group[2] == [] else False for one in group[2]: if one in layout: found = True if not found: - print(f'ERROR: device "{target}" because "{field}" is defined at least one of the following fields must also be {group[2]}') - had_error = True + had_error |= report_error( + 'missing_one_of_related_fields', + f'device "{target}" because "{field}" is defined at least one of the following fields must also be {group[2]}' + ) return had_error @@ -267,8 +283,7 @@ def validate_pin_uniqueness(target, layout, field): if field in duplicate and used_pins[pin] in duplicate: allowed = True if not allowed: - print(f'ERROR: device "{target}" PIN {pin} "{field}" is already assigned to "{used_pins[pin]}"') - had_error = True + had_error |= report_error('duplicate_pin_assignment', f'device "{target}" PIN {pin} "{field}" is already assigned to "{used_pins[pin]}"') else: used_pins[pin] = field return had_error @@ -281,14 +296,11 @@ def validate_power_config(target, layout, firmware): had_error = False if 'power_min' not in layout: - print(f'ERROR: device "{target}" does not define power_min') - had_error = True + had_error |= report_error('missing_power_min', f'device "{target}" does not define power_min') if 'power_max' not in layout: - print(f'ERROR: device "{target}" does not define power_max') - had_error = True + had_error |= report_error('missing_power_max', f'device "{target}" does not define power_max') if 'power_default' not in layout: - print(f'ERROR: device "{target}" does not define power_default') - had_error = True + had_error |= report_error('missing_power_default', f'device "{target}" does not define power_default') if had_error: return had_error @@ -299,51 +311,39 @@ def validate_power_config(target, layout, firmware): power_high = power_max if 'power_high' not in layout else layout['power_high'] if power_min > power_max: - print(f'ERROR: device "{target}" power_min must be less than or equal to power_max') - had_error = True + had_error |= report_error('invalid_power_range', f'device "{target}" power_min must be less than or equal to power_max') if power_default < power_min or power_default > power_max: - print(f'ERROR: device "{target}" power_default must lie between power_min and power_max') - had_error = True + had_error |= report_error('invalid_power_default', f'device "{target}" power_default must lie between power_min and power_max') if power_high < power_min or power_high > power_max: - print(f'ERROR: device "{target}" power_high must lie between power_min and power_max') - had_error = True + had_error |= report_error('invalid_power_high', f'device "{target}" power_high must lie between power_min and power_max') if 'power_values' in layout: power_values = layout['power_values'] power_values_dual = layout['power_values_dual'] if 'power_values_dual' in layout else None if power_values and power_max - power_min + 1 > len(power_values): - print(f'ERROR: device "{target}" power_values must have the correct number of entries to match all values from power_min to power_max') - had_error = True + had_error |= report_error('invalid_power_values_length', f'device "{target}" power_values must have the correct number of entries to match all values from power_min to power_max') if power_values_dual and power_max - power_min + 1 > len(power_values_dual): - print(f'ERROR: device "{target}" power_values_dual must have the correct number of entries to match all values from power_min to power_max') - had_error = True + had_error |= report_error('invalid_power_values_dual_length', f'device "{target}" power_values_dual must have the correct number of entries to match all values from power_min to power_max') if layout['power_control'] == 3 and 'power_apc2' not in layout: - print(f'ERROR: device "{target}" defines power_control as DACWRITE and power_apc2 is undefined') - had_error = True + had_error |= report_error('missing_power_apc2_for_dacwrite', f'device "{target}" defines power_control as DACWRITE and power_apc2 is undefined') if 'power_values2' in layout: if len(layout['power_values2']) != len(power_values): - print(f'ERROR: device "{target}" power_values2 must have the same number of entries as power_values') - had_error = True + had_error |= report_error('invalid_power_values2_length', f'device "{target}" power_values2 must have the same number of entries as power_values') if layout['power_control'] != 3: - print(f'ERROR: device "{target}" power_values2 is defined so power_control must be set to 3 (DACWRITE)') - had_error = True + had_error |= report_error('invalid_power_control_for_power_values2', f'device "{target}" power_values2 is defined so power_control must be set to 3 (DACWRITE)') if 'power_apc2' not in layout: - print(f'ERROR: device "{target}" power_values2 is defined so the power_apc2 pin must also be defined') - had_error = True + had_error |= report_error('missing_power_apc2_for_power_values2', f'device "{target}" power_values2 is defined so the power_apc2 pin must also be defined') elif 'power_values_dual' in layout: power_values_dual = layout['power_values_dual'] if power_values_dual and power_max - power_min + 1 > len(power_values_dual): - print(f'ERROR: device "{target}" power_values_dual must have the correct number of entries to match all values from power_min to power_max') - had_error = True + had_error |= report_error('invalid_power_values_dual_length', f'device "{target}" power_values_dual must have the correct number of entries to match all values from power_min to power_max') if layout['power_control'] == 3 and 'power_apc2' not in layout: - print(f'ERROR: device "{target}" defines power_control as DACWRITE and power_apc2 is undefined') - had_error = True + had_error |= report_error('missing_power_apc2_for_dacwrite', f'device "{target}" defines power_control as DACWRITE and power_apc2 is undefined') # if SX1280 or (LR1121 & power_values_dual) then lna_gain MUST be defined if '_2400_' in firmware or ('_LR1121_' in firmware and 'power_values_dual' in layout and layout['power_values_dual'] is not None): if 'power_lna_gain' not in layout: - print(f'ERROR: device "{target}" has 2.4GHz band, but does not define power_lna_gain') - had_error = True + had_error |= report_error('missing_power_lna_gain', f'device "{target}" has 2.4GHz band, but does not define power_lna_gain') return had_error @@ -352,11 +352,9 @@ def validate_backpack(target, layout): had_error = False if 'passthrough_baud' in layout: if layout['serial_rx'] == layout['serial_tx'] and layout['passthrough_baud'] != 230400: - print(f'ERROR: device "{target}" an external module with a backpack should set the baud rate to 230400') - had_error = True + had_error |= report_error('backpack_passthrough_baud', f'device "{target}" an external module with a backpack should set the baud rate to 230400') if layout['serial_rx'] != layout['serial_tx'] and layout['passthrough_baud'] != 460800: - print(f'ERROR: device "{target}" an internal module with a backpack should set the baud rate to 460800') - had_error = True + had_error |= report_error('backpack_passthrough_baud', f'device "{target}" an internal module with a backpack should set the baud rate to 460800') return had_error @@ -364,19 +362,15 @@ def validate_joystick(target, layout): had_error = False if 'joystick' in layout or 'joystick_values' in layout: if 'joystick' not in layout: - print(f'ERROR: device "{target}" joystick_values is defined so the joystick pin must also be defined') - had_error = True + had_error |= report_error('missing_joystick_pin', f'device "{target}" joystick_values is defined so the joystick pin must also be defined') elif 'joystick_values' not in layout: - print(f'ERROR: device "{target}" joystick is defined so the joystick_values must also be defined') - had_error = True + had_error |= report_error('missing_joystick_values', f'device "{target}" joystick is defined so the joystick_values must also be defined') elif len(layout['joystick_values']) != 6: - print(f'ERROR: device "{target}" joystick_values must have 6 values defined') - had_error = True + had_error |= report_error('invalid_joystick_values_length', f'device "{target}" joystick_values must have 6 values defined') else: for value in layout['joystick_values']: if value < 0 or value > 4095: - print(f'ERROR: device "{target}" joystick_values must be between 0 and 4095 inclusive') - had_error = True + had_error |= report_error('invalid_joystick_value_range', f'device "{target}" joystick_values must be between 0 and 4095 inclusive') return had_error @@ -388,8 +382,7 @@ def validate_pwm_outputs(target, layout): field in layout and \ layout[field] in layout['pwm_outputs'] and \ field not in allowable_pwm_shared: - print(f'ERROR: device "{target}" pwm_output pin {layout[field]} is not allowed to be shared with {field}') - had_error = True + had_error |= report_error('invalid_pwm_pin_sharing', f'device "{target}" pwm_output pin {layout[field]} is not allowed to be shared with {field}') return had_error @@ -397,11 +390,9 @@ def validate_pin_function(target, layout, field, platform): if hardware_fields[field].value > FieldType.PIN.value: function = get_pin_function(platform, layout[field]) if function is None: - print(f'ERROR: device "{target}" has an invalid pin number for {field}, {layout[field]}') - return True + return report_error('invalid_pin_number', f'device "{target}" has an invalid pin number for {field}, {layout[field]}') if hardware_fields[field] == FieldType.INPUT and not (function & 1): - print(f'ERROR: device "{target}" pin for {field} must be assigned to a pin that supports INPUT') - return True + return report_error('invalid_input_pin_function', f'device "{target}" pin for {field} must be assigned to a pin that supports INPUT') return False diff --git a/.github/targets_validator.py b/.github/targets_validator.py index da42bc2..b16b1c1 100644 --- a/.github/targets_validator.py +++ b/.github/targets_validator.py @@ -8,6 +8,7 @@ hadError = False warnEnabled = False firmwares = set() +ignored_validations = set() stm32_targets = [ 'DIY_900_TX_STM32_SX1272', 'DIY_900_RX_STM32_SX1272', @@ -37,13 +38,22 @@ ] -def error(msg): +def set_ignored_validations(device=None): + global ignored_validations + ignored_validations = set() if device is None else set(device.get('validation_ignores', [])) + + +def error(msg, code=None): + if code is not None and code in ignored_validations: + return global hadError hadError = True print("ERROR: " + msg) -def warn(msg): +def warn(msg, code=None): + if code is not None and code in ignored_validations: + return if warnEnabled: global hadError hadError = True @@ -53,37 +63,37 @@ def warn(msg): def validate_stm32(vendor, type, devname, device): for method in device['upload_methods']: if method not in ['stlink', 'dfu', 'uart', 'wifi', 'betaflight', 'stock']: - error(f'Invalid upload method "{method}" for target "{vendor}.{type}.{devname}"') + error(f'Invalid upload method "{method}" for target "{vendor}.{type}.{devname}"', 'invalid_upload_method') if 'stlink' not in device['upload_methods']: - error(f'STM32 based devices must always have "stlink" as an upload_method for target "{vendor}.{type}.{devname}"') + error(f'STM32 based devices must always have "stlink" as an upload_method for target "{vendor}.{type}.{devname}"', 'missing_stlink_upload_method') if 'stlink' not in device: - error(f'STM32 based devices must always have "stlink" attribute for target "{vendor}.{type}.{devname}"') + error(f'STM32 based devices must always have "stlink" attribute for target "{vendor}.{type}.{devname}"', 'missing_stlink_attribute') else: stlink = device['stlink'] if 'cpus' not in stlink: - error(f'The "stlink" attribute for target "{vendor}.{type}.{devname}" must have a list of valid "cpus"') + error(f'The "stlink" attribute for target "{vendor}.{type}.{devname}" must have a list of valid "cpus"', 'missing_stlink_cpus') if 'offset' not in stlink: - error(f'The "stlink" attribute for target "{vendor}.{type}.{devname}" must have a valid "offset"') + error(f'The "stlink" attribute for target "{vendor}.{type}.{devname}" must have a valid "offset"', 'missing_stlink_offset') if 'bootloader' not in stlink: - error(f'The "stlink" attribute for target "{vendor}.{type}.{devname}" must have a valid "bootloader"') + error(f'The "stlink" attribute for target "{vendor}.{type}.{devname}" must have a valid "bootloader"', 'missing_stlink_bootloader') # could check the existence of the bootloader file def validate_esp(vendor, type, devname, device): if 'lua_name' not in device: - error(f'device "{vendor}.{type}.{devname}" must have a "lua_name" child element') + error(f'device "{vendor}.{type}.{devname}" must have a "lua_name" child element', 'missing_lua_name') if len(device['lua_name']) > 16: - error(f'device "{vendor}.{type}.{devname}" must have a "lua_name" of 16 characters or less') + error(f'device "{vendor}.{type}.{devname}" must have a "lua_name" of 16 characters or less', 'lua_name_too_long') # validate layout_file if not device['firmware'].startswith('Unified'): - error(f'ESP target "{vendor}.{type}.{devname}" must be using a Unified firmware') + error(f'ESP target "{vendor}.{type}.{devname}" must be using a Unified firmware', 'non_unified_esp_firmware') if 'layout_file' not in device: - error(f'device "{vendor}.{type}.{devname}" must have a "layout_file" child element') + error(f'device "{vendor}.{type}.{devname}" must have a "layout_file" child element', 'missing_layout_file') else: dir = ('RX/' if type.startswith('rx') else 'TX/') layout_file = device['layout_file'] if not os.path.isfile(dir + layout_file): - error(f'File specified by layout_file "{layout_file}" in target "{vendor}.{type}.{devname}", does not exist') + error(f'File specified by layout_file "{layout_file}" in target "{vendor}.{type}.{devname}", does not exist', 'missing_layout_file_on_disk') else: # load file, merge overlay, call validate_hardware with open(dir + layout_file, 'r') as f: @@ -95,70 +105,72 @@ def validate_esp(vendor, type, devname, device): # could validate overlay if 'prior_target_name' not in device: - warn(f'device "{vendor}.{type}.{devname}" should have a "prior_target_name" child element') + warn(f'device "{vendor}.{type}.{devname}" should have a "prior_target_name" child element', 'missing_prior_target_name') # Validate the platform matches the firmware file if device['platform'] == 'esp32-c3': if device['min_version'] < '3.5': - error(f'device "{vendor}.{type}.{devname}" "min_version" must be at least 3.5.0') + error(f'device "{vendor}.{type}.{devname}" "min_version" must be at least 3.5.0', 'min_version_too_low_for_esp32_c3') if '_ESP32C3_' not in device['firmware']: - error(f'device "{vendor}.{type}.{devname}" firmware and platform MUST match') + error(f'device "{vendor}.{type}.{devname}" firmware and platform MUST match', 'firmware_platform_mismatch') if device['platform'] == 'esp32-s3': if '_ESP32S3_' not in device['firmware']: - error(f'device "{vendor}.{type}.{devname}" firmware and platform MUST match') + error(f'device "{vendor}.{type}.{devname}" firmware and platform MUST match', 'firmware_platform_mismatch') if device['platform'] == 'esp32': if '_ESP32_' not in device['firmware']: - error(f'device "{vendor}.{type}.{devname}" firmware and platform MUST match') + error(f'device "{vendor}.{type}.{devname}" firmware and platform MUST match', 'firmware_platform_mismatch') if device['platform'] == 'esp8285': if '_ESP8285_' not in device['firmware']: - error(f'device "{vendor}.{type}.{devname}" firmware and platform MUST match') + error(f'device "{vendor}.{type}.{devname}" firmware and platform MUST match', 'firmware_platform_mismatch') def validate_esp32(vendor, type, devname, device): for method in device['upload_methods']: if method not in ['uart', 'etx', 'wifi', 'betaflight']: - error(f'Invalid upload method "{method}" for target "{vendor}.{type}.{devname}"') + error(f'Invalid upload method "{method}" for target "{vendor}.{type}.{devname}"', 'invalid_upload_method') if (device['platform'] == 'esp32-c3' and '_ESP32C3_' not in device['firmware']) or \ (device['platform'] == 'esp32-s3' and '_ESP32S3_' not in device['firmware']) or \ (device['platform'] == 'esp32' and '_ESP32_' not in device['firmware']) : - error(f'"firmware" and "platform" must match for target "{vendor}.{type}.{devname}"') + error(f'"firmware" and "platform" must match for target "{vendor}.{type}.{devname}"', 'firmware_platform_mismatch') validate_esp(vendor, type, devname, device) def validate_esp8285(vendor, type, devname, device): for method in device['upload_methods']: if method not in ['uart', 'wifi', 'betaflight']: - error(f'Invalid upload method "{method}" for target "{vendor}.{type}.{devname}"') + error(f'Invalid upload method "{method}" for target "{vendor}.{type}.{devname}"', 'invalid_upload_method') if 'ESP8285' not in device['firmware']: - error(f'"firmware" and "platform" must match for target "{vendor}.{type}.{devname}"') + error(f'"firmware" and "platform" must match for target "{vendor}.{type}.{devname}"', 'firmware_platform_mismatch') validate_esp(vendor, type, devname, device) def validate_devices(vendor, type, devname, device): + set_ignored_validations(device) allowed_json_key_characters = re.compile("^[a-z0-9_-]+$") if not allowed_json_key_characters.match(devname): - error(f'device tag "{devname}" can only include lowercase a-z, 0-9, and underscores and dashes') + error(f'device tag "{devname}" can only include lowercase a-z, 0-9, and underscores and dashes', 'invalid_device_tag') if 'product_name' not in device: - error(f'device "{vendor}.{type}.{devname}" must have a "product_name" child element') + error(f'device "{vendor}.{type}.{devname}" must have a "product_name" child element', 'missing_product_name') if 'upload_methods' not in device: - error(f'device "{vendor}.{type}.{devname}" must have a "upload_methods" child element') + error(f'device "{vendor}.{type}.{devname}" must have a "upload_methods" child element', 'missing_upload_methods') if 'min_version' not in device: - error(f'device "{vendor}.{type}.{devname}" must have a "min_version" child element') + error(f'device "{vendor}.{type}.{devname}" must have a "min_version" child element', 'missing_min_version') if 'firmware' not in device: - error(f'device "{vendor}.{type}.{devname}" must have a "firmware" child element') + error(f'device "{vendor}.{type}.{devname}" must have a "firmware" child element', 'missing_firmware') else: firmware = device['firmware'] if firmware in stm32_targets: + set_ignored_validations() return if len(firmwares) != 0 and firmware not in firmwares: - error(f'device "{vendor}.{type}.{devname}" has an invalid firmware file "{firmware}"') + error(f'device "{vendor}.{type}.{devname}" has an invalid firmware file "{firmware}"', 'invalid_firmware_file') elif firmware.endswith('_TX') and 'tx_' not in type: - error(f'device "{vendor}.{type}.{devname}" has an invalid firmware file "{firmware}", it must be a TX target firmware') + error(f'device "{vendor}.{type}.{devname}" has an invalid firmware file "{firmware}", it must be a TX target firmware', 'firmware_type_mismatch') elif firmware.endswith('_RX') and 'rx_' not in type: - error(f'device "{vendor}.{type}.{devname}" has an invalid firmware file "{firmware}", it must be an RX target firmware') + error(f'device "{vendor}.{type}.{devname}" has an invalid firmware file "{firmware}", it must be an RX target firmware', 'firmware_type_mismatch') if 'platform' not in device: - error(f'device "{vendor}.{type}.{devname}" must have a "platform" child element') + error(f'device "{vendor}.{type}.{devname}" must have a "platform" child element', 'missing_platform') else: platform = device['platform'] if platform == 'stm32': @@ -168,12 +180,13 @@ def validate_devices(vendor, type, devname, device): elif platform.startswith('esp32'): validate_esp32(vendor, type, devname, device) else: - error(f'invalid platform "{platform}" in device "{vendor}.{type}.{devname}"') + error(f'invalid platform "{platform}" in device "{vendor}.{type}.{devname}"', 'invalid_platform') if 'features' in device: for feature in device['features']: if feature not in ['buzzer', 'unlock-higher-power', 'fan', 'sbus-uart']: - error(f'features must contain one or more of [\'buzzer\', \'unlock-higher-power\', \'fan\', \'sbus-uart\'], if present in target "{vendor}.{type}.{devname}"') + error(f'features must contain one or more of [\'buzzer\', \'unlock-higher-power\', \'fan\', \'sbus-uart\'], if present in target "{vendor}.{type}.{devname}"', 'invalid_feature') + set_ignored_validations() def validate_vendor(name, types): allowed_json_key_characters = re.compile("^[a-z0-9_-]+$") diff --git a/TX/Radiomaster AX12.json b/TX/Radiomaster AX12.json index 5eddb45..6de88cf 100644 --- a/TX/Radiomaster AX12.json +++ b/TX/Radiomaster AX12.json @@ -36,5 +36,5 @@ "debug_backpack_tx": 5, "backpack_boot": 23, "backpack_en": 19, - "passthrough_baud": 460800 + "passthrough_baud": 230400 } diff --git a/targets.json b/targets.json index e3c1dba..87a2768 100644 --- a/targets.json +++ b/targets.json @@ -3562,6 +3562,7 @@ "product_name": "RadioMaster AX12 2.4/900 TX", "lua_name": "RM AX12 X-Band", "layout_file": "Radiomaster AX12.json", + "validation_ignores": ["backpack_passthrough_baud"], "upload_methods": ["uart", "wifi"], "min_version": "3.4.0", "platform": "esp32",