Ver Fonte

Initial commit

JDierkse há 5 anos atrás
commit
4f5998cf5f

+ 15 - 0
.gitignore

@@ -0,0 +1,15 @@
+*.o.*
+*.d.*
+*.a.*
+.*.swp
+.AppleDouble
+.debug
+.gdbinit*
+gdb*
+core-*
+*-core
+core.*
+*.core
+ToonBridge*
+!ToonBridge.cc
+!ToonBridge.ini

+ 3 - 0
.gitmodules

@@ -0,0 +1,3 @@
+[submodule "Makefiles"]
+	path = Makefiles
+	url = https://gogs.dierkse.nl/JDierkse/Makefiles

+ 1 - 0
Application/Makefile

@@ -0,0 +1 @@
+../Makefile

+ 85 - 0
Application/ToonBridge.cc

@@ -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 - 0
Libraries/Http

@@ -0,0 +1 @@
+../../Libraries/Http

+ 1 - 0
Libraries/Logging

@@ -0,0 +1 @@
+../../Libraries/Logging

+ 1 - 0
Libraries/MQTT

@@ -0,0 +1 @@
+../../Libraries/MQTT

+ 1 - 0
Libraries/SimpleSignal

@@ -0,0 +1 @@
+../../Libraries/SimpleSignal

+ 1 - 0
Libraries/Utilities

@@ -0,0 +1 @@
+../../Libraries/Utilities

+ 1 - 0
Libraries/clipp

@@ -0,0 +1 @@
+../../Libraries/clipp

+ 1 - 0
Libraries/inih

@@ -0,0 +1 @@
+../../Libraries/inih

+ 1 - 0
Libraries/json

@@ -0,0 +1 @@
+../../Libraries/json

+ 1 - 0
Makefile

@@ -0,0 +1 @@
+Makefiles/Makefile

+ 31 - 0
Makefile.conf

@@ -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 - 0
Makefile.target

@@ -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

+ 1 - 0
Makefiles

@@ -0,0 +1 @@
+Subproject commit cd9ef1a1b0f7b88f36b0c51b4209d1859aca83aa

+ 81 - 0
Toon/Bridge.cpp

@@ -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 - 0
Toon/Bridge.h

@@ -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 - 0
Toon/MQTTSettings.h

@@ -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 - 0
Toon/Makefile

@@ -0,0 +1 @@
+../Makefile

+ 169 - 0
Toon/MessageHandler.cpp

@@ -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 - 0
Toon/MessageHandler.h

@@ -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 - 0
Toon/ToonSettings.h

@@ -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 - 0
Toon/WebSocketSubscription.cpp

@@ -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 - 0
Toon/WebSocketSubscription.h

@@ -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 - 0
ToonBridge.ini

@@ -0,0 +1,15 @@
+[ToonBridge]
+Port =
+
+[Toon]
+AgreementID = 
+AccessToken = 
+DisplayCommonName = 
+ApplicationID = 
+CallbackURL = 
+
+[MQTT]
+Hostname = 
+Port = 
+Topic = 
+