├── .gitignore ├── .travis.yml ├── README.md ├── lib ├── espmqtt │ ├── .gitignore │ ├── Kconfig │ ├── LICENSE │ ├── README.md │ ├── component.mk │ ├── mqtt.c │ ├── mqtt.h │ ├── mqtt_config.h │ ├── mqtt_msg.c │ ├── mqtt_msg.h │ ├── ringbuf.c │ └── ringbuf.h └── readme.txt ├── platformio.ini └── src ├── .gitignore ├── main.c ├── sdkconfig.h └── user_config.h.example /.gitignore: -------------------------------------------------------------------------------- 1 | .pioenvs 2 | .clang_complete 3 | .gcc-flags.json 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < http://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < http://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < http://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choice one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # 39 | # script: 40 | # - platformio run 41 | 42 | 43 | # 44 | # Template #2: The project is intended to by used as a library with examples 45 | # 46 | 47 | # language: python 48 | # python: 49 | # - "2.7" 50 | # 51 | # sudo: false 52 | # cache: 53 | # directories: 54 | # - "~/.platformio" 55 | # 56 | # env: 57 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 58 | # - PLATFORMIO_CI_SRC=examples/file.ino 59 | # - PLATFORMIO_CI_SRC=path/to/test/directory 60 | # 61 | # install: 62 | # - pip install -U platformio 63 | # 64 | # script: 65 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32 bluetooth beacon scanner 2 | 3 | This is an example of using the ESP32 IDF SDK to scan for bluetooth devices, 4 | and send notifications to an mqtt topic. 5 | 6 | Install platformio to build. 7 | -------------------------------------------------------------------------------- /lib/espmqtt/.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | *.su 34 | -------------------------------------------------------------------------------- /lib/espmqtt/Kconfig: -------------------------------------------------------------------------------- 1 | menu "MQTT" 2 | 3 | # This is actually also handled in the ESP32 startup code, not only in FreeRTOS. 4 | config MQTT_PROTOCOL_311 5 | bool "MQTT Protocol version 3.1.1" 6 | default y 7 | help 8 | If disable, it will use MQTT protocol version 3.1 9 | 10 | config MQTT_SECURITY_ON 11 | bool "Enable MQTT over SSL" 12 | default n 13 | help 14 | Enable MQTT Over SSL 15 | 16 | config MQTT_PRIORITY 17 | int "MQTT Task Priority" 18 | range 1 15 19 | default 5 20 | 21 | config MQTT_LOG_ERROR_ON 22 | bool "Enable MQTT Debug message" 23 | default y 24 | help 25 | Disable it will redurce memory and run faster 26 | config MQTT_LOG_WARN_ON 27 | bool "Enable MQTT Warning message" 28 | default y 29 | help 30 | Disable it will redurce memory and run faster 31 | config MQTT_LOG_INFO_ON 32 | bool "Enable MQTT Info message" 33 | default y 34 | help 35 | Disable it will redurce memory and run faster 36 | config MQTT_RECONNECT_TIMEOUT 37 | int "Reconnect timeout (in second)" 38 | range 10 16535 39 | default 60 40 | 41 | config MQTT_QUEUE_BUFFER_SIZE_WORD 42 | int "Outbox queue buffer size in word (4 bytes)" 43 | range 256 4096 44 | default 1024 45 | 46 | config MQTT_BUFFER_SIZE_BYTE 47 | int "Network buffer size for MQTT in byte" 48 | range 128 4096 49 | default 1024 50 | 51 | config MQTT_MAX_HOST_LEN 52 | int "Maximum host name len - in byte" 53 | range 32 256 54 | default 64 55 | 56 | config MQTT_MAX_CLIENT_LEN 57 | int "Maximum client id len - in byte" 58 | range 4 128 59 | default 32 60 | 61 | config MQTT_MAX_USERNAME_LEN 62 | int "Maximum mqtt username len - in byte" 63 | range 4 128 64 | default 32 65 | 66 | config MQTT_MAX_PASSWORD_LEN 67 | int "Maximum mqtt password len - in byte" 68 | range 4 128 69 | default 32 70 | config MQTT_MAX_LWT_TOPIC 71 | int "Maximum mqtt lwt topic len - in byte" 72 | range 4 128 73 | default 32 74 | config MQTT_MAX_LWT_MSG 75 | int "Maximum mqtt lwt message len - in byte" 76 | range 4 128 77 | default 32 78 | 79 | endmenu 80 | -------------------------------------------------------------------------------- /lib/espmqtt/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2016 Tuan PM 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /lib/espmqtt/README.md: -------------------------------------------------------------------------------- 1 | # ESP32 MQTT Library 2 | 3 | This is component based on ESP-IDF for ESP32 4 | 5 | Full documentation and sample project: https://github.com/tuanpmt/esp32-mqtt 6 | -------------------------------------------------------------------------------- /lib/espmqtt/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # Component Makefile 3 | # 4 | # This Makefile should, at the very least, just include $(SDK_PATH)/make/component.mk. By default, 5 | # this will take the sources in this directory, compile them and link them into 6 | # lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, 7 | # please read the SDK documents if you need to do this. 8 | # 9 | COMPONENT_ADD_INCLUDEDIRS := include 10 | #COMPONENT_PRIV_INCLUDEDIRS := 11 | 12 | COMPONENT_SRCDIRS := . 13 | #EXTRA_CFLAGS := -DICACHE_RODATA_ATTR 14 | CFLAGS += -Wno-error=implicit-function-declaration -Wno-error=format= -DHAVE_CONFIG_H 15 | 16 | 17 | 18 | include $(IDF_PATH)/make/component_common.mk 19 | -------------------------------------------------------------------------------- /lib/espmqtt/mqtt.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Tuan PM 3 | * @Date: 2016-09-10 09:33:06 4 | * @Last Modified by: Tuan PM 5 | * @Last Modified time: 2017-02-15 13:11:53 6 | */ 7 | #include 8 | #include "freertos/FreeRTOS.h" 9 | #include "freertos/task.h" 10 | #include "freertos/semphr.h" 11 | #include "freertos/queue.h" 12 | 13 | #include "lwip/sockets.h" 14 | #include "lwip/dns.h" 15 | #include "lwip/netdb.h" 16 | #include "ringbuf.h" 17 | #include "mqtt.h" 18 | 19 | static TaskHandle_t xMqttTask = NULL; 20 | static TaskHandle_t xMqttSendingTask = NULL; 21 | 22 | 23 | static int resolve_dns(const char *host, struct sockaddr_in *ip) { 24 | struct hostent *he; 25 | struct in_addr **addr_list; 26 | he = gethostbyname(host); 27 | if (he == NULL) return 0; 28 | addr_list = (struct in_addr **)he->h_addr_list; 29 | if (addr_list[0] == NULL) return 0; 30 | ip->sin_family = AF_INET; 31 | memcpy(&ip->sin_addr, addr_list[0], sizeof(ip->sin_addr)); 32 | return 1; 33 | } 34 | static void mqtt_queue(mqtt_client *client) 35 | { 36 | int msg_len; 37 | while (rb_available(&client->send_rb) < client->mqtt_state.outbound_message->length) { 38 | xQueueReceive(client->xSendingQueue, &msg_len, 1000 / portTICK_RATE_MS); 39 | rb_read(&client->send_rb, client->mqtt_state.out_buffer, msg_len); 40 | } 41 | rb_write(&client->send_rb, 42 | client->mqtt_state.outbound_message->data, 43 | client->mqtt_state.outbound_message->length); 44 | xQueueSend(client->xSendingQueue, &client->mqtt_state.outbound_message->length, 0); 45 | } 46 | 47 | static bool client_connect(mqtt_client *client) 48 | { 49 | int ret; 50 | struct sockaddr_in remote_ip; 51 | 52 | while (1) { 53 | 54 | bzero(&remote_ip, sizeof(struct sockaddr_in)); 55 | remote_ip.sin_family = AF_INET; 56 | remote_ip.sin_port = htons(client->settings->port); 57 | 58 | 59 | //if host is not ip address, resolve it 60 | if (inet_aton( client->settings->host, &(remote_ip.sin_addr)) == 0) { 61 | mqtt_info("Resolve dns for domain: %s", client->settings->host); 62 | 63 | if (!resolve_dns(client->settings->host, &remote_ip)) { 64 | vTaskDelay(1000 / portTICK_RATE_MS); 65 | continue; 66 | } 67 | } 68 | 69 | 70 | #if defined(CONFIG_MQTT_SECURITY_ON) // ENABLE MQTT OVER SSL 71 | client->ctx = NULL; 72 | client->ssl = NULL; 73 | 74 | client->ctx = SSL_CTX_new(TLSv1_2_client_method()); 75 | if (!client->ctx) { 76 | mqtt_error("Failed to create SSL CTX"); 77 | goto failed1; 78 | } 79 | #endif 80 | 81 | client->socket = socket(PF_INET, SOCK_STREAM, 0); 82 | if (client->socket == -1) { 83 | mqtt_error("Failed to create socket"); 84 | goto failed2; 85 | } 86 | 87 | 88 | 89 | mqtt_info("Connecting to server %s:%d,%d", 90 | inet_ntoa((remote_ip.sin_addr)), 91 | client->settings->port, 92 | remote_ip.sin_port); 93 | 94 | 95 | if (connect(client->socket, (struct sockaddr *)(&remote_ip), sizeof(struct sockaddr)) != 00) { 96 | mqtt_error("Connect failed"); 97 | goto failed3; 98 | } 99 | 100 | #if defined(CONFIG_MQTT_SECURITY_ON) // ENABLE MQTT OVER SSL 101 | mqtt_info("Creating SSL object..."); 102 | client->ssl = SSL_new(client->ctx); 103 | if (!client->ssl) { 104 | mqtt_error("Unable to creat new SSL"); 105 | goto failed3; 106 | } 107 | 108 | if (!SSL_set_fd(client->ssl, client->socket)) { 109 | mqtt_error("SSL set_fd failed"); 110 | goto failed3; 111 | } 112 | 113 | mqtt_info("Start SSL connect.."); 114 | ret = SSL_connect(client->ssl); 115 | if (!ret) { 116 | mqtt_error("SSL Connect FAILED"); 117 | goto failed4; 118 | } 119 | #endif 120 | mqtt_info("Connected!"); 121 | 122 | return true; 123 | 124 | //failed5: 125 | // SSL_shutdown(client->ssl); 126 | 127 | #if defined(CONFIG_MQTT_SECURITY_ON) 128 | failed4: 129 | SSL_free(client->ssl); 130 | client->ssl = NULL; 131 | #endif 132 | 133 | failed3: 134 | close(client->socket); 135 | client->socket = -1; 136 | 137 | failed2: 138 | #if defined(CONFIG_MQTT_SECURITY_ON) 139 | SSL_CTX_free(client->ctx); 140 | 141 | failed1: 142 | client->ctx = NULL; 143 | #endif 144 | vTaskDelay(1000 / portTICK_RATE_MS); 145 | 146 | } 147 | } 148 | 149 | 150 | // Close client socket 151 | // including SSL objects if CNFIG_MQTT_SECURITY_ON is enabled 152 | void closeclient(mqtt_client *client) 153 | { 154 | 155 | #if defined(CONFIG_MQTT_SECURITY_ON) 156 | if (client->ssl != NULL) 157 | { 158 | SSL_shutdown(client->ssl); 159 | 160 | SSL_free(client->ssl); 161 | client->ssl = NULL; 162 | } 163 | #endif 164 | if (client->socket != -1) 165 | { 166 | close(client->socket); 167 | client->socket = -1; 168 | } 169 | 170 | #if defined(CONFIG_MQTT_SECURITY_ON) 171 | if (client->ctx != NULL) 172 | { 173 | SSL_CTX_free(client->ctx); 174 | client->ctx = NULL; 175 | } 176 | #endif 177 | 178 | } 179 | /* 180 | * mqtt_connect 181 | * input - client 182 | * return 1: success, 0: fail 183 | */ 184 | static bool mqtt_connect(mqtt_client *client) 185 | { 186 | int write_len, read_len, connect_rsp_code; 187 | struct timeval tv; 188 | 189 | tv.tv_sec = 10; /* 30 Secs Timeout */ 190 | tv.tv_usec = 0; // Not init'ing this can cause strange errors 191 | 192 | setsockopt(client->socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval)); 193 | 194 | mqtt_msg_init(&client->mqtt_state.mqtt_connection, 195 | client->mqtt_state.out_buffer, 196 | client->mqtt_state.out_buffer_length); 197 | client->mqtt_state.outbound_message = mqtt_msg_connect(&client->mqtt_state.mqtt_connection, 198 | client->mqtt_state.connect_info); 199 | client->mqtt_state.pending_msg_type = mqtt_get_type(client->mqtt_state.outbound_message->data); 200 | client->mqtt_state.pending_msg_id = mqtt_get_id(client->mqtt_state.outbound_message->data, 201 | client->mqtt_state.outbound_message->length); 202 | mqtt_info("Sending MQTT CONNECT message, type: %d, id: %04X", 203 | client->mqtt_state.pending_msg_type, 204 | client->mqtt_state.pending_msg_id); 205 | 206 | write_len = ClientWrite( 207 | client->mqtt_state.outbound_message->data, 208 | client->mqtt_state.outbound_message->length); 209 | 210 | mqtt_info("Reading MQTT CONNECT response message"); 211 | 212 | read_len = ClientRead(client->mqtt_state.in_buffer, CONFIG_MQTT_BUFFER_SIZE_BYTE); 213 | 214 | tv.tv_sec = 0; /* No timeout */ 215 | setsockopt(client->socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval)); 216 | 217 | if (read_len < 0) { 218 | mqtt_error("Error network response"); 219 | return false; 220 | } 221 | if (mqtt_get_type(client->mqtt_state.in_buffer) != MQTT_MSG_TYPE_CONNACK) { 222 | mqtt_error("Invalid MSG_TYPE response: %d, read_len: %d", mqtt_get_type(client->mqtt_state.in_buffer), read_len); 223 | return false; 224 | } 225 | connect_rsp_code = mqtt_get_connect_return_code(client->mqtt_state.in_buffer); 226 | switch (connect_rsp_code) { 227 | case CONNECTION_ACCEPTED: 228 | mqtt_info("Connected"); 229 | return true; 230 | case CONNECTION_REFUSE_PROTOCOL: 231 | case CONNECTION_REFUSE_SERVER_UNAVAILABLE: 232 | case CONNECTION_REFUSE_BAD_USERNAME: 233 | case CONNECTION_REFUSE_NOT_AUTHORIZED: 234 | mqtt_warn("Connection refuse, reason code: %d", connect_rsp_code); 235 | return false; 236 | default: 237 | mqtt_warn("Connection refuse, Unknow reason"); 238 | return false; 239 | } 240 | return false; 241 | } 242 | 243 | void mqtt_sending_task(void *pvParameters) 244 | { 245 | mqtt_client *client = (mqtt_client *)pvParameters; 246 | uint32_t msg_len, send_len; 247 | mqtt_info("mqtt_sending_task"); 248 | 249 | while (1) { 250 | if (xQueueReceive(client->xSendingQueue, &msg_len, 1000 / portTICK_RATE_MS)) { 251 | //queue available 252 | while (msg_len > 0) { 253 | send_len = msg_len; 254 | if (send_len > CONFIG_MQTT_BUFFER_SIZE_BYTE) 255 | send_len = CONFIG_MQTT_BUFFER_SIZE_BYTE; 256 | mqtt_info("Sending...%d bytes", send_len); 257 | 258 | rb_read(&client->send_rb, client->mqtt_state.out_buffer, send_len); 259 | client->mqtt_state.pending_msg_type = mqtt_get_type(client->mqtt_state.out_buffer); 260 | client->mqtt_state.pending_msg_id = mqtt_get_id(client->mqtt_state.out_buffer, send_len); 261 | ClientWrite(client->mqtt_state.out_buffer, send_len); 262 | 263 | //TODO: Check sending type, to callback publish message 264 | msg_len -= send_len; 265 | } 266 | //invalidate keepalive timer 267 | client->keepalive_tick = client->settings->keepalive / 2; 268 | } 269 | else { 270 | if (client->keepalive_tick > 0) client->keepalive_tick --; 271 | else { 272 | client->keepalive_tick = client->settings->keepalive / 2; 273 | client->mqtt_state.outbound_message = mqtt_msg_pingreq(&client->mqtt_state.mqtt_connection); 274 | client->mqtt_state.pending_msg_type = mqtt_get_type(client->mqtt_state.outbound_message->data); 275 | client->mqtt_state.pending_msg_id = mqtt_get_id(client->mqtt_state.outbound_message->data, 276 | client->mqtt_state.outbound_message->length); 277 | mqtt_info("Sending pingreq"); 278 | ClientWrite( 279 | client->mqtt_state.outbound_message->data, 280 | client->mqtt_state.outbound_message->length); 281 | } 282 | } 283 | } 284 | vTaskDelete(NULL); 285 | } 286 | 287 | void deliver_publish(mqtt_client *client, uint8_t *message, int length) 288 | { 289 | mqtt_event_data_t event_data; 290 | int len_read, total_mqtt_len = 0, mqtt_len = 0, mqtt_offset = 0; 291 | 292 | do 293 | { 294 | event_data.topic_length = length; 295 | event_data.topic = mqtt_get_publish_topic(message, &event_data.topic_length); 296 | event_data.data_length = length; 297 | event_data.data = mqtt_get_publish_data(message, &event_data.data_length); 298 | 299 | if(total_mqtt_len == 0){ 300 | total_mqtt_len = client->mqtt_state.message_length - client->mqtt_state.message_length_read + event_data.data_length; 301 | mqtt_len = event_data.data_length; 302 | } else { 303 | mqtt_len = len_read; 304 | } 305 | 306 | event_data.data_total_length = total_mqtt_len; 307 | event_data.data_offset = mqtt_offset; 308 | event_data.data_length = mqtt_len; 309 | 310 | mqtt_info("Data received: %d/%d bytes ", mqtt_len, total_mqtt_len); 311 | if(client->settings->data_cb) { 312 | client->settings->data_cb(client, &event_data); 313 | } 314 | mqtt_offset += mqtt_len; 315 | if (client->mqtt_state.message_length_read >= client->mqtt_state.message_length) 316 | break; 317 | 318 | len_read = ClientRead(client->mqtt_state.in_buffer, CONFIG_MQTT_BUFFER_SIZE_BYTE); 319 | client->mqtt_state.message_length_read += len_read; 320 | } while (1); 321 | 322 | } 323 | void mqtt_start_receive_schedule(mqtt_client *client) 324 | { 325 | int read_len; 326 | uint8_t msg_type; 327 | uint8_t msg_qos; 328 | uint16_t msg_id; 329 | 330 | while (1) { 331 | 332 | read_len = ClientRead(client->mqtt_state.in_buffer, CONFIG_MQTT_BUFFER_SIZE_BYTE); 333 | 334 | mqtt_info("Read len %d", read_len); 335 | if (read_len == 0) 336 | break; 337 | 338 | msg_type = mqtt_get_type(client->mqtt_state.in_buffer); 339 | msg_qos = mqtt_get_qos(client->mqtt_state.in_buffer); 340 | msg_id = mqtt_get_id(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_length); 341 | // mqtt_info("msg_type %d, msg_id: %d, pending_id: %d", msg_type, msg_id, client->mqtt_state.pending_msg_type); 342 | switch (msg_type) 343 | { 344 | case MQTT_MSG_TYPE_SUBACK: 345 | if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_SUBSCRIBE && client->mqtt_state.pending_msg_id == msg_id) { 346 | mqtt_info("Subscribe successful"); 347 | if (client->settings->subscribe_cb) { 348 | client->settings->subscribe_cb(client, NULL); 349 | } 350 | } 351 | break; 352 | case MQTT_MSG_TYPE_UNSUBACK: 353 | if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_UNSUBSCRIBE && client->mqtt_state.pending_msg_id == msg_id) 354 | mqtt_info("UnSubscribe successful"); 355 | break; 356 | case MQTT_MSG_TYPE_PUBLISH: 357 | if (msg_qos == 1) 358 | client->mqtt_state.outbound_message = mqtt_msg_puback(&client->mqtt_state.mqtt_connection, msg_id); 359 | else if (msg_qos == 2) 360 | client->mqtt_state.outbound_message = mqtt_msg_pubrec(&client->mqtt_state.mqtt_connection, msg_id); 361 | 362 | if (msg_qos == 1 || msg_qos == 2) { 363 | mqtt_info("Queue response QoS: %d", msg_qos); 364 | mqtt_queue(client); 365 | // if (QUEUE_Puts(&client->msgQueue, client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length) == -1) { 366 | // mqtt_info("MQTT: Queue full"); 367 | // } 368 | } 369 | client->mqtt_state.message_length_read = read_len; 370 | client->mqtt_state.message_length = mqtt_get_total_length(client->mqtt_state.in_buffer, client->mqtt_state.message_length_read); 371 | mqtt_info("deliver_publish"); 372 | 373 | deliver_publish(client, client->mqtt_state.in_buffer, client->mqtt_state.message_length_read); 374 | // deliver_publish(client, client->mqtt_state.in_buffer, client->mqtt_state.message_length_read); 375 | break; 376 | case MQTT_MSG_TYPE_PUBACK: 377 | if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH && client->mqtt_state.pending_msg_id == msg_id) { 378 | mqtt_info("received MQTT_MSG_TYPE_PUBACK, finish QoS1 publish"); 379 | } 380 | 381 | break; 382 | case MQTT_MSG_TYPE_PUBREC: 383 | client->mqtt_state.outbound_message = mqtt_msg_pubrel(&client->mqtt_state.mqtt_connection, msg_id); 384 | mqtt_queue(client); 385 | break; 386 | case MQTT_MSG_TYPE_PUBREL: 387 | client->mqtt_state.outbound_message = mqtt_msg_pubcomp(&client->mqtt_state.mqtt_connection, msg_id); 388 | mqtt_queue(client); 389 | 390 | break; 391 | case MQTT_MSG_TYPE_PUBCOMP: 392 | if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBREL && client->mqtt_state.pending_msg_id == msg_id) { 393 | mqtt_info("Receive MQTT_MSG_TYPE_PUBCOMP, finish QoS2 publish"); 394 | } 395 | break; 396 | case MQTT_MSG_TYPE_PINGREQ: 397 | client->mqtt_state.outbound_message = mqtt_msg_pingresp(&client->mqtt_state.mqtt_connection); 398 | mqtt_queue(client); 399 | break; 400 | case MQTT_MSG_TYPE_PINGRESP: 401 | mqtt_info("MQTT_MSG_TYPE_PINGRESP"); 402 | // Ignore 403 | break; 404 | } 405 | } 406 | mqtt_info("network disconnected"); 407 | } 408 | 409 | void mqtt_destroy(mqtt_client *client) 410 | { 411 | free(client->mqtt_state.in_buffer); 412 | free(client->mqtt_state.out_buffer); 413 | free(client); 414 | vTaskDelete(xMqttTask); 415 | } 416 | 417 | void mqtt_task(void *pvParameters) 418 | { 419 | mqtt_client *client = (mqtt_client *)pvParameters; 420 | 421 | while (1) { 422 | client_connect(client); 423 | 424 | mqtt_info("Connected to server %s:%d", client->settings->host, client->settings->port); 425 | if (!mqtt_connect(client)) { 426 | closeclient(client); 427 | continue; 428 | //return; 429 | } 430 | mqtt_info("Connected to MQTT broker, create sending thread before call connected callback"); 431 | xTaskCreate(&mqtt_sending_task, "mqtt_sending_task", 2048, client, CONFIG_MQTT_PRIORITY + 1, &xMqttSendingTask); 432 | if (client->settings->connected_cb) { 433 | client->settings->connected_cb(client, NULL); 434 | } 435 | 436 | mqtt_info("mqtt_start_receive_schedule"); 437 | mqtt_start_receive_schedule(client); 438 | 439 | closeclient(client); 440 | vTaskDelete(xMqttSendingTask); 441 | vTaskDelay(1000 / portTICK_RATE_MS); 442 | 443 | } 444 | mqtt_destroy(client); 445 | 446 | 447 | } 448 | 449 | mqtt_client *mqtt_start(mqtt_settings *settings) 450 | { 451 | int stackSize = 2048; 452 | 453 | uint8_t *rb_buf; 454 | if (xMqttTask != NULL) 455 | return NULL; 456 | mqtt_client *client = malloc(sizeof(mqtt_client)); 457 | 458 | if (client == NULL) { 459 | mqtt_error("Memory not enough"); 460 | return NULL; 461 | } 462 | memset(client, 0, sizeof(mqtt_client)); 463 | 464 | client->settings = settings; 465 | client->connect_info.client_id = settings->client_id; 466 | client->connect_info.username = settings->username; 467 | client->connect_info.password = settings->password; 468 | client->connect_info.will_topic = settings->lwt_topic; 469 | client->connect_info.will_message = settings->lwt_msg; 470 | client->connect_info.will_qos = settings->lwt_qos; 471 | client->connect_info.will_retain = settings->lwt_retain; 472 | 473 | 474 | client->keepalive_tick = settings->keepalive / 2; 475 | 476 | client->connect_info.keepalive = settings->keepalive; 477 | client->connect_info.clean_session = settings->clean_session; 478 | 479 | client->mqtt_state.in_buffer = (uint8_t *)malloc(CONFIG_MQTT_BUFFER_SIZE_BYTE); 480 | client->mqtt_state.in_buffer_length = CONFIG_MQTT_BUFFER_SIZE_BYTE; 481 | client->mqtt_state.out_buffer = (uint8_t *)malloc(CONFIG_MQTT_BUFFER_SIZE_BYTE); 482 | client->mqtt_state.out_buffer_length = CONFIG_MQTT_BUFFER_SIZE_BYTE; 483 | client->mqtt_state.connect_info = &client->connect_info; 484 | 485 | client->socket = -1; 486 | 487 | #if defined(CONFIG_MQTT_SECURITY_ON) // ENABLE MQTT OVER SSL 488 | client->ctx = NULL; 489 | client->ssl = NULL; 490 | stackSize = 10240; // Need more stack to handle SSL handshake 491 | #endif 492 | 493 | /* Create a queue capable of containing 64 unsigned long values. */ 494 | client->xSendingQueue = xQueueCreate(64, sizeof( uint32_t )); 495 | rb_buf = (uint8_t*) malloc(CONFIG_MQTT_QUEUE_BUFFER_SIZE_WORD * 4); 496 | 497 | if (rb_buf == NULL) { 498 | mqtt_error("Memory not enough"); 499 | return NULL; 500 | } 501 | 502 | rb_init(&client->send_rb, rb_buf, CONFIG_MQTT_QUEUE_BUFFER_SIZE_WORD * 4, 1); 503 | 504 | mqtt_msg_init(&client->mqtt_state.mqtt_connection, 505 | client->mqtt_state.out_buffer, 506 | client->mqtt_state.out_buffer_length); 507 | 508 | xTaskCreate(&mqtt_task, "mqtt_task", stackSize, client, CONFIG_MQTT_PRIORITY, &xMqttTask); 509 | return client; 510 | } 511 | 512 | void mqtt_subscribe(mqtt_client *client, char *topic, uint8_t qos) 513 | { 514 | client->mqtt_state.outbound_message = mqtt_msg_subscribe(&client->mqtt_state.mqtt_connection, 515 | topic, qos, 516 | &client->mqtt_state.pending_msg_id); 517 | mqtt_info("Queue subscribe, topic\"%s\", id: %d", topic, client->mqtt_state.pending_msg_id); 518 | mqtt_queue(client); 519 | } 520 | 521 | void mqtt_publish(mqtt_client* client, char *topic, char *data, int len, int qos, int retain) 522 | { 523 | 524 | client->mqtt_state.outbound_message = mqtt_msg_publish(&client->mqtt_state.mqtt_connection, 525 | topic, data, len, 526 | qos, retain, 527 | &client->mqtt_state.pending_msg_id); 528 | mqtt_queue(client); 529 | mqtt_info("Queuing publish, length: %d, queue size(%d/%d)\r\n", 530 | client->mqtt_state.outbound_message->length, 531 | client->send_rb.fill_cnt, 532 | client->send_rb.size); 533 | } 534 | 535 | void mqtt_stop() 536 | { 537 | 538 | } 539 | 540 | -------------------------------------------------------------------------------- /lib/espmqtt/mqtt.h: -------------------------------------------------------------------------------- 1 | #ifndef _MQTT_H_ 2 | #define _MQTT_H_ 3 | #include 4 | #include 5 | #include "mqtt_config.h" 6 | #include "mqtt_msg.h" 7 | #include "ringbuf.h" 8 | 9 | 10 | #if defined(CONFIG_MQTT_SECURITY_ON) // ENABLE MQTT OVER SSL 11 | #include "openssl/ssl.h" 12 | 13 | #define ClientRead(buf,num) SSL_read(client->ssl, buf, num) 14 | #define ClientWrite(buf,num) SSL_write(client->ssl, buf, num) 15 | 16 | #else 17 | 18 | #define ClientRead(buf,num) read(client->socket, buf, num) 19 | #define ClientWrite(buf,num) write(client->socket, buf, num) 20 | #endif 21 | 22 | 23 | typedef void (* mqtt_callback)(void *, void *); 24 | 25 | typedef struct { 26 | mqtt_callback connected_cb; 27 | mqtt_callback disconnected_cb; 28 | mqtt_callback reconnect_cb; 29 | 30 | mqtt_callback subscribe_cb; 31 | mqtt_callback publish_cb; 32 | mqtt_callback data_cb; 33 | 34 | char host[CONFIG_MQTT_MAX_HOST_LEN]; 35 | uint32_t port; 36 | char client_id[CONFIG_MQTT_MAX_CLIENT_LEN]; 37 | char username[CONFIG_MQTT_MAX_USERNAME_LEN]; 38 | char password[CONFIG_MQTT_MAX_PASSWORD_LEN]; 39 | char lwt_topic[CONFIG_MQTT_MAX_LWT_TOPIC]; 40 | char lwt_msg[CONFIG_MQTT_MAX_LWT_MSG]; 41 | uint32_t lwt_qos; 42 | uint32_t lwt_retain; 43 | uint32_t clean_session; 44 | uint32_t keepalive; 45 | } mqtt_settings; 46 | 47 | typedef struct mqtt_event_data_t 48 | { 49 | uint8_t type; 50 | const char* topic; 51 | const char* data; 52 | uint16_t topic_length; 53 | uint16_t data_length; 54 | uint16_t data_offset; 55 | uint16_t data_total_length; 56 | } mqtt_event_data_t; 57 | 58 | typedef struct mqtt_state_t 59 | { 60 | uint16_t port; 61 | int auto_reconnect; 62 | mqtt_connect_info_t* connect_info; 63 | uint8_t* in_buffer; 64 | uint8_t* out_buffer; 65 | int in_buffer_length; 66 | int out_buffer_length; 67 | uint16_t message_length; 68 | uint16_t message_length_read; 69 | mqtt_message_t* outbound_message; 70 | mqtt_connection_t mqtt_connection; 71 | uint16_t pending_msg_id; 72 | int pending_msg_type; 73 | int pending_publish_qos; 74 | } mqtt_state_t; 75 | 76 | typedef struct { 77 | int socket; 78 | 79 | #if defined(CONFIG_MQTT_SECURITY_ON) // ENABLE MQTT OVER SSL 80 | SSL_CTX *ctx; 81 | SSL *ssl; 82 | #endif 83 | 84 | mqtt_settings *settings; 85 | mqtt_state_t mqtt_state; 86 | mqtt_connect_info_t connect_info; 87 | QueueHandle_t xSendingQueue; 88 | RINGBUF send_rb; 89 | uint32_t keepalive_tick; 90 | } mqtt_client; 91 | 92 | mqtt_client *mqtt_start(mqtt_settings *mqtt_info); 93 | void mqtt_stop(); 94 | void mqtt_task(void *pvParameters); 95 | void mqtt_subscribe(mqtt_client *client, char *topic, uint8_t qos); 96 | void mqtt_publish(mqtt_client* client, char *topic, char *data, int len, int qos, int retain); 97 | void mqtt_detroy(); 98 | #endif 99 | -------------------------------------------------------------------------------- /lib/espmqtt/mqtt_config.h: -------------------------------------------------------------------------------- 1 | #ifndef _MQTT_CONFIG_H_ 2 | #define _MQTT_CONFIG_H_ 3 | #include "sdkconfig.h" 4 | #include 5 | 6 | #ifdef CONFIG_MQTT_LOG_ERROR_ON 7 | #define mqtt_error(format, ... ) printf( "[MQTT ERROR] " format "\n", ##__VA_ARGS__) 8 | #else 9 | #define mqtt_error( format, ... ) 10 | #endif 11 | #ifdef CONFIG_MQTT_LOG_WARN_ON 12 | #define mqtt_warn(format, ... ) printf( "[MQTT WARN] " format "\n", ##__VA_ARGS__) 13 | #else 14 | #define mqtt_warn( format, ... ) 15 | #endif 16 | #ifdef CONFIG_MQTT_LOG_INFO_ON 17 | #define mqtt_info(format, ... ) printf( "[MQTT INFO] " format "\n", ##__VA_ARGS__) 18 | #else 19 | #define mqtt_info(format, ... ) 20 | #endif 21 | 22 | #ifndef CONFIG_MQTT_QUEUE_BUFFER_SIZE_WORD 23 | #define CONFIG_MQTT_QUEUE_BUFFER_SIZE_WORD 1024 24 | #endif 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /lib/espmqtt/mqtt_msg.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Stephen Robinson 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 3. Neither the name of the copyright holder nor the names of its 15 | * contributors may be used to endorse or promote products derived 16 | * from this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | * 30 | */ 31 | #include 32 | #include 33 | #include "mqtt_msg.h" 34 | #include "mqtt_config.h" 35 | #define MQTT_MAX_FIXED_HEADER_SIZE 3 36 | 37 | enum mqtt_connect_flag 38 | { 39 | MQTT_CONNECT_FLAG_USERNAME = 1 << 7, 40 | MQTT_CONNECT_FLAG_PASSWORD = 1 << 6, 41 | MQTT_CONNECT_FLAG_WILL_RETAIN = 1 << 5, 42 | MQTT_CONNECT_FLAG_WILL = 1 << 2, 43 | MQTT_CONNECT_FLAG_CLEAN_SESSION = 1 << 1 44 | }; 45 | 46 | struct __attribute((__packed__)) mqtt_connect_variable_header 47 | { 48 | uint8_t lengthMsb; 49 | uint8_t lengthLsb; 50 | #if defined(CONFIG_MQTT_PROTOCOL_311) 51 | uint8_t magic[4]; 52 | #else 53 | uint8_t magic[6]; 54 | #endif 55 | uint8_t version; 56 | uint8_t flags; 57 | uint8_t keepaliveMsb; 58 | uint8_t keepaliveLsb; 59 | }; 60 | 61 | static int append_string(mqtt_connection_t* connection, const char* string, int len) 62 | { 63 | if (connection->message.length + len + 2 > connection->buffer_length) 64 | return -1; 65 | 66 | connection->buffer[connection->message.length++] = len >> 8; 67 | connection->buffer[connection->message.length++] = len & 0xff; 68 | memcpy(connection->buffer + connection->message.length, string, len); 69 | connection->message.length += len; 70 | 71 | return len + 2; 72 | } 73 | 74 | static uint16_t append_message_id(mqtt_connection_t* connection, uint16_t message_id) 75 | { 76 | // If message_id is zero then we should assign one, otherwise 77 | // we'll use the one supplied by the caller 78 | while (message_id == 0) 79 | message_id = ++connection->message_id; 80 | 81 | if (connection->message.length + 2 > connection->buffer_length) 82 | return 0; 83 | 84 | connection->buffer[connection->message.length++] = message_id >> 8; 85 | connection->buffer[connection->message.length++] = message_id & 0xff; 86 | 87 | return message_id; 88 | } 89 | 90 | static int init_message(mqtt_connection_t* connection) 91 | { 92 | connection->message.length = MQTT_MAX_FIXED_HEADER_SIZE; 93 | return MQTT_MAX_FIXED_HEADER_SIZE; 94 | } 95 | 96 | static mqtt_message_t* fail_message(mqtt_connection_t* connection) 97 | { 98 | connection->message.data = connection->buffer; 99 | connection->message.length = 0; 100 | return &connection->message; 101 | } 102 | 103 | static mqtt_message_t* fini_message(mqtt_connection_t* connection, int type, int dup, int qos, int retain) 104 | { 105 | int remaining_length = connection->message.length - MQTT_MAX_FIXED_HEADER_SIZE; 106 | 107 | if (remaining_length > 127) 108 | { 109 | connection->buffer[0] = ((type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1); 110 | connection->buffer[1] = 0x80 | (remaining_length % 128); 111 | connection->buffer[2] = remaining_length / 128; 112 | connection->message.length = remaining_length + 3; 113 | connection->message.data = connection->buffer; 114 | } 115 | else 116 | { 117 | connection->buffer[1] = ((type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1); 118 | connection->buffer[2] = remaining_length; 119 | connection->message.length = remaining_length + 2; 120 | connection->message.data = connection->buffer + 1; 121 | } 122 | 123 | return &connection->message; 124 | } 125 | 126 | void mqtt_msg_init(mqtt_connection_t* connection, uint8_t* buffer, uint16_t buffer_length) 127 | { 128 | memset(connection, 0, sizeof(mqtt_connection_t)); 129 | connection->buffer = buffer; 130 | connection->buffer_length = buffer_length; 131 | } 132 | 133 | int mqtt_get_total_length(uint8_t* buffer, uint16_t length) 134 | { 135 | int i; 136 | int totlen = 0; 137 | 138 | for (i = 1; i < length; ++i) 139 | { 140 | totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); 141 | if ((buffer[i] & 0x80) == 0) 142 | { 143 | ++i; 144 | break; 145 | } 146 | } 147 | totlen += i; 148 | 149 | return totlen; 150 | } 151 | 152 | const char* mqtt_get_publish_topic(uint8_t* buffer, uint16_t* length) 153 | { 154 | int i; 155 | int totlen = 0; 156 | int topiclen; 157 | 158 | for (i = 1; i < *length; ++i) 159 | { 160 | totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); 161 | if ((buffer[i] & 0x80) == 0) 162 | { 163 | ++i; 164 | break; 165 | } 166 | } 167 | totlen += i; 168 | 169 | if (i + 2 >= *length) 170 | return NULL; 171 | topiclen = buffer[i++] << 8; 172 | topiclen |= buffer[i++]; 173 | 174 | if (i + topiclen > *length) 175 | return NULL; 176 | 177 | *length = topiclen; 178 | return (const char*)(buffer + i); 179 | } 180 | 181 | const char* mqtt_get_publish_data(uint8_t* buffer, uint16_t* length) 182 | { 183 | int i; 184 | int totlen = 0; 185 | int topiclen; 186 | int blength = *length; 187 | *length = 0; 188 | 189 | for (i = 1; i < blength; ++i) 190 | { 191 | totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); 192 | if ((buffer[i] & 0x80) == 0) 193 | { 194 | ++i; 195 | break; 196 | } 197 | } 198 | totlen += i; 199 | 200 | if (i + 2 >= blength) 201 | return NULL; 202 | topiclen = buffer[i++] << 8; 203 | topiclen |= buffer[i++]; 204 | 205 | if (i + topiclen >= blength) 206 | return NULL; 207 | 208 | i += topiclen; 209 | 210 | if (mqtt_get_qos(buffer) > 0) 211 | { 212 | if (i + 2 >= blength) 213 | return NULL; 214 | i += 2; 215 | } 216 | 217 | if (totlen < i) 218 | return NULL; 219 | 220 | if (totlen <= blength) 221 | *length = totlen - i; 222 | else 223 | *length = blength - i; 224 | return (const char*)(buffer + i); 225 | } 226 | 227 | uint16_t mqtt_get_id(uint8_t* buffer, uint16_t length) 228 | { 229 | if (length < 1) 230 | return 0; 231 | 232 | switch (mqtt_get_type(buffer)) 233 | { 234 | case MQTT_MSG_TYPE_PUBLISH: 235 | { 236 | int i; 237 | int topiclen; 238 | 239 | for (i = 1; i < length; ++i) 240 | { 241 | if ((buffer[i] & 0x80) == 0) 242 | { 243 | ++i; 244 | break; 245 | } 246 | } 247 | 248 | if (i + 2 >= length) 249 | return 0; 250 | topiclen = buffer[i++] << 8; 251 | topiclen |= buffer[i++]; 252 | 253 | if (i + topiclen >= length) 254 | return 0; 255 | i += topiclen; 256 | 257 | if (mqtt_get_qos(buffer) > 0) 258 | { 259 | if (i + 2 >= length) 260 | return 0; 261 | //i += 2; 262 | } else { 263 | return 0; 264 | } 265 | 266 | return (buffer[i] << 8) | buffer[i + 1]; 267 | } 268 | case MQTT_MSG_TYPE_PUBACK: 269 | case MQTT_MSG_TYPE_PUBREC: 270 | case MQTT_MSG_TYPE_PUBREL: 271 | case MQTT_MSG_TYPE_PUBCOMP: 272 | case MQTT_MSG_TYPE_SUBACK: 273 | case MQTT_MSG_TYPE_UNSUBACK: 274 | case MQTT_MSG_TYPE_SUBSCRIBE: 275 | { 276 | // This requires the remaining length to be encoded in 1 byte, 277 | // which it should be. 278 | if (length >= 4 && (buffer[1] & 0x80) == 0) 279 | return (buffer[2] << 8) | buffer[3]; 280 | else 281 | return 0; 282 | } 283 | 284 | default: 285 | return 0; 286 | } 287 | } 288 | 289 | mqtt_message_t* mqtt_msg_connect(mqtt_connection_t* connection, mqtt_connect_info_t* info) 290 | { 291 | struct mqtt_connect_variable_header* variable_header; 292 | 293 | init_message(connection); 294 | 295 | if (connection->message.length + sizeof(*variable_header) > connection->buffer_length) 296 | return fail_message(connection); 297 | variable_header = (void*)(connection->buffer + connection->message.length); 298 | connection->message.length += sizeof(*variable_header); 299 | 300 | variable_header->lengthMsb = 0; 301 | #if defined(CONFIG_MQTT_PROTOCOL_311) 302 | variable_header->lengthLsb = 4; 303 | memcpy(variable_header->magic, "MQTT", 4); 304 | variable_header->version = 4; 305 | #else 306 | variable_header->lengthLsb = 6; 307 | memcpy(variable_header->magic, "MQIsdp", 6); 308 | variable_header->version = 3; 309 | #endif 310 | 311 | variable_header->flags = 0; 312 | variable_header->keepaliveMsb = info->keepalive >> 8; 313 | variable_header->keepaliveLsb = info->keepalive & 0xff; 314 | 315 | if (info->clean_session) 316 | variable_header->flags |= MQTT_CONNECT_FLAG_CLEAN_SESSION; 317 | 318 | if (info->client_id != NULL && info->client_id[0] != '\0') 319 | { 320 | if (append_string(connection, info->client_id, strlen(info->client_id)) < 0) 321 | return fail_message(connection); 322 | } 323 | else 324 | return fail_message(connection); 325 | 326 | if (info->will_topic != NULL && info->will_topic[0] != '\0') 327 | { 328 | if (append_string(connection, info->will_topic, strlen(info->will_topic)) < 0) 329 | return fail_message(connection); 330 | 331 | if (append_string(connection, info->will_message, strlen(info->will_message)) < 0) 332 | return fail_message(connection); 333 | 334 | variable_header->flags |= MQTT_CONNECT_FLAG_WILL; 335 | if (info->will_retain) 336 | variable_header->flags |= MQTT_CONNECT_FLAG_WILL_RETAIN; 337 | variable_header->flags |= (info->will_qos & 3) << 3; 338 | } 339 | 340 | if (info->username != NULL && info->username[0] != '\0') 341 | { 342 | if (append_string(connection, info->username, strlen(info->username)) < 0) 343 | return fail_message(connection); 344 | 345 | variable_header->flags |= MQTT_CONNECT_FLAG_USERNAME; 346 | } 347 | 348 | if (info->password != NULL && info->password[0] != '\0') 349 | { 350 | if (append_string(connection, info->password, strlen(info->password)) < 0) 351 | return fail_message(connection); 352 | 353 | variable_header->flags |= MQTT_CONNECT_FLAG_PASSWORD; 354 | } 355 | 356 | return fini_message(connection, MQTT_MSG_TYPE_CONNECT, 0, 0, 0); 357 | } 358 | 359 | mqtt_message_t* mqtt_msg_publish(mqtt_connection_t* connection, const char* topic, const char* data, int data_length, int qos, int retain, uint16_t* message_id) 360 | { 361 | init_message(connection); 362 | 363 | if (topic == NULL || topic[0] == '\0') 364 | return fail_message(connection); 365 | 366 | if (append_string(connection, topic, strlen(topic)) < 0) 367 | return fail_message(connection); 368 | 369 | if (qos > 0) 370 | { 371 | if ((*message_id = append_message_id(connection, 0)) == 0) 372 | return fail_message(connection); 373 | } 374 | else 375 | *message_id = 0; 376 | 377 | if (connection->message.length + data_length > connection->buffer_length) 378 | return fail_message(connection); 379 | memcpy(connection->buffer + connection->message.length, data, data_length); 380 | connection->message.length += data_length; 381 | 382 | return fini_message(connection, MQTT_MSG_TYPE_PUBLISH, 0, qos, retain); 383 | } 384 | 385 | mqtt_message_t* mqtt_msg_puback(mqtt_connection_t* connection, uint16_t message_id) 386 | { 387 | init_message(connection); 388 | if (append_message_id(connection, message_id) == 0) 389 | return fail_message(connection); 390 | return fini_message(connection, MQTT_MSG_TYPE_PUBACK, 0, 0, 0); 391 | } 392 | 393 | mqtt_message_t* mqtt_msg_pubrec(mqtt_connection_t* connection, uint16_t message_id) 394 | { 395 | init_message(connection); 396 | if (append_message_id(connection, message_id) == 0) 397 | return fail_message(connection); 398 | return fini_message(connection, MQTT_MSG_TYPE_PUBREC, 0, 0, 0); 399 | } 400 | 401 | mqtt_message_t* mqtt_msg_pubrel(mqtt_connection_t* connection, uint16_t message_id) 402 | { 403 | init_message(connection); 404 | if (append_message_id(connection, message_id) == 0) 405 | return fail_message(connection); 406 | return fini_message(connection, MQTT_MSG_TYPE_PUBREL, 0, 1, 0); 407 | } 408 | 409 | mqtt_message_t* mqtt_msg_pubcomp(mqtt_connection_t* connection, uint16_t message_id) 410 | { 411 | init_message(connection); 412 | if (append_message_id(connection, message_id) == 0) 413 | return fail_message(connection); 414 | return fini_message(connection, MQTT_MSG_TYPE_PUBCOMP, 0, 0, 0); 415 | } 416 | 417 | mqtt_message_t* mqtt_msg_subscribe(mqtt_connection_t* connection, const char* topic, int qos, uint16_t* message_id) 418 | { 419 | init_message(connection); 420 | 421 | if (topic == NULL || topic[0] == '\0') 422 | return fail_message(connection); 423 | 424 | if ((*message_id = append_message_id(connection, 0)) == 0) 425 | return fail_message(connection); 426 | 427 | if (append_string(connection, topic, strlen(topic)) < 0) 428 | return fail_message(connection); 429 | 430 | if (connection->message.length + 1 > connection->buffer_length) 431 | return fail_message(connection); 432 | connection->buffer[connection->message.length++] = qos; 433 | 434 | return fini_message(connection, MQTT_MSG_TYPE_SUBSCRIBE, 0, 1, 0); 435 | } 436 | 437 | mqtt_message_t* mqtt_msg_unsubscribe(mqtt_connection_t* connection, const char* topic, uint16_t* message_id) 438 | { 439 | init_message(connection); 440 | 441 | if (topic == NULL || topic[0] == '\0') 442 | return fail_message(connection); 443 | 444 | if ((*message_id = append_message_id(connection, 0)) == 0) 445 | return fail_message(connection); 446 | 447 | if (append_string(connection, topic, strlen(topic)) < 0) 448 | return fail_message(connection); 449 | 450 | return fini_message(connection, MQTT_MSG_TYPE_UNSUBSCRIBE, 0, 1, 0); 451 | } 452 | 453 | mqtt_message_t* mqtt_msg_pingreq(mqtt_connection_t* connection) 454 | { 455 | init_message(connection); 456 | return fini_message(connection, MQTT_MSG_TYPE_PINGREQ, 0, 0, 0); 457 | } 458 | 459 | mqtt_message_t* mqtt_msg_pingresp(mqtt_connection_t* connection) 460 | { 461 | init_message(connection); 462 | return fini_message(connection, MQTT_MSG_TYPE_PINGRESP, 0, 0, 0); 463 | } 464 | 465 | mqtt_message_t* mqtt_msg_disconnect(mqtt_connection_t* connection) 466 | { 467 | init_message(connection); 468 | return fini_message(connection, MQTT_MSG_TYPE_DISCONNECT, 0, 0, 0); 469 | } 470 | -------------------------------------------------------------------------------- /lib/espmqtt/mqtt_msg.h: -------------------------------------------------------------------------------- 1 | #ifndef MQTT_MSG_H 2 | #define MQTT_MSG_H 3 | #include "mqtt_config.h" 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | /* 9 | * Copyright (c) 2014, Stephen Robinson 10 | * All rights reserved. 11 | * 12 | * Redistribution and use in source and binary forms, with or without 13 | * modification, are permitted provided that the following conditions 14 | * are met: 15 | * 16 | * 1. Redistributions of source code must retain the above copyright 17 | * notice, this list of conditions and the following disclaimer. 18 | * 2. Redistributions in binary form must reproduce the above copyright 19 | * notice, this list of conditions and the following disclaimer in the 20 | * documentation and/or other materials provided with the distribution. 21 | * 3. Neither the name of the copyright holder nor the names of its 22 | * contributors may be used to endorse or promote products derived 23 | * from this software without specific prior written permission. 24 | * 25 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 26 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 29 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 30 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 31 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 32 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 33 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 34 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 | * POSSIBILITY OF SUCH DAMAGE. 36 | * 37 | */ 38 | /* 7 6 5 4 3 2 1 0 */ 39 | /*| --- Message Type---- | DUP Flag | QoS Level | Retain | */ 40 | /* Remaining Length */ 41 | 42 | 43 | enum mqtt_message_type 44 | { 45 | MQTT_MSG_TYPE_CONNECT = 1, 46 | MQTT_MSG_TYPE_CONNACK = 2, 47 | MQTT_MSG_TYPE_PUBLISH = 3, 48 | MQTT_MSG_TYPE_PUBACK = 4, 49 | MQTT_MSG_TYPE_PUBREC = 5, 50 | MQTT_MSG_TYPE_PUBREL = 6, 51 | MQTT_MSG_TYPE_PUBCOMP = 7, 52 | MQTT_MSG_TYPE_SUBSCRIBE = 8, 53 | MQTT_MSG_TYPE_SUBACK = 9, 54 | MQTT_MSG_TYPE_UNSUBSCRIBE = 10, 55 | MQTT_MSG_TYPE_UNSUBACK = 11, 56 | MQTT_MSG_TYPE_PINGREQ = 12, 57 | MQTT_MSG_TYPE_PINGRESP = 13, 58 | MQTT_MSG_TYPE_DISCONNECT = 14 59 | }; 60 | 61 | enum mqtt_connect_return_code 62 | { 63 | CONNECTION_ACCEPTED = 0, 64 | CONNECTION_REFUSE_PROTOCOL, 65 | CONNECTION_REFUSE_ID_REJECTED, 66 | CONNECTION_REFUSE_SERVER_UNAVAILABLE, 67 | CONNECTION_REFUSE_BAD_USERNAME, 68 | CONNECTION_REFUSE_NOT_AUTHORIZED 69 | }; 70 | 71 | typedef struct mqtt_message 72 | { 73 | uint8_t* data; 74 | uint16_t length; 75 | 76 | } mqtt_message_t; 77 | 78 | typedef struct mqtt_connection 79 | { 80 | mqtt_message_t message; 81 | 82 | uint16_t message_id; 83 | uint8_t* buffer; 84 | uint16_t buffer_length; 85 | 86 | } mqtt_connection_t; 87 | 88 | typedef struct mqtt_connect_info 89 | { 90 | char* client_id; 91 | char* username; 92 | char* password; 93 | char* will_topic; 94 | char* will_message; 95 | int keepalive; 96 | int will_qos; 97 | int will_retain; 98 | int clean_session; 99 | 100 | } mqtt_connect_info_t; 101 | 102 | 103 | static inline int mqtt_get_type(uint8_t* buffer) { return (buffer[0] & 0xf0) >> 4; } 104 | static inline int mqtt_get_connect_return_code(uint8_t* buffer) { return buffer[3]; } 105 | static inline int mqtt_get_dup(uint8_t* buffer) { return (buffer[0] & 0x08) >> 3; } 106 | static inline int mqtt_get_qos(uint8_t* buffer) { return (buffer[0] & 0x06) >> 1; } 107 | static inline int mqtt_get_retain(uint8_t* buffer) { return (buffer[0] & 0x01); } 108 | 109 | void mqtt_msg_init(mqtt_connection_t* connection, uint8_t* buffer, uint16_t buffer_length); 110 | int mqtt_get_total_length(uint8_t* buffer, uint16_t length); 111 | const char* mqtt_get_publish_topic(uint8_t* buffer, uint16_t* length); 112 | const char* mqtt_get_publish_data(uint8_t* buffer, uint16_t* length); 113 | uint16_t mqtt_get_id(uint8_t* buffer, uint16_t length); 114 | 115 | mqtt_message_t* mqtt_msg_connect(mqtt_connection_t* connection, mqtt_connect_info_t* info); 116 | mqtt_message_t* mqtt_msg_publish(mqtt_connection_t* connection, const char* topic, const char* data, int data_length, int qos, int retain, uint16_t* message_id); 117 | mqtt_message_t* mqtt_msg_puback(mqtt_connection_t* connection, uint16_t message_id); 118 | mqtt_message_t* mqtt_msg_pubrec(mqtt_connection_t* connection, uint16_t message_id); 119 | mqtt_message_t* mqtt_msg_pubrel(mqtt_connection_t* connection, uint16_t message_id); 120 | mqtt_message_t* mqtt_msg_pubcomp(mqtt_connection_t* connection, uint16_t message_id); 121 | mqtt_message_t* mqtt_msg_subscribe(mqtt_connection_t* connection, const char* topic, int qos, uint16_t* message_id); 122 | mqtt_message_t* mqtt_msg_unsubscribe(mqtt_connection_t* connection, const char* topic, uint16_t* message_id); 123 | mqtt_message_t* mqtt_msg_pingreq(mqtt_connection_t* connection); 124 | mqtt_message_t* mqtt_msg_pingresp(mqtt_connection_t* connection); 125 | mqtt_message_t* mqtt_msg_disconnect(mqtt_connection_t* connection); 126 | 127 | 128 | #ifdef __cplusplus 129 | } 130 | #endif 131 | 132 | #endif /* MQTT_MSG_H */ 133 | 134 | -------------------------------------------------------------------------------- /lib/espmqtt/ringbuf.c: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * Ring Buffer library 4 | */ 5 | #include 6 | #include 7 | #include "ringbuf.h" 8 | 9 | /** 10 | * \brief init a RINGBUF object 11 | * \param r pointer to a RINGBUF object 12 | * \param buf pointer to a byte array 13 | * \param size size of buf 14 | * \param block_size is size of data as block 15 | * \return 0 if successfull, otherwise failed 16 | */ 17 | int32_t rb_init(RINGBUF *r, uint8_t* buf, int32_t size, int32_t block_size) 18 | { 19 | if (r == 0 || buf == 0 || size < 2) return -1; 20 | 21 | if (size % block_size != 0) return -1; 22 | 23 | r->p_o = r->p_r = r->p_w = buf; 24 | r->fill_cnt = 0; 25 | r->size = size; 26 | r->block_size = block_size; 27 | return 0; 28 | } 29 | /** 30 | * \brief put a character into ring buffer 31 | * \param r pointer to a ringbuf object 32 | * \param c character to be put 33 | * \return 0 if successfull, otherwise failed 34 | */ 35 | int32_t rb_put(RINGBUF *r, uint8_t *c) 36 | { 37 | int32_t i; 38 | uint8_t *data = c; 39 | if (r->fill_cnt >= r->size) 40 | return -1; // ring buffer is full, this should be atomic operation 41 | 42 | 43 | r->fill_cnt += r->block_size; // increase filled slots count, this should be atomic operation 44 | 45 | for (i = 0; i < r->block_size; i++) { 46 | *r->p_w = *data; // put character into buffer 47 | 48 | r->p_w ++; 49 | data ++; 50 | } 51 | 52 | if (r->p_w >= r->p_o + r->size) // rollback if write pointer go pass 53 | r->p_w = r->p_o; // the physical boundary 54 | 55 | return 0; 56 | } 57 | /** 58 | * \brief get a character from ring buffer 59 | * \param r pointer to a ringbuf object 60 | * \param c read character 61 | * \return 0 if successfull, otherwise failed 62 | */ 63 | int32_t rb_get(RINGBUF *r, uint8_t *c) 64 | { 65 | int32_t i; 66 | uint8_t *data = c; 67 | if (r->fill_cnt <= 0)return -1; // ring buffer is empty, this should be atomic operation 68 | 69 | r->fill_cnt -= r->block_size; // decrease filled slots count 70 | 71 | for (i = 0; i < r->block_size; i++) 72 | *data++ = *r->p_r++; // get the character out 73 | 74 | if (r->p_r >= r->p_o + r->size) // rollback if write pointer go pass 75 | r->p_r = r->p_o; // the physical boundary 76 | 77 | return 0; 78 | } 79 | 80 | int32_t rb_available(RINGBUF *r) 81 | { 82 | return (r->size - r->fill_cnt); 83 | } 84 | 85 | uint32_t rb_read(RINGBUF *r, uint8_t *buf, int len) 86 | { 87 | int n = 0; 88 | uint8_t data; 89 | while (len > 0) { 90 | while (rb_get(r, &data) != 0); 91 | *buf++ = data; 92 | n ++; 93 | len --; 94 | } 95 | 96 | return n; 97 | } 98 | 99 | uint32_t rb_write(RINGBUF *r, uint8_t *buf, int len) 100 | { 101 | uint32_t wi; 102 | for (wi = 0; wi < len; wi++) { 103 | while (rb_put(r, &buf[wi]) != 0); 104 | } 105 | return 0; 106 | } 107 | -------------------------------------------------------------------------------- /lib/espmqtt/ringbuf.h: -------------------------------------------------------------------------------- 1 | #ifndef _RING_BUF_H_ 2 | #define _RING_BUF_H_ 3 | 4 | #include 5 | 6 | 7 | typedef struct{ 8 | uint8_t* p_o; /**< Original pointer */ 9 | uint8_t* volatile p_r; /**< Read pointer */ 10 | uint8_t* volatile p_w; /**< Write pointer */ 11 | volatile int32_t fill_cnt; /**< Number of filled slots */ 12 | int32_t size; /**< Buffer size */ 13 | int32_t block_size; 14 | }RINGBUF; 15 | 16 | int32_t rb_init(RINGBUF *r, uint8_t* buf, int32_t size, int32_t block_size); 17 | int32_t rb_put(RINGBUF *r, uint8_t* c); 18 | int32_t rb_get(RINGBUF *r, uint8_t* c); 19 | int32_t rb_available(RINGBUF *r); 20 | uint32_t rb_read(RINGBUF *r, uint8_t *buf, int len); 21 | uint32_t rb_write(RINGBUF *r, uint8_t *buf, int len); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /lib/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for the project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link to executable file. 4 | 5 | The source code of each library should be placed in separate directory, like 6 | "lib/private_lib/[here are source files]". 7 | 8 | For example, see how can be organised `Foo` and `Bar` libraries: 9 | 10 | |--lib 11 | | |--Bar 12 | | | |--docs 13 | | | |--examples 14 | | | |--src 15 | | | |- Bar.c 16 | | | |- Bar.h 17 | | |--Foo 18 | | | |- Foo.c 19 | | | |- Foo.h 20 | | |- readme.txt --> THIS FILE 21 | |- platformio.ini 22 | |--src 23 | |- main.c 24 | 25 | Then in `src/main.c` you should use: 26 | 27 | #include 28 | #include 29 | 30 | // rest H/C/CPP code 31 | 32 | PlatformIO will find your libraries automatically, configure preprocessor's 33 | include paths and build them. 34 | 35 | See additional options for PlatformIO Library Dependency Finder `lib_*`: 36 | 37 | http://docs.platformio.org/page/projectconf.html#lib-install 38 | 39 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter, extra scripting 4 | ; Upload options: custom port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; 7 | ; Please visit documentation for the other options and examples 8 | ; http://docs.platformio.org/page/projectconf.html 9 | 10 | [env:esp32dev] 11 | platform = espressif32 12 | framework = espidf 13 | board = esp32dev 14 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | user_config.h -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | This is a BLE Central Node observing advertising packets. 3 | */ 4 | 5 | // Based from the IDF GATT Client example - https://github.com/espressif/esp-idf/tree/master/examples/bluetooth/gatt_client 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "controller.h" 13 | 14 | #include "bt.h" 15 | #include "bt_trace.h" 16 | #include "bt_types.h" 17 | #include "btm_api.h" 18 | #include "bta_api.h" 19 | #include "bta_gatt_api.h" 20 | #include "esp_gap_ble_api.h" 21 | #include "esp_gatt_defs.h" 22 | #include "esp_bt_main.h" 23 | 24 | #include "esp_wifi.h" 25 | #include "esp_system.h" 26 | #include "nvs_flash.h" 27 | #include "esp_event_loop.h" 28 | 29 | #include "freertos/FreeRTOS.h" 30 | #include "freertos/task.h" 31 | #include "freertos/semphr.h" 32 | #include "freertos/queue.h" 33 | 34 | #include "lwip/sockets.h" 35 | #include "lwip/dns.h" 36 | #include "lwip/netdb.h" 37 | 38 | #include "user_config.h" 39 | 40 | #define INFO(...) printf(__VA_ARGS__) 41 | 42 | #include "mqtt.h" 43 | 44 | const uint32_t scan_duration = 3000; // seconds ? 45 | 46 | ///Declare static functions 47 | static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); 48 | 49 | static esp_ble_scan_params_t ble_scan_params = { 50 | .scan_type = BLE_SCAN_TYPE_PASSIVE, 51 | .own_addr_type = BLE_ADDR_TYPE_PUBLIC, 52 | .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, 53 | .scan_interval = 0x10, 54 | .scan_window = 0x08, 55 | }; 56 | 57 | static mqtt_client *client; 58 | 59 | typedef uint8_t mac_address_t[6]; 60 | 61 | static mac_address_t addresses[] = { 62 | DEVICE_ADDR_1, 63 | DEVICE_ADDR_1, 64 | }; 65 | 66 | static void publish_mac(char *mac, int rssi) { 67 | char payload[256]; 68 | int payload_len = sprintf(payload, "{\"topic\":\"beacon\",\"mac\":\"%s\",\"rssi\":%d}", mac, rssi); 69 | printf("Publishing payload: %s\n", payload); 70 | mqtt_publish(client, "gohome/beacon", payload, payload_len, 0, 0); 71 | } 72 | 73 | static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { 74 | switch (event) { 75 | case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { 76 | printf("scan param set complete, scanning...\n"); 77 | esp_ble_gap_start_scanning(scan_duration); 78 | break; 79 | } 80 | case ESP_GAP_BLE_SCAN_RESULT_EVT: { 81 | esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param; 82 | switch (scan_result->scan_rst.search_evt) { 83 | case ESP_GAP_SEARCH_INQ_RES_EVT: { 84 | // forward beacon to MQTT 85 | uint8_t *addr = scan_result->scan_rst.bda; 86 | // check addresses 87 | bool matched = false; 88 | for (int i = 0; i < sizeof(addresses)/sizeof(mac_address_t); ++i) { 89 | uint8_t *a = (uint8_t *)addresses[i]; 90 | if (addr[0] == a[0] && addr[1] == a[1] && addr[2] == a[2] && addr[3] == a[3] && addr[4] == a[4] && addr[5] == a[5]) { 91 | matched = true; 92 | break; 93 | } 94 | } 95 | 96 | if (!matched) 97 | return; 98 | 99 | char mac[18]; 100 | sprintf(mac, "%02X:%02X:%02X:%02X:%02X:%02X", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); 101 | int rssi = scan_result->scan_rst.rssi; 102 | printf("Device %s, RSSI=%i\n", mac, rssi); 103 | publish_mac(mac, rssi); 104 | break; 105 | } 106 | case ESP_GAP_SEARCH_INQ_CMPL_EVT: { 107 | // scan params must be reset before starting another scan 108 | printf("scan completed, restarting...\n"); 109 | esp_ble_gap_set_scan_params(&ble_scan_params); 110 | break; 111 | } 112 | default: 113 | break; 114 | } 115 | break; 116 | } 117 | default: 118 | break; 119 | } 120 | } 121 | 122 | void ble_client_appRegister(void) 123 | { 124 | esp_err_t status; 125 | 126 | printf("register callback\n"); 127 | 128 | // register the scan callback function to the gap module 129 | if ((status = esp_ble_gap_register_callback(esp_gap_cb)) != ESP_OK) { 130 | printf("ERROR: gap register error, error code = %x\n", status); 131 | return; 132 | } 133 | 134 | esp_ble_gap_set_scan_params(&ble_scan_params); 135 | } 136 | 137 | void mqtt_connected_cb(void *self, void *params) 138 | { 139 | } 140 | 141 | void mqtt_disconnected_cb(void *self, void *params) 142 | { 143 | 144 | } 145 | 146 | void mqtt_reconnect_cb(void *self, void *params) 147 | { 148 | 149 | } 150 | 151 | void mqtt_subscribe_cb(void *self, void *params) 152 | { 153 | INFO("[APP] Subscribe ok, test publish msg\n"); 154 | mqtt_publish(client, "/test", "abcde", 5, 0, 0); 155 | } 156 | 157 | void mqtt_publish_cb(void *self, void *params) 158 | { 159 | 160 | } 161 | 162 | mqtt_settings settings = { 163 | .host = "mqtt", 164 | .port = 1883, 165 | .client_id = "mqtt_client_id", 166 | .clean_session = 0, 167 | .keepalive = 120, 168 | .connected_cb = mqtt_connected_cb, 169 | .disconnected_cb = mqtt_disconnected_cb, 170 | .reconnect_cb = mqtt_reconnect_cb, 171 | .publish_cb = mqtt_publish_cb, 172 | }; 173 | 174 | static esp_err_t wifi_event_handler(void *ctx, system_event_t *event) 175 | { 176 | switch(event->event_id) { 177 | case SYSTEM_EVENT_STA_START: 178 | ESP_ERROR_CHECK(esp_wifi_connect()); 179 | break; 180 | 181 | case SYSTEM_EVENT_STA_GOT_IP: 182 | 183 | client = mqtt_start(&settings); 184 | ble_client_appRegister(); 185 | // Notice that, all callback will called in mqtt_task 186 | // All function publish, subscribe 187 | break; 188 | case SYSTEM_EVENT_STA_DISCONNECTED: 189 | /* This is a workaround as ESP32 WiFi libs don't currently 190 | auto-reassociate. */ 191 | 192 | mqtt_stop(); 193 | ESP_ERROR_CHECK(esp_wifi_connect()); 194 | break; 195 | default: 196 | break; 197 | } 198 | return ESP_OK; 199 | 200 | 201 | } 202 | 203 | void wifi_conn_init(void) 204 | { 205 | INFO("[APP] Start, connect to Wifi network: %s ..\n", WIFI_SSID); 206 | 207 | tcpip_adapter_init(); 208 | 209 | ESP_ERROR_CHECK( esp_event_loop_init(wifi_event_handler, NULL) ); 210 | 211 | wifi_init_config_t icfg = WIFI_INIT_CONFIG_DEFAULT(); 212 | ESP_ERROR_CHECK( esp_wifi_init(&icfg) ); 213 | ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); 214 | 215 | wifi_config_t wifi_config = { 216 | .sta = { 217 | .ssid = WIFI_SSID, 218 | .password = WIFI_PASS 219 | }, 220 | }; 221 | 222 | ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA)); 223 | ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); 224 | ESP_ERROR_CHECK( esp_wifi_start()); 225 | } 226 | 227 | void app_main() 228 | { 229 | INFO("[APP] Startup..\n"); 230 | INFO("[APP] Free memory: %d bytes\n", esp_get_free_heap_size()); 231 | 232 | nvs_flash_init(); 233 | wifi_conn_init(); 234 | 235 | esp_bt_controller_init(); 236 | 237 | if (esp_bt_controller_enable(ESP_BT_MODE_BTDM) != ESP_OK) { 238 | return; 239 | } 240 | esp_bluedroid_init(); 241 | esp_bluedroid_enable(); 242 | } 243 | -------------------------------------------------------------------------------- /src/sdkconfig.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Automatically generated file; DO NOT EDIT. 4 | * Espressif IoT Development Framework Configuration 5 | * 6 | */ 7 | #define CONFIG_ESP32_PHY_MAX_TX_POWER 20 8 | #define CONFIG_PHY_ENABLED 1 9 | #define CONFIG_TRACEMEM_RESERVE_DRAM 0x0 10 | #define CONFIG_FOUR_MAC_ADDRESS_FROM_EFUSE 1 11 | #define CONFIG_ESPTOOLPY_FLASHFREQ "40m" 12 | #define CONFIG_NEWLIB_STDOUT_ADDCR 1 13 | #define CONFIG_TASK_WDT_CHECK_IDLE_TASK 1 14 | #define CONFIG_ESPTOOLPY_FLASHSIZE "2MB" 15 | #define CONFIG_ETHERNET 1 16 | #define CONFIG_INT_WDT 1 17 | #define CONFIG_ESPTOOLPY_FLASHFREQ_40M 1 18 | #define CONFIG_LOG_BOOTLOADER_LEVEL_INFO 1 19 | #define CONFIG_ESPTOOLPY_FLASHSIZE_2MB 1 20 | #define CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS 1 21 | #define CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM 10 22 | #define CONFIG_LOG_DEFAULT_LEVEL_INFO 1 23 | #define CONFIG_BT_RESERVE_DRAM 0x10000 24 | #define CONFIG_ESP32_PANIC_PRINT_REBOOT 1 25 | #define CONFIG_CONSOLE_UART_BAUDRATE 115200 26 | #define CONFIG_LWIP_MAX_SOCKETS 10 27 | #define CONFIG_EMAC_TASK_PRIORITY 20 28 | #define CONFIG_ULP_COPROC_RESERVE_MEM 0 29 | #define CONFIG_ESPTOOLPY_BAUD 115200 30 | #define CONFIG_INT_WDT_CHECK_CPU1 1 31 | #define CONFIG_ESPTOOLPY_AFTER_RESET 1 32 | #define CONFIG_TOOLPREFIX "xtensa-esp32-elf-" 33 | #define CONFIG_ESP32_WIFI_AMPDU_ENABLED 1 34 | #define CONFIG_CONSOLE_UART_NUM 0 35 | #define CONFIG_ESP32_RTC_CLOCK_SOURCE_INTERNAL_RC 1 36 | #define CONFIG_ESPTOOLPY_BAUD_115200B 1 37 | #define CONFIG_LWIP_THREAD_LOCAL_STORAGE_INDEX 0 38 | #define CONFIG_CONSOLE_UART_DEFAULT 1 39 | #define CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN 16384 40 | #define CONFIG_ESPTOOLPY_FLASHSIZE_DETECT 1 41 | #define CONFIG_MBEDTLS_MPI_USE_INTERRUPT 1 42 | #define CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE 1 43 | #define CONFIG_TASK_WDT 1 44 | #define CONFIG_MAIN_TASK_STACK_SIZE 4096 45 | #define CONFIG_TASK_WDT_TIMEOUT_S 5 46 | #define CONFIG_INT_WDT_TIMEOUT_MS 300 47 | #define CONFIG_ESPTOOLPY_FLASHMODE "dio" 48 | #define CONFIG_BTC_TASK_STACK_SIZE 3072 49 | #define CONFIG_ESPTOOLPY_BEFORE "default_reset" 50 | #define CONFIG_LOG_DEFAULT_LEVEL 3 51 | #define CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION 1 52 | #define CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM 0 53 | #define CONFIG_SPI_FLASH_ENABLE_COUNTERS 1 54 | #define CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER 20 55 | #define CONFIG_ESP32_WIFI_NVS_ENABLED 1 56 | #define CONFIG_DMA_RX_BUF_NUM 10 57 | #define CONFIG_ESPTOOLPY_FLASHMODE_DIO 1 58 | #define CONFIG_TCP_SYNMAXRTX 6 59 | #define CONFIG_PYTHON "python" 60 | #define CONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1 1 61 | #define CONFIG_ESPTOOLPY_COMPRESSED 1 62 | #define CONFIG_PARTITION_TABLE_FILENAME "partitions_singleapp.csv" 63 | #define CONFIG_LWIP_DHCP_MAX_NTP_SERVERS 1 64 | #define CONFIG_PARTITION_TABLE_SINGLE_APP 1 65 | #define CONFIG_NUMBER_OF_MAC_ADDRESS_GENERATED_FROM_EFUSE 4 66 | #define CONFIG_WIFI_ENABLED 1 67 | #define CONFIG_LWIP_DHCP_DOES_ARP_CHECK 1 68 | #define CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE 4096 69 | #define CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY 0 70 | #define CONFIG_PHY_DATA_OFFSET 0xf000 71 | #define CONFIG_PARTITION_TABLE_CUSTOM_APP_BIN_OFFSET 0x10000 72 | #define CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM 32 73 | #define CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1 1 74 | #define CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ 240 75 | #define CONFIG_MBEDTLS_HARDWARE_AES 1 76 | #define CONFIG_FREERTOS_HZ 100 77 | #define CONFIG_LOG_COLORS 1 78 | #define CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE 1 79 | #define CONFIG_FREERTOS_ASSERT_FAIL_ABORT 1 80 | #define CONFIG_MONITOR_BAUD_115200B 1 81 | #define CONFIG_LOG_BOOTLOADER_LEVEL 3 82 | #define CONFIG_ESPTOOLPY_BEFORE_RESET 1 83 | #define CONFIG_ESPTOOLPY_BAUD_OTHER_VAL 115200 84 | #define CONFIG_ESP32_DEFAULT_CPU_FREQ_240 1 85 | #define CONFIG_TCP_MAXRTX 12 86 | #define CONFIG_ESPTOOLPY_AFTER "hard_reset" 87 | #define CONFIG_DMA_TX_BUF_NUM 10 88 | #define CONFIG_ESP32_DEBUG_OCDAWARE 1 89 | #define CONFIG_FREERTOS_BREAK_ON_SCHEDULER_START_JTAG 1 90 | #define CONFIG_BT_ENABLED 1 91 | #define CONFIG_MONITOR_BAUD 115200 92 | #define CONFIG_MBEDTLS_HARDWARE_SHA 1 93 | #define CONFIG_FREERTOS_CORETIMER_0 1 94 | #define CONFIG_PARTITION_TABLE_CUSTOM_FILENAME "partitions.csv" 95 | #define CONFIG_MBEDTLS_HAVE_TIME 1 96 | #define CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY 1 97 | #define CONFIG_FREERTOS_ISR_STACKSIZE 1536 98 | #define CONFIG_OPENSSL_ASSERT_DO_NOTHING 1 99 | #define CONFIG_OPTIMIZATION_LEVEL_DEBUG 1 100 | #define CONFIG_SYSTEM_EVENT_QUEUE_SIZE 32 101 | #define CONFIG_APP_OFFSET 0x10000 102 | #define CONFIG_MEMMAP_SMP 1 103 | #define CONFIG_MBEDTLS_HARDWARE_MPI 1 104 | #define CONFIG_MONITOR_BAUD_OTHER_VAL 115200 105 | #define CONFIG_ESPTOOLPY_PORT "/dev/ttyUSB0" 106 | 107 | #define CONFIG_MQTT_PROTOCOL_311 y 108 | #define CONFIG_MQTT_PRIORITY 5 109 | #define CONFIG_MQTT_LOG_ERROR_ON y 110 | #define CONFIG_MQTT_LOG_WARN_ON y 111 | #define CONFIG_MQTT_LOG_INFO_ON y 112 | #define CONFIG_MQTT_RECONNECT_TIMEOUT 60 113 | #define CONFIG_MQTT_QUEUE_BUFFER_SIZE_WORD 1024 114 | #define CONFIG_MQTT_BUFFER_SIZE_BYTE 1024 115 | #define CONFIG_MQTT_MAX_HOST_LEN 64 116 | #define CONFIG_MQTT_MAX_CLIENT_LEN 32 117 | #define CONFIG_MQTT_MAX_USERNAME_LEN 32 118 | #define CONFIG_MQTT_MAX_PASSWORD_LEN 32 119 | #define CONFIG_MQTT_MAX_LWT_TOPIC 32 120 | #define CONFIG_MQTT_MAX_LWT_MSG 32 -------------------------------------------------------------------------------- /src/user_config.h.example: -------------------------------------------------------------------------------- 1 | /* 2 | * ESPRSSIF MIT License 3 | * 4 | * Copyright (c) 2015 5 | * 6 | * Permission is hereby granted for use on ESPRESSIF SYSTEMS ESP8266 only, in which case, 7 | * it is free of charge, to any person obtaining a copy of this software and associated 8 | * documentation files (the "Software"), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished 11 | * to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all copies or 14 | * substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | * 23 | */ 24 | 25 | #ifndef __USER_CONFIG_LOCAL_H__ 26 | #define __USER_CONFIG_LOCAL_H__ 27 | 28 | #define WIFI_SSID "yourssid" 29 | #define WIFI_PASS "yourpass" 30 | 31 | #define DEVICE_ADDR_1 {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC}, 32 | #define DEVICE_ADDR_2 {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBD}, 33 | 34 | #endif 35 | --------------------------------------------------------------------------------