Compare commits

...

24 Commits

Author SHA1 Message Date
hsaturn
6fcfc9dfc0 ptr 2021-03-29 23:20:49 +02:00
hsaturn
a6596ffc89 Fix ptr 2021-03-29 23:06:40 +02:00
hsaturn
533ab0c70d Try to mock Esp 2021-03-29 21:47:14 +02:00
hsaturn
792a28e831 deleted 2021-03-29 20:45:33 +02:00
hsaturn
9407193454 MqttClient::unsubscribe implemented 2021-03-29 20:39:10 +02:00
hsaturn
602050f309 Update readme.md 2021-03-29 11:45:51 +02:00
hsaturn
1a70c90af2 refix EspWifi 2021-03-29 00:01:32 +02:00
hsaturn
ed4091c53e ESP8266WiFi lib added for aunit 2021-03-28 23:57:06 +02:00
hsaturn
f2a805f724 Fix makefile 2021-03-28 23:50:50 +02:00
hsaturn
3083bcf071 Revert auint 2021-03-28 23:44:11 +02:00
hsaturn
d01f46dbc1 Fix aunit 2021-03-28 23:42:57 +02:00
hsaturn
39b2257619 Merge branch 'linter' of github.com:hsaturn/TinyMqtt into linter 2021-03-28 23:39:50 +02:00
hsaturn
60d385189b Fixed make target 2021-03-28 23:39:37 +02:00
hsaturn
3f2c1c57e1 Fixed make target 2021-03-28 23:30:19 +02:00
hsaturn
e550197d0a Fixed make target 2021-03-28 23:28:48 +02:00
hsaturn
253bc9b3f5 Added makefile for aunit 2021-03-28 23:26:53 +02:00
hsaturn
96d8018960 Test aunit 2021-03-28 23:16:07 +02:00
hsaturn
505cacc2df Lint fixes 2021-03-28 23:09:10 +02:00
hsaturn
62848056a2 test 2021-03-28 23:01:14 +02:00
hsaturn
01998e74ec Test linter 2021-03-28 22:59:42 +02:00
hsaturn
5f46fd304c Release 0.7.0 2021-03-28 22:47:13 +02:00
hsaturn
213d637eaf Code cleaning 2021-03-28 21:31:10 +02:00
hsaturn
3bb2dd5a81 Code cleaning 2021-03-28 21:29:02 +02:00
hsaturn
7d9ab6381d Disconnect added (finally) 2021-03-28 21:28:06 +02:00
12 changed files with 299 additions and 39 deletions

28
.github/workflows/aunit.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
# See https://docs.github.com/en/actions/guides for documentation about GitHub
# Actions.
name: AUnit Tests
# Run on all branches.
on: [push]
jobs:
build:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Setup
run: |
cd ..
git clone https://github.com/bxparks/EpoxyDuino
git clone https://github.com/bxparks/AceRoutine
git clone https://github.com/bxparks/AUnit
git clone https://github.com/bxparks/AceCommon
git clone https://github.com/hsaturn/EspMock
- name: Verify tests
run: |
make -C tests
make -C tests runtests

