Compare commits

...

7 Commits
pr22 ... 0.9.1

Author SHA1 Message Date
hsaturn
776242b259 Release 0.9.1 2022-10-30 19:45:50 +01:00
hsaturn
a9d19c3218 [lib] Remove library.json 2022-10-30 18:31:46 +01:00
hsaturn
7bd299ec07 Use slim lint 2022-10-30 15:23:50 +01:00
hsaturn
107469cd78 Lint action, upgrade to latest 2022-10-30 15:13:42 +01:00
hsaturn
709e1fd567 Tabs changed to spaces (at least) 2022-10-30 13:26:33 +01:00
hsaturn
4eb8f18ebf [MqttMessage] Rewrite of the length encoding 2022-10-30 13:24:05 +01:00
hsaturn
d5d27c8020 Merge pull request #25 from hsaturn/pr22
was pr#22 : added reply to message subscribe and unsubscribe
2022-01-10 05:28:08 +01:00
7 changed files with 1027 additions and 904 deletions

View File

@@ -1,26 +1,52 @@
name: Super-Linter ---
#################################
#################################
## Super Linter GitHub Actions ##
#################################
#################################
name: Lint Code Base
# Run this workflow every time a new commit pushed to your repository
# #
# Documentation:
# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions
#
#############################
# Start the job on all push #
#############################
on: push on: push
###############
# Set the Job #
###############
jobs: jobs:
# Set the job key. The key is displayed as the job name build:
# when a job name is not provided
super-lint:
# Name the Job # Name the Job
name: Lint code base name: Lint Code Base
# Set the type of machine to run on # Set the agent to run on
runs-on: ubuntu-latest runs-on: ubuntu-latest
##################
# Load all steps #
##################
steps: steps:
# Checks out a copy of your repository on the ubuntu-latest machine ##########################
- name: Checkout code # Checkout the code base #
uses: actions/checkout@v2 ##########################
- name: Checkout Code
uses: actions/checkout@v3
with:
# Full git history is needed to get a proper
# list of changed files within `super-linter`
fetch-depth: 0
# Runs the Super-Linter action ################################
- name: Run Super-Linter # Run Linter against code base #
uses: github/super-linter@v3 ################################
- name: Lint Code Base
uses: github/super-linter/slim@v4
env: env:
VALIDATE_ALL_CODEBASE: false
# Change to 'master' if your main branch differs
DEFAULT_BRANCH: main DEFAULT_BRANCH: main
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,18 +0,0 @@
{
"name": "TinyMqtt",
"keywords": "ethernet, mqtt, m2m, iot",
"description": "MQTT is a lightweight messaging protocol ideal for small devices. This library allows to send and receive and host a broker for MQTT. It does support MQTT 3.1.1 with QOS=0 on ESP8266 and ESP32 WROOM platfrms.",
"repository": {
"type": "git",
"url": "https://github.com/hsaturn/TinyMqtt.git"
},
"version": "0.8.0",
"exclude": "",
"examples": "examples/*/*.ino",
"frameworks": "arduino",
"platforms": [
"atmelavr",
"espressif8266",
"espressif32"
]
}

View File

@@ -1,5 +1,5 @@
name=TinyMqtt name=TinyMqtt
version=0.8.0 version=0.9.1
author=Francois BIOT, HSaturn, <hsaturn@gmail.com> author=Francois BIOT, HSaturn, <hsaturn@gmail.com>
maintainer=Francois BIOT, HSaturn, <hsaturn@gmail.com> maintainer=Francois BIOT, HSaturn, <hsaturn@gmail.com>
sentence=A tiny broker and client library for MQTT messaging. sentence=A tiny broker and client library for MQTT messaging.

View File

