Skip to content

Commit 118336e

Browse files
author
Jamie C. Driver
committed
ota: optionally send extended reply data for each uploaded fw chunk
1 parent 8658676 commit 118336e

File tree

8 files changed

+103
-21
lines changed

8 files changed

+103
-21
lines changed

docs/index.rst

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -468,14 +468,16 @@ Request to initiate a firmware update passing the full firmware image.
468468
"fwsize": 926448,
469469
"cmpsize": 579204,
470470
"cmphash": <32 bytes>,
471-
"fwhash": <32 bytes>
471+
"fwhash": <32 bytes>,
472+
"extended_replies": false
472473
}
473474
}
474475
475476
* 'fwsize' is the total length of the final firmware when uncompressed.
476477
* 'cmpsize' is the length of the compressed firmware image which will be uploaded.
477478
* 'cmphash' is the sha256 hash of the compressed firmware image.
478479
* 'fwhash' is the sha256 hash of the final firmware image to be booted.
480+
* 'extended_replies' is optional, and if set enables rich progress replies (see ota_data_reply_).
479481
* NOTE: 'fwhash' is a new addition and is optional at this time, although it will become mandatory in a future release.
480482

481483
.. _ota_reply:
@@ -490,6 +492,7 @@ ota reply
490492
"result": true
491493
}
492494
495+
493496
After this reply is received, the compressed firmware is sent in chunks using 'ota_data' messages. The chunks can be any size up to the `JADE_OTA_MAX_CHUNK` limit (see get_version_info_request_).
494497

495498
.. _ota_delta_request:
@@ -508,14 +511,16 @@ Request to initiate a firmware update using a binary diff/patch to be applied on
508511
"fwsize": 926448,
509512
"patchsize": 987291,
510513
"cmpsize": 14006,
511-
"cmphash": <32 bytes>
514+
"cmphash": <32 bytes>,
515+
"extended_replies": false
512516
}
513517
}
514518
515519
* 'fwsize' is the total length of the final firmware when uncompressed.
516520
* 'patchsize' is the length of the patch when uncompressed.
517521
* 'cmpsize' is the length of the compressed firmware patch which will be uploaded.
518522
* 'cmphash' is the sha256 hash of the compressed firmware patch.
523+
* 'extended_replies' is optional, and if set enables rich progress replies (see ota_data_reply_).
519524

520525
.. _ota_delta_reply:
521526

@@ -549,6 +554,22 @@ ota_data reply
549554
"result": true
550555
}
551556
557+
or, if 'extended_replies' was set in the original request, and the Jade fw supports this:
558+
559+
.. code-block:: cbor
560+
561+
{
562+
"id": "48",
563+
"result": {
564+
"confirmed": true
565+
"progress": 58
566+
}
567+
}
568+
569+
* 'confirmed' is initially false, until the user verifies the OTA upload, then all subsequent replies will be true
570+
* 'progress' is the percentage complete value as shown on Jade (which is based on uncompressed final firmware written, so can differ from the proportion of compressed data uploaded)
571+
NOTE: 'extended_replies' is supported as of Jade fw 1.0.34
572+
552573
We then send the 'ota_complete' message to verify the OTA was successful (before the device reboots).
553574

554575
.. _ota_complete_request:

jade_ota.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ def get_bleid(jade):
248248
# final (uncompressed) firmware, the length of the uncompressed diff/patch
249249
# (if this is a patch to apply to the current running firmware), and whether
250250
# to apply the test mnemonic rather than using normal pinserver authentication.
251-
def ota(jade, fwcompressed, fwlength, fwhash, patchlen=None, pushmnemonic=False):
251+
def ota(jade, fwcompressed, fwlength, fwhash, patchlen, extended_replies, pushmnemonic):
252252
info = jade.get_version_info()
253253
logger.info(f'Running OTA on: {info}')
254254
has_pin = info['JADE_HAS_PIN']
@@ -273,10 +273,15 @@ def ota(jade, fwcompressed, fwlength, fwhash, patchlen=None, pushmnemonic=False)
273273
last_written = 0
274274

