From de7840be25ee089eb3ca7e4695da2331214e8e89 Mon Sep 17 00:00:00 2001 From: Deomid Ryabkov Date: Fri, 14 Dec 2018 22:43:21 +0000 Subject: [PATCH] Refactored BT GATTS API, added mJS bindings All the ESP32 specifics are hidden in the implementation, new API is generic and is much easier to use: * Prepared writes are handled by mos, user doesn't need to care about reassembling the write. * Notification handling is much simpler - CCCD management is handled by mos, user gets simple "notifications on/off" event. * ESP32 code cleaned up, all processing moved to main task. Updated existing GATTS service implementations to use it. CL: Refactored BT GATTS API, added mJS bindings PUBLISHED_FROM=7f1a63102d207b68baf1f50b6b9e7fd7c175f35b --- mos.yml | 4 + mos_esp32.yml | 2 - src/esp32/esp32_bt_svc_config.c | 343 -------------------------------- src/mgos_bt_svc_config.c | 295 +++++++++++++++++++++++++++ 4 files changed, 299 insertions(+), 345 deletions(-) delete mode 100644 mos_esp32.yml delete mode 100644 src/esp32/esp32_bt_svc_config.c create mode 100644 src/mgos_bt_svc_config.c diff --git a/mos.yml b/mos.yml index 0cb0955..a76a4a8 100644 --- a/mos.yml +++ b/mos.yml @@ -3,11 +3,15 @@ description: Configuration over Bluetooth GATT service support type: lib version: 1.0 +sources: + - src + libs: - origin: https://github.com/mongoose-os-libs/bt-common config_schema: - ["bt.config_svc_enable", "b", true, {title: "Enable the config service"}] + - ["bt.config_svc_sec_level", "i", 0, {title: "Minimum required security level"}] tags: - bt diff --git a/mos_esp32.yml b/mos_esp32.yml deleted file mode 100644 index eedd0d3..0000000 --- a/mos_esp32.yml +++ /dev/null @@ -1,2 +0,0 @@ -sources: - - src/esp32 diff --git a/src/esp32/esp32_bt_svc_config.c b/src/esp32/esp32_bt_svc_config.c deleted file mode 100644 index 6200c84..0000000 --- a/src/esp32/esp32_bt_svc_config.c +++ /dev/null @@ -1,343 +0,0 @@ -/* - * 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. - */ - -/* - * Interface to sys_config over BLE GATT service. - * See README.md for high-level description. - */ - -#include - -#include "common/cs_dbg.h" -#include "common/mbuf.h" -#include "common/mg_str.h" - -#include "mgos_config_util.h" -#include "mgos_hal.h" -#include "mgos_sys_config.h" -#include "mgos_utils.h" - -#include "esp32_bt_gatts.h" - -/* Note: UUIDs below are in reverse, because that's how ESP wants them. */ -static const esp_bt_uuid_t mos_cfg_svc_uuid = { - .len = ESP_UUID_LEN_128, - .uuid.uuid128 = - { - /* _mOS_CFG_SVC_ID_, 5f6d4f53-5f43-4647-5f53-56435f49445f */ - 0x5f, 0x44, 0x49, 0x5f, 0x43, 0x56, 0x53, 0x5f, 0x47, 0x46, 0x43, 0x5f, - 0x53, 0x4f, 0x6d, 0x5f, - }, -}; - -static const esp_bt_uuid_t mos_cfg_key_uuid = { - .len = ESP_UUID_LEN_128, - .uuid.uuid128 = - { - /* 0mOS_CFG_key___0, 306d4f53-5f43-4647-5f6b-65795f5f5f30 */ - 0x30, 0x5f, 0x5f, 0x5f, 0x79, 0x65, 0x6b, 0x5f, 0x47, 0x46, 0x43, 0x5f, - 0x53, 0x4f, 0x6d, 0x30, - }, -}; -static uint16_t mos_cfg_key_ah; - -static const esp_bt_uuid_t mos_cfg_value_uuid = { - .len = ESP_UUID_LEN_128, - .uuid.uuid128 = - { - /* 1mOS_CFG_value_1, 316d4f53-5f43-4647-5f76-616c75655f31 */ - 0x31, 0x5f, 0x65, 0x75, 0x6c, 0x61, 0x76, 0x5f, 0x47, 0x46, 0x43, 0x5f, - 0x53, 0x4f, 0x6d, 0x31, - }, -}; -static uint16_t mos_cfg_value_ah; - -static const esp_bt_uuid_t mos_cfg_save_uuid = { - .len = ESP_UUID_LEN_128, - .uuid.uuid128 = - { - /* 2mOS_CFG_save__2, 326d4f53-5f43-4647-5f73-6176655f5f32 */ - 0x32, 0x5f, 0x5f, 0x65, 0x76, 0x61, 0x73, 0x5f, 0x47, 0x46, 0x43, 0x5f, - 0x53, 0x4f, 0x6d, 0x32, - }, -}; -static uint16_t mos_cfg_save_ah; - -const esp_gatts_attr_db_t mos_cfg_gatt_db[7] = { - { - .attr_control = {.auto_rsp = ESP_GATT_AUTO_RSP}, - .att_desc = - { - .uuid_length = ESP_UUID_LEN_16, - .uuid_p = (uint8_t *) &primary_service_uuid, - .perm = ESP_GATT_PERM_READ, - .max_length = ESP_UUID_LEN_128, - .length = ESP_UUID_LEN_128, - .value = (uint8_t *) mos_cfg_svc_uuid.uuid.uuid128, - }, - }, - - /* key */ - {{ESP_GATT_AUTO_RSP}, - {ESP_UUID_LEN_16, (uint8_t *) &char_decl_uuid, ESP_GATT_PERM_READ, 1, 1, - (uint8_t *) &char_prop_write}}, - {{ESP_GATT_RSP_BY_APP}, - {ESP_UUID_LEN_128, (uint8_t *) mos_cfg_key_uuid.uuid.uuid128, - ESP_GATT_PERM_WRITE, 0, 0, NULL}}, - - /* value */ - {{ESP_GATT_AUTO_RSP}, - {ESP_UUID_LEN_16, (uint8_t *) &char_decl_uuid, ESP_GATT_PERM_READ, 1, 1, - (uint8_t *) &char_prop_read_write}}, - {{ESP_GATT_RSP_BY_APP}, - {ESP_UUID_LEN_128, (uint8_t *) mos_cfg_value_uuid.uuid.uuid128, - ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, 0, 0, NULL}}, - - /* save */ - {{ESP_GATT_AUTO_RSP}, - {ESP_UUID_LEN_16, (uint8_t *) &char_decl_uuid, ESP_GATT_PERM_READ, 1, 1, - (uint8_t *) &char_prop_write}}, - {{ESP_GATT_RSP_BY_APP}, - {ESP_UUID_LEN_128, (uint8_t *) mos_cfg_save_uuid.uuid.uuid128, - ESP_GATT_PERM_WRITE, 0, 0, NULL}}, -}; - -enum bt_cfg_state { - BT_CFG_STATE_KEY_ENTRY = 0, - BT_CFG_STATE_VALUE_ENTRY = 1, - BT_CFG_STATE_VALUE_READ = 2, - BT_CFG_STATE_SAVE = 3, -}; - -struct bt_cfg_svc_data { - struct mbuf key; - struct mbuf value; - enum bt_cfg_state state; -}; - -static bool mgos_bt_svc_config_set(struct bt_cfg_svc_data *sd) { - bool ret = false; - const struct mgos_conf_entry *e = mgos_conf_find_schema_entry_s( - mg_mk_str_n(sd->key.buf, sd->key.len), mgos_config_schema()); - if (e == NULL) { - LOG(LL_ERROR, - ("Config key '%.*s' not found", (int) sd->key.len, sd->key.buf)); - return false; - } - /* Make sure value is NUL-terminated, for simplicity. */ - mbuf_append(&sd->value, "", 1); - sd->value.len--; - const char *vt = NULL; - char *vp = (((char *) &mgos_sys_config) + e->offset); - /* For simplicity, we only allow setting leaf values. */ - switch (e->type) { - case CONF_TYPE_INT: { - int *ivp = (int *) vp; - char *endptr = NULL; - int v = strtol(sd->value.buf, &endptr, 0); - vt = "int"; - if (endptr - sd->value.buf == sd->value.len) { - *ivp = v; - ret = true; - LOG(LL_INFO, ("'%.*s' = %d", (int) sd->key.len, sd->key.buf, *ivp)); - } - break; - } - case CONF_TYPE_DOUBLE: { - double *dvp = (double *) vp; - char *endptr = NULL; - double v = strtod(sd->value.buf, &endptr); - vt = "float"; - if (endptr - sd->value.buf == sd->value.len) { - *dvp = v; - ret = true; - LOG(LL_INFO, ("'%.*s' = %f", (int) sd->key.len, sd->key.buf, *dvp)); - } - break; - } - case CONF_TYPE_STRING: { - vt = "string"; - char **svp = (char **) vp; - mgos_conf_set_str(svp, sd->value.buf); - LOG(LL_INFO, ("'%.*s' = '%s'", (int) sd->key.len, sd->key.buf, - (*svp ? *svp : ""))); - ret = true; - break; - } - case CONF_TYPE_BOOL: { - bool *bvp = (bool *) vp; - const struct mg_str vs = mg_mk_str_n(sd->value.buf, sd->value.len); - vt = "bool"; - if (mg_vcmp(&vs, "true") == 0 || mg_vcmp(&vs, "false") == 0) { - *bvp = (mg_vcmp(&vs, "true") == 0); - LOG(LL_INFO, ("'%.*s' = %s", (int) sd->key.len, sd->key.buf, - (*bvp ? "true" : "false"))); - ret = true; - } - break; - } - case CONF_TYPE_OBJECT: { - LOG(LL_ERROR, ("Setting objects is not allowed (%.*s)", (int) sd->key.len, - sd->key.buf)); - break; - } - } - if (!ret && vt != NULL) { - LOG(LL_ERROR, ("'%.*s': invalid %s value '%.*s'", (int) sd->key.len, - sd->key.buf, vt, (int) sd->value.len, sd->value.buf)); - } - return ret; -} - -static bool mgos_bt_svc_config_ev(struct esp32_bt_session *bs, - esp_gatts_cb_event_t ev, - esp_ble_gatts_cb_param_t *ep) { - bool ret = false; - struct bt_cfg_svc_data *sd = NULL; - struct esp32_bt_connection *bc = NULL; - if (bs != NULL) { /* CREAT_ATTR_TAB is not associated with any session. */ - bc = bs->bc; - sd = (struct bt_cfg_svc_data *) bs->user_data; - } - switch (ev) { - case ESP_GATTS_CREAT_ATTR_TAB_EVT: { - const struct gatts_add_attr_tab_evt_param *p = &ep->add_attr_tab; - uint16_t svch = p->handles[0]; - mos_cfg_key_ah = p->handles[2]; - mos_cfg_value_ah = p->handles[4]; - mos_cfg_save_ah = p->handles[6]; - LOG(LL_DEBUG, ("svch = %d key_ah = %d value_ah = %d save_ah = %d", svch, - mos_cfg_key_ah, mos_cfg_value_ah, mos_cfg_save_ah)); - break; - } - case ESP_GATTS_CONNECT_EVT: { - sd = (struct bt_cfg_svc_data *) calloc(1, sizeof(*sd)); - if (sd == NULL) break; - mbuf_init(&sd->key, 0); - mbuf_init(&sd->value, 0); - sd->state = BT_CFG_STATE_KEY_ENTRY; - bs->user_data = sd; - break; - } - case ESP_GATTS_READ_EVT: { - const struct gatts_read_evt_param *p = &ep->read; - if (sd == NULL || p->handle != mos_cfg_value_ah) break; - if (sd->key.len == 0) { - LOG(LL_ERROR, ("Key to read is not set")); - break; - } - sd->state = BT_CFG_STATE_VALUE_READ; - const struct mgos_conf_entry *e = mgos_conf_find_schema_entry_s( - mg_mk_str_n(sd->key.buf, sd->key.len), mgos_config_schema()); - if (e == NULL) { - LOG(LL_ERROR, - ("Config key '%.*s' not found", (int) sd->key.len, sd->key.buf)); - break; - } - struct mbuf vb; - mbuf_init(&vb, 0); - mgos_conf_emit_cb(&mgos_sys_config, NULL /* base */, e, - false /* pretty */, &vb, NULL /* cb */, - NULL /* cb_param */); - uint16_t to_send = bc->mtu - 1; - if (p->offset > vb.len) break; - if (vb.len - p->offset < to_send) to_send = vb.len - p->offset; - LOG(LL_INFO, - ("Read '%.*s' %d @ %d = '%.*s'", (int) sd->key.len, sd->key.buf, - (int) to_send, (int) p->offset, (int) to_send, vb.buf + p->offset)); - esp_gatt_rsp_t rsp = {.attr_value = {.handle = mos_cfg_value_ah, - .offset = p->offset, - .len = to_send}}; - memcpy(rsp.attr_value.value, vb.buf + p->offset, to_send); - esp_ble_gatts_send_response(bc->gatt_if, bc->conn_id, p->trans_id, - ESP_GATT_OK, &rsp); - ret = true; - mbuf_free(&vb); - break; - } - case ESP_GATTS_WRITE_EVT: { - const struct gatts_write_evt_param *p = &ep->write; - if (sd == NULL) break; - if (p->handle == mos_cfg_key_ah) { - if (sd->state != BT_CFG_STATE_KEY_ENTRY) { - mbuf_free(&sd->key); - mbuf_free(&sd->value); - mbuf_init(&sd->key, p->len); - sd->state = BT_CFG_STATE_KEY_ENTRY; - } - mbuf_append(&sd->key, p->value, p->len); - LOG(LL_DEBUG, ("Key = '%.*s'", (int) sd->key.len, sd->key.buf)); - ret = true; - } else if (p->handle == mos_cfg_value_ah) { - if (sd->state != BT_CFG_STATE_VALUE_ENTRY) { - mbuf_free(&sd->value); - mbuf_init(&sd->value, p->len); - sd->state = BT_CFG_STATE_VALUE_ENTRY; - } - if (p->len == 1 && p->value[0] == 0) { - mbuf_free(&sd->value); - mbuf_init(&sd->value, 0); - } else { - mbuf_append(&sd->value, p->value, p->len); - } - LOG(LL_DEBUG, ("Value = '%.*s'", (int) sd->value.len, - (sd->value.buf ? sd->value.buf : ""))); - ret = true; - } else if (p->handle == mos_cfg_save_ah) { - sd->state = BT_CFG_STATE_SAVE; - /* NULL value is a legal value, so we check for state here. */ - if (sd->key.len > 0 && sd->state != BT_CFG_STATE_VALUE_ENTRY) { - ret = mgos_bt_svc_config_set(sd); - } else { - ret = true; /* Allow save and reboot without setting anything. */ - } - if (ret) { - if (p->len == 1 && (p->value[0] == '1' || p->value[0] == '2')) { - char *msg = NULL; - ret = save_cfg(&mgos_sys_config, &msg); - if (!ret) { - LOG(LL_ERROR, ("Error saving config: %s", msg)); - } else if (p->value[0] == '2') { - mgos_system_restart_after(100); - } - } - } - } - break; - } - case ESP_GATTS_DISCONNECT_EVT: { - if (sd != NULL) { - mbuf_free(&sd->key); - mbuf_free(&sd->value); - free(sd); - bs->user_data = NULL; - } - break; - } - default: - break; - } - return ret; -} - -bool mgos_bt_service_config_init(void) { - if (mgos_sys_config_get_bt_config_svc_enable()) { - mgos_bt_gatts_register_service(mos_cfg_gatt_db, ARRAY_SIZE(mos_cfg_gatt_db), - mgos_bt_svc_config_ev); - } - return true; -} diff --git a/src/mgos_bt_svc_config.c b/src/mgos_bt_svc_config.c new file mode 100644 index 0000000..09f91a8 --- /dev/null +++ b/src/mgos_bt_svc_config.c @@ -0,0 +1,295 @@ +/* + * 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. + */ + +/* + * Interface to sys_config over BLE GATT service. + * See README.md for high-level description. + */ + +#include + +#include "common/cs_dbg.h" +#include "common/mbuf.h" +#include "common/mg_str.h" + +#include "mgos_config_util.h" +#include "mgos_hal.h" +#include "mgos_sys_config.h" +#include "mgos_utils.h" + +#include "mgos_bt_gatts.h" + +enum bt_cfg_state { + BT_CFG_STATE_KEY_ENTRY = 0, + BT_CFG_STATE_VALUE_ENTRY = 1, + BT_CFG_STATE_VALUE_READ = 2, + BT_CFG_STATE_SAVE = 3, +}; + +struct bt_cfg_svc_data { + struct mbuf key; + struct mbuf value; + enum bt_cfg_state state; +}; + +static bool mgos_bt_svc_config_set(struct bt_cfg_svc_data *sd) { + bool ret = false; + const struct mgos_conf_entry *e = mgos_conf_find_schema_entry_s( + mg_mk_str_n(sd->key.buf, sd->key.len), mgos_config_schema()); + if (e == NULL) { + LOG(LL_ERROR, + ("Config key '%.*s' not found", (int) sd->key.len, sd->key.buf)); + return false; + } + /* Make sure value is NUL-terminated, for simplicity. */ + mbuf_append(&sd->value, "", 1); + sd->value.len--; + const char *vt = NULL; + char *vp = (((char *) &mgos_sys_config) + e->offset); + /* For simplicity, we only allow setting leaf values. */ + switch (e->type) { + case CONF_TYPE_INT: { + int *ivp = (int *) vp; + char *endptr = NULL; + int v = strtol(sd->value.buf, &endptr, 0); + vt = "int"; + if (endptr - sd->value.buf == sd->value.len) { + *ivp = v; + ret = true; + LOG(LL_INFO, ("'%.*s' = %d", (int) sd->key.len, sd->key.buf, *ivp)); + } + break; + } + case CONF_TYPE_DOUBLE: { + double *dvp = (double *) vp; + char *endptr = NULL; + double v = strtod(sd->value.buf, &endptr); + vt = "float"; + if (endptr - sd->value.buf == sd->value.len) { + *dvp = v; + ret = true; + LOG(LL_INFO, ("'%.*s' = %f", (int) sd->key.len, sd->key.buf, *dvp)); + } + break; + } + case CONF_TYPE_STRING: { + vt = "string"; + char **svp = (char **) vp; + mgos_conf_set_str(svp, sd->value.buf); + LOG(LL_INFO, ("'%.*s' = '%s'", (int) sd->key.len, sd->key.buf, + (*svp ? *svp : ""))); + ret = true; + break; + } + case CONF_TYPE_BOOL: { + bool *bvp = (bool *) vp; + const struct mg_str vs = mg_mk_str_n(sd->value.buf, sd->value.len); + vt = "bool"; + if (mg_vcmp(&vs, "true") == 0 || mg_vcmp(&vs, "false") == 0) { + *bvp = (mg_vcmp(&vs, "true") == 0); + LOG(LL_INFO, ("'%.*s' = %s", (int) sd->key.len, sd->key.buf, + (*bvp ? "true" : "false"))); + ret = true; + } + break; + } + case CONF_TYPE_OBJECT: { + LOG(LL_ERROR, ("Setting objects is not allowed (%.*s)", (int) sd->key.len, + sd->key.buf)); + break; + } + } + if (!ret && vt != NULL) { + LOG(LL_ERROR, ("'%.*s': invalid %s value '%.*s'", (int) sd->key.len, + sd->key.buf, vt, (int) sd->value.len, sd->value.buf)); + } + return ret; +} + +static enum mgos_bt_gatt_status mgos_bt_cfg_svc_ev(struct mgos_bt_gatts_conn *c, + enum mgos_bt_gatts_ev ev, + void *ev_arg, + void *handler_arg) { + switch (ev) { + case MGOS_BT_GATTS_EV_CONNECT: { + if (!mgos_sys_config_get_bt_config_svc_enable()) { + /* Turned off at runtime. */ + return MGOS_BT_GATT_STATUS_REQUEST_NOT_SUPPORTED; + } + struct bt_cfg_svc_data *sd = + (struct bt_cfg_svc_data *) calloc(1, sizeof(*sd)); + if (sd == NULL) return MGOS_BT_GATT_STATUS_INSUF_RESOURCES; + mbuf_init(&sd->key, 0); + mbuf_init(&sd->value, 0); + sd->state = BT_CFG_STATE_KEY_ENTRY; + c->user_data = sd; + return MGOS_BT_GATT_STATUS_OK; + } + case MGOS_BT_GATTS_EV_CLOSE: { + struct bt_cfg_svc_data *sd = (struct bt_cfg_svc_data *) c->user_data; + mbuf_free(&sd->key); + mbuf_free(&sd->value); + free(sd); + return MGOS_BT_GATT_STATUS_OK; + } + default: + break; + } + return MGOS_BT_GATT_STATUS_REQUEST_NOT_SUPPORTED; +} + +static enum mgos_bt_gatt_status mgos_bt_cfg_key_ev(struct mgos_bt_gatts_conn *c, + enum mgos_bt_gatts_ev ev, + void *ev_arg, + void *handler_arg) { + struct bt_cfg_svc_data *sd = (struct bt_cfg_svc_data *) c->user_data; + if (ev != MGOS_BT_GATTS_EV_WRITE) { + return MGOS_BT_GATT_STATUS_REQUEST_NOT_SUPPORTED; + } + struct mgos_bt_gatts_write_arg *wa = + (struct mgos_bt_gatts_write_arg *) ev_arg; + if (sd->state != BT_CFG_STATE_KEY_ENTRY) { + mbuf_free(&sd->key); + mbuf_free(&sd->value); + mbuf_init(&sd->key, wa->data.len); + sd->state = BT_CFG_STATE_KEY_ENTRY; + } + mbuf_append(&sd->key, wa->data.p, wa->data.len); + LOG(LL_DEBUG, ("Key = '%.*s'", (int) sd->key.len, sd->key.buf)); + return MGOS_BT_GATT_STATUS_OK; +} + +static enum mgos_bt_gatt_status mgos_bt_cfg_val_ev(struct mgos_bt_gatts_conn *c, + enum mgos_bt_gatts_ev ev, + void *ev_arg, + void *handler_arg) { + struct bt_cfg_svc_data *sd = (struct bt_cfg_svc_data *) c->user_data; + if (ev == MGOS_BT_GATTS_EV_READ) { + struct mgos_bt_gatts_read_arg *ra = + (struct mgos_bt_gatts_read_arg *) ev_arg; + if (sd->key.len == 0) { + LOG(LL_ERROR, ("Key to read is not set")); + return MGOS_BT_GATT_STATUS_READ_NOT_PERMITTED; + } + sd->state = BT_CFG_STATE_VALUE_READ; + const struct mgos_conf_entry *e = mgos_conf_find_schema_entry_s( + mg_mk_str_n(sd->key.buf, sd->key.len), mgos_config_schema()); + if (e == NULL) { + LOG(LL_ERROR, + ("Config key '%.*s' not found", (int) sd->key.len, sd->key.buf)); + return MGOS_BT_GATT_STATUS_INVALID_OFFSET; + } + struct mbuf vb; + mbuf_init(&vb, 0); + mgos_conf_emit_cb(&mgos_sys_config, NULL /* base */, e, false /* pretty */, + &vb, NULL /* cb */, NULL /* cb_param */); + uint16_t to_send = c->gc.mtu - 1; + if (ra->offset > vb.len) return MGOS_BT_GATT_STATUS_INVALID_OFFSET; + if (vb.len - ra->offset < to_send) to_send = vb.len - ra->offset; + struct mg_str data = MG_MK_STR_N(vb.buf + ra->offset, to_send); + LOG(LL_INFO, + ("Read '%.*s' %d @ %d = '%.*s'", (int) sd->key.len, sd->key.buf, + (int) to_send, (int) ra->offset, (int) data.len, data.p)); + mgos_bt_gatts_send_resp_data(c, ra, data); + mbuf_free(&vb); + return MGOS_BT_GATT_STATUS_OK; + } else if (ev == MGOS_BT_GATTS_EV_WRITE) { + struct mgos_bt_gatts_write_arg *wa = + (struct mgos_bt_gatts_write_arg *) ev_arg; + if (sd->state != BT_CFG_STATE_VALUE_ENTRY) { + mbuf_free(&sd->value); + mbuf_init(&sd->value, wa->data.len); + sd->state = BT_CFG_STATE_VALUE_ENTRY; + } + if (wa->data.len == 1 && wa->data.p[0] == 0) { + mbuf_free(&sd->value); + mbuf_init(&sd->value, 0); + } else { + mbuf_append(&sd->value, wa->data.p, wa->data.len); + } + LOG(LL_DEBUG, ("Value = '%.*s'", (int) sd->value.len, + (sd->value.buf ? sd->value.buf : ""))); + return MGOS_BT_GATT_STATUS_OK; + } + return MGOS_BT_GATT_STATUS_REQUEST_NOT_SUPPORTED; +} + +static enum mgos_bt_gatt_status mgos_bt_cfg_save_ev( + struct mgos_bt_gatts_conn *c, enum mgos_bt_gatts_ev ev, void *ev_arg, + void *handler_arg) { + struct bt_cfg_svc_data *sd = (struct bt_cfg_svc_data *) c->user_data; + if (ev != MGOS_BT_GATTS_EV_WRITE) { + return MGOS_BT_GATT_STATUS_REQUEST_NOT_SUPPORTED; + } + struct mgos_bt_gatts_write_arg *wa = + (struct mgos_bt_gatts_write_arg *) ev_arg; + sd->state = BT_CFG_STATE_SAVE; + /* NULL value is a legal value, so we check for state here. */ + if (sd->key.len > 0 && sd->state != BT_CFG_STATE_VALUE_ENTRY) { + if (!mgos_bt_svc_config_set(sd)) { + LOG(LL_ERROR, ("Error setting config value")); + return MGOS_BT_GATT_STATUS_INVALID_OFFSET; + } + } else { + /* Allow save and reboot without setting anything. */ + } + if (wa->data.len == 1) { + char op = wa->data.p[0]; + if (op != '1' && op != '2') { + LOG(LL_ERROR, ("Invalid save action %c", op)); + return MGOS_BT_GATT_STATUS_INVALID_OFFSET; + } + char *msg = NULL; + if (!save_cfg(&mgos_sys_config, &msg)) { + LOG(LL_ERROR, ("Error saving config: %s", msg)); + return MGOS_BT_GATT_STATUS_INVALID_OFFSET; + } + if (op == '2') { + mgos_system_restart_after(300); + } + } + return MGOS_BT_GATT_STATUS_OK; +} + +static const struct mgos_bt_gatts_char_def s_cfg_svc_def[] = { + { + .uuid = "306d4f53-5f43-4647-5f6b-65795f5f5f30", /* 0mOS_CFG_key___0 */ + .prop = MGOS_BT_GATTS_PROP_RWNI(0, 1, 0, 0), + .handler = mgos_bt_cfg_key_ev, + }, + { + .uuid = "316d4f53-5f43-4647-5f76-616c75655f31", /* 1mOS_CFG_value_1 */ + .prop = MGOS_BT_GATTS_PROP_RWNI(1, 1, 0, 0), + .handler = mgos_bt_cfg_val_ev, + }, + { + .uuid = "326d4f53-5f43-4647-5f73-6176655f5f32", /* 2mOS_CFG_save__2 */ + .prop = MGOS_BT_GATTS_PROP_RWNI(0, 1, 0, 0), + .handler = mgos_bt_cfg_save_ev, + }, + {.uuid = NULL}, +}; + +bool mgos_bt_service_config_init(void) { + if (!mgos_sys_config_get_bt_config_svc_enable()) return true; + mgos_bt_gatts_register_service( + "5f6d4f53-5f43-4647-5f53-56435f49445f", /* _mOS_CFG_SVC_ID_ */ + (enum mgos_bt_gatt_sec_level) + mgos_sys_config_get_bt_config_svc_sec_level(), + s_cfg_svc_def, mgos_bt_cfg_svc_ev, NULL); + return true; +}