wiliot-certificate 4.5.0a3__py3-none-any.whl → 4.5.0a4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- certificate/cert_common.py +33 -16
- certificate/cert_config.py +3 -3
- certificate/cert_data_sim.py +12 -9
- certificate/cert_defines.py +6 -0
- certificate/cert_gw_sim.py +3 -3
- certificate/cert_mqtt.py +5 -4
- certificate/cert_results.py +42 -32
- certificate/cert_utils.py +9 -10
- certificate/certificate.py +7 -5
- certificate/certificate_cli.py +9 -12
- certificate/tests/cloud_connectivity/acl_test/acl_test.py +13 -15
- certificate/tests/cloud_connectivity/brg_ota_test/brg_ota_test.json +1 -1
- certificate/tests/cloud_connectivity/channel_scan_behaviour_test/channel_scan_behaviour_test.py +2 -2
- certificate/tests/cloud_connectivity/connection_test/connection_test.py +4 -13
- certificate/tests/cloud_connectivity/deduplication_test/deduplication_test.py +1 -2
- certificate/tests/cloud_connectivity/ext_adv_stress_test/ext_adv_stress_test.py +12 -6
- certificate/tests/cloud_connectivity/registration_test/registration_test_cli.py +1 -1
- certificate/tests/cloud_connectivity/stress_test/stress_test.py +12 -7
- certificate/tests/cloud_connectivity/uplink_ext_adv_test/uplink_ext_adv_test.py +1 -2
- certificate/tests/cloud_connectivity/uplink_test/uplink_test.py +26 -20
- certificate/tests/datapath/event_ble5_test/event_ble5_test.json +1 -1
- certificate/tests/datapath/event_ble5_test/event_ble5_test.py +1 -9
- certificate/tests/datapath/event_test/event_test.json +1 -1
- certificate/tests/datapath/event_test/event_test.py +1 -7
- certificate/tests/datapath/rx_rate_gen2_test/rx_rate_gen2_test.py +1 -1
- certificate/tests/energy2400/signal_indicator_ble5_test/signal_indicator_ble5_test.py +3 -1
- certificate/tests/energy2400/signal_indicator_ext_adv_test/signal_indicator_ext_adv_test.py +2 -0
- certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.py +1 -1
- common/api_if/api_validation.py +6 -0
- gui_certificate/server.py +151 -58
- gui_certificate/templates/cert_run.html +86 -93
- {wiliot_certificate-4.5.0a3.dist-info → wiliot_certificate-4.5.0a4.dist-info}/METADATA +3 -3
- {wiliot_certificate-4.5.0a3.dist-info → wiliot_certificate-4.5.0a4.dist-info}/RECORD +37 -53
- certificate/ag/wlt_types_ag_jsons/brg2brg_ota.json +0 -211
- certificate/ag/wlt_types_ag_jsons/brg2gw_hb.json +0 -894
- certificate/ag/wlt_types_ag_jsons/brg2gw_hb_sleep.json +0 -184
- certificate/ag/wlt_types_ag_jsons/calibration.json +0 -490
- certificate/ag/wlt_types_ag_jsons/custom.json +0 -614
- certificate/ag/wlt_types_ag_jsons/datapath.json +0 -900
- certificate/ag/wlt_types_ag_jsons/energy2400.json +0 -670
- certificate/ag/wlt_types_ag_jsons/energySub1g.json +0 -691
- certificate/ag/wlt_types_ag_jsons/externalSensor.json +0 -727
- certificate/ag/wlt_types_ag_jsons/interface.json +0 -1095
- certificate/ag/wlt_types_ag_jsons/powerManagement.json +0 -1439
- certificate/ag/wlt_types_ag_jsons/side_info_sensor.json +0 -105
- certificate/ag/wlt_types_ag_jsons/signal_indicator_data.json +0 -77
- certificate/ag/wlt_types_ag_jsons/unified_echo_ext_pkt.json +0 -126
- certificate/ag/wlt_types_ag_jsons/unified_echo_pkt.json +0 -175
- certificate/ag/wlt_types_ag_jsons/unified_sensor_pkt.json +0 -65
- {wiliot_certificate-4.5.0a3.dist-info → wiliot_certificate-4.5.0a4.dist-info}/WHEEL +0 -0
- {wiliot_certificate-4.5.0a3.dist-info → wiliot_certificate-4.5.0a4.dist-info}/entry_points.txt +0 -0
- {wiliot_certificate-4.5.0a3.dist-info → wiliot_certificate-4.5.0a4.dist-info}/licenses/LICENSE +0 -0
- {wiliot_certificate-4.5.0a3.dist-info → wiliot_certificate-4.5.0a4.dist-info}/top_level.txt +0 -0
gui_certificate/server.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import json
|
|
3
3
|
import tempfile
|
|
4
|
-
|
|
4
|
+
import datetime
|
|
5
|
+
from flask import Flask, render_template, request, session, redirect, url_for, flash, send_file
|
|
5
6
|
from jinja2 import ChoiceLoader, PackageLoader
|
|
6
7
|
from werkzeug.utils import secure_filename
|
|
7
8
|
from certificate.certificate_cli import CertificateCLI
|
|
@@ -31,9 +32,21 @@ TEMP_BASE = tempfile.gettempdir()
|
|
|
31
32
|
UPLOAD_FOLDER = os.path.join(TEMP_BASE, "wiliot_certificate_uploaded_schemas")
|
|
32
33
|
CONFIG_FOLDER = os.path.join(TEMP_BASE, "wiliot_certificate_saved_configs")
|
|
33
34
|
TEST_LIST_FOLDER = os.path.join(TEMP_BASE, "wiliot_certificate_test_lists")
|
|
35
|
+
CUSTOM_BROKER_FOLDER = os.path.join(TEMP_BASE, "wiliot_certificate_custom_brokers")
|
|
34
36
|
ALLOWED_EXTENSIONS = {'json'}
|
|
35
37
|
MAX_UPLOAD_SIZE = 16 * 1024 * 1024 # 16MB
|
|
36
38
|
|
|
39
|
+
# Default custom broker configuration (from hivemq.json)
|
|
40
|
+
DEFAULT_CUSTOM_BROKER = {
|
|
41
|
+
"port": 8883,
|
|
42
|
+
"brokerUrl": "mqtts://broker.hivemq.com",
|
|
43
|
+
"username": "",
|
|
44
|
+
"password": "",
|
|
45
|
+
"updateTopic": "update/wiliot/<gatewayId>",
|
|
46
|
+
"statusTopic": "status/wiliot/<gatewayId>",
|
|
47
|
+
"dataTopic": "data/wiliot/<gatewayId>"
|
|
48
|
+
}
|
|
49
|
+
|
|
37
50
|
app = Flask(__name__)
|
|
38
51
|
app.secret_key = os.urandom(24) # Required for sessions
|
|
39
52
|
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
|
@@ -43,6 +56,7 @@ app.config['MAX_CONTENT_LENGTH'] = MAX_UPLOAD_SIZE
|
|
|
43
56
|
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
|
44
57
|
os.makedirs(CONFIG_FOLDER, exist_ok=True)
|
|
45
58
|
os.makedirs(TEST_LIST_FOLDER, exist_ok=True)
|
|
59
|
+
os.makedirs(CUSTOM_BROKER_FOLDER, exist_ok=True)
|
|
46
60
|
|
|
47
61
|
# extend Jinja search path to include shared dir
|
|
48
62
|
app.jinja_loader = ChoiceLoader([
|
|
@@ -84,11 +98,11 @@ def get_mandatory_modules_by_schema(schema_data, flavor):
|
|
|
84
98
|
return []
|
|
85
99
|
|
|
86
100
|
# Map schema module names to test module names
|
|
87
|
-
mandatory_by_schema = []
|
|
101
|
+
mandatory_by_schema = []
|
|
88
102
|
for schema_mod_name in schema_modules.keys():
|
|
89
103
|
# Map schema module name to test module name
|
|
90
104
|
test_mod_name = test_module_and_schema_module_to_other(schema_mod_name)
|
|
91
|
-
if test_mod_name not in mandatory_by_schema:
|
|
105
|
+
if test_mod_name not in mandatory_by_schema and test_mod_name != 'custom':
|
|
92
106
|
mandatory_by_schema.append(test_mod_name)
|
|
93
107
|
|
|
94
108
|
return mandatory_by_schema
|
|
@@ -324,7 +338,7 @@ def verify_schema_matches_selection(schema_data, flavor, selected_modules, selec
|
|
|
324
338
|
|
|
325
339
|
return len(errors) == 0, errors, warnings
|
|
326
340
|
|
|
327
|
-
def check_certification_status(flavor, selected_modules, selected_tests, tests_schema, schema_data=None):
|
|
341
|
+
def check_certification_status(flavor, selected_modules, selected_tests, tests_schema, schema_data=None, unsterile_run=False):
|
|
328
342
|
"""Check if the selection qualifies for certification or is test-only."""
|
|
329
343
|
mandatory_modules = get_mandatory_modules_for_flavor(flavor)
|
|
330
344
|
mandatory_by_schema = get_mandatory_modules_by_schema(schema_data, flavor) if schema_data else []
|
|
@@ -359,8 +373,9 @@ def check_certification_status(flavor, selected_modules, selected_tests, tests_s
|
|
|
359
373
|
if len(additional_modules) == 0:
|
|
360
374
|
missing_additional_module = True
|
|
361
375
|
|
|
376
|
+
# A certifying run must be sterile - if unsterile_run is set, force non-certifying
|
|
362
377
|
is_certified = (len(missing_modules) == 0 and len(missing_modules_by_schema) == 0 and
|
|
363
|
-
len(missing_tests) == 0 and not missing_additional_module)
|
|
378
|
+
len(missing_tests) == 0 and not missing_additional_module and not unsterile_run)
|
|
364
379
|
|
|
365
380
|
return is_certified, missing_modules, missing_tests, missing_additional_module, missing_modules_by_schema
|
|
366
381
|
|
|
@@ -465,6 +480,43 @@ def _write_testlist(selected_ids: list[str], form_data=None, is_certified=False)
|
|
|
465
480
|
print(f"Custom test list saved in {path}")
|
|
466
481
|
return path
|
|
467
482
|
|
|
483
|
+
def _write_custom_broker_config(form_data, cert_schema_title="cert_run") -> str:
|
|
484
|
+
"""
|
|
485
|
+
Generate a custom broker JSON configuration file from form data.
|
|
486
|
+
Extracts broker field values and creates a JSON file matching hivemq.json format.
|
|
487
|
+
"""
|
|
488
|
+
# Extract broker configuration from form_data
|
|
489
|
+
broker_config = {}
|
|
490
|
+
|
|
491
|
+
# Field names follow pattern: {schema_title}_custom_broker_{field_name}
|
|
492
|
+
field_prefix = f"{cert_schema_title}_custom_broker_"
|
|
493
|
+
|
|
494
|
+
# Extract values from form_data, using defaults if not provided
|
|
495
|
+
for field in DEFAULT_CUSTOM_BROKER.keys():
|
|
496
|
+
field_name = f"{field_prefix}{field}"
|
|
497
|
+
value = form_data.get(field_name, "")
|
|
498
|
+
|
|
499
|
+
# Convert port to int if it's a number
|
|
500
|
+
if field == "port":
|
|
501
|
+
try:
|
|
502
|
+
broker_config[field] = int(value) if value else DEFAULT_CUSTOM_BROKER[field]
|
|
503
|
+
except (ValueError, TypeError):
|
|
504
|
+
broker_config[field] = DEFAULT_CUSTOM_BROKER[field]
|
|
505
|
+
else:
|
|
506
|
+
broker_config[field] = value
|
|
507
|
+
|
|
508
|
+
# Generate filename with timestamp
|
|
509
|
+
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
510
|
+
filename = f"custom_broker_{timestamp}.json"
|
|
511
|
+
filepath = os.path.join(CUSTOM_BROKER_FOLDER, filename)
|
|
512
|
+
|
|
513
|
+
# Write JSON file
|
|
514
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
515
|
+
json.dump(broker_config, f, indent=4)
|
|
516
|
+
|
|
517
|
+
print(f"Custom broker config saved in {filepath}")
|
|
518
|
+
return filepath
|
|
519
|
+
|
|
468
520
|
@app.route("/")
|
|
469
521
|
def index():
|
|
470
522
|
"""Redirect to step 1 or show current step."""
|
|
@@ -632,43 +684,19 @@ def step(step_num):
|
|
|
632
684
|
else:
|
|
633
685
|
form_data_dict[key] = request.form.get(key)
|
|
634
686
|
|
|
635
|
-
# Handle custom_broker
|
|
687
|
+
# Handle custom_broker configuration from form fields
|
|
636
688
|
cert_cli = CertificateCLI()
|
|
637
689
|
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
638
690
|
custom_broker_field_name = f"{cert_schema['title']}_custom_broker"
|
|
639
|
-
custom_broker_file_key = f"{custom_broker_field_name}_file"
|
|
640
|
-
|
|
641
|
-
# Preserve existing custom_broker_path if no new file is uploaded
|
|
642
|
-
existing_custom_broker_path = session.get('custom_broker_path')
|
|
643
|
-
if existing_custom_broker_path:
|
|
644
|
-
form_data_dict[custom_broker_field_name] = existing_custom_broker_path
|
|
645
691
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
# Validate file is valid JSON
|
|
656
|
-
try:
|
|
657
|
-
with open(filepath, "r", encoding="utf-8") as f:
|
|
658
|
-
json.load(f)
|
|
659
|
-
# Store normalized path
|
|
660
|
-
normalized_path = os.path.normpath(filepath)
|
|
661
|
-
session['custom_broker_path'] = normalized_path
|
|
662
|
-
form_data_dict[custom_broker_field_name] = normalized_path
|
|
663
|
-
except json.JSONDecodeError as e:
|
|
664
|
-
error = f"Invalid JSON in custom broker file: {e}"
|
|
665
|
-
os.remove(filepath)
|
|
666
|
-
except Exception as e:
|
|
667
|
-
error = f"Error reading custom broker file: {e}"
|
|
668
|
-
if os.path.exists(filepath):
|
|
669
|
-
os.remove(filepath)
|
|
670
|
-
elif file and file.filename:
|
|
671
|
-
error = "Custom broker file must be a JSON file"
|
|
692
|
+
# Generate custom broker JSON file from form fields
|
|
693
|
+
try:
|
|
694
|
+
broker_config_path = _write_custom_broker_config(form_data_dict, cert_schema['title'])
|
|
695
|
+
normalized_path = os.path.normpath(broker_config_path)
|
|
696
|
+
session['custom_broker_path'] = normalized_path
|
|
697
|
+
form_data_dict[custom_broker_field_name] = normalized_path
|
|
698
|
+
except Exception as e:
|
|
699
|
+
error = f"Error generating custom broker configuration: {e}"
|
|
672
700
|
|
|
673
701
|
# Validate all required fields (required for moving forward, but allow going back)
|
|
674
702
|
if action == 'next':
|
|
@@ -688,13 +716,22 @@ def step(step_num):
|
|
|
688
716
|
field_name = f"{cert_schema['title']}_{field['name']}"
|
|
689
717
|
field_value = form_data_dict.get(field_name, '')
|
|
690
718
|
|
|
691
|
-
# Special handling for custom_broker
|
|
719
|
+
# Special handling for custom_broker - check if all required broker fields are filled
|
|
692
720
|
if field['name'] == 'custom_broker':
|
|
693
|
-
# Check
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
if not
|
|
721
|
+
# Check all broker fields except username and password are filled
|
|
722
|
+
broker_field_prefix = f"{cert_schema['title']}_custom_broker_"
|
|
723
|
+
optional_fields = {'username', 'password'}
|
|
724
|
+
for broker_field_key in DEFAULT_CUSTOM_BROKER.keys():
|
|
725
|
+
if broker_field_key not in optional_fields:
|
|
726
|
+
broker_field_name = f"{broker_field_prefix}{broker_field_key}"
|
|
727
|
+
broker_field_value = form_data_dict.get(broker_field_name, '')
|
|
728
|
+
if not broker_field_value or (isinstance(broker_field_value, str) and not broker_field_value.strip()):
|
|
729
|
+
missing_required.append(f"custom_broker.{broker_field_key}")
|
|
730
|
+
break
|
|
731
|
+
# Also check if path was generated (should be generated above if fields are valid)
|
|
732
|
+
if not field_value and not session.get('custom_broker_path'):
|
|
733
|
+
# Only add if we haven't already added a broker field error
|
|
734
|
+
if not any('custom_broker.' in req for req in missing_required):
|
|
698
735
|
missing_required.append(field.get('name', field['label']))
|
|
699
736
|
else:
|
|
700
737
|
# For other fields, check if value is provided
|
|
@@ -792,6 +829,15 @@ def step(step_num):
|
|
|
792
829
|
schema_data = load_schema_data(schema_path) if schema_path else None
|
|
793
830
|
filtered_modules = filter_modules_by_flavor(tests_schema, flavor, schema_data)
|
|
794
831
|
|
|
832
|
+
# Get CLI schemas for form fields - use certificate_cli for all flavors (needed early for unsterile_run check)
|
|
833
|
+
cert_cli = CertificateCLI()
|
|
834
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
835
|
+
|
|
836
|
+
# Check if unsterile_run is set in form_data
|
|
837
|
+
form_data = _prepare_form_data_for_template(session, cert_schema)
|
|
838
|
+
unsterile_run_field = f"{cert_schema['title']}_unsterile_run"
|
|
839
|
+
unsterile_run = bool(form_data.get(unsterile_run_field))
|
|
840
|
+
|
|
795
841
|
# Get certification status
|
|
796
842
|
is_certified = True
|
|
797
843
|
missing_modules = []
|
|
@@ -804,7 +850,7 @@ def step(step_num):
|
|
|
804
850
|
module_dicts = [m for m in filtered_modules if m.get('name') in selected_modules]
|
|
805
851
|
schema_data = load_schema_data(schema_path) if schema_path else None
|
|
806
852
|
is_certified, missing_modules, missing_tests, missing_additional_module, missing_modules_by_schema = check_certification_status(
|
|
807
|
-
flavor, module_dicts, selected_tests, tests_schema, schema_data
|
|
853
|
+
flavor, module_dicts, selected_tests, tests_schema, schema_data, unsterile_run
|
|
808
854
|
)
|
|
809
855
|
|
|
810
856
|
# Get test details (labels) for missing tests
|
|
@@ -834,12 +880,9 @@ def step(step_num):
|
|
|
834
880
|
# Get missing schema-mandatory modules for display (recalculate if not already set)
|
|
835
881
|
if not missing_modules_by_schema and step_num >= 4 and flavor and selected_modules:
|
|
836
882
|
_, _, _, _, missing_modules_by_schema = check_certification_status(
|
|
837
|
-
flavor, module_dicts, selected_tests, tests_schema, schema_data
|
|
883
|
+
flavor, module_dicts, selected_tests, tests_schema, schema_data, unsterile_run
|
|
838
884
|
)
|
|
839
885
|
|
|
840
|
-
# Get CLI schemas for form fields - use certificate_cli for all flavors
|
|
841
|
-
cert_cli = CertificateCLI()
|
|
842
|
-
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
843
886
|
|
|
844
887
|
# Calculate total test count including mandatory tests from selected modules
|
|
845
888
|
total_test_count = len(selected_tests)
|
|
@@ -933,11 +976,14 @@ def step(step_num):
|
|
|
933
976
|
error=error,
|
|
934
977
|
warning=warning,
|
|
935
978
|
form_data=_prepare_form_data_for_template(session, cert_schema),
|
|
979
|
+
custom_broker_defaults=DEFAULT_CUSTOM_BROKER,
|
|
980
|
+
custom_broker_field_keys=list(DEFAULT_CUSTOM_BROKER.keys()),
|
|
936
981
|
run_completed=run_completed,
|
|
937
982
|
run_terminal=run_terminal,
|
|
938
983
|
run_pid=run_pid,
|
|
939
984
|
run_is_certified=run_is_certified,
|
|
940
|
-
available_ports=available_ports
|
|
985
|
+
available_ports=available_ports,
|
|
986
|
+
unsterile_run=unsterile_run)
|
|
941
987
|
|
|
942
988
|
def execute_certificate():
|
|
943
989
|
"""Execute the certificate run based on session data."""
|
|
@@ -964,7 +1010,12 @@ def execute_certificate():
|
|
|
964
1010
|
filtered_modules = filter_modules_by_flavor(tests_schema, flavor)
|
|
965
1011
|
module_dicts = [m for m in filtered_modules if m.get('name') in session.get('selected_modules', [])]
|
|
966
1012
|
schema_data = load_schema_data(schema_path) if schema_path else None
|
|
967
|
-
|
|
1013
|
+
# Check if unsterile_run is set in form_data
|
|
1014
|
+
cert_cli = CertificateCLI()
|
|
1015
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
1016
|
+
unsterile_run_field = f"{cert_schema['title']}_unsterile_run"
|
|
1017
|
+
unsterile_run = bool(form_data.get(unsterile_run_field))
|
|
1018
|
+
is_certified, _, _, _, _ = check_certification_status(flavor, module_dicts, selected_tests, tests_schema, schema_data, unsterile_run)
|
|
968
1019
|
|
|
969
1020
|
full_cmd = []
|
|
970
1021
|
|
|
@@ -1027,6 +1078,17 @@ def export_config():
|
|
|
1027
1078
|
flash("No configuration to export. Please complete at least step 1 and 2.", "error")
|
|
1028
1079
|
return redirect(url_for('step', step_num=session.get('current_step', 1)))
|
|
1029
1080
|
|
|
1081
|
+
# Extract custom broker form field values from form_data
|
|
1082
|
+
form_data = session.get('form_data', {})
|
|
1083
|
+
cert_cli = CertificateCLI()
|
|
1084
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
1085
|
+
custom_broker_fields = {}
|
|
1086
|
+
field_prefix = f"{cert_schema['title']}_custom_broker_"
|
|
1087
|
+
for field_name in DEFAULT_CUSTOM_BROKER.keys():
|
|
1088
|
+
full_field_name = f"{field_prefix}{field_name}"
|
|
1089
|
+
if full_field_name in form_data:
|
|
1090
|
+
custom_broker_fields[field_name] = form_data[full_field_name]
|
|
1091
|
+
|
|
1030
1092
|
# Prepare configuration data
|
|
1031
1093
|
config = {
|
|
1032
1094
|
'version': '1.0',
|
|
@@ -1035,9 +1097,10 @@ def export_config():
|
|
|
1035
1097
|
'schema_path': session.get('schema_path'),
|
|
1036
1098
|
'selected_modules': session.get('selected_modules', []),
|
|
1037
1099
|
'selected_tests': session.get('selected_tests', []),
|
|
1038
|
-
'form_data':
|
|
1100
|
+
'form_data': form_data,
|
|
1039
1101
|
'schema_data': load_schema_data(session.get('schema_path')), # Load schema data from file for export
|
|
1040
|
-
'custom_broker_path': session.get('custom_broker_path') # Include custom broker file path
|
|
1102
|
+
'custom_broker_path': session.get('custom_broker_path'), # Include custom broker file path for backward compatibility
|
|
1103
|
+
'custom_broker_fields': custom_broker_fields # Include custom broker form field values
|
|
1041
1104
|
}
|
|
1042
1105
|
|
|
1043
1106
|
# Create filename with timestamp
|
|
@@ -1098,17 +1161,47 @@ def import_config():
|
|
|
1098
1161
|
json.dump(schema_data, f, indent=2)
|
|
1099
1162
|
except Exception:
|
|
1100
1163
|
pass # If we can't save, user will need to re-upload
|
|
1101
|
-
|
|
1164
|
+
# Handle custom broker configuration
|
|
1165
|
+
custom_broker_fields = config_data.get('custom_broker_fields', {})
|
|
1166
|
+
if custom_broker_fields:
|
|
1167
|
+
# Restore broker form field values to form_data
|
|
1168
|
+
cert_cli = CertificateCLI()
|
|
1169
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
1170
|
+
field_prefix = f"{cert_schema['title']}_custom_broker_"
|
|
1171
|
+
for field_name, field_value in custom_broker_fields.items():
|
|
1172
|
+
full_field_name = f"{field_prefix}{field_name}"
|
|
1173
|
+
session['form_data'][full_field_name] = field_value
|
|
1174
|
+
# Regenerate broker config file from form fields
|
|
1175
|
+
try:
|
|
1176
|
+
broker_config_path = _write_custom_broker_config(session['form_data'], cert_schema['title'])
|
|
1177
|
+
session['custom_broker_path'] = os.path.normpath(broker_config_path)
|
|
1178
|
+
except Exception:
|
|
1179
|
+
pass # If generation fails, user can reconfigure in step 4
|
|
1180
|
+
else:
|
|
1181
|
+
# Backward compatibility: try to use existing path
|
|
1182
|
+
session['custom_broker_path'] = config_data.get('custom_broker_path')
|
|
1102
1183
|
|
|
1103
1184
|
# Validate schema file still exists
|
|
1104
1185
|
if session['schema_path'] and not os.path.exists(session['schema_path']):
|
|
1105
1186
|
flash(f"Warning: Schema file not found at {session['schema_path']}. Please re-upload it in step 2.", "warning")
|
|
1106
1187
|
session['current_step'] = 2
|
|
1107
|
-
# Validate custom_broker file still exists if present
|
|
1188
|
+
# Validate custom_broker file still exists if present (or regenerate if we have fields)
|
|
1108
1189
|
elif session.get('custom_broker_path') and not os.path.exists(session['custom_broker_path']):
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1190
|
+
if custom_broker_fields:
|
|
1191
|
+
# Regenerate from fields if file doesn't exist
|
|
1192
|
+
try:
|
|
1193
|
+
cert_cli = CertificateCLI()
|
|
1194
|
+
cert_schema = web_utils.parser_to_schema(cert_cli.parser)
|
|
1195
|
+
broker_config_path = _write_custom_broker_config(session['form_data'], cert_schema['title'])
|
|
1196
|
+
session['custom_broker_path'] = os.path.normpath(broker_config_path)
|
|
1197
|
+
except Exception:
|
|
1198
|
+
flash(f"Warning: Custom broker configuration will be regenerated in step 4.", "warning")
|
|
1199
|
+
if session.get('current_step', 0) < 4:
|
|
1200
|
+
session['current_step'] = 4
|
|
1201
|
+
else:
|
|
1202
|
+
flash(f"Warning: Custom broker file not found at {session['custom_broker_path']}. Please reconfigure it in step 4.", "warning")
|
|
1203
|
+
if session.get('current_step', 0) < 4:
|
|
1204
|
+
session['current_step'] = 4
|
|
1112
1205
|
else:
|
|
1113
1206
|
# Determine appropriate step based on what's configured
|
|
1114
1207
|
if session.get('selected_tests'):
|
|
@@ -156,6 +156,7 @@
|
|
|
156
156
|
{%- endmacro %}
|
|
157
157
|
|
|
158
158
|
{% set non_cert_text = "NOT READY FOR CERTIFICATION" %}
|
|
159
|
+
{% set unsterile_run_warning = "Unsterile run mode is set - a certifying run must be sterile." %}
|
|
159
160
|
|
|
160
161
|
<!-- Progress Indicator -->
|
|
161
162
|
<div class="step-indicator">
|
|
@@ -454,7 +455,7 @@
|
|
|
454
455
|
{% set common_parameters = ['dut', 'custom_broker'] %}
|
|
455
456
|
|
|
456
457
|
{% for f in cert_schema.fields %}
|
|
457
|
-
{% if f.name != 'validation_schema' and f.name != 'tl' and f.name in common_parameters %}
|
|
458
|
+
{% if f.name != 'validation_schema' and f.name != 'tl' and f.name != 'custom_broker' and f.name in common_parameters %}
|
|
458
459
|
{% set arg_label = (f.option_strings|join(', ') if f.option_strings else f.name) %}
|
|
459
460
|
{% set tip_id = 'help_' ~ f.name %}
|
|
460
461
|
{% set field_name = cert_schema.title ~ '_' ~ f.name %}
|
|
@@ -481,18 +482,6 @@
|
|
|
481
482
|
<input type="checkbox" class="form-check-input" id="{{ field_name }}" name="{{ field_name }}" {% if field_value %}checked{% endif %}>
|
|
482
483
|
</div>
|
|
483
484
|
</div>
|
|
484
|
-
{% elif f.name == "overwrite_defaults" %}
|
|
485
|
-
<div class="col-sm-4">
|
|
486
|
-
<textarea id="{{ field_name }}" class="form-control" name="{{ field_name }}" rows="3" placeholder="key1=value1 key2=value2">{{ field_value }}</textarea>
|
|
487
|
-
</div>
|
|
488
|
-
{% elif f.name == "custom_broker" %}
|
|
489
|
-
<div class="col-sm-4">
|
|
490
|
-
<input type="file" class="form-control" id="{{ field_name }}" name="{{ field_name }}_file" accept=".json">
|
|
491
|
-
{% if field_value %}
|
|
492
|
-
{% set path_parts = field_value.replace('\\', '/').split('/') %}
|
|
493
|
-
<div class="form-text">Current: {{ path_parts[-1] if path_parts|length > 1 else field_value }}</div>
|
|
494
|
-
{% endif %}
|
|
495
|
-
</div>
|
|
496
485
|
{% else %}
|
|
497
486
|
<div class="col-sm-4">
|
|
498
487
|
<input type="{{ f.type }}" class="form-control" id="{{ field_name }}" name="{{ field_name }}" value="{{ field_value }}">
|
|
@@ -512,6 +501,43 @@
|
|
|
512
501
|
{% endif %}
|
|
513
502
|
{% endfor %}
|
|
514
503
|
|
|
504
|
+
<!-- Custom Broker Configuration -->
|
|
505
|
+
{% set cert_schema = cert_schema %}
|
|
506
|
+
{% set broker_field_prefix = cert_schema.title ~ '_custom_broker_' %}
|
|
507
|
+
<div class="card mb-3">
|
|
508
|
+
<div class="card-header d-flex align-items-center">
|
|
509
|
+
<button class="btn btn-sm btn-link text-decoration-none test-toggle me-2 collapsed"
|
|
510
|
+
type="button"
|
|
511
|
+
data-bs-toggle="collapse"
|
|
512
|
+
data-bs-target="#customBrokerConfig"
|
|
513
|
+
aria-expanded="false">
|
|
514
|
+
<svg class="chev" width="16" height="16" viewBox="0 0 16 16">
|
|
515
|
+
<path d="M4.646 5.646a.5.5 0 0 1 .708 0L8 8.293l2.646-2.647a.5.5 0 1 1 .708.708L8.354 9.354a.5.5 0 0 1-.708 0L4.646 6.354a.5.5 0 0 1 0-.708z" fill="currentColor"/>
|
|
516
|
+
</svg>
|
|
517
|
+
</button>
|
|
518
|
+
<strong>Custom MQTT Broker Configuration</strong>
|
|
519
|
+
<span class="text-danger ms-2">*</span>
|
|
520
|
+
</div>
|
|
521
|
+
<div id="customBrokerConfig" class="collapse">
|
|
522
|
+
<div class="card-body">
|
|
523
|
+
{% for field_key, field_default in custom_broker_defaults.items() %}
|
|
524
|
+
{% set field_name = broker_field_prefix ~ field_key %}
|
|
525
|
+
{% set field_value = form_data.get(field_name, field_default) %}
|
|
526
|
+
{% set input_type = 'number' if field_key == 'port' else 'password' if field_key == 'password' else 'text' %}
|
|
527
|
+
{% set is_required = field_key not in ['username', 'password'] %}
|
|
528
|
+
<div class="row mb-3">
|
|
529
|
+
<label for="{{ field_name }}" class="col-sm-3 col-form-label">
|
|
530
|
+
{{ field_key }}{% if is_required %} <span class="text-danger">*</span>{% endif %}
|
|
531
|
+
</label>
|
|
532
|
+
<div class="col-sm-4">
|
|
533
|
+
<input type="{{ input_type }}" class="form-control" id="{{ field_name }}" name="{{ field_name }}" value="{{ field_value }}" {% if is_required %}required{% endif %}>
|
|
534
|
+
</div>
|
|
535
|
+
</div>
|
|
536
|
+
{% endfor %}
|
|
537
|
+
</div>
|
|
538
|
+
</div>
|
|
539
|
+
</div>
|
|
540
|
+
|
|
515
541
|
<div class="card mb-3">
|
|
516
542
|
<div class="card-header d-flex align-items-center">
|
|
517
543
|
<button class="btn btn-sm btn-link text-decoration-none test-toggle me-2 collapsed"
|
|
@@ -618,10 +644,20 @@
|
|
|
618
644
|
<small class="text-muted d-block mt-1">All mandatory modules and tests are selected</small>
|
|
619
645
|
{% else %}
|
|
620
646
|
<span class="badge bg-warning text-dark">{{ non_cert_text }}</span>
|
|
621
|
-
<small class="text-muted d-block mt-1">
|
|
647
|
+
<small class="text-muted d-block mt-1">
|
|
648
|
+
{% if not unsterile_run %}
|
|
649
|
+
Some mandatory modules or tests are missing
|
|
650
|
+
{% endif %}
|
|
651
|
+
</small>
|
|
622
652
|
{% endif %}
|
|
623
653
|
</p>
|
|
624
654
|
|
|
655
|
+
{% if unsterile_run %}
|
|
656
|
+
<div class="alert alert-warning">
|
|
657
|
+
<strong>⚠️ Warning:</strong> {{ unsterile_run_warning }}
|
|
658
|
+
</div>
|
|
659
|
+
{% endif %}
|
|
660
|
+
|
|
625
661
|
{% if not is_certified %}
|
|
626
662
|
{% if missing_modules %}
|
|
627
663
|
<div class="alert alert-warning">
|
|
@@ -711,76 +747,6 @@
|
|
|
711
747
|
</div>
|
|
712
748
|
</div>
|
|
713
749
|
|
|
714
|
-
<!-- Run Parameters Summary -->
|
|
715
|
-
{% set cert_schema = cert_schema %}
|
|
716
|
-
{% set cb_index = -1 %}
|
|
717
|
-
{% for f in cert_schema.fields %}
|
|
718
|
-
{% if f.name == 'custom_broker' %}
|
|
719
|
-
{% set cb_index = loop.index0 %}
|
|
720
|
-
{% endif %}
|
|
721
|
-
{% endfor %}
|
|
722
|
-
{% set has_advanced = False %}
|
|
723
|
-
{% for f in cert_schema.fields %}
|
|
724
|
-
{% if f.name != 'validation_schema' and f.name != 'tl' and cb_index >= 0 and loop.index0 > cb_index %}
|
|
725
|
-
{% set has_advanced = True %}
|
|
726
|
-
{% endif %}
|
|
727
|
-
{% endfor %}
|
|
728
|
-
{% if has_advanced %}
|
|
729
|
-
<div class="card mb-4">
|
|
730
|
-
<div class="card-header">
|
|
731
|
-
<h5 class="mb-0">Run Parameters</h5>
|
|
732
|
-
</div>
|
|
733
|
-
<div class="card-body">
|
|
734
|
-
{% for f in cert_schema.fields %}
|
|
735
|
-
{% if f.name != 'validation_schema' and f.name != 'tl' %}
|
|
736
|
-
{% set field_name = cert_schema.title ~ '_' ~ f.name %}
|
|
737
|
-
{% set field_value = form_data.get(field_name, f.default if f.default is not none else '') %}
|
|
738
|
-
{% set is_advanced = cb_index >= 0 and loop.index0 > cb_index %}
|
|
739
|
-
|
|
740
|
-
{% if not is_advanced %}
|
|
741
|
-
{% set arg_label = (f.option_strings|join(', ') if f.option_strings else f.name) %}
|
|
742
|
-
<p><strong>{{ arg_label }}:</strong>
|
|
743
|
-
{% if f.is_flag %}
|
|
744
|
-
{{ 'Yes' if field_value else 'No' }}
|
|
745
|
-
{% else %}
|
|
746
|
-
{{ field_value if field_value else '(not set)' }}
|
|
747
|
-
{% endif %}
|
|
748
|
-
</p>
|
|
749
|
-
{% endif %}
|
|
750
|
-
{% endif %}
|
|
751
|
-
{% endfor %}
|
|
752
|
-
|
|
753
|
-
{% if has_advanced %}
|
|
754
|
-
<div class="mb-3">
|
|
755
|
-
<button class="btn btn-outline-secondary btn-sm" type="button" data-bs-toggle="collapse" data-bs-target="#advancedParamsReview" aria-expanded="false" aria-controls="advancedParamsReview">
|
|
756
|
-
Advanced Parameters ▼
|
|
757
|
-
</button>
|
|
758
|
-
</div>
|
|
759
|
-
<div class="collapse" id="advancedParamsReview">
|
|
760
|
-
{% for f in cert_schema.fields %}
|
|
761
|
-
{% if f.name != 'validation_schema' and f.name != 'tl' %}
|
|
762
|
-
{% set field_name = cert_schema.title ~ '_' ~ f.name %}
|
|
763
|
-
{% set field_value = form_data.get(field_name, f.default if f.default is not none else '') %}
|
|
764
|
-
{% set is_advanced = cb_index >= 0 and loop.index0 > cb_index %}
|
|
765
|
-
|
|
766
|
-
{% if is_advanced %}
|
|
767
|
-
{% set arg_label = (f.option_strings|join(', ') if f.option_strings else f.name) %}
|
|
768
|
-
<p><strong>{{ arg_label }}:</strong>
|
|
769
|
-
{% if f.is_flag %}
|
|
770
|
-
{{ 'Yes' if field_value else 'No' }}
|
|
771
|
-
{% else %}
|
|
772
|
-
{{ field_value if field_value else '(not set)' }}
|
|
773
|
-
{% endif %}
|
|
774
|
-
</p>
|
|
775
|
-
{% endif %}
|
|
776
|
-
{% endif %}
|
|
777
|
-
{% endfor %}
|
|
778
|
-
</div>
|
|
779
|
-
{% endif %}
|
|
780
|
-
</div>
|
|
781
|
-
</div>
|
|
782
|
-
{% endif %}
|
|
783
|
-
|
|
784
750
|
<!-- Edit Links -->
|
|
785
751
|
<div class="mb-4">
|
|
786
752
|
<p>Need to make changes?</p>
|
|
@@ -860,6 +826,7 @@
|
|
|
860
826
|
const mandatoryTests = {{ mandatory_tests_for_js|tojson|safe }};
|
|
861
827
|
const currentSelectedModules = {{ selected_modules|tojson|safe }};
|
|
862
828
|
const brgCertSchema = {{ cert_schema|tojson|safe }};
|
|
829
|
+
const customBrokerFieldKeys = {{ custom_broker_field_keys|tojson|safe }};
|
|
863
830
|
|
|
864
831
|
// Step 3: Module selection validation
|
|
865
832
|
const mandatoryModulesBySchema = {{ mandatory_modules_by_schema_for_js|tojson|safe }};
|
|
@@ -925,6 +892,12 @@
|
|
|
925
892
|
|
|
926
893
|
let warnings = [];
|
|
927
894
|
|
|
895
|
+
// Check if unsterile_run is enabled
|
|
896
|
+
const unsterileRunField = document.querySelector('input[id*="unsterile_run"], input[name*="unsterile_run"]');
|
|
897
|
+
if (unsterileRunField && unsterileRunField.checked) {
|
|
898
|
+
warnings.push('{{ unsterile_run_warning }}');
|
|
899
|
+
}
|
|
900
|
+
|
|
928
901
|
// Check if mandatory modules are deselected (from step 3)
|
|
929
902
|
const missingMandatoryModules = mandatoryModules.filter(m => !selectedModules.includes(m));
|
|
930
903
|
if (missingMandatoryModules.length > 0) {
|
|
@@ -1072,6 +1045,18 @@
|
|
|
1072
1045
|
if (stepNum === 3) checkStep3Validation();
|
|
1073
1046
|
if (stepNum === 4) checkStep4Validation();
|
|
1074
1047
|
}
|
|
1048
|
+
|
|
1049
|
+
// Unsterile run checkbox change
|
|
1050
|
+
const unsterileRunField = document.querySelector('input[id*="unsterile_run"], input[name*="unsterile_run"]');
|
|
1051
|
+
if (unsterileRunField && e.target === unsterileRunField) {
|
|
1052
|
+
// Show/hide warning and update validation
|
|
1053
|
+
const warningDiv = document.getElementById('step4_warning');
|
|
1054
|
+
const warningText = document.getElementById('step4_warning_text');
|
|
1055
|
+
if (warningDiv && warningText) {
|
|
1056
|
+
// Re-run validation to update warning
|
|
1057
|
+
if (stepNum === 4) checkStep4Validation();
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1075
1060
|
});
|
|
1076
1061
|
|
|
1077
1062
|
// Initialize params visibility for pre-checked tests
|
|
@@ -1104,18 +1089,26 @@
|
|
|
1104
1089
|
const fieldName = `${brgCertSchema.title}_${field.name}`;
|
|
1105
1090
|
let fieldValue = null;
|
|
1106
1091
|
|
|
1107
|
-
// Special handling for custom_broker
|
|
1092
|
+
// Special handling for custom_broker - check if all required broker fields have values (like dut)
|
|
1093
|
+
// Username and password are optional, all other fields are required
|
|
1108
1094
|
if (field.name === 'custom_broker') {
|
|
1109
|
-
const
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
//
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1095
|
+
const brokerFieldPrefix = `${brgCertSchema.title}_custom_broker_`;
|
|
1096
|
+
const optionalFields = ['username', 'password'];
|
|
1097
|
+
let allRequiredFieldsFilled = true;
|
|
1098
|
+
for (const brokerField of customBrokerFieldKeys) {
|
|
1099
|
+
// Skip optional fields
|
|
1100
|
+
if (optionalFields.includes(brokerField)) {
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
const input = document.querySelector(`input[name="${brokerFieldPrefix}${brokerField}"]`);
|
|
1104
|
+
if (!input || !input.value || (typeof input.value === 'string' && !input.value.trim())) {
|
|
1105
|
+
allRequiredFieldsFilled = false;
|
|
1106
|
+
break;
|
|
1117
1107
|
}
|
|
1118
1108
|
}
|
|
1109
|
+
if (allRequiredFieldsFilled) {
|
|
1110
|
+
fieldValue = 'configured';
|
|
1111
|
+
}
|
|
1119
1112
|
} else {
|
|
1120
1113
|
// For other fields, check input/select value
|
|
1121
1114
|
const inputs = document.querySelectorAll(`input[name="${fieldName}"], select[name="${fieldName}"]`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wiliot_certificate
|
|
3
|
-
Version: 4.5.
|
|
3
|
+
Version: 4.5.0a4
|
|
4
4
|
Summary: A library for certifying Wiliot-compliant boards
|
|
5
5
|
Author-email: Wiliot <support@wiliot.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -55,7 +55,7 @@ wlt-cert
|
|
|
55
55
|
````
|
|
56
56
|
This tool is the default to test and certify your device.
|
|
57
57
|
It runs a setup wizard that walks you through the initialization steps before running the tests.
|
|
58
|
-
You'll need a [validation schema](https://community.wiliot.com/customers/s/article/Validation-Schema), tester device and custom broker json file (
|
|
58
|
+
You'll need a [validation schema](https://community.wiliot.com/customers/s/article/Validation-Schema), tester device and custom broker json file (more info [here](https://community.wiliot.com/customers/s/article/Wiliot-Certification)).
|
|
59
59
|
Once set up it opens a terminal and tests your device.
|
|
60
60
|
|
|
61
61
|
|
|
@@ -80,7 +80,7 @@ wlt-cert-reg
|
|
|
80
80
|
````
|
|
81
81
|
Certify the gateway registration process to Wiliot platform.
|
|
82
82
|
The gateway must use Wiliot production MQTT broker, and mustn't be registered to any account on Wiliot platform.
|
|
83
|
-
Use -h for details on the arguments
|
|
83
|
+
Use -h for details on the arguments (see [Registration](https://community.wiliot.com/customers/s/article/Wiliot-Certification) for more info).
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
## The following capabilities are not tested in this version
|