275275
# Callback to log progress
276-
def _log_progress(written, compressed_size):
276+
def _log_progress(written, compressed_size, extended_reply):
277277
nonlocal last_time
278278
nonlocal last_written
279279

280+
# For the purposes of this test we usually assume the hw supports
281+
# extended replies. Use --no-extended-reply if this is not the case
282+
# (eg. updating from older firmware).
283+
assert (extended_reply is not None) == (extended_replies is True)
284+
280285
current_time = time.time()
281286
secs = current_time - last_time
282287
total_secs = current_time - start_time
@@ -287,13 +292,18 @@ def _log_progress(written, compressed_size):
287292
secs_remaining = (compressed_size - written) / avg_rate
288293
template = '{0:.2f} b/s - progress {1:.2f}% - {2:.2f} seconds left'
289294
logger.info(template.format(last_rate, progress, secs_remaining))
290-
logger.info('Written {0}b in {1:.2f}s'.format(written, total_secs))
295+
logger.info(f'Written {written}b in {total_secs}s')
296+
if extended_reply:
297+
logger.info(f'Confirmed: {extended_reply["confirmed"]}, ' +
298+
f'Write progress: {extended_reply["progress"]}%')
291299

292300
last_time = current_time
293301
last_written = written
294302

295303
result = jade.ota_update(fwcompressed, fwlength, chunksize, fwhash,
296-
patchlen=patchlen, cb=_log_progress, gcov_dump=info.get('GCOV', False))
304+
patchlen=patchlen, cb=_log_progress,
305+
extended_replies=extended_replies,
306+
gcov_dump=info.get('GCOV', False))
297307
assert result is True
298308

