diff --git a/MqttClient.h b/MqttClient.h index be130a313ff828d45f1f261a9c766f1960aace71..4b6ccafda537776e31bad3c2ee9f0a3fc4ef3ff2 100644 --- a/MqttClient.h +++ b/MqttClient.h @@ -1,5 +1,4 @@ #pragma once - #include <iostream> #include <cstring> #include <cstdio> @@ -15,40 +14,44 @@ private: dbSqlite* db; public: + // constructs the class and connects to configured mqtt server MqttClient(const char* id_new, dbSqlite* db_new) : mosquittopp(id_new) { if (DEBUG) std::cout << "MqttClient::MqttClient()" << std::endl; this->db = db_new; int keepalive = 60; if (DEBUG) std::cout << "MqttClient: Connecting to host: " << db->getSettings().mqtt_host << ":" << db->getSettings().mqtt_port << std::endl; - // Benutzername und Passwort setzen + // Set username and password mosquittopp::username_pw_set(db->getSettings().mqtt_user.c_str(), db->getSettings().mqtt_pass.c_str()); - // Asynchrone Verbindung aufbauen + // Start asynchrone connection mosquittopp::connect(db->getSettings().mqtt_host.c_str(), db->getSettings().mqtt_port, keepalive); - // Subscribe zu Backend Topic + // Subscribe to backend topic mosquittopp::subscribe(NULL, (db->getSettings().mqtt_topic_backend + "#").c_str()); - // Startet den Thread + // Start the thread in loop mosquittopp::loop_start(); } ~MqttClient() { - // Stoppt den Thread + // Stopps the loop thread loop_stop(); // Mosuqitto library cleanup mosqpp::lib_cleanup(); } + // executed if mqtt is connected void on_connect(int rc) { if (!rc) std::cout << "MqttClient::on_connect(): Connected(" << rc << ")" << std::endl; } + // executed if mqtt has subscribed to a topic void on_subscribe(int mid, int qos_count, const int* granted_qos) { if (DEBUG) std::cout << "MqttClient::on_subscribe()" << std::endl; } + // sends a message to given topic bool send_message(const char* message, const char* topic) { // Send message - depending on QoS, mosquitto lib managed re-submission this the thread // @@ -64,12 +67,13 @@ public: return (ret == MOSQ_ERR_SUCCESS); } + // executed if message was published void on_publish(int mid){ if(DEBUG) std::cout << "MqttClient::on_publish(" << mid << ") published " << std::endl; } + // executed if a message arrived void on_message(const mosquitto_message* message) { - //if (DEBUG) std::cout << "MqttClient::on_message()" << " received message of topic: " << message->topic << " Data: " << reinterpret_cast<char*>(message->payload) << std::endl; if (DEBUG) std::cout << "MqttClient::on_message()" << " received message of topic: " << message->topic << std::endl; const char payload_size = sizeof(message->payload) + 1; char buf[payload_size]; @@ -77,20 +81,10 @@ public: std::string topic = message->topic; std::string topicDb = db->getSettings().mqtt_topic_backend + "settings"; - //if (!strcmp(message->topic, (db->getSettings().mqtt_topic_backend + "settings").c_str())) { + // if message has frontend settings, update in sqlite if(topic.compare(topicDb) == 0) { - //memset(buf, 0, message->payloadlen); - - /* Copy N-1 bytes to ensure always 0 terminated. */ - //memcpy(buf, message->payload, message->payloadlen); - if (DEBUG) std::cout << "MqttClient::on_message() - new settings found" << std::endl; db->updateFrontendSettings(reinterpret_cast<char*>(message->payload)); - //std::string client = str.substr(str.find("_") + 1); - //std::cout << "Client " + client + " requested update" << std::endl; - //std::string result = db->getFrontendData().c_str(); - //snprintf(buf, payload_size, result.c_str()); - //publish(NULL, db->getSettings().mqtt_topic_frontend.c_str(), strlen(result.c_str()), result.c_str()); } } }; \ No newline at end of file diff --git a/backend.cpp b/backend.cpp index 374bccfb0f7bea8671c20fe8e0d0e940123569ce..a196d489f19eaf5c8e876ec61ae2cf1d30d4fcc3 100644 --- a/backend.cpp +++ b/backend.cpp @@ -10,7 +10,7 @@ #include "dbSqlite.h" #include "structs.h" -#define DEBUG true +#define DEBUG false using std::string; using std::cout; @@ -19,7 +19,9 @@ using std::thread; // Database object dbSqlite* db; +// Openweather Object openweathermap* owmw; +// MQTT client Object MqttClient* mqttClient; // Thread for openweathermap:getWeather @@ -30,15 +32,22 @@ void schedulerWeather(int time) { std::vector<Region> region; std::vector<Frontend> frontend; + // do forever while (1) { + // get frontend regions from database region = db->queryRegions(); + // get frontends from database frontend = db->queryFrontends(); + // for each region in object do for (int i = 0; i < region.size(); i++) { cout << "Weather new RUN(" << region[i].plz <<") "; if (DEBUG) cout << "Wetterstation::schedulerWeather(" + region[i].plz + ")" << endl; + // get weather for region owmw->getWeather(region[i].plz, region[i].lngCode); weather = db->getFrontendDataWeather().c_str(); + // for each frontend in object do for (int fr = 0; fr < frontend.size(); fr++) { + // if frontend is in current region send mqtt message with current weather if (frontend[fr].plz == region[i].plz) { cout << "Send weather to frontend: " << frontend[fr].frontendId << endl; topic = db->getSettings().mqtt_topic_frontend + frontend[fr].frontendId + "/weather"; @@ -56,23 +65,37 @@ void schedulerForecast(int time) { if (DEBUG) cout << "Wetterstation::schedulerForecast()" << endl; string topic; string forecast; + string forecastNext; std::vector<Region> region; std::vector<Frontend> frontend; + // do forever while (1) { + // get frontend regions from database region = db->queryRegions(); + // get frontends from database frontend = db->queryFrontends(); + // for each region in object do for (int i = 0; i < region.size(); i++) { cout << "Forecast new RUN(" << region[i].plz << ") "; if (DEBUG) cout << "Wetterstation::schedulerForecast(" + region[i].plz + ")" << endl; owmw->getForecast(region[i].plz, region[i].lngCode); forecast = db->getFrontendDataForecast().c_str(); + forecastNext = db->getFrontendDataForecastNext().c_str(); + // for each frontend in object do for (int fr = 0; fr < frontend.size(); fr++) { + // if frontend is in current region send mqtt message with weather forecast if (frontend[fr].plz == region[i].plz) { + // Send forecasts cout << "Send forecast to frontend: " << frontend[fr].frontendId << endl; topic = db->getSettings().mqtt_topic_frontend + frontend[fr].frontendId + "/forecast"; mqttClient->send_message(forecast.c_str(), topic.c_str()); std::this_thread::sleep_for(std::chrono::milliseconds(50)); + // Send next forecast + cout << "Send next forecast to frontend: " << frontend[fr].frontendId << endl; + topic = db->getSettings().mqtt_topic_frontend + frontend[fr].frontendId + "/forecastNext"; + mqttClient->send_message(forecastNext.c_str(), topic.c_str()); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); } } } @@ -88,14 +111,20 @@ void schedulerInfluxGraph(int time) { std::vector<Region> region; std::vector<Frontend> frontend; + // do forever while (1) { + // get frontend regions from database region = db->queryRegions(); + // get frontends from database frontend = db->queryFrontends(); + // for each region in object do for (int i = 0; i < region.size(); i++) { cout << "Graph new RUN(" << region[i].plz << ") " << endl; if (DEBUG) cout << "Wetterstation::schedulerInfluxGraph(" + region[i].plz + ")" << endl; influxData = db->getGraphFromInfluxDb(region[i].plz); + // for each frontend in object do for (int fr = 0; fr < frontend.size(); fr++) { + // if frontend is in current region send mqtt message with graph data if (frontend[fr].plz == region[i].plz) { cout << "Send InfluxGraph to frontend: " << frontend[fr].frontendId << endl; topic = db->getSettings().mqtt_topic_frontend + frontend[fr].frontendId + "/graph"; @@ -114,11 +143,16 @@ void schedulerInfluxSensors(int time) { string topic; std::vector<Frontend> frontend; + // do forever while (1) { + // get frontends from database frontend = db->queryFrontends(); cout << "Sensors new RUN() " << endl; + // for each frontend in object do for (int fr = 0; fr < frontend.size(); fr++) { + // for each sensor node in frontend object do if (frontend[fr].node1Id.size() > 0) { + // if frontend has inhouse sensor send sensor data if (frontend[fr].node1Innen.size() > 0) { string innenData = db->getFrontendDataSensors(frontend[fr].node1Id, frontend[fr].node1Innen); if (DEBUG) cout << "Wetterstation::schedulerInfluxSensors(" + frontend[fr].node1Id + ", " + frontend[fr].node1Innen + ")" << endl; @@ -127,6 +161,7 @@ void schedulerInfluxSensors(int time) { mqttClient->send_message(innenData.c_str(), topic.c_str()); std::this_thread::sleep_for(std::chrono::milliseconds(50)); } + // if frontend has outdoor sensor send sensor data if (frontend[fr].node1Aussen.size() > 0) { string aussenData = db->getFrontendDataSensors(frontend[fr].node1Id, frontend[fr].node1Aussen); if (DEBUG) cout << "Wetterstation::schedulerInfluxSensors(" + frontend[fr].node1Id + ", " + frontend[fr].node1Aussen + ")" << endl; @@ -141,6 +176,7 @@ void schedulerInfluxSensors(int time) { } } +// start MQTT service void mqttStart() { if (DEBUG) cout << "Wetterstation::mqttStart()" << endl; mosqpp::lib_init(); @@ -148,11 +184,11 @@ void mqttStart() { mosqpp::lib_cleanup(); } -// Main method +// main method is executed with program startup int main() { if (DEBUG) cout << "Wetterstation::main()" << endl; - // Sockets aktivieren + // activate sockets WSADATA wsaData; int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != 0) { @@ -160,31 +196,32 @@ int main() { return 1; } - // Initialisieren der Datenbank + // initialize database object db = new dbSqlite(); - // Initialisieren der Openweathermap + // Initialize Openweathermap object owmw = new openweathermap(db); - // MQTT starten + // start MQTT client mqttStart(); - // Thread für getWeather starten + // start thread for getWeather thread thrWeather{ schedulerWeather, 60000 }; std::this_thread::sleep_for(std::chrono::milliseconds(100)); - // Thread für getForecast starten + // start thread for getForecast thread thrForecast{ schedulerForecast, 60000 }; std::this_thread::sleep_for(std::chrono::milliseconds(100)); - // Thread für getGraphFromInfluxDb starten + // start thread for getGraphFromInfluxDb thread thrGraph{ schedulerInfluxGraph, 60000 }; std::this_thread::sleep_for(std::chrono::milliseconds(100)); - // Thread für getSensorsFromInfluxDb starten + // start thread for getSensorsFromInfluxDb thread thrSensors{ schedulerInfluxSensors, 30000 }; std::this_thread::sleep_for(std::chrono::milliseconds(100)); + // do forever while (1) { Sleep(1000); } diff --git a/dbSqlite.h b/dbSqlite.h index 3812e38a53b6419afc1dbfb7573fe47d3ad14a1b..7456729d899ce01c93ea931ab5f55d16632b310a 100644 --- a/dbSqlite.h +++ b/dbSqlite.h @@ -34,15 +34,18 @@ public: this->querySettings(); }; + // get settings from database Settings getSettings() { if (DEBUG) cout << "dbSqlite::getSettings()" << endl; return this->settings; } + // returns current weather as object WeatherData getSWeather() { return this->sWeather; } + // returns current weather object as json string getFrontendDataWeather() { if (DEBUG) cout << "dbSqlite::getFrontendDataWeather()" << endl; nlohmann::json jObj; @@ -73,6 +76,7 @@ public: return jObj.dump(); } + // returns forecast object as json string getFrontendDataForecast() { if (DEBUG) cout << "dbSqlite::getFrontendDataForecast()" << endl; nlohmann::json jObj; @@ -106,10 +110,43 @@ public: index++; } } - + + return jObj.dump(); + } + + // returns only next forecast as json + string getFrontendDataForecastNext() { + if (DEBUG) cout << "dbSqlite::getFrontendDataForecast()" << endl; + nlohmann::json jObj; + + jObj["plz"] = vecForecast[1].plz; + jObj["lngCode"] = vecForecast[1].lngCode; + jObj["sunrise"] = vecForecast[1].sunrise; + jObj["sunset"] = vecForecast[1].sunset; + jObj["visibility"] = vecForecast[1].visibility; + jObj["temp"] = vecForecast[1].temp; + jObj["tempFeelsLike"] = vecForecast[1].tempFeelsLike; + jObj["tempMin"] = vecForecast[1].tempMin; + jObj["tempMax"] = vecForecast[1].tempMax; + jObj["humidity"] = vecForecast[1].humidity; + jObj["pressure"] = vecForecast[1].pressure; + jObj["windSpeed"] = vecForecast[1].windSpeed; + jObj["windDeg"] = vecForecast[1].windDeg; + jObj["clouds"] = vecForecast[1].clouds; + jObj["rain1h"] = vecForecast[1].rain1h; + jObj["rain3h"] = vecForecast[1].rain3h; + jObj["snow1h"] = vecForecast[1].snow1h; + jObj["snow3h"] = vecForecast[1].snow3h; + jObj["icon1Id"] = vecForecast[1].icon1Id; + jObj["icon1"] = vecForecast[1].icon1; + jObj["icon2Id"] = vecForecast[1].icon2Id; + jObj["icon2"] = vecForecast[1].icon2; + jObj["from"] = vecForecast[1].from; + return jObj.dump(); } + // returns sensor data as json string getFrontendDataSensors(string node, string sensor) { if (DEBUG) cout << "dbSqlite::getFrontendDataSensors()" << endl; nlohmann::json jObj; @@ -117,11 +154,14 @@ public: int j = 0; string queryResult; + // connect to influx database influxdb_cpp::server_info si(this->getSettings().influx_host, this->getSettings().influx_port, this->getSettings().influx_db, this->getSettings().influx_user, this->getSettings().influx_pass); + // create and execute query influxdb_cpp::query(queryResult, "SELECT * FROM Sensors WHERE client = '" + node + "' AND sensor = '" + sensor + "' ORDER BY time DESC LIMIT 1;", si); auto response = nlohmann::json::parse(queryResult); + // creates json from returned database object nlohmann::json columns = response["results"][0]["series"][0]["columns"]; for (nlohmann::json line : response["results"][0]["series"][0]["values"]) { j = 0; @@ -136,18 +176,17 @@ public: return jObj.dump(); } - string getFrontendJson() { - nlohmann::json combinedjObj; - } - + // sets weather object void setSWeather(WeatherData wd) { this->sWeather = wd; } + // sets forecast object void setSForecast(std::vector<WeatherData> fc) { this->vecForecast = fc; } + // gets all settings from database in object void querySettings() { if (DEBUG) cout << "dbSqlite::querySettings()" << endl; Session session("SQLite", this->dbFile); @@ -174,6 +213,7 @@ public: select.execute(); } + // requests all regions (zip) from database grouped std::vector<Region> queryRegions() { if (DEBUG) cout << "dbSqlite::queryRegions()" << endl; Session session("SQLite", this->dbFile); @@ -194,6 +234,7 @@ public: return result; } + // gets all frontends for given zip std::vector<Frontend> queryFrontendsByPlz(string plz) { if (DEBUG) cout << "dbSqlite::queryFrontendsByPlz(" << plz << ")" << endl; Session session("SQLite", this->dbFile); @@ -215,6 +256,7 @@ public: return result; } + // gets all frontends from database std::vector<Frontend> queryFrontends() { if (DEBUG) cout << "dbSqlite::queryFrontends()" << endl; @@ -240,6 +282,7 @@ public: return result; } + // frontend has send new settings, save in database void updateFrontendSettings(string settingsData) { if (DEBUG) cout << "dbSqlite::updateSettings" << endl; @@ -256,6 +299,7 @@ public: cout << " --> dbSqlite::updateSettings for Frontend: " << frontend.frontendId << endl; + // first insert and ignore errors if frontend exists, then update existing frontend object if (frontend.frontendId.size() > 0 && frontend.plz.size() > 0 && frontend.lngCode.size() > 0) { Statement insert(session); insert << "INSERT OR IGNORE INTO settings_frontend(frontendId, plz, lngCode, node1Id, node1Innen, node1Aussen) VALUES(?, ?, ?, ?, ?, ?);", @@ -279,6 +323,7 @@ public: } } + // get graph data from influxdb string getGraphFromInfluxDb(string plz) { influxdb_cpp::server_info si(getSettings().influx_host, getSettings().influx_port, getSettings().influx_db, getSettings().influx_user, getSettings().influx_pass); diff --git a/openweathermap.h b/openweathermap.h index 1277605554b4b0ad533a95e2335d787ebe59d36b..b38da64b166969e9c9afcda74a6a7153ae7f0e68 100644 --- a/openweathermap.h +++ b/openweathermap.h @@ -22,6 +22,9 @@ #define DEBUG false +/* + * This class handles the connection and data from openweathermap project + */ class openweathermap { private: std::string appid; @@ -32,11 +35,13 @@ private: dbSqlite* db; public: + // constructor gets the db object openweathermap(dbSqlite* db_new) { if (DEBUG) std::cout << "openweathermap::openweathermap()" << std::endl; this->db = db_new; } + // returns the string in lower case std::string str_tolower(std::string s) { if (DEBUG) std::cout << "openweathermap::str_tolower()" << std::endl; std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); } @@ -44,16 +49,19 @@ public: return s; } + // returns the object which contains the current weather WeatherData getSWeather() { if (DEBUG) std::cout << "openweathermap::getSWeather()" << std::endl; return this->sWeather; } + // returns the object which contains the forecast for next 5 days in 3 hour steps std::vector<WeatherData> getSForecast() { if (DEBUG) std::cout << "openweathermap::getSForecast()" << std::endl; return this->vecForecast; } + // gets the current weather object from openweather api and fills the weather object void setSWeather(json::JSON jObj, std::string plz, std::string lngCode) { if (DEBUG) std::cout << "openweathermap::setSWeather(" << plz << ")" << std::endl; @@ -81,11 +89,14 @@ public: this->sWeather.icon2Id = jObj["weather"][1]["id"].ToInt(); this->sWeather.icon2 = jObj["weather"][1]["icon"].ToString(); } + // set object db->setSWeather(this->sWeather); } + // gets the forecast object from openweather api and fills the forecast object void setSForecast(json::JSON jObj, std::string plz, std::string lngCode) { if (DEBUG) std::cout << "openweathermap::setSForecast(" << plz << ") " << jObj["cnt"] << std::endl; + // for each item in json array creates a new item for (int i = 0; i < jObj["cnt"].ToInt(); i++) { WeatherData item; @@ -105,6 +116,8 @@ public: item.from = jObj["list"][i]["dt_txt"].ToString(); item.icon1Id = jObj["list"][i]["weather"][0]["id"].ToInt(); item.icon1 = jObj["list"][i]["weather"][0]["icon"].ToString(); + item.snow3h = jObj["list"][i]["weather"][0]["snow"]["3h"].ToInt(); + item.rain3h = jObj["list"][i]["weather"][0]["rain"]["3h"].ToInt(); if(jObj["list"][i]["weather"].size() == 2) { item.icon2Id = jObj["list"][i]["weather"][1]["id"].ToInt(); item.icon2 = jObj["list"][i]["weather"][1]["icon"].ToString(); @@ -116,9 +129,11 @@ public: this->vecForecast[i] = item; } } + // set object db->setSForecast(this->vecForecast); } + // create http request and returns result as string from other end std::string getResponse(std::string url) { try { // prepare session @@ -149,13 +164,17 @@ public: } } + // gets the weather json from openweather, calls the setSWeather method and fills the influx database void getWeather(std::string plz, std::string lngCode) { - if (DEBUG) std::cout << "openweathermap::getWeather(" << plz << ", " << lngCode << ")" << std::endl; - lngCode = str_tolower(lngCode); - std::string url = "http://api.openweathermap.org/data/2.5/weather?zip=" + plz + "," + lngCode + "&appid=" + db->getSettings().owm_appid + "&mode=json&units=metric&lang=de"; - this->setSWeather(json::JSON::Load(this->getResponse(url)), plz, lngCode); + if (DEBUG) std::cout << "openweathermap::getWeather(" << plz << ", " << str_tolower(lngCode) << ")" << std::endl; + // get data from openweather api as http request + std::string url = "http://api.openweathermap.org/data/2.5/weather?zip=" + plz + "," + str_tolower(lngCode) + "&appid=" + db->getSettings().owm_appid + "&mode=json&units=metric&lang=de"; + this->setSWeather(json::JSON::Load(this->getResponse(url)), plz, str_tolower(lngCode)); + + // connect to influxdb server influxdb_cpp::server_info si(db->getSettings().influx_host, db->getSettings().influx_port, db->getSettings().influx_db, db->getSettings().influx_user, db->getSettings().influx_pass); + // build and execute database query influxdb_cpp::builder() .meas("Weather") .tag("plz", this->sWeather.plz) @@ -183,10 +202,11 @@ public: .post_http(si); } + // gets the forecast json from openweather, calls the setSForecast method to save in object void getForecast(std::string plz, std::string lngCode) { - if (DEBUG) std::cout << "openweathermap::getForecast(" << plz << ", " << lngCode << ")" << std::endl; - lngCode = str_tolower(lngCode); - std::string url = "http://api.openweathermap.org/data/2.5/forecast?zip=" + plz + "," + lngCode + "&appid=" + db->getSettings().owm_appid + "&mode=json&units=metric&lang=de"; - this->setSForecast(json::JSON::Load(this->getResponse(url)), plz, lngCode); + if (DEBUG) std::cout << "openweathermap::getForecast(" << plz << ", " << str_tolower(lngCode) << ")" << std::endl; + // get data from openweather api as http request + std::string url = "http://api.openweathermap.org/data/2.5/forecast?zip=" + plz + "," + str_tolower(lngCode) + "&appid=" + db->getSettings().owm_appid + "&mode=json&units=metric&lang=de"; + this->setSForecast(json::JSON::Load(this->getResponse(url)), plz, str_tolower(lngCode)); } }; \ No newline at end of file diff --git a/structs.h b/structs.h index 9418a1979634c6a245d5e78485dafd9e3d2aa3af..a28b78e51b7409c4dc01abdfc42619330e3e667e 100644 --- a/structs.h +++ b/structs.h @@ -3,6 +3,7 @@ using std::string; +// struct for backend settings struct Settings { string owm_appid; string owm_plz; @@ -22,6 +23,7 @@ struct Settings { int mqtt_port; }; +// saves weather data struct WeatherData { string plz; string lngCode; @@ -48,6 +50,7 @@ struct WeatherData { string from; }; +// object for frontend struct Frontend { string frontendId; string plz; @@ -57,6 +60,7 @@ struct Frontend { string node1Aussen; }; +// regions for frontends struct Region { string plz; string lngCode; diff --git a/weatherstation.db b/weatherstation.db index 35d590941ef1b0417f7d47a8137f1049a589482e..f8281d9a494dcaa0aec19de9eaa3ff75f5ffbece 100644 Binary files a/weatherstation.db and b/weatherstation.db differ