Initial commit

This commit is contained in:
2020-06-16 09:43:20 +02:00
commit 4f5998cf5f
26 changed files with 696 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
*.o.*
*.d.*
*.a.*
.*.swp
.AppleDouble
.debug
.gdbinit*
gdb*
core-*
*-core
core.*
*.core
ToonBridge*
!ToonBridge.cc
!ToonBridge.ini
+3
View File
@@ -0,0 +1,3 @@
[submodule "Makefiles"]
path = Makefiles
url = https://gogs.dierkse.nl/JDierkse/Makefiles
+1
View File
@@ -0,0 +1 @@
../Makefile
+85
View File
@@ -0,0 +1,85 @@
#include "Toon/Bridge.h"
#include <INIReader.h>
#include <Logging.h>
#include <signal.h>
#include <iostream>
#include <sstream>
#include <string>
int main(int argc, char** argv)
{
try
{
Logging::OpenLog();
Logging::SetLogMask(Logging::Severity::Debug);
INIReader iniReader("ToonBridge.ini");
if (iniReader.ParseError() != 0)
throw std::runtime_error("Can't read ToonBridge.ini.");
Logging::Log(Logging::Severity::Info, "Starting ToonBridge");
int port = iniReader.GetInteger("ToonBridge", "Port", 0);
if (port == 0)
throw std::runtime_error("Port directive missing in ini file.");
ToonBridge::Toon::ToonSettings toonSettings;
toonSettings.agreementId = iniReader.Get("Toon", "AgreementID", "");
if (toonSettings.agreementId.empty())
throw std::runtime_error("Toon AgreementID directive missing in ini file.");
toonSettings.accessToken = iniReader.Get("Toon", "AccessToken", "");
if (toonSettings.accessToken.empty())
throw std::runtime_error("Toon AccessToken directive missing in ini file.");
toonSettings.displayCommonName = iniReader.Get("Toon", "DisplayCommonName", "");
if (toonSettings.displayCommonName.empty())
throw std::runtime_error("Toon DisplayCommonName directive missing in ini file.");
toonSettings.applicationId = iniReader.Get("Toon", "ApplicationID", "");
if (toonSettings.applicationId.empty())
throw std::runtime_error("Toon ApplicationID directive missing in ini file.");
toonSettings.callbackUrl = iniReader.Get("Toon", "CallbackURL", "");
if (toonSettings.callbackUrl.empty())
throw std::runtime_error("Toon CallbackURL directive missing in ini file.");
ToonBridge::Toon::MQTTSettings mqttSettings;
mqttSettings.hostname = iniReader.Get("MQTT", "Hostname", "");
if (mqttSettings.hostname.empty())
throw std::runtime_error("MQTT Hostname directive missing in ini file.");
mqttSettings.port = iniReader.GetInteger("MQTT", "Port", 0);
if (mqttSettings.port == 0)
throw std::runtime_error("MQTT Port directive missing in ini file.");
mqttSettings.topic = iniReader.Get("MQTT", "Topic", "");
if (mqttSettings.topic.empty())
throw std::runtime_error("MQTT Topic directive missing in ini file.");
ToonBridge::Toon::Bridge bridge(port, toonSettings, mqttSettings);
Logging::Log(Logging::Severity::Info, "Startup Complete");
bridge.Wait();
Logging::Log(Logging::Severity::Info, "Stopping ToonBridge...");
Logging::CloseLog();
}
catch (const std::exception& e)
{
std::cerr << "Exception caught" << std::endl;
std::stringstream ss;
ss << "Type : " << typeid(e).name() << std::endl;
ss << "ERROR: " << e.what() << std::endl;
Logging::Log(Logging::Severity::Error, ss.str());
Logging::CloseLog();
return -1;
}
return 0;
}
+1
View File
@@ -0,0 +1 @@
../../Libraries/Http
+1
View File
@@ -0,0 +1 @@
../../Libraries/Logging
+1
View File
@@ -0,0 +1 @@
../../Libraries/MQTT
+1
View File
@@ -0,0 +1 @@
../../Libraries/SimpleSignal
+1
View File
@@ -0,0 +1 @@
../../Libraries/Utilities
+1
View File
@@ -0,0 +1 @@
../../Libraries/clipp
+1
View File
@@ -0,0 +1 @@
../../Libraries/inih
+1
View File
@@ -0,0 +1 @@
../../Libraries/json
Symlink
+1
View File
@@ -0,0 +1 @@
Makefiles/Makefile
+31
View File
@@ -0,0 +1,31 @@
#
# Makefile.conf
#
LFLAGS += -lHttp -lcrypto -lcurl -lssl -lz
LFLAGS += -L$(ROOTPATH)/Libraries/Http/lib/$(ARCH)
CFLAGS += -I$(ROOTPATH)/Libraries/Http/include
LFLAGS += -lLogging
LFLAGS += -L$(ROOTPATH)/Libraries/Logging/lib/$(ARCH)
CFLAGS += -I$(ROOTPATH)/Libraries/Logging/include
LFLAGS += -lUtilities
LFLAGS += -L$(ROOTPATH)/Libraries/Utilities/lib/$(ARCH)
CFLAGS += -I$(ROOTPATH)/Libraries/Utilities/include
LFLAGS += -lMQTT -lmosquitto
LFLAGS += -L$(ROOTPATH)/Libraries/MQTT/lib/$(ARCH)
CFLAGS += -I$(ROOTPATH)/Libraries/MQTT/include
CFLAGS += -I$(ROOTPATH)/Libraries/clipp/include
CFLAGS += -I$(ROOTPATH)/Libraries/inih
CFLAGS += -I$(ROOTPATH)/Libraries/json/include/nlohmann -I$(ROOTPATH)/Libraries/json/include
CFLAGS += -I$(ROOTPATH)/Libraries/SimpleSignal
LFLAGS += -pthread
CFLAGS += -I$(ROOTPATH) -I$(ROOTPATH)/Libraries
DEBUGDIR := .debug
+13
View File
@@ -0,0 +1,13 @@
#
# Makefile.target
#
ToonBridge.$(ARCH): $(OBJECTS) Application/ToonBridge.o.$(ARCH)
$(call build_target_arch,$@,$^)
ToonBridge:
$(call build_target,$@)
.DEFAULT_GOAL := ToonBridge
TARGETS += ToonBridge
Submodule
+1
Submodule Makefiles added at cd9ef1a1b0
+81
View File
@@ -0,0 +1,81 @@
#include "Bridge.h"
#include <Logging.h>
#include <functional>
#include <sstream>
namespace ToonBridge {
namespace Toon {
Bridge::Bridge(int port, const ToonSettings& toonSettings, const MQTTSettings& mqttSettings) :
m_mqttClient(mqttSettings.hostname, mqttSettings.port),
m_messageHandler(m_mqttClient),
m_port(port),
m_toonSettings(toonSettings),
m_mqttSettings(mqttSettings)
{
Start();
}
Bridge::~Bridge()
{
MQTT::MQTTMessage message;
message.topic = m_mqttSettings.topic;
message.payload = "ToonBridge Stop";
m_mqttClient.Send(message);
}
void Bridge::Wait()
{
m_pHttpServer->Wait();
}
void Bridge::Start()
{
Http::HttpServer::CallbackMethod httpCallback = std::bind(&Bridge::HttpCallback, this, std::placeholders::_1, std::placeholders::_2);
m_pHttpServer.reset(new Http::HttpServer(m_port, httpCallback));
m_pWebSocketSubscription.reset(new WebSocketSubscription(m_toonSettings));
m_messageHandler.Connect(std::bind(&Bridge::TimeToLiveCallback, this, m_pWebSocketSubscription.get(), std::placeholders::_1));
MQTT::MQTTMessage message;
message.topic = m_mqttSettings.topic;
message.payload = "ToonBridge Start";
m_mqttClient.Send(message);
}
Http::HttpServer::HttpReply Bridge::HttpCallback(const std::string& uri, const std::vector<Http::HttpPostData>& postData)
{
Http::HttpServer::HttpReply reply;
reply.status = Http::HttpServer::HttpReply::Status::Ok;
try
{
if (postData.size() > 0)
{
for (auto& data : postData)
{
if (data.name == "data")
m_messageHandler.HandleMessage(data.value);
}
}
}
catch (const std::exception& e)
{
std::stringstream ss;
ss << "Bridge::HttpCallback() - Error: " << e.what() << std::endl;
Logging::Log(Logging::Severity::Error, ss.str());
}
return reply;
}
void Bridge::TimeToLiveCallback(ToonBridge::Toon::WebSocketSubscription* pSubscription, int timeToLive)
{
if (timeToLive < 20)
pSubscription->Reconnect();
}
} // namespace Toon
} // namespace ToonBridge
+45
View File
@@ -0,0 +1,45 @@
#ifndef TOON_BRIDGE_H
#define TOON_BRIDGE_H
#include "MessageHandler.h"
#include "MQTTSettings.h"
#include "ToonSettings.h"
#include "WebSocketSubscription.h"
#include <HttpServer.h>
#include <MQTT.h>
#include <memory>
#include <string>
namespace ToonBridge {
namespace Toon {
class Bridge
{
public:
Bridge(int port, const ToonSettings& toonSettings, const MQTTSettings& mqttSettings);
~Bridge();
void Wait();
private:
void Start();
Http::HttpServer::HttpReply HttpCallback(const std::string& uri, const std::vector<Http::HttpPostData>& postData);
void TimeToLiveCallback(WebSocketSubscription* pSubscription, int timeToLive);
private:
std::unique_ptr<Http::HttpServer> m_pHttpServer;
MQTT::MQTT m_mqttClient;
MessageHandler m_messageHandler;
int m_port;
ToonSettings m_toonSettings;
MQTTSettings m_mqttSettings;
std::unique_ptr<WebSocketSubscription> m_pWebSocketSubscription;
};
} // namespace Toon
} // namespace ToonBridge
#endif // TOON_BRIDGE_H
+21
View File
@@ -0,0 +1,21 @@
#ifndef TOON_MQTTSETTINGS_H
#define TOON_MQTTSETTINGS_H
#include <string>
namespace ToonBridge {
namespace Toon {
struct MQTTSettings
{
std::string hostname;
int port;
std::string topic;
};
} // namespace Toon
} // namespace ToonBridge
#endif // TOON_MQTTSETTINGS_H
+1
View File
@@ -0,0 +1 @@
../Makefile
+169
View File
@@ -0,0 +1,169 @@
#include "MessageHandler.h"
#include <Logging.h>
#include <sstream>
namespace ToonBridge {
namespace Toon {
MessageHandler::MessageHandler(MQTT::MQTT& mqtt) :
m_mqtt(mqtt)
{
}
MessageHandler::~MessageHandler()
{
}
void MessageHandler::HandleMessage(const std::string& message)
{
nlohmann::json data = nlohmann::json::parse(message);
auto updateDataSet = data["updateDataSet"];
if (updateDataSet.contains("powerUsage"))
HandlePowerUsageMessage(updateDataSet["powerUsage"]);
if (updateDataSet.contains("gasUsage"))
HandleGasUsageMessage(updateDataSet["gasUsage"]);
if (updateDataSet.contains("waterUsage"))
HandleWaterUsageMessage(updateDataSet["waterUsage"]);
if (updateDataSet.contains("thermostatInfo"))
HandleThermostatInfoMessage(updateDataSet["thermostatInfo"]);
if (updateDataSet.contains("thermostatStates"))
HandleThermostatStatesMessage(updateDataSet["thermostatStates"]);
m_signal.emit(data["timeToLiveSeconds"]);
}
size_t MessageHandler::Connect(TTLCallbackMethod function)
{
return m_signal.connect(function);
}
void MessageHandler::Disconnect(size_t connection)
{
m_signal.disconnect(connection);
}
void MessageHandler::HandlePowerUsageMessage(const nlohmann::json& data)
{
MQTT::MQTTMessage message;
message.topic = "ToonBridge/PowerUsage";
message.payload = data.dump();
m_mqtt.Send(message);
/*
{"value":419,
"dayCost":1.21,
"valueProduced":0,
"valueSolar":0,
"maxSolar":0,
"avgValue":493.92,
"avgDayValue":11854.00,
"avgProduValue":0,
"meterReading":5528208,
"meterReadingLow":6886191,
"meterReadingProdu":2,
"meterReadingLowProdu":87,
"dayUsage":5268,
"dayLowUsage":2860,
"isSmart":1,
"lowestDayValue":301,
"solarProducedToday":0,
"lastUpdatedFromDisplay":1591801516233}
*/
}
void MessageHandler::HandleGasUsageMessage(const nlohmann::json& data)
{
MQTT::MQTTMessage message;
message.topic = "ToonBridge/GasUsage";
message.payload = data.dump();
m_mqtt.Send(message);
/*
{"value":0,
"dayCost":0.32,
"avgValue":26.48,
"meterReading":2613589,
"avgDayValue":635.43,
"dayUsage":389,
"isSmart":1,
"lastUpdatedFromDisplay":1591801517950}
*/
}
void MessageHandler::HandleWaterUsageMessage(const nlohmann::json& data)
{
MQTT::MQTTMessage message;
message.topic = "ToonBridge/WaterUsage";
message.payload = data.dump();
m_mqtt.Send(message);
/*
{"installed":0,
"value":0,
"dayCost":0,
"dayUsage":0,
"meterReading":0,
"isSmart":0,
"lastUpdatedFromDisplay":1591801517802}
*/
}
void MessageHandler::HandleThermostatInfoMessage(const nlohmann::json& data)
{
MQTT::MQTTMessage message;
message.topic = "ToonBridge/ThermostatInfo";
message.payload = data.dump();
m_mqtt.Send(message);
/*
{"currentSetpoint":1750,
"currentDisplayTemp":2100,
"programState":0,
"activeState":1,
"nextProgram":-1,
"nextState":-1,
"nextTime":0,
"nextSetpoint":0,
"hasBoilerFault":0,
"errorFound":255,
"boilerModuleConnected":1,
"realSetpoint":1750,
"burnerInfo":"0",
"otCommError":"0",
"currentModulationLevel":0,
"haveOTBoiler":1,
"lastUpdatedFromDisplay":1591801515921,
"setByLoadShifting":0}
*/
}
void MessageHandler::HandleThermostatStatesMessage(const nlohmann::json& data)
{
MQTT::MQTTMessage message;
message.topic = "ToonBridge/ThermostatStates";
message.payload = data.dump();
m_mqtt.Send(message);
/*
thermostatStates
{"currentSetpoint":1750,
"currentDisplayTemp":2100,
"programState":0,
"activeState":1,
"nextProgram":-1,
"nextState":-1,
"nextTime":0,
"nextSetpoint":0,
"hasBoilerFault":0,
"errorFound":255,
"boilerModuleConnected":1,
"realSetpoint":1750,
"burnerInfo":"0",
"otCommError":"0",
"currentModulationLevel":0,
"haveOTBoiler":1,
"lastUpdatedFromDisplay":1591801515921,
"setByLoadShifting":0}
*/
}
} // namespace Toon
} // namespace ToonBridge
+46
View File
@@ -0,0 +1,46 @@
#ifndef TOON_MESSAGEHANDLER_H
#define TOON_MESSAGEHANDLER_H
#include <MQTT.h>
#include <SimpleSignal.h>
#include <json.hpp>
#include <string>
namespace ToonBridge {
namespace Toon {
class MessageHandler
{
public:
MessageHandler(MQTT::MQTT& mqtt);
~MessageHandler();
void HandleMessage(const std::string& message);
public:
typedef std::function<void(int timeToLive)> TTLCallbackMethod;
public:
size_t Connect(TTLCallbackMethod function);
void Disconnect(size_t connection);
private:
typedef Simple::Signal<void(int timeToLive)> TTLMessage;
private:
void HandlePowerUsageMessage(const nlohmann::json& data);
void HandleGasUsageMessage(const nlohmann::json& data);
void HandleWaterUsageMessage(const nlohmann::json& data);
void HandleThermostatInfoMessage(const nlohmann::json& data);
void HandleThermostatStatesMessage(const nlohmann::json& data);
private:
MQTT::MQTT& m_mqtt;
TTLMessage m_signal;
};
} // namespace Toon
} // namespace ToonBridge
#endif // DEVICE_ACTIVITYDEVICE_H
+22
View File
@@ -0,0 +1,22 @@
#ifndef TOON_TOONSETTINGS_H
#define TOON_TOONSETTINGS_H
#include <string>
namespace ToonBridge {
namespace Toon {
struct ToonSettings
{
std::string agreementId;
std::string accessToken;
std::string displayCommonName;
std::string applicationId;
std::string callbackUrl;
};
} // namespace Toon
} // namespace ToonBridge
#endif // TOON_TOONSETTINGS_H
+102
View File
@@ -0,0 +1,102 @@
#include "WebSocketSubscription.h"
#include <json.hpp>
#include <sstream>
namespace ToonBridge {
namespace Toon {
WebSocketSubscription::WebSocketSubscription(const ToonSettings& toonSettings) :
m_toonSettings(toonSettings)
{
OpenWebSocket();
}
WebSocketSubscription::~WebSocketSubscription()
{
CloseWebSocket();
}
void WebSocketSubscription::Reconnect()
{
CloseWebSocket();
OpenWebSocket();
}
void WebSocketSubscription::OpenWebSocket()
{
std::stringstream url;
url << "https://api.toon.eu/toon/v3/";
url << m_toonSettings.agreementId;
url << "/webhooks";
std::vector<std::string> subscribedActions;
subscribedActions.push_back("Thermostat");
subscribedActions.push_back("PowerUsage");
subscribedActions.push_back("BoilerError");
nlohmann::json data;
data["applicationId"] = m_toonSettings.applicationId;
data["callbackUrl"] = m_toonSettings.callbackUrl;
data["subscribedActions"] = subscribedActions;
Http::HttpRequest request(url.str());
request.Method(Http::HttpRequest::Method::POST);
request.Headers(RequestHeaders());
request.Data(data.dump());
m_httpClient.Open(request);
}
void WebSocketSubscription::WebSocketStatus()
{
std::stringstream url;
url << "https://api.toon.eu/toon/v3/";
url << m_toonSettings.agreementId;
url << "/webhooks/";
Http::HttpRequest request(url.str());
request.Headers(RequestHeaders());
nlohmann::json answer = nlohmann::json::parse(m_httpClient.Open(request));
}
void WebSocketSubscription::CloseWebSocket()
{
std::stringstream url;
url << "https://api.toon.eu/toon/v3/";
url << m_toonSettings.agreementId;
url << "/webhooks/";
url << m_toonSettings.applicationId;
Http::HttpRequest request(url.str());
request.Method(Http::HttpRequest::Method::DELETE);
request.Headers(RequestHeaders());
m_httpClient.Open(request);
}
std::vector<std::string> WebSocketSubscription::RequestHeaders()
{
std::vector<std::string> headers;
headers.push_back("cache-control: no-cache");
headers.push_back("accept: application/json");
headers.push_back("content-type: application/json");
std::stringstream header;
header << "authorization: Bearer " << m_toonSettings.accessToken;
headers.push_back(header.str());
header.str("");
header << "X-CommonName: " << m_toonSettings.displayCommonName;
headers.push_back(header.str());
header.str("");
header << "X-Agreement-ID: " << m_toonSettings.agreementId;
headers.push_back(header.str());
return headers;
}
} // namespace Toon
} // namespace ToonBridge
+36
View File
@@ -0,0 +1,36 @@
#ifndef TOON_WEBSOCKETSUBSCRIPTION_H
#define TOON_WEBSOCKETSUBSCRIPTION_H
#include "ToonSettings.h"
#include <HttpClient.h>
#include <string>
#include <vector>
namespace ToonBridge {
namespace Toon {
class WebSocketSubscription
{
public:
WebSocketSubscription(const ToonSettings& toonSettings);
~WebSocketSubscription();
void Reconnect();
private:
void OpenWebSocket();
void WebSocketStatus();
void CloseWebSocket();
std::vector<std::string> RequestHeaders();
private:
Http::HttpClient m_httpClient;
ToonSettings m_toonSettings;
};
} // namespace Toon
} // namespace ToonBridge
#endif // TOON_WEBSOCKETSUBSCRIPTION_H
+15
View File
@@ -0,0 +1,15 @@
[ToonBridge]
Port =
[Toon]
AgreementID =
AccessToken =
DisplayCommonName =
ApplicationID =
CallbackURL =
[MQTT]
Hostname =
Port =
Topic =