Skip to content

Commit ab374f7

Browse files
committed
actions: Add board provisioning testing
Run provisioning tests on schedule. Signed-off-by: Jorgen Kvalvaag <[email protected]>
1 parent 55dd378 commit ab374f7

5 files changed

Lines changed: 110 additions & 48 deletions

File tree

.github/workflows/build-and-target-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
PUSH=${{ github.event_name == 'push' }}
3737
push_memory_badges=false
3838
if [[ $SCHEDULED == true ]]; then
39-
devices='["thingy91x","nrf9151dk","ppk_thingy91x","gnss_nrf9151dk"]'
39+
devices='["thingy91x","nrf9151dk","ppk_thingy91x","gnss_nrf9151dk", "prov_thingy91x"]'
4040
build_all=true
4141
push_memory_badges=true
4242
elif [[ $PUSH == true ]]; then

app/prj.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,5 @@ CONFIG_NRF_PROVISIONING_RX_BUF_SZ=4096
244244
CONFIG_NRF_PROVISIONING_TX_BUF_SZ=4096
245245
CONFIG_NRF_PROVISIONING_CODEC_AT_CMD_LEN=2048
246246
CONFIG_NRF_PROVISIONING_CODEC_RX_SZ_START=2048
247+
248+
CONFIG_NRF_PROVISIONING_COAP_TIMEOUT_SECONDS=6

tests/on_target/tests/conftest.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@
2323
DEVICE_UUID = os.getenv('UUID')
2424
NRFCLOUD_API_KEY = os.getenv('NRFCLOUD_API_KEY')
2525
DUT_DEVICE_TYPE = os.getenv('DUT_DEVICE_TYPE')
26-
if DUT_DEVICE_TYPE == "ppk_thingy91x":
27-
DUT_DEVICE_TYPE = "thingy91x"
28-
2926

3027
def get_uarts():
3128
base_path = "/dev/serial/by-id"

tests/on_target/tests/test_provisioning/test_provisioning.py

Lines changed: 104 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import os
22
import sys
33
import json
4-
import requests.exceptions # Used for handling HTTP errors from nRF Cloud API
4+
import time
5+
import pytest
6+
import requests.exceptions # Used for handling HTTP errors from nRF Cloud API
57

68
# Ensure the utils directory is in the Python path
79
sys.path.append(os.getcwd())
@@ -16,6 +18,7 @@
1618

1719
# --- Helper Functions ---
1820

21+
1922
def _perform_initial_device_setup_and_factory_reset(dut_cloud, hex_file: str):
2023
logger.info(f"Flashing device with {hex_file} and performing factory reset.")
2124

@@ -24,40 +27,57 @@ def _perform_initial_device_setup_and_factory_reset(dut_cloud, hex_file: str):
2427
dut_cloud.uart.flush()
2528
reset_device()
2629

30+
2731
def _wait_for_lte_connection(dut_cloud, timeout: int = 240):
2832
logger.info("Waiting for device to connect to LTE network...")
2933

3034
log_pattern_network_connected = "network: Network connectivity established"
31-
dut_cloud.uart.wait_for_str(log_pattern_network_connected, timeout=timeout)
35+
dut_cloud.uart.wait_for_str(
36+
log_pattern_network_connected,
37+
timeout=timeout,
38+
start_pos=dut_cloud.uart.get_size(),
39+
)
3240

3341
logger.info("Device connected to LTE network.")
3442

43+
3544
def _disconnect_network_and_clear_modem_credentials(dut_cloud, sec_tag: int):
3645
logger.info("Disconnecting network and clearing modem credentials...")
3746

3847
log_pattern_network_disconnected = "network: Network connectivity lost"
3948
dut_cloud.uart.write("att_network disconnect\r\n")
40-
dut_cloud.uart.wait_for_str(log_pattern_network_disconnected, timeout=20)
49+
dut_cloud.uart.wait_for_str(
50+
log_pattern_network_disconnected,
51+
timeout=20,
52+
start_pos=dut_cloud.uart.get_size(),
53+
)
4154

4255
# Clear any existing credentials for the given security tag
4356
dut_cloud.uart.write(f"at AT%CMNG=3,{sec_tag},0\r\n")
57+
time.sleep(1)
4458
dut_cloud.uart.write(f"at AT%CMNG=3,{sec_tag},1\r\n")
59+
time.sleep(1)
4560
dut_cloud.uart.write(f"at AT%CMNG=3,{sec_tag},2\r\n")
61+
time.sleep(1)
4662

4763
logger.info("Modem credentials cleared.")
4864