@@ -9,121 +9,121 @@
*/ */
class StringIndexer class StringIndexer
{ {
class StringCounter class StringCounter
{ {
std::string str; std::string str;
uint8_t used=0; uint8_t used=0;
friend class StringIndexer; friend class StringIndexer;
#if EPOXY_DUINO #if EPOXY_DUINO
public: public:
// Workaround to avoid coredump in Indexer::release // Workaround to avoid coredump in Indexer::release
// when destroying a Topic after the deletion of // when destroying a Topic after the deletion of
// StringIndexer::strings map (which can occurs only with AUnit, // StringIndexer::strings map (which can occurs only with AUnit,
// never in the ESP itself, because ESP never ends) // never in the ESP itself, because ESP never ends)
// (I hate static vars) // (I hate static vars)
~StringCounter() { used=255; } ~StringCounter() { used=255; }
#endif #endif
}; };
public: public:
using index_t=uint8_t; using index_t=uint8_t;
static index_t strToIndex(const char* str, uint8_t len) static index_t strToIndex(const char* str, uint8_t len)
{ {
for(auto it=strings.begin(); it!=strings.end(); it++) for(auto it=strings.begin(); it!=strings.end(); it++)
{ {
if (strncmp(it->second.str.c_str(), str, len)==0) if (strncmp(it->second.str.c_str(), str, len)==0)
{ {
it->second.used++; it->second.used++;
return it->first; return it->first;
} }
} }
for(index_t index=1; index; index++) for(index_t index=1; index; index++)
{ {
if (strings.find(index)==strings.end()) if (strings.find(index)==strings.end())
{ {
strings[index].str = std::string(str, len); strings[index].str = std::string(str, len);
strings[index].used++; strings[index].used++;
// Serial << "Creating index " << index << " for (" << strings[index].str.c_str() << ") len=" << len << endl; // Serial << "Creating index " << index << " for (" << strings[index].str.c_str() << ") len=" << len << endl;
return index; return index;
} }
} }
return 0; // TODO out of indexes return 0; // TODO out of indexes
} }
static const std::string& str(const index_t& index) static const std::string& str(const index_t& index)
{ {
static std::string dummy; static std::string dummy;
const auto& it=strings.find(index); const auto& it=strings.find(index);
if (it == strings.end()) return dummy; if (it == strings.end()) return dummy;
return it->second.str; return it->second.str;
} }
static void use(const index_t& index) static void use(const index_t& index)
{ {
auto it=strings.find(index); auto it=strings.find(index);
if (it != strings.end()) it->second.used++; if (it != strings.end()) it->second.used++;
} }
static void release(const index_t& index) static void release(const index_t& index)
{ {
auto it=strings.find(index); auto it=strings.find(index);
if (it != strings.end()) if (it != strings.end())
{ {
it->second.used--; it->second.used--;
if (it->second.used == 0) if (it->second.used == 0)
{ {
strings.erase(it); strings.erase(it);
// Serial << "Removing string(" << it->second.str.c_str() << ") size=" << strings.size() << endl; // Serial << "Removing string(" << it->second.str.c_str() << ") size=" << strings.size() << endl;
} }
} }
} }
static uint16_t count() { return strings.size(); } static uint16_t count() { return strings.size(); }
private: private:
static std::map<index_t, StringCounter> strings; static std::map<index_t, StringCounter> strings;
}; };
class IndexedString class IndexedString
{ {
public: public:
IndexedString(const IndexedString& source) IndexedString(const IndexedString& source)
{ {
StringIndexer::use(source.index); StringIndexer::use(source.index);
index = source.index; index = source.index;
} }
IndexedString(const char* str, uint8_t len) IndexedString(const char* str, uint8_t len)
{ {
index=StringIndexer::strToIndex(str, len); index=StringIndexer::strToIndex(str, len);
} }
IndexedString(const std::string& str) : IndexedString(str.c_str(), str.length()) {}; IndexedString(const std::string& str) : IndexedString(str.c_str(), str.length()) {};
~IndexedString() { StringIndexer::release(index); } ~IndexedString() { StringIndexer::release(index); }
IndexedString& operator=(const IndexedString& source) IndexedString& operator=(const IndexedString& source)
{ {
StringIndexer::use(source.index); StringIndexer::use(source.index);
index = source.index; index = source.index;
return *this; return *this;
} }
friend bool operator<(const IndexedString& i1, const IndexedString& i2) friend bool operator<(const IndexedString& i1, const IndexedString& i2)
{ {
return i1.index < i2.index; return i1.index < i2.index;
} }
friend bool operator==(const IndexedString& i1, const IndexedString& i2) friend bool operator==(const IndexedString& i1, const IndexedString& i2)
{ {
return i1.index == i2.index; return i1.index == i2.index;
} }
const std::string& str() const { return StringIndexer::str(index); } const std::string& str() const { return StringIndexer::str(index); }
const StringIndexer::index_t& getIndex() const { return index; } const StringIndexer::index_t& getIndex() const { return index; }
private: private:
StringIndexer::index_t index; StringIndexer::index_t index;
}; };

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,18 @@
#pragma once #pragma once
// TODO Should add a AUnit with both TCP_ASYNC and not TCP_ASYNC // TODO Should add a AUnit with both TCP_ASYNC and not TCP_ASYNC
// #define TCP_ASYNC // Uncomment this to use ESPAsyncTCP instead of normal cnx // #define TCP_ASYNC // Uncomment this to use ESPAsyncTCP instead of normal cnx
#if defined(ESP8266) || defined(EPOXY_DUINO) #if defined(ESP8266) || defined(EPOXY_DUINO)
#ifdef TCP_ASYNC #ifdef TCP_ASYNC
#include <ESPAsyncTCP.h> #include <ESPAsyncTCP.h>
#else #else
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#endif #endif
#elif defined(ESP32) #elif defined(ESP32)
#include <WiFi.h> #include <WiFi.h>
#ifdef TCP_ASYNC #ifdef TCP_ASYNC
#include <AsyncTCP.h> // https://github.com/me-no-dev/AsyncTCP #include <AsyncTCP.h> // https://github.com/me-no-dev/AsyncTCP
#endif #endif
#endif #endif
#ifdef EPOXY_DUINO #ifdef EPOXY_DUINO
@@ -20,6 +20,14 @@
#else #else
#define dbg_ptr uint32_t #define dbg_ptr uint32_t
#endif #endif
#ifdef WIO_TERMINAL
// Uncommon board handling
// If you have a problem with this line, just remove it.
// Note: https://github.com/hsaturn/TinyMqtt/issues/41
#include <rpcWiFi.h>
#endif
#include <vector> #include <vector>
#include <set> #include <set>
#include <string> #include <string>
@@ -44,165 +52,165 @@
enum __attribute__((packed)) MqttError enum __attribute__((packed)) MqttError
{ {
MqttOk = 0, MqttOk = 0,
MqttNowhereToSend=1, MqttNowhereToSend=1,
MqttInvalidMessage=2, MqttInvalidMessage=2,
}; };
class Topic : public IndexedString class Topic : public IndexedString
{ {
public: public:
Topic(const char* s, uint8_t len) : IndexedString(s,len){} Topic(const char* s, uint8_t len) : IndexedString(s,len){}
Topic(const char* s) : Topic(s, strlen(s)) {} Topic(const char* s) : Topic(s, strlen(s)) {}
Topic(const std::string s) : Topic(s.c_str(), s.length()){}; Topic(const std::string s) : Topic(s.c_str(), s.length()){};
const char* c_str() const { return str().c_str(); } const char* c_str() const { return str().c_str(); }
bool matches(const Topic&) const; bool matches(const Topic&) const;
}; };
class MqttClient; class MqttClient;
class MqttMessage class MqttMessage
{ {
const uint16_t MaxBufferLength = 4096; //hard limit: 16k due to size decoding const uint16_t MaxBufferLength = 4096; //hard limit: 16k due to size decoding
public: public:
enum __attribute__((packed)) Type enum __attribute__((packed)) Type
{ {
Unknown = 0, Unknown = 0,
Connect = 0x10, Connect = 0x10,
ConnAck = 0x20, ConnAck = 0x20,
Publish = 0x30, Publish = 0x30,
PubAck = 0x40, PubAck = 0x40,
Subscribe = 0x80, Subscribe = 0x80,
SubAck = 0x90, SubAck = 0x90,
UnSubscribe = 0xA0, UnSubscribe = 0xA0,
UnSuback = 0xB0, UnSuback = 0xB0,
PingReq = 0xC0, PingReq = 0xC0,
PingResp = 0xD0, PingResp = 0xD0,
Disconnect = 0xE0 Disconnect = 0xE0
}; };
enum __attribute__((packed)) State enum __attribute__((packed)) State
{ {
FixedHeader=0, FixedHeader=0,
Length=1, Length=1,
VariableHeader=2, VariableHeader=2,
PayLoad=3, PayLoad=3,
Complete=4, Complete=4,
Error=5, Error=5,
Create=6 Create=6
}; };
MqttMessage() { reset(); } MqttMessage() { reset(); }
MqttMessage(Type t, uint8_t bits_d3_d0=0) { create(t); buffer[0] |= bits_d3_d0; } MqttMessage(Type t, uint8_t bits_d3_d0=0) { create(t); buffer[0] |= bits_d3_d0; }
void incoming(char byte); void incoming(char byte);
void add(char byte) { incoming(byte); } void add(char byte) { incoming(byte); }
void add(const char* p, size_t len, bool addLength=true ); void add(const char* p, size_t len, bool addLength=true );
void add(const std::string& s) { add(s.c_str(), s.length()); } void add(const std::string& s) { add(s.c_str(), s.length()); }
void add(const Topic& t) { add(t.str()); } void add(const Topic& t) { add(t.str()); }
const char* end() const { return &buffer[0]+buffer.size(); } const char* end() const { return &buffer[0]+buffer.size(); }
const char* getVHeader() const { return &buffer[vheader]; } const char* getVHeader() const { return &buffer[vheader]; }
void complete() { encodeLength(); } void complete() { encodeLength(); }
void reset(); void reset();
// buff is MSB/LSB/STRING // buff is MSB/LSB/STRING
// output buff+=2, len=length(str) // output buff+=2, len=length(str)
static void getString(const char* &buff, uint16_t& len); static void getString(const char* &buff, uint16_t& len);
Type type() const Type type() const
{ {
return state == Complete ? static_cast<Type>(buffer[0] & 0xF0) : Unknown; return state == Complete ? static_cast<Type>(buffer[0] & 0xF0) : Unknown;
} }
uint8_t flags() const { return static_cast<uint8_t>(buffer[0] & 0x0F); } uint8_t flags() const { return static_cast<uint8_t>(buffer[0] & 0x0F); }
void create(Type type) void create(Type type)
{ {
buffer=(decltype(buffer)::value_type)type; buffer=(decltype(buffer)::value_type)type;
buffer+='\0'; // reserved for msg length byte 1/2 buffer+='\0'; // reserved for msg length byte 1/2
buffer+='\0'; // reserved for msg length byte 2/2 (fixed) buffer+='\0'; // reserved for msg length byte 2/2 (fixed)
vheader=3; // Should never change vheader=3; // Should never change
size=0; size=0;
state=Create; state=Create;
} }
MqttError sendTo(MqttClient*); MqttError sendTo(MqttClient*);
void hexdump(const char* prefix=nullptr) const; void hexdump(const char* prefix=nullptr) const;
private: private:
void encodeLength(); void encodeLength();
std::string buffer; std::string buffer;
uint8_t vheader; uint8_t vheader;
uint16_t size; // bytes left to receive uint16_t size; // bytes left to receive
State state; State state;
}; };
class MqttBroker; class MqttBroker;
class MqttClient class MqttClient
{ {
using CallBack = void (*)(const MqttClient* source, const Topic& topic, const char* payload, size_t payload_length); using CallBack = void (*)(const MqttClient* source, const Topic& topic, const char* payload, size_t payload_length);
enum __attribute__((packed)) Flags enum __attribute__((packed)) Flags
{ {
FlagUserName = 128, FlagUserName = 128,
FlagPassword = 64, FlagPassword = 64,
FlagWillRetain = 32, // unsupported FlagWillRetain = 32, // unsupported
FlagWillQos = 16 | 8, // unsupported FlagWillQos = 16 | 8, // unsupported
FlagWill = 4, // unsupported FlagWill = 4, // unsupported
FlagCleanSession = 2, // unsupported FlagCleanSession = 2, // unsupported
FlagReserved = 1 FlagReserved = 1
}; };
public: public:
/** Constructor. If broker is not null, this is the adress of a local broker. /** Constructor. If broker is not null, this is the adress of a local broker.
If you want to connect elsewhere, leave broker null and use connect() **/ If you want to connect elsewhere, leave broker null and use connect() **/
MqttClient(MqttBroker* broker = nullptr, const std::string& id=""); MqttClient(MqttBroker* broker = nullptr, const std::string& id="");
MqttClient(const std::string& id) : MqttClient(nullptr, id){} MqttClient(const std::string& id) : MqttClient(nullptr, id){}
~MqttClient(); ~MqttClient();
void connect(MqttBroker* parent); void connect(MqttBroker* parent);
void connect(std::string broker, uint16_t port, uint16_t keep_alive = 10); void connect(std::string broker, uint16_t port, uint16_t keep_alive = 10);
// TODO it seems that connected returns true in tcp mode even if // TODO it seems that connected returns true in tcp mode even if
// no negociation occured (only if tcp link is established) // no negociation occured (only if tcp link is established)
bool connected() { return bool connected() { return
(parent!=nullptr and client==nullptr) or (parent!=nullptr and client==nullptr) or
(client and client->connected()); } (client and client->connected()); }
void write(const char* buf, size_t length) void write(const char* buf, size_t length)
{ if (client) client->write(buf, length); } { if (client) client->write(buf, length); }
const std::string& id() const { return clientId; } const std::string& id() const { return clientId; }
void id(std::string& new_id) { clientId = new_id; } void id(std::string& new_id) { clientId = new_id; }
/** Should be called in main loop() */ /** Should be called in main loop() */
void loop(); void loop();
void close(bool bSendDisconnect=true); void close(bool bSendDisconnect=true);
void setCallback(CallBack fun) void setCallback(CallBack fun)
{ {
callback=fun; callback=fun;
#ifdef TINY_MQTT_DEBUG #ifdef TINY_MQTT_DEBUG
Serial << "Callback set to " << (long)fun << endl; Serial << "Callback set to " << (long)fun << endl;
if (callback) callback(this, "test/topic", "value", 5); if (callback) callback(this, "test/topic", "value", 5);
#endif #endif
}; };
// Publish from client to the world // Publish from client to the world
MqttError publish(const Topic&, const char* payload, size_t pay_length); MqttError publish(const Topic&, const char* payload, size_t pay_length);
MqttError publish(const Topic& t, const char* payload) { return publish(t, payload, strlen(payload)); } MqttError publish(const Topic& t, const char* payload) { return publish(t, payload, strlen(payload)); }
MqttError publish(const Topic& t, const String& s) { return publish(t, s.c_str(), s.length()); } MqttError publish(const Topic& t, const String& s) { return publish(t, s.c_str(), s.length()); }
MqttError publish(const Topic& t, const std::string& s) { return publish(t,s.c_str(),s.length());} MqttError publish(const Topic& t, const std::string& s) { return publish(t,s.c_str(),s.length());}
MqttError publish(const Topic& t) { return publish(t, nullptr, 0);}; MqttError publish(const Topic& t) { return publish(t, nullptr, 0);};
MqttError subscribe(Topic topic, uint8_t qos=0); MqttError subscribe(Topic topic, uint8_t qos=0);
MqttError unsubscribe(Topic topic); MqttError unsubscribe(Topic topic);
bool isSubscribedTo(const Topic& topic) const; bool isSubscribedTo(const Topic& topic) const;
// connected to local broker // connected to local broker
// TODO seems to be useless // TODO seems to be useless
bool isLocal() const { return client == nullptr; } bool isLocal() const { return client == nullptr; }
void dump(std::string indent="") void dump(std::string indent="")
{ {
(void)indent; (void)indent;
#ifdef TINY_MQTT_DEBUG #ifdef TINY_MQTT_DEBUG
uint32_t ms=millis(); uint32_t ms=millis();
Serial << indent << "+-- " << '\'' << clientId.c_str() << "' " << (connected() ? " ON " : " OFF"); Serial << indent << "+-- " << '\'' << clientId.c_str() << "' " << (connected() ? " ON " : " OFF");
@@ -223,100 +231,100 @@ class MqttClient
} }
Serial << endl; Serial << endl;
#endif #endif
} }
#ifdef EPOXY_DUINO #ifdef EPOXY_DUINO
static std::map<MqttMessage::Type, int> counters; // Number of processed messages static std::map<MqttMessage::Type, int> counters; // Number of processed messages
#endif #endif
private: private:
// event when tcp/ip link established (real or fake) // event when tcp/ip link established (real or fake)
static void onConnect(void * client_ptr, TcpClient*); static void onConnect(void * client_ptr, TcpClient*);
#ifdef TCP_ASYNC #ifdef TCP_ASYNC
static void onData(void* client_ptr, TcpClient*, void* data, size_t len); static void onData(void* client_ptr, TcpClient*, void* data, size_t len);
#endif #endif
MqttError sendTopic(const Topic& topic, MqttMessage::Type type, uint8_t qos); MqttError sendTopic(const Topic& topic, MqttMessage::Type type, uint8_t qos);
void resubscribe(); void resubscribe();
friend class MqttBroker; friend class MqttBroker;
MqttClient(MqttBroker* parent, TcpClient* client); MqttClient(MqttBroker* parent, TcpClient* client);
// republish a received publish if topic matches any in subscriptions // republish a received publish if topic matches any in subscriptions
MqttError publishIfSubscribed(const Topic& topic, MqttMessage& msg); MqttError publishIfSubscribed(const Topic& topic, MqttMessage& msg);
void clientAlive(uint32_t more_seconds); void clientAlive(uint32_t more_seconds);
void processMessage(MqttMessage* message); void processMessage(MqttMessage* message);
bool mqtt_connected = false; bool mqtt_connected = false;
char mqtt_flags; char mqtt_flags;
uint32_t keep_alive = 60; uint32_t keep_alive = 60;
uint32_t alive; uint32_t alive;
MqttMessage message; MqttMessage message;
// TODO having a pointer on MqttBroker may produce larger binaries // TODO having a pointer on MqttBroker may produce larger binaries
// due to unecessary function linked if ever parent is not used // due to unecessary function linked if ever parent is not used
// (this is the case when MqttBroker isn't used except here) // (this is the case when MqttBroker isn't used except here)
MqttBroker* parent=nullptr; // connection to local broker MqttBroker* parent=nullptr; // connection to local broker
TcpClient* client=nullptr; // connection to remote broker TcpClient* client=nullptr; // connection to remote broker
std::set<Topic> subscriptions; std::set<Topic> subscriptions;
std::string clientId; std::string clientId;
CallBack callback = nullptr; CallBack callback = nullptr;
}; };
class MqttBroker class MqttBroker
{ {
enum __attribute__((packed)) State enum __attribute__((packed)) State
{ {
Disconnected, // Also the initial state Disconnected, // Also the initial state
Connecting, // connect and sends a fake publish to avoid circular cnx Connecting, // connect and sends a fake publish to avoid circular cnx
Connected, // this->broker is connected and circular cnx avoided Connected, // this->broker is connected and circular cnx avoided
}; };
public: public:
// TODO limit max number of clients // TODO limit max number of clients
MqttBroker(uint16_t port); MqttBroker(uint16_t port);
~MqttBroker(); ~MqttBroker();
void begin() { server->begin(); } void begin() { server->begin(); }
void loop(); void loop();
void connect(const std::string& host, uint16_t port=1883); void connect(const std::string& host, uint16_t port=1883);
bool connected() const { return state == Connected; } bool connected() const { return state == Connected; }
size_t clientsCount() const { return clients.size(); } size_t clientsCount() const { return clients.size(); }
void dump(std::string indent="") void dump(std::string indent="")
{ {
for(auto client: clients) for(auto client: clients)
client->dump(indent); client->dump(indent);
} }
private: private:
friend class MqttClient; friend class MqttClient;
static void onClient(void*, TcpClient*); static void onClient(void*, TcpClient*);
bool checkUser(const char* user, uint8_t len) const bool checkUser(const char* user, uint8_t len) const
{ return compareString(auth_user, user, len); } { return compareString(auth_user, user, len); }
bool checkPassword(const char* password, uint8_t len) const bool checkPassword(const char* password, uint8_t len) const
{ return compareString(auth_password, password, len); } { return compareString(auth_password, password, len); }
MqttError publish(const MqttClient* source, const Topic& topic, MqttMessage& msg) const; MqttError publish(const MqttClient* source, const Topic& topic, MqttMessage& msg) const;
MqttError subscribe(const Topic& topic, uint8_t qos); MqttError subscribe(const Topic& topic, uint8_t qos);
// For clients that are added not by the broker itself // For clients that are added not by the broker itself
void addClient(MqttClient* client); void addClient(MqttClient* client);
void removeClient(MqttClient* client); void removeClient(MqttClient* client);
bool compareString(const char* good, const char* str, uint8_t str_len) const; bool compareString(const char* good, const char* str, uint8_t str_len) const;
std::vector<MqttClient*> clients; std::vector<MqttClient*> clients;
TcpServer* server; TcpServer* server;
const char* auth_user = "guest"; const char* auth_user = "guest";
const char* auth_password = "guest"; const char* auth_password = "guest";
State state = Disconnected; State state = Disconnected;
MqttClient* broker = nullptr; MqttClient* broker = nullptr;
}; };