26
.github/workflows/superlinter.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Super-Linter
# Run this workflow every time a new commit pushed to your repository
#
on: push
jobs:
# Set the job key. The key is displayed as the job name
# when a job name is not provided
super-lint:
# Name the Job
name: Lint code base
# Set the type of machine to run on
runs-on: ubuntu-latest
steps:
# Checks out a copy of your repository on the ubuntu-latest machine
- name: Checkout code
uses: actions/checkout@v2
# Runs the Super-Linter action
- name: Run Super-Linter
uses: github/super-linter@v3
env:
DEFAULT_BRANCH: main
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,10 +1,10 @@
# TinyMqtt # TinyMqtt
![](https://img.shields.io/github/v/release/hsaturn/TinyMqtt) ![Release](https://img.shields.io/github/v/release/hsaturn/TinyMqtt)
![](https://img.shields.io/github/issues/hsaturn/TinyMqtt) ![Issues](https://img.shields.io/github/issues/hsaturn/TinyMqtt)
![](https://img.shields.io/badge/platform-ESP8266-green) ![Esp8266](https://img.shields.io/badge/platform-ESP8266-green)
![](https://img.shields.io/github/license/hsaturn/TinyMqtt) ![Gpl 3.0](https://img.shields.io/github/license/hsaturn/TinyMqtt)
![](https://img.shields.io/badge/Mqtt-%203.1.1-yellow) ![Mqtt 3.1.1](https://img.shields.io/badge/Mqtt-%203.1.1-yellow)
ESP 8266 is a small, fast and capable Mqtt Broker and Client ESP 8266 is a small, fast and capable Mqtt Broker and Client
@@ -26,6 +26,7 @@ ESP 8266 is a small, fast and capable Mqtt Broker and Client
* Implement zeroconf mode (needs async) * Implement zeroconf mode (needs async)
* Add a max_clients in MqttBroker. Used with zeroconf, there will be * Add a max_clients in MqttBroker. Used with zeroconf, there will be
no need for having tons of clients (also RAM is the problem with many clients) no need for having tons of clients (also RAM is the problem with many clients)
* Why not a 'global' TinyMqtt::loop() instead of having to call loop for all broker/clients instances
* Test what is the real max number of clients for broker. As far as I saw, 1k is needed per client which would make more than 30 clients critical. * Test what is the real max number of clients for broker. As far as I saw, 1k is needed per client which would make more than 30 clients critical.
* ~~MqttMessage uses a buffer 256 bytes which is usually far than needed.~~ * ~~MqttMessage uses a buffer 256 bytes which is usually far than needed.~~
* ~~MqttClient does not support more than one subscription at time~~ * ~~MqttClient does not support more than one subscription at time~~

View File

@@ -16,11 +16,6 @@
* cons - Takes more memory * cons - Takes more memory
* - a bit hard to understand * - a bit hard to understand
* *
* This sounds crazy: a mqtt mqtt that do not need a broker !
* The use case arise when one ESP wants to publish topics and subscribe to them at the same time.
* Without broker, the ESP won't react to its own topics.
*
* TinyMqtt mqtt allows this use case to work.
*/ */
#include <my_credentials.h> #include <my_credentials.h>
@@ -28,18 +23,26 @@
std::string topic="sensor/temperature"; std::string topic="sensor/temperature";
void onPublish(const MqttClient* srce, const Topic& topic, const char* payload, size_t length) void onPublish(const MqttClient* srce, const Topic& topic, const char* payload, size_t length)
{ Serial << "--> " << srce->id().c_str() << ": ======> received " << topic.c_str() << endl; } {
Serial << "--> " << srce->id().c_str() << ": ======> received " << topic.c_str();
if (payload) Serial << ", payload[" << length << "]=[";
while(length--)
{
const char c=*payload++;
if (c!=10 and c!=13 and c <32) Serial << '?';
Serial << *payload++;
}
Serial<< endl;
}
std::map<std::string, MqttClient*> clients; std::map<std::string, MqttClient*> clients;
std::map<std::string, MqttBroker*> brokers; std::map<std::string, MqttBroker*> brokers;
void setup() void setup()
{ {
Serial.begin(115200); Serial.begin(115200);
delay(500); delay(500);
Serial << endl << endl << endl Serial << endl << endl << endl
<< "Demo started. Type help for more..." << endl
<< "Connecting to '" << ssid << "' "; << "Connecting to '" << ssid << "' ";
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
@@ -49,6 +52,7 @@ void setup()
{ Serial << '-'; delay(500); } { Serial << '-'; delay(500); }
Serial << "Connected to " << ssid << "IP address: " << WiFi.localIP() << endl; Serial << "Connected to " << ssid << "IP address: " << WiFi.localIP() << endl;
Serial << "Type help for more..." << endl;
MqttBroker* broker = new MqttBroker(1883); MqttBroker* broker = new MqttBroker(1883);
broker->begin(); broker->begin();
@@ -92,7 +96,6 @@ std::string getip(std::string& str, const char* if_empty=nullptr, char sep=' ')
std::string addr=getword(str, if_empty, sep); std::string addr=getword(str, if_empty, sep);
std::string ip=addr; std::string ip=addr;
std::vector<std::string> build; std::vector<std::string> build;
bool ok=true;
while(ip.length()) while(ip.length())
{ {
std::string b=getword(ip,nullptr,'.'); std::string b=getword(ip,nullptr,'.');

View File

@@ -6,7 +6,7 @@
"type": "git", "type": "git",
"url": "https://github.com/hsaturn/TinyMqtt.git" "url": "https://github.com/hsaturn/TinyMqtt.git"
}, },
"version": "0.6.0", "version": "0.7.1",
"exclude": "", "exclude": "",
"examples": "examples/*/*.ino", "examples": "examples/*/*.ino",
"frameworks": "arduino", "frameworks": "arduino",

View File

@@ -1,5 +1,5 @@
name=TinyMqtt name=TinyMqtt
version=0.6.0 version=0.7.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

@@ -104,7 +104,7 @@ class IndexedString
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;

View File

@@ -43,12 +43,17 @@ MqttClient::~MqttClient()
delete client; delete client;
} }
void MqttClient::close() void MqttClient::close(bool bSendDisconnect)
{ {
debug("close " << id().c_str()); debug("close " << id().c_str());
mqtt_connected = false; mqtt_connected = false;
if (client) if (client)
{ {
if (bSendDisconnect and client->connected())
{
message.create(MqttMessage::Type::Disconnect);
message.sendTo(this);
}
client->stop(); client->stop();
} }
@@ -120,7 +125,7 @@ void MqttBroker::loop()
// for(auto it=clients.begin(); it!=clients.end(); it++) // for(auto it=clients.begin(); it!=clients.end(); it++)
// use index because size can change during the loop // use index because size can change during the loop
for(int i=0; i<clients.size(); i++) for(size_t i=0; i<clients.size(); i++)
{ {
auto client = clients[i]; auto client = clients[i];
if (client->connected()) if (client->connected())
@@ -263,10 +268,30 @@ MqttError MqttClient::subscribe(Topic topic, uint8_t qos)
subscriptions.insert(topic); subscriptions.insert(topic);
if (parent==nullptr) // remote broker ? if (parent==nullptr) // remote broker
{ {
debug("remote subscribe"); return sendTopic(topic, MqttMessage::Type::Subscribe, qos);
MqttMessage msg(MqttMessage::Type::Subscribe, 2); }
return ret;
}
MqttError MqttClient::unsubscribe(Topic topic)
{
auto it=subscriptions.find(topic);
if (it != subscriptions.end())
{
subscriptions.erase(it);
if (parent==nullptr) // remote broker
{
return sendTopic(topic, MqttMessage::Type::UnSubscribe, 0);
}
}
return MqttOk;
}
MqttError MqttClient::sendTopic(const Topic& topic, MqttMessage::Type type, uint8_t qos)
{
MqttMessage msg(type, 2);
// TODO manage packet identifier // TODO manage packet identifier
msg.add(0); msg.add(0);
@@ -274,11 +299,9 @@ MqttError MqttClient::subscribe(Topic topic, uint8_t qos)
msg.add(topic); msg.add(topic);
msg.add(qos); msg.add(qos);
ret = msg.sendTo(this);
// TODO we should wait (state machine) for SUBACK // TODO instead we should wait (state machine) for SUBACK / UNSUBACK ?
} return msg.sendTo(this);
return ret;
} }
long MqttClient::counter=0; long MqttClient::counter=0;
@@ -447,11 +470,21 @@ if (message.type() != MqttMessage::Type::PingReq && message.type() != MqttMessag
{ {
callback(this, published, nullptr, 0); // TODO send the real payload callback(this, published, nullptr, 0); // TODO send the real payload
} }
// TODO should send PUBACK message.create(MqttMessage::Type::PubAck);
// TODO re-add packet identifier if any
message.sendTo(this);
bclose = false; bclose = false;
} }
break; break;
case MqttMessage::Type::Disconnect:
// TODO should discard any will message
if (!mqtt_connected) break;
mqtt_connected = false;
close(false);
bclose=false;
break;
default: default:
bclose=true; bclose=true;
break; break;
@@ -602,7 +635,7 @@ void MqttMessage::encodeLength(char* msb, int length)
MqttError MqttMessage::sendTo(MqttClient* client) MqttError MqttMessage::sendTo(MqttClient* client)
{ {
if (buffer.size()>2) if (buffer.size())
{ {
debug("sending " << buffer.size() << " bytes"); debug("sending " << buffer.size() << " bytes");
encodeLength(&buffer[1], buffer.size()-2); encodeLength(&buffer[1], buffer.size()-2);

View File

@@ -49,6 +49,7 @@ class MqttMessage
UnSubscribe = 0xA0, UnSubscribe = 0xA0,
PingReq = 0xC0, PingReq = 0xC0,
PingResp = 0xD0, PingResp = 0xD0,
Disconnect = 0xE0
}; };
enum State enum State
{ {
@@ -120,6 +121,7 @@ class MqttClient
}; };
public: public:
MqttClient(MqttBroker*); MqttClient(MqttBroker*);
MqttClient(MqttBroker* brk, const std::string& id) : MqttClient(brk) { clientId=id; }
MqttClient() : MqttClient(nullptr) {}; MqttClient() : MqttClient(nullptr) {};
~MqttClient(); ~MqttClient();
@@ -137,7 +139,7 @@ class MqttClient
void id(std::string& new_id) { clientId = new_id; } void id(std::string& new_id) { clientId = new_id; }
void loop(); void loop();
void close(); void close(bool bSendDisconnect=true);
void setCallback(CallBack fun) {callback=fun; }; void setCallback(CallBack fun) {callback=fun; };
// Publish from client to the world // Publish from client to the world
@@ -147,7 +149,7 @@ class MqttClient
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);
// connected to local broker // connected to local broker
// TODO seems to be useless // TODO seems to be useless
@@ -156,10 +158,10 @@ class MqttClient
void dump() void dump()
{ {
uint32_t ms=millis(); uint32_t ms=millis();
Serial << "MqttClient (" << clientId.c_str() << ") p=" << (int32_t) parent Serial << "MqttClient (" << clientId.c_str() << ") p=" << (uint64_t) parent
<< " c=" << (int32_t)client << (connected() ? " ON " : " OFF"); << " c=" << (uint64_t)client << (connected() ? " ON " : " OFF");
Serial << ", alive=" << (uint32_t)alive << '/' << ms << ", ka=" << keep_alive; Serial << ", alive=" << (uint32_t)alive << '/' << ms << ", ka=" << keep_alive;
Serial << " cnx " << (client && client->connected()); Serial << (client && client->connected() ? "" : "dis") << "connected";
message.hexdump("entrant msg"); message.hexdump("entrant msg");
bool c=false; bool c=false;
Serial << " ["; Serial << " [";
@@ -176,6 +178,7 @@ class MqttClient
static long counter; // Number of messages sent static long counter; // Number of messages sent
private: private:
MqttError sendTopic(const Topic& topic, MqttMessage::Type type, uint8_t qos);
void resubscribe(); void resubscribe();
friend class MqttBroker; friend class MqttBroker;
@@ -224,6 +227,8 @@ class MqttBroker
void connect(std::string host, uint32_t port=1883); void connect(std::string host, uint32_t port=1883);
bool connected() const { return state == Connected; } bool connected() const { return state == Connected; }
size_t clientsCount() const { return clients.size(); }
void dump() void dump()
{ {
Serial << clients.size() << " client/s" << endl; Serial << clients.size() << " client/s" << endl;

20
tests/Makefile Normal file
View File

@@ -0,0 +1,20 @@
tests:
set -e; \
for i in *-tests/Makefile; do \
echo '==== Making:' $$(dirname $$i); \
$(MAKE) -C $$(dirname $$i) -j; \
done
runtests:
set -e; \
for i in *-tests/Makefile; do \
echo '==== Running:' $$(dirname $$i); \
$$(dirname $$i)/$$(dirname $$i).out; \
done
clean:
set -e; \
for i in *-tests/Makefile; do \
echo '==== Cleaning:' $$(dirname $$i); \
$(MAKE) -C $$(dirname $$i) clean; \
done

View File

@@ -0,0 +1,6 @@
# See https://github.com/bxparks/EpoxyDuino for documentation about this
# Makefile to compile and run Arduino programs natively on Linux or MacOS.
APP_NAME := local-tests
ARDUINO_LIBS := AUnit AceCommon AceTime TinyMqtt EspMock
include ../../../EpoxyDuino/EpoxyDuino.mk

View File

@@ -0,0 +1,138 @@
#include <AUnit.h>
#include <TinyMqtt.h>
#include <map>
/**
* TinyMqtt local unit tests.
*
* No wifi connection unit tests.
**/
using namespace std;
MqttBroker broker(1883);
std::map<std::string, std::map<Topic, int>> published; // map[client_id] => map[topic] = count
void onPublish(const MqttClient* srce, const Topic& topic, const char* payload, size_t length)
{
if (srce)
published[srce->id()][topic]++;
}
test(local_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);
}
test(local_connect)
{
assertEqual(broker.clientsCount(), (size_t)0);
MqttClient client(&broker);
assertTrue(client.connected());
assertEqual(broker.clientsCount(), (size_t)1);
}
test(local_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
assertTrue(published[""]["a/b"] == 1);
assertTrue(published[""]["a/c"] == 2);
}
test(local_publish_should_be_dispatched_to_local_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");
publisher.publish("a/c");
assertEqual(published.size(), (size_t)2); // 2 clients have received something
assertTrue(published["A"]["a/b"] == 1);
assertTrue(published["A"]["a/c"] == 1);
assertTrue(published["B"]["a/b"] == 1);
assertTrue(published["B"]["a/c"] == 0);
}
test(local_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");
subscriber.unsubscribe("a/b");
publisher.publish("a/b");
publisher.publish("a/b");
assertTrue(published[""]["a/b"] == 1); // Only one publish has been received
}
test(local_nocallback_when_destroyed)
{
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");
assertEqual(published.size(), (size_t)0); // Only one publish has been received
}
//----------------------------------------------------------------------------
// setup() and loop()
void setup() {
delay(1000);
Serial.begin(115200);
while(!Serial);
Serial.println("=============[ NO WIFI CONNECTION TinyMqtt TESTS ]========================");
}
void loop() {
aunit::TestRunner::run();
if (Serial.available()) ESP.reset();
}