├── .clang-format ├── .gitignore ├── LICENSE ├── README.md ├── include ├── mgos_mqtt.h └── mgos_mqtt_conn.h ├── mjs_fs └── api_mqtt.js ├── mos.yml └── src ├── mgos_mqtt.c ├── mgos_mqtt_conn.c ├── mgos_mqtt_conn_internal.h └── mgos_mqtt_ws_conn.c /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | AllowShortFunctionsOnASingleLine: false 3 | SpaceAfterCStyleCast: true 4 | PointerBindsToType: false 5 | DerivePointerBinding: false 6 | IncludeBlocks: Preserve 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | build 4 | build/*** 5 | core-* 6 | -------------------------------------------------------------------------------- /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 | # Generic MQTT client 2 | 3 | This library provides [MQTT protocol](https://en.wikipedia.org/wiki/MQTT) client 4 | API that allows devices to talk to MQTT servers. 5 | 6 | Mongoose OS implements MQTT 3.1.1 client functionality, and works with 7 | all popular MQTT server implementations, like AWS IoT, Google IoT Core, 8 | Microsoft Azure, IBM Watson, HiveMQ, Mosquitto, etc. 9 | 10 | In order to talk to an MQTT server, configure MQTT server settings - 11 | see Configuration section below. Once configured, Mongoose OS keeps that 12 | connection alive by reconnecting and re-subscribing to all topics 13 | after disconnections - you do not need to implement the reconnection logic. 14 | 15 | If you want to use TLS, set `mqtt.ssl_ca_cert=ca.pem`. Make sure that `ca.pem` 16 | file has required CA certificates. If you want to use mutual TLS, set 17 | `mqtt.ssl_cert=CLIENT_CERT.pem` and `mqtt.ssl_key=PRIVATE_KEY.pem`. 18 | 19 | See example video (don't forget to set `mqtt.enable=true` before you try it): 20 | 21 | 23 | 24 | ## Configuration 25 | 26 | The MQTT library adds `mqtt` section to the device configuration: 27 | 28 | ```javascript 29 | { 30 | "clean_session": true, // Clean session info stored on server 31 | "client_id": "", // If not set, device.id is used 32 | "enable": false, // Enable MQTT functionality 33 | "keep_alive": 60, // How often to send PING messages in seconds 34 | "pass": "", // User password 35 | "reconnect_timeout_min": 2, // Minimum reconnection timeout in seconds 36 | "reconnect_timeout_max": 60, // Maximum reconnection timeout in seconds 37 | "server": "iot.eclipse.org", // Server to connect to. if `:PORT` is not specified, 38 | // 1883 or 8883 is used depending on whether SSL is enabled. 39 | "ssl_ca_cert": "", // Set this to file name with CA certs to enable TLS 40 | "ssl_cert": "", // Client certificate for mutual TLS 41 | "ssl_cipher_suites": "", // TLS cipher suites 42 | "ssl_key": "", // Private key for the client certificate 43 | "ssl_psk_identity": "", // If set, a preshared key auth is used 44 | "ssl_psk_key": "", // Preshared key 45 | "user": "", // MQTT user name, if MQTT auth is used 46 | "will_message": "", // MQTT last will message 47 | "will_topic": "" // MQTT last will topic 48 | } 49 | ``` 50 | 51 | ## Reconnect behavior and backup server 52 | 53 | It is possible to have a "backup" server that device will connect to if it fails to connect to the primary server. 54 | 55 | Backup server is configured under the `mqtt1` section which contains exactly the same parameters as `mqtt` described above. 56 | 57 | Device will first try to connect to the main server configured under `mqtt`. 58 | It will keep connecting to it, increasing the reconnection interval from `reconnect_timeout_min` to `reconnect_timeout_max`. 59 | Reconnection interval is doubled after each attempt so for values above there will be 60 | connection attempts after 2, 4, 8, 16, 32 and 60 seconds. 61 | After reaching the maximum reconnect interval and if `mqtt1.enable` is set, it will switch to the `mqtt1` 62 | configuration and reset the reconnect interval, so it will try to connect to `mqtt1` the same way. 63 | If that works, it will stay connected to `mqtt1`. If connection drops, it will try to reconnect to `mqtt1` 64 | in the same way. If connection to backup server fails, it will go back to the main server and so on. 65 | -------------------------------------------------------------------------------- /include/mgos_mqtt.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 | /* 19 | * MQTT API. 20 | * 21 | * See https://mongoose-os.com/blog/why-mqtt-is-getting-so-popular-in-iot/ 22 | * for some background information. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "mgos_mqtt_conn.h" 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif /* __cplusplus */ 32 | 33 | /* 34 | * Subscribe to a specific topic. 35 | * This handler will receive SUBACK - when first subscribed to the topic, 36 | * PUBLISH - for messages published to this topic. 37 | */ 38 | void mgos_mqtt_global_subscribe(const struct mg_str topic, 39 | mg_event_handler_t handler, void *ud); 40 | 41 | /* Registers a mongoose handler to be invoked on the global MQTT connection */ 42 | void mgos_mqtt_add_global_handler(mg_event_handler_t handler, void *ud); 43 | 44 | /* 45 | * Set connect callback. It is invoked when CONNECT message is about to 46 | * be sent. The callback is responsible to call `mg_send_mqtt_handshake_opt()` 47 | */ 48 | void mgos_mqtt_set_connect_fn(mgos_mqtt_connect_fn_t cb, void *fn_arg); 49 | 50 | /* 51 | * Returns current MQTT connection if it is established; otherwise returns 52 | * `NULL` 53 | */ 54 | struct mg_connection *mgos_mqtt_get_global_conn(void); 55 | 56 | /* 57 | * Attempt MQTT connection now (if enabled and not already connected). 58 | * Normally MQTT will try to connect in the background, at certain interval. 59 | * This function will force immediate connection attempt. 60 | */ 61 | bool mgos_mqtt_global_connect(void); 62 | 63 | /* 64 | * Disconnect from and/or stop trying to connect to MQTT server 65 | * until mgos_mqtt_global_connect() is called. 66 | */ 67 | void mgos_mqtt_global_disconnect(void); 68 | 69 | /* Returns true if MQTT connection is up, false otherwise. */ 70 | bool mgos_mqtt_global_is_connected(void); 71 | 72 | /* 73 | * Publish message to the configured MQTT server, to the given MQTT topic. 74 | * Return value will be the packet id (> 0) if there is a connection to the 75 | * server and the message has been queued for sending. In case no connection is 76 | * available, 0 is returned. In case of QoS 1 return value does not indicate 77 | * that PUBACK has been received; there is currently no way to check for that. 78 | */ 79 | uint16_t mgos_mqtt_pub(const char *topic, const void *message, size_t len, 80 | int qos, bool retain); 81 | 82 | /* Variant of mgos_mqtt_pub for publishing a JSON-formatted string */ 83 | uint16_t mgos_mqtt_pubf(const char *topic, int qos, bool retain, 84 | const char *json_fmt, ...); 85 | uint16_t mgos_mqtt_pubv(const char *topic, int qos, bool retain, 86 | const char *json_fmt, va_list ap); 87 | 88 | /* 89 | * Callback signature for `mgos_mqtt_sub()` below. 90 | */ 91 | typedef void (*sub_handler_t)(struct mg_connection *nc, const char *topic, 92 | int topic_len, const char *msg, int msg_len, 93 | void *ud); 94 | /* 95 | * Subscribe on a topic on a configured MQTT server. 96 | */ 97 | void mgos_mqtt_sub(const char *topic, sub_handler_t, void *ud); 98 | 99 | /* 100 | * Unsubscribe on a topic on a configured MQTT server. 101 | */ 102 | bool mgos_mqtt_unsub(const char *topic); 103 | 104 | /* 105 | * Returns number of pending bytes to send. 106 | */ 107 | size_t mgos_mqtt_num_unsent_bytes(void); 108 | 109 | /* 110 | * Returns next packet id; the returned value is incremented every time the 111 | * function is called, and it's never 0 (so after 0xffff it'll be 1) 112 | */ 113 | uint16_t mgos_mqtt_get_packet_id(void); 114 | 115 | /* 116 | * Set maximum QOS level that is supported by server: 0, 1 or 2. 117 | * Some servers, particularly AWS GreenGrass, accept only QoS0 transactions. 118 | * An attempt to use any other QoS results into silent disconnect. 119 | * Therefore, instead of forcing all client code to track such server's quirks, 120 | * we add mechanism to transparently downgrade the QoS. 121 | */ 122 | void mgos_mqtt_set_max_qos(int qos); 123 | 124 | /* 125 | * (Re)configure MQTT. 126 | */ 127 | struct mgos_config_mqtt; 128 | bool mgos_mqtt_set_config(const struct mgos_config_mqtt *cfg); 129 | 130 | #ifdef __cplusplus 131 | } 132 | #endif /* __cplusplus */ 133 | -------------------------------------------------------------------------------- /include/mgos_mqtt_conn.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Deomid "rojer" Ryabkov 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 | #include 22 | 23 | #include "mgos_mongoose.h" 24 | #include "mgos_sys_config.h" 25 | 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | 30 | /* Opaque struct */ 31 | struct mgos_mqtt_conn; 32 | 33 | /* Create a connection. */ 34 | struct mgos_mqtt_conn *mgos_mqtt_conn_create( 35 | int conn_id, const struct mgos_config_mqtt *cfg); 36 | 37 | /* Create connection with failover config. */ 38 | struct mgos_mqtt_conn *mgos_mqtt_conn_create2( 39 | int conn_id, const struct mgos_config_mqtt *cfg, 40 | const struct mgos_config_mqtt *cfg2); 41 | 42 | /* Use this to obtain mgos_mqtt_conn from mg_connection. */ 43 | struct mgos_mqtt_conn *mgos_mqtt_conn_from_nc(struct mg_connection *nc); 44 | 45 | /* Registers a handler to be invoked on the MQTT connection. */ 46 | void mgos_mqtt_conn_add_handler(struct mgos_mqtt_conn *c, 47 | mg_event_handler_t handler, void *user_data); 48 | 49 | /* 50 | * Callback signature for `mgos_mqtt_set_connect_fn()`, see its docs for 51 | * details. 52 | */ 53 | typedef void (*mgos_mqtt_connect_fn_t)(struct mg_connection *nc, 54 | const char *client_id, 55 | struct mg_send_mqtt_handshake_opts *opts, 56 | void *fn_arg); 57 | 58 | /* 59 | * Set connect callback. It is invoked when CONNECT message is about to 60 | * be sent. The callback is responsible to call `mg_send_mqtt_handshake_opt()` 61 | */ 62 | void mgos_mqtt_conn_set_connect_fn(struct mgos_mqtt_conn *c, 63 | mgos_mqtt_connect_fn_t fn, void *fn_arg); 64 | 65 | /* 66 | * Attempt MQTT connection now (if enabled and not already connected). 67 | * Normally MQTT will try to connect in the background, at certain interval. 68 | * This function will force immediate connection attempt. 69 | */ 70 | bool mgos_mqtt_conn_connect(struct mgos_mqtt_conn *c); 71 | 72 | /* 73 | * Disconnect from and/or stop trying to connect to MQTT server 74 | * until mgos_mqtt_conn_connect() is called. 75 | */ 76 | void mgos_mqtt_conn_disconnect(struct mgos_mqtt_conn *c); 77 | 78 | /* Returns true if MQTT connection is up, false otherwise. */ 79 | bool mgos_mqtt_conn_is_connected(struct mgos_mqtt_conn *c); 80 | 81 | /* 82 | * Publish message to the configured MQTT server, to the given MQTT topic. 83 | * Return value will be the packet id (> 0) if there is a connection to the 84 | * server and the message has been queued for sending. In case no connection is 85 | * available, 0 is returned. In case of QoS 1 return value does not indicate 86 | * that PUBACK has been received, use connection handler to check for that. 87 | */ 88 | uint16_t mgos_mqtt_conn_pub(struct mgos_mqtt_conn *c, const char *topic, 89 | struct mg_str msg, int qos, bool retain); 90 | 91 | /* Variants of mgos_mqtt_conn_pub for publishing a JSON-formatted string */ 92 | uint16_t mgos_mqtt_conn_pubv(struct mgos_mqtt_conn *c, const char *topic, 93 | int qos, bool retain, const char *json_fmt, 94 | va_list ap); 95 | uint16_t mgos_mqtt_conn_pubf(struct mgos_mqtt_conn *c, const char *topic, 96 | int qos, bool retain, const char *json_fmt, ...); 97 | 98 | /* 99 | * Subscribe to a specific topic. 100 | * This handler will receive SUBACK - when first subscribed to the topic, 101 | * PUBLISH - for messages published to this topic. 102 | */ 103 | void mgos_mqtt_conn_sub(struct mgos_mqtt_conn *c, const char *topic, int qos, 104 | mg_event_handler_t handler, void *user_data); 105 | 106 | /* 107 | * Unsubscribe from a specific topic. 108 | */ 109 | bool mgos_mqtt_conn_unsub(struct mgos_mqtt_conn *c, const char *topic); 110 | 111 | /* 112 | * Returns number of pending bytes to send. 113 | */ 114 | size_t mgos_mqtt_conn_num_unsent_bytes(struct mgos_mqtt_conn *c); 115 | 116 | /* 117 | * Set maximum QOS level that is supported by server: 0, 1 or 2. 118 | * Some servers, particularly AWS GreenGrass, accept only QoS0 transactions. 119 | * An attempt to use any other QoS results into silent disconnect. 120 | * Therefore, instead of forcing all client code to track such server's quirks, 121 | * we add mechanism to transparently downgrade the QoS. 122 | */ 123 | void mgos_mqtt_conn_set_max_qos(struct mgos_mqtt_conn *c, int qos); 124 | 125 | /* 126 | * (Re)configure MQTT. 127 | */ 128 | bool mgos_mqtt_conn_set_config(struct mgos_mqtt_conn *c, 129 | const struct mgos_config_mqtt *cfg); 130 | 131 | #ifdef __cplusplus 132 | } 133 | #endif 134 | -------------------------------------------------------------------------------- /mjs_fs/api_mqtt.js: -------------------------------------------------------------------------------- 1 | let MQTT = { 2 | _sub: ffi('void mgos_mqtt_sub(char *, void (*)(void *, void *, int, void *, int, userdata), userdata)'), 3 | _subf: function(conn, topic, len1, msg, len2, ud) { 4 | return ud.cb(conn, mkstr(topic, len1), mkstr(msg, len2), ud.ud); 5 | }, 6 | 7 | // ## **`MQTT.isConnected()`** 8 | // Return value: true if MQTT connection is up, false otherwise. 9 | isConnected: ffi('bool mgos_mqtt_global_is_connected()'), 10 | 11 | // ## **`MQTT.sub(topic, handler)`** 12 | // Subscribe to a topic, and call given handler function when message arrives. 13 | // A handler receives 4 parameters: MQTT connection, topic name, 14 | // message, and userdata. 15 | // Return value: none. 16 | // 17 | // Example: 18 | // ```javascript 19 | // load('api_mqtt.js'); 20 | // MQTT.sub('my/topic/#', function(conn, topic, msg) { 21 | // print('Topic:', topic, 'message:', msg); 22 | // }, null); 23 | // ``` 24 | sub: function(topic, cb, ud) { 25 | return this._sub(topic, this._subf, { cb: cb, ud: ud }); 26 | }, 27 | 28 | _pub: ffi('int mgos_mqtt_pub(char *, void *, int, int, bool)'), 29 | 30 | // ## **`MQTT.pub(topic, message, qos, retain)`** 31 | // Publish message to a topic. If `qos` is not specified, it defaults to 0. 32 | // If `retain` is not specified, it defaults to `false`. 33 | // Return value: 0 on failure (e.g. no connection to server), 1 on success. 34 | // 35 | // Example - send MQTT message on button press, with QoS 1, no retain: 36 | // ```javascript 37 | // load('api_mqtt.js'); 38 | // load('api_gpio.js'); 39 | // let pin = 0, topic = 'my/topic'; 40 | // GPIO.set_button_handler(pin, GPIO.PULL_UP, GPIO.INT_EDGE_NEG, 200, function() { 41 | // let res = MQTT.pub('my/topic', JSON.stringify({ a: 1, b: 2 }), 1); 42 | // print('Published:', res ? 'yes' : 'no'); 43 | // }, null); 44 | // ``` 45 | pub: function(t, m, qos, retain) { 46 | qos = qos || 0; 47 | return this._pub(t, m, m.length, qos, retain || false); 48 | }, 49 | 50 | // ## **`MQTT.setEventHandler(handler, userdata)`** 51 | // Set MQTT connection event handler. Event handler is 52 | // `ev_handler(conn, ev, edata)`, where `conn` is an opaque connection handle, 53 | // `ev` is an event number, `edata` is an event-specific data. 54 | // `ev` values could be low-level network events, like `Net.EV_CLOSE` 55 | // or `Net.EV_POLL`, or MQTT specific events, like `MQTT.EV_CONNACK`. 56 | // 57 | // Example: 58 | // ```javascript 59 | // MQTT.setEventHandler(function(conn, ev, edata) { 60 | // if (ev !== 0) print('MQTT event handler: got', ev); 61 | // }, null); 62 | // ``` 63 | setEventHandler: ffi('void mgos_mqtt_add_global_handler(void (*)(void *, int, void *, userdata), userdata)'), 64 | 65 | // Event codes. 66 | EV_CONNACK: 202, // Connection to broker has been established. 67 | EV_PUBLISH: 203, // A message has been published to one of the topics we are subscribed to. 68 | EV_PUBACK: 204, // Ack for publishing of a message with QoS > 0. 69 | EV_SUBACK: 209, // Ack for a subscribe request. 70 | EV_UNSUBACK: 211, // Ack for an unsubscribe request. 71 | EV_CLOSE: 5, // Connection to broker was closed. 72 | }; 73 | -------------------------------------------------------------------------------- /mos.yml: -------------------------------------------------------------------------------- 1 | author: mongoose-os 2 | description: MQTT protocol support 3 | type: lib 4 | version: 1.0 5 | manifest_version: 2017-09-29 6 | sources: 7 | - src 8 | includes: 9 | - include 10 | config_schema: 11 | # NB: mqtt and mqtt1 must be identical. 12 | - ["mqtt", "o", {title: "MQTT settings"}] 13 | - ["mqtt.enable", "b", false, {title: "Enable MQTT"}] 14 | - ["mqtt.server", "s", "iot.eclipse.org:1883", {title: "MQTT server"}] 15 | - ["mqtt.client_id", "s", "", {title: "ClientID t send to the broker. Defaults to device.id."}] 16 | - ["mqtt.user", "s", "", {title: "User name"}] 17 | - ["mqtt.pass", "s", "", {title: "Password"}] 18 | - ["mqtt.reconnect_timeout_min", "d", 2.0, {title: "Starting reconnect timeout"}] 19 | - ["mqtt.reconnect_timeout_max", "d", 60.0, {title: "Maximum reconnect timeout"}] 20 | - ["mqtt.ssl_cert", "s", "", {title: "Client certificate to present to the server"}] 21 | - ["mqtt.ssl_key", "s", "", {title: "Private key corresponding to the certificate"}] 22 | - ["mqtt.ssl_ca_cert", "s", "", {title: "Verify server certificate using this CA bundle"}] 23 | - ["mqtt.ssl_cipher_suites", "s", "", {title: "Cipher suites to offer to the server"}] 24 | - ["mqtt.ssl_psk_identity", "s", "", {title: "PSK identity (must specify PSK cipher suites)"}] 25 | - ["mqtt.ssl_psk_key", "s", "", {title: "PSK key"}] 26 | - ["mqtt.clean_session", "b", true, {title: "Clean Session"}] 27 | - ["mqtt.keep_alive", "i", 60, {title: "Keep alive interval"}] 28 | - ["mqtt.will_topic", "s", "", {title: "Will topic"}] 29 | - ["mqtt.will_message", "s", "", {title: "Will message"}] 30 | - ["mqtt.will_retain", "b", false, {title: "Will retain flag"}] 31 | - ["mqtt.max_qos", "i", 2, {title: "Limit QoS of outgoing messages to at most this"}] 32 | - ["mqtt.recv_mbuf_limit", "i", 3072, {title: "Limit recv buffer size"}] 33 | - ["mqtt.require_time", "b", false, {title: "Only connect if (when) wall time is set"}] 34 | - ["mqtt.cloud_events", "b", true, {title: "Trigger cloud events when connected / disconnected"}] 35 | - ["mqtt.max_queue_length", "i", 5, {title: "Maximum queue length for buffering QoS 1+ messages. 0 to disable queue."}] 36 | - ["mqtt.ws_enable", "b", false, {title: "Enable WebSocket encapsulation"}] 37 | - ["mqtt.ws_path", "s", "/mqtt", {title: "Path to use for WebSocket handshake"}] 38 | - ["mqtt.debug_use_log_level", "b", false, {title: "Use the cs_log_level enum instead of stdout/stderr flag"}] 39 | # Alternative MQTT configuration. If enabled, client will alternate between mqtt and mqtt1 40 | # when unable to connect. 41 | - ["mqtt1", "mqtt", {title: "Backup MQTT settings"}] 42 | - ["mqtt1.enable", false] 43 | - ["mqtt1.server", ""] 44 | - ["debug.stdout_topic", "s", "", {title: "MQTT topic to publish STDOUT to"}] 45 | - ["debug.stderr_topic", "s", "", {title: "MQTT topic to publish STDERR to"}] 46 | cdefs: 47 | MG_ENABLE_MQTT: 1 48 | tags: 49 | - mqtt 50 | - c 51 | - js 52 | - net 53 | - docs:net:MQTT 54 | 55 | # Temporary, while root manifest change propagates (2018/03/29). 56 | libs: 57 | - location: https://github.com/mongoose-os-libs/core 58 | -------------------------------------------------------------------------------- /src/mgos_mqtt.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_mqtt.h" 19 | 20 | #include "mgos.h" 21 | #include "mgos_mqtt_conn.h" 22 | #include "mgos_sys_config.h" 23 | 24 | #ifndef MGOS_MQTT_LOG_PUSHBACK_THRESHOLD 25 | #define MGOS_MQTT_LOG_PUSHBACK_THRESHOLD 2048 26 | #endif 27 | 28 | #ifndef MGOS_MQTT_SUBSCRIBE_QOS 29 | #define MGOS_MQTT_SUBSCRIBE_QOS 1 30 | #endif 31 | 32 | static struct mgos_mqtt_conn *s_conn = NULL; 33 | 34 | extern uint16_t mgos_mqtt_conn_get_packet_id(struct mgos_mqtt_conn *c); 35 | uint16_t mgos_mqtt_get_packet_id(void) { 36 | return mgos_mqtt_conn_get_packet_id(s_conn); 37 | } 38 | 39 | extern void mgos_mqtt_conn_sub_s(struct mgos_mqtt_conn *c, struct mg_str topic, 40 | int qos, mg_event_handler_t handler, 41 | void *user_data); 42 | void mgos_mqtt_global_subscribe(const struct mg_str topic, 43 | mg_event_handler_t handler, void *ud) { 44 | mgos_mqtt_conn_sub_s(s_conn, topic, MGOS_MQTT_SUBSCRIBE_QOS, handler, ud); 45 | } 46 | 47 | void mgos_mqtt_add_global_handler(mg_event_handler_t handler, void *ud) { 48 | mgos_mqtt_conn_add_handler(s_conn, handler, ud); 49 | } 50 | 51 | void mgos_mqtt_set_connect_fn(mgos_mqtt_connect_fn_t fn, void *fn_arg) { 52 | mgos_mqtt_conn_set_connect_fn(s_conn, fn, fn_arg); 53 | } 54 | 55 | static void s_debug_write_cb(int ev, void *ev_data, void *userdata) { 56 | if (s_conn == NULL) return; 57 | struct mgos_debug_hook_arg *arg = (struct mgos_debug_hook_arg *) ev_data; 58 | const char *topic = (arg->fd == 1 ? mgos_sys_config_get_debug_stdout_topic() 59 | : arg->fd == 2 ? mgos_sys_config_get_debug_stderr_topic() 60 | : NULL); 61 | if (topic != NULL && 62 | mgos_mqtt_num_unsent_bytes() < MGOS_MQTT_LOG_PUSHBACK_THRESHOLD) { 63 | static uint32_t s_seq = 0; 64 | char *msg = arg->buf; 65 | 66 | int log_level = arg->fd; 67 | 68 | if (mgos_sys_config_get_mqtt_debug_use_log_level()) { 69 | log_level = (int) arg->level; // ie LL_INFO 70 | if (arg->fd == 2) { // stderr 71 | log_level = 0; // LL_ERROR 72 | } 73 | } 74 | 75 | int msg_len = mg_asprintf( 76 | &msg, MGOS_DEBUG_TMP_BUF_SIZE, "%s %u %.3lf %d|%.*s", 77 | (mgos_sys_config_get_device_id() ? mgos_sys_config_get_device_id() 78 | : "-"), 79 | (unsigned int) s_seq, mg_time(), log_level, (int) arg->len, 80 | (const char *) arg->data); 81 | if (arg->len > 0) { 82 | mgos_mqtt_conn_pub(s_conn, topic, mg_mk_str_n(msg, msg_len), 0 /* qos */, 83 | false /* retain */); 84 | s_seq++; 85 | } 86 | if (msg != arg->buf) free(msg); 87 | } 88 | 89 | (void) ev; 90 | (void) userdata; 91 | } 92 | 93 | bool mgos_mqtt_set_config(const struct mgos_config_mqtt *cfg) { 94 | if (s_conn == NULL) { 95 | s_conn = mgos_mqtt_conn_create(0, cfg); 96 | return true; 97 | } 98 | return mgos_mqtt_conn_set_config(s_conn, cfg); 99 | } 100 | 101 | bool mgos_mqtt_global_connect(void) { 102 | return mgos_mqtt_conn_connect(s_conn); 103 | } 104 | 105 | void mgos_mqtt_global_disconnect(void) { 106 | mgos_mqtt_conn_disconnect(s_conn); 107 | } 108 | 109 | bool mgos_mqtt_global_is_connected(void) { 110 | return mgos_mqtt_conn_is_connected(s_conn); 111 | } 112 | 113 | extern struct mg_connection *mgos_mqtt_conn_nc(struct mgos_mqtt_conn *c); 114 | 115 | struct mg_connection *mgos_mqtt_get_global_conn(void) { 116 | return mgos_mqtt_conn_nc(s_conn); 117 | } 118 | 119 | uint16_t mgos_mqtt_pub(const char *topic, const void *message, size_t len, 120 | int qos, bool retain) { 121 | return mgos_mqtt_conn_pub(s_conn, topic, mg_mk_str_n(message, len), qos, 122 | retain); 123 | } 124 | 125 | uint16_t mgos_mqtt_pubf(const char *topic, int qos, bool retain, 126 | const char *json_fmt, ...) { 127 | uint16_t res; 128 | va_list ap; 129 | va_start(ap, json_fmt); 130 | res = mgos_mqtt_conn_pubv(s_conn, topic, qos, retain, json_fmt, ap); 131 | va_end(ap); 132 | return res; 133 | } 134 | 135 | uint16_t mgos_mqtt_pubv(const char *topic, int qos, bool retain, 136 | const char *json_fmt, va_list ap) { 137 | return mgos_mqtt_conn_pubv(s_conn, topic, qos, retain, json_fmt, ap); 138 | } 139 | 140 | struct sub_data { 141 | sub_handler_t handler; 142 | void *user_data; 143 | }; 144 | 145 | static void mqttsubtrampoline(struct mg_connection *c, int ev, void *ev_data, 146 | void *user_data) { 147 | if (ev != MG_EV_MQTT_PUBLISH) return; 148 | struct sub_data *sd = (struct sub_data *) user_data; 149 | struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data; 150 | sd->handler(c, mm->topic.p, mm->topic.len, mm->payload.p, mm->payload.len, 151 | sd->user_data); 152 | } 153 | 154 | void mgos_mqtt_sub(const char *topic, sub_handler_t handler, void *user_data) { 155 | struct sub_data *sd = (struct sub_data *) malloc(sizeof(*sd)); 156 | sd->handler = handler; 157 | sd->user_data = user_data; 158 | mgos_mqtt_global_subscribe(mg_mk_str(topic), mqttsubtrampoline, sd); 159 | } 160 | 161 | bool mgos_mqtt_unsub(const char *topic) { 162 | return mgos_mqtt_conn_unsub(s_conn, topic); 163 | } 164 | 165 | size_t mgos_mqtt_num_unsent_bytes(void) { 166 | return mgos_mqtt_conn_num_unsent_bytes(s_conn); 167 | } 168 | 169 | bool mgos_mqtt_init(void) { 170 | if (mgos_sys_config_get_debug_stdout_topic() != NULL) { 171 | char *stdout_topic = strdup(mgos_sys_config_get_debug_stdout_topic()); 172 | mgos_expand_mac_address_placeholders(stdout_topic); 173 | mgos_sys_config_set_debug_stdout_topic(stdout_topic); 174 | free(stdout_topic); 175 | } 176 | if (mgos_sys_config_get_debug_stderr_topic() != NULL) { 177 | char *stderr_topic = strdup(mgos_sys_config_get_debug_stderr_topic()); 178 | mgos_expand_mac_address_placeholders(stderr_topic); 179 | mgos_sys_config_set_debug_stderr_topic(stderr_topic); 180 | free(stderr_topic); 181 | } 182 | 183 | if (mgos_sys_config_get_mqtt_enable()) { 184 | s_conn = mgos_mqtt_conn_create2(0, mgos_sys_config_get_mqtt(), 185 | mgos_sys_config_get_mqtt1()); 186 | } 187 | 188 | mgos_event_add_handler(MGOS_EVENT_LOG, s_debug_write_cb, NULL); 189 | 190 | return true; 191 | } 192 | -------------------------------------------------------------------------------- /src/mgos_mqtt_conn.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Deomid "rojer" Ryabkov 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_mqtt_conn_internal.h" 19 | 20 | #include 21 | 22 | #include "mgos.h" 23 | #include "mgos_debug.h" 24 | #include "mgos_event.h" 25 | #include "mgos_mongoose.h" 26 | 27 | static void mgos_mqtt_conn_net_ev(int ev, void *evd, void *arg); 28 | static void mgos_mqtt_free_cfg(struct mgos_config_mqtt *cfg); 29 | 30 | static struct mgos_config_mqtt *mgos_mqtt_copy_cfg( 31 | const struct mgos_config_mqtt *cfg) { 32 | if (cfg == NULL) return NULL; 33 | struct mgos_config_mqtt *new_cfg = NULL; 34 | if (cfg == mgos_sys_config_get_mqtt() || cfg == mgos_sys_config_get_mqtt1()) { 35 | new_cfg = (struct mgos_config_mqtt *) cfg; 36 | } else { 37 | new_cfg = (struct mgos_config_mqtt *) calloc(1, sizeof(*new_cfg)); 38 | if (new_cfg == NULL || !mgos_config_mqtt_copy(cfg, new_cfg)) { 39 | mgos_mqtt_free_cfg(new_cfg); 40 | free(new_cfg); 41 | new_cfg = NULL; 42 | } else { 43 | } 44 | } 45 | return new_cfg; 46 | } 47 | 48 | static void mgos_mqtt_free_cfg(struct mgos_config_mqtt *cfg) { 49 | if (cfg == NULL) return; 50 | if (cfg == mgos_sys_config_get_mqtt() || cfg == mgos_sys_config_get_mqtt1()) 51 | return; 52 | mgos_config_mqtt_free(cfg); 53 | free(cfg); 54 | } 55 | 56 | struct mgos_mqtt_conn *mgos_mqtt_conn_create2( 57 | int conn_id, const struct mgos_config_mqtt *cfg0, 58 | const struct mgos_config_mqtt *cfg1) { 59 | struct mgos_mqtt_conn *c = (struct mgos_mqtt_conn *) calloc(1, sizeof(*c)); 60 | if (c == NULL) return NULL; 61 | c->conn_id = (int16_t) conn_id; 62 | c->cfg0 = mgos_mqtt_copy_cfg(cfg0); 63 | c->cfg1 = mgos_mqtt_copy_cfg(cfg1); 64 | c->cfg = c->cfg0; 65 | c->reconnect_timer_id = MGOS_INVALID_TIMER_ID; 66 | c->max_qos = -1; 67 | STAILQ_INIT(&c->queue); 68 | mgos_event_add_handler(MGOS_NET_EV_IP_ACQUIRED, mgos_mqtt_conn_net_ev, c); 69 | return c; 70 | } 71 | 72 | struct mgos_mqtt_conn *mgos_mqtt_conn_create( 73 | int conn_id, const struct mgos_config_mqtt *cfg) { 74 | return mgos_mqtt_conn_create2(conn_id, cfg, NULL); 75 | } 76 | 77 | struct mgos_mqtt_conn *mgos_mqtt_conn_from_nc(struct mg_connection *nc) { 78 | return (nc != NULL ? (struct mgos_mqtt_conn *) nc->user_data : NULL); 79 | } 80 | 81 | struct mg_connection *mgos_mqtt_conn_nc(struct mgos_mqtt_conn *c) { 82 | return (c != NULL ? c->nc : NULL); 83 | } 84 | 85 | static int adjust_qos(const struct mgos_mqtt_conn *c, int qos) { 86 | int max_qos = c->max_qos; 87 | if (max_qos < 0 && c->cfg != NULL) max_qos = c->cfg->max_qos; 88 | if (max_qos < 0) return qos; 89 | return MIN(qos, max_qos); 90 | } 91 | 92 | uint16_t mgos_mqtt_conn_get_packet_id(struct mgos_mqtt_conn *c) { 93 | while (true) { 94 | c->packet_id++; 95 | if (c->packet_id == 0) continue; 96 | struct mgos_mqtt_conn_queue_entry *qe = NULL; 97 | STAILQ_FOREACH(qe, &c->queue, next) { 98 | if (qe->packet_id == c->packet_id) break; 99 | } 100 | if (qe == NULL) break; 101 | } 102 | return c->packet_id; 103 | } 104 | 105 | static void mgos_mqtt_conn_queue_entry_free( 106 | struct mgos_mqtt_conn_queue_entry *qe) { 107 | free(qe->topic); 108 | mg_strfree(&qe->msg); 109 | free(qe); 110 | } 111 | 112 | static void mgos_mqtt_conn_queue_remove(struct mgos_mqtt_conn *c, 113 | uint16_t packet_id) { 114 | struct mgos_mqtt_conn_queue_entry *qe, *qet; 115 | STAILQ_FOREACH_SAFE(qe, &c->queue, next, qet) { 116 | if (qe->packet_id != packet_id) continue; 117 | STAILQ_REMOVE(&c->queue, qe, mgos_mqtt_conn_queue_entry, next); 118 | LOG(LL_DEBUG, ("MQTT%d ack %d", c->conn_id, qe->packet_id)); 119 | mgos_mqtt_conn_queue_entry_free(qe); 120 | } 121 | } 122 | 123 | static bool call_topic_handler(struct mgos_mqtt_conn *c, int ev, 124 | void *ev_data) { 125 | bool handled = false; 126 | struct mg_mqtt_message *msg = (struct mg_mqtt_message *) ev_data; 127 | struct mgos_mqtt_subscription *s; 128 | SLIST_FOREACH(s, &c->subscriptions, next) { 129 | if ((ev == MG_EV_MQTT_SUBACK && s->sub_id == msg->message_id) || 130 | mg_mqtt_match_topic_expression(s->topic, msg->topic)) { 131 | if (!handled && ev == MG_EV_MQTT_PUBLISH && msg->qos > 0) { 132 | mg_mqtt_puback(c->nc, msg->message_id); 133 | } 134 | s->handler(c->nc, ev, ev_data, s->user_data); 135 | handled = true; 136 | } 137 | } 138 | return handled; 139 | } 140 | 141 | static void call_conn_handlers(struct mgos_mqtt_conn *c, int ev, 142 | void *ev_data) { 143 | struct mgos_mqtt_conn_handler *ch; 144 | SLIST_FOREACH(ch, &c->conn_handlers, next) { 145 | ch->handler(c->nc, ev, ev_data, ch->user_data); 146 | } 147 | } 148 | 149 | static void do_pub(struct mgos_mqtt_conn *c, uint16_t packet_id, 150 | const char *topic, struct mg_str msg, int flags) { 151 | int qos = MG_MQTT_GET_QOS(flags); 152 | bool dup = ((flags & MG_MQTT_DUP) != 0); 153 | bool retain = ((flags & MG_MQTT_RETAIN) != 0); 154 | LOG(LL_DEBUG, 155 | ("MQTT%d pub -> %d %s @ %d%s%s (%d): [%.*s]", c->conn_id, packet_id, 156 | topic, qos, (dup ? " DUP" : ""), (retain ? " RETAIN" : ""), 157 | (int) msg.len, (int) msg.len, msg.p)); 158 | mg_mqtt_publish(c->nc, topic, packet_id, flags, msg.p, msg.len); 159 | (void) qos; 160 | (void) dup; 161 | (void) retain; 162 | } 163 | 164 | static void do_sub(struct mgos_mqtt_conn *c, struct mgos_mqtt_subscription *s) { 165 | struct mg_mqtt_topic_expression te = {.topic = s->topic.p, 166 | .qos = adjust_qos(c, s->qos)}; 167 | s->sub_id = mgos_mqtt_conn_get_packet_id(c); 168 | mg_mqtt_subscribe(c->nc, &te, 1, s->sub_id); 169 | LOG(LL_INFO, ("MQTT%d sub %s @ %d", c->conn_id, te.topic, te.qos)); 170 | } 171 | 172 | void mgos_mqtt_ev(struct mg_connection *nc, int ev, void *ev_data, 173 | void *user_data) { 174 | struct mgos_mqtt_conn *c = (struct mgos_mqtt_conn *) user_data; 175 | if (c == NULL || nc != c->nc) { 176 | nc->flags |= MG_F_CLOSE_IMMEDIATELY; 177 | return; 178 | } 179 | if (ev > MG_MQTT_EVENT_BASE) { 180 | LOG(LL_DEBUG, ("MQTT%d event: %d", c->conn_id, ev)); 181 | } 182 | const struct mgos_config_mqtt *cfg = c->cfg; 183 | const struct mg_mqtt_message *msg = (struct mg_mqtt_message *) ev_data; 184 | switch (ev) { 185 | case MG_EV_CONNECT: { 186 | int status = *((int *) ev_data); 187 | LOG(LL_INFO, ("MQTT%d %s connected %s (%d)", c->conn_id, 188 | (cfg->ws_enable ? "WS" : "TCP"), 189 | (status == 0 ? "ok" : "error"), status)); 190 | if (status != 0) { 191 | if (cfg->cloud_events) { 192 | struct mgos_cloud_arg arg = {.type = MGOS_CLOUD_MQTT}; 193 | mgos_event_trigger(MGOS_EVENT_CLOUD_CONNECTERROR, &arg); 194 | } 195 | break; 196 | } 197 | struct mg_send_mqtt_handshake_opts opts; 198 | memset(&opts, 0, sizeof(opts)); 199 | opts.user_name = cfg->user; 200 | opts.password = cfg->pass; 201 | if (cfg->clean_session) { 202 | opts.flags |= MG_MQTT_CLEAN_SESSION; 203 | } 204 | opts.keep_alive = cfg->keep_alive; 205 | opts.will_topic = cfg->will_topic; 206 | opts.will_message = cfg->will_message; 207 | if (cfg->will_retain) { 208 | opts.flags |= MG_MQTT_WILL_RETAIN; 209 | } 210 | const char *client_id = 211 | (cfg->client_id != NULL ? cfg->client_id 212 | : mgos_sys_config_get_device_id()); 213 | if (c->connect_fn != NULL) { 214 | c->connect_fn(nc, client_id, &opts, c->connect_fn_arg); 215 | } else { 216 | mg_send_mqtt_handshake_opt(nc, client_id, opts); 217 | } 218 | break; 219 | } 220 | case MG_EV_CLOSE: { 221 | LOG(LL_INFO, ("MQTT%d disconnected", c->conn_id)); 222 | c->nc = NULL; 223 | bool connected = c->connected; 224 | c->connected = false; 225 | if (connected) { 226 | if (cfg->cloud_events) { 227 | struct mgos_cloud_arg arg = {.type = MGOS_CLOUD_MQTT}; 228 | mgos_event_trigger(MGOS_EVENT_CLOUD_DISCONNECTED, &arg); 229 | } 230 | call_conn_handlers(c, ev, ev_data); 231 | } 232 | mgos_mqtt_conn_reconnect(c); 233 | break; 234 | } 235 | case MG_EV_POLL: { 236 | call_conn_handlers(c, ev, ev_data); 237 | break; 238 | } 239 | case MG_EV_MQTT_CONNACK: { 240 | int code = ((struct mg_mqtt_message *) ev_data)->connack_ret_code; 241 | LOG((code == 0 ? LL_INFO : LL_ERROR), 242 | ("MQTT%d CONNACK %d", c->conn_id, code)); 243 | if (code == 0) { 244 | c->connected = true; 245 | c->reconnect_timeout_ms = 0; 246 | if (cfg->cloud_events) { 247 | struct mgos_cloud_arg arg = {.type = MGOS_CLOUD_MQTT}; 248 | mgos_event_trigger(MGOS_EVENT_CLOUD_CONNECTED, &arg); 249 | } 250 | struct mgos_mqtt_subscription *s; 251 | SLIST_FOREACH(s, &c->subscriptions, next) { 252 | do_sub(c, s); 253 | } 254 | if (!STAILQ_EMPTY(&c->queue)) { 255 | struct mgos_mqtt_conn_queue_entry *qe; 256 | STAILQ_FOREACH(qe, &c->queue, next) { 257 | qe->flags &= ~MG_MQTT_DUP; 258 | } 259 | c->queue_drained = false; 260 | } else { 261 | c->queue_drained = true; 262 | } 263 | call_conn_handlers(c, ev, ev_data); 264 | } else { 265 | nc->flags |= MG_F_CLOSE_IMMEDIATELY; 266 | } 267 | break; 268 | } 269 | /* Delegate almost all MQTT events to the user's handler */ 270 | case MG_EV_MQTT_SUBACK: 271 | case MG_EV_MQTT_PUBLISH: 272 | if (call_topic_handler(c, ev, ev_data)) break; 273 | call_conn_handlers(c, ev, ev_data); 274 | break; 275 | case MG_EV_MQTT_PUBACK: 276 | mgos_mqtt_conn_queue_remove(c, msg->message_id); 277 | /* fall through */ 278 | case MG_EV_MQTT_CONNECT: 279 | case MG_EV_MQTT_PUBREL: 280 | case MG_EV_MQTT_PUBCOMP: 281 | case MG_EV_MQTT_SUBSCRIBE: 282 | case MG_EV_MQTT_UNSUBSCRIBE: 283 | case MG_EV_MQTT_UNSUBACK: 284 | case MG_EV_MQTT_PINGREQ: 285 | case MG_EV_MQTT_DISCONNECT: 286 | call_conn_handlers(c, ev, ev_data); 287 | break; 288 | case MG_EV_MQTT_PUBREC: { 289 | mg_mqtt_pubrel(nc, msg->message_id); 290 | call_conn_handlers(c, ev, ev_data); 291 | mgos_mqtt_conn_queue_remove(c, msg->message_id); 292 | break; 293 | } 294 | } 295 | /* If we have received some sort of MQTT message, queue is not empty 296 | * and we have nothing else to send, drain the queue. 297 | * This will usually be CONNACK and PUBACK, but can be PINGRESP as well. */ 298 | if ((ev == MG_EV_MQTT_CONNACK || ev == MG_EV_MQTT_PUBACK || 299 | ev == MG_EV_MQTT_PUBREC || ev == MG_EV_MQTT_PINGRESP) && 300 | c->connected) { 301 | struct mgos_mqtt_conn_queue_entry *qe; 302 | if (ev == MG_EV_MQTT_PINGRESP) { 303 | /* PINGRESP allows re-retransmission, i.e. we will re-send messages 304 | * we have already sent in this connection and that server has not yet 305 | * acked for some reason. This rarely (if ever) happens. */ 306 | STAILQ_FOREACH(qe, &c->queue, next) { 307 | qe->flags &= ~MG_MQTT_DUP; 308 | } 309 | } 310 | if (!STAILQ_EMPTY(&c->queue)) { 311 | STAILQ_FOREACH(qe, &c->queue, next) { 312 | if (!(qe->flags & MG_MQTT_DUP)) break; 313 | } 314 | if (qe != NULL) { 315 | qe->flags |= MG_MQTT_DUP; 316 | do_pub(c, qe->packet_id, qe->topic, qe->msg, qe->flags); 317 | } 318 | } else if (!c->queue_drained && STAILQ_EMPTY(&c->queue)) { 319 | // The queue has been drained at least once, immediate publishing is 320 | // allowed. 321 | c->queue_drained = true; 322 | LOG(LL_DEBUG, ("MQTT%d queue drained", c->conn_id)); 323 | } 324 | } 325 | } 326 | 327 | void mgos_mqtt_ws_ev(struct mg_connection *nc, int ev, void *ev_data, 328 | void *user_data); 329 | 330 | void mgos_mqtt_conn_add_handler(struct mgos_mqtt_conn *c, 331 | mg_event_handler_t handler, void *user_data) { 332 | if (c == NULL) return; 333 | struct mgos_mqtt_conn_handler *ch = calloc(1, sizeof(*ch)); 334 | ch->handler = handler; 335 | ch->user_data = user_data; 336 | SLIST_INSERT_HEAD(&c->conn_handlers, ch, next); 337 | } 338 | 339 | void mgos_mqtt_conn_set_connect_fn(struct mgos_mqtt_conn *c, 340 | mgos_mqtt_connect_fn_t fn, void *fn_arg) { 341 | if (c == NULL) return; 342 | c->connect_fn = fn; 343 | c->connect_fn_arg = fn_arg; 344 | } 345 | 346 | static void mgos_mqtt_conn_net_ev(int ev, void *evd, void *arg) { 347 | struct mgos_mqtt_conn *c = arg; 348 | if (c->reconnect_timeout_ms < 0) { 349 | return; // User doesn't want us to reconnect. 350 | } 351 | mgos_mqtt_conn_connect(c); 352 | (void) evd; 353 | (void) ev; 354 | } 355 | 356 | void mgos_mqtt_conn_set_max_qos(struct mgos_mqtt_conn *c, int qos) { 357 | if (c == NULL || c->cfg == NULL || c->cfg->max_qos == qos) return; 358 | c->max_qos = (int16_t) qos; 359 | } 360 | 361 | bool mgos_mqtt_conn_set_config(struct mgos_mqtt_conn *c, 362 | const struct mgos_config_mqtt *cfg) { 363 | if (c == NULL) return false; 364 | bool ret = false; 365 | struct mgos_config_mqtt *new_cfg = NULL; 366 | if (!cfg->enable) { 367 | ret = true; 368 | goto out; 369 | } 370 | if (cfg->server == NULL) { 371 | LOG(LL_ERROR, ("MQTT requires server name")); 372 | goto out; 373 | } 374 | new_cfg = mgos_mqtt_copy_cfg(cfg); 375 | ret = (new_cfg != NULL); 376 | 377 | out: 378 | if (ret) { 379 | const struct mgos_config_mqtt *old_cfg = c->cfg; 380 | c->cfg = new_cfg; 381 | if (c->nc != NULL) { 382 | c->nc->flags |= MG_F_CLOSE_IMMEDIATELY; 383 | c->nc = NULL; 384 | } 385 | mgos_mqtt_free_cfg((struct mgos_config_mqtt *) old_cfg); 386 | } else { 387 | mgos_mqtt_free_cfg(new_cfg); 388 | } 389 | return ret; 390 | } 391 | 392 | static bool mgos_mqtt_time_ok(struct mgos_mqtt_conn *c) { 393 | if (c->cfg == NULL) return false; 394 | if (!c->cfg->require_time) return true; 395 | if (mg_time() < 1500000000) { 396 | LOG(LL_ERROR, ("Time is not set, not connecting")); 397 | return false; 398 | } 399 | return true; 400 | } 401 | 402 | bool mgos_mqtt_conn_connect(struct mgos_mqtt_conn *c) { 403 | bool ret = true; 404 | char *server = NULL; 405 | const char *err_str = NULL; 406 | struct mg_connect_opts opts = {0}; 407 | 408 | if (c == NULL || c->cfg == NULL || !c->cfg->enable || 409 | c->cfg->server == NULL) { 410 | return false; 411 | } 412 | 413 | /* If we're already connected, do nothing */ 414 | if (c->nc != NULL) return true; 415 | 416 | if (c->reconnect_timeout_ms < 0) { 417 | c->reconnect_timeout_ms = 0; 418 | } 419 | 420 | if (!mgos_mqtt_time_ok(c)) { 421 | mgos_mqtt_conn_reconnect(c); 422 | return false; 423 | } 424 | 425 | const struct mgos_config_mqtt *cfg = c->cfg; 426 | opts.error_string = &err_str; 427 | #if MG_ENABLE_SSL 428 | opts.ssl_cert = cfg->ssl_cert; 429 | opts.ssl_key = cfg->ssl_key; 430 | opts.ssl_ca_cert = cfg->ssl_ca_cert; 431 | opts.ssl_cipher_suites = cfg->ssl_cipher_suites; 432 | opts.ssl_psk_identity = cfg->ssl_psk_identity; 433 | opts.ssl_psk_key = cfg->ssl_psk_key; 434 | #endif 435 | if (strchr(cfg->server, ':') == NULL) { 436 | int port = (cfg->ssl_ca_cert != NULL ? 8883 : 1883); 437 | mg_asprintf(&server, 0, "%s:%d", cfg->server, port); 438 | if (server == NULL) return false; 439 | } else { 440 | server = strdup(cfg->server); 441 | } 442 | LOG(LL_INFO, ("MQTT%d connecting to %s", c->conn_id, server)); 443 | struct mgos_cloud_arg arg = {.type = MGOS_CLOUD_MQTT}; 444 | mgos_event_trigger(MGOS_EVENT_CLOUD_CONNECTING, &arg); 445 | 446 | c->connected = false; 447 | mg_event_handler_t h = (cfg->ws_enable ? mgos_mqtt_ws_ev : mgos_mqtt_ev); 448 | c->nc = mg_connect_opt(mgos_get_mgr(), server, h, c, opts); 449 | if (c->nc != NULL) { 450 | if (cfg->ws_enable) { 451 | mg_set_protocol_http_websocket(c->nc); 452 | } else { 453 | mg_set_protocol_mqtt(c->nc); 454 | } 455 | c->nc->recv_mbuf_limit = cfg->recv_mbuf_limit; 456 | } else { 457 | LOG(LL_ERROR, ("Error: %s", err_str)); 458 | mgos_mqtt_conn_reconnect(c); 459 | ret = false; 460 | } 461 | free(server); 462 | return ret; 463 | } 464 | 465 | void mgos_mqtt_conn_disconnect(struct mgos_mqtt_conn *c) { 466 | if (c == NULL) return; 467 | c->reconnect_timeout_ms = -1; // Prevent reconnect. 468 | if (c->nc != NULL) { 469 | LOG(LL_INFO, ("MQTT%d disconnect", c->conn_id)); 470 | mg_mqtt_disconnect(c->nc); 471 | c->nc->flags |= MG_F_SEND_AND_CLOSE; 472 | } 473 | } 474 | 475 | bool mgos_mqtt_conn_is_connected(struct mgos_mqtt_conn *c) { 476 | return (c != NULL && c->connected); 477 | } 478 | 479 | static void reconnect_timer_cb(void *arg) { 480 | struct mgos_mqtt_conn *c = arg; 481 | c->reconnect_timer_id = MGOS_INVALID_TIMER_ID; 482 | mgos_mqtt_conn_connect(c); 483 | } 484 | 485 | static void mqtt_switch_config(struct mgos_mqtt_conn *c) { 486 | const struct mgos_config_mqtt *cfg; 487 | if (c->cfg == c->cfg0) { 488 | cfg = c->cfg1; 489 | } else if (c->cfg == c->cfg1) { 490 | cfg = c->cfg0; 491 | } else { 492 | /* User set a custom config - don't mess with it. */ 493 | return; 494 | } 495 | if (cfg != NULL && cfg->enable) { 496 | c->cfg = cfg; 497 | c->reconnect_timeout_ms = cfg->reconnect_timeout_min * 1000; 498 | } 499 | } 500 | 501 | void mgos_mqtt_conn_reconnect(struct mgos_mqtt_conn *c) { 502 | int rt_ms; 503 | if (c == NULL || c->cfg == NULL || c->cfg->server == NULL || 504 | c->reconnect_timeout_ms < 0) { 505 | return; 506 | } 507 | 508 | if (c->reconnect_timeout_ms >= c->cfg->reconnect_timeout_max * 1000) { 509 | mqtt_switch_config(c); 510 | } 511 | 512 | const struct mgos_config_mqtt *cfg = c->cfg; 513 | 514 | if (c->reconnect_timeout_ms <= 0) c->reconnect_timeout_ms = 1000; 515 | 516 | rt_ms = c->reconnect_timeout_ms * 2; 517 | 518 | if (rt_ms < cfg->reconnect_timeout_min * 1000) { 519 | rt_ms = cfg->reconnect_timeout_min * 1000; 520 | } 521 | if (rt_ms > cfg->reconnect_timeout_max * 1000) { 522 | rt_ms = cfg->reconnect_timeout_max * 1000; 523 | } 524 | c->reconnect_timeout_ms = rt_ms; 525 | /* Fuzz the time a little. */ 526 | rt_ms = (int) mgos_rand_range(rt_ms * 0.9, rt_ms * 1.1); 527 | LOG(LL_INFO, ("MQTT%d connecting after %d ms", c->conn_id, rt_ms)); 528 | mgos_clear_timer(c->reconnect_timer_id); 529 | c->reconnect_timer_id = mgos_set_timer(rt_ms, 0, reconnect_timer_cb, c); 530 | } 531 | 532 | uint16_t mgos_mqtt_conn_pub(struct mgos_mqtt_conn *c, const char *topic, 533 | struct mg_str msg, int qos, bool retain) { 534 | if (c == NULL) return 0; 535 | bool published = false; 536 | uint16_t packet_id = mgos_mqtt_conn_get_packet_id(c); 537 | int flags = MG_MQTT_QOS(adjust_qos(c, qos)); 538 | if (retain) flags |= MG_MQTT_RETAIN; 539 | // If we are connected and have drained the initial queue, publish immediately 540 | // On initial connection we want queue to be processed in order, without 541 | // later entries jumping ahead of the queue - this could cause out of order 542 | // shadow updates, for example, where a newer update would be overridden by 543 | // an entry replayed from the queue. 544 | if (c->connected && (qos == 0 || c->queue_drained)) { 545 | do_pub(c, packet_id, topic, msg, flags); 546 | published = true; 547 | } 548 | if (qos > 0 && c->cfg->max_queue_length > 0) { 549 | size_t qlen = 0; 550 | struct mgos_mqtt_conn_queue_entry *qe; 551 | STAILQ_FOREACH(qe, &c->queue, next) { 552 | qlen++; 553 | } 554 | while (qlen >= (size_t) c->cfg->max_queue_length) { 555 | LOG(LL_ERROR, ("MQTT%d queue overflow!", c->conn_id)); 556 | qe = STAILQ_FIRST(&c->queue); 557 | STAILQ_REMOVE_HEAD(&c->queue, next); 558 | mgos_mqtt_conn_queue_entry_free(qe); 559 | qlen--; 560 | } 561 | qe = calloc(1, sizeof(*qe)); 562 | if (qe == NULL) goto out; 563 | qe->topic = strdup(topic); 564 | qe->packet_id = packet_id; 565 | qe->flags = (uint16_t) flags; 566 | qe->msg = mg_strdup(msg); 567 | // Prevent immediate retry. 568 | if (published) qe->flags |= MG_MQTT_DUP; 569 | STAILQ_INSERT_TAIL(&c->queue, qe, next); 570 | } 571 | out: 572 | return packet_id; 573 | } 574 | 575 | uint16_t mgos_mqtt_conn_pubv(struct mgos_mqtt_conn *c, const char *topic, 576 | int qos, bool retain, const char *json_fmt, 577 | va_list ap) { 578 | if (c == NULL) return 0; 579 | uint16_t res = 0; 580 | char *msg = json_vasprintf(json_fmt, ap); 581 | if (msg != NULL) { 582 | res = mgos_mqtt_conn_pub(c, topic, mg_mk_str(msg), qos, retain); 583 | free(msg); 584 | } 585 | return res; 586 | } 587 | 588 | uint16_t mgos_mqtt_conn_pubf(struct mgos_mqtt_conn *c, const char *topic, 589 | int qos, bool retain, const char *json_fmt, ...) { 590 | if (c == NULL) return 0; 591 | uint16_t res; 592 | va_list ap; 593 | va_start(ap, json_fmt); 594 | res = mgos_mqtt_conn_pubv(c, topic, qos, retain, json_fmt, ap); 595 | va_end(ap); 596 | return res; 597 | } 598 | 599 | void mgos_mqtt_conn_sub_s(struct mgos_mqtt_conn *c, struct mg_str topic, 600 | int qos, mg_event_handler_t handler, 601 | void *user_data) { 602 | if (c == NULL) return; 603 | struct mgos_mqtt_subscription *s = calloc(1, sizeof(*s)); 604 | s->topic = mg_strdup_nul(topic); 605 | s->handler = handler; 606 | s->user_data = user_data; 607 | s->qos = adjust_qos(c, qos); 608 | SLIST_INSERT_HEAD(&c->subscriptions, s, next); 609 | if (c->connected) do_sub(c, s); 610 | } 611 | 612 | void mgos_mqtt_conn_sub(struct mgos_mqtt_conn *c, const char *topic, int qos, 613 | mg_event_handler_t handler, void *user_data) { 614 | mgos_mqtt_conn_sub_s(c, mg_mk_str(topic), qos, handler, user_data); 615 | } 616 | 617 | bool mgos_mqtt_conn_unsub(struct mgos_mqtt_conn *c, const char *topic) { 618 | if (c == NULL) return false; 619 | 620 | struct mgos_mqtt_subscription *s; 621 | SLIST_FOREACH(s, &c->subscriptions, next) { 622 | if (0 == mg_vcmp(&s->topic, topic)) { 623 | LOG(LL_INFO, 624 | ("MQTT%d unsub %.*s", c->conn_id, (int) s->topic.len, s->topic.p)); 625 | if (c->connected) { 626 | mg_mqtt_unsubscribe(c->nc, (char **) &topic, 1, 627 | mgos_mqtt_conn_get_packet_id(c)); 628 | } 629 | SLIST_REMOVE(&c->subscriptions, s, mgos_mqtt_subscription, next); 630 | mg_strfree(&s->topic); 631 | free(s->user_data); 632 | free(s); 633 | return true; 634 | } 635 | } 636 | return false; 637 | } 638 | 639 | size_t mgos_mqtt_conn_num_unsent_bytes(struct mgos_mqtt_conn *c) { 640 | size_t num_bytes = 0; 641 | if (c == NULL) return 0; 642 | struct mgos_mqtt_conn_queue_entry *qe; 643 | STAILQ_FOREACH(qe, &c->queue, next) { 644 | num_bytes += qe->msg.len; 645 | } 646 | if (c->nc != NULL) num_bytes += c->nc->send_mbuf.len; 647 | return num_bytes; 648 | } 649 | -------------------------------------------------------------------------------- /src/mgos_mqtt_conn_internal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Deomid "rojer" Ryabkov 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 "common/queue.h" 21 | #include "mgos_mqtt_conn.h" 22 | #include "mgos_timers.h" 23 | 24 | struct mgos_mqtt_subscription { 25 | struct mg_str topic; 26 | uint16_t sub_id; 27 | uint8_t qos; 28 | mg_event_handler_t handler; 29 | void *user_data; 30 | SLIST_ENTRY(mgos_mqtt_subscription) next; 31 | }; 32 | 33 | struct mgos_mqtt_conn_handler { 34 | mg_event_handler_t handler; 35 | void *user_data; 36 | SLIST_ENTRY(mgos_mqtt_conn_handler) next; 37 | }; 38 | 39 | struct mgos_mqtt_conn_queue_entry { 40 | char *topic; 41 | uint16_t packet_id; 42 | // DUP flag on queue entry is used to delay re-delivery. 43 | uint16_t flags; 44 | struct mg_str msg; 45 | STAILQ_ENTRY(mgos_mqtt_conn_queue_entry) next; 46 | }; 47 | 48 | struct mgos_mqtt_ws_data; 49 | 50 | struct mgos_mqtt_conn { 51 | const struct mgos_config_mqtt *cfg; 52 | const struct mgos_config_mqtt *cfg0; 53 | const struct mgos_config_mqtt *cfg1; 54 | struct mg_connection *nc; 55 | int reconnect_timeout_ms; 56 | mgos_timer_id reconnect_timer_id; 57 | int16_t conn_id; 58 | int16_t max_qos; 59 | uint16_t packet_id; 60 | bool connected; 61 | bool queue_drained; 62 | mgos_mqtt_connect_fn_t connect_fn; 63 | void *connect_fn_arg; 64 | struct mgos_mqtt_ws_data *ws_data; 65 | SLIST_HEAD(subscriptions, mgos_mqtt_subscription) subscriptions; 66 | SLIST_HEAD(conn_handlers, mgos_mqtt_conn_handler) conn_handlers; 67 | STAILQ_HEAD(queue, mgos_mqtt_conn_queue_entry) queue; 68 | }; 69 | 70 | void mgos_mqtt_ev(struct mg_connection *nc, int ev, void *ev_data, 71 | void *user_data); 72 | void mgos_mqtt_ws_ev(struct mg_connection *nc, int ev, void *ev_data, 73 | void *user_data); 74 | void mgos_mqtt_conn_reconnect(struct mgos_mqtt_conn *c); 75 | -------------------------------------------------------------------------------- /src/mgos_mqtt_ws_conn.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Deomid "rojer" Ryabkov 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_mqtt_conn_internal.h" 19 | 20 | #include "mgos.h" 21 | #include "mgos_mongoose.h" 22 | 23 | /* 24 | * This is a WebSocket wrapper for an MQTT connection. 25 | * The outer connection handler is WS that invokes the MQTT handler. 26 | * It removes framing on the way in and adds on the way out. 27 | * State of the MQTT handler is maintained separately and is swapped in and out 28 | * before and after invocation. 29 | */ 30 | 31 | typedef void (*mg_proto_data_destructor_t)(void *); 32 | 33 | struct mgos_mqtt_ws_data { 34 | mg_event_handler_t mqtt_proto_handler, ws_proto_handler; 35 | void *mqtt_proto_data, *ws_proto_data; 36 | mg_proto_data_destructor_t mqtt_proto_data_destructor, 37 | ws_proto_data_destructor; 38 | struct mbuf mqtt_recv_mbuf, ws_recv_mbuf; 39 | struct mbuf mqtt_send_mbuf, ws_send_mbuf; 40 | }; 41 | 42 | static void ws_to_mqtt(struct mg_connection *nc, 43 | struct mgos_mqtt_ws_data *wsd) { 44 | mbuf_move(&nc->recv_mbuf, &wsd->ws_recv_mbuf); 45 | mbuf_move(&nc->send_mbuf, &wsd->ws_send_mbuf); 46 | mbuf_move(&wsd->mqtt_recv_mbuf, &nc->recv_mbuf); 47 | mbuf_move(&wsd->mqtt_send_mbuf, &nc->send_mbuf); 48 | nc->proto_handler = wsd->mqtt_proto_handler; 49 | nc->proto_data = wsd->mqtt_proto_data; 50 | nc->proto_data_destructor = wsd->mqtt_proto_data_destructor; 51 | nc->handler = mgos_mqtt_ev; 52 | } 53 | 54 | static void mqtt_to_ws(struct mg_connection *nc, 55 | struct mgos_mqtt_ws_data *wsd) { 56 | mbuf_move(&nc->recv_mbuf, &wsd->mqtt_recv_mbuf); 57 | mbuf_move(&nc->send_mbuf, &wsd->mqtt_send_mbuf); 58 | mbuf_move(&wsd->ws_recv_mbuf, &nc->recv_mbuf); 59 | mbuf_move(&wsd->ws_send_mbuf, &nc->send_mbuf); 60 | nc->proto_handler = wsd->ws_proto_handler; 61 | nc->proto_data = wsd->ws_proto_data; 62 | nc->proto_data_destructor = wsd->ws_proto_data_destructor; 63 | nc->handler = mgos_mqtt_ws_ev; 64 | if (wsd->mqtt_send_mbuf.len > 0) { 65 | mg_send_websocket_frame(nc, WEBSOCKET_OP_BINARY, wsd->mqtt_send_mbuf.buf, 66 | wsd->mqtt_send_mbuf.len); 67 | mbuf_clear(&wsd->mqtt_send_mbuf); 68 | } 69 | } 70 | 71 | static void mgos_mqtt_ws_data_free(struct mgos_mqtt_ws_data *wsd) { 72 | if (wsd == NULL) return; 73 | mbuf_free(&wsd->mqtt_recv_mbuf); 74 | mbuf_free(&wsd->mqtt_send_mbuf); 75 | mbuf_free(&wsd->ws_recv_mbuf); 76 | mbuf_free(&wsd->ws_send_mbuf); 77 | if (wsd->mqtt_proto_data_destructor != NULL && wsd->ws_proto_data != NULL) { 78 | wsd->mqtt_proto_data_destructor(wsd->mqtt_proto_data); 79 | } 80 | free(wsd); 81 | } 82 | 83 | void mgos_mqtt_ws_ev(struct mg_connection *nc, int ev, void *ev_data, 84 | void *user_data) { 85 | struct mgos_mqtt_conn *c = (struct mgos_mqtt_conn *) user_data; 86 | if (c == NULL || nc != c->nc) { 87 | nc->flags |= MG_F_CLOSE_IMMEDIATELY; 88 | return; 89 | } 90 | if (ev >= MG_EV_WEBSOCKET_HANDSHAKE_REQUEST && 91 | ev <= MG_EV_WEBSOCKET_CONTROL_FRAME) { 92 | LOG(LL_DEBUG, ("MQTT%d WS event: %d", c->conn_id, ev)); 93 | } 94 | const struct mgos_config_mqtt *cfg = c->cfg; 95 | struct mgos_mqtt_ws_data *wsd = c->ws_data; 96 | switch (ev) { 97 | case MG_EV_CONNECT: { 98 | int status = *((int *) ev_data); 99 | LOG(LL_INFO, ("MQTT%d %s connect %s (%d)", c->conn_id, "TCP", 100 | (status == 0 ? "ok" : "error"), status)); 101 | if (status != 0) break; 102 | LOG(LL_INFO, 103 | ("MQTT%d sending WS handshake to %s", c->conn_id, cfg->ws_path)); 104 | mg_send_websocket_handshake2(nc, cfg->ws_path, cfg->server, "mqtt", NULL); 105 | break; 106 | } 107 | case MG_EV_WEBSOCKET_HANDSHAKE_DONE: { 108 | wsd = calloc(1, sizeof(*wsd)); 109 | if (wsd == NULL) { 110 | nc->flags |= MG_F_CLOSE_IMMEDIATELY; 111 | break; 112 | } 113 | wsd->ws_proto_handler = nc->proto_handler; 114 | wsd->ws_proto_data = nc->proto_data; 115 | wsd->ws_proto_data_destructor = nc->proto_data_destructor; 116 | c->ws_data = wsd; 117 | // Emulate the "connect" event for the MQTT handler. 118 | int status = 0; 119 | mbuf_clear(&nc->recv_mbuf); 120 | ws_to_mqtt(nc, wsd); 121 | mg_set_protocol_mqtt(nc); 122 | wsd->mqtt_proto_handler = nc->proto_handler; 123 | wsd->mqtt_proto_data = nc->proto_data; 124 | wsd->mqtt_proto_data_destructor = nc->proto_data_destructor; 125 | nc->proto_handler(nc, MG_EV_CONNECT, &status, user_data); 126 | mqtt_to_ws(nc, wsd); 127 | break; 128 | } 129 | case MG_EV_WEBSOCKET_FRAME: { 130 | // Emulate the "receive" event. 131 | struct websocket_message *wm = ev_data; 132 | mbuf_append(&wsd->mqtt_recv_mbuf, wm->data, wm->size); 133 | wsd->ws_proto_handler = nc->proto_handler; 134 | ws_to_mqtt(nc, wsd); 135 | int size = (int) wm->size; 136 | nc->proto_handler(nc, MG_EV_RECV, &size, user_data); 137 | mqtt_to_ws(nc, wsd); 138 | break; 139 | } 140 | case MG_EV_POLL: 141 | case MG_EV_CLOSE: { 142 | bool was_connected = c->connected; 143 | if (was_connected) { 144 | ws_to_mqtt(nc, wsd); 145 | nc->proto_handler(nc, ev, NULL, user_data); 146 | mqtt_to_ws(nc, wsd); 147 | } 148 | if (ev == MG_EV_CLOSE) { 149 | c->ws_data = NULL; 150 | mgos_mqtt_ws_data_free(wsd); 151 | if (!was_connected) { 152 | // Do the part normally done by mgos_mqtt_ev. 153 | c->nc = NULL; 154 | mgos_mqtt_conn_reconnect(c); 155 | } 156 | } 157 | break; 158 | } 159 | } 160 | } 161 | --------------------------------------------------------------------------------