Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9d19c3218 | ||
|
|
7bd299ec07 | ||
|
|
107469cd78 | ||
|
|
709e1fd567 | ||
|
|
4eb8f18ebf | ||
|
|
d5d27c8020 | ||
|
|
da3ec41d71 | ||
|
|
5f7b4537c8 | ||
|
|
cce4fecac6 | ||
|
|
737e217172 | ||
|
|
8fe3517894 | ||
|
|
710503663a | ||
|
|
a5b8afc0bd | ||
|
|
f1d3a15498 | ||
|
|
dfd5983715 | ||
|
|
4dcc6a6cf4 | ||
|
|
b58f3e3d67 | ||
|
|
a6b3540cb8 | ||
|
|
ccbf42f81b | ||
|
|
d39c58d8f5 | ||
|
|
36dde2c063 |
52
.github/workflows/superlinter.yml
vendored
52
.github/workflows/superlinter.yml
vendored
@@ -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 }}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ TinyMqtt is a small, fast and capable Mqtt Broker and Client for Esp8266 / Esp32
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Very (very !!) fast broker I saw it re-sent 1000 topics per second for two
|
- Very fast broker I saw it re-sent 1000 topics per second for two
|
||||||
clients that had subscribed (payload ~15 bytes ESP8266). No topic lost.
|
clients that had subscribed (payload ~15 bytes ESP8266). No topic lost.
|
||||||
The max I've seen was 2k msg/s (1 client 1 subscription)
|
The max I've seen was 2k msg/s (1 client 1 subscription)
|
||||||
- Act as as a mqtt broker and/or a mqtt client
|
- Act as as a mqtt broker and/or a mqtt client
|
||||||
|
|||||||
18
library.json
18
library.json
@@ -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.7.9",
|
|
||||||
"exclude": "",
|
|
||||||
"examples": "examples/*/*.ino",
|
|
||||||
"frameworks": "arduino",
|
|
||||||
"platforms": [
|
|
||||||
"atmelavr",
|
|
||||||
"espressif8266",
|
|
||||||
"espressif32"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
name=TinyMqtt
|
name=TinyMqtt
|
||||||
version=0.7.9
|
version=0.8.0
|
||||||
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.
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
4. Simple _FMT mechanism ala printf, but without the typeunsafetyness
|
4. Simple _FMT mechanism ala printf, but without the typeunsafetyness
|
||||||
and no internal buffers for replaceable stream printing
|
and no internal buffers for replaceable stream printing
|
||||||
*/
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#ifndef ARDUINO_STREAMING
|
#ifndef ARDUINO_STREAMING
|
||||||
#define ARDUINO_STREAMING
|
#define ARDUINO_STREAMING
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
1215
src/TinyMqtt.cpp
1215
src/TinyMqtt.cpp
File diff suppressed because it is too large
Load Diff
432
src/TinyMqtt.h
432
src/TinyMqtt.h
@@ -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
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
// #define TINY_MQTT_DEBUG
|
// #define TINY_MQTT_DEBUG
|
||||||
|
|
||||||
#ifdef TINY_MQTT_DEBUG
|
#ifdef TINY_MQTT_DEBUG
|
||||||
#define debug(what) { Serial << __LINE__ << ' ' << what << endl; delay(100); }
|
#define debug(what) { Serial << (int)__LINE__ << ' ' << what << endl; delay(100); }
|
||||||
#else
|
#else
|
||||||
#define debug(what) {}
|
#define debug(what) {}
|
||||||
#endif
|
#endif
|
||||||
@@ -42,263 +42,281 @@
|
|||||||
using TcpServer = WiFiServer;
|
using TcpServer = WiFiServer;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
enum 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
|
const uint16_t MaxBufferLength = 4096; //hard limit: 16k due to size decoding
|
||||||
public:
|
public:
|
||||||
enum 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 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]; }
|
||||||
uint16_t length() const { return buffer.size(); }
|
void complete() { encodeLength(); }
|
||||||
void complete();
|
|
||||||
|
|
||||||
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]) : Unknown;
|
return state == Complete ? static_cast<Type>(buffer[0] & 0xF0) : Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
void create(Type type)
|
uint8_t flags() const { return static_cast<uint8_t>(buffer[0] & 0x0F); }
|
||||||
{
|
|
||||||
buffer=(char)type;
|
|
||||||
buffer+='\0'; // reserved for msg length
|
|
||||||
vheader=2;
|
|
||||||
size=0;
|
|
||||||
state=Create;
|
|
||||||
}
|
|
||||||
MqttError sendTo(MqttClient*) const;
|
|
||||||
void hexdump(const char* prefix=nullptr) const;
|
|
||||||
|
|
||||||
private:
|
void create(Type type)
|
||||||
void encodeLength(char* msb, int length) const;
|
{
|
||||||
|
buffer=(decltype(buffer)::value_type)type;
|
||||||
|
buffer+='\0'; // reserved for msg length byte 1/2
|
||||||
|
buffer+='\0'; // reserved for msg length byte 2/2 (fixed)
|
||||||
|
vheader=3; // Should never change
|
||||||
|
size=0;
|
||||||
|
state=Create;
|
||||||
|
}
|
||||||
|
MqttError sendTo(MqttClient*);
|
||||||
|
void hexdump(const char* prefix=nullptr) const;
|
||||||
|
|
||||||
mutable std::string buffer; // mutable -> sendTo()
|
private:
|
||||||
uint8_t vheader;
|
void encodeLength();
|
||||||
uint16_t size; // bytes left to receive
|
|
||||||
State state;
|
std::string buffer;
|
||||||
|
uint8_t vheader;
|
||||||
|
uint16_t size; // bytes left to receive
|
||||||
|
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 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);
|
||||||
|
|
||||||
bool connected() { return
|
// TODO it seems that connected returns true in tcp mode even if
|
||||||
(parent!=nullptr and client==nullptr) or
|
// no negociation occured (only if tcp link is established)
|
||||||
(client and client->connected()); }
|
bool connected() { return
|
||||||
void write(const char* buf, size_t length)
|
(parent!=nullptr and client==nullptr) or
|
||||||
{ if (client) client->write(buf, length); }
|
(client and client->connected()); }
|
||||||
|
void write(const char* buf, size_t 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) {callback=fun; };
|
void setCallback(CallBack fun)
|
||||||
|
{
|
||||||
|
callback=fun;
|
||||||
|
#ifdef TINY_MQTT_DEBUG
|
||||||
|
Serial << "Callback set to " << (long)fun << endl;
|
||||||
|
if (callback) callback(this, "test/topic", "value", 5);
|
||||||
|
#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="")
|
||||||
{
|
{
|
||||||
uint32_t ms=millis();
|
(void)indent;
|
||||||
Serial << indent << "+-- " << '\'' << clientId.c_str() << "' " << (connected() ? " ON " : " OFF");
|
#ifdef TINY_MQTT_DEBUG
|
||||||
Serial << ", alive=" << alive << '/' << ms << ", ka=" << keep_alive << ' ';
|
uint32_t ms=millis();
|
||||||
Serial << (client && client->connected() ? "" : "dis") << "connected";
|
Serial << indent << "+-- " << '\'' << clientId.c_str() << "' " << (connected() ? " ON " : " OFF");
|
||||||
if (subscriptions.size())
|
Serial << ", alive=" << alive << '/' << ms << ", ka=" << keep_alive << ' ';
|
||||||
{
|
Serial << (client && client->connected() ? "" : "dis") << "connected";
|
||||||
bool c = false;
|
if (subscriptions.size())
|
||||||
Serial << " [";
|
{
|
||||||
for(auto s: subscriptions)
|
bool c = false;
|
||||||
{
|
Serial << " [";
|
||||||
if (c) Serial << ", ";
|
for(auto s: subscriptions)
|
||||||
Serial << s.str().c_str();
|
(void)indent;
|
||||||
c=true;
|
{
|
||||||
}
|
if (c) Serial << ", ";
|
||||||
Serial << ']';
|
Serial << s.str().c_str();
|
||||||
}
|
c=true;
|
||||||
Serial << endl;
|
}
|
||||||
}
|
Serial << ']';
|
||||||
|
}
|
||||||
|
Serial << endl;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/** Count the number of messages that have been sent **/
|
#ifdef EPOXY_DUINO
|
||||||
static long counter;
|
static std::map<MqttMessage::Type, int> counters; // Number of processed messages
|
||||||
|
|
||||||
private:
|
|
||||||
static void onConnect(void * client_ptr, TcpClient*);
|
|
||||||
#ifdef TCP_ASYNC
|
|
||||||
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);
|
|
||||||
void resubscribe();
|
|
||||||
|
|
||||||
friend class MqttBroker;
|
private:
|
||||||
MqttClient(MqttBroker* parent, TcpClient* client);
|
|
||||||
// republish a received publish if topic matches any in subscriptions
|
|
||||||
MqttError publishIfSubscribed(const Topic& topic, const MqttMessage& msg);
|
|
||||||
|
|
||||||
void clientAlive(uint32_t more_seconds);
|
// event when tcp/ip link established (real or fake)
|
||||||
void processMessage(const MqttMessage* message);
|
static void onConnect(void * client_ptr, TcpClient*);
|
||||||
|
#ifdef TCP_ASYNC
|
||||||
|
static void onData(void* client_ptr, TcpClient*, void* data, size_t len);
|
||||||
|
#endif
|
||||||
|
MqttError sendTopic(const Topic& topic, MqttMessage::Type type, uint8_t qos);
|
||||||
|
void resubscribe();
|
||||||
|
|
||||||
bool mqtt_connected = false;
|
friend class MqttBroker;
|
||||||
char mqtt_flags;
|
MqttClient(MqttBroker* parent, TcpClient* client);
|
||||||
uint32_t keep_alive = 60;
|
// republish a received publish if topic matches any in subscriptions
|
||||||
uint32_t alive;
|
MqttError publishIfSubscribed(const Topic& topic, MqttMessage& msg);
|
||||||
MqttMessage message;
|
|
||||||
|
|
||||||
// TODO having a pointer on MqttBroker may produce larger binaries
|
void clientAlive(uint32_t more_seconds);
|
||||||
// due to unecessary function linked if ever parent is not used
|
void processMessage(MqttMessage* message);
|
||||||
// (this is the case when MqttBroker isn't used except here)
|
|
||||||
MqttBroker* parent=nullptr; // connection to local broker
|
|
||||||
|
|
||||||
TcpClient* client=nullptr; // connection to mqtt client or to remote broker
|
bool mqtt_connected = false;
|
||||||
std::set<Topic> subscriptions;
|
char mqtt_flags;
|
||||||
std::string clientId;
|
uint32_t keep_alive = 60;
|
||||||
CallBack callback = nullptr;
|
uint32_t alive;
|
||||||
|
MqttMessage message;
|
||||||
|
|
||||||
|
// TODO having a pointer on MqttBroker may produce larger binaries
|
||||||
|
// due to unecessary function linked if ever parent is not used
|
||||||
|
// (this is the case when MqttBroker isn't used except here)
|
||||||
|
MqttBroker* parent=nullptr; // connection to local broker
|
||||||
|
|
||||||
|
TcpClient* client=nullptr; // connection to remote broker
|
||||||
|
std::set<Topic> subscriptions;
|
||||||
|
std::string clientId;
|
||||||
|
CallBack callback = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MqttBroker
|
class MqttBroker
|
||||||
{
|
{
|
||||||
enum 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, const 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;
|
||||||
};
|
};
|
||||||
|
|||||||
7
tests/howto
Normal file
7
tests/howto
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
cd TinyMqtt/tests/../..
|
||||||
|
git clone https://github.com/hsaturn/EspMock.git
|
||||||
|
git clone https://github.com/bxparks/AUnit.git
|
||||||
|
git clone https://github.com/bxparks/EpoxyDuino.git
|
||||||
|
cd TinyMqtt/tests
|
||||||
|
make
|
||||||
|
make runtests
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
# See https://github.com/bxparks/EpoxyDuino for documentation about this
|
# See https://github.com/bxparks/EpoxyDuino for documentation about this
|
||||||
# Makefile to compile and run Arduino programs natively on Linux or MacOS.
|
# Makefile to compile and run Arduino programs natively on Linux or MacOS.
|
||||||
|
|
||||||
|
EXTRA_CXXFLAGS=-g3 -O0
|
||||||
|
|
||||||
|
# Remove flto flag from EpoxyDuino (too many <optimized out>)
|
||||||
|
CXXFLAGS = -Wextra -Wall -std=gnu++11 -fno-exceptions -fno-threadsafe-statics
|
||||||
|
|
||||||
APP_NAME := local-tests
|
APP_NAME := local-tests
|
||||||
ARDUINO_LIBS := AUnit AceCommon AceTime TinyMqtt EspMock ESP8266WiFi ESPAsyncTCP
|
ARDUINO_LIBS := AUnit AceCommon AceTime TinyMqtt EspMock ESP8266WiFi ESPAsyncTCP
|
||||||
ARDUINO_LIB_DIRS := ../../../EspMock/libraries
|
ARDUINO_LIB_DIRS := ../../../EspMock/libraries
|
||||||
|
|||||||
@@ -31,12 +31,10 @@ void onPublish(const MqttClient* srce, const Topic& topic, const char* payload,
|
|||||||
|
|
||||||
test(local_client_should_unregister_when_destroyed)
|
test(local_client_should_unregister_when_destroyed)
|
||||||
{
|
{
|
||||||
return;
|
|
||||||
assertEqual(broker.clientsCount(), (size_t)0);
|
assertEqual(broker.clientsCount(), (size_t)0);
|
||||||
{
|
{
|
||||||
MqttClient client;
|
|
||||||
assertEqual(broker.clientsCount(), (size_t)0); // Ensure client is not yet connected
|
assertEqual(broker.clientsCount(), (size_t)0); // Ensure client is not yet connected
|
||||||
client.connect("127.0.0.1", 1883);
|
MqttClient client(&broker);
|
||||||
assertEqual(broker.clientsCount(), (size_t)1); // Ensure client is now connected
|
assertEqual(broker.clientsCount(), (size_t)1); // Ensure client is now connected
|
||||||
}
|
}
|
||||||
assertEqual(broker.clientsCount(), (size_t)0);
|
assertEqual(broker.clientsCount(), (size_t)0);
|
||||||
@@ -68,8 +66,8 @@ test(local_publish_should_be_dispatched)
|
|||||||
publisher.publish("a/c");
|
publisher.publish("a/c");
|
||||||
|
|
||||||
assertEqual(published.size(), (size_t)1); // 1 client has received something
|
assertEqual(published.size(), (size_t)1); // 1 client has received something
|
||||||
assertTrue(published[""]["a/b"] == 1);
|
assertEqual(published[""]["a/b"], 1);
|
||||||
assertTrue(published[""]["a/c"] == 2);
|
assertEqual(published[""]["a/c"], 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
test(local_publish_should_be_dispatched_to_local_clients)
|
test(local_publish_should_be_dispatched_to_local_clients)
|
||||||
@@ -91,10 +89,10 @@ test(local_publish_should_be_dispatched_to_local_clients)
|
|||||||
publisher.publish("a/c");
|
publisher.publish("a/c");
|
||||||
|
|
||||||
assertEqual(published.size(), (size_t)2); // 2 clients have received something
|
assertEqual(published.size(), (size_t)2); // 2 clients have received something
|
||||||
assertTrue(published["A"]["a/b"] == 1);
|
assertEqual(published["A"]["a/b"], 1);
|
||||||
assertTrue(published["A"]["a/c"] == 1);
|
assertEqual(published["A"]["a/c"], 1);
|
||||||
assertTrue(published["B"]["a/b"] == 1);
|
assertEqual(published["B"]["a/b"], 1);
|
||||||
assertTrue(published["B"]["a/c"] == 0);
|
assertEqual(published["B"]["a/c"], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
test(local_unsubscribe)
|
test(local_unsubscribe)
|
||||||
@@ -114,7 +112,7 @@ test(local_unsubscribe)
|
|||||||
publisher.publish("a/b");
|
publisher.publish("a/b");
|
||||||
publisher.publish("a/b");
|
publisher.publish("a/b");
|
||||||
|
|
||||||
assertTrue(published[""]["a/b"] == 1); // Only one publish has been received
|
assertEqual(published[""]["a/b"], 1); // Only one publish has been received
|
||||||
}
|
}
|
||||||
|
|
||||||
test(local_nocallback_when_destroyed)
|
test(local_nocallback_when_destroyed)
|
||||||
@@ -143,7 +141,7 @@ void setup() {
|
|||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
while(!Serial);
|
while(!Serial);
|
||||||
|
|
||||||
Serial.println("=============[ NO WIFI CONNECTION TinyMqtt TESTS ]========================");
|
Serial.println("=============[ LOCAL TinyMqtt TESTS ]========================");
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
|
|||||||
13
tests/network-tests/Makefile
Normal file
13
tests/network-tests/Makefile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# See https://github.com/bxparks/EpoxyDuino for documentation about this
|
||||||
|
# Makefile to compile and run Arduino programs natively on Linux or MacOS.
|
||||||
|
|
||||||
|
EXTRA_CXXFLAGS=-g3 -O0
|
||||||
|
|
||||||
|
# Remove flto flag from EpoxyDuino (too many <optimized out>)
|
||||||
|
CXXFLAGS = -Wextra -Wall -std=gnu++11 -fno-exceptions -fno-threadsafe-statics
|
||||||
|
|
||||||
|
APP_NAME := network-tests
|
||||||
|
ARDUINO_LIBS := AUnit AceCommon AceTime TinyMqtt EspMock ESP8266WiFi ESPAsyncTCP
|
||||||
|
ARDUINO_LIB_DIRS := ../../../EspMock/libraries
|
||||||
|
EPOXY_CORE := EPOXY_CORE_ESP8266
|
||||||
|
include ../../../EpoxyDuino/EpoxyDuino.mk
|
||||||
429
tests/network-tests/network-tests.ino
Normal file
429
tests/network-tests/network-tests.ino
Normal file
@@ -0,0 +1,429 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include <AUnit.h>
|
||||||
|
#include <TinyMqtt.h>
|
||||||
|
#include <map>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TinyMqtt network unit tests.
|
||||||
|
*
|
||||||
|
* No wifi connection unit tests.
|
||||||
|
* Checks with a local broker. Clients must connect to the local broker
|
||||||
|
**/
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
return String(ip[0])+'.'+String(ip[1])+'.'+String(ip[2])+'.'+String(ip[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
MqttBroker broker(1883);
|
||||||
|
|
||||||
|
std::map<std::string, std::map<Topic, int>> published; // map[client_id] => map[topic] = count
|
||||||
|
|
||||||
|
char* lastPayload = nullptr;
|
||||||
|
size_t lastLength;
|
||||||
|
|
||||||
|
void start_servers(int n, bool early_accept = true)
|
||||||
|
{
|
||||||
|
ESP8266WiFiClass::resetInstances();
|
||||||
|
ESP8266WiFiClass::earlyAccept = early_accept;
|
||||||
|
while(n)
|
||||||
|
{
|
||||||
|
ESP8266WiFiClass::selectInstance(n--);
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.begin("fake_ssid", "fake_pwd");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onPublish(const MqttClient* srce, const Topic& topic, const char* payload, size_t length)
|
||||||
|
{
|
||||||
|
if (srce)
|
||||||
|
published[srce->id()][topic]++;
|
||||||
|
|
||||||
|
if (lastPayload) free(lastPayload);
|
||||||
|
lastPayload = strdup(payload);
|
||||||
|
lastLength = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
test(network_single_broker_begin)
|
||||||
|
{
|
||||||
|
assertEqual(WiFi.status(), WL_CONNECTED);
|
||||||
|
|
||||||
|
MqttBroker broker(1883);
|
||||||
|
broker.begin();
|
||||||
|
|
||||||
|
// TODO Nothing is tested here !
|
||||||
|
}
|
||||||
|
|
||||||
|
test(suback)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
test(network_client_to_broker_connexion)
|
||||||
|
{
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
test(network_one_client_one_broker_publish_and_subscribe_through_network)
|
||||||
|
{
|
||||||
|
start_servers(2, true);
|
||||||
|
published.clear();
|
||||||
|
assertEqual(WiFi.status(), WL_CONNECTED);
|
||||||
|
|
||||||
|
MqttBroker broker(1883);
|
||||||
|
broker.begin();
|
||||||
|
IPAddress ip_broker = WiFi.localIP();
|
||||||
|
|
||||||
|
// We have a 2nd ESP in order to test through wifi (opposed to local)
|
||||||
|
ESP8266WiFiClass::selectInstance(2);
|
||||||
|
MqttClient client;
|
||||||
|
client.connect(ip_broker.toString().c_str(), 1883);
|
||||||
|
broker.loop();
|
||||||
|
assertTrue(client.connected());
|
||||||
|
|
||||||
|
client.setCallback(onPublish);
|
||||||
|
client.subscribe("a/b");
|
||||||
|
client.publish("a/b", "ab");
|
||||||
|
|
||||||
|
for (int i =0; i<2; i++)
|
||||||
|
{
|
||||||
|
client.loop();
|
||||||
|
broker.loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEqual(published.size(), (size_t)1);
|
||||||
|
assertEqual((int)lastLength, (int)2); // sizeof(ab)
|
||||||
|
}
|
||||||
|
|
||||||
|
test(network_one_client_one_broker_hudge_publish_and_subscribe_through_network)
|
||||||
|
{
|
||||||
|
start_servers(2, true);
|
||||||
|
published.clear();
|
||||||
|
assertEqual(WiFi.status(), WL_CONNECTED);
|
||||||
|
|
||||||
|
MqttBroker broker(1883);
|
||||||
|
broker.begin();
|
||||||
|
IPAddress ip_broker = WiFi.localIP();
|
||||||
|
|
||||||
|
// We have a 2nd ESP in order to test through wifi (opposed to local)
|
||||||
|
ESP8266WiFiClass::selectInstance(2);
|
||||||
|
MqttClient client;
|
||||||
|
client.connect(ip_broker.toString().c_str(), 1883);
|
||||||
|
broker.loop();
|
||||||
|
assertTrue(client.connected());
|
||||||
|
|
||||||
|
std::string sent;
|
||||||
|
|
||||||
|
for(int i=0; i<200; i++)
|
||||||
|
sent += char('0'+i%10);
|
||||||
|
|
||||||
|
client.setCallback(onPublish);
|
||||||
|
client.subscribe("a/b");
|
||||||
|
client.publish("a/b", sent.c_str());
|
||||||
|
|
||||||
|
for (int i =0; i<2; i++)
|
||||||
|
{
|
||||||
|
client.loop();
|
||||||
|
broker.loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEqual(published.size(), (size_t)1);
|
||||||
|
assertEqual((unsigned int)lastLength, (unsigned int)sent.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
test(network_client_should_unregister_when_destroyed)
|
||||||
|
{
|
||||||
|
assertEqual(broker.clientsCount(), (size_t)0);
|
||||||
|
{
|
||||||
|
MqttClient client(&broker);
|
||||||
|
assertEqual(broker.clientsCount(), (size_t)1);
|
||||||
|
}
|
||||||
|
assertEqual(broker.clientsCount(), (size_t)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// THESE TESTS ARE IN LOCAL MODE
|
||||||
|
// WE HAVE TO CONVERT THEM TO WIFI MODE (pass through virtual TCP link)
|
||||||
|
|
||||||
|
test(network_connect)
|
||||||
|
{
|
||||||
|
assertEqual(broker.clientsCount(), (size_t)0);
|
||||||
|
|
||||||
|
MqttClient client(&broker);
|
||||||
|
assertTrue(client.connected());
|
||||||
|
assertEqual(broker.clientsCount(), (size_t)1);
|
||||||
|
}
|
||||||
|
|
||||||
|
test(network_publish_should_be_dispatched)
|
||||||
|
{
|
||||||
|
published.clear();
|
||||||
|
assertEqual(broker.clientsCount(), (size_t)0);
|
||||||
|
|
||||||
|
MqttClient subscriber(&broker);
|
||||||
|
subscriber.subscribe("a/b");
|
||||||
|
subscriber.subscribe("a/c");
|
||||||
|
subscriber.setCallback(onPublish);
|
||||||
|
|
||||||
|
MqttClient publisher(&broker);
|
||||||
|
publisher.publish("a/b");
|
||||||
|
publisher.publish("a/c");
|
||||||
|
publisher.publish("a/c");
|
||||||
|
|
||||||
|
assertEqual(published.size(), (size_t)1); // 1 client has received something
|
||||||
|
assertEqual(published[""]["a/b"], 1);
|
||||||
|
assertEqual(published[""]["a/c"], 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
test(network_publish_should_be_dispatched_to_clients)
|
||||||
|
{
|
||||||
|
published.clear();
|
||||||
|
assertEqual(broker.clientsCount(), (size_t)0);
|
||||||
|
|
||||||
|
MqttClient subscriber_a(&broker, "A");
|
||||||
|
subscriber_a.setCallback(onPublish);
|
||||||
|
subscriber_a.subscribe("a/b");
|
||||||
|
subscriber_a.subscribe("a/c");
|
||||||
|
|
||||||
|
MqttClient subscriber_b(&broker, "B");
|
||||||
|
subscriber_b.setCallback(onPublish);
|
||||||
|
subscriber_b.subscribe("a/b");
|
||||||
|
|
||||||
|
MqttClient publisher(&broker);
|
||||||
|
publisher.publish("a/b"); // A and B should receive this
|
||||||
|
publisher.publish("a/c"); // A should receive this
|
||||||
|
|
||||||
|
assertEqual(published.size(), (size_t)2); // 2 clients have received something
|
||||||
|
assertEqual(published["A"]["a/b"], 1);
|
||||||
|
assertEqual(published["A"]["a/c"], 1);
|
||||||
|
assertEqual(published["B"]["a/b"], 1);
|
||||||
|
assertEqual(published["B"]["a/c"], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
test(network_unsubscribe)
|
||||||
|
{
|
||||||
|
published.clear();
|
||||||
|
assertEqual(broker.clientsCount(), (size_t)0);
|
||||||
|
|
||||||
|
MqttClient subscriber(&broker);
|
||||||
|
subscriber.setCallback(onPublish);
|
||||||
|
subscriber.subscribe("a/b");
|
||||||
|
|
||||||
|
MqttClient publisher(&broker);
|
||||||
|
publisher.publish("a/b"); // This publish is received
|
||||||
|
|
||||||
|
subscriber.unsubscribe("a/b");
|
||||||
|
|
||||||
|
publisher.publish("a/b"); // Those one, no (unsubscribed)
|
||||||
|
publisher.publish("a/b");
|
||||||
|
|
||||||
|
assertEqual(published[""]["a/b"], 1); // Only one publish has been received
|
||||||
|
}
|
||||||
|
|
||||||
|
test(network_nocallback_when_destroyed)
|
||||||
|
{
|
||||||
|
published.clear();
|
||||||
|
assertEqual(broker.clientsCount(), (size_t)0);
|
||||||
|
|
||||||
|
MqttClient publisher(&broker);
|
||||||
|
|
||||||
|
{
|
||||||
|
MqttClient subscriber(&broker);
|
||||||
|
subscriber.setCallback(onPublish);
|
||||||
|
subscriber.subscribe("a/b");
|
||||||
|
publisher.publish("a/b");
|
||||||
|
}
|
||||||
|
|
||||||
|
publisher.publish("a/b");
|
||||||
|
|
||||||
|
assertEqual(published.size(), (size_t)1); // Only one publish has been received
|
||||||
|
}
|
||||||
|
|
||||||
|
test(network_small_payload)
|
||||||
|
{
|
||||||
|
published.clear();
|
||||||
|
|
||||||
|
const char* payload="abcd";
|
||||||
|
|
||||||
|
MqttClient subscriber(&broker);
|
||||||
|
subscriber.setCallback(onPublish);
|
||||||
|
subscriber.subscribe("a/b");
|
||||||
|
|
||||||
|
MqttClient publisher(&broker);
|
||||||
|
publisher.publish("a/b", payload, strlen(payload)); // This publish is received
|
||||||
|
|
||||||
|
// coming from MqttClient::publish(...)
|
||||||
|
assertEqual(payload, lastPayload);
|
||||||
|
assertEqual(lastLength, (size_t)4);
|
||||||
|
}
|
||||||
|
|
||||||
|
test(network_hudge_payload)
|
||||||
|
{
|
||||||
|
const char* payload="This payload is hudge, just because its length exceeds 127. Thus when encoding length, we have to encode it on two bytes at min. This should not prevent the message from being encoded and decoded successfully !";
|
||||||
|
|
||||||
|
MqttClient subscriber(&broker);
|
||||||
|
subscriber.setCallback(onPublish);
|
||||||
|
subscriber.subscribe("a/b"); // Note -> this does not send any byte .... (nowhere to send)
|
||||||
|
|
||||||
|
MqttClient publisher(&broker);
|
||||||
|
publisher.publish("a/b", payload); // This publish is received
|
||||||
|
|
||||||
|
// onPublish should have filled lastPayload and lastLength
|
||||||
|
assertEqual(payload, lastPayload);
|
||||||
|
assertEqual(lastLength, strlen(payload));
|
||||||
|
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()
|
||||||
|
void setup() {
|
||||||
|
/* delay(1000);
|
||||||
|
Serial.begin(115200);
|
||||||
|
while(!Serial);
|
||||||
|
*/
|
||||||
|
|
||||||
|
Serial.println("=============[ FAKE NETWORK TinyMqtt TESTS ]========================");
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.begin("network", "password");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
aunit::TestRunner::run();
|
||||||
|
|
||||||
|
if (Serial.available()) ESP.reset();
|
||||||
|
}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
# See https://github.com/bxparks/EpoxyDuino for documentation about this
|
# See https://github.com/bxparks/EpoxyDuino for documentation about this
|
||||||
# Makefile to compile and run Arduino programs natively on Linux or MacOS.
|
# Makefile to compile and run Arduino programs natively on Linux or MacOS.
|
||||||
|
|
||||||
|
EXTRA_CXXFLAGS=-g3 -O0
|
||||||
|
|
||||||
|
# Remove flto flag from EpoxyDuino (too many <optimized out>)
|
||||||
|
CXXFLAGS = -Wextra -Wall -std=gnu++11 -fno-exceptions -fno-threadsafe-statics
|
||||||
|
|
||||||
APP_NAME := nowifi-tests
|
APP_NAME := nowifi-tests
|
||||||
ARDUINO_LIBS := AUnit AceCommon AceTime TinyMqtt EspMock ESP8266WiFi ESPAsyncTCP
|
ARDUINO_LIBS := AUnit AceCommon AceTime TinyMqtt EspMock ESP8266WiFi ESPAsyncTCP
|
||||||
ARDUINO_LIB_DIRS := ../../../EspMock/libraries
|
ARDUINO_LIB_DIRS := ../../../EspMock/libraries
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ test(nowifi_nocallback_when_destroyed)
|
|||||||
assertEqual(published.size(), (size_t)1); // Only one publish has been received
|
assertEqual(published.size(), (size_t)1); // Only one publish has been received
|
||||||
}
|
}
|
||||||
|
|
||||||
test(nowifi_payload_nullptr)
|
test(nowifi_small_payload)
|
||||||
{
|
{
|
||||||
published.clear();
|
published.clear();
|
||||||
|
|
||||||
@@ -150,6 +150,22 @@ test(nowifi_payload_nullptr)
|
|||||||
assertEqual(lastLength, (size_t)4);
|
assertEqual(lastLength, (size_t)4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test(nowifi_hudge_payload)
|
||||||
|
{
|
||||||
|
const char* payload="This payload is hudge, just because its length exceeds 127. Thus when encoding length, we have to encode it on two bytes at min. This should not prevent the message from being encoded and decoded successfully !";
|
||||||
|
|
||||||
|
MqttClient subscriber(&broker);
|
||||||
|
subscriber.setCallback(onPublish);
|
||||||
|
subscriber.subscribe("a/b");
|
||||||
|
|
||||||
|
MqttClient publisher(&broker);
|
||||||
|
publisher.publish("a/b", payload); // This publish is received
|
||||||
|
|
||||||
|
// onPublish should have filled lastPayload and lastLength
|
||||||
|
assertEqual(payload, lastPayload);
|
||||||
|
assertEqual(lastLength, strlen(payload));
|
||||||
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
// setup() and loop()
|
// setup() and loop()
|
||||||
void setup() {
|
void setup() {
|
||||||
|
|||||||
Reference in New Issue
Block a user