├── .clang-format ├── LICENSE ├── README.md ├── include └── mgos_gcp.h ├── mjs_fs └── api_gcp.js ├── mos.yml └── src └── mgos_gcp.c /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | AllowShortFunctionsOnASingleLine: false 3 | SpaceAfterCStyleCast: true 4 | PointerBindsToType: false 5 | DerivePointerBinding: false 6 | IncludeBlocks: Preserve 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Cesanta Software Limited 2 | All rights reserved 3 | 4 | Licensed under the Apache License, Version 2.0 (the ""License""); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an ""AS IS"" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Google IoT Core integration for Mongoose OS 2 | 3 | This library implements integration of Mongoose OS with Google IoT Core. 4 | 5 | See tutorial at https://mongoose-os.com/docs/mongoose-os/cloud/google.md 6 | -------------------------------------------------------------------------------- /include/mgos_gcp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2018 Cesanta Software Limited 3 | * All rights reserved 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the ""License""); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an ""AS IS"" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #pragma once 19 | 20 | #include 21 | 22 | #include "common/mg_str.h" 23 | 24 | #include "mgos_event.h" 25 | 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | 30 | #define MGOS_GCP_EV_BASE MGOS_EVENT_BASE('G', 'C', 'P') 31 | 32 | enum mgos_gcp_event { 33 | /* Connected to Google IoT Core. Arg: NULL */ 34 | MGOS_GCP_EV_CONNECT = MGOS_GCP_EV_BASE, 35 | /* Incoming config. Arg: struct mgos_gcp_config_arg * */ 36 | MGOS_GCP_EV_CONFIG, 37 | /* Incoming command. Arg: struct mgos_gcp_command_arg * */ 38 | MGOS_GCP_EV_COMMAND, 39 | /* Disonnected from the cloud. Arg: NULL */ 40 | MGOS_GCP_EV_CLOSE, 41 | }; 42 | 43 | struct mgos_gcp_config_arg { 44 | struct mg_str value; 45 | }; 46 | 47 | struct mgos_gcp_command_arg { 48 | struct mg_str value; 49 | struct mg_str subfolder; 50 | }; 51 | 52 | struct mg_str mgos_gcp_get_device_id(void); 53 | 54 | /* Returns true if GCP connection is up, false otherwise. */ 55 | bool mgos_gcp_is_connected(void); 56 | 57 | /* 58 | * Send a telemetry event to the default topic. 59 | * 60 | * Se documentation here: 61 | * https://cloud.google.com/iot/docs/how-tos/mqtt-bridge#publishing_telemetry_events 62 | * 63 | * E.g.: mgos_gcp_send_eventf("{foo: %d}", foo); 64 | */ 65 | bool mgos_gcp_send_event(const struct mg_str data); 66 | bool mgos_gcp_send_eventp(const struct mg_str *data); 67 | bool mgos_gcp_send_eventf(const char *json_fmt, ...); 68 | 69 | /* 70 | * Send a telemetry event to a subfolder topic. 71 | * 72 | * E.g.: mgos_gcp_send_event_subf("foo_events", "{foo: %d}", foo); 73 | */ 74 | bool mgos_gcp_send_event_sub(const struct mg_str subfolder, 75 | const struct mg_str data); 76 | bool mgos_gcp_send_event_subp(const struct mg_str *subfolder, 77 | const struct mg_str *data); 78 | bool mgos_gcp_send_event_subf(const char *subfolder, const char *json_fmt, ...); 79 | 80 | #ifdef __cplusplus 81 | } 82 | #endif 83 | -------------------------------------------------------------------------------- /mjs_fs/api_gcp.js: -------------------------------------------------------------------------------- 1 | let GCP = { 2 | // ## **`GCP.isConnected()`** 3 | // Return value: true if GCP connection is up, false otherwise. 4 | isConnected: ffi('bool mgos_gcp_is_connected()'), 5 | 6 | sendEvent: ffi('bool mgos_gcp_send_eventp(struct mg_str *)'), 7 | }; 8 | -------------------------------------------------------------------------------- /mos.yml: -------------------------------------------------------------------------------- 1 | author: mongoose-os 2 | type: lib 3 | description: Google IoT Core integration 4 | version: 1.0 5 | 6 | sources: 7 | - src 8 | 9 | includes: 10 | - include 11 | 12 | config_schema: 13 | - ["gcp", "o", {title: "GCP IOT settings"}] 14 | - ["gcp.enable", "b", false, {title: "Enable GCP IOT"}] 15 | - ["gcp.server", "s", "mqtt.googleapis.com", {title: "Server address"}] 16 | - ["gcp.project", "s", "", {title: "Project ID"}] 17 | - ["gcp.region", "s", "", {title: "Region name"}] 18 | - ["gcp.registry", "s", "", {title: "Device registry name"}] 19 | - ["gcp.device", "s", "", {title: "Device name"}] 20 | - ["gcp.key", "s", "", {title: "Private key to use for token signing"}] 21 | - ["gcp.ca_cert", "s", "", {title: "CA cert to use for peer verification. If not set, falls back to mqtt.ssl_ca_cert and then to ca.pem"}] 22 | - ["gcp.token_ttl", "i", 3600, {title: "Life time of the token"}] 23 | - ["gcp.enable_config", "b", false, {title: "Subscribe to the configuration topic"}] 24 | - ["gcp.enable_commands", "b", true, {title: "Subscribe to the command topic"}] 25 | 26 | libs: 27 | - location: https://github.com/mongoose-os-libs/ca-bundle 28 | - location: https://github.com/mongoose-os-libs/mqtt 29 | # GCP requires valid time to sign tickets. 30 | - location: https://github.com/mongoose-os-libs/sntp 31 | 32 | tags: 33 | - c 34 | - cloud 35 | - google 36 | - docs:cloud:Google IoT Core 37 | 38 | manifest_version: 2017-09-29 39 | -------------------------------------------------------------------------------- /src/mgos_gcp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2018 Cesanta Software Limited 3 | * All rights reserved 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the ""License""); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an ""AS IS"" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include "mgos_gcp.h" 19 | 20 | #include "common/cs_base64.h" 21 | #include "common/cs_dbg.h" 22 | #include "common/json_utils.h" 23 | #include "common/mbuf.h" 24 | 25 | #include "mbedtls/asn1.h" 26 | #include "mbedtls/bignum.h" 27 | #include "mbedtls/pk.h" 28 | 29 | #include "frozen.h" 30 | #include "mgos_mongoose_internal.h" 31 | #include "mongoose.h" 32 | 33 | #include "mgos_event.h" 34 | #include "mgos_mqtt.h" 35 | #include "mgos_sys_config.h" 36 | #include "mgos_timers.h" 37 | 38 | static mbedtls_pk_context s_token_key; 39 | extern int mg_ssl_if_mbed_random(void *ctx, unsigned char *buf, size_t len); 40 | 41 | struct mg_str mgos_gcp_get_device_id(void) { 42 | const char *result = mgos_sys_config_get_gcp_device(); 43 | if (result == NULL) result = mgos_sys_config_get_device_id(); 44 | return mg_mk_str(result); 45 | } 46 | 47 | struct jwt_printer_ctx { 48 | struct cs_base64_ctx b64_ctx; 49 | struct mbuf jwt; 50 | }; 51 | 52 | struct gcp_state { 53 | unsigned int want_acks : 8; 54 | unsigned int have_acks : 8; 55 | unsigned int connected : 8; 56 | mgos_timer_id token_ttl_timer_id; 57 | }; 58 | 59 | static struct gcp_state *s_state = NULL; 60 | 61 | static int json_printer_jwt(struct json_out *out, const char *data, 62 | size_t len) { 63 | struct jwt_printer_ctx *ctx = (struct jwt_printer_ctx *) out->u.data; 64 | cs_base64_update(&ctx->b64_ctx, (const char *) data, len); 65 | return len; 66 | } 67 | 68 | static void base64url_putc(char c, void *arg) { 69 | struct jwt_printer_ctx *ctx = (struct jwt_printer_ctx *) arg; 70 | switch (c) { 71 | case '+': 72 | c = '-'; 73 | break; 74 | case '/': 75 | c = '_'; 76 | break; 77 | case '=': 78 | return; 79 | } 80 | mbuf_append(&ctx->jwt, &c, 1); 81 | } 82 | 83 | static void mgos_gcp_jwt_timeout(void *param) { 84 | struct gcp_state *state = (struct gcp_state *) param; 85 | state->token_ttl_timer_id = MGOS_INVALID_TIMER_ID; 86 | LOG(LL_INFO, ("Dropping MQTT connection due to imminent token expiration")); 87 | mgos_disconnect(mgos_mqtt_get_global_conn()); 88 | } 89 | 90 | static void mgos_gcp_mqtt_connect(struct mg_connection *c, 91 | const char *client_id, 92 | struct mg_send_mqtt_handshake_opts *opts, 93 | void *arg) { 94 | struct gcp_state *state = (struct gcp_state *) arg; 95 | if (state->token_ttl_timer_id != MGOS_INVALID_TIMER_ID) { 96 | mgos_clear_timer(state->token_ttl_timer_id); 97 | state->token_ttl_timer_id = MGOS_INVALID_TIMER_ID; 98 | } 99 | 100 | double now = mg_time(); 101 | struct jwt_printer_ctx ctx; 102 | bool is_rsa = mbedtls_pk_can_do(&s_token_key, MBEDTLS_PK_RSA); 103 | 104 | mbuf_init(&ctx.jwt, 200); 105 | struct json_out out = {.printer = json_printer_jwt, .u.data = &ctx}; 106 | 107 | cs_base64_init(&ctx.b64_ctx, base64url_putc, &ctx); 108 | json_printf(&out, "{typ:%Q,alg:%Q}", "JWT", (is_rsa ? "RS256" : "ES256")); 109 | cs_base64_finish(&ctx.b64_ctx); 110 | base64url_putc('.', &ctx); 111 | uint64_t iat = (uint64_t) now; 112 | uint64_t ttl = (uint64_t) mgos_sys_config_get_gcp_token_ttl(); 113 | uint64_t exp = iat + ttl; 114 | state->token_ttl_timer_id = 115 | mgos_set_timer((ttl - 30) * 1000, 0, mgos_gcp_jwt_timeout, state); 116 | 117 | cs_base64_init(&ctx.b64_ctx, base64url_putc, &ctx); 118 | json_printf(&out, "{aud:%Q,iat:%llu,exp:%llu}", 119 | mgos_sys_config_get_gcp_project(), iat, exp); 120 | cs_base64_finish(&ctx.b64_ctx); 121 | 122 | unsigned char hash[32]; 123 | int ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 124 | (const unsigned char *) ctx.jwt.buf, ctx.jwt.len, hash); 125 | if (ret != 0) { 126 | LOG(LL_ERROR, ("mbedtls_md failed: 0x%x", ret)); 127 | c->flags |= MG_F_CLOSE_IMMEDIATELY; 128 | return; 129 | } 130 | 131 | size_t key_len = mbedtls_pk_get_len(&s_token_key); 132 | size_t sig_len = (is_rsa ? key_len : key_len * 2 + 10); 133 | unsigned char *sig = (unsigned char *) calloc(1, sig_len); 134 | if (sig == NULL) { 135 | c->flags |= MG_F_CLOSE_IMMEDIATELY; 136 | return; 137 | } 138 | 139 | ret = mbedtls_pk_sign(&s_token_key, MBEDTLS_MD_SHA256, hash, sizeof(hash), 140 | sig, &sig_len, mg_ssl_if_mbed_random, NULL); 141 | if (ret != 0) { 142 | LOG(LL_ERROR, ("mbedtls_pk_sign failed: 0x%x", ret)); 143 | c->flags |= MG_F_CLOSE_IMMEDIATELY; 144 | return; 145 | } 146 | 147 | /* ECDSA signature comes as an ASN.1 sequence of R and S and needs to be 148 | * converted into its raw form. */ 149 | if (!is_rsa) { 150 | unsigned char *p = sig; 151 | const unsigned char *end = sig + sig_len; 152 | size_t len = 0; 153 | mbedtls_asn1_get_tag(&p, end, &len, 154 | MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE); 155 | mbedtls_mpi x; 156 | mbedtls_mpi_init(&x); 157 | mbedtls_asn1_get_mpi(&p, end, &x); /* R */ 158 | mbedtls_mpi_write_binary(&x, sig, key_len); 159 | mbedtls_asn1_get_mpi(&p, end, &x); /* S */ 160 | mbedtls_mpi_write_binary(&x, sig + key_len, key_len); 161 | mbedtls_mpi_free(&x); 162 | sig_len = 2 * key_len; 163 | } 164 | 165 | base64url_putc('.', &ctx); 166 | cs_base64_init(&ctx.b64_ctx, base64url_putc, &ctx); 167 | cs_base64_update(&ctx.b64_ctx, (const char *) sig, sig_len); 168 | cs_base64_finish(&ctx.b64_ctx); 169 | free(sig); 170 | 171 | mbuf_append(&ctx.jwt, "", 1); /* NUL */ 172 | 173 | char *cid = NULL; 174 | struct mg_str did = mgos_gcp_get_device_id(); 175 | mg_asprintf(&cid, 0, "projects/%s/locations/%s/registries/%s/devices/%.*s", 176 | mgos_sys_config_get_gcp_project(), 177 | mgos_sys_config_get_gcp_region(), 178 | mgos_sys_config_get_gcp_registry(), (int) did.len, did.p); 179 | 180 | LOG(LL_DEBUG, ("ID : %s", cid)); 181 | LOG(LL_DEBUG, ("JWT: %s", ctx.jwt.buf)); 182 | LOG(LL_DEBUG, ("JWT Refresh Timeout: %d", ((int) ttl - 30) * 1000)); 183 | 184 | opts->user_name = "unused"; 185 | opts->password = ctx.jwt.buf; 186 | mg_send_mqtt_handshake_opt(c, cid, *opts); 187 | mbuf_free(&ctx.jwt); 188 | free(cid); 189 | (void) client_id; 190 | } 191 | 192 | static void mgos_gcp_tigger_connected(struct gcp_state *state) { 193 | if (!state->connected || state->have_acks != state->want_acks) return; 194 | struct mgos_cloud_arg arg = {.type = MGOS_CLOUD_GCP}; 195 | mgos_event_trigger(MGOS_EVENT_CLOUD_CONNECTED, &arg); 196 | mgos_event_trigger(MGOS_GCP_EV_CONNECT, NULL); 197 | } 198 | 199 | static void mgos_gcp_mqtt_ev(struct mg_connection *nc, int ev, void *ev_data, 200 | void *user_data) { 201 | struct gcp_state *state = (struct gcp_state *) user_data; 202 | 203 | switch (ev) { 204 | case MG_EV_MQTT_CONNACK: { 205 | int code = ((struct mg_mqtt_message *) ev_data)->connack_ret_code; 206 | state->connected = (code == 0); 207 | mgos_gcp_tigger_connected(state); 208 | break; 209 | } 210 | case MG_EV_MQTT_DISCONNECT: { 211 | if (state->connected) { 212 | state->connected = false; 213 | state->have_acks = 0; 214 | struct mgos_cloud_arg arg = {.type = MGOS_CLOUD_GCP}; 215 | mgos_event_trigger(MGOS_EVENT_CLOUD_DISCONNECTED, &arg); 216 | mgos_event_trigger(MGOS_GCP_EV_CLOSE, NULL); 217 | } 218 | if (state->token_ttl_timer_id != MGOS_INVALID_TIMER_ID) { 219 | mgos_clear_timer(state->token_ttl_timer_id); 220 | state->token_ttl_timer_id = MGOS_INVALID_TIMER_ID; 221 | } 222 | break; 223 | } 224 | 225 | default: 226 | break; 227 | } 228 | 229 | (void) nc; 230 | } 231 | 232 | bool mgos_gcp_send_event(const struct mg_str data) { 233 | return mgos_gcp_send_eventp(&data); 234 | } 235 | 236 | bool mgos_gcp_send_eventp(const struct mg_str *data) { 237 | struct mg_str ns = MG_NULL_STR; 238 | return mgos_gcp_send_event_subp(&ns, data); 239 | } 240 | 241 | bool mgos_gcp_send_eventf(const char *json_fmt, ...) { 242 | bool res = false; 243 | va_list ap; 244 | va_start(ap, json_fmt); 245 | char *data = json_vasprintf(json_fmt, ap); 246 | va_end(ap); 247 | if (data != NULL) { 248 | res = mgos_gcp_send_event(mg_mk_str(data)); 249 | free(data); 250 | } 251 | return res; 252 | } 253 | 254 | bool mgos_gcp_send_event_sub(const struct mg_str subfolder, 255 | const struct mg_str data) { 256 | return mgos_gcp_send_event_subp(&subfolder, &data); 257 | } 258 | 259 | bool mgos_gcp_send_event_subp(const struct mg_str *subfolder, 260 | const struct mg_str *data) { 261 | char *topic; 262 | bool res = false; 263 | struct mg_str did = mgos_gcp_get_device_id(); 264 | if (did.len == 0) goto out; 265 | mg_asprintf(&topic, 0, "/devices/%.*s/events%s%.*s", (int) did.len, did.p, 266 | (subfolder->len > 0 && subfolder->p[0] != '/' ? "/" : ""), 267 | (int) subfolder->len, subfolder->p); 268 | if (topic != NULL) { 269 | res = mgos_mqtt_pub(topic, data->p, data->len, 1 /* qos */, 0 /* retain */); 270 | free(topic); 271 | } 272 | out: 273 | return res; 274 | } 275 | 276 | bool mgos_gcp_send_event_subf(const char *subfolder, const char *json_fmt, 277 | ...) { 278 | bool res = false; 279 | va_list ap; 280 | va_start(ap, json_fmt); 281 | char *data = json_vasprintf(json_fmt, ap); 282 | va_end(ap); 283 | if (data != NULL) { 284 | res = mgos_gcp_send_event_sub(mg_mk_str(subfolder), mg_mk_str(data)); 285 | free(data); 286 | } 287 | return res; 288 | } 289 | 290 | bool mgos_gcp_is_connected(void) { 291 | if (s_state == NULL) return false; 292 | return (s_state->connected && s_state->have_acks >= s_state->want_acks); 293 | } 294 | 295 | static void mgos_gcp_config_ev(struct mg_connection *nc, int ev, void *ev_data, 296 | void *user_data) { 297 | struct gcp_state *state = (struct gcp_state *) user_data; 298 | if (ev == MG_EV_MQTT_SUBACK) { 299 | state->have_acks++; 300 | mgos_gcp_tigger_connected(state); 301 | return; 302 | } else if (ev != MG_EV_MQTT_PUBLISH) { 303 | return; 304 | } 305 | struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data; 306 | struct mgos_gcp_config_arg arg = { 307 | .value = mm->payload, 308 | }; 309 | LOG(LL_DEBUG, ("Config: '%.*s'", (int) arg.value.len, arg.value.p)); 310 | mgos_event_trigger(MGOS_GCP_EV_CONFIG, &arg); 311 | (void) nc; 312 | } 313 | 314 | static void mgos_gcp_command_ev(struct mg_connection *nc, int ev, void *ev_data, 315 | void *user_data) { 316 | struct gcp_state *state = (struct gcp_state *) user_data; 317 | if (ev == MG_EV_MQTT_SUBACK) { 318 | state->have_acks++; 319 | mgos_gcp_tigger_connected(state); 320 | return; 321 | } else if (ev != MG_EV_MQTT_PUBLISH) { 322 | return; 323 | } 324 | struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data; 325 | struct mgos_gcp_command_arg arg = { 326 | .value = mm->payload, 327 | }; 328 | const char *sc = mg_strstr(mm->topic, mg_mk_str("/commands/")); 329 | if (sc != NULL) { 330 | arg.subfolder.p = sc + 10; 331 | arg.subfolder.len = (mm->topic.p + mm->topic.len) - arg.subfolder.p; 332 | } 333 | LOG(LL_DEBUG, ("Command ('%.*s'): '%.*s'", (int) arg.subfolder.len, 334 | arg.subfolder.p, (int) arg.value.len, arg.value.p)); 335 | mgos_event_trigger(MGOS_GCP_EV_COMMAND, &arg); 336 | (void) nc; 337 | } 338 | 339 | bool mgos_gcp_init(void) { 340 | mgos_event_register_base(MGOS_GCP_EV_BASE, __FILE__); 341 | if (!mgos_sys_config_get_gcp_enable()) return true; 342 | if (mgos_sys_config_get_gcp_project() == NULL || 343 | mgos_sys_config_get_gcp_region() == NULL || 344 | mgos_sys_config_get_gcp_registry() == NULL || 345 | mgos_sys_config_get_gcp_key() == NULL) { 346 | LOG(LL_ERROR, ("gcp.project, region, registry and key are required")); 347 | return false; 348 | } 349 | if (mgos_gcp_get_device_id().len == 0) { 350 | LOG(LL_ERROR, ("Either gcp.device or device.id must be set")); 351 | return false; 352 | } 353 | mbedtls_pk_init(&s_token_key); 354 | int r = mbedtls_pk_parse_keyfile(&s_token_key, mgos_sys_config_get_gcp_key(), 355 | NULL); 356 | if (r != 0) { 357 | LOG(LL_ERROR, ("Invalid gcp.key (0x%x)", r)); 358 | return false; 359 | } 360 | 361 | struct gcp_state *state = calloc(1, sizeof(*state)); 362 | state->token_ttl_timer_id = MGOS_INVALID_TIMER_ID; 363 | 364 | struct mg_str did = mgos_gcp_get_device_id(); 365 | LOG(LL_INFO, 366 | ("GCP client for %s/%s/%s/%.*s, %s key in %s", 367 | mgos_sys_config_get_gcp_project(), mgos_sys_config_get_gcp_region(), 368 | mgos_sys_config_get_gcp_registry(), (int) did.len, did.p, 369 | mbedtls_pk_get_name(&s_token_key), mgos_sys_config_get_gcp_key())); 370 | struct mgos_config_mqtt mcfg = *mgos_sys_config_get_mqtt(); 371 | mcfg.enable = true; 372 | mcfg.require_time = true; 373 | mcfg.cloud_events = false; 374 | mcfg.server = mgos_sys_config_get_gcp_server(); 375 | if (mgos_sys_config_get_gcp_ca_cert() != NULL) { 376 | mcfg.ssl_ca_cert = mgos_sys_config_get_gcp_ca_cert(); 377 | } 378 | if (mcfg.ssl_ca_cert == NULL) mcfg.ssl_ca_cert = "ca.pem"; 379 | s_state = state; 380 | if (!mgos_mqtt_set_config(&mcfg)) return false; 381 | mgos_mqtt_set_connect_fn(mgos_gcp_mqtt_connect, state); 382 | mgos_mqtt_add_global_handler(mgos_gcp_mqtt_ev, state); 383 | if (mgos_sys_config_get_gcp_enable_config()) { 384 | char *topic = NULL; 385 | mg_asprintf(&topic, 0, "/devices/%.*s/config", (int) did.len, did.p); 386 | mgos_mqtt_global_subscribe(mg_mk_str(topic), mgos_gcp_config_ev, state); 387 | free(topic); 388 | state->want_acks++; 389 | } 390 | if (mgos_sys_config_get_gcp_enable_commands()) { 391 | char *topic = NULL; 392 | mg_asprintf(&topic, 0, "/devices/%.*s/commands/#", (int) did.len, did.p); 393 | mgos_mqtt_global_subscribe(mg_mk_str(topic), mgos_gcp_command_ev, state); 394 | free(topic); 395 | state->want_acks++; 396 | } 397 | return true; 398 | } 399 | --------------------------------------------------------------------------------