Skip to content

Commit

Permalink
v1.2.4
Browse files Browse the repository at this point in the history
- implemented command API handling via MQTT channel
  • Loading branch information
genemars committed Mar 10, 2024
1 parent 2cb3e89 commit 8f5589f
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 122 deletions.
74 changes: 1 addition & 73 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
HomeGenie mini *(code name **Sbirulino**)* is an **open source library** for building custom firmware for smart devices
based on *ESP32* or *ESP8266* chip.

https://homegenie.it/mini

## Features

Expand Down Expand Up @@ -237,79 +238,6 @@ pio run -e playground-c3 -t upload



## HomeGenie API

HomeGenie Mini API is a subset of HomeGenie Server API that makes HomeGenie Mini a real
fully working light version of HomeGenie Server specifically designed for microcontrollers.

### [HomeAutomation.HomeGenie](https://genielabs.github.io/HomeGenie/api/mig/core_api_config.html)

Implemented subset:

- [`/api/HomeAutomation.HomeGenie/Logging/RealTime.EventStream/`](https://genielabs.github.io/HomeGenie/api/mig/core_api_logging.html#1)
- [`/api/HomeAutomation.HomeGenie/Config/Modules.Get`](https://genielabs.github.io/HomeGenie/api/mig/core_api_config.html#2)
- [`/api/HomeAutomation.HomeGenie/Config/Modules.List`](https://genielabs.github.io/HomeGenie/api/mig/core_api_config.html#3)
- [`/api/HomeAutomation.HomeGenie/Config/Groups.List`](https://genielabs.github.io/HomeGenie/api/mig/core_api_config.html#4)

`EXAMPLE Request`
```
GET /api/HomeAutomation.HomeGenie/Config/Modules.Get/HomeAutomation.HomeGenie/mini
```

`Response`
```
{
"Name": "HG-Mini",
"Description": "HomeGenie Mini node",
"DeviceType": "Sensor",
"Domain": "HomeAutomation.HomeGenie",
"Address": "mini",
"Properties": [{
"Name": "Sensor.Luminance",
"Value": "114",
"Description": "",
"FieldType": "",
"UpdateTime": "2019-01-30T13:34:02.293Z"
},{
"Name": "Sensor.Temperature",
"Value": "18.25",
"Description": "",
"FieldType": "",
"UpdateTime": "2019-01-30T13:34:02.293Z"
}],
"RoutingNode": ""
}
```

### HomeGenie Mini builtin API

It's possible to control the 4 GPIOs on the `P1` expansion port using the following API:

- `/api/HomeAutomation.HomeGenie/<pin_number>/Control.On`
- `/api/HomeAutomation.HomeGenie/<pin_number>/Control.Off`
- `/api/HomeAutomation.HomeGenie/<pin_number>/Control.Level/<level>`
- `/api/HomeAutomation.HomeGenie/<pin_number>/Control.Toggle`

Where `<pin_name>` can be `D5`, `D6`, `D7` or `D8` and `<level>` a integer between `0` and `100`.

**Examples**

```
# Set output D6 to 50% (1.65V)
/api/HomeAutomation.HomeGenie/D6/Control.Level/50
# Set output D5 to 100% (3.3V)
/api/HomeAutomation.HomeGenie/D5/Control.Level/100
# or
/api/HomeAutomation.HomeGenie/D5/Control.On
# Set output D8 to 0% (0V)
/api/HomeAutomation.HomeGenie/D8/Control.Level/0
# or
/api/HomeAutomation.HomeGenie/D8/Control.Off
```


---

# Disclaimer
Expand Down
7 changes: 4 additions & 3 deletions src/HomeGenie.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,10 @@ namespace Service {
// HomeGenie-Mini Terminal CLI
if (Serial.available() > 0) {
String cmd = Serial.readStringUntil('\n');
auto apiCommand = APIRequest::parse(cmd);
// TODO: implement API commands from console input as well
// - see `HomeGenie::api(...)` method
if (!cmd.isEmpty()) {
// TODO: implement SerialCallback
onNetRequest(this, cmd.c_str(), nullptr);
}
}

// TODO: sort of system load index could be obtained by measuring time elapsed for `TaskManager::loop()` method
Expand Down
125 changes: 101 additions & 24 deletions src/net/MQTTServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@

namespace Net {

WebSocketsServer *ws;
MQTTBrokerMini *mb;
WebSocketsServer* ws;
MQTTBrokerMini* mb;

uint8_t* buf = nullptr;
size_t totalLength = 0;

void MQTTServer::begin() {

Expand All @@ -44,10 +47,14 @@ namespace Net {
mb = mqttBroker;

webSocket->begin();
webSocket->onEvent(webSocketEventStatic);
webSocket->onEvent([this](uint8_t n, WStype_t t, uint8_t * p, size_t l) {
webSocketEvent(n, t, p, l);
});

mqttBroker->begin();
mqttBroker->setCallback(mqttCallbackStatic);
mqttBroker->setCallback([this](uint8_t n, const Net::MQTT::Events_t* e, const String* topic_name, uint8_t* payload, uint16_t payload_length) {
mqttCallback(n, e, topic_name, payload, payload_length);
});

}

Expand All @@ -57,43 +64,113 @@ namespace Net {
}
}

void MQTTServer::mqttCallbackStatic(uint8_t num, Events_t event, String topic_name, uint8_t *payload,
void MQTTServer::mqttCallback(uint8_t num, const Events_t* event, const String* topic_name, uint8_t *payload,
uint16_t length_payload) {
auto msg = String((char*)payload);
switch (event){
case EVENT_CONNECT:
IO::Logger::trace(":%s [%d] >> CONNECT from '%s'", MQTTBROKER_NS_PREFIX, num, topic_name.c_str());
switch (*event){
case EVENT_CONNECT: {
IO::Logger::trace(":%s [%d] >> CONNECT from '%s'", MQTTBROKER_NS_PREFIX, num, (*topic_name).c_str());
break;
case EVENT_SUBSCRIBE:
IO::Logger::trace(":%s [%d] >> SUBSCRIBE to '%s'", MQTTBROKER_NS_PREFIX, num, topic_name.c_str());
}
case EVENT_SUBSCRIBE: {
IO::Logger::trace(":%s [%d] >> SUBSCRIBE to '%s'", MQTTBROKER_NS_PREFIX, num, (*topic_name).c_str());
break;
case EVENT_PUBLISH:
IO::Logger::trace(":%s [%d] >> PUBLISH to '%s'", MQTTBROKER_NS_PREFIX, num, topic_name.c_str());
// TODO: ... IMPLEMENT HG API HANDLE TOPIC
if (topic_name == "TODO_CHANGE_WITH_MY_ID/control") {
// TODO: Control API
}
case EVENT_PUBLISH: {
IO::Logger::trace(":%s [%d] >> PUBLISH to '%s'", MQTTBROKER_NS_PREFIX, num, (*topic_name).c_str());

auto controlTopic = String ("/") + WiFi.macAddress() + String("/command");
auto msg = String(payload, length_payload);
if ((*topic_name).endsWith(controlTopic)) { // initial part is the source node id, ending part is the destination node

JsonDocument doc;
deserializeJson(doc, msg);
if (apiCallback != nullptr && doc.containsKey("Domain") && doc.containsKey("Address") && doc.containsKey("Command")) {
auto domain = String((const char*)doc["Domain"]);
auto address = String((const char*)doc["Address"]);
auto command = String((const char*)doc["Command"]);
apiCallback(num, domain.c_str(), address.c_str(), command.c_str());
}
doc.clear();

} else {
// broadcast message to subscribed clients
mb->broadcast(num, (topic_name).c_str(), payload, length_payload);

// broadcast message to subscribed clients
mb->broadcast((*topic_name).c_str(), payload, length_payload);

}

break;
case EVENT_DISCONNECT:
IO::Logger::trace(":%s [%d] >> DISCONNECT =/", MQTTBROKER_NS_PREFIX, num);
}
case EVENT_DISCONNECT: {
IO::Logger::trace(":%s [%d] >> DISCONNECT =/", MQTTBROKER_NS_PREFIX, num);
break;
}
}
}

void MQTTServer::webSocketEventStatic(uint8_t num, WStype_t type, uint8_t *payload, size_t length) {
void MQTTServer::webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) {
switch(type) {
case WStype_DISCONNECTED:
case WStype_DISCONNECTED: {
if (mb->clientIsConnected(num)) mb->disconnect(num);
}
break;
case WStype_TEXT: {
}
break;
case WStype_FRAGMENT_BIN_START: {
if (buf != nullptr) {
free(buf);
}
buf = (uint8_t*)malloc(sizeof(uint8_t) * length);
if (buf != nullptr) {
memcpy(buf, payload, length);
totalLength = length;
}
}
break;
case WStype_BIN:
mb->parsing(num, payload, (uint16_t)length);
case WStype_FRAGMENT: {
if (buf != nullptr) {
uint8_t* old = buf;
buf = (uint8_t*) malloc(sizeof(uint8_t) * (totalLength + length));
if (buf != nullptr) {
memcpy(buf, old, totalLength);
memcpy(&buf[totalLength], payload, length);
totalLength += length;
}
free(old);
}
}
break;
case WStype_FRAGMENT_FIN: {
if (buf != nullptr) {
uint8_t* old = buf;
buf = (uint8_t*) malloc(sizeof(uint8_t) * (totalLength + length));
if (buf != nullptr) {
memcpy(buf, old, totalLength);
memcpy(&buf[totalLength], payload, length);
totalLength += length;
}
free(old);
}
if (buf != nullptr && totalLength > 0) {
mb->parsing(num, buf, (uint16_t) totalLength);
}
totalLength = 0;
free(buf);
buf = nullptr;
}
break;
case WStype_BIN: {
mb->parsing(num, payload, (uint16_t) length);
}
break;
}
}

void MQTTServer::broadcast(uint8_t num, String *topic, String *payload) {
mb->broadcast(num, *topic, (uint8_t *)payload->c_str(), (uint16_t)payload->length());
}

void MQTTServer::broadcast(String *topic, String *payload) {
mb->broadcast(*topic, (uint8_t *)payload->c_str(), (uint16_t)payload->length());
}
Expand Down
20 changes: 14 additions & 6 deletions src/net/MQTTServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,27 @@
namespace Net {
using namespace MQTT;

/// Simple MQTT Broker implementation over WebSockets
typedef std::function<void(uint8_t num, const char* domain, const char* address, const char* command)> ApiRequestEvent;

/// Simple MQTT Broker implementation over WebSockets
class MQTTServer : Task {
public:
void begin();
void loop() override;

void broadcast(String *topic, String *payload);
void broadcast(uint8_t num, String* topic, String* payload);
void broadcast(String* topic, String* payload);
void onRequest(ApiRequestEvent cb) {
apiCallback = cb;
}

void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length);
void mqttCallback(uint8_t num, const Events_t* event, const String* topic_name, uint8_t* payload, uint16_t length_payload);

static void webSocketEventStatic(uint8_t num, WStype_t type, uint8_t * payload, size_t length);
static void mqttCallbackStatic(uint8_t num, Events_t event, String topic_name, uint8_t * payload, uint16_t length_payload);
private:
WebSocketsServer *webSocket = nullptr;
MQTTBrokerMini *mqttBroker = nullptr;
WebSocketsServer* webSocket = nullptr;
MQTTBrokerMini* mqttBroker = nullptr;
ApiRequestEvent apiCallback = nullptr;
};

}
Expand Down
20 changes: 18 additions & 2 deletions src/net/NetManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ namespace Net {
}
break;
case WStype_TEXT: {
// message received
// clear-text message received
Serial.printf("[%u] TEXT\t%s\n", num, payload);
char message[length + 5];
sprintf(message, "api/%s", payload);
Expand All @@ -84,12 +84,13 @@ namespace Net {
case WStype_ERROR:
break;
case WStype_BIN: {
// binary-packed message received
MsgPack::Unpacker unpacker;
std::array<String, 2> req;
unpacker.feed(payload, length);
unpacker.unpack(req);
unpacker.clear();

// route message with response callback
String rid = req[0];
String request = "api/" + req[1];
Serial.printf("[%u] BIN\t%s\t%s\n", num, rid.c_str(), request.c_str());
Expand All @@ -116,6 +117,21 @@ namespace Net {

#ifndef DISABLE_MQTT
mqttServer = new MQTTServer();
mqttServer->onRequest([this](uint8_t num, const char* domain, const char* address, const char* command) {

auto c = String(command);
if (c == "Module.Describe") {
String topic = WiFi.macAddress() + "/" + domain + "/" + address + "/description";
String apiCommand = "/api/" + String(IOEventDomains::HomeAutomation_HomeGenie) + "/Config/Modules.Get/" + domain + "/" + address;
auto cb = MQTTResponseCallback(mqttServer, num, &topic);
netRequestHandler->onNetRequest(mqttServer, apiCommand.c_str(), &cb);
} else {
String apiCommand = "/api/" + String(domain) + "/" + String(address) + "/" + c;
auto cb = MQTTResponseCallback(mqttServer, 0, nullptr);
netRequestHandler->onNetRequest(mqttServer, apiCommand.c_str(), &cb);
}

});
mqttServer->begin();
#endif
timeClient = new TimeClient();
Expand Down
27 changes: 27 additions & 0 deletions src/net/NetManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
#include <net/BluetoothManager.h>
#endif

#include "io/IOEventDomains.h"

#include "TimeClient.h"

#define NETMANAGER_LOG_PREFIX "@Net::NetManager"
Expand All @@ -70,6 +72,31 @@ namespace Net {
virtual void error(const char* s) = 0;
};

class MQTTResponseCallback : public ResponseCallback {
public:
MQTTResponseCallback(MQTTServer *server, uint8_t clientId, String* destinationTopic) {
mq = server;
cid = clientId;
topic = destinationTopic;
}
void beginGetLength() override {
buffer = "";
};
void endGetLength() override {
mq->broadcast(topic, &buffer);
};
void write(const char* s) override {
buffer += s;
};
void writeAll(const char* s) override {};
void error(const char* s) override {};
private:
MQTTServer* mq;
uint8_t cid;
String* topic;
String buffer;
};

// WebSocketResponseCallback
class WebSocketResponseCallback : public ResponseCallback {
public:
Expand Down
5 changes: 3 additions & 2 deletions src/net/mqtt/MQTTBrokerMini.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ namespace Net { namespace MQTT {
void MQTTBrokerMini::runCallback(uint8_t num, Events_t event, uint8_t *topic_name, uint16_t length_topic_name,
uint8_t *payload, uint16_t length_payload) {
if (callback) {
delay(0);
callback(num, event, data_to_string(topic_name, length_topic_name), payload, length_payload);
delay(0); // TODO: <-- not sure what this delay is for
String topic = data_to_string(topic_name, length_topic_name);
callback(num, &event, &topic, payload, length_payload);
}
}

Expand Down
Loading

0 comments on commit 8f5589f

Please sign in to comment.