Rebase private OTA libs on 2.7

This commit is contained in:
Pim van Pelt
2018-10-28 12:12:24 +01:00
parent 497c62b382
commit 9ee393bc8a
29 changed files with 3836 additions and 521 deletions

View File

@ -0,0 +1,14 @@
Copyright (c) 2018 Cesanta Software Limited
All rights reserved
Licensed under the Apache License, Version 2.0 (the ""License"");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an ""AS IS"" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,89 @@
# RPC Service - OTA (Over The Air updates)
This service provides an ability to manage OTA on devices remotely.
It is possible to call this service programmatically via serial, HTTP/RESTful,
Websocket, MQTT or other transports
(see [RPC section](/docs/mos/userguide/rpc.md)) or via the `mos` tool.
See in-depth description of our OTA mechanism at
[Updating firmware reliably - embedded.com](http://www.embedded.com/design/prototyping-and-development/4443082/Updating-firmware-reliably).
See OTA video tutorial:
<iframe width="560" src="https://www.youtube.com/embed/o05sBDfaFO8"
height="315" align="center" frameborder="0" allowfullscreen></iframe>
Below is a list of exported RPC methods and arguments:
## OTA.Update
Trigger OTA firmware update. Arguments:
```javascript
{
"url": "https://foo.com/fw123.zip", // Required. URL to the new firmware.
"commit_timeout": "300" // Optional. Time frame in seconds to do
// OTA.Commit after reboot. If commit is
// not done during the timeout, OTA rolls back.
}
```
A new firmware gets downloaded to the separate flash partition,
and is marked dirty. When the download is complete, device is rebooted.
After reboot, a firmware partition could become committed by calling
`OTA.Commit` - in which case, it is marked as "good". Otherwise, a device
reboots back into the old firmware after the `commit_timeout` seconds.
Example usage:
<pre class="command-line language-bash" data-user="chris" data-host="localhost" data-output="2-100"><code>mos call OTA.Update '{"url": "http://1.2.3.4/fw.zip", "commit_timeout": 300}'</code></pre>
## OTA.Commit
Commit current firmware. Arguments: none.
Example usage:
<pre class="command-line language-bash" data-user="chris" data-host="localhost" data-output="2-100"><code>mos call OTA.Commit</code></pre>
## OTA.Revert
Rolls back to the previous firmware. Arguments: none.
Example usage:
<pre class="command-line language-bash" data-user="chris" data-host="localhost" data-output="2-100"><code>mos call OTA.Revert</code></pre>
## OTA.CreateSnapshot
Create new firmware patition with the copy of currently running firmware. Arguments:
```javascript
{
// Optional. If true, then current firmware is uncommited, and needs to
// be explicitly commited after the first reboot. Otherwise, it'll reboot
// into the created snapshot. This option is useful if a dangerous, risky
// live update is to be done on the living device. Then, if the update
// fails and device bricks, it'll revert to the created good snapshot.
"set_as_revert": false,
// Optional. Same meaning as for OTA.Update
"commit_timeout": "300"
}
```
Example usage:
<pre class="command-line language-bash" data-user="chris" data-host="localhost" data-output="2-100"><code>mos call OTA.CreateSnapshot</code></pre>
## OTA.GetBootState
Get current boot state. Arguments: none.
Example usage:
<pre class="command-line language-bash" data-user="chris" data-host="localhost" data-output="2-100"><code>mos call OTA.GetBootState
{
"active_slot": 0, # Currently active flash partition.
"is_committed": true, # Current firmware is marked as "good" (committed).
"revert_slot": 0, # If uncommitted, slot to roll back to.
"commit_timeout": 0 # Commit timeout.
}</code></pre>
## OTA.SetBootState
Get current boot state. Arguments: see `OTA.GetBootState` reply section.
Example usage:
<pre class="command-line language-bash" data-user="chris" data-host="localhost" data-output="2-100"><code>mos call OTA.SetBootState '{"revert_slot": 1}'</code></pre>

View File

@ -1,6 +1,18 @@
/*
* Copyright (c) 2016 Cesanta Software Limited
* Copyright (c) 2014-2018 Cesanta Software Limited
* All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the ""License"");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an ""AS IS"" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CS_FW_SRC_MGOS_UPDATER_MG_RPC_H_

View File

@ -1,17 +1,17 @@
author: mongoose-os
description: Support for Over-The-Air update via RPC
type: lib
version: 1.18
version: 1.0
manifest_version: 2017-09-29
sources:
- src
includes:
- include
libs:
- origin: https://github.com/mongoose-os-libs/ota-http-client
- origin: https://github.com/mongoose-os-libs/rpc-common
- origin: libs/ota-http-client
tags:
- c
- ota
- rpc
- updater
- docs:rpc:Service - OTA

View File

@ -1,13 +1,25 @@
/*
* Copyright (c) 2016 Cesanta Software Limited
* Copyright (c) 2014-2018 Cesanta Software Limited
* All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the ""License"");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an ""AS IS"" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "mgos_rpc_service_ota.h"
#include "mg_rpc.h"
#include "mgos_rpc.h"
#include "mgos_ota_http_client.h"
#include "mgos_rpc.h"
#include "common/cs_dbg.h"
#include "common/mg_str.h"
@ -17,23 +29,29 @@
static struct mg_rpc_request_info *s_update_req;
static void mg_rpc_updater_result(struct update_context *ctx) {
if (s_update_req == NULL) {
return;
static void ota_status_cb(int ev, void *ev_data, void *userdata) {
struct mgos_ota_status *s = (struct mgos_ota_status *) ev_data;
if (s_update_req == NULL) return;
if (s->state == MGOS_OTA_STATE_ERROR) {
mg_rpc_send_errorf(s_update_req, -1, s->msg);
s_update_req = NULL;
} else if (s->state == MGOS_OTA_STATE_SUCCESS) {
mg_rpc_send_responsef(s_update_req, "true");
s_update_req = NULL;
}
mg_rpc_send_errorf(s_update_req, (ctx->result > 0 ? 0 : -1), ctx->status_msg);
s_update_req = NULL;
(void) ev;
(void) userdata;
}
static void handle_update_req(struct mg_rpc_request_info *ri, void *cb_arg,
struct mg_rpc_frame_info *fi,
struct mg_str args) {
char * blob_url = NULL;
struct json_token url_tok = JSON_INVALID_TOKEN;
int commit_timeout = 0;
char *blob_url = NULL;
struct json_token url_tok = JSON_INVALID_TOKEN;
int timeout = 0, commit_timeout = 0;
struct update_context *ctx = NULL;
LOG(LL_DEBUG, ("Update request received: %.*s", (int)args.len, args.p));
LOG(LL_DEBUG, ("Update request received: %.*s", (int) args.len, args.p));
const char *reply = "Malformed request";
@ -41,13 +59,12 @@ static void handle_update_req(struct mg_rpc_request_info *ri, void *cb_arg,
goto clean;
}
json_scanf(args.p, args.len, ri->args_fmt, &url_tok, &commit_timeout);
json_scanf(args.p, args.len, ri->args_fmt, &url_tok, &timeout,
&commit_timeout);
if (url_tok.len == 0 || url_tok.type != JSON_TYPE_STRING) {
goto clean;
}
if (url_tok.len == 0 || url_tok.type != JSON_TYPE_STRING) goto clean;
LOG(LL_DEBUG, ("URL: %.*s commit_timeout: %d", url_tok.len, url_tok.ptr,
LOG(LL_DEBUG, ("URL: %.*s to: %d cto: %d", url_tok.len, url_tok.ptr, timeout,
commit_timeout));
/*
@ -63,28 +80,25 @@ static void handle_update_req(struct mg_rpc_request_info *ri, void *cb_arg,
memcpy(blob_url, url_tok.ptr, url_tok.len);
ctx = updater_context_create();
ctx = updater_context_create(timeout);
if (ctx == NULL) {
reply = "Failed to init updater";
goto clean;
}
ctx->fctx.commit_timeout = commit_timeout;
ctx->result_cb = mg_rpc_updater_result;
s_update_req = ri;
s_update_req = ri;
mgos_ota_http_start(ctx, blob_url);
free(blob_url);
return;
clean:
if (blob_url != NULL) {
free(blob_url);
}
if (blob_url != NULL) free(blob_url);
LOG(LL_ERROR, ("Failed to start update: %s", reply));
mg_rpc_send_errorf(ri, -1, reply);
ri = NULL;
(void)cb_arg;
(void)fi;
(void) cb_arg;
(void) fi;
}
static void handle_commit_req(struct mg_rpc_request_info *ri, void *cb_arg,
@ -96,9 +110,9 @@ static void handle_commit_req(struct mg_rpc_request_info *ri, void *cb_arg,
mg_rpc_send_errorf(ri, -1, NULL);
}
ri = NULL;
(void)cb_arg;
(void)fi;
(void)args;
(void) cb_arg;
(void) fi;
(void) args;
}
static void handle_revert_req(struct mg_rpc_request_info *ri, void *cb_arg,
@ -106,14 +120,14 @@ static void handle_revert_req(struct mg_rpc_request_info *ri, void *cb_arg,
struct mg_str args) {
if (mgos_upd_revert(false /* reboot */)) {
mg_rpc_send_responsef(ri, NULL);
mgos_system_restart_after(100);
mgos_system_restart_after(300);
} else {
mg_rpc_send_errorf(ri, -1, NULL);
}
ri = NULL;
(void)cb_arg;
(void)fi;
(void)args;
(void) cb_arg;
(void) fi;
(void) args;
}
static void handle_create_snapshot_req(struct mg_rpc_request_info *ri,
@ -121,32 +135,31 @@ static void handle_create_snapshot_req(struct mg_rpc_request_info *ri,
struct mg_rpc_frame_info *fi,
struct mg_str args) {
const char *err_msg = NULL;
int ret = -1;
int ret = -1;
if (mgos_upd_is_committed()) {
ret = mgos_upd_create_snapshot();
if (ret >= 0) {
bool set_as_revert = false;
int commit_timeout = -1;
bool set_as_revert = false;
int commit_timeout = -1;
json_scanf(args.p, args.len, ri->args_fmt, &set_as_revert,
&commit_timeout);
if (set_as_revert) {
struct mgos_upd_boot_state bs;
if (mgos_upd_boot_get_state(&bs)) {
bs.is_committed = false;
bs.revert_slot = ret;
bs.revert_slot = ret;
if (mgos_upd_boot_set_state(&bs)) {
if (commit_timeout >= 0 &&
!mgos_upd_set_commit_timeout(commit_timeout)) {
ret = -4;
ret = -4;
err_msg = "Failed to set commit timeout";
}
} else {
ret = -3;
ret = -3;
err_msg = "Failed to set boot state";
}
} else {
ret = -2;
ret = -2;
err_msg = "Failed to get boot state";
}
}
@ -154,7 +167,7 @@ static void handle_create_snapshot_req(struct mg_rpc_request_info *ri,
err_msg = "Failed to create snapshot";
}
} else {
ret = -1;
ret = -1;
err_msg = "Cannot create snapshots in uncommitted state";
}
if (ret >= 0) {
@ -162,8 +175,8 @@ static void handle_create_snapshot_req(struct mg_rpc_request_info *ri,
} else {
mg_rpc_send_errorf(ri, ret, err_msg);
}
(void)cb_arg;
(void)fi;
(void) cb_arg;
(void) fi;
}
static void handle_get_boot_state_req(struct mg_rpc_request_info *ri,
@ -171,7 +184,6 @@ static void handle_get_boot_state_req(struct mg_rpc_request_info *ri,
struct mg_rpc_frame_info *fi,
struct mg_str args) {
struct mgos_upd_boot_state bs;
if (!mgos_upd_boot_get_state(&bs)) {
mg_rpc_send_errorf(ri, -1, NULL);
} else {
@ -181,9 +193,9 @@ static void handle_get_boot_state_req(struct mg_rpc_request_info *ri,
bs.active_slot, bs.is_committed, bs.revert_slot,
mgos_upd_get_commit_timeout());
}
(void)cb_arg;
(void)fi;
(void)args;
(void) cb_arg;
(void) fi;
(void) args;
}
static void handle_set_boot_state_req(struct mg_rpc_request_info *ri,
@ -192,7 +204,6 @@ static void handle_set_boot_state_req(struct mg_rpc_request_info *ri,
struct mg_str args) {
int ret = 0;
struct mgos_upd_boot_state bs;
if (mgos_upd_boot_get_state(&bs)) {
int commit_timeout = -1;
if (json_scanf(args.p, args.len, ri->args_fmt, &bs.active_slot,
@ -212,28 +223,107 @@ static void handle_set_boot_state_req(struct mg_rpc_request_info *ri,
} else {
mg_rpc_send_errorf(ri, ret, NULL);
}
(void)cb_arg;
(void)fi;
(void) cb_arg;
(void) fi;
}
static void handle_status(struct mg_rpc_request_info *ri, void *cb_arg,
struct mg_rpc_frame_info *fi, struct mg_str args) {
struct mgos_ota_status s;
mgos_upd_get_status(&s);
mg_rpc_send_responsef(ri,
"{state: %d, message: %Q, is_committed: %B, "
"progress_percent: %d, commit_timeout: %d, partition: "
"%d}",
s.state, s.msg, s.is_committed, s.progress_percent,
s.commit_timeout, s.partition);
(void) args;
(void) cb_arg;
(void) fi;
}
static void handle_begin(struct mg_rpc_request_info *ri, void *cb_arg,
struct mg_rpc_frame_info *fi, struct mg_str args) {
int timeout = 0, commit_timeout = 0, size = 0;
json_scanf(args.p, args.len, ri->args_fmt, &timeout, &commit_timeout, &size);
struct update_context *ctx = updater_context_create(timeout);
if (ctx == NULL) {
mg_rpc_send_errorf(ri, -1, "Failed to init updater");
} else {
ctx->fctx.commit_timeout = commit_timeout;
ctx->zip_file_size = size;
mg_rpc_send_responsef(ri, NULL);
}
(void) cb_arg;
(void) fi;
}
static void handle_write(struct mg_rpc_request_info *ri, void *cb_arg,
struct mg_rpc_frame_info *fi, struct mg_str args) {
char *data = NULL;
int len;
struct update_context *ctx = updater_context_get_current();
if (ctx == NULL) {
mg_rpc_send_errorf(ri, -1, "Update not started");
return;
}
json_scanf(args.p, args.len, ri->args_fmt, &data, &len);
if (data == NULL) {
mg_rpc_send_errorf(ri, -1, "Data required");
} else if (updater_process(ctx, data, len) < 0) {
mg_rpc_send_errorf(ri, -1, "Write error: %s",
(ctx->status_msg ? ctx->status_msg : ""));
} else {
mg_rpc_send_responsef(ri, NULL);
}
free(data);
(void) cb_arg;
(void) fi;
}
static void handle_end(struct mg_rpc_request_info *ri, void *cb_arg,
struct mg_rpc_frame_info *fi, struct mg_str args) {
struct update_context *ctx = updater_context_get_current();
if (ctx == NULL) {
mg_rpc_send_errorf(ri, -1, "Update not started");
} else if (updater_finalize(ctx) < 0) {
mg_rpc_send_errorf(ri, -1, "Update finalize failed: %s", ctx->status_msg);
} else {
mgos_system_restart_after(500);
handle_status(ri, cb_arg, fi, args);
}
/* Finalized successfully or not, update is finished. */
if (ctx != NULL) {
updater_finish(ctx);
updater_context_free(ctx);
}
}
bool mgos_rpc_service_ota_init(void) {
struct mg_rpc *mg_rpc = mgos_rpc_get_global();
if (mg_rpc == NULL) {
return true;
}
mg_rpc_add_handler(mg_rpc, "OTA.Update", "{url: %T, commit_timeout: %d}",
if (mg_rpc == NULL) return true;
mg_rpc_add_handler(mg_rpc, "OTA.Update",
"{url: %T, timeout: %d, commit_timeout: %d}",
handle_update_req, NULL);
mg_rpc_add_handler(mg_rpc, "OTA.Commit", "", handle_commit_req, NULL);
mg_rpc_add_handler(mg_rpc, "OTA.Revert", "", handle_revert_req, NULL);
mg_rpc_add_handler(mg_rpc, "OTA.CreateSnapshot",
"{set_as_revert: %B, commit_timeout: %d}",
handle_create_snapshot_req, NULL);
mg_rpc_add_handler(mg_rpc, "OTA.Begin",
"{timeout: %d, commit_timeout: %d, size: %d}",
handle_begin, NULL);
mg_rpc_add_handler(mg_rpc, "OTA.Write", "{data: %V}", handle_write, NULL);
mg_rpc_add_handler(mg_rpc, "OTA.End", "", handle_end, NULL);
mg_rpc_add_handler(mg_rpc, "OTA.Status", "", handle_status, NULL);
mg_rpc_add_handler(mg_rpc, "OTA.GetBootState", "", handle_get_boot_state_req,
NULL);
mg_rpc_add_handler(mg_rpc, "OTA.SetBootState",
"{active_slot: %d, is_committed: %B, revert_slot: %d, "
"commit_timeout: %d}",
handle_set_boot_state_req, NULL);
mgos_event_add_handler(MGOS_EVENT_OTA_STATUS, ota_status_cb, NULL);
return true;
}