65+
4966
def _get_attestation_token_from_device(dut_cloud) -> str:
5067
logger.info("Getting attestation token from device...")
5168

5269
dut_cloud.uart.at_cmd_write("at AT%ATTESTTOKEN\r\n")
53-
token_match = dut_cloud.uart.wait_for_str_re(r'%ATTESTTOKEN: "([^"]+)"', timeout=20)
70+
token_match = dut_cloud.uart.wait_for_str_re(
71+
r'%ATTESTTOKEN: "([^"]+)"', timeout=20
72+
)
5473

5574
assert token_match, "No attestation token found"
5675
attestation_token = token_match[0]
5776
logger.info(f"Attestation Token: {attestation_token}")
5877

5978
return attestation_token
6079

80+
6181
def _unclaim_device_from_nrf_cloud_if_exists(dut_cloud):
6282
"""
6383
Attempts to unclaim the device from nRF Cloud.
@@ -70,42 +90,74 @@ def _unclaim_device_from_nrf_cloud_if_exists(dut_cloud):
7090
logger.info(f"Unclaim device status_code: {status_code}")
7191
except requests.exceptions.HTTPError as e:
7292
if e.response.status_code == 404:
73-
logger.info(f"Device {dut_cloud.device_id} not found or already unclaimed (404), proceeding.")
93+
logger.info(
94+
f"Device {dut_cloud.device_id} not found or already unclaimed (404), proceeding."
95+
)
7496
else:
7597
logger.error(f"Error unclaiming device: {e}")
76-
raise # Re-raise other HTTP errors
98+
raise # Re-raise other HTTP errors
99+
77100

78101
def _connect_to_network_and_wait_for_claiming_prompt(dut_cloud):
79102
logger.info("Connecting to network and waiting for device to request claiming...")
80103

81104
log_pattern_network_connected = "network: Network connectivity established"
82-
log_pattern_need_claiming = "Claim the device using the device's attestation token on nrfcloud.com"
105+
log_pattern_need_claiming = (
106+
"Claim the device using the device's attestation token on nrfcloud.com"
107+
)
83108

84109
dut_cloud.uart.write("att_network connect\r\n")
85-
dut_cloud.uart.wait_for_str(log_pattern_network_connected, timeout=240)
86-
dut_cloud.uart.wait_for_str(log_pattern_need_claiming, timeout=240)
110+
dut_cloud.uart.wait_for_str(
111+
log_pattern_network_connected, timeout=240, start_pos=dut_cloud.uart.get_size()
112+
)
113+
dut_cloud.uart.wait_for_str(
114+
log_pattern_need_claiming, timeout=240, start_pos=dut_cloud.uart.get_size()
115+
)
87116

88117
logger.info("Device is ready to be claimed.")
89118

119+
90120
def _claim_device_on_nrf_cloud(dut_cloud, attestation_token: str):
91121
logger.info("Claiming device on nRF Cloud...")
92122

93123
dut_cloud.cloud.claim_device(attestation_token=attestation_token)
94124

95125
logger.info("Device claimed successfully.")
96126

97-
def _wait_for_provisioning_completion_and_cloud_connection(dut_cloud, timeout: int = 240):
98-
logger.info("Waiting for provisioning to complete and device to connect to nRF Cloud...")
99127

100-
dut_cloud.uart.wait_for_str("cloud: Provisioning finished", timeout=timeout)
101-
dut_cloud.uart.wait_for_str("cloud: Connected to Cloud", timeout=timeout)
128+
def _wait_for_provisioning_completion_and_cloud_connection(
129+
dut_cloud, timeout: int = 240
130+
):
131+
logger.info(
132+
"Waiting for provisioning to complete and device to connect to nRF Cloud..."
133+
)
134+
135+
dut_cloud.uart.wait_for_str(
136+
[
137+
"cloud: nrf_provisioning_callback: Provisioning finished",
138+
"cloud: Connected to Cloud",
139+
],
140+
timeout=timeout,
141+
start_pos=dut_cloud.uart.get_size(),
142+
)
102143

103144
logger.info("Device provisioned and connected to nRF Cloud.")
104145

105-
def _verify_device_location_data(dut_cloud, expected_lat: float = 61.5, expected_lon: float = 10.5, lat_tolerance: float = 2.0, lon_tolerance: float = 1.0, timeout: int = 300):
146+
147+
def _verify_device_location_data(
148+
dut_cloud,
149+
expected_lat: float = 61.5,
150+
expected_lon: float = 10.5,
151+
lat_tolerance: float = 2.0,
152+
lon_tolerance: float = 1.0,
153+
timeout: int = 300,
154+
):
106155
logger.info("Verifying device location data...")
107156

108-
values = dut_cloud.uart.wait_for_str_re(r'location_event_handler: Got location: lat: ([\d.-]+), lon: ([\d.-]+), acc: ([\d.-]+), method:', timeout=timeout)
157+
values = dut_cloud.uart.wait_for_str_re(
158+
r"location_event_handler: Got location: lat: ([\d.-]+), lon: ([\d.-]+), acc: ([\d.-]+),method:",
159+
timeout=timeout
160+
)
109161
assert values, "Failed to get location data from device."
110162

111163
lat_str, lon_str, acc_str = values
@@ -117,6 +169,7 @@ def _verify_device_location_data(dut_cloud, expected_lat: float = 61.5, expected
117169

118170
logger.info("Device location data verified.")
119171

172+
120173
def _trigger_device_reprovisioning_with_new_credentials(dut_cloud, sec_tag: int):
121174
"""
122175
Initiates the reprovisioning process on the device by:
@@ -127,26 +180,25 @@ def _trigger_device_reprovisioning_with_new_credentials(dut_cloud, sec_tag: int)
127180
logger.info("Starting reprovisioning process with new credentials...")
128181

