Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87a78c549f | ||
|
|
5211360b91 | ||
|
|
e71ffefc5a | ||
|
|
b6a0dde2b1 | ||
|
|
babc391632 | ||
|
|
27bdbb9a0b | ||
|
|
6a9e158428 | ||
|
|
6fc6794dc3 | ||
|
|
1eaa514579 | ||
|
|
7af4c2ca69 | ||
|
|
a340558460 | ||
|
|
9a7db237d3 | ||
|
|
91e083e7b0 | ||
|
|
97adc985e6 | ||
|
|
6fcfc9dfc0 | ||
|
|
a6596ffc89 | ||
|
|
533ab0c70d | ||
|
|
d5dd896b45 | ||
|
|
bd7fa8f39c | ||
|
|
6395e931ce | ||
|
|
635fee6f7c | ||
|
|
dc2420d88e | ||
|
|
2fbc46cbe2 | ||
|
|
a003156ae1 | ||
|
|
913e1aa7ae | ||
|
|
8272515bd7 | ||
|
|
9a7f6a3020 | ||
|
|
fead702d9f | ||
|
|
eaf938f2fd | ||
|
|
8eefa63f45 | ||
|
|
9d48c436d8 | ||
|
|
792a28e831 | ||
|
|
9407193454 | ||
|
|
602050f309 | ||
|
|
1a70c90af2 | ||
|
|
ed4091c53e | ||
|
|
f2a805f724 | ||
|
|
3083bcf071 | ||
|
|
d01f46dbc1 | ||
|
|
39b2257619 | ||
|
|
60d385189b | ||
|
|
3f2c1c57e1 | ||
|
|
e550197d0a | ||
|
|
253bc9b3f5 | ||
|
|
96d8018960 | ||
|
|
505cacc2df | ||
|
|
62848056a2 |
28
.github/workflows/aunit.yml
vendored
Normal file
28
.github/workflows/aunit.yml
vendored
Normal 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
|
||||
1
.github/workflows/superlinter.yml
vendored
1
.github/workflows/superlinter.yml
vendored
@@ -1,6 +1,7 @@
|
||||
name: Super-Linter
|
||||
|
||||
# Run this workflow every time a new commit pushed to your repository
|
||||
#
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,5 @@
|
||||
*~
|
||||
src/my_credentials.h
|
||||
*.o
|
||||
*.swp
|
||||
*.out
|
||||
|
||||
11
README.md
11
README.md
@@ -1,10 +1,10 @@
|
||||
# TinyMqtt
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
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)
|
||||
* 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)
|
||||
* 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.
|
||||
* ~~MqttMessage uses a buffer 256 bytes which is usually far than needed.~~
|
||||
* ~~MqttClient does not support more than one subscription at time~~
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/hsaturn/TinyMqtt.git"
|
||||
},
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.1",
|
||||
"exclude": "",
|
||||
"examples": "examples/*/*.ino",
|
||||
"frameworks": "arduino",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name=TinyMqtt
|
||||
version=0.7.0
|
||||
version=0.7.1
|
||||
author=Francois BIOT, HSaturn, <hsaturn@gmail.com>
|
||||
maintainer=Francois BIOT, HSaturn, <hsaturn@gmail.com>
|
||||
sentence=A tiny broker and client library for MQTT messaging.
|
||||
|
||||
@@ -15,6 +15,16 @@ class StringIndexer
|
||||
std::string str;
|
||||
uint8_t used=0;
|
||||
friend class StringIndexer;
|
||||
|
||||
#if EPOXY_DUINO
|
||||
public:
|
||||
// Workaround to avoid coredump in Indexer::release
|
||||
// when destroying a Topic after the deletion of
|
||||
// StringIndexer::strings map (which can occurs only with AUnit,
|
||||
// never in the ESP itself, because ESP never ends)
|
||||
// (I hate static vars)
|
||||
~StringCounter() { used=255; }
|
||||
#endif
|
||||
};
|
||||
public:
|
||||
using index_t=uint8_t;
|
||||
|
||||
@@ -29,8 +29,8 @@ MqttClient::MqttClient(MqttBroker* parent, WiFiClient& new_client)
|
||||
alive = millis()+5000; // client expires after 5s if no CONNECT msg
|
||||
}
|
||||
|
||||
MqttClient::MqttClient(MqttBroker* parent)
|
||||
: parent(parent)
|
||||
MqttClient::MqttClient(MqttBroker* parent, const std::string& id)
|
||||
: parent(parent), clientId(id)
|
||||
{
|
||||
client = nullptr;
|
||||
|
||||
@@ -41,7 +41,6 @@ MqttClient::~MqttClient()
|
||||
{
|
||||
close();
|
||||
delete client;
|
||||
Serial << "Client deleted" << endl;
|
||||
}
|
||||
|
||||
void MqttClient::close(bool bSendDisconnect)
|
||||
@@ -269,10 +268,30 @@ MqttError MqttClient::subscribe(Topic topic, uint8_t qos)
|
||||
|
||||
subscriptions.insert(topic);
|
||||
|
||||
if (parent==nullptr) // remote broker ?
|
||||
if (parent==nullptr) // remote broker
|
||||
{
|
||||
debug("remote subscribe");
|
||||
MqttMessage msg(MqttMessage::Type::Subscribe, 2);
|
||||
return sendTopic(topic, MqttMessage::Type::Subscribe, qos);
|
||||
}
|
||||
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
|
||||
msg.add(0);
|
||||
@@ -280,11 +299,9 @@ MqttError MqttClient::subscribe(Topic topic, uint8_t qos)
|
||||
|
||||
msg.add(topic);
|
||||
msg.add(qos);
|
||||
ret = msg.sendTo(this);
|
||||
|
||||
// TODO we should wait (state machine) for SUBACK
|
||||
}
|
||||
return ret;
|
||||
// TODO instead we should wait (state machine) for SUBACK / UNSUBACK ?
|
||||
return msg.sendTo(this);
|
||||
}
|
||||
|
||||
long MqttClient::counter=0;
|
||||
|
||||
@@ -120,9 +120,8 @@ class MqttClient
|
||||
FlagReserved = 1
|
||||
};
|
||||
public:
|
||||
MqttClient(MqttBroker*);
|
||||
MqttClient(MqttBroker* brk, const std::string& id) : MqttClient(brk) { clientId=id; }
|
||||
MqttClient() : MqttClient(nullptr) {};
|
||||
MqttClient(MqttBroker* brk = nullptr, const std::string& id="");
|
||||
MqttClient(const std::string& id) : MqttClient(nullptr, id){}
|
||||
|
||||
~MqttClient();
|
||||
|
||||
@@ -149,7 +148,7 @@ class MqttClient
|
||||
MqttError publish(const Topic& t) { return publish(t, nullptr, 0);};
|
||||
|
||||
MqttError subscribe(Topic topic, uint8_t qos=0);
|
||||
MqttError unsubscribe(Topic& topic);
|
||||
MqttError unsubscribe(Topic topic);
|
||||
|
||||
// connected to local broker
|
||||
// TODO seems to be useless
|
||||
@@ -158,9 +157,8 @@ class MqttClient
|
||||
void dump()
|
||||
{
|
||||
uint32_t ms=millis();
|
||||
Serial << "MqttClient (" << clientId.c_str() << ") p=" << (int32_t) parent
|
||||
<< " c=" << (int32_t)client << (connected() ? " ON " : " OFF");
|
||||
Serial << ", alive=" << (uint32_t)alive << '/' << ms << ", ka=" << keep_alive;
|
||||
Serial << "MqttClient (" << clientId.c_str() << ") " << (connected() ? " ON " : " OFF");
|
||||
Serial << ", alive=" << alive << '/' << ms << ", ka=" << keep_alive;
|
||||
Serial << (client && client->connected() ? "" : "dis") << "connected";
|
||||
message.hexdump("entrant msg");
|
||||
bool c=false;
|
||||
@@ -178,6 +176,7 @@ class MqttClient
|
||||
static long counter; // Number of messages sent
|
||||
|
||||
private:
|
||||
MqttError sendTopic(const Topic& topic, MqttMessage::Type type, uint8_t qos);
|
||||
void resubscribe();
|
||||
|
||||
friend class MqttBroker;
|
||||
@@ -221,9 +220,9 @@ class MqttBroker
|
||||
void begin() { server.begin(); }
|
||||
void loop();
|
||||
|
||||
uint8_t port() const { return server.port(); }
|
||||
uint16_t port() const { return server.port(); }
|
||||
|
||||
void connect(std::string host, uint32_t port=1883);
|
||||
void connect(std::string host, uint16_t port=1883);
|
||||
bool connected() const { return state == Connected; }
|
||||
|
||||
size_t clientsCount() const { return clients.size(); }
|
||||
|
||||
20
tests/Makefile
Normal file
20
tests/Makefile
Normal 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
|
||||
6
tests/local-tests/Makefile
Normal file
6
tests/local-tests/Makefile
Normal 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
|
||||
146
tests/local-tests/local-tests.ino
Normal file
146
tests/local-tests/local-tests.ino
Normal file
@@ -0,0 +1,146 @@
|
||||
#include <AUnit.h>
|
||||
#include <TinyMqtt.h>
|
||||
#include <map>
|
||||
|
||||
/**
|
||||
* TinyMqtt local unit tests.
|
||||
*
|
||||
* Clients are connected to pseudo remote broker
|
||||
* The remote will be 127.0.0.1:1883
|
||||
* We are using 127.0.0.1 because this is simpler to test with a single ESP
|
||||
* Also, this will allow to mock and thus run Action on github
|
||||
**/
|
||||
|
||||
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;
|
||||
assertEqual(broker.clientsCount(), (size_t)0); // Ensure client is not yet connected
|
||||
client.connect("127.0.0.1", 1883);
|
||||
assertEqual(broker.clientsCount(), (size_t)1); // Ensure client is now connected
|
||||
}
|
||||
assertEqual(broker.clientsCount(), (size_t)0);
|
||||
}
|
||||
|
||||
#if 0
|
||||
test(local_connect)
|
||||
{
|
||||
assertEqual(broker.clientsCount(), (size_t)0);
|
||||
|
||||
MqttClient client;
|
||||
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;
|
||||
subscriber.subscribe("a/b");
|
||||
subscriber.subscribe("a/c");
|
||||
subscriber.setCallback(onPublish);
|
||||
|
||||
MqttClient publisher;
|
||||
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("A");
|
||||
subscriber_a.setCallback(onPublish);
|
||||
subscriber_a.subscribe("a/b");
|
||||
subscriber_a.subscribe("a/c");
|
||||
|
||||
MqttClient subscriber_b("B");
|
||||
subscriber_b.setCallback(onPublish);
|
||||
subscriber_b.subscribe("a/b");
|
||||
|
||||
MqttClient publisher;
|
||||
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;
|
||||
subscriber.setCallback(onPublish);
|
||||
subscriber.subscribe("a/b");
|
||||
|
||||
MqttClient publisher;
|
||||
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 publisher;
|
||||
{
|
||||
MqttClient subscriber;
|
||||
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
|
||||
}
|
||||
#endif
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// 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();
|
||||
}
|
||||
6
tests/nowifi-tests/Makefile
Normal file
6
tests/nowifi-tests/Makefile
Normal 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 := nowifi-tests
|
||||
ARDUINO_LIBS := AUnit AceCommon AceTime TinyMqtt EspMock
|
||||
include ../../../EpoxyDuino/EpoxyDuino.mk
|
||||
@@ -3,9 +3,10 @@
|
||||
#include <map>
|
||||
|
||||
/**
|
||||
* TinyMqtt local unit tests.
|
||||
* TinyMqtt nowifi unit tests.
|
||||
*
|
||||
* No wifi connection unit tests.
|
||||
* Checks with a local broker. Clients must connect to the local client
|
||||
**/
|
||||
|
||||
using namespace std;
|
||||
@@ -20,7 +21,7 @@ void onPublish(const MqttClient* srce, const Topic& topic, const char* payload,
|
||||
published[srce->id()][topic]++;
|
||||
}
|
||||
|
||||
test(local_client_should_unregister_when_destroyed)
|
||||
test(nowifi_client_should_unregister_when_destroyed)
|
||||
{
|
||||
assertEqual(broker.clientsCount(), (size_t)0);
|
||||
{
|
||||
@@ -30,7 +31,7 @@ test(local_client_should_unregister_when_destroyed)
|
||||
assertEqual(broker.clientsCount(), (size_t)0);
|
||||
}
|
||||
|
||||
test(local_connect)
|
||||
test(nowifi_connect)
|
||||
{
|
||||
assertEqual(broker.clientsCount(), (size_t)0);
|
||||
|
||||
@@ -39,7 +40,7 @@ test(local_connect)
|
||||
assertEqual(broker.clientsCount(), (size_t)1);
|
||||
}
|
||||
|
||||
test(local_publish_should_be_dispatched)
|
||||
test(nowifi_publish_should_be_dispatched)
|
||||
{
|
||||
published.clear();
|
||||
assertEqual(broker.clientsCount(), (size_t)0);
|
||||
@@ -59,7 +60,7 @@ test(local_publish_should_be_dispatched)
|
||||
assertTrue(published[""]["a/c"] == 2);
|
||||
}
|
||||
|
||||
test(local_publish_should_be_dispatched_to_local_clients)
|
||||
test(nowifi_publish_should_be_dispatched_to_nowifi_clients)
|
||||
{
|
||||
published.clear();
|
||||
assertEqual(broker.clientsCount(), (size_t)0);
|
||||
@@ -84,7 +85,7 @@ test(local_publish_should_be_dispatched_to_local_clients)
|
||||
assertTrue(published["B"]["a/c"] == 0);
|
||||
}
|
||||
|
||||
test(local_unsubscribe)
|
||||
test(nowifi_unsubscribe)
|
||||
{
|
||||
published.clear();
|
||||
assertEqual(broker.clientsCount(), (size_t)0);
|
||||
@@ -96,31 +97,33 @@ test(local_unsubscribe)
|
||||
MqttClient publisher(&broker);
|
||||
publisher.publish("a/b");
|
||||
|
||||
// subscriber.unsubscribe("a/b"); TODO not yet implemented
|
||||
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)
|
||||
test(nowifi_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");
|
||||
}
|
||||
|
||||
MqttClient publisher(&broker);
|
||||
publisher.publish("a/b");
|
||||
|
||||
assertEqual(published.size(), (size_t)0); // Only one publish has been received
|
||||
assertEqual(published.size(), (size_t)1); // Only one publish has been received
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// setup() and loop()
|
||||
void setup() {
|
||||
6
tests/result.json
Normal file
6
tests/result.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"schemaVersion" : 1,
|
||||
"label" : "tests",
|
||||
"message" : "Message content",
|
||||
"color": "red"
|
||||
}
|
||||
2
tests/result.yaml
Normal file
2
tests/result.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
result: 1
|
||||
insert: "passed"
|
||||
Reference in New Issue
Block a user