@@ -0,0 +1,12 @@ | |||
root = true | |||
[*] | |||
end_of_line = lf | |||
insert_final_newline = true | |||
charset = utf-8 | |||
indent_style = space | |||
indent_size = 2 | |||
trim_trailing_whitespace = true | |||
[keywords.txt] | |||
indent_style = tab |
@@ -0,0 +1,27 @@ | |||
name: Build with Platformio | |||
on: [push, pull_request] | |||
jobs: | |||
build: | |||
runs-on: ubuntu-latest | |||
steps: | |||
- uses: actions/checkout@v1 | |||
- name: Set up Python | |||
uses: actions/setup-python@v1 | |||
- name: Install dependencies | |||
run: | | |||
python -m pip install --upgrade pip | |||
pip install platformio | |||
- name: Add libraries | |||
run: | | |||
platformio lib -g install AsyncTCP | |||
platformio lib -g install ESPAsyncTCP | |||
- name: Getting ready | |||
run: | | |||
chmod +x ./scripts/CI/build_examples_pio.sh | |||
- name: Build examples | |||
run: | | |||
./scripts/CI/build_examples_pio.sh |
@@ -0,0 +1,20 @@ | |||
name: cpplint | |||
on: [push, pull_request] | |||
jobs: | |||
build: | |||
runs-on: ubuntu-latest | |||
steps: | |||
- uses: actions/checkout@v1 | |||
- name: Set up Python | |||
uses: actions/setup-python@v1 | |||
- name: Install dependencies | |||
run: | | |||
python -m pip install --upgrade pip | |||
pip install cpplint | |||
- name: Linting | |||
run: | | |||
cpplint --repository=. --recursive --filter=-whitespace/line_length,-legal/copyright,-runtime/printf,-build/include,-build/namespace ./src |
@@ -0,0 +1,2 @@ | |||
/config.json | |||
.vscode/ |
@@ -0,0 +1 @@ | |||
{"type": "library", "name": "AsyncMqttClient", "version": "0.9.0", "spec": {"owner": "marvinroger", "id": 346, "name": "AsyncMqttClient", "requirements": null, "uri": null}} |
@@ -0,0 +1,21 @@ | |||
The MIT License (MIT) | |||
Copyright (c) 2015-2021 Marvin Roger | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all | |||
copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
SOFTWARE. |
@@ -0,0 +1,3 @@ | |||
cpplint: | |||
cpplint --repository=. --recursive --filter=-whitespace/line_length,-legal/copyright,-runtime/printf,-build/include,-build/namespace ./src | |||
.PHONY: cpplint |
@@ -0,0 +1,19 @@ | |||
# Async MQTT client for ESP8266 and ESP32 | |||
![Build with PlatformIO](https://github.com/marvinroger/async-mqtt-client/workflows/Build%20with%20Platformio/badge.svg) | |||
![cpplint](https://github.com/marvinroger/async-mqtt-client/workflows/cpplint/badge.svg) | |||
An Arduino for ESP8266 and ESP32 asynchronous [MQTT](http://mqtt.org/) client implementation, built on [me-no-dev/ESPAsyncTCP (ESP8266)](https://github.com/me-no-dev/ESPAsyncTCP) | [me-no-dev/AsyncTCP (ESP32)](https://github.com/me-no-dev/AsyncTCP) . | |||
## Features | |||
* Compliant with the 3.1.1 version of the protocol | |||
* Fully asynchronous | |||
* Subscribe at QoS 0, 1 and 2 | |||
* Publish at QoS 0, 1 and 2 | |||
* SSL/TLS support | |||
* Available in the [PlatformIO registry](http://platformio.org/lib/show/346/AsyncMqttClient) | |||
## Requirements, installation and usage | |||
The project is documented in the [/docs folder](docs). |
@@ -0,0 +1,6 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project version="1"> | |||
<includedir> | |||
<dir name="src/"/> | |||
</includedir> | |||
</project> |
@@ -0,0 +1,135 @@ | |||
/* | |||
This example uses FreeRTOS softwaretimers as there is no built-in Ticker library | |||
*/ | |||
#include <WiFi.h> | |||
extern "C" { | |||
#include "freertos/FreeRTOS.h" | |||
#include "freertos/timers.h" | |||
} | |||
#include <AsyncMqttClient.h> | |||
#define WIFI_SSID "yourSSID" | |||
#define WIFI_PASSWORD "yourpass" | |||
#define MQTT_HOST IPAddress(192, 168, 1, 10) | |||
#define MQTT_PORT 1883 | |||
AsyncMqttClient mqttClient; | |||
TimerHandle_t mqttReconnectTimer; | |||
TimerHandle_t wifiReconnectTimer; | |||
void connectToWifi() { | |||
Serial.println("Connecting to Wi-Fi..."); | |||
WiFi.begin(WIFI_SSID, WIFI_PASSWORD); | |||
} | |||
void connectToMqtt() { | |||
Serial.println("Connecting to MQTT..."); | |||
mqttClient.connect(); | |||
} | |||
void WiFiEvent(WiFiEvent_t event) { | |||
Serial.printf("[WiFi-event] event: %d\n", event); | |||
switch(event) { | |||
case SYSTEM_EVENT_STA_GOT_IP: | |||
Serial.println("WiFi connected"); | |||
Serial.println("IP address: "); | |||
Serial.println(WiFi.localIP()); | |||
connectToMqtt(); | |||
break; | |||
case SYSTEM_EVENT_STA_DISCONNECTED: | |||
Serial.println("WiFi lost connection"); | |||
xTimerStop(mqttReconnectTimer, 0); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi | |||
xTimerStart(wifiReconnectTimer, 0); | |||
break; | |||
} | |||
} | |||
void onMqttConnect(bool sessionPresent) { | |||
Serial.println("Connected to MQTT."); | |||
Serial.print("Session present: "); | |||
Serial.println(sessionPresent); | |||
uint16_t packetIdSub = mqttClient.subscribe("test/lol", 2); | |||
Serial.print("Subscribing at QoS 2, packetId: "); | |||
Serial.println(packetIdSub); | |||
mqttClient.publish("test/lol", 0, true, "test 1"); | |||
Serial.println("Publishing at QoS 0"); | |||
uint16_t packetIdPub1 = mqttClient.publish("test/lol", 1, true, "test 2"); | |||
Serial.print("Publishing at QoS 1, packetId: "); | |||
Serial.println(packetIdPub1); | |||
uint16_t packetIdPub2 = mqttClient.publish("test/lol", 2, true, "test 3"); | |||
Serial.print("Publishing at QoS 2, packetId: "); | |||
Serial.println(packetIdPub2); | |||
} | |||
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { | |||
Serial.println("Disconnected from MQTT."); | |||
if (WiFi.isConnected()) { | |||
xTimerStart(mqttReconnectTimer, 0); | |||
} | |||
} | |||
void onMqttSubscribe(uint16_t packetId, uint8_t qos) { | |||
Serial.println("Subscribe acknowledged."); | |||
Serial.print(" packetId: "); | |||
Serial.println(packetId); | |||
Serial.print(" qos: "); | |||
Serial.println(qos); | |||
} | |||
void onMqttUnsubscribe(uint16_t packetId) { | |||
Serial.println("Unsubscribe acknowledged."); | |||
Serial.print(" packetId: "); | |||
Serial.println(packetId); | |||
} | |||
void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { | |||
Serial.println("Publish received."); | |||
Serial.print(" topic: "); | |||
Serial.println(topic); | |||
Serial.print(" qos: "); | |||
Serial.println(properties.qos); | |||
Serial.print(" dup: "); | |||
Serial.println(properties.dup); | |||
Serial.print(" retain: "); | |||
Serial.println(properties.retain); | |||
Serial.print(" len: "); | |||
Serial.println(len); | |||
Serial.print(" index: "); | |||
Serial.println(index); | |||
Serial.print(" total: "); | |||
Serial.println(total); | |||
} | |||
void onMqttPublish(uint16_t packetId) { | |||
Serial.println("Publish acknowledged."); | |||
Serial.print(" packetId: "); | |||
Serial.println(packetId); | |||
} | |||
void setup() { | |||
Serial.begin(115200); | |||
Serial.println(); | |||
Serial.println(); | |||
mqttReconnectTimer = xTimerCreate("mqttTimer", pdMS_TO_TICKS(2000), pdFALSE, (void*)0, reinterpret_cast<TimerCallbackFunction_t>(connectToMqtt)); | |||
wifiReconnectTimer = xTimerCreate("wifiTimer", pdMS_TO_TICKS(2000), pdFALSE, (void*)0, reinterpret_cast<TimerCallbackFunction_t>(connectToWifi)); | |||
WiFi.onEvent(WiFiEvent); | |||
mqttClient.onConnect(onMqttConnect); | |||
mqttClient.onDisconnect(onMqttDisconnect); | |||
mqttClient.onSubscribe(onMqttSubscribe); | |||
mqttClient.onUnsubscribe(onMqttUnsubscribe); | |||
mqttClient.onMessage(onMqttMessage); | |||
mqttClient.onPublish(onMqttPublish); | |||
mqttClient.setServer(MQTT_HOST, MQTT_PORT); | |||
connectToWifi(); | |||
} | |||
void loop() { | |||
} |
@@ -0,0 +1,122 @@ | |||
#include <ESP8266WiFi.h> | |||
#include <Ticker.h> | |||
#include <AsyncMqttClient.h> | |||
#define WIFI_SSID "My_Wi-Fi" | |||
#define WIFI_PASSWORD "my-awesome-password" | |||
#define MQTT_HOST IPAddress(192, 168, 1, 10) | |||
#define MQTT_PORT 1883 | |||
AsyncMqttClient mqttClient; | |||
Ticker mqttReconnectTimer; | |||
WiFiEventHandler wifiConnectHandler; | |||
WiFiEventHandler wifiDisconnectHandler; | |||
Ticker wifiReconnectTimer; | |||
void connectToWifi() { | |||
Serial.println("Connecting to Wi-Fi..."); | |||
WiFi.begin(WIFI_SSID, WIFI_PASSWORD); | |||
} | |||
void connectToMqtt() { | |||
Serial.println("Connecting to MQTT..."); | |||
mqttClient.connect(); | |||
} | |||
void onWifiConnect(const WiFiEventStationModeGotIP& event) { | |||
Serial.println("Connected to Wi-Fi."); | |||
connectToMqtt(); | |||
} | |||
void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) { | |||
Serial.println("Disconnected from Wi-Fi."); | |||
mqttReconnectTimer.detach(); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi | |||
wifiReconnectTimer.once(2, connectToWifi); | |||
} | |||
void onMqttConnect(bool sessionPresent) { | |||
Serial.println("Connected to MQTT."); | |||
Serial.print("Session present: "); | |||
Serial.println(sessionPresent); | |||
uint16_t packetIdSub = mqttClient.subscribe("test/lol", 2); | |||
Serial.print("Subscribing at QoS 2, packetId: "); | |||
Serial.println(packetIdSub); | |||
mqttClient.publish("test/lol", 0, true, "test 1"); | |||
Serial.println("Publishing at QoS 0"); | |||
uint16_t packetIdPub1 = mqttClient.publish("test/lol", 1, true, "test 2"); | |||
Serial.print("Publishing at QoS 1, packetId: "); | |||
Serial.println(packetIdPub1); | |||
uint16_t packetIdPub2 = mqttClient.publish("test/lol", 2, true, "test 3"); | |||
Serial.print("Publishing at QoS 2, packetId: "); | |||
Serial.println(packetIdPub2); | |||
} | |||
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { | |||
Serial.println("Disconnected from MQTT."); | |||
if (WiFi.isConnected()) { | |||
mqttReconnectTimer.once(2, connectToMqtt); | |||
} | |||
} | |||
void onMqttSubscribe(uint16_t packetId, uint8_t qos) { | |||
Serial.println("Subscribe acknowledged."); | |||
Serial.print(" packetId: "); | |||
Serial.println(packetId); | |||
Serial.print(" qos: "); | |||
Serial.println(qos); | |||
} | |||
void onMqttUnsubscribe(uint16_t packetId) { | |||
Serial.println("Unsubscribe acknowledged."); | |||
Serial.print(" packetId: "); | |||
Serial.println(packetId); | |||
} | |||
void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { | |||
Serial.println("Publish received."); | |||
Serial.print(" topic: "); | |||
Serial.println(topic); | |||
Serial.print(" qos: "); | |||
Serial.println(properties.qos); | |||
Serial.print(" dup: "); | |||
Serial.println(properties.dup); | |||
Serial.print(" retain: "); | |||
Serial.println(properties.retain); | |||
Serial.print(" len: "); | |||
Serial.println(len); | |||
Serial.print(" index: "); | |||
Serial.println(index); | |||
Serial.print(" total: "); | |||
Serial.println(total); | |||
} | |||
void onMqttPublish(uint16_t packetId) { | |||
Serial.println("Publish acknowledged."); | |||
Serial.print(" packetId: "); | |||
Serial.println(packetId); | |||
} | |||
void setup() { | |||
Serial.begin(115200); | |||
Serial.println(); | |||
Serial.println(); | |||
wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect); | |||
wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect); | |||
mqttClient.onConnect(onMqttConnect); | |||
mqttClient.onDisconnect(onMqttDisconnect); | |||
mqttClient.onSubscribe(onMqttSubscribe); | |||
mqttClient.onUnsubscribe(onMqttUnsubscribe); | |||
mqttClient.onMessage(onMqttMessage); | |||
mqttClient.onPublish(onMqttPublish); | |||
mqttClient.setServer(MQTT_HOST, MQTT_PORT); | |||
connectToWifi(); | |||
} | |||
void loop() { | |||
} |
@@ -0,0 +1,24 @@ | |||
# | |||
# Example PlatformIO configuration file for SSL and non-SSL builds. | |||
# | |||
# Before you will be able to build the SSL version of this project, you will | |||
# need to explicitly install the espressif8266_stage platform. | |||
# | |||
# To perform this installation, refer to step 1 of: | |||
# http://docs.platformio.org/en/latest/platforms/espressif8266.html#using-arduino-framework-with-staging-version | |||
[platformio] | |||
env_default = ssl | |||
[env:ssl] | |||
platform = espressif8266_stage | |||
framework = arduino | |||
board = esp01_1m | |||
build_flags = -DASYNC_TCP_SSL_ENABLED=1 | |||
lib_deps = AsyncMqttClient | |||
[env:nossl] | |||
platform = espressif8266 | |||
framework = arduino | |||
board = esp01_1m | |||
lib_deps = AsyncMqttClient |
@@ -0,0 +1,145 @@ | |||
// Example project which can be built with SSL enabled or disabled. | |||
// The espressif8266_stage platform must be installed. | |||
// Refer to platformio.ini for the build configuration and platform installation. | |||
#include <Arduino.h> | |||
#include <ESP8266WiFi.h> | |||
#include <Ticker.h> | |||
#include <AsyncMqttClient.h> | |||
#define WIFI_SSID "My_Wi-Fi" | |||
#define WIFI_PASSWORD "my-awesome-password" | |||
#define MQTT_HOST IPAddress(192, 168, 1, 10) | |||
#if ASYNC_TCP_SSL_ENABLED | |||
#define MQTT_SECURE true | |||
#define MQTT_SERVER_FINGERPRINT {0x7e, 0x36, 0x22, 0x01, 0xf9, 0x7e, 0x99, 0x2f, 0xc5, 0xdb, 0x3d, 0xbe, 0xac, 0x48, 0x67, 0x5b, 0x5d, 0x47, 0x94, 0xd2} | |||
#define MQTT_PORT 8883 | |||
#else | |||
#define MQTT_PORT 1883 | |||
#endif | |||
AsyncMqttClient mqttClient; | |||
Ticker mqttReconnectTimer; | |||
WiFiEventHandler wifiConnectHandler; | |||
WiFiEventHandler wifiDisconnectHandler; | |||
Ticker wifiReconnectTimer; | |||
void connectToWifi() { | |||
Serial.println("Connecting to Wi-Fi..."); | |||
WiFi.begin(WIFI_SSID, WIFI_PASSWORD); | |||
} | |||
void connectToMqtt() { | |||
Serial.println("Connecting to MQTT..."); | |||
mqttClient.connect(); | |||
} | |||
void onWifiConnect(const WiFiEventStationModeGotIP& event) { | |||
Serial.println("Connected to Wi-Fi."); | |||
connectToMqtt(); | |||
} | |||
void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) { | |||
Serial.println("Disconnected from Wi-Fi."); | |||
mqttReconnectTimer.detach(); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi | |||
wifiReconnectTimer.once(2, connectToWifi); | |||
} | |||
void onMqttConnect(bool sessionPresent) { | |||
Serial.println("Connected to MQTT."); | |||
Serial.print("Session present: "); | |||
Serial.println(sessionPresent); | |||
uint16_t packetIdSub = mqttClient.subscribe("test/lol", 2); | |||
Serial.print("Subscribing at QoS 2, packetId: "); | |||
Serial.println(packetIdSub); | |||
mqttClient.publish("test/lol", 0, true, "test 1"); | |||
Serial.println("Publishing at QoS 0"); | |||
uint16_t packetIdPub1 = mqttClient.publish("test/lol", 1, true, "test 2"); | |||
Serial.print("Publishing at QoS 1, packetId: "); | |||
Serial.println(packetIdPub1); | |||
uint16_t packetIdPub2 = mqttClient.publish("test/lol", 2, true, "test 3"); | |||
Serial.print("Publishing at QoS 2, packetId: "); | |||
Serial.println(packetIdPub2); | |||
} | |||
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { | |||
Serial.println("Disconnected from MQTT."); | |||
if (reason == AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT) { | |||
Serial.println("Bad server fingerprint."); | |||
} | |||
if (WiFi.isConnected()) { | |||
mqttReconnectTimer.once(2, connectToMqtt); | |||
} | |||
} | |||
void onMqttSubscribe(uint16_t packetId, uint8_t qos) { | |||
Serial.println("Subscribe acknowledged."); | |||
Serial.print(" packetId: "); | |||
Serial.println(packetId); | |||
Serial.print(" qos: "); | |||
Serial.println(qos); | |||
} | |||
void onMqttUnsubscribe(uint16_t packetId) { | |||
Serial.println("Unsubscribe acknowledged."); | |||
Serial.print(" packetId: "); | |||
Serial.println(packetId); | |||
} | |||
void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { | |||
Serial.println("Publish received."); | |||
Serial.print(" topic: "); | |||
Serial.println(topic); | |||
Serial.print(" qos: "); | |||
Serial.println(properties.qos); | |||
Serial.print(" dup: "); | |||
Serial.println(properties.dup); | |||
Serial.print(" retain: "); | |||
Serial.println(properties.retain); | |||
Serial.print(" len: "); | |||
Serial.println(len); | |||
Serial.print(" index: "); | |||
Serial.println(index); | |||
Serial.print(" total: "); | |||
Serial.println(total); | |||
} | |||
void onMqttPublish(uint16_t packetId) { | |||
Serial.println("Publish acknowledged."); | |||
Serial.print(" packetId: "); | |||
Serial.println(packetId); | |||
} | |||
void setup() { | |||
Serial.begin(115200); | |||
Serial.println(); | |||
Serial.println(); | |||
wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect); | |||
wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect); | |||
mqttClient.onConnect(onMqttConnect); | |||
mqttClient.onDisconnect(onMqttDisconnect); | |||
mqttClient.onSubscribe(onMqttSubscribe); | |||
mqttClient.onUnsubscribe(onMqttUnsubscribe); | |||
mqttClient.onMessage(onMqttMessage); | |||
mqttClient.onPublish(onMqttPublish); | |||
mqttClient.setServer(MQTT_HOST, MQTT_PORT); | |||
#if ASYNC_TCP_SSL_ENABLED | |||
mqttClient.setSecure(MQTT_SECURE); | |||
if (MQTT_SECURE) { | |||
mqttClient.addServerFingerprint((const uint8_t[])MQTT_SERVER_FINGERPRINT); | |||
} | |||
#endif | |||
connectToWifi(); | |||
} | |||
void loop() { | |||
} |
@@ -0,0 +1,48 @@ | |||
####################################### | |||
# Datatypes (KEYWORD1) | |||
####################################### | |||
AsyncMqttClient KEYWORD1 | |||
AsyncMqttClientDisconnectReason KEYWORD1 | |||
AsyncMqttClientMessageProperties KEYWORD1 | |||
####################################### | |||
# Methods and Functions (KEYWORD2) | |||
####################################### | |||
setKeepAlive KEYWORD2 | |||
setClientId KEYWORD2 | |||
setCleanSession KEYWORD2 | |||
setMaxTopicLength KEYWORD2 | |||
setCredentials KEYWORD2 | |||
setWill KEYWORD2 | |||
setServer KEYWORD2 | |||
setSecure KEYWORD2 | |||
addServerFingerprint KEYWORD2 | |||
onConnect KEYWORD2 | |||
onDisconnect KEYWORD2 | |||
onSubscribe KEYWORD2 | |||
onUnsubscribe KEYWORD2 | |||
onMessage KEYWORD2 | |||
onPublish KEYWORD2 | |||
connected KEYWORD2 | |||
connect KEYWORD2 | |||
disconnect KEYWORD2 | |||
subscribe KEYWORD2 | |||
unsubscribe KEYWORD2 | |||
publish KEYWORD2 | |||
clearQueue KEYWORD2 | |||
####################################### | |||
# Constants (LITERAL1) | |||
####################################### | |||
TCP_DISCONNECTED LITERAL1 | |||
MQTT_UNACCEPTABLE_PROTOCOL_VERSION LITERAL1 | |||
MQTT_IDENTIFIER_REJECTED LITERAL1 | |||
MQTT_SERVER_UNAVAILABLE LITERAL1 | |||
MQTT_MALFORMED_CREDENTIALS LITERAL1 | |||
MQTT_NOT_AUTHORIZED LITERAL1 |
@@ -0,0 +1,30 @@ | |||
{ | |||
"name": "AsyncMqttClient", | |||
"keywords": "iot, home, automation, async, mqtt, client, esp8266", | |||
"description": "An Arduino for ESP8266 / ESP32 asynchronous MQTT client implementation", | |||
"authors": | |||
{ | |||
"name": "Marvin ROGER", | |||
"url": "https://www.marvinroger.fr" | |||
}, | |||
"repository": | |||
{ | |||
"type": "git", | |||
"url": "https://github.com/marvinroger/async-mqtt-client.git" | |||
}, | |||
"version": "0.9.0", | |||
"frameworks": "arduino", | |||
"platforms": ["espressif8266", "espressif32"], | |||
"dependencies": [ | |||
{ | |||
"name": "ESPAsyncTCP", | |||
"version": ">=1.2.2", | |||
"platforms": "espressif8266" | |||
}, | |||
{ | |||
"name": "AsyncTCP", | |||
"version": ">=1.1.1", | |||
"platforms": "espressif32" | |||
} | |||
] | |||
} |
@@ -0,0 +1,9 @@ | |||
name=AsyncMqttClient | |||
version=0.9.0 | |||
author=Marvin ROGER | |||
maintainer=Marvin ROGER | |||
sentence=An Arduino for ESP8266 and ESP32 asynchronous MQTT client implementation | |||
paragraph=Like this project? Please star it on GitHub! | |||
category=Communication | |||
url=https://github.com/marvinroger/async-mqtt-client | |||
architectures=esp8266,esp32 |
@@ -0,0 +1,49 @@ | |||
#!/bin/bash | |||
#pip install -U platformio | |||
#platformio update | |||
platformio lib -g install AsyncTCP | |||
platformio lib -g install ESPAsyncTCP | |||
RED='\033[0;31m' | |||
GREEN='\033[0;32m' | |||
YELLOW='\033[0;33m' | |||
NC='\033[0m' | |||
lines=$(find ./examples/ -maxdepth 1 -mindepth 1 -type d) | |||
retval=0 | |||
while read line; do | |||
if [[ "$line" != *ESP8266 && "$line" != *ESP32 ]] | |||
then | |||
echo -e "========================== BUILDING $line ==========================" | |||
echo -e "${YELLOW}SKIPPING${NC}" | |||
continue | |||
fi | |||
echo -e "========================== BUILDING $line ==========================" | |||
if [[ -e "$line/platformio.ini" ]] | |||
then | |||
# skipping | |||
#output=$(platformio ci --lib="." --project-conf="$line/platformio.ini" $line 2>&1) | |||
: | |||
else | |||
if [[ "$line" == *ESP8266 ]] | |||
then | |||
output=$(platformio ci --lib="." --project-conf="scripts/CI/platformio_esp8266.ini" $line 2>&1) | |||
else | |||
output=$(platformio ci --lib="." --project-conf="scripts/CI/platformio_esp32.ini" $line 2>&1) | |||
fi | |||
fi | |||
if [ $? -ne 0 ]; then | |||
echo "$output" | |||
echo -e "Building $line ${RED}FAILED${NC}" | |||
retval=1 | |||
else | |||
echo -e "${GREEN}SUCCESS${NC}" | |||
fi | |||
done <<< "$lines" | |||
# cleanup | |||
platformio lib -g uninstall AsyncTCP | |||
platformio lib -g uninstall ESPAsyncTCP | |||
exit "$retval" |
@@ -0,0 +1,16 @@ | |||
; PlatformIO Project Configuration File | |||
; | |||
; Build options: build flags, source filter | |||
; Upload options: custom upload port, speed and extra flags | |||
; Library options: dependencies, extra library storages | |||
; Advanced options: extra scripting | |||
; | |||
; Please visit documentation for the other options and examples | |||
; https://docs.platformio.org/page/projectconf.html | |||
[env:esp32] | |||
platform = espressif32 | |||
board = esp32dev | |||
framework = arduino | |||
build_flags = | |||
-Wall |
@@ -0,0 +1,16 @@ | |||
; PlatformIO Project Configuration File | |||
; | |||
; Build options: build flags, source filter | |||
; Upload options: custom upload port, speed and extra flags | |||
; Library options: dependencies, extra library storages | |||
; Advanced options: extra scripting | |||
; | |||
; Please visit documentation for the other options and examples | |||
; https://docs.platformio.org/page/projectconf.html | |||
[env:esp8266] | |||
platform = espressif8266 | |||
board = esp01_1m | |||
framework = arduino | |||
build_flags = | |||
-Wall |
@@ -0,0 +1,28 @@ | |||
#!/usr/bin/env python | |||
import argparse | |||
import ssl | |||
import hashlib | |||
parser = argparse.ArgumentParser(description='Compute SSL/TLS fingerprints.') | |||
parser.add_argument('--host', required=True) | |||
parser.add_argument('--port', default=8883) | |||
args = parser.parse_args() | |||
print(args.host) | |||
cert_pem = ssl.get_server_certificate((args.host, args.port)) | |||
cert_der = ssl.PEM_cert_to_DER_cert(cert_pem) | |||
md5 = hashlib.md5(cert_der).hexdigest() | |||
sha1 = hashlib.sha1(cert_der).hexdigest() | |||
sha256 = hashlib.sha256(cert_der).hexdigest() | |||
print("MD5: " + md5) | |||
print("SHA1: " + sha1) | |||
print("SHA256: " + sha256) | |||
print("\nSHA1 as array initializer:") | |||
print("const uint8_t fingerprint[] = {0x" + ", 0x".join([sha1[i:i+2] for i in range(0, len(sha1), 2)]) + "};") | |||
print("\nSHA1 as function call:") | |||
print("mqttClient.addServerFingerprint((const uint8_t[]){0x" + ", 0x".join([sha1[i:i+2] for i in range(0, len(sha1), 2)]) + "});") |
@@ -0,0 +1,755 @@ | |||
#include "AsyncMqttClient.hpp" | |||
AsyncMqttClient::AsyncMqttClient() | |||
: _client() | |||
, _head(nullptr) | |||
, _tail(nullptr) | |||
, _sent(0) | |||
, _state(DISCONNECTED) | |||
, _disconnectReason(AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) | |||
, _lastClientActivity(0) | |||
, _lastServerActivity(0) | |||
, _lastPingRequestTime(0) | |||
, _generatedClientId{0} | |||
, _ip() | |||
, _host(nullptr) | |||
, _useIp(false) | |||
#if ASYNC_TCP_SSL_ENABLED | |||
, _secure(false) | |||
#endif | |||
, _port(0) | |||
, _keepAlive(15) | |||
, _cleanSession(true) | |||
, _clientId(nullptr) | |||
, _username(nullptr) | |||
, _password(nullptr) | |||
, _willTopic(nullptr) | |||
, _willPayload(nullptr) | |||
, _willPayloadLength(0) | |||
, _willQos(0) | |||
, _willRetain(false) | |||
#if ASYNC_TCP_SSL_ENABLED | |||
, _secureServerFingerprints() | |||
#endif | |||
, _onConnectUserCallbacks() | |||
, _onDisconnectUserCallbacks() | |||
, _onSubscribeUserCallbacks() | |||
, _onUnsubscribeUserCallbacks() | |||
, _onMessageUserCallbacks() | |||
, _onPublishUserCallbacks() | |||
, _parsingInformation { .bufferState = AsyncMqttClientInternals::BufferState::NONE } | |||
, _currentParsedPacket(nullptr) | |||
, _remainingLengthBufferPosition(0) | |||
, _remainingLengthBuffer{0} | |||
, _pendingPubRels() { | |||
_client.onConnect([](void* obj, AsyncClient* c) { (static_cast<AsyncMqttClient*>(obj))->_onConnect(); }, this); | |||
_client.onDisconnect([](void* obj, AsyncClient* c) { (static_cast<AsyncMqttClient*>(obj))->_onDisconnect(); }, this); | |||
// _client.onError([](void* obj, AsyncClient* c, int8_t error) { (static_cast<AsyncMqttClient*>(obj))->_onError(error); }, this); | |||
// _client.onTimeout([](void* obj, AsyncClient* c, uint32_t time) { (static_cast<AsyncMqttClient*>(obj))->_onTimeout(); }, this); | |||
_client.onAck([](void* obj, AsyncClient* c, size_t len, uint32_t time) { (static_cast<AsyncMqttClient*>(obj))->_onAck(len); }, this); | |||
_client.onData([](void* obj, AsyncClient* c, void* data, size_t len) { (static_cast<AsyncMqttClient*>(obj))->_onData(static_cast<char*>(data), len); }, this); | |||
_client.onPoll([](void* obj, AsyncClient* c) { (static_cast<AsyncMqttClient*>(obj))->_onPoll(); }, this); | |||
_client.setNoDelay(true); // send small packets immediately (PINGREQ/DISCONN are only 2 bytes) | |||
#ifdef ESP32 | |||
sprintf(_generatedClientId, "esp32-%06llx", ESP.getEfuseMac()); | |||
_xSemaphore = xSemaphoreCreateMutex(); | |||
#elif defined(ESP8266) | |||
sprintf(_generatedClientId, "esp8266-%06x", ESP.getChipId()); | |||
#endif | |||
_clientId = _generatedClientId; | |||
setMaxTopicLength(128); | |||
} | |||
AsyncMqttClient::~AsyncMqttClient() { | |||
delete _currentParsedPacket; | |||
delete[] _parsingInformation.topicBuffer; | |||
_clear(); | |||
_pendingPubRels.clear(); | |||
_pendingPubRels.shrink_to_fit(); | |||
_clearQueue(false); // _clear() doesn't clear session data | |||
#ifdef ESP32 | |||
vSemaphoreDelete(_xSemaphore); | |||
#endif | |||
} | |||
AsyncMqttClient& AsyncMqttClient::setKeepAlive(uint16_t keepAlive) { | |||
_keepAlive = keepAlive; | |||
return *this; | |||
} | |||
AsyncMqttClient& AsyncMqttClient::setClientId(const char* clientId) { | |||
_clientId = clientId; | |||
return *this; | |||
} | |||
AsyncMqttClient& AsyncMqttClient::setCleanSession(bool cleanSession) { | |||
_cleanSession = cleanSession; | |||
return *this; | |||
} | |||
AsyncMqttClient& AsyncMqttClient::setMaxTopicLength(uint16_t maxTopicLength) { | |||
_parsingInformation.maxTopicLength = maxTopicLength; | |||
delete[] _parsingInformation.topicBuffer; | |||
_parsingInformation.topicBuffer = new char[maxTopicLength + 1]; | |||
return *this; | |||
} | |||
AsyncMqttClient& AsyncMqttClient::setCredentials(const char* username, const char* password) { | |||
_username = username; | |||
_password = password; | |||
return *this; | |||
} | |||
AsyncMqttClient& AsyncMqttClient::setWill(const char* topic, uint8_t qos, bool retain, const char* payload, size_t length) { | |||
_willTopic = topic; | |||
_willQos = qos; | |||
_willRetain = retain; | |||
_willPayload = payload; | |||
_willPayloadLength = length; | |||
return *this; | |||
} | |||
AsyncMqttClient& AsyncMqttClient::setServer(IPAddress ip, uint16_t port) { | |||
_useIp = true; | |||
_ip = ip; | |||
_port = port; | |||
return *this; | |||
} | |||
AsyncMqttClient& AsyncMqttClient::setServer(const char* host, uint16_t port) { | |||
_useIp = false; | |||
_host = host; | |||
_port = port; | |||
return *this; | |||
} | |||
#if ASYNC_TCP_SSL_ENABLED | |||
AsyncMqttClient& AsyncMqttClient::setSecure(bool secure) { | |||
_secure = secure; | |||
return *this; | |||
} | |||
AsyncMqttClient& AsyncMqttClient::addServerFingerprint(const uint8_t* fingerprint) { | |||
std::array<uint8_t, SHA1_SIZE> newFingerprint; | |||
memcpy(newFingerprint.data(), fingerprint, SHA1_SIZE); | |||
_secureServerFingerprints.push_back(newFingerprint); | |||
return *this; | |||
} | |||
#endif | |||
AsyncMqttClient& AsyncMqttClient::onConnect(AsyncMqttClientInternals::OnConnectUserCallback callback) { | |||
_onConnectUserCallbacks.push_back(callback); | |||
return *this; | |||
} | |||
AsyncMqttClient& AsyncMqttClient::onDisconnect(AsyncMqttClientInternals::OnDisconnectUserCallback callback) { | |||
_onDisconnectUserCallbacks.push_back(callback); | |||
return *this; | |||
} | |||
AsyncMqttClient& AsyncMqttClient::onSubscribe(AsyncMqttClientInternals::OnSubscribeUserCallback callback) { | |||
_onSubscribeUserCallbacks.push_back(callback); | |||
return *this; | |||
} | |||
AsyncMqttClient& AsyncMqttClient::onUnsubscribe(AsyncMqttClientInternals::OnUnsubscribeUserCallback callback) { | |||
_onUnsubscribeUserCallbacks.push_back(callback); | |||
return *this; | |||
} | |||
AsyncMqttClient& AsyncMqttClient::onMessage(AsyncMqttClientInternals::OnMessageUserCallback callback) { | |||
_onMessageUserCallbacks.push_back(callback); | |||
return *this; | |||
} | |||
AsyncMqttClient& AsyncMqttClient::onPublish(AsyncMqttClientInternals::OnPublishUserCallback callback) { | |||
_onPublishUserCallbacks.push_back(callback); | |||
return *this; | |||
} | |||
void AsyncMqttClient::_freeCurrentParsedPacket() { | |||
delete _currentParsedPacket; | |||
_currentParsedPacket = nullptr; | |||
} | |||
void AsyncMqttClient::_clear() { | |||
_lastPingRequestTime = 0; | |||
_freeCurrentParsedPacket(); | |||
_clearQueue(true); // keep session data for now | |||
_parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::NONE; | |||
_client.setRxTimeout(0); | |||
} | |||
/* TCP */ | |||
void AsyncMqttClient::_onConnect() { | |||
log_i("TCP conn, MQTT CONNECT"); | |||
#if ASYNC_TCP_SSL_ENABLED | |||
if (_secure && _secureServerFingerprints.size() > 0) { | |||
SSL* clientSsl = _client.getSSL(); | |||
bool sslFoundFingerprint = false; | |||
for (std::array<uint8_t, SHA1_SIZE> fingerprint : _secureServerFingerprints) { | |||
if (ssl_match_fingerprint(clientSsl, fingerprint.data()) == SSL_OK) { | |||
sslFoundFingerprint = true; | |||
break; | |||
} | |||
} | |||
if (!sslFoundFingerprint) { | |||
_disconnectReason = AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT; | |||
_client.close(true); | |||
return; | |||
} | |||
} | |||
#endif | |||
AsyncMqttClientInternals::OutPacket* msg = | |||
new AsyncMqttClientInternals::ConnectOutPacket(_cleanSession, | |||
_username, | |||
_password, | |||
_willTopic, | |||
_willRetain, | |||
_willQos, | |||
_willPayload, | |||
_willPayloadLength, | |||
_keepAlive, | |||
_clientId); | |||
_addFront(msg); | |||
_handleQueue(); | |||
} | |||
void AsyncMqttClient::_onDisconnect() { | |||
log_i("TCP disconn"); | |||
_state = DISCONNECTED; | |||
_clear(); | |||
for (auto callback : _onDisconnectUserCallbacks) callback(_disconnectReason); | |||
} | |||
/* | |||
void AsyncMqttClient::_onError(int8_t error) { | |||
(void)error; | |||
// _onDisconnect called anyway | |||
} | |||
void AsyncMqttClient::_onTimeout() { | |||
// disconnection will be handled by ping/pong management | |||
} | |||
*/ | |||
void AsyncMqttClient::_onAck(size_t len) { | |||
log_i("ack %u", len); | |||
_handleQueue(); | |||
} | |||
void AsyncMqttClient::_onData(char* data, size_t len) { | |||
log_i("data rcv (%u)", len); | |||
size_t currentBytePosition = 0; | |||
char currentByte; | |||
_lastServerActivity = millis(); | |||
do { | |||
switch (_parsingInformation.bufferState) { | |||
case AsyncMqttClientInternals::BufferState::NONE: | |||
currentByte = data[currentBytePosition++]; | |||
_parsingInformation.packetType = currentByte >> 4; | |||
_parsingInformation.packetFlags = (currentByte << 4) >> 4; | |||
_parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::REMAINING_LENGTH; | |||
switch (_parsingInformation.packetType) { | |||
case AsyncMqttClientInternals::PacketType.CONNACK: | |||
log_i("rcv CONNACK"); | |||
_currentParsedPacket = new AsyncMqttClientInternals::ConnAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onConnAck, this, std::placeholders::_1, std::placeholders::_2)); | |||
_client.setRxTimeout(0); | |||
break; | |||
case AsyncMqttClientInternals::PacketType.PINGRESP: | |||
log_i("rcv PINGRESP"); | |||
_currentParsedPacket = new AsyncMqttClientInternals::PingRespPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPingResp, this)); | |||
break; | |||
case AsyncMqttClientInternals::PacketType.SUBACK: | |||
log_i("rcv SUBACK"); | |||
_currentParsedPacket = new AsyncMqttClientInternals::SubAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onSubAck, this, std::placeholders::_1, std::placeholders::_2)); | |||
break; | |||
case AsyncMqttClientInternals::PacketType.UNSUBACK: | |||
log_i("rcv UNSUBACK"); | |||
_currentParsedPacket = new AsyncMqttClientInternals::UnsubAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onUnsubAck, this, std::placeholders::_1)); | |||
break; | |||
case AsyncMqttClientInternals::PacketType.PUBLISH: | |||
log_i("rcv PUBLISH"); | |||
_currentParsedPacket = new AsyncMqttClientInternals::PublishPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6, std::placeholders::_7, std::placeholders::_8, std::placeholders::_9), std::bind(&AsyncMqttClient::_onPublish, this, std::placeholders::_1, std::placeholders::_2)); | |||
break; | |||
case AsyncMqttClientInternals::PacketType.PUBREL: | |||
log_i("rcv PUBREL"); | |||
_currentParsedPacket = new AsyncMqttClientInternals::PubRelPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubRel, this, std::placeholders::_1)); | |||
break; | |||
case AsyncMqttClientInternals::PacketType.PUBACK: | |||
log_i("rcv PUBACK"); | |||
_currentParsedPacket = new AsyncMqttClientInternals::PubAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubAck, this, std::placeholders::_1)); | |||
break; | |||
case AsyncMqttClientInternals::PacketType.PUBREC: | |||
log_i("rcv PUBREC"); | |||
_currentParsedPacket = new AsyncMqttClientInternals::PubRecPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubRec, this, std::placeholders::_1)); | |||
break; | |||
case AsyncMqttClientInternals::PacketType.PUBCOMP: | |||
log_i("rcv PUBCOMP"); | |||
_currentParsedPacket = new AsyncMqttClientInternals::PubCompPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubComp, this, std::placeholders::_1)); | |||
break; | |||
default: | |||
log_i("rcv PROTOCOL VIOLATION"); | |||
disconnect(true); | |||
break; | |||
} | |||
break; | |||
case AsyncMqttClientInternals::BufferState::REMAINING_LENGTH: | |||
currentByte = data[currentBytePosition++]; | |||
_remainingLengthBuffer[_remainingLengthBufferPosition++] = currentByte; | |||
if (currentByte >> 7 == 0) { | |||
_parsingInformation.remainingLength = AsyncMqttClientInternals::Helpers::decodeRemainingLength(_remainingLengthBuffer); | |||
_remainingLengthBufferPosition = 0; | |||
if (_parsingInformation.remainingLength > 0) { | |||
_parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::VARIABLE_HEADER; | |||
} else { | |||
// PINGRESP is a special case where it has no variable header, so the packet ends right here | |||
_parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::NONE; | |||
_onPingResp(); | |||
} | |||
} | |||
break; | |||
case AsyncMqttClientInternals::BufferState::VARIABLE_HEADER: | |||
_currentParsedPacket->parseVariableHeader(data, len, ¤tBytePosition); | |||
break; | |||
case AsyncMqttClientInternals::BufferState::PAYLOAD: | |||
_currentParsedPacket->parsePayload(data, len, ¤tBytePosition); | |||
break; | |||
default: | |||
currentBytePosition = len; | |||
} | |||
} while (currentBytePosition != len); | |||
} | |||
void AsyncMqttClient::_onPoll() { | |||
// if there is too much time the client has sent a ping request without a response, disconnect client to avoid half open connections | |||
if (_lastPingRequestTime != 0 && (millis() - _lastPingRequestTime) >= (_keepAlive * 1000 * 2)) { | |||
log_w("PING t/o, disconnecting"); | |||
disconnect(true); | |||
return; | |||
} | |||
// send ping to ensure the server will receive at least one message inside keepalive window | |||
if (_state == CONNECTED && _lastPingRequestTime == 0 && (millis() - _lastClientActivity) >= (_keepAlive * 1000 * 0.7)) { | |||
_sendPing(); | |||
// send ping to verify if the server is still there (ensure this is not a half connection) | |||
} else if (_state == CONNECTED && _lastPingRequestTime == 0 && (millis() - _lastServerActivity) >= (_keepAlive * 1000 * 0.7)) { | |||
_sendPing(); | |||
} | |||
_handleQueue(); | |||
} | |||
/* QUEUE */ | |||
void AsyncMqttClient::_insert(AsyncMqttClientInternals::OutPacket* packet) { | |||
// We only use this for QoS2 PUBREL so there must be a PUBLISH packet present. | |||
// The queue therefore cannot be empty and _head points to this PUBLISH packet. | |||
SEMAPHORE_TAKE(); | |||
log_i("new insert #%u", packet->packetType()); | |||
packet->next = _head->next; | |||
_head->next = packet; | |||
if (_head == _tail) { // PUB packet is the only one in the queue | |||
_tail = packet; | |||
} | |||
SEMAPHORE_GIVE(); | |||
_handleQueue(); | |||
} | |||
void AsyncMqttClient::_addFront(AsyncMqttClientInternals::OutPacket* packet) { | |||
// This is only used for the CONNECT packet, to be able to establish a connection | |||
// before anything else. The queue can be empty or has packets from the continued session. | |||
// In both cases, _head should always point to the CONNECT packet afterwards. | |||
SEMAPHORE_TAKE(); | |||
log_i("new front #%u", packet->packetType()); | |||
if (_head == nullptr) { | |||
_tail = packet; | |||
} else { | |||
packet->next = _head; | |||
} | |||
_head = packet; | |||
SEMAPHORE_GIVE(); | |||
_handleQueue(); | |||
} | |||
void AsyncMqttClient::_addBack(AsyncMqttClientInternals::OutPacket* packet) { | |||
SEMAPHORE_TAKE(); | |||
log_i("new back #%u", packet->packetType()); | |||
if (!_tail) { | |||
_head = packet; | |||
} else { | |||
_tail->next = packet; | |||
} | |||
_tail = packet; | |||
_tail->next = nullptr; | |||
SEMAPHORE_GIVE(); | |||
_handleQueue(); | |||
} | |||
void AsyncMqttClient::_handleQueue() { | |||
SEMAPHORE_TAKE(); | |||
// On ESP32, onDisconnect is called within the close()-call. So we need to make sure we don't lock | |||
bool disconnect = false; | |||
while (_head && _client.space() > 10) { // safe but arbitrary value, send at least 10 bytes | |||
// 1. try to send | |||
if (_head->size() > _sent) { | |||
// On SSL the TCP library returns the total amount of bytes, not just the unencrypted payload length. | |||
// So we calculate the amount to be written ourselves. | |||
size_t willSend = std::min(_head->size() - _sent, _client.space()); | |||
size_t realSent = _client.add(reinterpret_cast<const char*>(_head->data(_sent)), willSend, ASYNC_WRITE_FLAG_COPY); // flag is set by LWIP anyway, added for clarity | |||
_sent += willSend; | |||
(void)realSent; | |||
_client.send(); | |||
_lastClientActivity = millis(); | |||
_lastPingRequestTime = 0; | |||
#if ASYNC_TCP_SSL_ENABLED | |||
log_i("snd #%u: (tls: %u) %u/%u", _head->packetType(), realSent, _sent, _head->size()); | |||
#else | |||
log_i("snd #%u: %u/%u", _head->packetType(), _sent, _head->size()); | |||
#endif | |||
if (_head->packetType() == AsyncMqttClientInternals::PacketType.DISCONNECT) { | |||
disconnect = true; | |||
} | |||
} | |||
// 2. stop processing when we have to wait for an MQTT acknowledgment | |||
if (_head->size() == _sent) { | |||
if (_head->released()) { | |||
log_i("p #%d rel", _head->packetType()); | |||
AsyncMqttClientInternals::OutPacket* tmp = _head; | |||
_head = _head->next; | |||
if (!_head) _tail = nullptr; | |||
delete tmp; | |||
_sent = 0; | |||
} else { | |||
break; // sending is complete however send next only after mqtt confirmation | |||
} | |||
} | |||
} | |||
SEMAPHORE_GIVE(); | |||
if (disconnect) { | |||
log_i("snd DISCONN, disconnecting"); | |||
_client.close(); | |||
} | |||
} | |||
void AsyncMqttClient::_clearQueue(bool keepSessionData) { | |||
SEMAPHORE_TAKE(); | |||
AsyncMqttClientInternals::OutPacket* packet = _head; | |||
_head = nullptr; | |||
_tail = nullptr; | |||
while (packet) { | |||
/* MQTT spec 3.1.2.4 Clean Session: | |||
* - QoS 1 and QoS 2 messages which have been sent to the Server, but have not been completely acknowledged. | |||
* - QoS 2 messages which have been received from the Server, but have not been completely acknowledged. | |||
* + (unsent PUB messages with QoS > 0) | |||
* | |||
* To be kept: | |||
* - possibly first message (sent to server but not acked) | |||
* - PUBREC messages (QoS 2 PUB received but not acked) | |||
* - PUBCOMP messages (QoS 2 PUBREL received but not acked) | |||
*/ | |||
if (keepSessionData) { | |||
if (packet->qos() > 0 && packet->size() <= _sent) { // check for qos includes check for PUB-packet type | |||
reinterpret_cast<AsyncMqttClientInternals::PublishOutPacket*>(packet)->setDup(); | |||
AsyncMqttClientInternals::OutPacket* next = packet->next; | |||
log_i("keep #%u", packet->packetType()); | |||
SEMAPHORE_GIVE(); | |||
_addBack(packet); | |||
SEMAPHORE_TAKE(); | |||
packet = next; | |||
} else if (packet->qos() > 0 || | |||
packet->packetType() == AsyncMqttClientInternals::PacketType.PUBREC || | |||
packet->packetType() == AsyncMqttClientInternals::PacketType.PUBCOMP) { | |||
AsyncMqttClientInternals::OutPacket* next = packet->next; | |||
log_i("keep #%u", packet->packetType()); | |||
SEMAPHORE_GIVE(); | |||
_addBack(packet); | |||
SEMAPHORE_TAKE(); | |||
packet = next; | |||
} else { | |||
AsyncMqttClientInternals::OutPacket* next = packet->next; | |||
delete packet; | |||
packet = next; | |||
} | |||
/* Delete everything when not keeping session data | |||
*/ | |||
} else { | |||
AsyncMqttClientInternals::OutPacket* next = packet->next; | |||
delete packet; | |||
packet = next; | |||
} | |||
} | |||
_sent = 0; | |||
SEMAPHORE_GIVE(); | |||
} | |||
/* MQTT */ | |||
void AsyncMqttClient::_onPingResp() { | |||
log_i("PINGRESP"); | |||
_freeCurrentParsedPacket(); | |||
_lastPingRequestTime = 0; | |||
} | |||
void AsyncMqttClient::_onConnAck(bool sessionPresent, uint8_t connectReturnCode) { | |||
log_i("CONNACK"); | |||
_freeCurrentParsedPacket(); | |||
if (!sessionPresent) { | |||
_pendingPubRels.clear(); | |||
_pendingPubRels.shrink_to_fit(); | |||
_clearQueue(false); // remove session data | |||
} | |||
if (connectReturnCode == 0) { | |||
_state = CONNECTED; | |||
for (auto callback : _onConnectUserCallbacks) callback(sessionPresent); | |||
} else { | |||
// Callbacks are handled by the onDisconnect function which is called from the AsyncTcp lib | |||
_disconnectReason = static_cast<AsyncMqttClientDisconnectReason>(connectReturnCode); | |||
return; | |||
} | |||
_handleQueue(); // send any remaining data from continued session | |||
} | |||
void AsyncMqttClient::_onSubAck(uint16_t packetId, char status) { | |||
log_i("SUBACK"); | |||
_freeCurrentParsedPacket(); | |||
SEMAPHORE_TAKE(); | |||
if (_head && _head->packetId() == packetId) { | |||
_head->release(); | |||
log_i("SUB released"); | |||
} | |||
SEMAPHORE_GIVE(); | |||
for (auto callback : _onSubscribeUserCallbacks) callback(packetId, status); | |||
_handleQueue(); // subscribe confirmed, ready to send next queued item | |||
} | |||
void AsyncMqttClient::_onUnsubAck(uint16_t packetId) { | |||
log_i("UNSUBACK"); | |||
_freeCurrentParsedPacket(); | |||
SEMAPHORE_TAKE(); | |||
if (_head && _head->packetId() == packetId) { | |||
_head->release(); | |||
log_i("UNSUB released"); | |||
} | |||
SEMAPHORE_GIVE(); | |||
for (auto callback : _onUnsubscribeUserCallbacks) callback(packetId); | |||
_handleQueue(); // unsubscribe confirmed, ready to send next queued item | |||
} | |||
void AsyncMqttClient::_onMessage(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId) { | |||
bool notifyPublish = true; | |||
if (qos == 2) { | |||
for (AsyncMqttClientInternals::PendingPubRel pendingPubRel : _pendingPubRels) { | |||
if (pendingPubRel.packetId == packetId) { | |||
notifyPublish = false; | |||
break; | |||
} | |||
} | |||
} | |||
if (notifyPublish) { | |||
AsyncMqttClientMessageProperties properties; | |||
properties.qos = qos; | |||
properties.dup = dup; | |||
properties.retain = retain; | |||
for (auto callback : _onMessageUserCallbacks) callback(topic, payload, properties, len, index, total); | |||
} | |||
} | |||
void AsyncMqttClient::_onPublish(uint16_t packetId, uint8_t qos) { | |||
AsyncMqttClientInternals::PendingAck pendingAck; | |||
if (qos == 1) { | |||
pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBACK; | |||
pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBACK_RESERVED; | |||
pendingAck.packetId = packetId; | |||
AsyncMqttClientInternals::OutPacket* msg = new AsyncMqttClientInternals::PubAckOutPacket(pendingAck); | |||
_addBack(msg); | |||
} else if (qos == 2) { | |||
pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBREC; | |||
pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBREC_RESERVED; | |||
pendingAck.packetId = packetId; | |||
AsyncMqttClientInternals::OutPacket* msg = new AsyncMqttClientInternals::PubAckOutPacket(pendingAck); | |||
_addBack(msg); | |||
bool pubRelAwaiting = false; | |||
for (AsyncMqttClientInternals::PendingPubRel pendingPubRel : _pendingPubRels) { | |||
if (pendingPubRel.packetId == packetId) { | |||
pubRelAwaiting = true; | |||
break; | |||
} | |||
} | |||
if (!pubRelAwaiting) { | |||
AsyncMqttClientInternals::PendingPubRel pendingPubRel; | |||
pendingPubRel.packetId = packetId; | |||
_pendingPubRels.push_back(pendingPubRel); | |||
} | |||
} | |||
_freeCurrentParsedPacket(); | |||
} | |||
void AsyncMqttClient::_onPubRel(uint16_t packetId) { | |||
_freeCurrentParsedPacket(); | |||
AsyncMqttClientInternals::PendingAck pendingAck; | |||
pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBCOMP; | |||
pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBCOMP_RESERVED; | |||
pendingAck.packetId = packetId; | |||
if (_head && _head->packetId() == packetId) { | |||
AsyncMqttClientInternals::OutPacket* msg = new AsyncMqttClientInternals::PubAckOutPacket(pendingAck); | |||
_head->release(); | |||
_insert(msg); | |||
log_i("PUBREC released"); | |||
} | |||
for (size_t i = 0; i < _pendingPubRels.size(); i++) { | |||
if (_pendingPubRels[i].packetId == packetId) { | |||
_pendingPubRels.erase(_pendingPubRels.begin() + i); | |||
_pendingPubRels.shrink_to_fit(); | |||
} | |||
} | |||
} | |||
void AsyncMqttClient::_onPubAck(uint16_t packetId) { | |||
_freeCurrentParsedPacket(); | |||
if (_head && _head->packetId() == packetId) { | |||
_head->release(); | |||
log_i("PUB released"); | |||
} | |||
for (auto callback : _onPublishUserCallbacks) callback(packetId); | |||
} | |||
void AsyncMqttClient::_onPubRec(uint16_t packetId) { | |||
_freeCurrentParsedPacket(); | |||
// We will only be sending 1 QoS>0 PUB message at a time (to honor message | |||
// ordering). So no need to store ACKS in a separate container as it will | |||
// be stored in the outgoing queue until a PUBCOMP comes in. | |||
AsyncMqttClientInternals::PendingAck pendingAck; | |||
pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBREL; | |||
pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBREL_RESERVED; | |||
pendingAck.packetId = packetId; | |||
log_i("snd PUBREL"); | |||
AsyncMqttClientInternals::OutPacket* msg = new AsyncMqttClientInternals::PubAckOutPacket(pendingAck); | |||
if (_head && _head->packetId() == packetId) { | |||
_head->release(); | |||
log_i("PUB released"); | |||
} | |||
_insert(msg); | |||
} | |||
void AsyncMqttClient::_onPubComp(uint16_t packetId) { | |||
_freeCurrentParsedPacket(); | |||
// _head points to the PUBREL package | |||
if (_head && _head->packetId() == packetId) { | |||
_head->release(); | |||
log_i("PUBREL released"); | |||
} | |||
for (auto callback : _onPublishUserCallbacks) callback(packetId); | |||
} | |||
void AsyncMqttClient::_sendPing() { | |||
log_i("PING"); | |||
_lastPingRequestTime = millis(); | |||
AsyncMqttClientInternals::OutPacket* msg = new AsyncMqttClientInternals::PingReqOutPacket; | |||
_addBack(msg); | |||
} | |||
bool AsyncMqttClient::connected() const { | |||
return _state == CONNECTED; | |||
} | |||
void AsyncMqttClient::connect() { | |||
if (_state != DISCONNECTED) return; | |||
log_i("CONNECTING"); | |||
_state = CONNECTING; | |||
_disconnectReason = AsyncMqttClientDisconnectReason::TCP_DISCONNECTED; // reset any previous | |||
_client.setRxTimeout(_keepAlive); | |||
#if ASYNC_TCP_SSL_ENABLED | |||
if (_useIp) { | |||
_client.connect(_ip, _port, _secure); | |||
} else { | |||
_client.connect(_host, _port, _secure); | |||
} | |||
#else | |||
if (_useIp) { | |||
_client.connect(_ip, _port); | |||
} else { | |||
_client.connect(_host, _port); | |||
} | |||
#endif | |||
} | |||
void AsyncMqttClient::disconnect(bool force) { | |||
if (_state == DISCONNECTED) return; | |||
log_i("DISCONNECT (f:%d)", force); | |||
if (force) { | |||
_state = DISCONNECTED; | |||
_client.close(true); | |||
} else if (_state != DISCONNECTING) { | |||
_state = DISCONNECTING; | |||
AsyncMqttClientInternals::OutPacket* msg = new AsyncMqttClientInternals::DisconnOutPacket; | |||
_addBack(msg); | |||
} | |||
} | |||
uint16_t AsyncMqttClient::subscribe(const char* topic, uint8_t qos) { | |||
if (_state != CONNECTED) return 0; | |||
log_i("SUBSCRIBE"); | |||
AsyncMqttClientInternals::OutPacket* msg = new AsyncMqttClientInternals::SubscribeOutPacket(topic, qos); | |||
_addBack(msg); | |||
return msg->packetId(); | |||
} | |||
uint16_t AsyncMqttClient::unsubscribe(const char* topic) { | |||
if (_state != CONNECTED) return 0; | |||
log_i("UNSUBSCRIBE"); | |||
AsyncMqttClientInternals::OutPacket* msg = new AsyncMqttClientInternals::UnsubscribeOutPacket(topic); | |||
_addBack(msg); | |||
return msg->packetId(); | |||
} | |||
uint16_t AsyncMqttClient::publish(const char* topic, uint8_t qos, bool retain, const char* payload, size_t length, bool dup, uint16_t message_id) { | |||
if (_state != CONNECTED || GET_FREE_MEMORY() < MQTT_MIN_FREE_MEMORY) return 0; | |||
log_i("PUBLISH"); | |||
AsyncMqttClientInternals::OutPacket* msg = new AsyncMqttClientInternals::PublishOutPacket(topic, qos, retain, payload, length); | |||
_addBack(msg); | |||
return msg->packetId(); | |||
} | |||
bool AsyncMqttClient::clearQueue() { | |||
if (_state != DISCONNECTED) return false; | |||
_clearQueue(false); | |||
return true; | |||
} | |||
const char* AsyncMqttClient::getClientId() const { | |||
return _clientId; | |||
} |
@@ -0,0 +1,6 @@ | |||
#ifndef SRC_ASYNCMQTTCLIENT_H_ | |||
#define SRC_ASYNCMQTTCLIENT_H_ | |||
#include "AsyncMqttClient.hpp" | |||
#endif // SRC_ASYNCMQTTCLIENT_H_ |
@@ -0,0 +1,179 @@ | |||
#pragma once | |||
#include <functional> | |||
#include <vector> | |||
#include "Arduino.h" | |||
#ifndef MQTT_MIN_FREE_MEMORY | |||
#define MQTT_MIN_FREE_MEMORY 4096 | |||
#endif | |||
#ifdef ESP32 | |||
#include <AsyncTCP.h> | |||
#include <freertos/semphr.h> | |||
#elif defined(ESP8266) | |||
#include <ESPAsyncTCP.h> | |||
#else | |||
#error Platform not supported | |||
#endif | |||
#if ASYNC_TCP_SSL_ENABLED | |||
#include <tcp_axtls.h> | |||
#define SHA1_SIZE 20 | |||
#endif | |||
#include "AsyncMqttClient/Flags.hpp" | |||
#include "AsyncMqttClient/ParsingInformation.hpp" | |||
#include "AsyncMqttClient/MessageProperties.hpp" | |||
#include "AsyncMqttClient/Helpers.hpp" | |||
#include "AsyncMqttClient/Callbacks.hpp" | |||
#include "AsyncMqttClient/DisconnectReasons.hpp" | |||
#include "AsyncMqttClient/Storage.hpp" | |||
#include "AsyncMqttClient/Packets/Packet.hpp" | |||
#include "AsyncMqttClient/Packets/ConnAckPacket.hpp" | |||
#include "AsyncMqttClient/Packets/PingRespPacket.hpp" | |||
#include "AsyncMqttClient/Packets/SubAckPacket.hpp" | |||
#include "AsyncMqttClient/Packets/UnsubAckPacket.hpp" | |||
#include "AsyncMqttClient/Packets/PublishPacket.hpp" | |||
#include "AsyncMqttClient/Packets/PubRelPacket.hpp" | |||
#include "AsyncMqttClient/Packets/PubAckPacket.hpp" | |||
#include "AsyncMqttClient/Packets/PubRecPacket.hpp" | |||
#include "AsyncMqttClient/Packets/PubCompPacket.hpp" | |||
#include "AsyncMqttClient/Packets/Out/Connect.hpp" | |||
#include "AsyncMqttClient/Packets/Out/PingReq.hpp" | |||
#include "AsyncMqttClient/Packets/Out/PubAck.hpp" | |||
#include "AsyncMqttClient/Packets/Out/Disconn.hpp" | |||
#include "AsyncMqttClient/Packets/Out/Subscribe.hpp" | |||
#include "AsyncMqttClient/Packets/Out/Unsubscribe.hpp" | |||
#include "AsyncMqttClient/Packets/Out/Publish.hpp" | |||
class AsyncMqttClient { | |||
public: | |||
AsyncMqttClient(); | |||
~AsyncMqttClient(); | |||
AsyncMqttClient& setKeepAlive(uint16_t keepAlive); | |||
AsyncMqttClient& setClientId(const char* clientId); | |||
AsyncMqttClient& setCleanSession(bool cleanSession); | |||
AsyncMqttClient& setMaxTopicLength(uint16_t maxTopicLength); | |||
AsyncMqttClient& setCredentials(const char* username, const char* password = nullptr); | |||
AsyncMqttClient& setWill(const char* topic, uint8_t qos, bool retain, const char* payload = nullptr, size_t length = 0); | |||
AsyncMqttClient& setServer(IPAddress ip, uint16_t port); | |||
AsyncMqttClient& setServer(const char* host, uint16_t port); | |||
#if ASYNC_TCP_SSL_ENABLED | |||
AsyncMqttClient& setSecure(bool secure); | |||
AsyncMqttClient& addServerFingerprint(const uint8_t* fingerprint); | |||
#endif | |||
AsyncMqttClient& onConnect(AsyncMqttClientInternals::OnConnectUserCallback callback); | |||
AsyncMqttClient& onDisconnect(AsyncMqttClientInternals::OnDisconnectUserCallback callback); | |||
AsyncMqttClient& onSubscribe(AsyncMqttClientInternals::OnSubscribeUserCallback callback); | |||
AsyncMqttClient& onUnsubscribe(AsyncMqttClientInternals::OnUnsubscribeUserCallback callback); | |||
AsyncMqttClient& onMessage(AsyncMqttClientInternals::OnMessageUserCallback callback); | |||
AsyncMqttClient& onPublish(AsyncMqttClientInternals::OnPublishUserCallback callback); | |||
bool connected() const; | |||
void connect(); | |||
void disconnect(bool force = false); | |||
uint16_t subscribe(const char* topic, uint8_t qos); | |||
uint16_t unsubscribe(const char* topic); | |||
uint16_t publish(const char* topic, uint8_t qos, bool retain, const char* payload = nullptr, size_t length = 0, bool dup = false, uint16_t message_id = 0); | |||
bool clearQueue(); // Not MQTT compliant! | |||
const char* getClientId() const; | |||
private: | |||
AsyncClient _client; | |||
AsyncMqttClientInternals::OutPacket* _head; | |||
AsyncMqttClientInternals::OutPacket* _tail; | |||
size_t _sent; | |||
enum { | |||
CONNECTING, | |||
CONNECTED, | |||
DISCONNECTING, | |||
DISCONNECTED | |||
} _state; | |||
AsyncMqttClientDisconnectReason _disconnectReason; | |||
uint32_t _lastClientActivity; | |||
uint32_t _lastServerActivity; | |||
uint32_t _lastPingRequestTime; | |||
char _generatedClientId[18 + 1]; // esp8266-abc123 and esp32-abcdef123456 | |||
IPAddress _ip; | |||
const char* _host; | |||
bool _useIp; | |||
#if ASYNC_TCP_SSL_ENABLED | |||
bool _secure; | |||
#endif | |||
uint16_t _port; | |||
uint16_t _keepAlive; | |||
bool _cleanSession; | |||
const char* _clientId; | |||
const char* _username; | |||
const char* _password; | |||
const char* _willTopic; | |||
const char* _willPayload; | |||
uint16_t _willPayloadLength; | |||
uint8_t _willQos; | |||
bool _willRetain; | |||
#if ASYNC_TCP_SSL_ENABLED | |||
std::vector<std::array<uint8_t, SHA1_SIZE>> _secureServerFingerprints; | |||
#endif | |||
std::vector<AsyncMqttClientInternals::OnConnectUserCallback> _onConnectUserCallbacks; | |||
std::vector<AsyncMqttClientInternals::OnDisconnectUserCallback> _onDisconnectUserCallbacks; | |||
std::vector<AsyncMqttClientInternals::OnSubscribeUserCallback> _onSubscribeUserCallbacks; | |||
std::vector<AsyncMqttClientInternals::OnUnsubscribeUserCallback> _onUnsubscribeUserCallbacks; | |||
std::vector<AsyncMqttClientInternals::OnMessageUserCallback> _onMessageUserCallbacks; | |||
std::vector<AsyncMqttClientInternals::OnPublishUserCallback> _onPublishUserCallbacks; | |||
AsyncMqttClientInternals::ParsingInformation _parsingInformation; | |||
AsyncMqttClientInternals::Packet* _currentParsedPacket; | |||
uint8_t _remainingLengthBufferPosition; | |||
char _remainingLengthBuffer[4]; | |||
std::vector<AsyncMqttClientInternals::PendingPubRel> _pendingPubRels; | |||
#if defined(ESP32) | |||
SemaphoreHandle_t _xSemaphore = nullptr; | |||
#elif defined(ESP8266) | |||
bool _xSemaphore = false; | |||
#endif | |||
void _clear(); | |||
void _freeCurrentParsedPacket(); | |||
// TCP | |||
void _onConnect(); | |||
void _onDisconnect(); | |||
// void _onError(int8_t error); | |||
// void _onTimeout(); | |||
void _onAck(size_t len); | |||
void _onData(char* data, size_t len); | |||
void _onPoll(); | |||
// QUEUE | |||
void _insert(AsyncMqttClientInternals::OutPacket* packet); // for PUBREL | |||
void _addFront(AsyncMqttClientInternals::OutPacket* packet); // for CONNECT | |||
void _addBack(AsyncMqttClientInternals::OutPacket* packet); // all the rest | |||
void _handleQueue(); | |||
void _clearQueue(bool keepSessionData); | |||
// MQTT | |||
void _onPingResp(); | |||
void _onConnAck(bool sessionPresent, uint8_t connectReturnCode); | |||
void _onSubAck(uint16_t packetId, char status); | |||
void _onUnsubAck(uint16_t packetId); | |||
void _onMessage(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId); | |||
void _onPublish(uint16_t packetId, uint8_t qos); | |||
void _onPubRel(uint16_t packetId); | |||
void _onPubAck(uint16_t packetId); | |||
void _onPubRec(uint16_t packetId); | |||
void _onPubComp(uint16_t packetId); | |||
void _sendPing(); | |||
}; |
@@ -0,0 +1,30 @@ | |||
#pragma once | |||
#include <functional> | |||
#include "DisconnectReasons.hpp" | |||
#include "MessageProperties.hpp" | |||
#include "Errors.hpp" | |||
namespace AsyncMqttClientInternals { | |||
// user callbacks | |||
typedef std::function<void(bool sessionPresent)> OnConnectUserCallback; | |||
typedef std::function<void(AsyncMqttClientDisconnectReason reason)> OnDisconnectUserCallback; | |||
typedef std::function<void(uint16_t packetId, uint8_t qos)> OnSubscribeUserCallback; | |||
typedef std::function<void(uint16_t packetId)> OnUnsubscribeUserCallback; | |||
typedef std::function<void(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)> OnMessageUserCallback; | |||
typedef std::function<void(uint16_t packetId)> OnPublishUserCallback; | |||
typedef std::function<void(uint16_t packetId, AsyncMqttClientError error)> OnErrorUserCallback; | |||
// internal callbacks | |||
typedef std::function<void(bool sessionPresent, uint8_t connectReturnCode)> OnConnAckInternalCallback; | |||
typedef std::function<void()> OnPingRespInternalCallback; | |||
typedef std::function<void(uint16_t packetId, char status)> OnSubAckInternalCallback; | |||
typedef std::function<void(uint16_t packetId)> OnUnsubAckInternalCallback; | |||
typedef std::function<void(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId)> OnMessageInternalCallback; | |||
typedef std::function<void(uint16_t packetId, uint8_t qos)> OnPublishInternalCallback; | |||
typedef std::function<void(uint16_t packetId)> OnPubRelInternalCallback; | |||
typedef std::function<void(uint16_t packetId)> OnPubAckInternalCallback; | |||
typedef std::function<void(uint16_t packetId)> OnPubRecInternalCallback; | |||
typedef std::function<void(uint16_t packetId)> OnPubCompInternalCallback; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,15 @@ | |||
#pragma once | |||
enum class AsyncMqttClientDisconnectReason : uint8_t { | |||
TCP_DISCONNECTED = 0, | |||
MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1, | |||
MQTT_IDENTIFIER_REJECTED = 2, | |||
MQTT_SERVER_UNAVAILABLE = 3, | |||
MQTT_MALFORMED_CREDENTIALS = 4, | |||
MQTT_NOT_AUTHORIZED = 5, | |||
ESP8266_NOT_ENOUGH_SPACE = 6, | |||
TLS_BAD_FINGERPRINT = 7 | |||
}; |
@@ -0,0 +1,6 @@ | |||
#pragma once | |||
enum class AsyncMqttClientError : uint8_t { | |||
MAX_RETRIES = 0, | |||
OUT_OF_MEMORY = 1 | |||
}; |
@@ -0,0 +1,57 @@ | |||
#pragma once | |||
namespace AsyncMqttClientInternals { | |||
constexpr struct { | |||
const uint8_t RESERVED = 0; | |||
const uint8_t CONNECT = 1; | |||
const uint8_t CONNACK = 2; | |||
const uint8_t PUBLISH = 3; | |||
const uint8_t PUBACK = 4; | |||
const uint8_t PUBREC = 5; | |||
const uint8_t PUBREL = 6; | |||
const uint8_t PUBCOMP = 7; | |||
const uint8_t SUBSCRIBE = 8; | |||
const uint8_t SUBACK = 9; | |||
const uint8_t UNSUBSCRIBE = 10; | |||
const uint8_t UNSUBACK = 11; | |||
const uint8_t PINGREQ = 12; | |||
const uint8_t PINGRESP = 13; | |||
const uint8_t DISCONNECT = 14; | |||
const uint8_t RESERVED2 = 1; | |||
} PacketType; | |||
constexpr struct { | |||
const uint8_t CONNECT_RESERVED = 0x00; | |||
const uint8_t CONNACK_RESERVED = 0x00; | |||
const uint8_t PUBLISH_DUP = 0x08; | |||
const uint8_t PUBLISH_QOS0 = 0x00; | |||
const uint8_t PUBLISH_QOS1 = 0x02; | |||
const uint8_t PUBLISH_QOS2 = 0x04; | |||
const uint8_t PUBLISH_QOSRESERVED = 0x06; | |||
const uint8_t PUBLISH_RETAIN = 0x01; | |||
const uint8_t PUBACK_RESERVED = 0x00; | |||
const uint8_t PUBREC_RESERVED = 0x00; | |||
const uint8_t PUBREL_RESERVED = 0x02; | |||
const uint8_t PUBCOMP_RESERVED = 0x00; | |||
const uint8_t SUBSCRIBE_RESERVED = 0x02; | |||
const uint8_t SUBACK_RESERVED = 0x00; | |||
const uint8_t UNSUBSCRIBE_RESERVED = 0x02; | |||
const uint8_t UNSUBACK_RESERVED = 0x00; | |||
const uint8_t PINGREQ_RESERVED = 0x00; | |||
const uint8_t PINGRESP_RESERVED = 0x00; | |||
const uint8_t DISCONNECT_RESERVED = 0x00; | |||
const uint8_t RESERVED2_RESERVED = 0x00; | |||
} HeaderFlag; | |||
constexpr struct { | |||
const uint8_t USERNAME = 0x80; | |||
const uint8_t PASSWORD = 0x40; | |||
const uint8_t WILL_RETAIN = 0x20; | |||
const uint8_t WILL_QOS0 = 0x00; | |||
const uint8_t WILL_QOS1 = 0x08; | |||
const uint8_t WILL_QOS2 = 0x10; | |||
const uint8_t WILL = 0x04; | |||
const uint8_t CLEAN_SESSION = 0x02; | |||
const uint8_t RESERVED = 0x00; | |||
} ConnectFlag; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,61 @@ | |||
#pragma once | |||
namespace AsyncMqttClientInternals { | |||
class Helpers { | |||
public: | |||
static uint32_t decodeRemainingLength(char* bytes) { | |||
uint32_t multiplier = 1; | |||
uint32_t value = 0; | |||
uint8_t currentByte = 0; | |||
uint8_t encodedByte; | |||
do { | |||
encodedByte = bytes[currentByte++]; | |||
value += (encodedByte & 127) * multiplier; | |||
multiplier *= 128; | |||
} while ((encodedByte & 128) != 0); | |||
return value; | |||
} | |||
static uint8_t encodeRemainingLength(uint32_t remainingLength, char* destination) { | |||
uint8_t currentByte = 0; | |||
uint8_t bytesNeeded = 0; | |||
do { | |||
uint8_t encodedByte = remainingLength % 128; | |||
remainingLength /= 128; | |||
if (remainingLength > 0) { | |||
encodedByte = encodedByte | 128; | |||
} | |||
destination[currentByte++] = encodedByte; | |||
bytesNeeded++; | |||
} while (remainingLength > 0); | |||
return bytesNeeded; | |||
} | |||
}; | |||
#if defined(ARDUINO_ARCH_ESP32) | |||
#define SEMAPHORE_TAKE() xSemaphoreTake(_xSemaphore, portMAX_DELAY) | |||
#define SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore) | |||
#define GET_FREE_MEMORY() ESP.getMaxAllocHeap() | |||
#include <esp32-hal-log.h> | |||
#elif defined(ARDUINO_ARCH_ESP8266) | |||
#define SEMAPHORE_TAKE(X) while (_xSemaphore) { /*ESP.wdtFeed();*/ } _xSemaphore = true | |||
#define SEMAPHORE_GIVE() _xSemaphore = false | |||
#define GET_FREE_MEMORY() ESP.getMaxFreeBlockSize() | |||
#if defined(DEBUG_ESP_PORT) && defined(DEBUG_ASYNC_MQTT_CLIENT) | |||
#define log_i(...) DEBUG_ESP_PORT.printf(__VA_ARGS__); DEBUG_ESP_PORT.print("\n") | |||
#define log_e(...) DEBUG_ESP_PORT.printf(__VA_ARGS__); DEBUG_ESP_PORT.print("\n") | |||
#define log_w(...) DEBUG_ESP_PORT.printf(__VA_ARGS__); DEBUG_ESP_PORT.print("\n") | |||
#else | |||
#define log_i(...) | |||
#define log_e(...) | |||
#define log_w(...) | |||
#endif | |||
#else | |||
#pragma error "No valid architecture" | |||
#endif | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,7 @@ | |||
#pragma once | |||
struct AsyncMqttClientMessageProperties { | |||
uint8_t qos; | |||
bool dup; | |||
bool retain; | |||
}; |
@@ -0,0 +1,30 @@ | |||
#include "ConnAckPacket.hpp" | |||
using AsyncMqttClientInternals::ConnAckPacket; | |||
ConnAckPacket::ConnAckPacket(ParsingInformation* parsingInformation, OnConnAckInternalCallback callback) | |||
: _parsingInformation(parsingInformation) | |||
, _callback(callback) | |||
, _bytePosition(0) | |||
, _sessionPresent(false) | |||
, _connectReturnCode(0) { | |||
} | |||
ConnAckPacket::~ConnAckPacket() { | |||
} | |||
void ConnAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { | |||
char currentByte = data[(*currentBytePosition)++]; | |||
if (_bytePosition++ == 0) { | |||
_sessionPresent = (currentByte << 7) >> 7; | |||
} else { | |||
_connectReturnCode = currentByte; | |||
_parsingInformation->bufferState = BufferState::NONE; | |||
_callback(_sessionPresent, _connectReturnCode); | |||
} | |||
} | |||
void ConnAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { | |||
(void)data; | |||
(void)currentBytePosition; | |||
} |
@@ -0,0 +1,25 @@ | |||
#pragma once | |||
#include "Arduino.h" | |||
#include "Packet.hpp" | |||
#include "../ParsingInformation.hpp" | |||
#include "../Callbacks.hpp" | |||
namespace AsyncMqttClientInternals { | |||
class ConnAckPacket : public Packet { | |||
public: | |||
explicit ConnAckPacket(ParsingInformation* parsingInformation, OnConnAckInternalCallback callback); | |||
~ConnAckPacket(); | |||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); | |||
void parsePayload(char* data, size_t len, size_t* currentBytePosition); | |||
private: | |||
ParsingInformation* _parsingInformation; | |||
OnConnAckInternalCallback _callback; | |||
uint8_t _bytePosition; | |||
bool _sessionPresent; | |||
uint8_t _connectReturnCode; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,162 @@ | |||
#include "Connect.hpp" | |||
using AsyncMqttClientInternals::ConnectOutPacket; | |||
ConnectOutPacket::ConnectOutPacket(bool cleanSession, | |||
const char* username, | |||
const char* password, | |||
const char* willTopic, | |||
bool willRetain, | |||
uint8_t willQos, | |||
const char* willPayload, | |||
uint16_t willPayloadLength, | |||
uint16_t keepAlive, | |||
const char* clientId) { | |||
char fixedHeader[5]; | |||
fixedHeader[0] = AsyncMqttClientInternals::PacketType.CONNECT; | |||
fixedHeader[0] = fixedHeader[0] << 4; | |||
fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.CONNECT_RESERVED; | |||
uint16_t protocolNameLength = 4; | |||
char protocolNameLengthBytes[2]; | |||
protocolNameLengthBytes[0] = protocolNameLength >> 8; | |||
protocolNameLengthBytes[1] = protocolNameLength & 0xFF; | |||
char protocolLevel[1]; | |||
protocolLevel[0] = 0x04; | |||
char connectFlags[1]; | |||
connectFlags[0] = 0; | |||
if (cleanSession) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.CLEAN_SESSION; | |||
if (username != nullptr) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.USERNAME; | |||
if (password != nullptr) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.PASSWORD; | |||
if (willTopic != nullptr) { | |||
connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL; | |||
if (willRetain) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_RETAIN; | |||
switch (willQos) { | |||
case 0: | |||
connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_QOS0; | |||
break; | |||
case 1: | |||
connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_QOS1; | |||
break; | |||
case 2: | |||
connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_QOS2; | |||
break; | |||
} | |||
} | |||
char keepAliveBytes[2]; | |||
keepAliveBytes[0] = keepAlive >> 8; | |||
keepAliveBytes[1] = keepAlive & 0xFF; | |||
uint16_t clientIdLength = strlen(clientId); | |||
char clientIdLengthBytes[2]; | |||
clientIdLengthBytes[0] = clientIdLength >> 8; | |||
clientIdLengthBytes[1] = clientIdLength & 0xFF; | |||
// Optional fields | |||
uint16_t willTopicLength = 0; | |||
char willTopicLengthBytes[2]; | |||
char willPayloadLengthBytes[2]; | |||
if (willTopic != nullptr) { | |||
willTopicLength = strlen(willTopic); | |||
willTopicLengthBytes[0] = willTopicLength >> 8; | |||
willTopicLengthBytes[1] = willTopicLength & 0xFF; | |||
if (willPayload != nullptr && willPayloadLength == 0) willPayloadLength = strlen(willPayload); | |||
willPayloadLengthBytes[0] = willPayloadLength >> 8; | |||
willPayloadLengthBytes[1] = willPayloadLength & 0xFF; | |||
} | |||
uint16_t usernameLength = 0; | |||
char usernameLengthBytes[2]; | |||
if (username != nullptr) { | |||
usernameLength = strlen(username); | |||
usernameLengthBytes[0] = usernameLength >> 8; | |||
usernameLengthBytes[1] = usernameLength & 0xFF; | |||
} | |||
uint16_t passwordLength = 0; | |||
char passwordLengthBytes[2]; | |||
if (password != nullptr) { | |||
passwordLength = strlen(password); | |||
passwordLengthBytes[0] = passwordLength >> 8; | |||
passwordLengthBytes[1] = passwordLength & 0xFF; | |||
} | |||
uint32_t remainingLength = 2 + protocolNameLength + 1 + 1 + 2 + 2 + clientIdLength; // always present | |||
if (willTopic != nullptr) remainingLength += 2 + willTopicLength + 2 + willPayloadLength; | |||
if (username != nullptr) remainingLength += 2 + usernameLength; | |||
if (password != nullptr) remainingLength += 2 + passwordLength; | |||
uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(remainingLength, fixedHeader + 1); | |||
uint32_t neededSpace = 1 + remainingLengthLength; | |||
neededSpace += 2; | |||
neededSpace += protocolNameLength; | |||
neededSpace += 1; | |||
neededSpace += 1; | |||
neededSpace += 2; | |||
neededSpace += 2; | |||
neededSpace += clientIdLength; | |||
if (willTopic != nullptr) { | |||
neededSpace += 2; | |||
neededSpace += willTopicLength; | |||
neededSpace += 2; | |||
if (willPayload != nullptr) neededSpace += willPayloadLength; | |||
} | |||
if (username != nullptr) { | |||
neededSpace += 2; | |||
neededSpace += usernameLength; | |||
} | |||
if (password != nullptr) { | |||
neededSpace += 2; | |||
neededSpace += passwordLength; | |||
} | |||
_data.reserve(neededSpace); | |||
_data.insert(_data.end(), fixedHeader, fixedHeader + 1 + remainingLengthLength); | |||
_data.push_back(protocolNameLengthBytes[0]); | |||
_data.push_back(protocolNameLengthBytes[1]); | |||
_data.push_back('M'); | |||
_data.push_back('Q'); | |||
_data.push_back('T'); | |||
_data.push_back('T'); | |||
_data.push_back(protocolLevel[0]); | |||
_data.push_back(connectFlags[0]); | |||
_data.push_back(keepAliveBytes[0]); | |||
_data.push_back(keepAliveBytes[1]); | |||
_data.push_back(clientIdLengthBytes[0]); | |||
_data.push_back(clientIdLengthBytes[1]); | |||
_data.insert(_data.end(), clientId, clientId + clientIdLength); | |||
if (willTopic != nullptr) { | |||
_data.insert(_data.end(), willTopicLengthBytes, willTopicLengthBytes + 2); | |||
_data.insert(_data.end(), willTopic, willTopic + willTopicLength); | |||
_data.insert(_data.end(), willPayloadLengthBytes, willPayloadLengthBytes + 2); | |||
if (willPayload != nullptr) _data.insert(_data.end(), willPayload, willPayload + willPayloadLength); | |||
} | |||
if (username != nullptr) { | |||
_data.insert(_data.end(), usernameLengthBytes, usernameLengthBytes + 2); | |||
_data.insert(_data.end(), username, username + usernameLength); | |||
} | |||
if (password != nullptr) { | |||
_data.insert(_data.end(), passwordLengthBytes, passwordLengthBytes + 2); | |||
_data.insert(_data.end(), password, password + passwordLength); | |||
} | |||
} | |||
const uint8_t* ConnectOutPacket::data(size_t index) const { | |||
return &_data.data()[index]; | |||
} | |||
size_t ConnectOutPacket::size() const { | |||
return _data.size(); | |||
} |
@@ -0,0 +1,29 @@ | |||
#pragma once | |||
#include <vector> | |||
#include <cstring> // strlen | |||
#include "OutPacket.hpp" | |||
#include "../../Flags.hpp" | |||
#include "../../Helpers.hpp" | |||
namespace AsyncMqttClientInternals { | |||
class ConnectOutPacket : public OutPacket { | |||
public: | |||
ConnectOutPacket(bool cleanSession, | |||
const char* username, | |||
const char* password, | |||
const char* willTopic, | |||
bool willRetain, | |||
uint8_t willQos, | |||
const char* willPayload, | |||
uint16_t willPayloadLength, | |||
uint16_t keepAlive, | |||
const char* clientId); | |||
const uint8_t* data(size_t index = 0) const; | |||
size_t size() const; | |||
private: | |||
std::vector<uint8_t> _data; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,18 @@ | |||
#include "Disconn.hpp" | |||
using AsyncMqttClientInternals::DisconnOutPacket; | |||
DisconnOutPacket::DisconnOutPacket() { | |||
_data[0] = AsyncMqttClientInternals::PacketType.DISCONNECT; | |||
_data[0] = _data[0] << 4; | |||
_data[0] = _data[0] | AsyncMqttClientInternals::HeaderFlag.DISCONNECT_RESERVED; | |||
_data[1] = 0; | |||
} | |||
const uint8_t* DisconnOutPacket::data(size_t index) const { | |||
return &_data[index]; | |||
} | |||
size_t DisconnOutPacket::size() const { | |||
return 2; | |||
} |
@@ -0,0 +1,17 @@ | |||
#pragma once | |||
#include "OutPacket.hpp" | |||
#include "../../Flags.hpp" | |||
#include "../../Helpers.hpp" | |||
namespace AsyncMqttClientInternals { | |||
class DisconnOutPacket : public OutPacket { | |||
public: | |||
DisconnOutPacket(); | |||
const uint8_t* data(size_t index = 0) const; | |||
size_t size() const; | |||
private: | |||
uint8_t _data[2]; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,44 @@ | |||
#include "OutPacket.hpp" | |||
using AsyncMqttClientInternals::OutPacket; | |||
OutPacket::OutPacket() | |||
: next(nullptr) | |||
, timeout(0) | |||
, noTries(0) | |||
, _released(true) | |||
, _packetId(0) {} | |||
OutPacket::~OutPacket() {} | |||
bool OutPacket::released() const { | |||
return _released; | |||
} | |||
uint8_t OutPacket::packetType() const { | |||
return data(0)[0] >> 4; | |||
} | |||
uint16_t OutPacket::packetId() const { | |||
return _packetId; | |||
} | |||
uint8_t OutPacket::qos() const { | |||
if (packetType() == AsyncMqttClientInternals::PacketType.PUBLISH) { | |||
return (data()[1] & 0x06) >> 1; | |||
} | |||
return 0; | |||
} | |||
void OutPacket::release() { | |||
_released = true; | |||
} | |||
uint16_t OutPacket::_nextPacketId = 0; | |||
uint16_t OutPacket::_getNextPacketId() { | |||
if (++_nextPacketId == 0) { | |||
++_nextPacketId; | |||
} | |||
return _nextPacketId; | |||
} |
@@ -0,0 +1,35 @@ | |||
#pragma once | |||
#include <stdint.h> // uint*_t | |||
#include <stddef.h> // size_t | |||
#include <algorithm> // std::min | |||
#include "../../Flags.hpp" | |||
namespace AsyncMqttClientInternals { | |||
class OutPacket { | |||
public: | |||
OutPacket(); | |||
virtual ~OutPacket(); | |||
virtual const uint8_t* data(size_t index = 0) const = 0; | |||
virtual size_t size() const = 0; | |||
bool released() const; | |||
uint8_t packetType() const; | |||
uint16_t packetId() const; | |||
uint8_t qos() const; | |||
void release(); | |||
public: | |||
OutPacket* next; | |||
uint32_t timeout; | |||
uint8_t noTries; | |||
protected: | |||
static uint16_t _getNextPacketId(); | |||
bool _released; | |||
uint16_t _packetId; | |||
private: | |||
static uint16_t _nextPacketId; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,18 @@ | |||
#include "PingReq.hpp" | |||
using AsyncMqttClientInternals::PingReqOutPacket; | |||
PingReqOutPacket::PingReqOutPacket() { | |||
_data[0] = AsyncMqttClientInternals::PacketType.PINGREQ; | |||
_data[0] = _data[0] << 4; | |||
_data[0] = _data[0] | AsyncMqttClientInternals::HeaderFlag.PINGREQ_RESERVED; | |||
_data[1] = 0; | |||
} | |||
const uint8_t* PingReqOutPacket::data(size_t index) const { | |||
return &_data[index];; | |||
} | |||
size_t PingReqOutPacket::size() const { | |||
return 2; | |||
} |
@@ -0,0 +1,17 @@ | |||
#pragma once | |||
#include "OutPacket.hpp" | |||
#include "../../Flags.hpp" | |||
#include "../../Helpers.hpp" | |||
namespace AsyncMqttClientInternals { | |||
class PingReqOutPacket : public OutPacket { | |||
public: | |||
PingReqOutPacket(); | |||
const uint8_t* data(size_t index = 0) const; | |||
size_t size() const; | |||
private: | |||
uint8_t _data[2]; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,25 @@ | |||
#include "PubAck.hpp" | |||
using AsyncMqttClientInternals::PubAckOutPacket; | |||
PubAckOutPacket::PubAckOutPacket(PendingAck pendingAck) { | |||
_data[0] = pendingAck.packetType; | |||
_data[0] = _data[0] << 4; | |||
_data[0] = _data[0] | pendingAck.headerFlag; | |||
_data[1] = 2; | |||
_packetId = pendingAck.packetId; | |||
_data[2] = pendingAck.packetId >> 8; | |||
_data[3] = pendingAck.packetId & 0xFF; | |||
if (packetType() == AsyncMqttClientInternals::PacketType.PUBREL || | |||
packetType() == AsyncMqttClientInternals::PacketType.PUBREC) { | |||
_released = false; | |||
} | |||
} | |||
const uint8_t* PubAckOutPacket::data(size_t index) const { | |||
return &_data[index]; | |||
} | |||
size_t PubAckOutPacket::size() const { | |||
return 4; | |||
} |
@@ -0,0 +1,18 @@ | |||
#pragma once | |||
#include "OutPacket.hpp" | |||
#include "../../Flags.hpp" | |||
#include "../../Helpers.hpp" | |||
#include "../../Storage.hpp" | |||
namespace AsyncMqttClientInternals { | |||
class PubAckOutPacket : public OutPacket { | |||
public: | |||
explicit PubAckOutPacket(PendingAck pendingAck); | |||
const uint8_t* data(size_t index = 0) const; | |||
size_t size() const; | |||
private: | |||
uint8_t _data[4]; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,69 @@ | |||
#include "Publish.hpp" | |||
using AsyncMqttClientInternals::PublishOutPacket; | |||
PublishOutPacket::PublishOutPacket(const char* topic, uint8_t qos, bool retain, const char* payload, size_t length) { | |||
char fixedHeader[5]; | |||
fixedHeader[0] = AsyncMqttClientInternals::PacketType.PUBLISH; | |||
fixedHeader[0] = fixedHeader[0] << 4; | |||
// if (dup) fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_DUP; | |||
if (retain) fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_RETAIN; | |||
switch (qos) { | |||
case 0: | |||
fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_QOS0; | |||
break; | |||
case 1: | |||
fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_QOS1; | |||
break; | |||
case 2: | |||
fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_QOS2; | |||
break; | |||
} | |||
uint16_t topicLength = strlen(topic); | |||
char topicLengthBytes[2]; | |||
topicLengthBytes[0] = topicLength >> 8; | |||
topicLengthBytes[1] = topicLength & 0xFF; | |||
uint32_t payloadLength = length; | |||
if (payload != nullptr && payloadLength == 0) payloadLength = strlen(payload); | |||
uint32_t remainingLength = 2 + topicLength + payloadLength; | |||
if (qos != 0) remainingLength += 2; | |||
uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(remainingLength, fixedHeader + 1); | |||
size_t neededSpace = 0; | |||
neededSpace += 1 + remainingLengthLength; | |||
neededSpace += 2; | |||
neededSpace += topicLength; | |||
if (qos != 0) neededSpace += 2; | |||
if (payload != nullptr) neededSpace += payloadLength; | |||
_data.reserve(neededSpace); | |||
_packetId = (qos !=0) ? _getNextPacketId() : 1; | |||
char packetIdBytes[2]; | |||
packetIdBytes[0] = _packetId >> 8; | |||
packetIdBytes[1] = _packetId & 0xFF; | |||
_data.insert(_data.end(), fixedHeader, fixedHeader + 1 + remainingLengthLength); | |||
_data.insert(_data.end(), topicLengthBytes, topicLengthBytes + 2); | |||
_data.insert(_data.end(), topic, topic + topicLength); | |||
if (qos != 0) { | |||
_data.insert(_data.end(), packetIdBytes, packetIdBytes + 2); | |||
_released = false; | |||
} | |||
if (payload != nullptr) _data.insert(_data.end(), payload, payload + payloadLength); | |||
} | |||
const uint8_t* PublishOutPacket::data(size_t index) const { | |||
return &_data.data()[index]; | |||
} | |||
size_t PublishOutPacket::size() const { | |||
return _data.size(); | |||
} | |||
void PublishOutPacket::setDup() { | |||
_data[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_DUP; | |||
} |
@@ -0,0 +1,23 @@ | |||
#pragma once | |||
#include <cstring> // strlen | |||
#include <vector> | |||
#include "OutPacket.hpp" | |||
#include "../../Flags.hpp" | |||
#include "../../Helpers.hpp" | |||
#include "../../Storage.hpp" | |||
namespace AsyncMqttClientInternals { | |||
class PublishOutPacket : public OutPacket { | |||
public: | |||
PublishOutPacket(const char* topic, uint8_t qos, bool retain, const char* payload, size_t length); | |||
const uint8_t* data(size_t index = 0) const; | |||
size_t size() const; | |||
void setDup(); // you cannot unset dup | |||
private: | |||
std::vector<uint8_t> _data; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,49 @@ | |||
#include "Subscribe.hpp" | |||
using AsyncMqttClientInternals::SubscribeOutPacket; | |||
SubscribeOutPacket::SubscribeOutPacket(const char* topic, uint8_t qos) { | |||
char fixedHeader[5]; | |||
fixedHeader[0] = AsyncMqttClientInternals::PacketType.SUBSCRIBE; | |||
fixedHeader[0] = fixedHeader[0] << 4; | |||
fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.SUBSCRIBE_RESERVED; | |||
uint16_t topicLength = strlen(topic); | |||
char topicLengthBytes[2]; | |||
topicLengthBytes[0] = topicLength >> 8; | |||
topicLengthBytes[1] = topicLength & 0xFF; | |||
char qosByte[1]; | |||
qosByte[0] = qos; | |||
uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(2 + 2 + topicLength + 1, fixedHeader + 1); | |||
size_t neededSpace = 0; | |||
neededSpace += 1 + remainingLengthLength; | |||
neededSpace += 2; | |||
neededSpace += 2; | |||
neededSpace += topicLength; | |||
neededSpace += 1; | |||
_data.reserve(neededSpace); | |||
_packetId = _getNextPacketId(); | |||
char packetIdBytes[2]; | |||
packetIdBytes[0] = _packetId >> 8; | |||
packetIdBytes[1] = _packetId & 0xFF; | |||
_data.insert(_data.end(), fixedHeader, fixedHeader + 1 + remainingLengthLength); | |||
_data.insert(_data.end(), packetIdBytes, packetIdBytes + 2); | |||
_data.insert(_data.end(), topicLengthBytes, topicLengthBytes + 2); | |||
_data.insert(_data.end(), topic, topic + topicLength); | |||
_data.push_back(qosByte[0]); | |||
_released = false; | |||
} | |||
const uint8_t* SubscribeOutPacket::data(size_t index) const { | |||
return &_data.data()[index]; | |||
} | |||
size_t SubscribeOutPacket::size() const { | |||
return _data.size(); | |||
} |
@@ -0,0 +1,21 @@ | |||
#pragma once | |||
#include <cstring> // strlen | |||
#include <vector> | |||
#include "OutPacket.hpp" | |||
#include "../../Flags.hpp" | |||
#include "../../Helpers.hpp" | |||
#include "../../Storage.hpp" | |||
namespace AsyncMqttClientInternals { | |||
class SubscribeOutPacket : public OutPacket { | |||
public: | |||
SubscribeOutPacket(const char* topic, uint8_t qos); | |||
const uint8_t* data(size_t index = 0) const; | |||
size_t size() const; | |||
private: | |||
std::vector<uint8_t> _data; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,42 @@ | |||
#include "Unsubscribe.hpp" | |||
using AsyncMqttClientInternals::UnsubscribeOutPacket; | |||
UnsubscribeOutPacket::UnsubscribeOutPacket(const char* topic) { | |||
char fixedHeader[5]; | |||
fixedHeader[0] = AsyncMqttClientInternals::PacketType.UNSUBSCRIBE; | |||
fixedHeader[0] = fixedHeader[0] << 4; | |||
fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.UNSUBSCRIBE_RESERVED; | |||
uint16_t topicLength = strlen(topic); | |||
char topicLengthBytes[2]; | |||
topicLengthBytes[0] = topicLength >> 8; | |||
topicLengthBytes[1] = topicLength & 0xFF; | |||
uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(2 + 2 + topicLength, fixedHeader + 1); | |||
size_t neededSpace = 0; | |||
neededSpace += 1 + remainingLengthLength; | |||
neededSpace += 2; | |||
neededSpace += 2; | |||
neededSpace += topicLength; | |||
_packetId = _getNextPacketId(); | |||
char packetIdBytes[2]; | |||
packetIdBytes[0] = _packetId >> 8; | |||
packetIdBytes[1] = _packetId & 0xFF; | |||
_data.insert(_data.end(), fixedHeader, fixedHeader + 1 + remainingLengthLength); | |||
_data.insert(_data.end(), packetIdBytes, packetIdBytes + 2); | |||
_data.insert(_data.end(), topicLengthBytes, topicLengthBytes + 2); | |||
_data.insert(_data.end(), topic, topic + topicLength); | |||
_released = false; | |||
} | |||
const uint8_t* UnsubscribeOutPacket::data(size_t index) const { | |||
return &_data.data()[index]; | |||
} | |||
size_t UnsubscribeOutPacket::size() const { | |||
return _data.size(); | |||
} |
@@ -0,0 +1,21 @@ | |||
#pragma once | |||
#include <cstring> // strlen | |||
#include <vector> | |||
#include "OutPacket.hpp" | |||
#include "../../Flags.hpp" | |||
#include "../../Helpers.hpp" | |||
#include "../../Storage.hpp" | |||
namespace AsyncMqttClientInternals { | |||
class UnsubscribeOutPacket : public OutPacket { | |||
public: | |||
explicit UnsubscribeOutPacket(const char* topic); | |||
const uint8_t* data(size_t index = 0) const; | |||
size_t size() const; | |||
private: | |||
std::vector<uint8_t> _data; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,11 @@ | |||
#pragma once | |||
namespace AsyncMqttClientInternals { | |||
class Packet { | |||
public: | |||
virtual ~Packet() {} | |||
virtual void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) = 0; | |||
virtual void parsePayload(char* data, size_t len, size_t* currentBytePosition) = 0; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,21 @@ | |||
#include "PingRespPacket.hpp" | |||
using AsyncMqttClientInternals::PingRespPacket; | |||
PingRespPacket::PingRespPacket(ParsingInformation* parsingInformation, OnPingRespInternalCallback callback) | |||
: _parsingInformation(parsingInformation) | |||
, _callback(callback) { | |||
} | |||
PingRespPacket::~PingRespPacket() { | |||
} | |||
void PingRespPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { | |||
(void)data; | |||
(void)currentBytePosition; | |||
} | |||
void PingRespPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { | |||
(void)data; | |||
(void)currentBytePosition; | |||
} |
@@ -0,0 +1,21 @@ | |||
#pragma once | |||
#include "Arduino.h" | |||
#include "Packet.hpp" | |||
#include "../ParsingInformation.hpp" | |||
#include "../Callbacks.hpp" | |||
namespace AsyncMqttClientInternals { | |||
class PingRespPacket : public Packet { | |||
public: | |||
explicit PingRespPacket(ParsingInformation* parsingInformation, OnPingRespInternalCallback callback); | |||
~PingRespPacket(); | |||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); | |||
void parsePayload(char* data, size_t len, size_t* currentBytePosition); | |||
private: | |||
ParsingInformation* _parsingInformation; | |||
OnPingRespInternalCallback _callback; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,30 @@ | |||
#include "PubAckPacket.hpp" | |||
using AsyncMqttClientInternals::PubAckPacket; | |||
PubAckPacket::PubAckPacket(ParsingInformation* parsingInformation, OnPubAckInternalCallback callback) | |||
: _parsingInformation(parsingInformation) | |||
, _callback(callback) | |||
, _bytePosition(0) | |||
, _packetIdMsb(0) | |||
, _packetId(0) { | |||
} | |||
PubAckPacket::~PubAckPacket() { | |||
} | |||
void PubAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { | |||
char currentByte = data[(*currentBytePosition)++]; | |||
if (_bytePosition++ == 0) { | |||
_packetIdMsb = currentByte; | |||
} else { | |||
_packetId = currentByte | _packetIdMsb << 8; | |||
_parsingInformation->bufferState = BufferState::NONE; | |||
_callback(_packetId); | |||
} | |||
} | |||
void PubAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { | |||
(void)data; | |||
(void)currentBytePosition; | |||
} |
@@ -0,0 +1,25 @@ | |||
#pragma once | |||
#include "Arduino.h" | |||
#include "Packet.hpp" | |||
#include "../ParsingInformation.hpp" | |||
#include "../Callbacks.hpp" | |||
namespace AsyncMqttClientInternals { | |||
class PubAckPacket : public Packet { | |||
public: | |||
explicit PubAckPacket(ParsingInformation* parsingInformation, OnPubAckInternalCallback callback); | |||
~PubAckPacket(); | |||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); | |||
void parsePayload(char* data, size_t len, size_t* currentBytePosition); | |||
private: | |||
ParsingInformation* _parsingInformation; | |||
OnPubAckInternalCallback _callback; | |||
uint8_t _bytePosition; | |||
char _packetIdMsb; | |||
uint16_t _packetId; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,30 @@ | |||
#include "PubCompPacket.hpp" | |||
using AsyncMqttClientInternals::PubCompPacket; | |||
PubCompPacket::PubCompPacket(ParsingInformation* parsingInformation, OnPubCompInternalCallback callback) | |||
: _parsingInformation(parsingInformation) | |||
, _callback(callback) | |||
, _bytePosition(0) | |||
, _packetIdMsb(0) | |||
, _packetId(0) { | |||
} | |||
PubCompPacket::~PubCompPacket() { | |||
} | |||
void PubCompPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { | |||
char currentByte = data[(*currentBytePosition)++]; | |||
if (_bytePosition++ == 0) { | |||
_packetIdMsb = currentByte; | |||
} else { | |||
_packetId = currentByte | _packetIdMsb << 8; | |||
_parsingInformation->bufferState = BufferState::NONE; | |||
_callback(_packetId); | |||
} | |||
} | |||
void PubCompPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { | |||
(void)data; | |||
(void)currentBytePosition; | |||
} |
@@ -0,0 +1,25 @@ | |||
#pragma once | |||
#include "Arduino.h" | |||
#include "Packet.hpp" | |||
#include "../ParsingInformation.hpp" | |||
#include "../Callbacks.hpp" | |||
namespace AsyncMqttClientInternals { | |||
class PubCompPacket : public Packet { | |||
public: | |||
explicit PubCompPacket(ParsingInformation* parsingInformation, OnPubCompInternalCallback callback); | |||
~PubCompPacket(); | |||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); | |||
void parsePayload(char* data, size_t len, size_t* currentBytePosition); | |||
private: | |||
ParsingInformation* _parsingInformation; | |||
OnPubCompInternalCallback _callback; | |||
uint8_t _bytePosition; | |||
char _packetIdMsb; | |||
uint16_t _packetId; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,30 @@ | |||
#include "PubRecPacket.hpp" | |||
using AsyncMqttClientInternals::PubRecPacket; | |||
PubRecPacket::PubRecPacket(ParsingInformation* parsingInformation, OnPubRecInternalCallback callback) | |||
: _parsingInformation(parsingInformation) | |||
, _callback(callback) | |||
, _bytePosition(0) | |||
, _packetIdMsb(0) | |||
, _packetId(0) { | |||
} | |||
PubRecPacket::~PubRecPacket() { | |||
} | |||
void PubRecPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { | |||
char currentByte = data[(*currentBytePosition)++]; | |||
if (_bytePosition++ == 0) { | |||
_packetIdMsb = currentByte; | |||
} else { | |||
_packetId = currentByte | _packetIdMsb << 8; | |||
_parsingInformation->bufferState = BufferState::NONE; | |||
_callback(_packetId); | |||
} | |||
} | |||
void PubRecPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { | |||
(void)data; | |||
(void)currentBytePosition; | |||
} |
@@ -0,0 +1,25 @@ | |||
#pragma once | |||
#include "Arduino.h" | |||
#include "Packet.hpp" | |||
#include "../ParsingInformation.hpp" | |||
#include "../Callbacks.hpp" | |||
namespace AsyncMqttClientInternals { | |||
class PubRecPacket : public Packet { | |||
public: | |||
explicit PubRecPacket(ParsingInformation* parsingInformation, OnPubRecInternalCallback callback); | |||
~PubRecPacket(); | |||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); | |||
void parsePayload(char* data, size_t len, size_t* currentBytePosition); | |||
private: | |||
ParsingInformation* _parsingInformation; | |||
OnPubRecInternalCallback _callback; | |||
uint8_t _bytePosition; | |||
char _packetIdMsb; | |||
uint16_t _packetId; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,30 @@ | |||
#include "PubRelPacket.hpp" | |||
using AsyncMqttClientInternals::PubRelPacket; | |||
PubRelPacket::PubRelPacket(ParsingInformation* parsingInformation, OnPubRelInternalCallback callback) | |||
: _parsingInformation(parsingInformation) | |||
, _callback(callback) | |||
, _bytePosition(0) | |||
, _packetIdMsb(0) | |||
, _packetId(0) { | |||
} | |||
PubRelPacket::~PubRelPacket() { | |||
} | |||
void PubRelPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { | |||
char currentByte = data[(*currentBytePosition)++]; | |||
if (_bytePosition++ == 0) { | |||
_packetIdMsb = currentByte; | |||
} else { | |||
_packetId = currentByte | _packetIdMsb << 8; | |||
_parsingInformation->bufferState = BufferState::NONE; | |||
_callback(_packetId); | |||
} | |||
} | |||
void PubRelPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { | |||
(void)data; | |||
(void)currentBytePosition; | |||
} |
@@ -0,0 +1,25 @@ | |||
#pragma once | |||
#include "Arduino.h" | |||
#include "Packet.hpp" | |||
#include "../ParsingInformation.hpp" | |||
#include "../Callbacks.hpp" | |||
namespace AsyncMqttClientInternals { | |||
class PubRelPacket : public Packet { | |||
public: | |||
explicit PubRelPacket(ParsingInformation* parsingInformation, OnPubRelInternalCallback callback); | |||
~PubRelPacket(); | |||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); | |||
void parsePayload(char* data, size_t len, size_t* currentBytePosition); | |||
private: | |||
ParsingInformation* _parsingInformation; | |||
OnPubRelInternalCallback _callback; | |||
uint8_t _bytePosition; | |||
char _packetIdMsb; | |||
uint16_t _packetId; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,91 @@ | |||
#include "PublishPacket.hpp" | |||
using AsyncMqttClientInternals::PublishPacket; | |||
PublishPacket::PublishPacket(ParsingInformation* parsingInformation, OnMessageInternalCallback dataCallback, OnPublishInternalCallback completeCallback) | |||
: _parsingInformation(parsingInformation) | |||
, _dataCallback(dataCallback) | |||
, _completeCallback(completeCallback) | |||
, _dup(false) | |||
, _qos(0) | |||
, _retain(0) | |||
, _bytePosition(0) | |||
, _topicLengthMsb(0) | |||
, _topicLength(0) | |||
, _ignore(false) | |||
, _packetIdMsb(0) | |||
, _packetId(0) | |||
, _payloadLength(0) | |||
, _payloadBytesRead(0) { | |||
_dup = _parsingInformation->packetFlags & HeaderFlag.PUBLISH_DUP; | |||
_retain = _parsingInformation->packetFlags & HeaderFlag.PUBLISH_RETAIN; | |||
char qosMasked = _parsingInformation->packetFlags & 0x06; | |||
switch (qosMasked) { | |||
case HeaderFlag.PUBLISH_QOS0: | |||
_qos = 0; | |||
break; | |||
case HeaderFlag.PUBLISH_QOS1: | |||
_qos = 1; | |||
break; | |||
case HeaderFlag.PUBLISH_QOS2: | |||
_qos = 2; | |||
break; | |||
} | |||
} | |||
PublishPacket::~PublishPacket() { | |||
} | |||
void PublishPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { | |||
char currentByte = data[(*currentBytePosition)++]; | |||
if (_bytePosition == 0) { | |||
_topicLengthMsb = currentByte; | |||
} else if (_bytePosition == 1) { | |||
_topicLength = currentByte | _topicLengthMsb << 8; | |||
if (_topicLength > _parsingInformation->maxTopicLength) { | |||
_ignore = true; | |||
} else { | |||
_parsingInformation->topicBuffer[_topicLength] = '\0'; | |||
} | |||
} else if (_bytePosition >= 2 && _bytePosition < 2 + _topicLength) { | |||
// Starting from here, _ignore might be true | |||
if (!_ignore) _parsingInformation->topicBuffer[_bytePosition - 2] = currentByte; | |||
if (_bytePosition == 2 + _topicLength - 1 && _qos == 0) { | |||
_preparePayloadHandling(_parsingInformation->remainingLength - (_bytePosition + 1)); | |||
return; | |||
} | |||
} else if (_bytePosition == 2 + _topicLength) { | |||
_packetIdMsb = currentByte; | |||
} else { | |||
_packetId = currentByte | _packetIdMsb << 8; | |||
_preparePayloadHandling(_parsingInformation->remainingLength - (_bytePosition + 1)); | |||
} | |||
_bytePosition++; | |||
} | |||
void PublishPacket::_preparePayloadHandling(uint32_t payloadLength) { | |||
_payloadLength = payloadLength; | |||
if (payloadLength == 0) { | |||
_parsingInformation->bufferState = BufferState::NONE; | |||
if (!_ignore) { | |||
_dataCallback(_parsingInformation->topicBuffer, nullptr, _qos, _dup, _retain, 0, 0, 0, _packetId); | |||
_completeCallback(_packetId, _qos); | |||
} | |||
} else { | |||
_parsingInformation->bufferState = BufferState::PAYLOAD; | |||
} | |||
} | |||
void PublishPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { | |||
size_t remainToRead = len - (*currentBytePosition); | |||
if (_payloadBytesRead + remainToRead > _payloadLength) remainToRead = _payloadLength - _payloadBytesRead; | |||
if (!_ignore) _dataCallback(_parsingInformation->topicBuffer, data + (*currentBytePosition), _qos, _dup, _retain, remainToRead, _payloadBytesRead, _payloadLength, _packetId); | |||
_payloadBytesRead += remainToRead; | |||
(*currentBytePosition) += remainToRead; | |||
if (_payloadBytesRead == _payloadLength) { | |||
_parsingInformation->bufferState = BufferState::NONE; | |||
if (!_ignore) _completeCallback(_packetId, _qos); | |||
} | |||
} |
@@ -0,0 +1,38 @@ | |||
#pragma once | |||
#include "Arduino.h" | |||
#include "Packet.hpp" | |||
#include "../Flags.hpp" | |||
#include "../ParsingInformation.hpp" | |||
#include "../Callbacks.hpp" | |||
namespace AsyncMqttClientInternals { | |||
class PublishPacket : public Packet { | |||
public: | |||
explicit PublishPacket(ParsingInformation* parsingInformation, OnMessageInternalCallback dataCallback, OnPublishInternalCallback completeCallback); | |||
~PublishPacket(); | |||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); | |||
void parsePayload(char* data, size_t len, size_t* currentBytePosition); | |||
private: | |||
ParsingInformation* _parsingInformation; | |||
OnMessageInternalCallback _dataCallback; | |||
OnPublishInternalCallback _completeCallback; | |||
void _preparePayloadHandling(uint32_t payloadLength); | |||
bool _dup; | |||
uint8_t _qos; | |||
bool _retain; | |||
uint8_t _bytePosition; | |||
char _topicLengthMsb; | |||
uint16_t _topicLength; | |||
bool _ignore; | |||
char _packetIdMsb; | |||
uint16_t _packetId; | |||
uint32_t _payloadLength; | |||
uint32_t _payloadBytesRead; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,46 @@ | |||
#include "SubAckPacket.hpp" | |||
using AsyncMqttClientInternals::SubAckPacket; | |||
SubAckPacket::SubAckPacket(ParsingInformation* parsingInformation, OnSubAckInternalCallback callback) | |||
: _parsingInformation(parsingInformation) | |||
, _callback(callback) | |||
, _bytePosition(0) | |||
, _packetIdMsb(0) | |||
, _packetId(0) { | |||
} | |||
SubAckPacket::~SubAckPacket() { | |||
} | |||
void SubAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { | |||
char currentByte = data[(*currentBytePosition)++]; | |||
if (_bytePosition++ == 0) { | |||
_packetIdMsb = currentByte; | |||
} else { | |||
_packetId = currentByte | _packetIdMsb << 8; | |||
_parsingInformation->bufferState = BufferState::PAYLOAD; | |||
} | |||
} | |||
void SubAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { | |||
char status = data[(*currentBytePosition)++]; | |||
/* switch (status) { | |||
case 0: | |||
Serial.println("Success QoS 0"); | |||
break; | |||
case 1: | |||
Serial.println("Success QoS 1"); | |||
break; | |||
case 2: | |||
Serial.println("Success QoS 2"); | |||
break; | |||
case 0x80: | |||
Serial.println("Failure"); | |||
break; | |||
} */ | |||
_parsingInformation->bufferState = BufferState::NONE; | |||
_callback(_packetId, status); | |||
} |
@@ -0,0 +1,25 @@ | |||
#pragma once | |||
#include "Arduino.h" | |||
#include "Packet.hpp" | |||
#include "../ParsingInformation.hpp" | |||
#include "../Callbacks.hpp" | |||
namespace AsyncMqttClientInternals { | |||
class SubAckPacket : public Packet { | |||
public: | |||
explicit SubAckPacket(ParsingInformation* parsingInformation, OnSubAckInternalCallback callback); | |||
~SubAckPacket(); | |||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); | |||
void parsePayload(char* data, size_t len, size_t* currentBytePosition); | |||
private: | |||
ParsingInformation* _parsingInformation; | |||
OnSubAckInternalCallback _callback; | |||
uint8_t _bytePosition; | |||
char _packetIdMsb; | |||
uint16_t _packetId; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,30 @@ | |||
#include "UnsubAckPacket.hpp" | |||
using AsyncMqttClientInternals::UnsubAckPacket; | |||
UnsubAckPacket::UnsubAckPacket(ParsingInformation* parsingInformation, OnUnsubAckInternalCallback callback) | |||
: _parsingInformation(parsingInformation) | |||
, _callback(callback) | |||
, _bytePosition(0) | |||
, _packetIdMsb(0) | |||
, _packetId(0) { | |||
} | |||
UnsubAckPacket::~UnsubAckPacket() { | |||
} | |||
void UnsubAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) { | |||
char currentByte = data[(*currentBytePosition)++]; | |||
if (_bytePosition++ == 0) { | |||
_packetIdMsb = currentByte; | |||
} else { | |||
_packetId = currentByte | _packetIdMsb << 8; | |||
_parsingInformation->bufferState = BufferState::NONE; | |||
_callback(_packetId); | |||
} | |||
} | |||
void UnsubAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) { | |||
(void)data; | |||
(void)currentBytePosition; | |||
} |
@@ -0,0 +1,25 @@ | |||
#pragma once | |||
#include "Arduino.h" | |||
#include "Packet.hpp" | |||
#include "../ParsingInformation.hpp" | |||
#include "../Callbacks.hpp" | |||
namespace AsyncMqttClientInternals { | |||
class UnsubAckPacket : public Packet { | |||
public: | |||
explicit UnsubAckPacket(ParsingInformation* parsingInformation, OnUnsubAckInternalCallback callback); | |||
~UnsubAckPacket(); | |||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition); | |||
void parsePayload(char* data, size_t len, size_t* currentBytePosition); | |||
private: | |||
ParsingInformation* _parsingInformation; | |||
OnUnsubAckInternalCallback _callback; | |||
uint8_t _bytePosition; | |||
char _packetIdMsb; | |||
uint16_t _packetId; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,21 @@ | |||
#pragma once | |||
namespace AsyncMqttClientInternals { | |||
enum class BufferState : uint8_t { | |||
NONE = 0, | |||
REMAINING_LENGTH = 2, | |||
VARIABLE_HEADER = 3, | |||
PAYLOAD = 4 | |||
}; | |||
struct ParsingInformation { | |||
BufferState bufferState; | |||
uint16_t maxTopicLength; | |||
char* topicBuffer; | |||
uint8_t packetType; | |||
uint16_t packetFlags; | |||
uint32_t remainingLength; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1,13 @@ | |||
#pragma once | |||
namespace AsyncMqttClientInternals { | |||
struct PendingPubRel { | |||
uint16_t packetId; | |||
}; | |||
struct PendingAck { | |||
uint8_t packetType; | |||
uint8_t headerFlag; | |||
uint16_t packetId; | |||
}; | |||
} // namespace AsyncMqttClientInternals |
@@ -0,0 +1 @@ | |||
{"type": "library", "name": "EspMQTT", "version": "1.0.25", "spec": {"owner": "politsin", "id": 181, "name": "EspMQTT", "requirements": null, "uri": null}} |
@@ -0,0 +1,68 @@ | |||
# ESP32 > MQTT | |||
ESP32 foundation library for MQTT based HomeIOT | |||
```cpp | |||
#include <Arduino.h> | |||
using std::string; | |||
#include <freertos/queue.h> | |||
QueueHandle_t mqttQueue; | |||
typedef struct { | |||
string name; | |||
string metric; | |||
} mqttMessage; | |||
// EspMqtt. | |||
#include "EspMQTT.h" | |||
EspMQTT mqtt; | |||
void mqtt_callback(std::string param, std::string value); | |||
void mqttSetup() { | |||
uint16_t debugLevel = 0; | |||
if (debugLevel) { | |||
mqtt.debugLevel = debugLevel; | |||
mqtt.setAvailabilityInterval(5); | |||
} | |||
// MqTT: | |||
mqtt.setWiFi("wifi-name", "wifi-pass", "hostname"); | |||
mqtt.setMqtt("mqtt-server", "mqtt-user", "mqtt-pass"); | |||
mqtt.setCommonTopics("my/root/topic/dir", "ds18b20"); | |||
mqtt.setCallback(mqtt_callback); | |||
mqtt.start(true); | |||
} | |||
// Main.cpp Setup. | |||
void setup() { | |||
mqttSetup(); | |||
mqttQueue = xQueueCreate(10, sizeof(mqttMessage)); | |||
} | |||
// Main.cpp Loop. | |||
float counter = 1.1; | |||
void loop() { | |||
if (xQueueReceive(mqttQueue, &message, 100 / portTICK_PERIOD_MS) == pdFALSE) { | |||
mqtt.publishMetric(message.name, message.metric); | |||
// printf("mqtt [%s] push = %s\n", msg.name.c_str(), msg.metric.c_str()); | |||
} | |||
else { | |||
counter += 0.1; | |||
char data[10]; | |||
sprintf(data, "%.2f", counter); | |||
string metric = std::string(data); | |||
mqttMessage msg = {"temperature", metric}; | |||
xQueueSend(mqttQueue, &msg, portMAX_DELAY); | |||
} | |||
vTaskDelay(5000 / portTICK_PERIOD_MS); | |||
} | |||
void mqtt_callback(std::string param, std::string value) { | |||
uint16_t val = atoi(value.c_str()); | |||
printf("%s=%s\n", param.c_str(), message.c_str()); | |||
} | |||
``` | |||
## Ref: | |||
* https://github.com/marvinroger/async-mqtt-client/blob/master/src/AsyncMqttClient.hpp | |||
* https://github.com/Dullage/ESP-LED-MQTT |
@@ -0,0 +1,35 @@ | |||
#include <Arduino.h> | |||
#include "EspMQTT.h" | |||
EspMQTT mqtt; | |||
void mqtt_callback(std::string param, std::string value); | |||
void setup() { | |||
pinMode(LED_BUILTIN, OUTPUT); | |||
digitalWrite(LED_BUILTIN, LOW); | |||
Serial.begin(115200); | |||
Serial.println("\nSetup"); | |||
// MqTT: | |||
mqtt.setWiFi("MY_ssid", "MY_pass", "MY_hostname"); | |||
mqtt.setMqtt("MY_host", "MY_user", "MY_pass"); | |||
mqtt.setCommonTopics("home/light", "roomled"); // (topicRoot, device) | |||
mqtt.setCallback(mqtt_callback); | |||
// mqtt.ota = true; // Default: false; | |||
// mqtt.test = true; // Default: false; | |||
// mqtt.debug = true; // Default: false; | |||
// mqtt.setAvailabilityInterval(sec) // Default: 30sec; | |||
mqtt.start(); | |||
} | |||
void loop() { | |||
mqtt.loop(); // otaHandle; | |||
if (WiFi.isConnected() && mqtt.online) { | |||
// Do someshing. | |||
} | |||
yield(); | |||
} | |||
void mqtt_callback(std::string param, std::string value) { | |||
uint16_t val = atoi(value.c_str()); | |||
Serial.printf("%s=%s\n", param.c_str(), message.c_str()); | |||
} |
@@ -0,0 +1,73 @@ | |||
#include <Arduino.h> | |||
using std::string; | |||
// Queue. | |||
#include <freertos/queue.h> | |||
QueueHandle_t mqttQueue; | |||
typedef struct { | |||
string name; | |||
string metric; | |||
} mqttMessage; | |||
// Ds18b20. | |||
#include <DallasTemperature.h> | |||
#include <OneWire.h> | |||
#define ONE_WIRE_BUS1 GPIO_NUM_18 | |||
OneWire oneWire1(ONE_WIRE_BUS1); | |||
DallasTemperature ds18(&oneWire1); | |||
TaskHandle_t ds18b20; | |||
void ds18b20Task(void *pvParam) { | |||
ds18.begin(); | |||
while (true) { | |||
float temperature = ds18.getTempCByIndex(0); | |||
if (temperature != DEVICE_DISCONNECTED_C) { | |||
char data[10]; | |||
sprintf(data, "%.2f", temperature); | |||
string metric = std::string(data); | |||
mqttMessage msg = {"temperature", metric}; | |||
xQueueSend(mqttQueue, &msg, portMAX_DELAY); | |||
// printf("Tin = %.2f C\n", temperature); | |||
} // else: printf("Error: Could not read temperature data\n"); | |||
vTaskDelay(10356 / portTICK_PERIOD_MS); | |||
} | |||
} | |||
// EspMqtt. | |||
#include "EspMQTT.h" | |||
EspMQTT mqtt; | |||
void mqtt_callback(std::string param, std::string value); | |||
void mqttSetup() { | |||
uint16_t debugLevel = 0; | |||
if (debugLevel) { | |||
mqtt.debugLevel = debugLevel; | |||
mqtt.setAvailabilityInterval(5); | |||
} | |||
// MqTT: | |||
mqtt.setWiFi("wifi-name", "wifi-pass", "hostname"); | |||
mqtt.setMqtt("mqtt-server", "mqtt-user", "mqtt-pass"); | |||
mqtt.setCommonTopics("my/root/topic/dir", "ds18b20"); | |||
// mqtt.setCallback(mqtt_callback); | |||
mqtt.start(true); | |||
} | |||
// Main.cpp Setup. | |||
void setup() { | |||
mqttSetup(); | |||
mqttQueue = xQueueCreate(10, sizeof(mqttMessage)); | |||
xTaskCreate(ds18b20Task, "ds18b20", 1024, NULL, 1, &ds18b20); | |||
} | |||
// Main.cpp Loop. | |||
mqttMessage message; | |||
QueueHandle_t mqttQueue; | |||
void loop() { | |||
if (xQueueReceive(mqttQueue, &message, 100 / portTICK_PERIOD_MS) == pdTRUE) { | |||
mqtt.publishMetric(message.name, message.metric); | |||
// printf("mqtt [%s] push = %s\n", msg.name.c_str(), msg.metric.c_str()); | |||
} | |||
// vTaskDelay(10 / portTICK_PERIOD_MS); | |||
} |
@@ -0,0 +1,26 @@ | |||
{ | |||
"name": "EspMQTT", | |||
"keywords": "esp, mqtt, arduino, esp32, wifi, iot", | |||
"description": "ESP8266 foundation library for MQTT based HomeIOT", | |||
"authors": { | |||
"name": "Anatoly Politsin", | |||
"url": "https://politsin.ru" | |||
}, | |||
"version": "1.0.25", | |||
"frameworks": "arduino", | |||
"platforms": ["espressif32"], | |||
"repository": { | |||
"type": "git", | |||
"url": "https://github.com/politsin/EspMQTT.git" | |||
}, | |||
"dependencies": [ | |||
{ | |||
"name": "AsyncMqttClient", | |||
"version": ">=0.8.0" | |||
}, | |||
{ | |||
"name": "ArduinoJson", | |||
"version": ">=6.13.0" | |||
} | |||
] | |||
} |
@@ -0,0 +1,336 @@ | |||
#include "EspMQTT.h" | |||
#include "EspMQTT_App.h" | |||
#include <Arduino.h> | |||
#include <WiFi.h> | |||
using std::string; | |||
// MQTT. | |||
AsyncMqttClient mqttClient; | |||
// Ticker. | |||
TimerHandle_t mqttReconnectTimer; | |||
TimerHandle_t wifiReconnectTimer; | |||
TimerHandle_t mqttAvailabilityTimer; | |||
// App. | |||
EspApp eapp; | |||
void EspMQTT::setWiFi(string ssid, string pass, string host) { | |||
strcpy(this->WiFiSsid, ssid.c_str()); | |||
strcpy(this->WiFiPass, pass.c_str()); | |||
strcpy(this->WiFiHost, host.c_str()); | |||
}; | |||
void EspMQTT::setMqtt(string server, string user, string pass) { | |||
char device[23]; | |||
uint64_t chipid = ESP.getEfuseMac(); | |||
uint16_t chip = (uint16_t)(chipid >> 32); | |||
snprintf(device, 23, "ESP32-%04X%08X", chip, (uint32_t)chipid); | |||
strcpy(this->mqttDevice, device); | |||
strcpy(this->mqttHost, server.c_str()); | |||
strcpy(this->mqttUser, user.c_str()); | |||
strcpy(this->mqttPass, pass.c_str()); | |||
}; | |||
void EspMQTT::setCommonTopics(string root, string name) { | |||
// Root: /home/[sensor/switch/light]/{name} | |||
string r = root + "/" + name; | |||
strcpy(this->mqttRootTopic, r.c_str()); | |||
// Metrics. | |||
string mRoot = r + string("/metric/"); | |||
strcpy(this->metricRoot, mRoot.c_str()); | |||
// Info. | |||
string availability = r + string("/availability"); | |||
string ip = availability + string("/$ip"); | |||
strcpy(this->availabilityTopic, availability.c_str()); | |||
strcpy(this->ipTopic, ip.c_str()); | |||
// Commands & Data. | |||
string cmd = r + string("/cmd/*"); | |||
string data = r + string("/data"); | |||
string state = r + string("/state"); | |||
string recovery = r + string("/recovery"); | |||
this->cmdTopicLength = cmd.length() - 1; | |||
strcpy(this->cmdTopic, cmd.c_str()); | |||
strcpy(this->dataTopic, data.c_str()); | |||
strcpy(this->stateTopic, state.c_str()); | |||
strcpy(this->recoveryTopic, recovery.c_str()); | |||
}; | |||
void EspMQTT::start(bool init) { | |||
this->initMqtt = init; | |||
if (this->initMqtt) { | |||
this->online = false; | |||
gpio_pad_select_gpio(LED); | |||
gpio_set_direction(LED, GPIO_MODE_OUTPUT); | |||
gpio_set_level(LED, LOW); | |||
if (this->debugLevel >= 2) { | |||
printf("MQTT Start\n"); | |||
} | |||
setupTimers(); | |||
mqttClient.onConnect(onMqttConnect); | |||
mqttClient.onDisconnect(onMqttDisconnect); | |||
mqttClient.onSubscribe(onMqttSubscribe); | |||
mqttClient.onUnsubscribe(onMqttUnsubscribe); | |||
mqttClient.onMessage(onMqttMessage); | |||
mqttClient.onPublish(onMqttPublish); | |||
mqttClient.setClientId(this->mqttDevice); | |||
mqttClient.setServer(this->mqttHost, this->mqttPort); | |||
mqttClient.setCredentials(this->mqttUser, this->mqttPass); | |||
mqttClient.setWill(availabilityTopic, 0, true, "offline"); | |||
// WiFi. | |||
WiFi.mode(WIFI_MODE_STA); | |||
WiFi.onEvent(WiFiEvent); | |||
connectToWifi(); | |||
} | |||
} | |||
void EspMQTT::setupTimers() { | |||
mqttReconnectTimer = | |||
xTimerCreate("mqttTimer", pdMS_TO_TICKS(2000), pdFALSE, (void *)0, | |||
reinterpret_cast<TimerCallbackFunction_t>(connectToMqtt)); | |||
wifiReconnectTimer = | |||
xTimerCreate("wifiTimer", pdMS_TO_TICKS(2000), pdFALSE, (void *)0, | |||
reinterpret_cast<TimerCallbackFunction_t>(connectToWifi)); | |||
mqttAvailabilityTimer = xTimerCreate( | |||
"availabilityTimer", pdMS_TO_TICKS(this->availabilityInterval), pdTRUE, | |||
(void *)0, reinterpret_cast<TimerCallbackFunction_t>(availabilityTime)); | |||
} | |||
void EspMQTT::messageLoop() { | |||
if (length >= 1024) { | |||
this->publishState("$error", "Message is too big"); | |||
return; | |||
} | |||
string param = string(topic).substr(this->cmdTopicLength); | |||
string message = string(payload, length); | |||
if ((char)payload[0] != '{') { | |||
// JSON. Do Nothing. | |||
} | |||
if ((char)param.at(0) == '$') { | |||
eapp.app(param, message); | |||
return; | |||
} | |||
this->callbackFunction(param, message); | |||
if (this->debugLevel) { | |||
printf("MQTT [%s] %s=%s\n", topic, param.c_str(), message.c_str()); | |||
} | |||
} | |||
void EspMQTT::connectToWifi() { | |||
if (mqtt.debugLevel >= 2) { | |||
printf("Connecting to Wi-Fi...\n"); | |||
} | |||
WiFi.begin(mqtt.WiFiSsid, mqtt.WiFiPass); | |||
} | |||
void EspMQTT::WiFiEvent(WiFiEvent_t event) { | |||
if (event == SYSTEM_EVENT_STA_GOT_IP) { | |||
mqtt.onWifiConnect(event); | |||
} | |||
if (event == SYSTEM_EVENT_STA_DISCONNECTED) { | |||
mqtt.onWifiDisconnect(event); | |||
} | |||
} | |||
void EspMQTT::onWifiConnect(WiFiEvent_t event) { | |||
string ip = WiFi.localIP().toString().c_str(); | |||
strcpy(mqtt.ip, ip.c_str()); | |||
if (mqtt.debugLevel >= 2) { | |||
printf("Connected to Wi-Fi. IP: %s\n", mqtt.ip); | |||
} | |||
connectToMqtt(); | |||
} | |||
void EspMQTT::onWifiDisconnect(WiFiEvent_t event) { | |||
mqtt.setOffline(); | |||
xTimerStop(mqttAvailabilityTimer, 0); | |||
xTimerStop(mqttReconnectTimer, 0); | |||
xTimerStart(wifiReconnectTimer, 0); | |||
if (mqtt.debugLevel >= 1) { | |||
printf("Disconnected from Wi-Fi.\n"); | |||
} | |||
} | |||
void EspMQTT::connectToMqtt() { | |||
if (mqtt.debugLevel >= 2) { | |||
printf("Connecting to MQTT...\n"); | |||
} | |||
mqttClient.connect(); | |||
} | |||
void EspMQTT::onMqttConnect(bool sessionPresent) { | |||
mqtt.setOnline(); | |||
gpio_set_level(LED, HIGH); | |||
xTimerStart(mqttAvailabilityTimer, 0); | |||
if (mqtt.debugLevel >= 2) { | |||
printf("Session present: %d\n", sessionPresent); | |||
} | |||
} | |||
void EspMQTT::setOnline() { | |||
this->online = true; | |||
this->publishAvailability(); | |||
this->mqttSubscribe(); | |||
WiFi.setHostname(this->WiFiHost); | |||
if (this->debugLevel >= 1) { | |||
printf("Connected to MQTT.\n"); | |||
printf("Set HostName %s.\n", this->WiFiHost); | |||
this->mqttTests(); | |||
} | |||
} | |||
void EspMQTT::setOffline() { | |||
this->online = false; | |||
gpio_set_level(LED, LOW); | |||
} | |||
void EspMQTT::mqttTests() { | |||
if (this->test) { | |||
string test = string(cmdTopic).substr(0, cmdTopicLength) + string("test"); | |||
const char *topic = test.c_str(); | |||
printf("--== Statr Tests == --\n"); | |||
printf("%s\n", topic); | |||
uint16_t packetIdSub = mqttClient.subscribe(topic, 2); | |||
printf("T0: Subscribing at QoS 2, packetId: %d\n", packetIdSub); | |||
mqttClient.publish(topic, 0, true, "test 1"); | |||
printf("T1: Publishing at QoS 0\n"); | |||
uint16_t packetIdPub1 = mqttClient.publish(topic, 1, true, "test 2"); | |||
printf("Т2: Publishing at QoS 1, packetId: %d\n", packetIdPub1); | |||
uint16_t packetIdPub2 = mqttClient.publish(topic, 2, true, "test 3"); | |||
printf("Т3: Publishing at QoS 2, packetId: %d\n", packetIdPub2); | |||
printf("--== End Tests Inits == --\n"); | |||
} | |||
} | |||
void EspMQTT::setAvailabilityInterval(uint16_t sec, bool onSetup) { | |||
this->availabilityInterval = (uint32_t)sec * 1000; | |||
if (onSetup) { | |||
xTimerStop(mqttAvailabilityTimer, 0); | |||
xTimerChangePeriod(mqttAvailabilityTimer, | |||
pdMS_TO_TICKS(this->availabilityInterval), 0); | |||
} | |||
if (this->online) { | |||
xTimerStart(mqttAvailabilityTimer, 0); | |||
} | |||
if (this->debugLevel >= 2) { | |||
printf("Availability Interval=%d ms\n", availabilityInterval); | |||
} | |||
} | |||
void EspMQTT::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { | |||
mqtt.setOffline(); | |||
if (WiFi.isConnected()) { | |||
xTimerStop(mqttAvailabilityTimer, 0); | |||
xTimerStart(mqttReconnectTimer, 0); | |||
} | |||
if (mqtt.debugLevel >= 1) { | |||
printf("Disconnected from MQTT.\n"); | |||
} | |||
} | |||
void EspMQTT::onMqttSubscribe(uint16_t packetId, uint8_t qos) { | |||
if (mqtt.debugLevel >= 2) { | |||
printf("Subscribe: packetId=%d | QOS=%d\n", packetId, qos); | |||
} | |||
} | |||
void EspMQTT::onMqttUnsubscribe(uint16_t packetId) { | |||
if (mqtt.debugLevel >= 2) { | |||
printf("Unsubscribe: packetId=%d\n", packetId); | |||
} | |||
} | |||
void EspMQTT::onMqttMessage(char *topic, char *payload, | |||
AsyncMqttClientMessageProperties prop, size_t len, | |||
size_t index, size_t total) { | |||
uint8_t pos = mqtt.cmdTopicLength; | |||
if ((char)topic[pos] == '$') { | |||
eapp.appInterrupt(topic, payload, len); | |||
return; | |||
} | |||
mqtt.messageFlag = true; | |||
mqtt.length = len; | |||
mqtt.topic = topic; | |||
mqtt.payload = payload; | |||
if (mqtt.test) { | |||
printf("Publish received.\n"); | |||
printf(" topic: %s\n", topic); | |||
printf(" QoS=%d \t| dup=%d \t| retain=%d\n", prop.qos, prop.dup, | |||
prop.retain); | |||
printf(" len=%d \t| index=%d \t| total=%d\n", len, index, total); | |||
} | |||
} | |||
void EspMQTT::onMqttPublish(uint16_t packetId) { | |||
if (false) { | |||
printf("Publish acknowledged.\n packetId:%i", packetId); | |||
} | |||
} | |||
void EspMQTT::mqttSubscribe() { mqttClient.subscribe(this->cmdTopic, 2); } | |||
void EspMQTT::setCallback( | |||
std::function<void(string param, string value)> cBack) { | |||
this->callbackFunction = cBack; | |||
}; | |||
void EspMQTT::setDebugLevel(uint8_t debugLevel) { | |||
this->debugLevel = debugLevel; | |||
if (this->debugLevel) { | |||
printf("debugLevel > ON %d\n", debugLevel); | |||
} | |||
} | |||
void EspMQTT::availabilityTime() { mqtt.publishAvailability(); } | |||
void EspMQTT::publishAvailability() { | |||
mqttClient.publish(ipTopic, 0, true, this->ip); | |||
mqttClient.publish(availabilityTopic, 0, true, "online"); | |||
if (this->debugLevel >= 1) { | |||
printf("MQTT [Publish Availability] at %s\n", ip); | |||
} | |||
} | |||
void EspMQTT::publishData(string data) { | |||
mqttClient.publish(dataTopic, 0, true, data.c_str()); | |||
} | |||
void EspMQTT::publishState(string key, string value) { | |||
string topic = string(this->stateTopic) + "/" + key; | |||
mqttClient.publish(topic.c_str(), 1, true, value.c_str()); | |||
} | |||
void EspMQTT::publishMetric(string key, string metric) { | |||
string topic = string(this->metricRoot) + key; | |||
mqttClient.publish(topic.c_str(), 0, true, metric.c_str()); | |||
} | |||
void EspMQTT::publishMetric(char *key, uint16_t metric) { | |||
char message[16]; | |||
itoa(metric, message, 10); | |||
char topic[255]; | |||
strcpy(topic, this->metricRoot); | |||
strcat(topic, key); | |||
mqttClient.publish(topic, 0, true, message); | |||
} | |||
void EspMQTT::publishMetric(string key, uint16_t metric) { | |||
char message[16]; | |||
itoa(metric, message, 10); | |||
string topic = string(this->metricRoot) + key; | |||
mqttClient.publish(topic.c_str(), 0, true, message); | |||
} | |||
void EspMQTT::publishMetric(string key, float metric) { | |||
if (metric > 0) { | |||
this->publishMetric(key, metric, false); | |||
} | |||
} | |||
void EspMQTT::publishMetric(string key, float metric, bool force) { | |||
if (metric > 0 || force) { | |||
char message[16]; | |||
itoa(metric, message, 10); | |||
string topic = string(this->metricRoot) + key; | |||
mqttClient.publish(topic.c_str(), 0, true, message); | |||
} | |||
} |
@@ -0,0 +1,113 @@ | |||
#ifndef Mqtt_h | |||
#define Mqtt_h | |||
#include "freertos/FreeRTOS.h" | |||
#include "freertos/timers.h" | |||
#include <AsyncMqttClient.h> | |||
#include <WiFi.h> | |||
#include <functional> | |||
#include <stdio.h> | |||
#include <string> | |||
using std::string; | |||
// TODO: | |||
#define LED GPIO_NUM_22 | |||
class EspMQTT { | |||
public: | |||
bool test = false; | |||
bool online = false; | |||
int8_t debugLevel = 0; | |||
// Flag (TODO). | |||
bool messageFlag = false; | |||
// Set. | |||
char WiFiSsid[255]; | |||
char WiFiPass[255]; | |||
char WiFiHost[255]; | |||
// Config. | |||
char mqttHost[255]; | |||
char mqttUser[255]; | |||
char mqttPass[255]; | |||
char mqttDevice[255]; | |||
const uint16_t mqttPort = 1883; | |||
char ip[255]; | |||
// Topics. | |||
char mqttRootTopic[255]; | |||
char metricRoot[255]; | |||
// Info. | |||
char availabilityTopic[255]; | |||
char ipTopic[255]; | |||
char testTopic[255]; | |||
// Cmd & Data | |||
char cmdTopic[255]; | |||
uint16_t cmdTopicLength = 0; | |||
char stateTopic[255]; | |||
char recoveryTopic[255]; | |||
char dataTopic[255]; | |||
// Message. | |||
size_t length; | |||
char *topic; | |||
char *payload; | |||
void messageLoop(); | |||
void start(bool init = true); | |||
void setupTimers(); | |||
void setDebugLevel(uint8_t debugLevel); | |||
void setOta(bool ota); | |||
// WiFi setters. | |||
void setWiFi(string ssid, string pass, string host); | |||
void setMqtt(string server, string user, string pass); | |||
void setCommonTopics(string root, string name); | |||
// Mqtt setters. | |||
void setstringValue(char *name, char *value); | |||
void addSubsribeTopic(string topic); | |||
// Loop. | |||
void setAvailabilityInterval(uint16_t se, bool onSetup = false); | |||
// Send. | |||
void publishData(string data); | |||
void publishState(string key, string value); | |||
void publishMetric(char *key, uint16_t metric); | |||
void publishMetric(string key, uint16_t metric); | |||
void publishMetric(string key, string metric); | |||
void publishMetric(string key, float metric); | |||
void publishMetric(string key, float metric, bool force); | |||
// Callbacks. | |||
// void callback(char *topic, char *payload, uint16_t length); | |||
void setCallback(std::function<void(string param, string value)> cBack); | |||
// Timers. | |||
// Online. | |||
void mqttTests(); | |||
void setOnline(); | |||
void setOffline(); | |||
void mqttSubscribe(); | |||
void publishAvailability(); | |||
// Async. | |||
static void connectToWifi(); | |||
static void onWifiConnect(WiFiEvent_t even); | |||
static void onWifiDisconnect(WiFiEvent_t even); | |||
static void WiFiEvent(WiFiEvent_t event); | |||
static void connectToMqtt(); | |||
static void availabilityTime(); | |||
static void onMqttConnect(bool sessionPresent); | |||
static void onMqttDisconnect(AsyncMqttClientDisconnectReason reason); | |||
static void onMqttSubscribe(uint16_t packetId, uint8_t qos); | |||
static void onMqttUnsubscribe(uint16_t packetId); | |||
static void onMqttMessage(char *topic, char *payload, | |||
AsyncMqttClientMessageProperties prop, size_t len, | |||
size_t index, size_t total); | |||
static void onMqttPublish(uint16_t packetId); | |||
private: | |||
bool initMqtt = true; | |||
uint32_t availabilityInterval = 30000; | |||
void setup(); | |||
void subsribe(); | |||
void callbackParceJson(string message); | |||
std::function<void(string param, string value)> callbackFunction; | |||
}; | |||
extern EspMQTT mqtt; | |||
#endif /* !Mqtt_h */ |
@@ -0,0 +1,58 @@ | |||
#include <Arduino.h> | |||
#include "EspMQTT_App.h" | |||
using std::string; | |||
bool EspApp::app(string param, string message) { | |||
if (param == "$echo") { | |||
mqtt.publishState(this->messageState, message); | |||
return true; | |||
} | |||
if (param == "$update") { | |||
mqtt.publishState(this->errorState, this->notReady); | |||
return true; | |||
} | |||
if (param == "$pinRead") { | |||
mqtt.publishState(this->errorState, this->notReady); | |||
return true; | |||
} | |||
if (param == "$pinReadAnalog") { | |||
mqtt.publishState(this->errorState, this->notReady); | |||
return true; | |||
} | |||
if (param == "$pinSet") { | |||
mqtt.publishState(this->errorState, this->notReady); | |||
return true; | |||
} | |||
if (param == "$pinSetPwm") { | |||
mqtt.publishState(this->errorState, this->notReady); | |||
return true; | |||
} | |||
if (this->debug) { | |||
Serial.printf("MQTT-APP %s=%s\n", param.c_str(), message.c_str()); | |||
} | |||
return false; | |||
} | |||
bool EspApp::appInterrupt(char* topic, char* payload, size_t length) { | |||
if (length >= 1024) { | |||
return false; | |||
} | |||
char param[255]; | |||
memcpy(param, &topic[mqtt.cmdTopicLength], strlen(topic)); | |||
if (this->compareStr(param, (char*) "$setInterval")) { | |||
int number = std::atoi(payload); | |||
if (number > 0) { | |||
uint16_t sec = static_cast<uint16_t>(number); | |||
mqtt.setAvailabilityInterval(sec); | |||
return true; | |||
} | |||
return false; | |||
} | |||
if (this->compareStr(param, (char*) "$debugInterrupt")) { | |||
string message = string(payload, length); | |||
Serial.printf("MQTT-Interrupt %s=%s\n", param, message.c_str()); | |||
mqtt.publishState(this->messageState, message); | |||
return true; | |||
} | |||
return false; | |||
} |
@@ -0,0 +1,29 @@ | |||
#ifndef Mqtt_App_h | |||
#define Mqtt_App_h | |||
#include "EspMQTT.h" | |||
#include <string> | |||
using std::string; | |||
class EspApp { | |||
public: | |||
bool debug = false; | |||
bool app(string param, string message); | |||
bool appInterrupt(char* topic, char* payload, size_t length); | |||
private: | |||
string messageState = string("$message"); | |||
string errorState = string("$error"); | |||
string notReady = "Not ready yet"; | |||
bool compareStr(char a[],char b[]){ | |||
for (int i = 0; a[i] != '\0'; i++) { | |||
if (a[i] != b[i]) { | |||
return 0; | |||
} | |||
} | |||
return 1; | |||
} | |||
}; | |||
extern EspApp eapp; | |||
#endif /* !Mqtt_h */ |
@@ -19,3 +19,4 @@ lib_deps = | |||
knolleary/PubSubClient@^2.8 | |||
einararnason/ArduinoQueue@^1.2.5 | |||
256dpi/MQTT@^2.5.0 | |||
politsin/EspMQTT@^1.0.25 |