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.
Files changed (53) hide show
  1. certificate/cert_common.py +33 -16
  2. certificate/cert_config.py +3 -3
  3. certificate/cert_data_sim.py +12 -9
  4. certificate/cert_defines.py +6 -0
  5. certificate/cert_gw_sim.py +3 -3
  6. certificate/cert_mqtt.py +5 -4
  7. certificate/cert_results.py +42 -32
  8. certificate/cert_utils.py +9 -10
  9. certificate/certificate.py +7 -5
  10. certificate/certificate_cli.py +9 -12
  11. certificate/tests/cloud_connectivity/acl_test/acl_test.py +13 -15
  12. certificate/tests/cloud_connectivity/brg_ota_test/brg_ota_test.json +1 -1
  13. certificate/tests/cloud_connectivity/channel_scan_behaviour_test/channel_scan_behaviour_test.py +2 -2
  14. certificate/tests/cloud_connectivity/connection_test/connection_test.py +4 -13
  15. certificate/tests/cloud_connectivity/deduplication_test/deduplication_test.py +1 -2
  16. certificate/tests/cloud_connectivity/ext_adv_stress_test/ext_adv_stress_test.py +12 -6
  17. certificate/tests/cloud_connectivity/registration_test/registration_test_cli.py +1 -1
  18. certificate/tests/cloud_connectivity/stress_test/stress_test.py +12 -7
  19. certificate/tests/cloud_connectivity/uplink_ext_adv_test/uplink_ext_adv_test.py +1 -2
  20. certificate/tests/cloud_connectivity/uplink_test/uplink_test.py +26 -20
  21. certificate/tests/datapath/event_ble5_test/event_ble5_test.json +1 -1
  22. certificate/tests/datapath/event_ble5_test/event_ble5_test.py +1 -9
  23. certificate/tests/datapath/event_test/event_test.json +1 -1
  24. certificate/tests/datapath/event_test/event_test.py +1 -7
  25. certificate/tests/datapath/rx_rate_gen2_test/rx_rate_gen2_test.py +1 -1
  26. certificate/tests/energy2400/signal_indicator_ble5_test/signal_indicator_ble5_test.py +3 -1
  27. certificate/tests/energy2400/signal_indicator_ext_adv_test/signal_indicator_ext_adv_test.py +2 -0
  28. certificate/tests/energy2400/signal_indicator_test/signal_indicator_test.py +1 -1
  29. common/api_if/api_validation.py +6 -0
  30. gui_certificate/server.py +151 -58
  31. gui_certificate/templates/cert_run.html +86 -93
  32. {wiliot_certificate-4.5.0a3.dist-info → wiliot_certificate-4.5.0a4.dist-info}/METADATA +3 -3
  33. {wiliot_certificate-4.5.0a3.dist-info → wiliot_certificate-4.5.0a4.dist-info}/RECORD +37 -53
  34. certificate/ag/wlt_types_ag_jsons/brg2brg_ota.json +0 -211
  35. certificate/ag/wlt_types_ag_jsons/brg2gw_hb.json +0 -894
  36. certificate/ag/wlt_types_ag_jsons/brg2gw_hb_sleep.json +0 -184
  37. certificate/ag/wlt_types_ag_jsons/calibration.json +0 -490
  38. certificate/ag/wlt_types_ag_jsons/custom.json +0 -614
  39. certificate/ag/wlt_types_ag_jsons/datapath.json +0 -900
  40. certificate/ag/wlt_types_ag_jsons/energy2400.json +0 -670
  41. certificate/ag/wlt_types_ag_jsons/energySub1g.json +0 -691
  42. certificate/ag/wlt_types_ag_jsons/externalSensor.json +0 -727
  43. certificate/ag/wlt_types_ag_jsons/interface.json +0 -1095
  44. certificate/ag/wlt_types_ag_jsons/powerManagement.json +0 -1439
  45. certificate/ag/wlt_types_ag_jsons/side_info_sensor.json +0 -105
  46. certificate/ag/wlt_types_ag_jsons/signal_indicator_data.json +0 -77
  47. certificate/ag/wlt_types_ag_jsons/unified_echo_ext_pkt.json +0 -126
  48. certificate/ag/wlt_types_ag_jsons/unified_echo_pkt.json +0 -175
  49. certificate/ag/wlt_types_ag_jsons/unified_sensor_pkt.json +0 -65
  50. {wiliot_certificate-4.5.0a3.dist-info → wiliot_certificate-4.5.0a4.dist-info}/WHEEL +0 -0
  51. {wiliot_certificate-4.5.0a3.dist-info → wiliot_certificate-4.5.0a4.dist-info}/entry_points.txt +0 -0
  52. {wiliot_certificate-4.5.0a3.dist-info → wiliot_certificate-4.5.0a4.dist-info}/licenses/LICENSE +0 -0
  53. {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
- from flask import Flask, render_template, request, session, redirect, url_for, flash, send_file, jsonify
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 file upload
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
- if custom_broker_file_key in request.files:
647
- file = request.files[custom_broker_file_key]
648
- if file and file.filename and allowed_file(file.filename):
649
- filename = secure_filename(file.filename)
650
- timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
651
- filename = f"{timestamp}_{filename}"
652
- filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
653
- file.save(filepath)
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 file upload
719
+ # Special handling for custom_broker - check if all required broker fields are filled
692
720
  if field['name'] == 'custom_broker':
693
- # Check if file was uploaded or path exists in session
694
- custom_broker_file_key = f"{field_name}_file"
695
- if custom_broker_file_key not in request.files or not request.files[custom_broker_file_key].filename:
696
- # Check if path exists in form_data or session
697
- if not field_value and not session.get('custom_broker_path'):
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
- is_certified, _, _, _, _ = check_certification_status(flavor, module_dicts, selected_tests, tests_schema, schema_data)
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': session.get('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
- session['custom_broker_path'] = config_data.get('custom_broker_path')
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
- flash(f"Warning: Custom broker file not found at {session['custom_broker_path']}. Please re-upload it in step 4.", "warning")
1110
- if session.get('current_step', 0) < 4:
1111
- session['current_step'] = 4
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&#10;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">Some mandatory modules or tests are missing</small>
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 file upload
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 fileInput = document.querySelector(`input[name="${fieldName}_file"]`);
1110
- if (fileInput && fileInput.files && fileInput.files.length > 0) {
1111
- fieldValue = fileInput.files[0].name;
1112
- } else {
1113
- // Check if there's a current value displayed
1114
- const currentValueDiv = fileInput?.closest('.col-sm-4')?.querySelector('.form-text');
1115
- if (currentValueDiv) {
1116
- fieldValue = 'exists';
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.0a3
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 ([more info here](https://community.wiliot.com/customers/s/article/Wiliot-Gateway-Certification)).
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. [More info here](https://community.wiliot.com/customers/s/article/Wiliot-Gateway-Certification)
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