129182
# Prepare the reprovisioning command for nRF Cloud
130-
command_json = json.dumps({
131-
"description": "Reprovisioning with new cloud credentials",
132-
"request": {
133-
"cloudAccessKeyGeneration": {
134-
"secTag": sec_tag
135-
}
183+
command_json = json.dumps(
184+
{
185+
"description": "Reprovisioning with new cloud credentials",
186+
"request": {"cloudAccessKeyGeneration": {"secTag": sec_tag}},
136187
}
137-
})
188+
)
138189

139190
logger.info(f"Adding reprovisioning command to nRF Cloud: {command_json}")
140191

141192
dut_cloud.cloud.add_provisioning_command(
142-
device_id=dut_cloud.device_id,
143-
command=command_json
193+
device_id=dut_cloud.device_id, command=command_json
144194
)
145195

146196
# Update device shadow to indicate a new provisioning command is available
147197
logger.info("Updating device shadow to trigger reprovisioning command processing.")
148198

149-
dut_cloud.cloud.patch_delete_command_entry_from_shadow(device_id=dut_cloud.device_id)
199+
dut_cloud.cloud.patch_delete_command_entry_from_shadow(
200+
device_id=dut_cloud.device_id
201+
)
150202
dut_cloud.cloud.patch_add_provisioning_command_to_shadow(
151203
device_id=dut_cloud.device_id,
152204
command=1, # Command ID 1 signifies a provisioning request
@@ -157,6 +209,7 @@ def _trigger_device_reprovisioning_with_new_credentials(dut_cloud, sec_tag: int)
157209

158210
dut_cloud.uart.write("att_button_press 1\r\n")
159211

212+
160213
def _trigger_device_reprovisioning_expecting_no_commands(dut_cloud):
161214
"""
162215
Initiates a reprovisioning check on the device when no new commands are expected from nRF Cloud.
@@ -167,7 +220,9 @@ def _trigger_device_reprovisioning_expecting_no_commands(dut_cloud):
167220
# Update device shadow to indicate a provisioning check (even if no commands are queued)
168221
logger.info("Updating device shadow to trigger provisioning check.")
169222

170-
dut_cloud.cloud.patch_delete_command_entry_from_shadow(device_id=dut_cloud.device_id)
223+
dut_cloud.cloud.patch_delete_command_entry_from_shadow(
224+
device_id=dut_cloud.device_id
225+
)
171226
dut_cloud.cloud.patch_add_provisioning_command_to_shadow(
172227
device_id=dut_cloud.device_id,
173228
command=1, # Command ID 1 typically signifies a provisioning request
@@ -178,8 +233,10 @@ def _trigger_device_reprovisioning_expecting_no_commands(dut_cloud):
178233

179234
dut_cloud.uart.write("att_button_press 1\r\n")
180235

236+
181237
# --- Test Phases ---
182238

239+
183240
def _run_initial_provisioning(dut_cloud, hex_file):
184241
logger.info("--- Starting Phase 1: Initial Device Provisioning ---")
185242

@@ -195,6 +252,7 @@ def _run_initial_provisioning(dut_cloud, hex_file):
195252

196253
logger.info("--- Phase 1: Initial Device Provisioning Completed Successfully ---")
197254

255+
198256
def _run_reprovisioning(dut_cloud):
199257
logger.info("--- Starting Phase 2: Reprovisioning with New Credentials ---")
200258

@@ -205,7 +263,10 @@ def _run_reprovisioning(dut_cloud):
205263
_wait_for_provisioning_completion_and_cloud_connection(dut_cloud, timeout=300)
206264
_verify_device_location_data(dut_cloud)
207265

208-
logger.info("--- Phase 2: Reprovisioning with New Credentials Completed Successfully ---")
266+
logger.info(
267+
"--- Phase 2: Reprovisioning with New Credentials Completed Successfully ---"
268+
)
269+
209270

210271
def _run_reprovisioning_expecting_no_commands(dut_cloud):
211272
logger.info("--- Starting Phase 3: Reprovisioning Expecting No Commands ---")
@@ -214,14 +275,22 @@ def _run_reprovisioning_expecting_no_commands(dut_cloud):
214275

215276
_trigger_device_reprovisioning_expecting_no_commands(dut_cloud)
216277
# Verify the device correctly handles the absence of new provisioning commands
217-
dut_cloud.uart.wait_for_str("cloud: No commands from the nRF Provisioning Service to process", timeout=300)
218-
dut_cloud.uart.wait_for_str("cloud: Connected to Cloud", timeout=240) # Ensure it reconnects
278+
dut_cloud.uart.wait_for_str(
279+
[
280+
"cloud: No commands from the nRF Provisioning Service to process",
281+
"cloud: Connected to Cloud",
282+
],
283+
timeout=300,
284+
start_pos=dut_cloud.uart.get_size(),
285+
)
286+
logger.info(
287+
"--- Phase 3: Reprovisioning Expecting No Commands Completed Successfully ---"
288+
)
219289

220-
logger.info("--- Phase 3: Reprovisioning Expecting No Commands Completed Successfully ---")
221290

222291
# --- Main Test ---
223-
224-
def test_device_provisioning(dut_cloud, hex_file):
292+
@pytest.mark.parametrize("_", range(1, 3))
293+
def test_device_provisioning(_, dut_cloud, hex_file):
225294
"""
226295
Tests the full device provisioning and reprovisioning lifecycle:
227296
1. Initial provisioning: Flashes, gets attestation token, claims on nRF Cloud, connects.
@@ -231,12 +300,6 @@ def test_device_provisioning(dut_cloud, hex_file):
231300
connects to nRF Cloud Provisioning Service, but finds no new commands to process and establishes
232301
a connection without reprovisioning.
233302
"""
234-
235-
for i in range(1, 21):
236-
logger.info(f"--- Starting Test Iteration: {i}/20 ---")
237-
_run_initial_provisioning(dut_cloud, hex_file)
238-
_run_reprovisioning(dut_cloud)
239-
_run_reprovisioning_expecting_no_commands(dut_cloud)
240-
logger.info(f"--- Completed Test Iteration: {i}/20 ---")
241-
242-
logger.info("Device provisioning and reprovisioning test completed successfully.")
303+
_run_initial_provisioning(dut_cloud, hex_file)
304+
_run_reprovisioning(dut_cloud)
305+
_run_reprovisioning_expecting_no_commands(dut_cloud)

tests/on_target/utils/logger.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
def get_logger(log_level = LOG_LEVEL):
2323
caller = inspect.stack()[1]
24-
filename = caller.filename
24+
filename = caller.filename.split(".")[0]
2525

2626
# Global Logging Function
2727
logger = logging.getLogger(filename)
@@ -34,7 +34,7 @@ def get_logger(log_level = LOG_LEVEL):
3434
console.setLevel(log_level)
3535
logger.addHandler(console)
3636

37-
formatter = '%(asctime)s - %(filename)s - %(levelname)s - %(message)s'
37+
formatter = '%(asctime)s:%(filename)s:%(levelname)s:%(message)s'
3838

3939
formatter = ColoredFormatter(formatter, datefmt='%H:%M:%S')
4040
console.setFormatter(formatter)
@@ -45,7 +45,7 @@ def get_logger(log_level = LOG_LEVEL):
4545
file_handler = logging.FileHandler(f"{LOG_DIR}/{LOG_FILENAME}_{level}.txt")
4646
file_handler.setLevel(level.upper())
4747
logger.addHandler(file_handler)
48-
formatter = '%(asctime)s - %(filename)s - %(levelname)s - %(message)s'
48+
formatter = '%(asctime)s:%(filename)s:%(levelname)s:%(message)s'
4949
file_handler.setFormatter(logging.Formatter(formatter, datefmt='%Y-%m-%d %H:%M:%S'))
5050

5151
logger.setLevel(logging.DEBUG)

0 commit comments

Comments
 (0)