mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 00:09:51 +03:00
use espMqttClient, qos2 fixed
This commit is contained in:
58
lib/espMqttClient/src/Config.h
Normal file
58
lib/espMqttClient/src/Config.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef EMC_TX_TIMEOUT
|
||||
#define EMC_TX_TIMEOUT 5000
|
||||
#endif
|
||||
|
||||
#ifndef EMC_RX_BUFFER_SIZE
|
||||
#define EMC_RX_BUFFER_SIZE 1440
|
||||
#endif
|
||||
|
||||
#ifndef EMC_TX_BUFFER_SIZE
|
||||
#define EMC_TX_BUFFER_SIZE 1440
|
||||
#endif
|
||||
|
||||
#ifndef EMC_MAX_TOPIC_LENGTH
|
||||
#define EMC_MAX_TOPIC_LENGTH 128
|
||||
#endif
|
||||
|
||||
#ifndef EMC_PAYLOAD_BUFFER_SIZE
|
||||
#define EMC_PAYLOAD_BUFFER_SIZE 32
|
||||
#endif
|
||||
|
||||
#ifndef EMC_MIN_FREE_MEMORY
|
||||
#define EMC_MIN_FREE_MEMORY 4096
|
||||
#endif
|
||||
|
||||
#ifndef EMC_ESP8266_MULTITHREADING
|
||||
#define EMC_ESP8266_MULTITHREADING 0
|
||||
#endif
|
||||
|
||||
#ifndef EMC_ALLOW_NOT_CONNECTED_PUBLISH
|
||||
#define EMC_ALLOW_NOT_CONNECTED_PUBLISH 1
|
||||
#endif
|
||||
|
||||
#ifndef EMC_WAIT_FOR_CONNACK
|
||||
#define EMC_WAIT_FOR_CONNACK 1
|
||||
#endif
|
||||
|
||||
#ifndef EMC_CLIENTID_LENGTH
|
||||
// esp8266abc123 and esp32abcdef123456
|
||||
#define EMC_CLIENTID_LENGTH 23 + 1
|
||||
#endif
|
||||
|
||||
#ifndef EMC_TASK_STACK_SIZE
|
||||
#define EMC_TASK_STACK_SIZE 5120
|
||||
#endif
|
||||
|
||||
#ifndef EMC_USE_WATCHDOG
|
||||
#define EMC_USE_WATCHDOG 0
|
||||
#endif
|
||||
49
lib/espMqttClient/src/Helpers.h
Normal file
49
lib/espMqttClient/src/Helpers.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include <Arduino.h> // millis(), ESP.getFreeHeap();
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#define EMC_SEMAPHORE_TAKE() xSemaphoreTake(_xSemaphore, portMAX_DELAY)
|
||||
#define EMC_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore)
|
||||
#define EMC_GET_FREE_MEMORY() std::max(ESP.getMaxAllocHeap(), ESP.getMaxAllocPsram())
|
||||
#define EMC_YIELD() taskYIELD()
|
||||
#define EMC_GENERATE_CLIENTID(x) snprintf(x, EMC_CLIENTID_LENGTH, "esp32%06llx", ESP.getEfuseMac());
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
#include <Arduino.h> // millis(), ESP.getFreeHeap();
|
||||
#if EMC_ESP8266_MULTITHREADING
|
||||
// This lib doesn't run use multithreading on ESP8266
|
||||
// _xSemaphore defined as std::atomic<bool>
|
||||
#define EMC_SEMAPHORE_TAKE() while (_xSemaphore) { /*ESP.wdtFeed();*/ } _xSemaphore = true
|
||||
#define EMC_SEMAPHORE_GIVE() _xSemaphore = false
|
||||
#else
|
||||
#define EMC_SEMAPHORE_TAKE()
|
||||
#define EMC_SEMAPHORE_GIVE()
|
||||
#endif
|
||||
#define EMC_GET_FREE_MEMORY() ESP.getMaxFreeBlockSize()
|
||||
// no need to yield for ESP8266, the Arduino framework does this internally
|
||||
// yielding in async is forbidden (will crash)
|
||||
#define EMC_YIELD()
|
||||
#define EMC_GENERATE_CLIENTID(x) snprintf(x, EMC_CLIENTID_LENGTH, "esp8266%06x", ESP.getChipId());
|
||||
#elif defined(__linux__)
|
||||
#include <chrono> // NOLINT [build/c++11]
|
||||
#include <thread> // NOLINT [build/c++11] for yield()
|
||||
#define millis() std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count()
|
||||
#define EMC_GET_FREE_MEMORY() 1000000000
|
||||
#define EMC_YIELD() std::this_thread::yield()
|
||||
#define EMC_GENERATE_CLIENTID(x) snprintf(x, EMC_CLIENTID_LENGTH, "Client%04d%04d%04d", rand()%10000, rand()%10000, rand()%10000)
|
||||
#include <mutex> // NOLINT [build/c++11]
|
||||
#define EMC_SEMAPHORE_TAKE() mtx.lock();
|
||||
#define EMC_SEMAPHORE_GIVE() mtx.unlock();
|
||||
#else
|
||||
#error Target platform not supported
|
||||
#endif
|
||||
43
lib/espMqttClient/src/Logging.h
Normal file
43
lib/espMqttClient/src/Logging.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include <esp32-hal-log.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#if defined(DEBUG_ESP_MQTT_CLIENT)
|
||||
// Logging is en/disabled by Arduino framework macros
|
||||
#define emc_log_i(...) log_i(__VA_ARGS__)
|
||||
#define emc_log_e(...) log_e(__VA_ARGS__)
|
||||
#define emc_log_w(...) log_w(__VA_ARGS__)
|
||||
#else
|
||||
// Logging is disabled
|
||||
#define emc_log_i(...)
|
||||
#define emc_log_e(...)
|
||||
#define emc_log_w(...)
|
||||
#endif
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
#if defined(DEBUG_ESP_PORT) && defined(DEBUG_ESP_MQTT_CLIENT)
|
||||
#include <Arduino.h>
|
||||
#define emc_log_i(...) DEBUG_ESP_PORT.printf(__VA_ARGS__); DEBUG_ESP_PORT.print("\n")
|
||||
#define emc_log_e(...) DEBUG_ESP_PORT.printf(__VA_ARGS__); DEBUG_ESP_PORT.print("\n")
|
||||
#define emc_log_w(...) DEBUG_ESP_PORT.printf(__VA_ARGS__); DEBUG_ESP_PORT.print("\n")
|
||||
#else
|
||||
#define emc_log_i(...)
|
||||
#define emc_log_e(...)
|
||||
#define emc_log_w(...)
|
||||
#endif
|
||||
#else
|
||||
// when building for PC, always show debug statements as part of testing suite
|
||||
#include <iostream>
|
||||
#define emc_log_i(...) std::cout << "[I] " << __FILE__ ":" << __LINE__ << ": "; printf(__VA_ARGS__); std::cout << std::endl
|
||||
#define emc_log_e(...) std::cout << "[E] " << __FILE__ ":" << __LINE__ << ": "; printf(__VA_ARGS__); std::cout << std::endl
|
||||
#define emc_log_w(...) std::cout << "[W] " << __FILE__ ":" << __LINE__ << ": "; printf(__VA_ARGS__); std::cout << std::endl
|
||||
#endif
|
||||
754
lib/espMqttClient/src/MqttClient.cpp
Normal file
754
lib/espMqttClient/src/MqttClient.cpp
Normal file
@@ -0,0 +1,754 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "MqttClient.h"
|
||||
|
||||
using espMqttClientInternals::Packet;
|
||||
using espMqttClientInternals::PacketType;
|
||||
using espMqttClientTypes::DisconnectReason;
|
||||
using espMqttClientTypes::Error;
|
||||
|
||||
MqttClient::MqttClient(espMqttClientTypes::UseInternalTask useInternalTask, uint8_t priority, uint8_t core)
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
: _useInternalTask(useInternalTask)
|
||||
, _transport(nullptr)
|
||||
#else
|
||||
: _transport(nullptr)
|
||||
#endif
|
||||
, _onConnectCallback(nullptr)
|
||||
, _onDisconnectCallback(nullptr)
|
||||
, _onSubscribeCallback(nullptr)
|
||||
, _onUnsubscribeCallback(nullptr)
|
||||
, _onMessageCallback(nullptr)
|
||||
, _onPublishCallback(nullptr)
|
||||
, _onErrorCallback(nullptr)
|
||||
, _clientId(nullptr)
|
||||
, _ip()
|
||||
, _host(nullptr)
|
||||
, _port(1883)
|
||||
, _useIp(false)
|
||||
, _keepAlive(15000)
|
||||
, _cleanSession(true)
|
||||
, _username(nullptr)
|
||||
, _password(nullptr)
|
||||
, _willTopic(nullptr)
|
||||
, _willPayload(nullptr)
|
||||
, _willPayloadLength(0)
|
||||
, _willQos(0)
|
||||
, _willRetain(false)
|
||||
, _timeout(EMC_TX_TIMEOUT)
|
||||
, _state(State::disconnected)
|
||||
, _generatedClientId{0}
|
||||
, _packetId(0)
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
, _xSemaphore(nullptr)
|
||||
, _taskHandle(nullptr)
|
||||
#endif
|
||||
, _rxBuffer{0}
|
||||
, _outbox()
|
||||
, _bytesSent(0)
|
||||
, _parser()
|
||||
, _lastClientActivity(0)
|
||||
, _lastServerActivity(0)
|
||||
, _pingSent(false)
|
||||
, _disconnectReason(DisconnectReason::TCP_DISCONNECTED)
|
||||
#if defined(ARDUINO_ARCH_ESP32) && ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO
|
||||
, _highWaterMark(4294967295)
|
||||
#endif
|
||||
{
|
||||
EMC_GENERATE_CLIENTID(_generatedClientId);
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
_xSemaphore = xSemaphoreCreateMutex();
|
||||
EMC_SEMAPHORE_GIVE(); // release before first use
|
||||
if (_useInternalTask == espMqttClientTypes::UseInternalTask::YES) {
|
||||
xTaskCreatePinnedToCore((TaskFunction_t)_loop, "mqttclient", EMC_TASK_STACK_SIZE, this, priority, &_taskHandle, core);
|
||||
}
|
||||
#else
|
||||
(void)useInternalTask;
|
||||
(void)priority;
|
||||
(void)core;
|
||||
#endif
|
||||
_clientId = _generatedClientId;
|
||||
}
|
||||
|
||||
MqttClient::~MqttClient() {
|
||||
disconnect(true);
|
||||
_clearQueue(2);
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
vSemaphoreDelete(_xSemaphore);
|
||||
if (_useInternalTask == espMqttClientTypes::UseInternalTask::YES) {
|
||||
#if EMC_USE_WATCHDOG
|
||||
esp_task_wdt_delete(_taskHandle); // not sure if this is really needed
|
||||
#endif
|
||||
vTaskDelete(_taskHandle);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MqttClient::connected() const {
|
||||
if (_state == State::connected)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MqttClient::disconnected() const {
|
||||
if (_state == State::disconnected)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MqttClient::connect() {
|
||||
bool result = true;
|
||||
if (_state == State::disconnected) {
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
if (_addPacketFront(_cleanSession,
|
||||
_username,
|
||||
_password,
|
||||
_willTopic,
|
||||
_willRetain,
|
||||
_willQos,
|
||||
_willPayload,
|
||||
_willPayloadLength,
|
||||
(uint16_t)(_keepAlive / 1000), // 32b to 16b doesn't overflow because it comes from 16b orignally
|
||||
_clientId)) {
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
if (_useInternalTask == espMqttClientTypes::UseInternalTask::YES) {
|
||||
vTaskResume(_taskHandle);
|
||||
}
|
||||
#endif
|
||||
_state = State::connectingTcp1;
|
||||
} else {
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
emc_log_e("Could not create CONNECT packet");
|
||||
_onError(0, Error::OUT_OF_MEMORY);
|
||||
result = false;
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MqttClient::disconnect(bool force) {
|
||||
if (force && _state != State::disconnected && _state != State::disconnectingTcp1 && _state != State::disconnectingTcp2) {
|
||||
_state = State::disconnectingTcp1;
|
||||
return true;
|
||||
}
|
||||
if (!force && _state == State::connected) {
|
||||
_state = State::disconnectingMqtt1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t MqttClient::publish(const char * topic, uint8_t qos, bool retain, const uint8_t * payload, size_t length) {
|
||||
#if !EMC_ALLOW_NOT_CONNECTED_PUBLISH
|
||||
if (_state != State::connected) {
|
||||
#else
|
||||
if (_state > State::connected) {
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
uint16_t packetId = (qos > 0) ? _getNextPacketId() : 1;
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
if (!_addPacket(packetId, topic, payload, length, qos, retain)) {
|
||||
emc_log_e("Could not create PUBLISH packet");
|
||||
_onError(packetId, Error::OUT_OF_MEMORY);
|
||||
packetId = 0;
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
return packetId;
|
||||
}
|
||||
|
||||
uint16_t MqttClient::publish(const char * topic, uint8_t qos, bool retain, const char * payload) {
|
||||
size_t len = strlen(payload);
|
||||
return publish(topic, qos, retain, reinterpret_cast<const uint8_t *>(payload), len);
|
||||
}
|
||||
|
||||
uint16_t MqttClient::publish(const char * topic, uint8_t qos, bool retain, espMqttClientTypes::PayloadCallback callback, size_t length) {
|
||||
#if !EMC_ALLOW_NOT_CONNECTED_PUBLISH
|
||||
if (_state != State::connected) {
|
||||
#else
|
||||
if (_state > State::connected) {
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
uint16_t packetId = (qos > 0) ? _getNextPacketId() : 1;
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
if (!_addPacket(packetId, topic, callback, length, qos, retain)) {
|
||||
emc_log_e("Could not create PUBLISH packet");
|
||||
_onError(packetId, Error::OUT_OF_MEMORY);
|
||||
packetId = 0;
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
return packetId;
|
||||
}
|
||||
|
||||
void MqttClient::clearQueue(bool deleteSessionData) {
|
||||
_clearQueue(deleteSessionData ? 2 : 0);
|
||||
}
|
||||
|
||||
const char * MqttClient::getClientId() const {
|
||||
return _clientId;
|
||||
}
|
||||
|
||||
void MqttClient::loop() {
|
||||
switch (_state) {
|
||||
case State::disconnected:
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
if (_useInternalTask == espMqttClientTypes::UseInternalTask::YES) {
|
||||
vTaskSuspend(_taskHandle);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case State::connectingTcp1:
|
||||
if (_useIp ? _transport->connect(_ip, _port) : _transport->connect(_host, _port)) {
|
||||
_state = State::connectingTcp2;
|
||||
} else {
|
||||
_state = State::disconnectingTcp1;
|
||||
_disconnectReason = DisconnectReason::TCP_DISCONNECTED;
|
||||
break;
|
||||
}
|
||||
// Falling through to speed up connecting on blocking transport 'connect' implementations
|
||||
[[fallthrough]];
|
||||
case State::connectingTcp2:
|
||||
if (_transport->connected()) {
|
||||
_parser.reset();
|
||||
_lastClientActivity = _lastServerActivity = millis();
|
||||
_state = State::connectingMqtt;
|
||||
}
|
||||
break;
|
||||
case State::connectingMqtt:
|
||||
#if EMC_WAIT_FOR_CONNACK
|
||||
if (_transport->connected()) {
|
||||
_sendPacket();
|
||||
_checkIncoming();
|
||||
_checkPing();
|
||||
} else {
|
||||
_state = State::disconnectingTcp1;
|
||||
_disconnectReason = DisconnectReason::TCP_DISCONNECTED;
|
||||
}
|
||||
break;
|
||||
#else
|
||||
// receipt of CONNACK packet will set state to CONNECTED
|
||||
// client however is allowed to send packets before CONNACK is received
|
||||
// so we fall through to 'connected'
|
||||
[[fallthrough]];
|
||||
#endif
|
||||
case State::connected:
|
||||
[[fallthrough]];
|
||||
case State::disconnectingMqtt2:
|
||||
if (_transport->connected()) {
|
||||
// CONNECT packet is first in the queue
|
||||
_checkOutbox();
|
||||
_checkIncoming();
|
||||
_checkPing();
|
||||
_checkTimeout();
|
||||
} else {
|
||||
_state = State::disconnectingTcp1;
|
||||
_disconnectReason = DisconnectReason::TCP_DISCONNECTED;
|
||||
}
|
||||
break;
|
||||
case State::disconnectingMqtt1:
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
if (_outbox.empty()) {
|
||||
if (!_addPacket(PacketType.DISCONNECT)) {
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
emc_log_e("Could not create DISCONNECT packet");
|
||||
_onError(0, Error::OUT_OF_MEMORY);
|
||||
} else {
|
||||
_state = State::disconnectingMqtt2;
|
||||
}
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
_checkOutbox();
|
||||
_checkIncoming();
|
||||
_checkPing();
|
||||
_checkTimeout();
|
||||
break;
|
||||
case State::disconnectingTcp1:
|
||||
_transport->stop();
|
||||
_state = State::disconnectingTcp2;
|
||||
break; // keep break to accomodate async clients
|
||||
case State::disconnectingTcp2:
|
||||
if (_transport->disconnected()) {
|
||||
_clearQueue(0);
|
||||
_bytesSent = 0;
|
||||
_state = State::disconnected;
|
||||
if (_onDisconnectCallback)
|
||||
_onDisconnectCallback(_disconnectReason);
|
||||
}
|
||||
break;
|
||||
// all cases covered, no default case
|
||||
}
|
||||
EMC_YIELD();
|
||||
#if defined(ARDUINO_ARCH_ESP32) && ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO
|
||||
size_t waterMark = uxTaskGetStackHighWaterMark(NULL);
|
||||
if (waterMark < _highWaterMark) {
|
||||
_highWaterMark = waterMark;
|
||||
emc_log_i("Stack usage: %zu/%i", EMC_TASK_STACK_SIZE - _highWaterMark, EMC_TASK_STACK_SIZE);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
void MqttClient::_loop(MqttClient * c) {
|
||||
#if EMC_USE_WATCHDOG
|
||||
if (esp_task_wdt_add(NULL) != ESP_OK) {
|
||||
emc_log_e("Failed to add async task to WDT");
|
||||
}
|
||||
#endif
|
||||
for (;;) {
|
||||
c->loop();
|
||||
#if EMC_USE_WATCHDOG
|
||||
esp_task_wdt_reset();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
uint16_t MqttClient::_getNextPacketId() {
|
||||
uint16_t packetId = 0;
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
packetId = (++_packetId == 0) ? ++_packetId : _packetId;
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
return packetId;
|
||||
}
|
||||
|
||||
void MqttClient::_checkOutbox() {
|
||||
while (_sendPacket() > 0) {
|
||||
if (!_advanceOutbox()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int MqttClient::_sendPacket() {
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
OutgoingPacket * packet = _outbox.getCurrent();
|
||||
|
||||
int32_t wantToWrite = 0;
|
||||
int32_t written = 0;
|
||||
if (packet && (wantToWrite == written)) {
|
||||
// mixing signed with unsigned here but safe because of MQTT packet size limits
|
||||
wantToWrite = packet->packet.available(_bytesSent);
|
||||
if (wantToWrite == 0) {
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
return 0;
|
||||
}
|
||||
written = _transport->write(packet->packet.data(_bytesSent), wantToWrite);
|
||||
if (written < 0) {
|
||||
emc_log_w("Write error, check connection");
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
return -1;
|
||||
}
|
||||
packet->timeSent = millis();
|
||||
_lastClientActivity = millis();
|
||||
_bytesSent += written;
|
||||
emc_log_i("tx %zu/%zu (%02x)", _bytesSent, packet->packet.size(), packet->packet.packetType());
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
return written;
|
||||
}
|
||||
|
||||
bool MqttClient::_advanceOutbox() {
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
OutgoingPacket * packet = _outbox.getCurrent();
|
||||
if (packet && _bytesSent == packet->packet.size()) {
|
||||
if ((packet->packet.packetType()) == PacketType.DISCONNECT) {
|
||||
_state = State::disconnectingTcp1;
|
||||
_disconnectReason = DisconnectReason::USER_OK;
|
||||
}
|
||||
if (packet->packet.removable()) {
|
||||
_outbox.removeCurrent();
|
||||
} else {
|
||||
// we already set 'dup' here, in case we have to retry
|
||||
if ((packet->packet.packetType()) == PacketType.PUBLISH)
|
||||
packet->packet.setDup();
|
||||
_outbox.next();
|
||||
}
|
||||
packet = _outbox.getCurrent();
|
||||
_bytesSent = 0;
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
return packet;
|
||||
}
|
||||
|
||||
void MqttClient::_checkIncoming() {
|
||||
int32_t remainingBufferLength = _transport->read(_rxBuffer, EMC_RX_BUFFER_SIZE);
|
||||
if (remainingBufferLength > 0) {
|
||||
_lastServerActivity = millis();
|
||||
emc_log_i("rx len %i", remainingBufferLength);
|
||||
size_t bytesParsed = 0;
|
||||
size_t index = 0;
|
||||
while (remainingBufferLength > 0) {
|
||||
espMqttClientInternals::ParserResult result = _parser.parse(&_rxBuffer[index], remainingBufferLength, &bytesParsed);
|
||||
if (result == espMqttClientInternals::ParserResult::packet) {
|
||||
espMqttClientInternals::MQTTPacketType packetType = _parser.getPacket().fixedHeader.packetType & 0xF0;
|
||||
if (_state == State::connectingMqtt && packetType != PacketType.CONNACK) {
|
||||
emc_log_w("Disconnecting, expected CONNACK - protocol error");
|
||||
_state = State::disconnectingTcp1;
|
||||
return;
|
||||
}
|
||||
switch (packetType & 0xF0) {
|
||||
case PacketType.CONNACK:
|
||||
_onConnack();
|
||||
if (_state != State::connected) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case PacketType.PUBLISH:
|
||||
if (_state >= State::disconnectingMqtt1)
|
||||
break; // stop processing incoming once user has called disconnect
|
||||
_onPublish();
|
||||
break;
|
||||
case PacketType.PUBACK:
|
||||
_onPuback();
|
||||
break;
|
||||
case PacketType.PUBREC:
|
||||
_onPubrec();
|
||||
break;
|
||||
case PacketType.PUBREL:
|
||||
_onPubrel();
|
||||
break;
|
||||
case PacketType.PUBCOMP:
|
||||
_onPubcomp();
|
||||
break;
|
||||
case PacketType.SUBACK:
|
||||
_onSuback();
|
||||
break;
|
||||
case PacketType.UNSUBACK:
|
||||
_onUnsuback();
|
||||
break;
|
||||
case PacketType.PINGRESP:
|
||||
_pingSent = false;
|
||||
break;
|
||||
}
|
||||
} else if (result == espMqttClientInternals::ParserResult::protocolError) {
|
||||
emc_log_w("Disconnecting, protocol error");
|
||||
_state = State::disconnectingTcp1;
|
||||
_disconnectReason = DisconnectReason::TCP_DISCONNECTED;
|
||||
return;
|
||||
}
|
||||
remainingBufferLength -= bytesParsed;
|
||||
index += bytesParsed;
|
||||
emc_log_i("Parsed %zu - remaining %i", bytesParsed, remainingBufferLength);
|
||||
bytesParsed = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MqttClient::_checkPing() {
|
||||
if (_keepAlive == 0)
|
||||
return; // keepalive is disabled
|
||||
|
||||
uint32_t currentMillis = millis();
|
||||
|
||||
// disconnect when server was inactive for twice the keepalive time
|
||||
if (currentMillis - _lastServerActivity > 2 * _keepAlive) {
|
||||
emc_log_w("Disconnecting, server exceeded keepalive");
|
||||
_state = State::disconnectingTcp1;
|
||||
_disconnectReason = DisconnectReason::TCP_DISCONNECTED;
|
||||
return;
|
||||
}
|
||||
|
||||
// send ping when client was inactive during the keepalive time
|
||||
// or when server hasn't responded within keepalive time (typically due to QOS 0)
|
||||
if (!_pingSent && ((currentMillis - _lastClientActivity > _keepAlive) || (currentMillis - _lastServerActivity > _keepAlive))) {
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
if (!_addPacket(PacketType.PINGREQ)) {
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
emc_log_e("Could not create PING packet");
|
||||
return;
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
_pingSent = true;
|
||||
}
|
||||
}
|
||||
|
||||
void MqttClient::_checkTimeout() {
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
espMqttClientInternals::Outbox<OutgoingPacket>::Iterator it = _outbox.front();
|
||||
// check that we're not busy sending
|
||||
// don't check when first item hasn't been sent yet
|
||||
if (it && _bytesSent == 0 && it.get() != _outbox.getCurrent()) {
|
||||
if (millis() - it.get()->timeSent > _timeout) {
|
||||
emc_log_w("Packet ack timeout, retrying");
|
||||
_outbox.resetCurrent();
|
||||
}
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
}
|
||||
|
||||
void MqttClient::_onConnack() {
|
||||
if (_parser.getPacket().variableHeader.fixed.connackVarHeader.returnCode == 0x00) {
|
||||
_pingSent = false; // reset after keepalive timeout disconnect
|
||||
_state = State::connected;
|
||||
_advanceOutbox();
|
||||
if (_parser.getPacket().variableHeader.fixed.connackVarHeader.sessionPresent == 0) {
|
||||
_clearQueue(1);
|
||||
}
|
||||
if (_onConnectCallback) {
|
||||
_onConnectCallback(_parser.getPacket().variableHeader.fixed.connackVarHeader.sessionPresent);
|
||||
}
|
||||
} else {
|
||||
_state = State::disconnectingTcp1;
|
||||
// cast is safe because the parser already checked for a valid return code
|
||||
_disconnectReason = static_cast<DisconnectReason>(_parser.getPacket().variableHeader.fixed.connackVarHeader.returnCode);
|
||||
}
|
||||
}
|
||||
|
||||
void MqttClient::_onPublish() {
|
||||
espMqttClientInternals::IncomingPacket p = _parser.getPacket();
|
||||
uint8_t qos = p.qos();
|
||||
bool retain = p.retain();
|
||||
bool dup = p.dup();
|
||||
uint16_t packetId = p.variableHeader.fixed.packetId;
|
||||
bool callback = true;
|
||||
if (qos == 1) {
|
||||
if (p.payload.index + p.payload.length == p.payload.total) {
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
if (!_addPacket(PacketType.PUBACK, packetId)) {
|
||||
emc_log_e("Could not create PUBACK packet");
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
}
|
||||
} else if (qos == 2) {
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
espMqttClientInternals::Outbox<OutgoingPacket>::Iterator it = _outbox.front();
|
||||
while (it) {
|
||||
if ((it.get()->packet.packetType()) == PacketType.PUBREC && it.get()->packet.packetId() == packetId) {
|
||||
callback = false;
|
||||
_outbox.remove(it);
|
||||
emc_log_e("QoS2 packet previously delivered");
|
||||
break;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
if (p.payload.index + p.payload.length == p.payload.total) {
|
||||
if (!_addPacket(PacketType.PUBREC, packetId)) {
|
||||
emc_log_e("Could not create PUBREC packet");
|
||||
}
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
}
|
||||
if (callback && _onMessageCallback)
|
||||
_onMessageCallback({qos, dup, retain, packetId}, p.variableHeader.topic, p.payload.data, p.payload.length, p.payload.index, p.payload.total);
|
||||
}
|
||||
|
||||
void MqttClient::_onPuback() {
|
||||
bool callback = false;
|
||||
uint16_t idToMatch = _parser.getPacket().variableHeader.fixed.packetId;
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
espMqttClientInternals::Outbox<OutgoingPacket>::Iterator it = _outbox.front();
|
||||
while (it) {
|
||||
// PUBACKs come in the order PUBs are sent. So we only check the first PUB packet in outbox
|
||||
// if it doesn't match the ID, return
|
||||
if ((it.get()->packet.packetType()) == PacketType.PUBLISH) {
|
||||
if (it.get()->packet.packetId() == idToMatch) {
|
||||
callback = true;
|
||||
_outbox.remove(it);
|
||||
break;
|
||||
}
|
||||
emc_log_w("Received out of order PUBACK");
|
||||
break;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
if (callback) {
|
||||
if (_onPublishCallback)
|
||||
_onPublishCallback(idToMatch);
|
||||
} else {
|
||||
emc_log_w("No matching PUBLISH packet found");
|
||||
}
|
||||
}
|
||||
|
||||
void MqttClient::_onPubrec() {
|
||||
bool success = false;
|
||||
uint16_t idToMatch = _parser.getPacket().variableHeader.fixed.packetId;
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
espMqttClientInternals::Outbox<OutgoingPacket>::Iterator it = _outbox.front();
|
||||
while (it) {
|
||||
// PUBRECs come in the order PUBs are sent. So we only check the first PUB packet in outbox
|
||||
// if it doesn't match the ID, return
|
||||
if ((it.get()->packet.packetType()) == PacketType.PUBLISH) {
|
||||
if (it.get()->packet.packetId() == idToMatch) {
|
||||
if (!_addPacket(PacketType.PUBREL, idToMatch)) {
|
||||
emc_log_e("Could not create PUBREL packet");
|
||||
}
|
||||
_outbox.remove(it);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
emc_log_w("Received out of order PUBREC");
|
||||
break;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
if (!success) {
|
||||
emc_log_w("No matching PUBLISH packet found");
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
}
|
||||
|
||||
void MqttClient::_onPubrel() {
|
||||
bool success = false;
|
||||
uint16_t idToMatch = _parser.getPacket().variableHeader.fixed.packetId;
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
espMqttClientInternals::Outbox<OutgoingPacket>::Iterator it = _outbox.front();
|
||||
while (it) {
|
||||
// PUBRELs come in the order PUBRECs are sent. So we only check the first PUBREC packet in outbox
|
||||
// if it doesn't match the ID, return
|
||||
if ((it.get()->packet.packetType()) == PacketType.PUBREC) {
|
||||
if (it.get()->packet.packetId() == idToMatch) {
|
||||
if (!_addPacket(PacketType.PUBCOMP, idToMatch)) {
|
||||
emc_log_e("Could not create PUBCOMP packet");
|
||||
}
|
||||
_outbox.remove(it);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
emc_log_w("Received out of order PUBREL");
|
||||
break;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
if (!success) {
|
||||
emc_log_w("No matching PUBREC packet found");
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
}
|
||||
|
||||
void MqttClient::_onPubcomp() {
|
||||
bool callback = false;
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
espMqttClientInternals::Outbox<OutgoingPacket>::Iterator it = _outbox.front();
|
||||
uint16_t idToMatch = _parser.getPacket().variableHeader.fixed.packetId;
|
||||
while (it) {
|
||||
// PUBCOMPs come in the order PUBRELs are sent. So we only check the first PUBREL packet in outbox
|
||||
// if it doesn't match the ID, return
|
||||
if ((it.get()->packet.packetType()) == PacketType.PUBREL) {
|
||||
if (it.get()->packet.packetId() == idToMatch) {
|
||||
// if (!_addPacket(PacketType.PUBCOMP, idToMatch)) {
|
||||
// emc_log_e("Could not create PUBCOMP packet");
|
||||
// }
|
||||
callback = true;
|
||||
_outbox.remove(it);
|
||||
break;
|
||||
}
|
||||
emc_log_w("Received out of order PUBCOMP");
|
||||
break;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
if (callback) {
|
||||
if (_onPublishCallback)
|
||||
_onPublishCallback(idToMatch);
|
||||
} else {
|
||||
emc_log_w("No matching PUBREL packet found");
|
||||
}
|
||||
}
|
||||
|
||||
void MqttClient::_onSuback() {
|
||||
bool callback = false;
|
||||
uint16_t idToMatch = _parser.getPacket().variableHeader.fixed.packetId;
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
espMqttClientInternals::Outbox<OutgoingPacket>::Iterator it = _outbox.front();
|
||||
while (it) {
|
||||
if (((it.get()->packet.packetType()) == PacketType.SUBSCRIBE) && it.get()->packet.packetId() == idToMatch) {
|
||||
callback = true;
|
||||
_outbox.remove(it);
|
||||
break;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
if (callback) {
|
||||
if (_onSubscribeCallback)
|
||||
_onSubscribeCallback(idToMatch,
|
||||
reinterpret_cast<const espMqttClientTypes::SubscribeReturncode *>(_parser.getPacket().payload.data),
|
||||
_parser.getPacket().payload.total);
|
||||
} else {
|
||||
emc_log_w("received SUBACK without SUB");
|
||||
}
|
||||
}
|
||||
|
||||
void MqttClient::_onUnsuback() {
|
||||
bool callback = false;
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
espMqttClientInternals::Outbox<OutgoingPacket>::Iterator it = _outbox.front();
|
||||
uint16_t idToMatch = _parser.getPacket().variableHeader.fixed.packetId;
|
||||
while (it) {
|
||||
if (it.get()->packet.packetId() == idToMatch) {
|
||||
callback = true;
|
||||
_outbox.remove(it);
|
||||
break;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
if (callback) {
|
||||
if (_onUnsubscribeCallback)
|
||||
_onUnsubscribeCallback(idToMatch);
|
||||
} else {
|
||||
emc_log_w("received UNSUBACK without UNSUB");
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t MqttClient::getQueue() const {
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
espMqttClientInternals::Outbox<OutgoingPacket>::Iterator it = _outbox.front();
|
||||
uint16_t count = 0;
|
||||
while (it) {
|
||||
++count;
|
||||
++it;
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
return count;
|
||||
}
|
||||
|
||||
void MqttClient::_clearQueue(int clearData) {
|
||||
emc_log_i("clearing queue (clear session: %d)", clearData);
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
espMqttClientInternals::Outbox<OutgoingPacket>::Iterator it = _outbox.front();
|
||||
if (clearData == 0) {
|
||||
// keep PUB (qos > 0, aka packetID != 0), PUBREC and PUBREL
|
||||
// Spec only mentions PUB and PUBREL but this lib implements method B from point 4.3.3 (Fig. 4.3)
|
||||
// and stores the packet id in the PUBREC packet. So we also must keep PUBREC.
|
||||
while (it) {
|
||||
espMqttClientInternals::MQTTPacketType type = it.get()->packet.packetType();
|
||||
if (type == PacketType.PUBREC || type == PacketType.PUBREL || (type == PacketType.PUBLISH && it.get()->packet.packetId() != 0)) {
|
||||
++it;
|
||||
} else {
|
||||
_outbox.remove(it);
|
||||
}
|
||||
}
|
||||
} else if (clearData == 1) {
|
||||
// keep PUB
|
||||
while (it) {
|
||||
if (it.get()->packet.packetType() == PacketType.PUBLISH) {
|
||||
++it;
|
||||
} else {
|
||||
_outbox.remove(it);
|
||||
}
|
||||
}
|
||||
} else { // clearData == 2
|
||||
while (it) {
|
||||
_outbox.remove(it);
|
||||
}
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
}
|
||||
|
||||
void MqttClient::_onError(uint16_t packetId, espMqttClientTypes::Error error) {
|
||||
if (_onErrorCallback) {
|
||||
_onErrorCallback(packetId, error);
|
||||
}
|
||||
}
|
||||
190
lib/espMqttClient/src/MqttClient.h
Normal file
190
lib/espMqttClient/src/MqttClient.h
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
API is based on the original work of Marvin Roger:
|
||||
https://github.com/marvinroger/async-mqtt-client
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <utility>
|
||||
|
||||
#include "Helpers.h"
|
||||
#include "Config.h"
|
||||
#include "TypeDefs.h"
|
||||
#include "Logging.h"
|
||||
#include "Outbox.h"
|
||||
#include "Packets/Packet.h"
|
||||
#include "Packets/Parser.h"
|
||||
#include "Transport/Transport.h"
|
||||
|
||||
class MqttClient {
|
||||
public:
|
||||
virtual ~MqttClient();
|
||||
bool connected() const;
|
||||
bool disconnected() const;
|
||||
bool connect();
|
||||
bool disconnect(bool force = false);
|
||||
template <typename... Args>
|
||||
uint16_t subscribe(const char* topic, uint8_t qos, Args&&... args) {
|
||||
uint16_t packetId = _getNextPacketId();
|
||||
if (_state != State::connected) {
|
||||
packetId = 0;
|
||||
} else {
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
if (!_addPacket(packetId, topic, qos, std::forward<Args>(args) ...)) {
|
||||
emc_log_e("Could not create SUBSCRIBE packet");
|
||||
packetId = 0;
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
}
|
||||
return packetId;
|
||||
}
|
||||
template <typename... Args>
|
||||
uint16_t unsubscribe(const char* topic, Args&&... args) {
|
||||
uint16_t packetId = _getNextPacketId();
|
||||
if (_state != State::connected) {
|
||||
packetId = 0;
|
||||
} else {
|
||||
EMC_SEMAPHORE_TAKE();
|
||||
if (!_addPacket(packetId, topic, std::forward<Args>(args) ...)) {
|
||||
emc_log_e("Could not create UNSUBSCRIBE packet");
|
||||
packetId = 0;
|
||||
}
|
||||
EMC_SEMAPHORE_GIVE();
|
||||
}
|
||||
return packetId;
|
||||
}
|
||||
uint16_t publish(const char* topic, uint8_t qos, bool retain, const uint8_t* payload, size_t length);
|
||||
uint16_t publish(const char* topic, uint8_t qos, bool retain, const char* payload);
|
||||
uint16_t publish(const char* topic, uint8_t qos, bool retain, espMqttClientTypes::PayloadCallback callback, size_t length);
|
||||
void clearQueue(bool deleteSessionData = false); // Not MQTT compliant and may cause unpredictable results when `deleteSessionData` = true!
|
||||
const char* getClientId() const;
|
||||
uint16_t getQueue() const;
|
||||
void loop();
|
||||
|
||||
protected:
|
||||
explicit MqttClient(espMqttClientTypes::UseInternalTask useInternalTask, uint8_t priority = 1, uint8_t core = 1);
|
||||
espMqttClientTypes::UseInternalTask _useInternalTask;
|
||||
espMqttClientInternals::Transport* _transport;
|
||||
|
||||
espMqttClientTypes::OnConnectCallback _onConnectCallback;
|
||||
espMqttClientTypes::OnDisconnectCallback _onDisconnectCallback;
|
||||
espMqttClientTypes::OnSubscribeCallback _onSubscribeCallback;
|
||||
espMqttClientTypes::OnUnsubscribeCallback _onUnsubscribeCallback;
|
||||
espMqttClientTypes::OnMessageCallback _onMessageCallback;
|
||||
espMqttClientTypes::OnPublishCallback _onPublishCallback;
|
||||
espMqttClientTypes::OnErrorCallback _onErrorCallback;
|
||||
typedef void(*mqttClientHook)(void*);
|
||||
const char* _clientId;
|
||||
IPAddress _ip;
|
||||
const char* _host;
|
||||
uint16_t _port;
|
||||
bool _useIp;
|
||||
uint32_t _keepAlive;
|
||||
bool _cleanSession;
|
||||
const char* _username;
|
||||
const char* _password;
|
||||
const char* _willTopic;
|
||||
const uint8_t* _willPayload;
|
||||
uint16_t _willPayloadLength;
|
||||
uint8_t _willQos;
|
||||
bool _willRetain;
|
||||
uint32_t _timeout;
|
||||
|
||||
// state is protected to allow state changes by the transport system, defined in child classes
|
||||
// eg. to allow AsyncTCP
|
||||
enum class State {
|
||||
disconnected = 0,
|
||||
connectingTcp1 = 1,
|
||||
connectingTcp2 = 2,
|
||||
connectingMqtt = 3,
|
||||
connected = 4,
|
||||
disconnectingMqtt1 = 5,
|
||||
disconnectingMqtt2 = 6,
|
||||
disconnectingTcp1 = 7,
|
||||
disconnectingTcp2 = 8
|
||||
};
|
||||
std::atomic<State> _state;
|
||||
|
||||
private:
|
||||
char _generatedClientId[EMC_CLIENTID_LENGTH];
|
||||
uint16_t _packetId;
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
SemaphoreHandle_t _xSemaphore;
|
||||
TaskHandle_t _taskHandle;
|
||||
static void _loop(MqttClient* c);
|
||||
#elif defined(ARDUINO_ARCH_ESP8266) && EMC_ESP8266_MULTITHREADING
|
||||
std::atomic<bool> _xSemaphore = false;
|
||||
#elif defined(__linux__)
|
||||
std::mutex mtx;
|
||||
#endif
|
||||
|
||||
uint8_t _rxBuffer[EMC_RX_BUFFER_SIZE];
|
||||
struct OutgoingPacket {
|
||||
uint32_t timeSent;
|
||||
espMqttClientInternals::Packet packet;
|
||||
template <typename... Args>
|
||||
OutgoingPacket(uint32_t t, espMqttClientTypes::Error error, Args&&... args) :
|
||||
timeSent(t),
|
||||
packet(error, std::forward<Args>(args) ...) {}
|
||||
};
|
||||
espMqttClientInternals::Outbox<OutgoingPacket> _outbox;
|
||||
size_t _bytesSent;
|
||||
espMqttClientInternals::Parser _parser;
|
||||
uint32_t _lastClientActivity;
|
||||
uint32_t _lastServerActivity;
|
||||
bool _pingSent;
|
||||
espMqttClientTypes::DisconnectReason _disconnectReason;
|
||||
|
||||
uint16_t _getNextPacketId();
|
||||
|
||||
template <typename... Args>
|
||||
bool _addPacket(Args&&... args) {
|
||||
espMqttClientTypes::Error error(espMqttClientTypes::Error::SUCCESS);
|
||||
espMqttClientInternals::Outbox<OutgoingPacket>::Iterator it = _outbox.emplace(0, error, std::forward<Args>(args) ...);
|
||||
if (it && error == espMqttClientTypes::Error::SUCCESS) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
bool _addPacketFront(Args&&... args) {
|
||||
espMqttClientTypes::Error error(espMqttClientTypes::Error::SUCCESS);
|
||||
espMqttClientInternals::Outbox<OutgoingPacket>::Iterator it = _outbox.emplaceFront(0, error, std::forward<Args>(args) ...);
|
||||
if (it && error == espMqttClientTypes::Error::SUCCESS) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void _checkOutbox();
|
||||
int _sendPacket();
|
||||
bool _advanceOutbox();
|
||||
void _checkIncoming();
|
||||
void _checkPing();
|
||||
void _checkTimeout();
|
||||
|
||||
void _onConnack();
|
||||
void _onPublish();
|
||||
void _onPuback();
|
||||
void _onPubrec();
|
||||
void _onPubrel();
|
||||
void _onPubcomp();
|
||||
void _onSuback();
|
||||
void _onUnsuback();
|
||||
|
||||
void _clearQueue(int clearData); // 0: keep session,
|
||||
// 1: keep only PUBLISH qos > 0
|
||||
// 2: delete all
|
||||
void _onError(uint16_t packetId, espMqttClientTypes::Error error);
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_INFO
|
||||
size_t _highWaterMark;
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
116
lib/espMqttClient/src/MqttClientSetup.h
Normal file
116
lib/espMqttClient/src/MqttClientSetup.h
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
API is based on the original work of Marvin Roger:
|
||||
https://github.com/marvinroger/async-mqtt-client
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MqttClient.h"
|
||||
|
||||
template <typename T>
|
||||
class MqttClientSetup : public MqttClient {
|
||||
public:
|
||||
T& setKeepAlive(uint16_t keepAlive) {
|
||||
_keepAlive = keepAlive * 1000; // s to ms conversion, will also do 16 to 32 bit conversion
|
||||
return static_cast<T&>(*this);
|
||||
}
|
||||
|
||||
T& setClientId(const char* clientId) {
|
||||
_clientId = clientId;
|
||||
return static_cast<T&>(*this);
|
||||
}
|
||||
|
||||
T& setCleanSession(bool cleanSession) {
|
||||
_cleanSession = cleanSession;
|
||||
return static_cast<T&>(*this);
|
||||
}
|
||||
|
||||
T& setCredentials(const char* username, const char* password) {
|
||||
_username = username;
|
||||
_password = password;
|
||||
return static_cast<T&>(*this);
|
||||
}
|
||||
|
||||
T& setWill(const char* topic, uint8_t qos, bool retain, const uint8_t* payload, size_t length) {
|
||||
_willTopic = topic;
|
||||
_willQos = qos;
|
||||
_willRetain = retain;
|
||||
_willPayload = payload;
|
||||
if (!_willPayload) {
|
||||
_willPayloadLength = 0;
|
||||
} else {
|
||||
_willPayloadLength = length;
|
||||
}
|
||||
return static_cast<T&>(*this);
|
||||
}
|
||||
|
||||
T& setWill(const char* topic, uint8_t qos, bool retain, const char* payload) {
|
||||
return setWill(topic, qos, retain, reinterpret_cast<const uint8_t*>(payload), strlen(payload));
|
||||
}
|
||||
|
||||
T& setServer(IPAddress ip, uint16_t port) {
|
||||
_ip = ip;
|
||||
_port = port;
|
||||
_useIp = true;
|
||||
return static_cast<T&>(*this);
|
||||
}
|
||||
|
||||
T& setServer(const char* host, uint16_t port) {
|
||||
_host = host;
|
||||
_port = port;
|
||||
_useIp = false;
|
||||
return static_cast<T&>(*this);
|
||||
}
|
||||
|
||||
T& setTimeout(uint16_t timeout) {
|
||||
_timeout = timeout * 1000; // s to ms conversion, will also do 16 to 32 bit conversion
|
||||
return static_cast<T&>(*this);
|
||||
}
|
||||
|
||||
T& onConnect(espMqttClientTypes::OnConnectCallback callback) {
|
||||
_onConnectCallback = callback;
|
||||
return static_cast<T&>(*this);
|
||||
}
|
||||
|
||||
T& onDisconnect(espMqttClientTypes::OnDisconnectCallback callback) {
|
||||
_onDisconnectCallback = callback;
|
||||
return static_cast<T&>(*this);
|
||||
}
|
||||
|
||||
T& onSubscribe(espMqttClientTypes::OnSubscribeCallback callback) {
|
||||
_onSubscribeCallback = callback;
|
||||
return static_cast<T&>(*this);
|
||||
}
|
||||
|
||||
T& onUnsubscribe(espMqttClientTypes::OnUnsubscribeCallback callback) {
|
||||
_onUnsubscribeCallback = callback;
|
||||
return static_cast<T&>(*this);
|
||||
}
|
||||
|
||||
T& onMessage(espMqttClientTypes::OnMessageCallback callback) {
|
||||
_onMessageCallback = callback;
|
||||
return static_cast<T&>(*this);
|
||||
}
|
||||
|
||||
T& onPublish(espMqttClientTypes::OnPublishCallback callback) {
|
||||
_onPublishCallback = callback;
|
||||
return static_cast<T&>(*this);
|
||||
}
|
||||
|
||||
/*
|
||||
T& onError(espMqttClientTypes::OnErrorCallback callback) {
|
||||
_onErrorCallback = callback;
|
||||
return static_cast<T&>(*this);
|
||||
}
|
||||
*/
|
||||
|
||||
protected:
|
||||
explicit MqttClientSetup(espMqttClientTypes::UseInternalTask useInternalTask, uint8_t priority = 1, uint8_t core = 1)
|
||||
: MqttClient(useInternalTask, priority, core) {}
|
||||
};
|
||||
207
lib/espMqttClient/src/Outbox.h
Normal file
207
lib/espMqttClient/src/Outbox.h
Normal file
@@ -0,0 +1,207 @@
|
||||
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <new> // new (std::nothrow)
|
||||
#include <utility> // std::forward
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
/**
|
||||
* @brief Singly linked queue with builtin non-invalidating forward iterator
|
||||
*
|
||||
* Queue items can only be emplaced, at front and back of the queue.
|
||||
* Remove items using an iterator or the builtin iterator.
|
||||
*/
|
||||
|
||||
template <typename T>
|
||||
class Outbox {
|
||||
public:
|
||||
Outbox()
|
||||
: _first(nullptr)
|
||||
, _last(nullptr)
|
||||
, _current(nullptr)
|
||||
, _prev(nullptr) {}
|
||||
~Outbox() {
|
||||
while (_first) {
|
||||
Node* n = _first->next;
|
||||
delete _first;
|
||||
_first = n;
|
||||
}
|
||||
}
|
||||
|
||||
struct Node {
|
||||
public:
|
||||
template <typename... Args>
|
||||
explicit Node(Args&&... args)
|
||||
: data(std::forward<Args>(args) ...)
|
||||
, next(nullptr) {
|
||||
// empty
|
||||
}
|
||||
|
||||
T data;
|
||||
Node* next;
|
||||
};
|
||||
|
||||
class Iterator {
|
||||
friend class Outbox;
|
||||
public:
|
||||
void operator++() {
|
||||
if (_node) {
|
||||
_prev = _node;
|
||||
_node = _node->next;
|
||||
}
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
if (_node) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
T* get() const {
|
||||
if (_node) return &(_node->data);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
Node* _node = nullptr;
|
||||
Node* _prev = nullptr;
|
||||
};
|
||||
|
||||
// add node to back, advance current to new if applicable
|
||||
template <class... Args>
|
||||
Iterator emplace(Args&&... args) {
|
||||
Iterator it;
|
||||
Node* node = new (std::nothrow) Node(std::forward<Args>(args) ...);
|
||||
if (node != nullptr) {
|
||||
if (!_first) {
|
||||
// queue is empty
|
||||
_first = _current = node;
|
||||
} else {
|
||||
// queue has at least one item
|
||||
_last->next = node;
|
||||
it._prev = _last;
|
||||
}
|
||||
_last = node;
|
||||
it._node = node;
|
||||
// point current to newly created if applicable
|
||||
if (!_current) {
|
||||
_current = _last;
|
||||
}
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
// add item to front, current points to newly created front.
|
||||
template <class... Args>
|
||||
Iterator emplaceFront(Args&&... args) {
|
||||
Iterator it;
|
||||
Node* node = new (std::nothrow) Node(std::forward<Args>(args) ...);
|
||||
if (node != nullptr) {
|
||||
if (!_first) {
|
||||
// queue is empty
|
||||
_last = node;
|
||||
} else {
|
||||
// queue has at least one item
|
||||
node->next = _first;
|
||||
}
|
||||
_current = _first = node;
|
||||
_prev = nullptr;
|
||||
it._node = node;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
// remove node at iterator, iterator points to next
|
||||
void remove(Iterator& it) { // NOLINT(runtime/references)
|
||||
if (!it) return;
|
||||
Node* node = it._node;
|
||||
Node* prev = it._prev;
|
||||
++it;
|
||||
_remove(prev, node);
|
||||
}
|
||||
|
||||
// remove current node, current points to next
|
||||
void removeCurrent() {
|
||||
_remove(_prev, _current);
|
||||
}
|
||||
|
||||
// Get current item or return nullptr
|
||||
T* getCurrent() const {
|
||||
if (_current) return &(_current->data);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void resetCurrent() {
|
||||
_current = _first;
|
||||
}
|
||||
|
||||
Iterator front() const {
|
||||
Iterator it;
|
||||
it._node = _first;
|
||||
return it;
|
||||
}
|
||||
|
||||
// Advance current item
|
||||
void next() {
|
||||
if (_current) {
|
||||
_prev = _current;
|
||||
_current = _current->next;
|
||||
}
|
||||
}
|
||||
|
||||
// Outbox is empty
|
||||
bool empty() {
|
||||
if (!_first) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
Node* _first;
|
||||
Node* _last;
|
||||
Node* _current;
|
||||
Node* _prev; // element just before _current
|
||||
|
||||
void _remove(Node* prev, Node* node) {
|
||||
if (!node) return;
|
||||
|
||||
// set current to next, node->next may be nullptr
|
||||
if (_current == node) {
|
||||
_current = node->next;
|
||||
}
|
||||
|
||||
if (_prev == node) {
|
||||
_prev = prev;
|
||||
}
|
||||
|
||||
// only one element in outbox
|
||||
if (_first == _last) {
|
||||
_first = _last = nullptr;
|
||||
|
||||
// delete first el in longer outbox
|
||||
} else if (_first == node) {
|
||||
_first = node->next;
|
||||
|
||||
// delete last in longer outbox
|
||||
} else if (_last == node) {
|
||||
_last = prev;
|
||||
_last->next = nullptr;
|
||||
|
||||
// delete somewhere in the middle
|
||||
} else {
|
||||
prev->next = node->next;
|
||||
}
|
||||
|
||||
// finally, delete the node
|
||||
delete node;
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace espMqttClientInternals
|
||||
77
lib/espMqttClient/src/Packets/Constants.h
Normal file
77
lib/espMqttClient/src/Packets/Constants.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
Parts are based on the original work of Marvin Roger:
|
||||
https://github.com/marvinroger/async-mqtt-client
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
constexpr const char PROTOCOL[] = "MQTT";
|
||||
constexpr const uint8_t PROTOCOL_LEVEL = 0b00000100;
|
||||
|
||||
typedef uint8_t MQTTPacketType;
|
||||
|
||||
constexpr struct {
|
||||
const uint8_t RESERVED1 = 0;
|
||||
const uint8_t CONNECT = 1 << 4;
|
||||
const uint8_t CONNACK = 2 << 4;
|
||||
const uint8_t PUBLISH = 3 << 4;
|
||||
const uint8_t PUBACK = 4 << 4;
|
||||
const uint8_t PUBREC = 5 << 4;
|
||||
const uint8_t PUBREL = 6 << 4;
|
||||
const uint8_t PUBCOMP = 7 << 4;
|
||||
const uint8_t SUBSCRIBE = 8 << 4;
|
||||
const uint8_t SUBACK = 9 << 4;
|
||||
const uint8_t UNSUBSCRIBE = 10 << 4;
|
||||
const uint8_t UNSUBACK = 11 << 4;
|
||||
const uint8_t PINGREQ = 12 << 4;
|
||||
const uint8_t PINGRESP = 13 << 4;
|
||||
const uint8_t DISCONNECT = 14 << 4;
|
||||
const uint8_t RESERVED2 = 1 << 4;
|
||||
} 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;
|
||||
|
||||
} // end namespace espMqttClientInternals
|
||||
438
lib/espMqttClient/src/Packets/Packet.cpp
Normal file
438
lib/espMqttClient/src/Packets/Packet.cpp
Normal file
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "Packet.h"
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
Packet::~Packet() {
|
||||
free(_data);
|
||||
}
|
||||
|
||||
size_t Packet::available(size_t index) {
|
||||
if (index >= _size) return 0;
|
||||
if (!_getPayload) return _size - index;
|
||||
return _chunkedAvailable(index);
|
||||
}
|
||||
|
||||
const uint8_t* Packet::data(size_t index) const {
|
||||
if (!_getPayload) {
|
||||
if (!_data) return nullptr;
|
||||
if (index >= _size) return nullptr;
|
||||
return &_data[index];
|
||||
}
|
||||
return _chunkedData(index);
|
||||
}
|
||||
|
||||
size_t Packet::size() const {
|
||||
return _size;
|
||||
}
|
||||
|
||||
void Packet::setDup() {
|
||||
if (!_data) return;
|
||||
if (packetType() != PacketType.PUBLISH) return;
|
||||
if (_packetId == 0) return;
|
||||
_data[0] |= 0x08;
|
||||
}
|
||||
|
||||
uint16_t Packet::packetId() const {
|
||||
return _packetId;
|
||||
}
|
||||
|
||||
MQTTPacketType Packet::packetType() const {
|
||||
if (_data) return static_cast<MQTTPacketType>(_data[0] & 0xF0);
|
||||
return static_cast<MQTTPacketType>(0);
|
||||
}
|
||||
|
||||
bool Packet::removable() const {
|
||||
if (_packetId == 0) return true;
|
||||
if ((packetType() == PacketType.PUBACK) || (packetType() == PacketType.PUBCOMP)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
Packet::Packet(espMqttClientTypes::Error& error,
|
||||
bool cleanSession,
|
||||
const char* username,
|
||||
const char* password,
|
||||
const char* willTopic,
|
||||
bool willRetain,
|
||||
uint8_t willQos,
|
||||
const uint8_t* willPayload,
|
||||
uint16_t willPayloadLength,
|
||||
uint16_t keepAlive,
|
||||
const char* clientId)
|
||||
: _packetId(0)
|
||||
, _data(nullptr)
|
||||
, _size(0)
|
||||
, _payloadIndex(0)
|
||||
, _payloadStartIndex(0)
|
||||
, _payloadEndIndex(0)
|
||||
, _getPayload(nullptr) {
|
||||
if (willPayload && willPayloadLength == 0) {
|
||||
size_t length = strlen(reinterpret_cast<const char*>(willPayload));
|
||||
if (length > UINT16_MAX) {
|
||||
emc_log_w("Payload length truncated (l:%zu)", length);
|
||||
willPayloadLength = UINT16_MAX;
|
||||
} else {
|
||||
willPayloadLength = length;
|
||||
}
|
||||
}
|
||||
if (!clientId || strlen(clientId) == 0) {
|
||||
emc_log_w("clientId not set error");
|
||||
error = espMqttClientTypes::Error::MALFORMED_PARAMETER;
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate size
|
||||
size_t remainingLength =
|
||||
6 + // protocol
|
||||
1 + // protocol level
|
||||
1 + // connect flags
|
||||
2 + // keepalive
|
||||
2 + strlen(clientId) +
|
||||
(willTopic ? 2 + strlen(willTopic) + 2 + willPayloadLength : 0) +
|
||||
(username ? 2 + strlen(username) : 0) +
|
||||
(password ? 2 + strlen(password) : 0);
|
||||
|
||||
// allocate memory
|
||||
if (!_allocate(remainingLength)) {
|
||||
error = espMqttClientTypes::Error::OUT_OF_MEMORY;
|
||||
return;
|
||||
}
|
||||
|
||||
// serialize
|
||||
size_t pos = 0;
|
||||
|
||||
// FIXED HEADER
|
||||
_data[pos++] = PacketType.CONNECT | HeaderFlag.CONNECT_RESERVED;
|
||||
pos += encodeRemainingLength(remainingLength, &_data[pos]);
|
||||
pos += encodeString(PROTOCOL, &_data[pos]);
|
||||
_data[pos++] = PROTOCOL_LEVEL;
|
||||
uint8_t connectFlags = 0;
|
||||
if (cleanSession) connectFlags |= espMqttClientInternals::ConnectFlag.CLEAN_SESSION;
|
||||
if (username != nullptr) connectFlags |= espMqttClientInternals::ConnectFlag.USERNAME;
|
||||
if (password != nullptr) connectFlags |= espMqttClientInternals::ConnectFlag.PASSWORD;
|
||||
if (willTopic != nullptr) {
|
||||
connectFlags |= espMqttClientInternals::ConnectFlag.WILL;
|
||||
if (willRetain) connectFlags |= espMqttClientInternals::ConnectFlag.WILL_RETAIN;
|
||||
switch (willQos) {
|
||||
case 0:
|
||||
connectFlags |= espMqttClientInternals::ConnectFlag.WILL_QOS0;
|
||||
break;
|
||||
case 1:
|
||||
connectFlags |= espMqttClientInternals::ConnectFlag.WILL_QOS1;
|
||||
break;
|
||||
case 2:
|
||||
connectFlags |= espMqttClientInternals::ConnectFlag.WILL_QOS2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
_data[pos++] = connectFlags;
|
||||
_data[pos++] = keepAlive >> 8;
|
||||
_data[pos++] = keepAlive & 0xFF;
|
||||
|
||||
// PAYLOAD
|
||||
// client ID
|
||||
pos += encodeString(clientId, &_data[pos]);
|
||||
// will
|
||||
if (willTopic != nullptr && willPayload != nullptr) {
|
||||
pos += encodeString(willTopic, &_data[pos]);
|
||||
_data[pos++] = willPayloadLength >> 8;
|
||||
_data[pos++] = willPayloadLength & 0xFF;
|
||||
memcpy(&_data[pos], willPayload, willPayloadLength);
|
||||
pos += willPayloadLength;
|
||||
}
|
||||
// credentials
|
||||
if (username != nullptr) pos += encodeString(username, &_data[pos]);
|
||||
if (password != nullptr) encodeString(password, &_data[pos]);
|
||||
|
||||
error = espMqttClientTypes::Error::SUCCESS;
|
||||
}
|
||||
|
||||
Packet::Packet(espMqttClientTypes::Error& error,
|
||||
uint16_t packetId,
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
size_t payloadLength,
|
||||
uint8_t qos,
|
||||
bool retain)
|
||||
: _packetId(packetId)
|
||||
, _data(nullptr)
|
||||
, _size(0)
|
||||
, _payloadIndex(0)
|
||||
, _payloadStartIndex(0)
|
||||
, _payloadEndIndex(0)
|
||||
, _getPayload(nullptr) {
|
||||
size_t remainingLength =
|
||||
2 + strlen(topic) + // topic length + topic
|
||||
2 + // packet ID
|
||||
payloadLength;
|
||||
|
||||
if (qos == 0) {
|
||||
remainingLength -= 2;
|
||||
_packetId = 0;
|
||||
}
|
||||
|
||||
if (!_allocate(remainingLength)) {
|
||||
error = espMqttClientTypes::Error::OUT_OF_MEMORY;
|
||||
return;
|
||||
}
|
||||
|
||||
size_t pos = _fillPublishHeader(packetId, topic, remainingLength, qos, retain);
|
||||
|
||||
// PAYLOAD
|
||||
memcpy(&_data[pos], payload, payloadLength);
|
||||
|
||||
error = espMqttClientTypes::Error::SUCCESS;
|
||||
}
|
||||
|
||||
Packet::Packet(espMqttClientTypes::Error& error,
|
||||
uint16_t packetId,
|
||||
const char* topic,
|
||||
espMqttClientTypes::PayloadCallback payloadCallback,
|
||||
size_t payloadLength,
|
||||
uint8_t qos,
|
||||
bool retain)
|
||||
: _packetId(packetId)
|
||||
, _data(nullptr)
|
||||
, _size(0)
|
||||
, _payloadIndex(0)
|
||||
, _payloadStartIndex(0)
|
||||
, _payloadEndIndex(0)
|
||||
, _getPayload(payloadCallback) {
|
||||
size_t remainingLength =
|
||||
2 + strlen(topic) + // topic length + topic
|
||||
2 + // packet ID
|
||||
payloadLength;
|
||||
|
||||
if (qos == 0) {
|
||||
remainingLength -= 2;
|
||||
_packetId = 0;
|
||||
}
|
||||
|
||||
if (!_allocate(remainingLength - payloadLength + std::min(payloadLength, static_cast<size_t>(EMC_RX_BUFFER_SIZE)))) {
|
||||
error = espMqttClientTypes::Error::OUT_OF_MEMORY;
|
||||
return;
|
||||
}
|
||||
|
||||
size_t pos = _fillPublishHeader(packetId, topic, remainingLength, qos, retain);
|
||||
|
||||
// payload will be added by 'Packet::available'
|
||||
_size = pos + payloadLength;
|
||||
_payloadIndex = pos;
|
||||
_payloadStartIndex = _payloadIndex;
|
||||
_payloadEndIndex = _payloadIndex;
|
||||
|
||||
error = espMqttClientTypes::Error::SUCCESS;
|
||||
}
|
||||
|
||||
Packet::Packet(espMqttClientTypes::Error& error, uint16_t packetId, const char* topic, uint8_t qos)
|
||||
: _packetId(packetId)
|
||||
, _data(nullptr)
|
||||
, _size(0)
|
||||
, _payloadIndex(0)
|
||||
, _payloadStartIndex(0)
|
||||
, _payloadEndIndex(0)
|
||||
, _getPayload(nullptr) {
|
||||
SubscribeItem list[1] = {topic, qos};
|
||||
_createSubscribe(error, list, 1);
|
||||
}
|
||||
|
||||
Packet::Packet(espMqttClientTypes::Error& error, MQTTPacketType type, uint16_t packetId)
|
||||
: _packetId(packetId)
|
||||
, _data(nullptr)
|
||||
, _size(0)
|
||||
, _payloadIndex(0)
|
||||
, _payloadStartIndex(0)
|
||||
, _payloadEndIndex(0)
|
||||
, _getPayload(nullptr) {
|
||||
if (!_allocate(2)) {
|
||||
error = espMqttClientTypes::Error::OUT_OF_MEMORY;
|
||||
return;
|
||||
}
|
||||
|
||||
size_t pos = 0;
|
||||
_data[pos] = type;
|
||||
if (type == PacketType.PUBREL) {
|
||||
_data[pos++] |= HeaderFlag.PUBREL_RESERVED;
|
||||
} else {
|
||||
pos++;
|
||||
}
|
||||
pos += encodeRemainingLength(2, &_data[pos]);
|
||||
_data[pos++] = packetId >> 8;
|
||||
_data[pos] = packetId & 0xFF;
|
||||
|
||||
error = espMqttClientTypes::Error::SUCCESS;
|
||||
}
|
||||
|
||||
Packet::Packet(espMqttClientTypes::Error& error, uint16_t packetId, const char* topic)
|
||||
: _packetId(packetId)
|
||||
, _data(nullptr)
|
||||
, _size(0)
|
||||
, _payloadIndex(0)
|
||||
, _payloadStartIndex(0)
|
||||
, _payloadEndIndex(0)
|
||||
, _getPayload(nullptr) {
|
||||
const char* list[1] = {topic};
|
||||
_createUnsubscribe(error, list, 1);
|
||||
}
|
||||
|
||||
Packet::Packet(espMqttClientTypes::Error& error, MQTTPacketType type)
|
||||
: _packetId(0)
|
||||
, _data(nullptr)
|
||||
, _size(0)
|
||||
, _payloadIndex(0)
|
||||
, _payloadStartIndex(0)
|
||||
, _payloadEndIndex(0)
|
||||
, _getPayload(nullptr) {
|
||||
if (!_allocate(0)) {
|
||||
error = espMqttClientTypes::Error::OUT_OF_MEMORY;
|
||||
return;
|
||||
}
|
||||
_data[0] |= type;
|
||||
|
||||
error = espMqttClientTypes::Error::SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
bool Packet::_allocate(size_t remainingLength) {
|
||||
if (EMC_GET_FREE_MEMORY() < EMC_MIN_FREE_MEMORY) {
|
||||
emc_log_w("Packet buffer not allocated: low memory");
|
||||
return false;
|
||||
}
|
||||
_size = 1 + remainingLengthLength(remainingLength) + remainingLength;
|
||||
_data = reinterpret_cast<uint8_t*>(malloc(_size));
|
||||
if (!_data) {
|
||||
_size = 0;
|
||||
emc_log_w("Alloc failed (l:%zu)", _size);
|
||||
return false;
|
||||
}
|
||||
emc_log_i("Alloc (l:%zu)", _size);
|
||||
memset(_data, 0, _size);
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t Packet::_fillPublishHeader(uint16_t packetId,
|
||||
const char* topic,
|
||||
size_t remainingLength,
|
||||
uint8_t qos,
|
||||
bool retain) {
|
||||
size_t index = 0;
|
||||
|
||||
// FIXED HEADER
|
||||
_data[index] = PacketType.PUBLISH;
|
||||
if (retain) _data[index] |= HeaderFlag.PUBLISH_RETAIN;
|
||||
if (qos == 0) {
|
||||
_data[index++] |= HeaderFlag.PUBLISH_QOS0;
|
||||
} else if (qos == 1) {
|
||||
_data[index++] |= HeaderFlag.PUBLISH_QOS1;
|
||||
} else if (qos == 2) {
|
||||
_data[index++] |= HeaderFlag.PUBLISH_QOS2;
|
||||
}
|
||||
index += encodeRemainingLength(remainingLength, &_data[index]);
|
||||
|
||||
// VARIABLE HEADER
|
||||
index += encodeString(topic, &_data[index]);
|
||||
if (qos > 0) {
|
||||
_data[index++] = packetId >> 8;
|
||||
_data[index++] = packetId & 0xFF;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
void Packet::_createSubscribe(espMqttClientTypes::Error& error,
|
||||
SubscribeItem* list,
|
||||
size_t numberTopics) {
|
||||
// Calculate size
|
||||
size_t payload = 0;
|
||||
for (size_t i = 0; i < numberTopics; ++i) {
|
||||
payload += 2 + strlen(list[i].topic) + 1; // length bytes, string, qos
|
||||
}
|
||||
size_t remainingLength = 2 + payload; // packetId + payload
|
||||
|
||||
// allocate memory
|
||||
if (!_allocate(remainingLength)) {
|
||||
error = espMqttClientTypes::Error::OUT_OF_MEMORY;
|
||||
return;
|
||||
}
|
||||
|
||||
// serialize
|
||||
size_t pos = 0;
|
||||
_data[pos++] = PacketType.SUBSCRIBE | HeaderFlag.SUBSCRIBE_RESERVED;
|
||||
pos += encodeRemainingLength(remainingLength, &_data[pos]);
|
||||
_data[pos++] = _packetId >> 8;
|
||||
_data[pos++] = _packetId & 0xFF;
|
||||
for (size_t i = 0; i < numberTopics; ++i) {
|
||||
pos += encodeString(list[i].topic, &_data[pos]);
|
||||
_data[pos++] = list[i].qos;
|
||||
}
|
||||
|
||||
error = espMqttClientTypes::Error::SUCCESS;
|
||||
}
|
||||
|
||||
void Packet::_createUnsubscribe(espMqttClientTypes::Error& error,
|
||||
const char** list,
|
||||
size_t numberTopics) {
|
||||
// Calculate size
|
||||
size_t payload = 0;
|
||||
for (size_t i = 0; i < numberTopics; ++i) {
|
||||
payload += 2 + strlen(list[i]); // length bytes, string
|
||||
}
|
||||
size_t remainingLength = 2 + payload; // packetId + payload
|
||||
|
||||
// allocate memory
|
||||
if (!_allocate(remainingLength)) {
|
||||
error = espMqttClientTypes::Error::OUT_OF_MEMORY;
|
||||
return;
|
||||
}
|
||||
|
||||
// serialize
|
||||
size_t pos = 0;
|
||||
_data[pos++] = PacketType.UNSUBSCRIBE | HeaderFlag.UNSUBSCRIBE_RESERVED;
|
||||
pos += encodeRemainingLength(remainingLength, &_data[pos]);
|
||||
_data[pos++] = _packetId >> 8;
|
||||
_data[pos++] = _packetId & 0xFF;
|
||||
for (size_t i = 0; i < numberTopics; ++i) {
|
||||
pos += encodeString(list[i], &_data[pos]);
|
||||
}
|
||||
|
||||
error = espMqttClientTypes::Error::SUCCESS;
|
||||
}
|
||||
|
||||
size_t Packet::_chunkedAvailable(size_t index) {
|
||||
// index vs size check done in 'available(index)'
|
||||
|
||||
// index points to header or first payload byte
|
||||
if (index < _payloadIndex) {
|
||||
if (_size > _payloadIndex && _payloadEndIndex != 0) {
|
||||
size_t copied = _getPayload(&_data[_payloadIndex], std::min(static_cast<size_t>(EMC_TX_BUFFER_SIZE), _size - _payloadStartIndex), index);
|
||||
_payloadStartIndex = _payloadIndex;
|
||||
_payloadEndIndex = _payloadStartIndex + copied - 1;
|
||||
}
|
||||
|
||||
// index points to payload unavailable
|
||||
} else if (index > _payloadEndIndex || _payloadStartIndex > index) {
|
||||
_payloadStartIndex = index;
|
||||
size_t copied = _getPayload(&_data[_payloadIndex], std::min(static_cast<size_t>(EMC_TX_BUFFER_SIZE), _size - _payloadStartIndex), index);
|
||||
_payloadEndIndex = _payloadStartIndex + copied - 1;
|
||||
}
|
||||
|
||||
// now index points to header or payload available
|
||||
return _payloadEndIndex - index + 1;
|
||||
}
|
||||
|
||||
const uint8_t* Packet::_chunkedData(size_t index) const {
|
||||
// CAUTION!! available(index) has to be called first to check available data and possibly fill payloadbuffer
|
||||
if (index < _payloadIndex) {
|
||||
return &_data[index];
|
||||
}
|
||||
return &_data[index - _payloadStartIndex + _payloadIndex];
|
||||
}
|
||||
|
||||
} // end namespace espMqttClientInternals
|
||||
155
lib/espMqttClient/src/Packets/Packet.h
Normal file
155
lib/espMqttClient/src/Packets/Packet.h
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "Constants.h"
|
||||
#include "../Config.h"
|
||||
#include "../TypeDefs.h"
|
||||
#include "../Helpers.h"
|
||||
#include "../Logging.h"
|
||||
#include "RemainingLength.h"
|
||||
#include "String.h"
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
class Packet {
|
||||
public:
|
||||
~Packet();
|
||||
size_t available(size_t index);
|
||||
const uint8_t* data(size_t index) const;
|
||||
|
||||
size_t size() const;
|
||||
void setDup();
|
||||
uint16_t packetId() const;
|
||||
MQTTPacketType packetType() const;
|
||||
bool removable() const;
|
||||
|
||||
protected:
|
||||
uint16_t _packetId; // save as separate variable: will be accessed frequently
|
||||
uint8_t* _data;
|
||||
size_t _size;
|
||||
|
||||
// variables for chunked payload handling
|
||||
size_t _payloadIndex;
|
||||
size_t _payloadStartIndex;
|
||||
size_t _payloadEndIndex;
|
||||
espMqttClientTypes::PayloadCallback _getPayload;
|
||||
|
||||
struct SubscribeItem {
|
||||
const char* topic;
|
||||
uint8_t qos;
|
||||
};
|
||||
|
||||
public:
|
||||
// CONNECT
|
||||
Packet(espMqttClientTypes::Error& error, // NOLINT(runtime/references)
|
||||
bool cleanSession,
|
||||
const char* username,
|
||||
const char* password,
|
||||
const char* willTopic,
|
||||
bool willRetain,
|
||||
uint8_t willQos,
|
||||
const uint8_t* willPayload,
|
||||
uint16_t willPayloadLength,
|
||||
uint16_t keepAlive,
|
||||
const char* clientId);
|
||||
// PUBLISH
|
||||
Packet(espMqttClientTypes::Error& error, // NOLINT(runtime/references)
|
||||
uint16_t packetId,
|
||||
const char* topic,
|
||||
const uint8_t* payload,
|
||||
size_t payloadLength,
|
||||
uint8_t qos,
|
||||
bool retain);
|
||||
Packet(espMqttClientTypes::Error& error, // NOLINT(runtime/references)
|
||||
uint16_t packetId,
|
||||
const char* topic,
|
||||
espMqttClientTypes::PayloadCallback payloadCallback,
|
||||
size_t payloadLength,
|
||||
uint8_t qos,
|
||||
bool retain);
|
||||
// SUBSCRIBE
|
||||
Packet(espMqttClientTypes::Error& error, // NOLINT(runtime/references)
|
||||
uint16_t packetId,
|
||||
const char* topic,
|
||||
uint8_t qos);
|
||||
template<typename ... Args>
|
||||
Packet(espMqttClientTypes::Error& error, // NOLINT(runtime/references)
|
||||
uint16_t packetId,
|
||||
const char* topic1,
|
||||
uint8_t qos1,
|
||||
const char* topic2,
|
||||
uint8_t qos2,
|
||||
Args&& ... args)
|
||||
: _packetId(packetId)
|
||||
, _data(nullptr)
|
||||
, _size(0)
|
||||
, _payloadIndex(0)
|
||||
, _payloadStartIndex(0)
|
||||
, _payloadEndIndex(0)
|
||||
, _getPayload(nullptr) {
|
||||
static_assert(sizeof...(Args) % 2 == 0, "Subscribe should be in topic/qos pairs");
|
||||
size_t numberTopics = 2 + (sizeof...(Args) / 2);
|
||||
SubscribeItem list[numberTopics] = {topic1, qos1, topic2, qos2, args...};
|
||||
_createSubscribe(error, list, numberTopics);
|
||||
}
|
||||
// UNSUBSCRIBE
|
||||
Packet(espMqttClientTypes::Error& error, // NOLINT(runtime/references)
|
||||
uint16_t packetId,
|
||||
const char* topic);
|
||||
template<typename ... Args>
|
||||
Packet(espMqttClientTypes::Error& error, // NOLINT(runtime/references)
|
||||
uint16_t packetId,
|
||||
const char* topic1,
|
||||
const char* topic2,
|
||||
Args&& ... args)
|
||||
: _packetId(packetId)
|
||||
, _data(nullptr)
|
||||
, _size(0)
|
||||
, _payloadIndex(0)
|
||||
, _payloadStartIndex(0)
|
||||
, _payloadEndIndex(0)
|
||||
, _getPayload(nullptr) {
|
||||
size_t numberTopics = 2 + sizeof...(Args);
|
||||
const char* list[numberTopics] = {topic1, topic2, args...};
|
||||
_createUnsubscribe(error, list, numberTopics);
|
||||
}
|
||||
// PUBACK, PUBREC, PUBREL, PUBCOMP
|
||||
Packet(espMqttClientTypes::Error& error, // NOLINT(runtime/references)
|
||||
MQTTPacketType type,
|
||||
uint16_t packetId);
|
||||
// PING, DISCONN
|
||||
explicit Packet(espMqttClientTypes::Error& error, // NOLINT(runtime/references)
|
||||
MQTTPacketType type);
|
||||
|
||||
private:
|
||||
// pass remainingLength = total size - header - remainingLengthLength!
|
||||
bool _allocate(size_t remainingLength);
|
||||
|
||||
// fills header and returns index of next available byte in buffer
|
||||
size_t _fillPublishHeader(uint16_t packetId,
|
||||
const char* topic,
|
||||
size_t remainingLength,
|
||||
uint8_t qos,
|
||||
bool retain);
|
||||
void _createSubscribe(espMqttClientTypes::Error& error, // NOLINT(runtime/references)
|
||||
SubscribeItem* list,
|
||||
size_t numberTopics);
|
||||
void _createUnsubscribe(espMqttClientTypes::Error& error, // NOLINT(runtime/references)
|
||||
const char** list,
|
||||
size_t numberTopics);
|
||||
|
||||
size_t _chunkedAvailable(size_t index);
|
||||
const uint8_t* _chunkedData(size_t index) const;
|
||||
};
|
||||
|
||||
} // end namespace espMqttClientInternals
|
||||
316
lib/espMqttClient/src/Packets/Parser.cpp
Normal file
316
lib/espMqttClient/src/Packets/Parser.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "Parser.h"
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
uint8_t IncomingPacket::qos() const {
|
||||
if ((fixedHeader.packetType & 0xF0) != PacketType.PUBLISH) return 0;
|
||||
return (fixedHeader.packetType & 0x06) >> 1; // mask 0x00000110
|
||||
}
|
||||
|
||||
bool IncomingPacket::retain() const {
|
||||
if ((fixedHeader.packetType & 0xF0) != PacketType.PUBLISH) return 0;
|
||||
return fixedHeader.packetType & 0x01; // mask 0x00000001
|
||||
}
|
||||
|
||||
bool IncomingPacket::dup() const {
|
||||
if ((fixedHeader.packetType & 0xF0) != PacketType.PUBLISH) return 0;
|
||||
return fixedHeader.packetType & 0x08; // mask 0x00001000
|
||||
}
|
||||
|
||||
void IncomingPacket::reset() {
|
||||
fixedHeader.packetType = 0;
|
||||
variableHeader.topicLength = 0;
|
||||
variableHeader.fixed.packetId = 0;
|
||||
payload.index = 0;
|
||||
payload.length = 0;
|
||||
}
|
||||
|
||||
Parser::Parser()
|
||||
: _data(nullptr)
|
||||
, _len(0)
|
||||
, _bytesRead(0)
|
||||
, _bytePos(0)
|
||||
, _parse(_fixedHeader)
|
||||
, _packet()
|
||||
, _payloadBuffer{0} {
|
||||
// empty
|
||||
}
|
||||
|
||||
ParserResult Parser::parse(const uint8_t* data, size_t len, size_t* bytesRead) {
|
||||
_data = data;
|
||||
_len = len;
|
||||
_bytesRead = 0;
|
||||
ParserResult result = ParserResult::awaitData;
|
||||
while (result == ParserResult::awaitData && _bytesRead < _len) {
|
||||
result = _parse(this);
|
||||
++_bytesRead;
|
||||
}
|
||||
(*bytesRead) += _bytesRead;
|
||||
return result;
|
||||
}
|
||||
|
||||
const IncomingPacket& Parser::getPacket() const {
|
||||
return _packet;
|
||||
}
|
||||
|
||||
void Parser::reset() {
|
||||
_parse = _fixedHeader;
|
||||
_bytesRead = 0;
|
||||
_bytePos = 0;
|
||||
_packet.reset();
|
||||
}
|
||||
|
||||
ParserResult Parser::_fixedHeader(Parser* p) {
|
||||
p->_packet.reset();
|
||||
p->_packet.fixedHeader.packetType = p->_data[p->_bytesRead];
|
||||
|
||||
// keep PUBLISH out of the switch and handle in separate if/else
|
||||
if ((p->_packet.fixedHeader.packetType & 0xF0) == PacketType.PUBLISH) {
|
||||
uint8_t headerFlags = p->_packet.fixedHeader.packetType & 0x0F;
|
||||
/* flags can be: 0b0000 --> no dup, qos 0, no retain
|
||||
0x0001 --> no dup, qos 0, retain
|
||||
0x0010 --> no dup, qos 1, no retain
|
||||
0x0011 --> no dup, qos 1, retain
|
||||
0x0100 --> no dup, qos 2, no retain
|
||||
0x0101 --> no dup, qos 2, retain
|
||||
0x1010 --> dup, qos 1, no retain
|
||||
0x1011 --> dup, qos 1, retain
|
||||
0x1100 --> dup, qos 2, no retain
|
||||
0x1101 --> dup, qos 2, retain
|
||||
*/
|
||||
if (headerFlags <= 0x05 || headerFlags >= 0x0A) {
|
||||
p->_parse = _remainingLengthVariable;
|
||||
p->_bytePos = 0;
|
||||
} else {
|
||||
emc_log_w("Invalid packet header: 0x%02x", p->_packet.fixedHeader.packetType);
|
||||
return ParserResult::protocolError;
|
||||
}
|
||||
} else {
|
||||
switch (p->_packet.fixedHeader.packetType) {
|
||||
case PacketType.CONNACK | HeaderFlag.CONNACK_RESERVED:
|
||||
case PacketType.PUBACK | HeaderFlag.PUBACK_RESERVED:
|
||||
case PacketType.PUBREC | HeaderFlag.PUBREC_RESERVED:
|
||||
case PacketType.PUBREL | HeaderFlag.PUBREL_RESERVED:
|
||||
case PacketType.PUBCOMP | HeaderFlag.PUBCOMP_RESERVED:
|
||||
case PacketType.UNSUBACK | HeaderFlag.UNSUBACK_RESERVED:
|
||||
p->_parse = _remainingLengthFixed;
|
||||
break;
|
||||
case PacketType.SUBACK | HeaderFlag.SUBACK_RESERVED:
|
||||
p->_parse = _remainingLengthVariable;
|
||||
p->_bytePos = 0;
|
||||
break;
|
||||
case PacketType.PINGRESP | HeaderFlag.PINGRESP_RESERVED:
|
||||
p->_parse = _remainingLengthNone;
|
||||
break;
|
||||
default:
|
||||
emc_log_w("Invalid packet header: 0x%02x", p->_packet.fixedHeader.packetType);
|
||||
return ParserResult::protocolError;
|
||||
}
|
||||
}
|
||||
emc_log_i("Packet type: 0x%02x", p->_packet.fixedHeader.packetType);
|
||||
return ParserResult::awaitData;
|
||||
}
|
||||
|
||||
ParserResult Parser::_remainingLengthFixed(Parser* p) {
|
||||
p->_packet.fixedHeader.remainingLength.remainingLength = p->_data[p->_bytesRead];
|
||||
|
||||
if (p->_packet.fixedHeader.remainingLength.remainingLength == 2) { // variable header is 2 bytes long
|
||||
if ((p->_packet.fixedHeader.packetType & 0xF0) != PacketType.CONNACK) {
|
||||
p->_parse = _varHeaderPacketId1;
|
||||
} else {
|
||||
p->_parse = _varHeaderConnack1;
|
||||
}
|
||||
emc_log_i("Remaining length: %zu", p->_packet.fixedHeader.remainingLength.remainingLength);
|
||||
return ParserResult::awaitData;
|
||||
}
|
||||
p->_parse = _fixedHeader;
|
||||
emc_log_w("Invalid remaining length (fixed): %zu", p->_packet.fixedHeader.remainingLength.remainingLength);
|
||||
return ParserResult::protocolError;
|
||||
}
|
||||
|
||||
ParserResult Parser::_remainingLengthVariable(Parser* p) {
|
||||
p->_packet.fixedHeader.remainingLength.remainingLengthRaw[p->_bytePos] = p->_data[p->_bytesRead];
|
||||
if (p->_packet.fixedHeader.remainingLength.remainingLengthRaw[p->_bytePos] & 0x80) {
|
||||
p->_bytePos++;
|
||||
if (p->_bytePos == 4) {
|
||||
emc_log_w("Invalid remaining length (variable)");
|
||||
return ParserResult::protocolError;
|
||||
} else {
|
||||
return ParserResult::awaitData;
|
||||
}
|
||||
}
|
||||
|
||||
// no need to check for negative decoded length, check is already done
|
||||
p->_packet.fixedHeader.remainingLength.remainingLength = decodeRemainingLength(p->_packet.fixedHeader.remainingLength.remainingLengthRaw);
|
||||
|
||||
if ((p->_packet.fixedHeader.packetType & 0xF0) == PacketType.PUBLISH) {
|
||||
p->_parse = _varHeaderTopicLength1;
|
||||
emc_log_i("Remaining length: %zu", p->_packet.fixedHeader.remainingLength.remainingLength);
|
||||
return ParserResult::awaitData;
|
||||
} else {
|
||||
int32_t payloadSize = p->_packet.fixedHeader.remainingLength.remainingLength - 2; // total - packet ID
|
||||
if (0 < payloadSize && payloadSize < EMC_PAYLOAD_BUFFER_SIZE) {
|
||||
p->_bytePos = 0;
|
||||
p->_packet.payload.data = p->_payloadBuffer;
|
||||
p->_packet.payload.index = 0;
|
||||
p->_packet.payload.length = payloadSize;
|
||||
p->_packet.payload.total = payloadSize;
|
||||
p->_parse = _varHeaderPacketId1;
|
||||
emc_log_i("Remaining length: %zu", p->_packet.fixedHeader.remainingLength.remainingLength);
|
||||
return ParserResult::awaitData;
|
||||
} else {
|
||||
emc_log_w("Invalid payload length");
|
||||
}
|
||||
}
|
||||
p->_parse = _fixedHeader;
|
||||
return ParserResult::protocolError;
|
||||
}
|
||||
|
||||
ParserResult Parser::_remainingLengthNone(Parser* p) {
|
||||
p->_packet.fixedHeader.remainingLength.remainingLength = p->_data[p->_bytesRead];
|
||||
p->_parse = _fixedHeader;
|
||||
if (p->_packet.fixedHeader.remainingLength.remainingLength == 0) {
|
||||
emc_log_i("Remaining length: %zu", p->_packet.fixedHeader.remainingLength.remainingLength);
|
||||
return ParserResult::packet;
|
||||
}
|
||||
emc_log_w("Invalid remaining length (none)");
|
||||
return ParserResult::protocolError;
|
||||
}
|
||||
|
||||
ParserResult Parser::_varHeaderConnack1(Parser* p) {
|
||||
uint8_t data = p->_data[p->_bytesRead];
|
||||
if (data < 2) { // session present flag: equal to 0 or 1
|
||||
p->_packet.variableHeader.fixed.connackVarHeader.sessionPresent = data;
|
||||
p->_parse = _varHeaderConnack2;
|
||||
return ParserResult::awaitData;
|
||||
}
|
||||
p->_parse = _fixedHeader;
|
||||
emc_log_w("Invalid session flags");
|
||||
return ParserResult::protocolError;
|
||||
}
|
||||
|
||||
ParserResult Parser::_varHeaderConnack2(Parser* p) {
|
||||
uint8_t data = p->_data[p->_bytesRead];
|
||||
p->_parse = _fixedHeader;
|
||||
if (data <= 5) { // connect return code max is 5
|
||||
p->_packet.variableHeader.fixed.connackVarHeader.returnCode = data;
|
||||
emc_log_i("Packet complete");
|
||||
return ParserResult::packet;
|
||||
}
|
||||
emc_log_w("Invalid connack return code");
|
||||
return ParserResult::protocolError;
|
||||
}
|
||||
|
||||
ParserResult Parser::_varHeaderPacketId1(Parser* p) {
|
||||
p->_packet.variableHeader.fixed.packetId |= p->_data[p->_bytesRead] << 8;
|
||||
p->_parse = _varHeaderPacketId2;
|
||||
return ParserResult::awaitData;
|
||||
}
|
||||
|
||||
ParserResult Parser::_varHeaderPacketId2(Parser* p) {
|
||||
p->_packet.variableHeader.fixed.packetId |= p->_data[p->_bytesRead];
|
||||
p->_parse = _fixedHeader;
|
||||
if (p->_packet.variableHeader.fixed.packetId != 0) {
|
||||
emc_log_i("Packet variable header complete");
|
||||
if ((p->_packet.fixedHeader.packetType & 0xF0) == PacketType.SUBACK) {
|
||||
p->_parse = _payloadSuback;
|
||||
return ParserResult::awaitData;
|
||||
} else if ((p->_packet.fixedHeader.packetType & 0xF0) == PacketType.PUBLISH) {
|
||||
p->_packet.payload.total -= 2; // substract packet id length from payload
|
||||
if (p->_packet.payload.total == 0) {
|
||||
p->_parse = _fixedHeader;
|
||||
return ParserResult::packet;
|
||||
} else {
|
||||
p->_parse = _payloadPublish;
|
||||
}
|
||||
return ParserResult::awaitData;
|
||||
} else {
|
||||
return ParserResult::packet;
|
||||
}
|
||||
} else {
|
||||
emc_log_w("Invalid packet id");
|
||||
return ParserResult::protocolError;
|
||||
}
|
||||
}
|
||||
|
||||
ParserResult Parser::_varHeaderTopicLength1(Parser* p) {
|
||||
p->_packet.variableHeader.topicLength = p->_data[p->_bytesRead] << 8;
|
||||
p->_parse = _varHeaderTopicLength2;
|
||||
return ParserResult::awaitData;
|
||||
}
|
||||
|
||||
ParserResult Parser::_varHeaderTopicLength2(Parser* p) {
|
||||
p->_packet.variableHeader.topicLength |= p->_data[p->_bytesRead];
|
||||
size_t maxTopicLength =
|
||||
p->_packet.fixedHeader.remainingLength.remainingLength
|
||||
- 2 // topic length bytes
|
||||
- ((p->_packet.fixedHeader.packetType & (HeaderFlag.PUBLISH_QOS1 | HeaderFlag.PUBLISH_QOS2)) ? 2 : 0);
|
||||
if (p->_packet.variableHeader.topicLength <= maxTopicLength) {
|
||||
p->_parse = _varHeaderTopic;
|
||||
p->_bytePos = 0;
|
||||
p->_packet.payload.total = p->_packet.fixedHeader.remainingLength.remainingLength - 2 - p->_packet.variableHeader.topicLength;
|
||||
return ParserResult::awaitData;
|
||||
}
|
||||
emc_log_w("Invalid topic length: %u > %zu", p->_packet.variableHeader.topicLength, maxTopicLength);
|
||||
p->_parse = _fixedHeader;
|
||||
return ParserResult::protocolError;
|
||||
}
|
||||
|
||||
ParserResult Parser::_varHeaderTopic(Parser* p) {
|
||||
// no checking for character [MQTT-3.3.2-1] [MQTT-3.3.2-2]
|
||||
p->_packet.variableHeader.topic[p->_bytePos] = static_cast<char>(p->_data[p->_bytesRead]);
|
||||
p->_bytePos++;
|
||||
if (p->_bytePos == p->_packet.variableHeader.topicLength || p->_bytePos == EMC_MAX_TOPIC_LENGTH) {
|
||||
p->_packet.variableHeader.topic[p->_bytePos] = 0x00; // add c-string delimiter
|
||||
emc_log_i("Packet variable header topic complete");
|
||||
if (p->_packet.fixedHeader.packetType & (HeaderFlag.PUBLISH_QOS1 | HeaderFlag.PUBLISH_QOS2)) {
|
||||
p->_parse = _varHeaderPacketId1;
|
||||
} else if (p->_packet.payload.total == 0) {
|
||||
p->_parse = _fixedHeader;
|
||||
return ParserResult::packet;
|
||||
} else {
|
||||
p->_parse = _payloadPublish;
|
||||
}
|
||||
}
|
||||
return ParserResult::awaitData;
|
||||
}
|
||||
|
||||
ParserResult Parser::_payloadSuback(Parser* p) {
|
||||
uint8_t data = p->_data[p->_bytesRead];
|
||||
if (data < 0x03 || data == 0x80) {
|
||||
p->_payloadBuffer[p->_bytePos] = data;
|
||||
p->_bytePos++;
|
||||
} else {
|
||||
p->_parse = _fixedHeader;
|
||||
emc_log_w("Invalid suback return code");
|
||||
return ParserResult::protocolError;
|
||||
}
|
||||
if (p->_bytePos == p->_packet.payload.total) {
|
||||
p->_parse = _fixedHeader;
|
||||
emc_log_i("Packet complete");
|
||||
return ParserResult::packet;
|
||||
}
|
||||
return ParserResult::awaitData;
|
||||
}
|
||||
|
||||
ParserResult Parser::_payloadPublish(Parser* p) {
|
||||
p->_packet.payload.index += p->_packet.payload.length;
|
||||
p->_packet.payload.data = &p->_data[p->_bytesRead];
|
||||
emc_log_i("payload: index %zu, total %zu, avail %zu/%zu", p->_packet.payload.index, p->_packet.payload.total, p->_len - p->_bytesRead, p->_len);
|
||||
p->_packet.payload.length = std::min(p->_len - p->_bytesRead, p->_packet.payload.total - p->_packet.payload.index);
|
||||
p->_bytesRead += p->_packet.payload.length - 1; // compensate for increment in _parse-loop
|
||||
if (p->_packet.payload.index + p->_packet.payload.length == p->_packet.payload.total) {
|
||||
p->_parse = _fixedHeader;
|
||||
}
|
||||
return ParserResult::packet;
|
||||
}
|
||||
|
||||
} // end namespace espMqttClientInternals
|
||||
100
lib/espMqttClient/src/Packets/Parser.h
Normal file
100
lib/espMqttClient/src/Packets/Parser.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../Config.h"
|
||||
#include "Constants.h"
|
||||
#include "../Logging.h"
|
||||
#include "RemainingLength.h"
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
struct IncomingPacket {
|
||||
struct __attribute__((__packed__)) {
|
||||
MQTTPacketType packetType;
|
||||
union {
|
||||
size_t remainingLength;
|
||||
uint8_t remainingLengthRaw[4];
|
||||
} remainingLength;
|
||||
} fixedHeader;
|
||||
struct __attribute__((__packed__)) {
|
||||
uint16_t topicLength;
|
||||
char topic[EMC_MAX_TOPIC_LENGTH + 1]; // + 1 for c-string delimiter
|
||||
union {
|
||||
struct {
|
||||
uint8_t sessionPresent;
|
||||
uint8_t returnCode;
|
||||
} connackVarHeader;
|
||||
uint16_t packetId;
|
||||
} fixed;
|
||||
} variableHeader;
|
||||
struct {
|
||||
const uint8_t* data;
|
||||
size_t length;
|
||||
size_t index;
|
||||
size_t total;
|
||||
} payload;
|
||||
|
||||
uint8_t qos() const;
|
||||
bool retain() const;
|
||||
bool dup() const;
|
||||
void reset();
|
||||
};
|
||||
|
||||
enum class ParserResult : uint8_t {
|
||||
awaitData,
|
||||
packet,
|
||||
protocolError
|
||||
};
|
||||
|
||||
class Parser;
|
||||
typedef ParserResult(*ParserFunc)(Parser*);
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
Parser();
|
||||
ParserResult parse(const uint8_t* data, size_t len, size_t* bytesRead);
|
||||
const IncomingPacket& getPacket() const;
|
||||
void reset();
|
||||
|
||||
private:
|
||||
// keep data variables in class to avoid copying on every iteration of the parser
|
||||
const uint8_t* _data;
|
||||
size_t _len;
|
||||
size_t _bytesRead;
|
||||
size_t _bytePos;
|
||||
ParserFunc _parse;
|
||||
IncomingPacket _packet;
|
||||
uint8_t _payloadBuffer[EMC_PAYLOAD_BUFFER_SIZE];
|
||||
|
||||
static ParserResult _fixedHeader(Parser* p);
|
||||
static ParserResult _remainingLengthFixed(Parser* p);
|
||||
static ParserResult _remainingLengthNone(Parser* p);
|
||||
static ParserResult _remainingLengthVariable(Parser* p);
|
||||
|
||||
|
||||
static ParserResult _varHeaderConnack1(Parser* p);
|
||||
static ParserResult _varHeaderConnack2(Parser* p);
|
||||
|
||||
static ParserResult _varHeaderPacketId1(Parser* p);
|
||||
static ParserResult _varHeaderPacketId2(Parser* p);
|
||||
|
||||
static ParserResult _varHeaderTopicLength1(Parser* p);
|
||||
static ParserResult _varHeaderTopicLength2(Parser* p);
|
||||
static ParserResult _varHeaderTopic(Parser* p);
|
||||
|
||||
static ParserResult _payloadSuback(Parser* p);
|
||||
static ParserResult _payloadPublish(Parser* p);
|
||||
};
|
||||
|
||||
} // end namespace espMqttClientInternals
|
||||
57
lib/espMqttClient/src/Packets/RemainingLength.cpp
Normal file
57
lib/espMqttClient/src/Packets/RemainingLength.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "RemainingLength.h"
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
int32_t decodeRemainingLength(const uint8_t* stream) {
|
||||
uint32_t multiplier = 1;
|
||||
int32_t remainingLength = 0;
|
||||
uint8_t currentByte = 0;
|
||||
uint8_t encodedByte;
|
||||
|
||||
do {
|
||||
encodedByte = stream[currentByte++];
|
||||
remainingLength += (encodedByte & 127) * multiplier;
|
||||
if (multiplier > 128 * 128 * 128) {
|
||||
emc_log_e("Malformed Remaining Length");
|
||||
return -1;
|
||||
}
|
||||
multiplier *= 128;
|
||||
} while ((encodedByte & 128) != 0);
|
||||
|
||||
return remainingLength;
|
||||
}
|
||||
|
||||
uint8_t remainingLengthLength(uint32_t remainingLength) {
|
||||
if (remainingLength < 128) return 1;
|
||||
if (remainingLength < 16384) return 2;
|
||||
if (remainingLength < 2097152) return 3;
|
||||
if (remainingLength > 268435455) return 0;
|
||||
return 4;
|
||||
}
|
||||
|
||||
uint8_t encodeRemainingLength(uint32_t remainingLength, uint8_t* 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;
|
||||
}
|
||||
|
||||
} // namespace espMqttClientInternals
|
||||
32
lib/espMqttClient/src/Packets/RemainingLength.h
Normal file
32
lib/espMqttClient/src/Packets/RemainingLength.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "../Logging.h"
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
// Calculations are based on non normative comment in section 2.2.3 Remaining Length of the MQTT specification
|
||||
|
||||
// returns decoded length based on input stream
|
||||
// stream is expected to contain full encoded remaining length
|
||||
// return -1 on error.
|
||||
int32_t decodeRemainingLength(const uint8_t* stream);
|
||||
|
||||
|
||||
// returns the number of bytes needed to encode the remaining length
|
||||
uint8_t remainingLengthLength(uint32_t remainingLength);
|
||||
|
||||
// encodes the given remaining length to destination and returns number of bytes used
|
||||
// destination is expected to be large enough to hold the number of bytes needed
|
||||
uint8_t encodeRemainingLength(uint32_t remainingLength, uint8_t* destination);
|
||||
|
||||
} // namespace espMqttClientInternals
|
||||
26
lib/espMqttClient/src/Packets/String.cpp
Normal file
26
lib/espMqttClient/src/Packets/String.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "String.h"
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
size_t encodeString(const char* source, uint8_t* dest) {
|
||||
size_t length = strlen(source);
|
||||
if (length > 65535) {
|
||||
emc_log_e("String length error");
|
||||
return 0;
|
||||
}
|
||||
|
||||
dest[0] = static_cast<uint16_t>(length) >> 8;
|
||||
dest[1] = static_cast<uint16_t>(length) & 0xFF;
|
||||
memcpy(&dest[2], source, length);
|
||||
return 2 + length;
|
||||
}
|
||||
|
||||
} // namespace espMqttClientInternals
|
||||
22
lib/espMqttClient/src/Packets/String.h
Normal file
22
lib/espMqttClient/src/Packets/String.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cstring> // memcpy
|
||||
|
||||
#include "../Logging.h"
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
// encodes the given source string into destination and returns number of bytes used
|
||||
// destination is expected to be large enough to hold the number of bytes needed
|
||||
size_t encodeString(const char* source, uint8_t* dest);
|
||||
|
||||
} // namespace espMqttClientInternals
|
||||
58
lib/espMqttClient/src/Transport/ClientAsync.cpp
Normal file
58
lib/espMqttClient/src/Transport/ClientAsync.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||
|
||||
#include "ClientAsync.h"
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
ClientAsync::ClientAsync()
|
||||
: client()
|
||||
, availableData(0)
|
||||
, bufData(nullptr) {
|
||||
// empty
|
||||
}
|
||||
|
||||
bool ClientAsync::connect(IPAddress ip, uint16_t port) {
|
||||
return client.connect(ip, port);
|
||||
}
|
||||
|
||||
bool ClientAsync::connect(const char* host, uint16_t port) {
|
||||
return client.connect(host, port);
|
||||
}
|
||||
|
||||
size_t ClientAsync::write(const uint8_t* buf, size_t size) {
|
||||
return client.write(reinterpret_cast<const char*>(buf), size);
|
||||
}
|
||||
|
||||
int ClientAsync::read(uint8_t* buf, size_t size) {
|
||||
size_t willRead = std::min(size, availableData);
|
||||
memcpy(buf, bufData, std::min(size, availableData));
|
||||
if (availableData > size) {
|
||||
emc_log_w("Buffer is smaller than available data: %zu - %zu", size, availableData);
|
||||
}
|
||||
availableData = 0;
|
||||
return willRead;
|
||||
}
|
||||
|
||||
void ClientAsync::stop() {
|
||||
client.close(false);
|
||||
}
|
||||
|
||||
bool ClientAsync::connected() {
|
||||
return client.connected();
|
||||
}
|
||||
|
||||
bool ClientAsync::disconnected() {
|
||||
return client.disconnected();
|
||||
}
|
||||
|
||||
} // namespace espMqttClientInternals
|
||||
|
||||
#endif
|
||||
44
lib/espMqttClient/src/Transport/ClientAsync.h
Normal file
44
lib/espMqttClient/src/Transport/ClientAsync.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include <AsyncTCP.h>
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
#include <ESPAsyncTCP.h>
|
||||
#endif
|
||||
|
||||
#include "Transport.h"
|
||||
#include "../Config.h"
|
||||
#include "../Logging.h"
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
class ClientAsync : public Transport {
|
||||
public:
|
||||
ClientAsync();
|
||||
bool connect(IPAddress ip, uint16_t port) override;
|
||||
bool connect(const char* host, uint16_t port) override;
|
||||
size_t write(const uint8_t* buf, size_t size) override;
|
||||
int read(uint8_t* buf, size_t size) override;
|
||||
void stop() override;
|
||||
bool connected() override;
|
||||
bool disconnected() override;
|
||||
|
||||
AsyncClient client;
|
||||
size_t availableData;
|
||||
uint8_t* bufData;
|
||||
};
|
||||
|
||||
} // namespace espMqttClientInternals
|
||||
|
||||
#endif
|
||||
92
lib/espMqttClient/src/Transport/ClientPosix.cpp
Normal file
92
lib/espMqttClient/src/Transport/ClientPosix.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "ClientPosix.h"
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
ClientPosix::ClientPosix()
|
||||
: _sockfd(-1)
|
||||
, _host() {
|
||||
// empty
|
||||
}
|
||||
|
||||
ClientPosix::~ClientPosix() {
|
||||
ClientPosix::stop();
|
||||
}
|
||||
|
||||
bool ClientPosix::connect(IPAddress ip, uint16_t port) {
|
||||
if (connected()) stop();
|
||||
|
||||
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (_sockfd < 0) {
|
||||
emc_log_e("Error %d opening socket", errno);
|
||||
}
|
||||
|
||||
int flag = 1;
|
||||
if (setsockopt(_sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int)) < 0) {
|
||||
emc_log_e("Error %d disabling nagle", errno);
|
||||
}
|
||||
|
||||
memset(&_host, 0, sizeof(_host));
|
||||
_host.sin_family = AF_INET;
|
||||
_host.sin_addr.s_addr = htonl(uint32_t(ip));
|
||||
_host.sin_port = ::htons(port);
|
||||
|
||||
int ret = ::connect(_sockfd, (struct sockaddr *)&_host, sizeof(_host));
|
||||
|
||||
if (ret < 0) {
|
||||
emc_log_e("Error connecting: %d - (%d) %s", ret, errno, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
emc_log_i("Connected");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClientPosix::connect(const char* host, uint16_t port) {
|
||||
// tbi
|
||||
(void) host;
|
||||
(void) port;
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t ClientPosix::write(const uint8_t* buf, size_t size) {
|
||||
return ::send(_sockfd, buf, size, 0);
|
||||
}
|
||||
|
||||
int ClientPosix::read(uint8_t* buf, size_t size) {
|
||||
int ret = ::recv(_sockfd, buf, size, MSG_DONTWAIT);
|
||||
/*
|
||||
if (ret < 0) {
|
||||
emc_log_e("Error reading: %s", strerror(errno));
|
||||
}
|
||||
*/
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ClientPosix::stop() {
|
||||
if (_sockfd >= 0) {
|
||||
::close(_sockfd);
|
||||
_sockfd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool ClientPosix::connected() {
|
||||
return _sockfd >= 0;
|
||||
}
|
||||
|
||||
bool ClientPosix::disconnected() {
|
||||
return _sockfd < 0;
|
||||
}
|
||||
|
||||
} // namespace espMqttClientInternals
|
||||
|
||||
#endif
|
||||
51
lib/espMqttClient/src/Transport/ClientPosix.h
Normal file
51
lib/espMqttClient/src/Transport/ClientPosix.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#include "Transport.h" // includes IPAddress
|
||||
#include "../Logging.h"
|
||||
|
||||
#ifndef EMC_POSIX_PEEK_SIZE
|
||||
#define EMC_POSIX_PEEK_SIZE 1500
|
||||
#endif
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
class ClientPosix : public Transport {
|
||||
public:
|
||||
ClientPosix();
|
||||
~ClientPosix();
|
||||
bool connect(IPAddress ip, uint16_t port) override;
|
||||
bool connect(const char* host, uint16_t port) override;
|
||||
size_t write(const uint8_t* buf, size_t size) override;
|
||||
int read(uint8_t* buf, size_t size) override;
|
||||
void stop() override;
|
||||
bool connected() override;
|
||||
bool disconnected() override;
|
||||
|
||||
protected:
|
||||
int _sockfd;
|
||||
struct sockaddr_in _host;
|
||||
};
|
||||
|
||||
} // namespace espMqttClientInternals
|
||||
|
||||
#endif
|
||||
71
lib/espMqttClient/src/Transport/ClientSecureSync.cpp
Normal file
71
lib/espMqttClient/src/Transport/ClientSecureSync.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||
|
||||
#include "ClientSecureSync.h"
|
||||
#include <lwip/sockets.h> // socket options
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
ClientSecureSync::ClientSecureSync()
|
||||
: client() {
|
||||
// empty
|
||||
}
|
||||
|
||||
bool ClientSecureSync::connect(IPAddress ip, uint16_t port) {
|
||||
bool ret = client.connect(ip, port); // implicit conversion of return code int --> bool
|
||||
if (ret) {
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
client.setNoDelay(true);
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
// Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure
|
||||
int val = true;
|
||||
client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
|
||||
#endif
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ClientSecureSync::connect(const char* host, uint16_t port) {
|
||||
bool ret = client.connect(host, port); // implicit conversion of return code int --> bool
|
||||
if (ret) {
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
client.setNoDelay(true);
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
// Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure
|
||||
int val = true;
|
||||
client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
|
||||
#endif
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t ClientSecureSync::write(const uint8_t* buf, size_t size) {
|
||||
return client.write(buf, size);
|
||||
}
|
||||
|
||||
int ClientSecureSync::read(uint8_t* buf, size_t size) {
|
||||
return client.read(buf, size);
|
||||
}
|
||||
|
||||
void ClientSecureSync::stop() {
|
||||
client.stop();
|
||||
}
|
||||
|
||||
bool ClientSecureSync::connected() {
|
||||
return client.connected();
|
||||
}
|
||||
|
||||
bool ClientSecureSync::disconnected() {
|
||||
return !client.connected();
|
||||
}
|
||||
|
||||
} // namespace espMqttClientInternals
|
||||
|
||||
#endif
|
||||
34
lib/espMqttClient/src/Transport/ClientSecureSync.h
Normal file
34
lib/espMqttClient/src/Transport/ClientSecureSync.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||
|
||||
#include <WiFiClientSecure.h> // includes IPAddress
|
||||
|
||||
#include "Transport.h"
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
class ClientSecureSync : public Transport {
|
||||
public:
|
||||
ClientSecureSync();
|
||||
bool connect(IPAddress ip, uint16_t port) override;
|
||||
bool connect(const char* host, uint16_t port) override;
|
||||
size_t write(const uint8_t* buf, size_t size) override;
|
||||
int read(uint8_t* buf, size_t size) override;
|
||||
void stop() override;
|
||||
bool connected() override;
|
||||
bool disconnected() override;
|
||||
WiFiClientSecure client;
|
||||
};
|
||||
|
||||
} // namespace espMqttClientInternals
|
||||
|
||||
#endif
|
||||
71
lib/espMqttClient/src/Transport/ClientSync.cpp
Normal file
71
lib/espMqttClient/src/Transport/ClientSync.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||
|
||||
#include "ClientSync.h"
|
||||
#include <lwip/sockets.h> // socket options
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
ClientSync::ClientSync()
|
||||
: client() {
|
||||
// empty
|
||||
}
|
||||
|
||||
bool ClientSync::connect(IPAddress ip, uint16_t port) {
|
||||
bool ret = client.connect(ip, port); // implicit conversion of return code int --> bool
|
||||
if (ret) {
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
client.setNoDelay(true);
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
// Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure (for consistency also here)
|
||||
int val = true;
|
||||
client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
|
||||
#endif
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ClientSync::connect(const char* host, uint16_t port) {
|
||||
bool ret = client.connect(host, port); // implicit conversion of return code int --> bool
|
||||
if (ret) {
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
client.setNoDelay(true);
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
// Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure (for consistency also here)
|
||||
int val = true;
|
||||
client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
|
||||
#endif
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t ClientSync::write(const uint8_t* buf, size_t size) {
|
||||
return client.write(buf, size);
|
||||
}
|
||||
|
||||
int ClientSync::read(uint8_t* buf, size_t size) {
|
||||
return client.read(buf, size);
|
||||
}
|
||||
|
||||
void ClientSync::stop() {
|
||||
client.stop();
|
||||
}
|
||||
|
||||
bool ClientSync::connected() {
|
||||
return client.connected();
|
||||
}
|
||||
|
||||
bool ClientSync::disconnected() {
|
||||
return !client.connected();
|
||||
}
|
||||
|
||||
} // namespace espMqttClientInternals
|
||||
|
||||
#endif
|
||||
34
lib/espMqttClient/src/Transport/ClientSync.h
Normal file
34
lib/espMqttClient/src/Transport/ClientSync.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||
|
||||
#include <WiFiClient.h> // includes IPAddress
|
||||
|
||||
#include "Transport.h"
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
class ClientSync : public Transport {
|
||||
public:
|
||||
ClientSync();
|
||||
bool connect(IPAddress ip, uint16_t port) override;
|
||||
bool connect(const char* host, uint16_t port) override;
|
||||
size_t write(const uint8_t* buf, size_t size) override;
|
||||
int read(uint8_t* buf, size_t size) override;
|
||||
void stop() override;
|
||||
bool connected() override;
|
||||
bool disconnected() override;
|
||||
WiFiClient client;
|
||||
};
|
||||
|
||||
} // namespace espMqttClientInternals
|
||||
|
||||
#endif
|
||||
32
lib/espMqttClient/src/Transport/IPAddress.cpp
Normal file
32
lib/espMqttClient/src/Transport/IPAddress.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
#include "IPAddress.h"
|
||||
|
||||
IPAddress::IPAddress()
|
||||
: _address(0) {
|
||||
// empty
|
||||
}
|
||||
|
||||
IPAddress::IPAddress(uint8_t p0, uint8_t p1, uint8_t p2, uint8_t p3)
|
||||
: _address(0) {
|
||||
_address = (uint32_t)p0 << 24 | (uint32_t)p1 << 16 | (uint32_t)p2 << 8 | p3;
|
||||
}
|
||||
|
||||
IPAddress::IPAddress(uint32_t address)
|
||||
: _address(address) {
|
||||
// empty
|
||||
}
|
||||
|
||||
IPAddress::operator uint32_t() {
|
||||
return _address;
|
||||
}
|
||||
|
||||
#endif
|
||||
28
lib/espMqttClient/src/Transport/IPAddress.h
Normal file
28
lib/espMqttClient/src/Transport/IPAddress.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(ARDUINO)
|
||||
#include <IPAddress.h>
|
||||
#else
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class IPAddress {
|
||||
public:
|
||||
IPAddress();
|
||||
IPAddress(uint8_t p0, uint8_t p1, uint8_t p2, uint8_t p3);
|
||||
explicit IPAddress(uint32_t address);
|
||||
operator uint32_t();
|
||||
|
||||
protected:
|
||||
uint32_t _address;
|
||||
};
|
||||
|
||||
#endif
|
||||
28
lib/espMqttClient/src/Transport/Transport.h
Normal file
28
lib/espMqttClient/src/Transport/Transport.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h> // size_t
|
||||
|
||||
#include "IPAddress.h"
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
class Transport {
|
||||
public:
|
||||
virtual bool connect(IPAddress ip, uint16_t port) = 0;
|
||||
virtual bool connect(const char* host, uint16_t port) = 0;
|
||||
virtual size_t write(const uint8_t* buf, size_t size) = 0;
|
||||
virtual int read(uint8_t* buf, size_t size) = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual bool connected() = 0;
|
||||
virtual bool disconnected() = 0;
|
||||
};
|
||||
|
||||
} // namespace espMqttClientInternals
|
||||
51
lib/espMqttClient/src/TypeDefs.cpp
Normal file
51
lib/espMqttClient/src/TypeDefs.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
Parts are based on the original work of Marvin Roger:
|
||||
https://github.com/marvinroger/async-mqtt-client
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "TypeDefs.h"
|
||||
|
||||
namespace espMqttClientTypes {
|
||||
|
||||
const char* disconnectReasonToString(DisconnectReason reason) {
|
||||
switch (reason) {
|
||||
case DisconnectReason::USER_OK: return "No error";
|
||||
case DisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION: return "Unacceptable protocol version";
|
||||
case DisconnectReason::MQTT_IDENTIFIER_REJECTED: return "Identified rejected";
|
||||
case DisconnectReason::MQTT_SERVER_UNAVAILABLE: return "Server unavailable";
|
||||
case DisconnectReason::MQTT_MALFORMED_CREDENTIALS: return "Malformed credentials";
|
||||
case DisconnectReason::MQTT_NOT_AUTHORIZED: return "Not authorized";
|
||||
case DisconnectReason::TLS_BAD_FINGERPRINT: return "Bad fingerprint";
|
||||
case DisconnectReason::TCP_DISCONNECTED: return "TCP disconnected";
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
const char* subscribeReturncodeToString(SubscribeReturncode returnCode) {
|
||||
switch (returnCode) {
|
||||
case SubscribeReturncode::QOS0: return "QoS 0";
|
||||
case SubscribeReturncode::QOS1: return "QoS 1";
|
||||
case SubscribeReturncode::QOS2: return "QoS 2";
|
||||
case SubscribeReturncode::FAIL: return "Failed";
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
const char* errorToString(Error error) {
|
||||
switch (error) {
|
||||
case Error::SUCCESS: return "Success";
|
||||
case Error::OUT_OF_MEMORY: return "Out of memory";
|
||||
case Error::MAX_RETRIES: return "Maximum retries exceeded";
|
||||
case Error::MALFORMED_PARAMETER: return "Malformed parameters";
|
||||
case Error::MISC_ERROR: return "Misc error";
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
} // end namespace espMqttClientTypes
|
||||
73
lib/espMqttClient/src/TypeDefs.h
Normal file
73
lib/espMqttClient/src/TypeDefs.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
Parts are based on the original work of Marvin Roger:
|
||||
https://github.com/marvinroger/async-mqtt-client
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <functional>
|
||||
|
||||
namespace espMqttClientTypes {
|
||||
|
||||
enum class DisconnectReason : uint8_t {
|
||||
USER_OK = 0,
|
||||
MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1,
|
||||
MQTT_IDENTIFIER_REJECTED = 2,
|
||||
MQTT_SERVER_UNAVAILABLE = 3,
|
||||
MQTT_MALFORMED_CREDENTIALS = 4,
|
||||
MQTT_NOT_AUTHORIZED = 5,
|
||||
TLS_BAD_FINGERPRINT = 6,
|
||||
TCP_DISCONNECTED = 7
|
||||
};
|
||||
|
||||
const char* disconnectReasonToString(DisconnectReason reason);
|
||||
|
||||
enum class SubscribeReturncode : uint8_t {
|
||||
QOS0 = 0x00,
|
||||
QOS1 = 0x01,
|
||||
QOS2 = 0x02,
|
||||
FAIL = 0X80
|
||||
};
|
||||
|
||||
const char* subscribeReturncodeToString(SubscribeReturncode returnCode);
|
||||
|
||||
enum class Error : uint8_t {
|
||||
SUCCESS = 0,
|
||||
OUT_OF_MEMORY = 1,
|
||||
MAX_RETRIES = 2,
|
||||
MALFORMED_PARAMETER = 3,
|
||||
MISC_ERROR = 4
|
||||
};
|
||||
|
||||
const char* errorToString(Error error);
|
||||
|
||||
struct MessageProperties {
|
||||
uint8_t qos;
|
||||
bool dup;
|
||||
bool retain;
|
||||
uint16_t packetId;
|
||||
};
|
||||
|
||||
typedef std::function<void(bool sessionPresent)> OnConnectCallback;
|
||||
typedef std::function<void(DisconnectReason reason)> OnDisconnectCallback;
|
||||
typedef std::function<void(uint16_t packetId, const SubscribeReturncode* returncodes, size_t len)> OnSubscribeCallback;
|
||||
typedef std::function<void(uint16_t packetId)> OnUnsubscribeCallback;
|
||||
typedef std::function<void(const MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)> OnMessageCallback;
|
||||
typedef std::function<void(uint16_t packetId)> OnPublishCallback;
|
||||
typedef std::function<size_t(uint8_t* data, size_t maxSize, size_t index)> PayloadCallback;
|
||||
typedef std::function<void(uint16_t packetId, Error error)> OnErrorCallback;
|
||||
|
||||
enum class UseInternalTask {
|
||||
NO = 0,
|
||||
YES = 1,
|
||||
};
|
||||
|
||||
} // end namespace espMqttClientTypes
|
||||
113
lib/espMqttClient/src/espMqttClient.cpp
Normal file
113
lib/espMqttClient/src/espMqttClient.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "espMqttClient.h"
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
espMqttClient::espMqttClient()
|
||||
: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO)
|
||||
, _client() {
|
||||
_transport = &_client;
|
||||
}
|
||||
|
||||
espMqttClientSecure::espMqttClientSecure()
|
||||
: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO)
|
||||
, _client() {
|
||||
_transport = &_client;
|
||||
}
|
||||
|
||||
espMqttClientSecure& espMqttClientSecure::setInsecure() {
|
||||
_client.client.setInsecure();
|
||||
return *this;
|
||||
}
|
||||
|
||||
espMqttClientSecure& espMqttClientSecure::setFingerprint(const uint8_t fingerprint[20]) {
|
||||
_client.client.setFingerprint(fingerprint);
|
||||
return *this;
|
||||
}
|
||||
|
||||
espMqttClientSecure& espMqttClientSecure::setTrustAnchors(const X509List *ta) {
|
||||
_client.client.setTrustAnchors(ta);
|
||||
return *this;
|
||||
}
|
||||
|
||||
espMqttClientSecure& espMqttClientSecure::setClientRSACert(const X509List *cert, const PrivateKey *sk) {
|
||||
_client.client.setClientRSACert(cert, sk);
|
||||
return *this;
|
||||
}
|
||||
|
||||
espMqttClientSecure& espMqttClientSecure::setClientECCert(const X509List *cert, const PrivateKey *sk, unsigned allowed_usages, unsigned cert_issuer_key_type) {
|
||||
_client.client.setClientECCert(cert, sk, allowed_usages, cert_issuer_key_type);
|
||||
return *this;
|
||||
}
|
||||
|
||||
espMqttClientSecure& espMqttClientSecure::setCertStore(CertStoreBase *certStore) {
|
||||
_client.client.setCertStore(certStore);
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
espMqttClient::espMqttClient(espMqttClientTypes::UseInternalTask useInternalTask)
|
||||
: MqttClientSetup(useInternalTask)
|
||||
, _client() {
|
||||
_transport = &_client;
|
||||
}
|
||||
|
||||
espMqttClient::espMqttClient(uint8_t priority, uint8_t core)
|
||||
: MqttClientSetup(espMqttClientTypes::UseInternalTask::YES, priority, core)
|
||||
, _client() {
|
||||
_transport = &_client;
|
||||
}
|
||||
|
||||
espMqttClientSecure::espMqttClientSecure(espMqttClientTypes::UseInternalTask useInternalTask)
|
||||
: MqttClientSetup(useInternalTask)
|
||||
, _client() {
|
||||
_transport = &_client;
|
||||
}
|
||||
|
||||
espMqttClientSecure::espMqttClientSecure(uint8_t priority, uint8_t core)
|
||||
: MqttClientSetup(espMqttClientTypes::UseInternalTask::YES, priority, core)
|
||||
, _client() {
|
||||
_transport = &_client;
|
||||
}
|
||||
|
||||
espMqttClientSecure& espMqttClientSecure::setInsecure() {
|
||||
_client.client.setInsecure();
|
||||
return *this;
|
||||
}
|
||||
|
||||
espMqttClientSecure& espMqttClientSecure::setCACert(const char* rootCA) {
|
||||
_client.client.setCACert(rootCA);
|
||||
return *this;
|
||||
}
|
||||
|
||||
espMqttClientSecure& espMqttClientSecure::setCertificate(const char* clientCa) {
|
||||
_client.client.setCertificate(clientCa);
|
||||
return *this;
|
||||
}
|
||||
|
||||
espMqttClientSecure& espMqttClientSecure::setPrivateKey(const char* privateKey) {
|
||||
_client.client.setPrivateKey(privateKey);
|
||||
return *this;
|
||||
}
|
||||
|
||||
espMqttClientSecure& espMqttClientSecure::setPreSharedKey(const char* pskIdent, const char* psKey) {
|
||||
_client.client.setPreSharedKey(pskIdent, psKey);
|
||||
return *this;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(__linux__)
|
||||
espMqttClient::espMqttClient()
|
||||
: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO)
|
||||
, _client() {
|
||||
_transport = &_client;
|
||||
}
|
||||
#endif
|
||||
80
lib/espMqttClient/src/espMqttClient.h
Normal file
80
lib/espMqttClient/src/espMqttClient.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
API is based on the original work of Marvin Roger:
|
||||
https://github.com/marvinroger/async-mqtt-client
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||
#include "Transport/ClientSync.h"
|
||||
#include "Transport/ClientSecureSync.h"
|
||||
#elif defined(__linux__)
|
||||
#include "Transport/ClientPosix.h"
|
||||
#endif
|
||||
|
||||
#include "MqttClientSetup.h"
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
class espMqttClient : public MqttClientSetup<espMqttClient> {
|
||||
public:
|
||||
espMqttClient();
|
||||
|
||||
protected:
|
||||
espMqttClientInternals::ClientSync _client;
|
||||
};
|
||||
|
||||
class espMqttClientSecure : public MqttClientSetup<espMqttClientSecure> {
|
||||
public:
|
||||
espMqttClientSecure();
|
||||
espMqttClientSecure& setInsecure();
|
||||
espMqttClientSecure& setFingerprint(const uint8_t fingerprint[20]);
|
||||
espMqttClientSecure& setTrustAnchors(const X509List *ta);
|
||||
espMqttClientSecure& setClientRSACert(const X509List *cert, const PrivateKey *sk);
|
||||
espMqttClientSecure& setClientECCert(const X509List *cert, const PrivateKey *sk, unsigned allowed_usages, unsigned cert_issuer_key_type);
|
||||
espMqttClientSecure& setCertStore(CertStoreBase *certStore);
|
||||
|
||||
protected:
|
||||
espMqttClientInternals::ClientSecureSync _client;
|
||||
};
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
class espMqttClient : public MqttClientSetup<espMqttClient> {
|
||||
public:
|
||||
explicit espMqttClient(espMqttClientTypes::UseInternalTask useInternalTask);
|
||||
explicit espMqttClient(uint8_t priority = 1, uint8_t core = 1);
|
||||
|
||||
protected:
|
||||
espMqttClientInternals::ClientSync _client;
|
||||
};
|
||||
|
||||
class espMqttClientSecure : public MqttClientSetup<espMqttClientSecure> {
|
||||
public:
|
||||
explicit espMqttClientSecure(espMqttClientTypes::UseInternalTask useInternalTask);
|
||||
explicit espMqttClientSecure(uint8_t priority = 1, uint8_t core = 1);
|
||||
espMqttClientSecure& setInsecure();
|
||||
espMqttClientSecure& setCACert(const char* rootCA);
|
||||
espMqttClientSecure& setCertificate(const char* clientCa);
|
||||
espMqttClientSecure& setPrivateKey(const char* privateKey);
|
||||
espMqttClientSecure& setPreSharedKey(const char* pskIdent, const char* psKey);
|
||||
|
||||
protected:
|
||||
espMqttClientInternals::ClientSecureSync _client;
|
||||
};
|
||||
#endif
|
||||
|
||||
#if defined(__linux__)
|
||||
class espMqttClient : public MqttClientSetup<espMqttClient> {
|
||||
public:
|
||||
espMqttClient();
|
||||
|
||||
protected:
|
||||
espMqttClientInternals::ClientPosix _client;
|
||||
};
|
||||
#endif
|
||||
61
lib/espMqttClient/src/espMqttClientAsync.cpp
Normal file
61
lib/espMqttClient/src/espMqttClientAsync.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||
|
||||
#include "espMqttClientAsync.h"
|
||||
|
||||
espMqttClientAsync::espMqttClientAsync()
|
||||
: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO)
|
||||
, _clientAsync() {
|
||||
_transport = &_clientAsync;
|
||||
_clientAsync.client.onConnect(onConnectCb, this);
|
||||
_clientAsync.client.onDisconnect(onDisconnectCb, this);
|
||||
_clientAsync.client.onData(onDataCb, this);
|
||||
_clientAsync.client.onPoll(onPollCb, this);
|
||||
}
|
||||
|
||||
bool espMqttClientAsync::connect() {
|
||||
bool ret = MqttClient::connect();
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void espMqttClientAsync::_setupClient(espMqttClientAsync* c) {
|
||||
(void)c;
|
||||
}
|
||||
|
||||
void espMqttClientAsync::onConnectCb(void* a, AsyncClient* c) {
|
||||
c->setNoDelay(true);
|
||||
espMqttClientAsync* client = reinterpret_cast<espMqttClientAsync*>(a);
|
||||
client->_state = MqttClient::State::connectingTcp2;
|
||||
client->loop();
|
||||
}
|
||||
|
||||
void espMqttClientAsync::onDataCb(void* a, AsyncClient* c, void* data, size_t len) {
|
||||
(void)c;
|
||||
espMqttClientAsync* client = reinterpret_cast<espMqttClientAsync*>(a);
|
||||
client->_clientAsync.bufData = reinterpret_cast<uint8_t*>(data);
|
||||
client->_clientAsync.availableData = len;
|
||||
client->loop();
|
||||
}
|
||||
|
||||
void espMqttClientAsync::onDisconnectCb(void* a, AsyncClient* c) {
|
||||
(void)c;
|
||||
espMqttClientAsync* client = reinterpret_cast<espMqttClientAsync*>(a);
|
||||
client->_state = MqttClient::State::disconnectingTcp2;
|
||||
client->loop();
|
||||
}
|
||||
|
||||
void espMqttClientAsync::onPollCb(void* a, AsyncClient* c) {
|
||||
(void)c;
|
||||
espMqttClientAsync* client = reinterpret_cast<espMqttClientAsync*>(a);
|
||||
client->loop();
|
||||
}
|
||||
|
||||
#endif
|
||||
36
lib/espMqttClient/src/espMqttClientAsync.h
Normal file
36
lib/espMqttClient/src/espMqttClientAsync.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
API is based on the original work of Marvin Roger:
|
||||
https://github.com/marvinroger/async-mqtt-client
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||
|
||||
#include "Transport/ClientAsync.h"
|
||||
|
||||
#include "MqttClientSetup.h"
|
||||
|
||||
class espMqttClientAsync : public MqttClientSetup<espMqttClientAsync> {
|
||||
public:
|
||||
espMqttClientAsync();
|
||||
bool connect();
|
||||
|
||||
protected:
|
||||
espMqttClientInternals::ClientAsync _clientAsync;
|
||||
static void _setupClient(espMqttClientAsync* c);
|
||||
static void _disconnectClient(espMqttClientAsync* c);
|
||||
|
||||
static void onConnectCb(void* a, AsyncClient* c);
|
||||
static void onDataCb(void* a, AsyncClient* c, void* data, size_t len);
|
||||
static void onDisconnectCb(void* a, AsyncClient* c);
|
||||
static void onPollCb(void* a, AsyncClient* c);
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user