299309
logger.info(f'Total ota time in secs: {time.time() - start_time}')
@@ -396,6 +406,11 @@ def _log_progress(written, compressed_size):
396406
dest='pushmnemonic',
397407
help='Sets a test mnemonic - only works with debug build of Jade',
398408
default=False)
409+
parser.add_argument('--no-extended-replies',
410+
action='store_true',
411+
dest='noextendedreplies',
412+
help='Do not request extended-replies (for backward-compatability)',
413+
default=False)
399414
parser.add_argument('--log',
400415
action='store',
401416
dest='loglevel',
@@ -479,7 +494,10 @@ def _log_progress(written, compressed_size):
479494
if not args.skipserial:
480495
logger.info(f'Jade OTA over serial')
481496
with JadeAPI.create_serial(device=args.serialport) as jade:
482-
has_radio, bleid = ota(jade, fwcmp, fwlen, fwhash, patchlen, args.pushmnemonic)
497+
# By default serial uses extended-replies
498+
extended_replies = not args.noextendedreplies
499+
has_radio, bleid = ota(jade, fwcmp, fwlen, fwhash, patchlen,
500+
extended_replies, args.pushmnemonic)
483501

484502
if not args.skipble:
485503
if has_radio and bleid is None and args.bleidfromserial:
@@ -490,7 +508,10 @@ def _log_progress(written, compressed_size):
490508
if has_radio:
491509
logger.info(f'Jade OTA over BLE {bleid}')
492510
with JadeAPI.create_ble(serial_number=bleid) as jade:
493-
ota(jade, fwcmp, fwlen, fwhash, patchlen, args.pushmnemonic)
511+
# Do not use extended-replies for ble
512+
extended_replies = False
513+
ota(jade, fwcmp, fwlen, fwhash, patchlen, extended_replies,
514+
args.pushmnemonic)
494515
else:
495516
msg = 'Skipping BLE tests - not enabled on the hardware'
496517
logger.warning(msg)

jadepy/jade.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ def logout(self):
458458
return self._jadeRpc('logout')
459459

460460
def ota_update(self, fwcmp, fwlen, chunksize, fwhash=None, patchlen=None, cb=None,
461-
gcov_dump=False):
461+
extended_replies=False, gcov_dump=False):
462462
"""
463463
RPC call to attempt to update the unit's firmware.
464464
@@ -488,9 +488,15 @@ def ota_update(self, fwcmp, fwlen, chunksize, fwhash=None, patchlen=None, cb=Non
488488
cb : function, optional
489489
Callback function accepting two integers - the amount of compressed firmware sent thus
490490
far, and the total length of the compressed firmware to send.
491+
If 'extended_replies' was set, this callback is also passed the extended data included
492+
in the replies, if not this is None.
491493
If passed, this function is invoked each time a fw chunk is successfully uploaded and
492494
ack'd by the hw, to notify of upload progress.
493495
Defaults to None, and nothing is called to report upload progress.
496+
extended_replies: bool, optional
497+
If set Jade may return addtional progress data with each data chunk uploaded, which is
498+
then passed to any progress callback as above. If not no additional data is returned
499+
or passed.
494500
495501
Returns
496502
-------
@@ -509,7 +515,8 @@ def ota_update(self, fwcmp, fwlen, chunksize, fwhash=None, patchlen=None, cb=Non
509515
ota_method = 'ota'
510516
params = {'fwsize': fwlen,
511517
'cmpsize': cmplen,
512-
'cmphash': cmphash}
518+
'cmphash': cmphash,
519+
'extended_replies': extended_replies}
513520

514521
if fwhash is not None:
515522
params['fwhash'] = fwhash
@@ -528,11 +535,13 @@ def ota_update(self, fwcmp, fwlen, chunksize, fwhash=None, patchlen=None, cb=Non
528535
length = min(remaining, chunksize)
529536
chunk = bytes(fwcmp[written:written + length])
530537
result = self._jadeRpc('ota_data', chunk)
531-
assert result is True
532538
written += length
533539

540+
have_extended_reply = isinstance(result, dict)
541+
assert result is True or (extended_replies and have_extended_reply)
542+
534543
if (cb):
535-
cb(written, cmplen)
544+
cb(written, cmplen, result if have_extended_reply else None)
536545

537546
if gcov_dump:
538547
self.run_remote_gcov_dump()

main/process/ota.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ void ota_process(void* process_ptr)
108108
goto cleanup;
109109
}
110110

111+
// Optional field indicating preference for rich reply data
112+
bool extended_replies = false;
113+
rpc_get_boolean("extended_replies", &params, &extended_replies);
114+
111115
// Can accept either uploaded file data hash (legacy) or hash of the full/final firmware image (preferred)
112116
uint8_t expected_hash[SHA256_LEN];
113117
char* expected_hash_hexstr = NULL;
@@ -151,6 +155,7 @@ void ota_process(void* process_ptr)
151155
.firmwaresize = firmwaresize,
152156
.expected_hash_hexstr = expected_hash_hexstr,
153157
.expected_hash = expected_hash,
158+
.extended_replies = extended_replies,
154159
};
155160

156161
if (!ota_init(&joctx)) {

main/process/ota_delta.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,10 @@ void ota_delta_process(void* process_ptr)
179179
goto cleanup;
180180
}
181181

182+
// Optional field indicating preference for rich reply data
183+
bool extended_replies = false;
184+
rpc_get_boolean("extended_replies", &params, &extended_replies);
185+
182186
// Can accept either uploaded file data hash (legacy) or hash of the full/final firmware image (preferred)
183187
uint8_t expected_hash[SHA256_LEN];
184188
char* expected_hash_hexstr = NULL;
@@ -223,6 +227,7 @@ void ota_delta_process(void* process_ptr)
223227
.compressedsize = compressedsize,
224228
.expected_hash_hexstr = expected_hash_hexstr,
225229
.expected_hash = expected_hash,
230+
.extended_replies = extended_replies,
226231
};
227232

228233
int ret = deflate_init_read_uncompressed(dctx, compressedsize, compressed_stream_reader, &joctx);

main/process/ota_util.c

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,30 @@ extern esp_app_desc_t running_app_info;
2020
const __attribute__((section(".rodata_custom_desc"))) esp_custom_app_desc_t custom_app_desc
2121
= { .version = 1, .board_type = JADE_OTA_BOARD_TYPE, .features = JADE_OTA_FEATURES, .config = JADE_OTA_CONFIG };
2222

23-
static void send_ok(const char* id, const jade_msg_source_t source)
23+
static void reply_ok(const void* ctx, CborEncoder* container)
2424
{
25-
JADE_ASSERT(id);
25+
JADE_ASSERT(ctx);
2626

27-
uint8_t ok_msg[MAXLEN_ID + 10];
28-
bool ok = true;
29-
jade_process_reply_to_message_result_with_id(id, ok_msg, sizeof(ok_msg), source, &ok, cbor_result_boolean_cb);
27+
const jade_ota_ctx_t* joctx = (const jade_ota_ctx_t*)ctx;
28+
29+
if (joctx->extended_replies) {
30+
// Extended/structured response
31+
JADE_LOGI("Sending extended reply ok for %s", joctx->id);
32+
CborEncoder map_encoder; // result data
33+
CborError cberr = cbor_encoder_create_map(container, &map_encoder, 2);
34+
JADE_ASSERT(cberr == CborNoError);
35+
36+
add_boolean_to_map(&map_encoder, "confirmed", *joctx->validated_confirmed);
37+
add_uint_to_map(&map_encoder, "progress", joctx->progress_bar.percent_last_value);
38+
39+
cberr = cbor_encoder_close_container(container, &map_encoder);
40+
JADE_ASSERT(cberr == CborNoError);
41+
} else {
42+
// Simple 'true' response
43+
JADE_LOGI("Sending simple ok for %s", joctx->id);
44+
const CborError cberr = cbor_encode_boolean(container, true);
45+
JADE_ASSERT(cberr == CborNoError);
46+
}
3047
}
3148

3249
void handle_in_bin_data(void* ctx, uint8_t* data, const size_t rawsize)
@@ -101,8 +118,9 @@ void handle_in_bin_data(void* ctx, uint8_t* data, const size_t rawsize)
101118
joctx->uncompressedsize - *joctx->remaining_uncompressed);
102119

103120
// Send ack after all processing - see comment above.
104-
JADE_LOGI("Sending ok for %s", joctx->id);
105-
send_ok(joctx->id, *joctx->expected_source);
121+
uint8_t reply_msg[64];
122+
jade_process_reply_to_message_result_with_id(
123+
joctx->id, reply_msg, sizeof(reply_msg), *joctx->expected_source, joctx, reply_ok);
106124

107125
// Blank out the current msg id once 'ok' is sent for it
108126
joctx->id[0] = '\0';

main/process/ota_util.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ typedef struct {
3939
mbedtls_sha256_context* sha_ctx;
4040
hash_type_t hash_type;
4141
char* id;
42+
bool extended_replies;
4243
const uint8_t* expected_hash;
4344
const char* expected_hash_hexstr;
4445
const esp_partition_t* running_partition;

update_jade_fw.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,12 @@ def ota(jade, verinfo, fwcompressed, fwlength, fwhash, patchlen=None):
176176
last_written = 0
177177

178178
# Callback to log progress
179-
def _log_progress(written, compressed_size):
179+
def _log_progress(written, compressed_size, extended_reply):
180180
nonlocal last_time
181181
nonlocal last_written
182182

183+
assert extended_reply is None
184+
183185
current_time = time.time()
184186
secs = current_time - last_time
185187
total_secs = current_time - start_time
@@ -198,7 +200,7 @@ def _log_progress(written, compressed_size):
198200
print('Please approve the firmware update on the Jade device')
199201
try:
200202
result = jade.ota_update(fwcompressed, fwlength, chunksize, fwhash,
201-
patchlen=patchlen, cb=_log_progress)
203+
patchlen=patchlen, cb=_log_progress, extended_replies=False)
202204
assert result is True
203205
print(f'Total OTA time: {time.time() - start_time}s')
204206
except JadeError as err:

0 commit comments

Comments
 (0)