Skip to content

Commit 61dde24

Browse files
committed
feat(configuration steps): added json schema validation and a "steps" toplevel
The "steps" level will allow adding extra sequence configuration data in the future
1 parent 3979ff7 commit 61dde24

15 files changed

+4338
-4101
lines changed

.github/workflows/pylint.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ jobs:
3737
- name: Install dependencies
3838
# these extra packages are required by pylint to validate the python imports
3939
run: |
40-
uv pip install 'pylint>=3.3.2' argcomplete defusedxml requests pymavlink pillow numpy matplotlib pyserial setuptools pytest
40+
uv pip install 'pylint>=3.3.2' argcomplete defusedxml requests pymavlink pillow numpy matplotlib pyserial setuptools pytest jsonschema
4141
42-
- name: Analysing the code with pylint
42+
- name: Analyzing the code with pylint
4343
run: |
4444
pylint $(git ls-files '*.py')

REUSE.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ path = "credits/setuptools-LICENSE"
151151
SPDX-FileCopyrightText = "Copyright (c) Setuptools developers."
152152
SPDX-License-Identifier = "MIT"
153153

154+
[[annotations]]
155+
path = "credits/jsonschema-COPYING"
156+
SPDX-FileCopyrightText = "Copyright (c) 2012 Julian Berman"
157+
SPDX-License-Identifier = "MIT"
158+
154159
[[annotations]]
155160
path = ["windows/ardupilot_methodic_configurator*", "windows/settings_template.json"]
156161
SPDX-FileCopyrightText = "2024-2025 Amilcar Lucas"

ardupilot_methodic_configurator/backend_filesystem_configuration_steps.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
from logging import warning as logging_warning
1919
from os import path as os_path
2020

21+
from jsonschema import validate as json_validate
22+
from jsonschema.exceptions import ValidationError
23+
2124
from ardupilot_methodic_configurator import _
2225
from ardupilot_methodic_configurator.annotate_params import Par
2326

@@ -42,17 +45,18 @@ def __init__(self, _vehicle_dir: str, vehicle_type: str) -> None:
4245
self.derived_parameters: dict[str, dict] = {}
4346
self.log_loaded_file = False
4447

45-
def re_init(self, vehicle_dir: str, vehicle_type: str) -> None:
48+
def re_init(self, vehicle_dir: str, vehicle_type: str) -> None: # pylint: disable=too-many-branches
4649
if vehicle_type == "":
4750
return
4851
self.configuration_steps_filename = "configuration_steps_" + vehicle_type + ".json"
4952
# Define a list of directories to search for the configuration_steps_filename file
5053
search_directories = [vehicle_dir, os_path.dirname(os_path.abspath(__file__))]
5154
file_found = False
55+
json_content = {}
5256
for i, directory in enumerate(search_directories):
5357
try:
5458
with open(os_path.join(directory, self.configuration_steps_filename), encoding="utf-8") as file:
55-
self.configuration_steps = json_load(file)
59+
json_content = json_load(file)
5660
file_found = True
5761
if self.log_loaded_file:
5862
if i == 0:
@@ -71,7 +75,22 @@ def re_init(self, vehicle_dir: str, vehicle_type: str) -> None:
7175
except JSONDecodeError as e:
7276
logging_error(_("Error in file '%s': %s"), self.configuration_steps_filename, e)
7377
break
78+
# Validate the vehicle configuration steps file against the configuration_steps_schema.json schema
7479
if file_found:
80+
schema_file = os_path.join(os_path.dirname(os_path.abspath(__file__)), "configuration_steps_schema.json")
81+
try:
82+
with open(schema_file, encoding="utf-8") as schema:
83+
schema_data = json_load(schema)
84+
json_validate(instance=json_content, schema=schema_data)
85+
except FileNotFoundError:
86+
logging_error(_("Schema file '%s' not found"), schema_file)
87+
except ValidationError as e:
88+
logging_error(_("Configuration steps validation error: %s"), str(e))
89+
except JSONDecodeError as e:
90+
logging_error(_("Error in schema file '%s': %s"), schema_file, e)
91+
92+
if file_found and "steps" in json_content:
93+
self.configuration_steps = json_content["steps"]
7594
for filename, file_info in self.configuration_steps.items():
7695
self.__validate_parameters_in_configuration_steps(filename, file_info, "forced")
7796
self.__validate_parameters_in_configuration_steps(filename, file_info, "derived")

ardupilot_methodic_configurator/configuration_steps_ArduCopter.json

Lines changed: 827 additions & 825 deletions
Large diffs are not rendered by default.

ardupilot_methodic_configurator/configuration_steps_ArduPlane.json