View File

@@ -2,6 +2,10 @@
#include <AUnit.h> #include <AUnit.h>
#include <TinyMqtt.h> #include <TinyMqtt.h>
#include <map> #include <map>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
/** /**
* TinyMqtt network unit tests. * TinyMqtt network unit tests.
@@ -10,7 +14,60 @@
* Checks with a local broker. Clients must connect to the local broker * Checks with a local broker. Clients must connect to the local broker
**/ **/
using namespace std; // if ascii_pos = 0, no ascii dump, else ascii dump starts after column ascii_pos
std::string bufferToHexa(const uint8_t* buffer, size_t length, char sep = 0, size_t ascii_pos = 0)
{
std::stringstream out;
std::string ascii;
std::string h("0123456789ABCDEF");
for(size_t i=0; i<length; i++)
{
uint8_t c = buffer[i];
out << h[ c >> 4] << h[ c & 0x0F ];
if (sep) out << sep;
if (ascii_pos)
{
if (c>=32)
ascii += c;
else
ascii +='.';
}
}
std::string ret(out.str());
if (ascii_pos)
{
while(ret.length() < ascii_pos)
ret += ' ';
ret +='[' + ascii + ']';
}
return ret;
}
void dumpMqttMessage(const uint8_t* buffer, size_t length)
{
std::map<int, std::string> pkt =
{ { MqttMessage::Unknown , "Unknown " },
{ MqttMessage::Connect , "Connect " },
{ MqttMessage::ConnAck , "ConnAck " },
{ MqttMessage::Publish , "Publish " },
{ MqttMessage::PubAck , "PubAck " },
{ MqttMessage::Subscribe , "Subscribe " },
{ MqttMessage::SubAck , "SubAck " },
{ MqttMessage::UnSubscribe , "Unsubscribe " },
{ MqttMessage::UnSuback , "UnSubAck " },
{ MqttMessage::PingReq , "PingReq " },
{ MqttMessage::PingResp , "PingResp " },
{ MqttMessage::Disconnect , "Disconnect " } };
std::cout << " | data sent " << std::setw(3) << length << " : ";
auto it = pkt.find(buffer[0] & 0xF0);
if (it == pkt.end())
std::cout << pkt[MqttMessage::Unknown];
else
std::cout << it->second;
std::cout << bufferToHexa(buffer, length, ' ', 60) << std::endl;
}
String toString(const IPAddress& ip) String toString(const IPAddress& ip)
{ {
@@ -299,7 +356,7 @@ test(network_hudge_payload)
MqttClient subscriber(&broker); MqttClient subscriber(&broker);
subscriber.setCallback(onPublish); subscriber.setCallback(onPublish);
subscriber.subscribe("a/b"); subscriber.subscribe("a/b"); // Note -> this does not send any byte .... (nowhere to send)
MqttClient publisher(&broker); MqttClient publisher(&broker);
publisher.publish("a/b", payload); // This publish is received publisher.publish("a/b", payload); // This publish is received
@@ -310,6 +367,47 @@ test(network_hudge_payload)
assertEqual(strcmp(payload, lastPayload), 0); assertEqual(strcmp(payload, lastPayload), 0);
} }
test(connack)
{
const bool view = false;
NetworkObserver check(
[this](const WiFiClient*, const uint8_t* buffer, size_t length)
{
if (view) dumpMqttMessage(buffer, length);
if (buffer[0] == MqttMessage::ConnAck)
{
std::string hex = bufferToHexa(buffer, length);
assertStringCaseEqual(hex.c_str(), "20020000");
}
}
);
start_servers(2, true);
assertEqual(WiFi.status(), WL_CONNECTED);
MqttBroker broker(1883);
broker.begin();
IPAddress broker_ip = WiFi.localIP();
ESP8266WiFiClass::selectInstance(2);
MqttClient client;
client.connect(broker_ip.toString().c_str(), 1883);
broker.loop();
assertTrue(broker.clientsCount() == 1);
assertTrue(client.connected());
MqttClient::counters[MqttMessage::Type::SubAck] = 0;
client.subscribe("a/b");
// TODO how to avoid these loops ???
broker.loop();
client.loop();
assertEqual(MqttClient::counters[MqttMessage::Type::SubAck], 1);
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
// setup() and loop() // setup() and loop()
void setup() { void setup() {