├── .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 |
--------------------------------------------------------------------------------