Rebase private OTA libs on 2.7
This commit is contained in:
14
libs/rpc-service-ota/LICENSE
Normal file
14
libs/rpc-service-ota/LICENSE
Normal 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.
|
89
libs/rpc-service-ota/README.md
Normal file
89
libs/rpc-service-ota/README.md
Normal 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>
|
@ -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_
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user