Lines changed: 827 additions & 825 deletions
Large diffs are not rendered by default.

ardupilot_methodic_configurator/configuration_steps_Heli.json

Lines changed: 824 additions & 822 deletions
Large diffs are not rendered by default.

ardupilot_methodic_configurator/configuration_steps_Rover.json

Lines changed: 827 additions & 825 deletions
Large diffs are not rendered by default.
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
{
2+
"type": "object",
3+
"required": ["steps"],
4+
"properties": {
5+
"steps": {
6+
"type": "object",
7+
"patternProperties": {
8+
"^[0-9]+_.+\\.param$": {
9+
"type": "object",
10+
"required": [
11+
"why",
12+
"why_now",
13+
"blog_text",
14+
"blog_url",
15+
"wiki_text",
16+
"wiki_url",
17+
"external_tool_text",
18+
"external_tool_url",
19+
"mandatory_text"
20+
],
21+
"properties": {
22+
"why": {
23+
"type": "string",
24+
"description": "Explanation of why this step is needed"
25+
},
26+
"why_now": {
27+
"type": "string",
28+
"description": "Explanation of why this step needs to be done at this point"
29+
},
30+
"blog_text": {
31+
"type": "string",
32+
"description": "Short description for blog reference"
33+
},
34+
"blog_url": {
35+
"type": "string",
36+
"pattern": "^https://",
37+
"description": "URL to blog documentation"
38+
},
39+
"wiki_text": {
40+
"type": "string",
41+
"description": "Short description for wiki reference"
42+
},
43+
"wiki_url": {
44+
"type": "string",
45+
"description": "URL to wiki documentation"
46+
},
47+
"external_tool_text": {
48+
"type": "string",
49+
"description": "Name/description of external tool needed"
50+
},
51+
"external_tool_url": {
52+
"type": "string",
53+
"description": "URL to external tool"
54+
},
55+
"mandatory_text": {
56+
"type": "string",
57+
"pattern": "^[0-9]{1,3}% mandatory \\([0-9]{1,3}% optional\\)$",
58+
"description": "Text indicating if step is mandatory or optional with percentages"
59+
},
60+
"auto_changed_by": {
61+
"type": "string",
62+
"description": "Name of tool/process that automatically changes these parameters"
63+
},
64+
"forced_parameters": {
65+
"type": "object",
66+
"patternProperties": {
67+
"^[A-Z0-9_]+$": {
68+
"type": "object",
69+
"required": ["New Value", "Change Reason"],
70+
"properties": {
71+
"New Value": {
72+
"type": ["number", "string"],
73+
"description": "New value for the parameter"
74+
},
75+
"Change Reason": {
76+
"type": "string",
77+
"description": "Reason for changing the parameter"
78+
}
79+
}
80+
}
81+
}
82+
},
83+
"derived_parameters": {
84+
"type": "object",
85+
"patternProperties": {
86+
"^[A-Z0-9_]+$": {
87+
"type": "object",
88+
"required": ["New Value", "Change Reason"],
89+
"properties": {
90+
"New Value": {
91+
"type": "string",
92+
"description": "Expression to derive new parameter value"
93+
},
94+
"Change Reason": {
95+
"type": "string",
96+
"description": "Reason for the derived parameter"
97+
}
98+
}
99+
}
100+
}
101+
},
102+
"jump_possible": {
103+
"type": "object",
104+
"patternProperties": {
105+
"^[0-9]+_.+\\.param$": {
106+
"type": "string",
107+
"description": "Message about possible jump to another step"
108+
}
109+
}
110+
},
111+
"old_filenames": {
112+
"type": "array",
113+
"items": {
114+
"type": "string",
115+
"pattern": "^[0-9]+_.+\\.param$"
116+
},
117+
"description": "Previous filenames for this step"
118+
},
119+
"rename_connection": {
120+
"type": "string",
121+
"description": "Expression to rename connection"
122+
},
123+
"download_file": {
124+
"type": "object",
125+
"required": ["source_url", "dest_local"],
126+
"properties": {
127+
"source_url": {
128+
"type": "string",
129+
"pattern": "^https://"
130+
},
131+
"dest_local": {
132+
"type": "string"
133+
}
134+
}
135+
},
136+
"upload_file": {
137+
"type": "object",
138+
"required": ["source_local", "dest_on_fc"],
139+
"properties": {
140+
"source_local": {
141+
"type": "string"
142+
},
143+
"dest_on_fc": {
144+
"type": "string",
145+
"pattern": "^/APM/"
146+
}
147+
}
148+
}
149+
}
150+
}
151+
}
152+
}
153+
},
154+
"additionalProperties": false
155+
}

0 commit comments

Comments
 (0)