Skip to content

Commit

Permalink
Fixed a bug where the Home Assistant entities would be faulty registe…
Browse files Browse the repository at this point in the history
…red and also would be inaccessible all the time
  • Loading branch information
tpanajott committed Apr 1, 2024
1 parent af1ee65 commit 2e2e0f2
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 63 deletions.
23 changes: 19 additions & 4 deletions docker/MQTTManager/include/nspanel/nspanel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,18 @@ void NSPanel::update_config(nlohmann::json &init_data) {
this->_is_register_accepted = true;
}

bool rebuilt_mqtt = false; // Wether or not to rebuild mqtt topics and subscribe to the new topics.
bool rebuilt_mqtt = false; // Wether or not to rebuild mqtt topics and subscribe to the new topics.
bool rebuilt_ha_mqtt = false; // Wether or not to rebuild mqtt topics for HA entity discovery.
if (init_data.contains("name")) {
if (this->_name.compare(init_data["name"]) != 0) {
rebuilt_mqtt = true;
rebuilt_ha_mqtt = true;
}
this->_name = init_data["name"];
} else if (init_data.contains("friendly_name")) {
if (this->_name.compare(init_data["friendly_name"]) != 0) {
rebuilt_mqtt = true;
rebuilt_ha_mqtt = true;
}
this->_name = init_data["friendly_name"];
}
Expand Down Expand Up @@ -154,16 +157,18 @@ void NSPanel::update_config(nlohmann::json &init_data) {

if (init_data.contains("relay1_is_light")) {
this->_relay1_is_mqtt_light = init_data["relay1_is_light"];
rebuilt_mqtt = true;
rebuilt_ha_mqtt = true;
} else {
this->_relay1_is_mqtt_light = false;
rebuilt_ha_mqtt = true;
}

if (init_data.contains("relay2_is_light")) {
this->_relay2_is_mqtt_light = init_data["relay2_is_light"];
rebuilt_mqtt = true;
rebuilt_ha_mqtt = true;
} else {
this->_relay2_is_mqtt_light = false;
rebuilt_ha_mqtt = true;
}

if (rebuilt_mqtt) {
Expand Down Expand Up @@ -195,6 +200,11 @@ void NSPanel::update_config(nlohmann::json &init_data) {
this->register_to_home_assistant();
}

if (!rebuilt_mqtt && rebuilt_ha_mqtt) {
this->reset_ha_mqtt_topics();
this->register_to_home_assistant();
}

if (this->_has_registered_to_manager) {
// If this NSPanel is registered to manager, listen to state topics.
MQTT_Manager::subscribe(this->_mqtt_relay1_state_topic, boost::bind(&NSPanel::mqtt_callback, this, _1, _2));
Expand Down Expand Up @@ -223,6 +233,11 @@ void NSPanel::reset_mqtt_topics() {
// This nspanel was removed. Clear any retain on any MQTT topic.
MQTT_Manager::clear_retain(this->_mqtt_status_topic);
MQTT_Manager::clear_retain(this->_mqtt_command_topic);

this->reset_ha_mqtt_topics();
}

void NSPanel::reset_ha_mqtt_topics() {
MQTT_Manager::clear_retain(this->_mqtt_light_relay1_topic);
MQTT_Manager::clear_retain(this->_mqtt_light_relay2_topic);
MQTT_Manager::clear_retain(this->_mqtt_switch_relay1_topic);
Expand Down Expand Up @@ -755,7 +770,7 @@ void NSPanel::register_to_home_assistant() {

// Register screensaver brightness
nlohmann::json screensaver_brightness_data = nlohmann::json(base_json);
screensaver_brightness_data["name"] = "Screen brightness";
screensaver_brightness_data["name"] = "Screensaver brightness";
screensaver_brightness_data["command_topic"] = fmt::format("nspanel/{}/brightness_screensaver", this->_name);
screensaver_brightness_data["min"] = "0";
screensaver_brightness_data["max"] = "100";
Expand Down
4 changes: 4 additions & 0 deletions docker/MQTTManager/include/nspanel/nspanel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ class NSPanel {
void update_config(nlohmann::json &init_data);
~NSPanel();
void reset_mqtt_topics();
/**
* Reset MQTT topics used to register entities to HA.
*/
void reset_ha_mqtt_topics();

/**
* Get the ID of this NSPanel.
Expand Down
181 changes: 132 additions & 49 deletions docker/MQTTManager/include/weather/weather.cpp
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
#include "weather.hpp"
#include "home_assistant_manager/home_assistant_manager.hpp"
#include "mqtt_manager/mqtt_manager.hpp"
#include "mqtt_manager_config/mqtt_manager_config.hpp"
#include "openhab_manager/openhab_manager.hpp"
#include <bits/types/time_t.h>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/bind.hpp>
#include <cstdint>
#include <cstdlib>
#include <ctime>
#include <curl/curl.h>
#include <fmt/core.h>
#include <mqtt_manager/mqtt_manager.hpp>
#include <math.h>
#include <nlohmann/json.hpp>
#include <nlohmann/json_fwd.hpp>
#include <spdlog/spdlog.h>
#include <string>

void MQTTManagerWeather::init() {
MQTTManagerWeather::_update_forecast_thread = std::thread(MQTTManagerWeather::_update_forecast);

MQTTManagerWeather::_update_forecast_thread.join();
}
#include <vector>

void MQTTManagerWeather::update_config() {
if (MqttManagerConfig::weather_controller.compare("home_assistant") == 0) {
SPDLOG_INFO("Initializing weather controller for Home Assistant.");
HomeAssistantManager::attach_event_observer(MqttManagerConfig::home_assistant_weather_entity, boost::bind(&MQTTManagerWeather::home_assistant_event_callback, this, _1));
HomeAssistantManager::attach_event_observer(MqttManagerConfig::home_assistant_sun_entity, boost::bind(&MQTTManagerWeather::home_assistant_event_callback, this, _1));
OpenhabManager::detach_event_observer(MqttManagerConfig::openhab_current_weather_item, boost::bind(&MQTTManagerWeather::openhab_current_weather_callback, this, _1));
OpenhabManager::detach_event_observer(MqttManagerConfig::openhab_forecast_weather_item, boost::bind(&MQTTManagerWeather::openhab_forecast_weather_callback, this, _1));
} else if (MqttManagerConfig::weather_controller.compare("openhab") == 0) {
SPDLOG_INFO("Initializing weather controller for OpenHAB.");
SPDLOG_DEBUG("Current weather item: {}", MqttManagerConfig::openhab_current_weather_item);
SPDLOG_DEBUG("Forecast weather item: {}", MqttManagerConfig::openhab_forecast_weather_item);
HomeAssistantManager::detach_event_observer(MqttManagerConfig::home_assistant_weather_entity, boost::bind(&MQTTManagerWeather::home_assistant_event_callback, this, _1));
HomeAssistantManager::detach_event_observer(MqttManagerConfig::home_assistant_sun_entity, boost::bind(&MQTTManagerWeather::home_assistant_event_callback, this, _1));
OpenhabManager::attach_event_observer(MqttManagerConfig::openhab_current_weather_item, boost::bind(&MQTTManagerWeather::openhab_current_weather_callback, this, _1));
OpenhabManager::attach_event_observer(MqttManagerConfig::openhab_forecast_weather_item, boost::bind(&MQTTManagerWeather::openhab_forecast_weather_callback, this, _1));
} else {
SPDLOG_ERROR("Unsupported weather controller '{}'.", MqttManagerConfig::weather_controller);
}

if (MqttManagerConfig::outside_temp_sensor_provider.compare("home_assistant") == 0) {
SPDLOG_INFO("Will load outside temperature from Home Assistant sensor {}", MqttManagerConfig::outside_temp_sensor_entity_id);
HomeAssistantManager::attach_event_observer(MqttManagerConfig::outside_temp_sensor_entity_id, boost::bind(&MQTTManagerWeather::home_assistant_event_callback, this, _1));
Expand All @@ -37,53 +51,122 @@ void MQTTManagerWeather::update_config() {
}

void MQTTManagerWeather::home_assistant_event_callback(nlohmann::json event_data) {
if (MqttManagerConfig::outside_temp_sensor_provider.compare("home_assistant") == 0 && std::string(event_data["event"]["data"]["entity_id"]).compare(MqttManagerConfig::outside_temp_sensor_entity_id) == 0) {
if (std::string(event_data["event"]["data"]["entity_id"]).compare(MqttManagerConfig::home_assistant_weather_entity) == 0) {
nlohmann::json new_state = event_data["event"]["data"]["new_state"];
this->_current_temperature = atof(std::string(new_state["state"]).c_str());
this->send_state_update();
}
}

static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) {
((std::string *)userp)->append((char *)contents, size * nmemb);
return size * nmemb;
}

void MQTTManagerWeather::_update_forecast() {
SPDLOG_INFO("Starting 'Update Forecast' thread.");
while (true) {
SPDLOG_DEBUG("Fetching new weather forecast.");
CURL *curl;
CURLcode res;
curl = curl_easy_init();
if (curl) {
std::string response_data;
curl_easy_setopt(curl, CURLOPT_URL, "https://api.openweathermap.org/data/2.5/forecast?lat=44.34&lon=10.99&appid=4ff881e8b20a81abe808164d88816a6a");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_data);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback);

/* Perform the request, res will get the return code */
SPDLOG_DEBUG("Requesting weather.");
res = curl_easy_perform(curl);
long http_code;
SPDLOG_DEBUG("Gathering response code.");
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
/* Check for errors */
if (res == CURLE_OK && http_code == 200) {
SPDLOG_DEBUG("Got response: {}", response_data);
} else {
SPDLOG_ERROR("Failed to fetch weather forecast. CURL error: {}", curl_easy_strerror(res));
this->_forecast_weather_info.clear();
for (nlohmann::json forecast : new_state["attributes"]["forecast"]) {
weather_info info;
SPDLOG_DEBUG("Loading weather forecast for {}", std::string(forecast["datetime"]));
info.condition = forecast["condition"];
info.wind_speed = forecast["wind_speed"];
info.precipitation = forecast["precipitation"];
info.precipitation_probability = forecast["precipitation_probability"];
info.temperature_low = forecast["templow"];
info.temperature_high = forecast["temperature"];

// Get day of week
std::string datetime = forecast["datetime"];
std::vector<std::string> datetime_parts;
size_t pos = datetime.find("T");
std::string date = datetime.substr(0, pos);
std::string year = date.substr(0, date.find("-"));
date.erase(0, date.find("-") + 1);
std::string month = date.substr(0, date.find("-"));
date.erase(0, date.find("-") + 1);
std::string day_of_month = date;

std::tm tm = {01, 00, 00, atoi(day_of_month.c_str()), atoi(month.c_str()) - 1, atoi(year.c_str()) - 1900};
std::time_t utc_time = std::mktime(&tm);
const std::tm *localtime = std::localtime(&utc_time);
info.time = *localtime;

switch (localtime->tm_wday) {
case 0:
info.day = "Sun";
break;
case 1:
info.day = "Mon";
break;
case 2:
info.day = "Tue";
break;
case 3:
info.day = "Wed";
break;
case 4:
info.day = "Thu";
break;
case 5:
info.day = "Fri";
break;
case 6:
info.day = "Sat";
break;
default:
info.day = std::to_string(localtime->tm_wday);
break;
}

/* always cleanup */
SPDLOG_DEBUG("Cleanup.");
curl_easy_cleanup(curl);
this->_forecast_weather_info.push_back(info);
}

SPDLOG_DEBUG("Loaded forecast for {} days.", this->_forecast_weather_info.size());

if (this->_forecast_weather_info.size() > 0) {
if (MqttManagerConfig::outside_temp_sensor_provider.length() == 0 && MqttManagerConfig::outside_temp_sensor_entity_id.length() == 0) {
this->_current_temperature = new_state["attributes"]["temperature"];
}
this->_current_condition = this->_forecast_weather_info[0].condition;
this->_current_precipitation_probability = this->_forecast_weather_info[0].precipitation_probability;
this->_current_wind_speed = new_state["attributes"]["wind_speed"];
this->_windspeed_unit = new_state["attributes"]["wind_speed_unit"];
this->_precipitation_unit = new_state["attributes"]["precipitation_unit"];
this->_current_min_temperature = this->_forecast_weather_info[0].temperature_low;
this->_current_max_temperature = this->_forecast_weather_info[0].temperature_high;
std::time_t time = std::time({});
this->_current_weather_time = *std::localtime(&time);
this->send_state_update();
} else {
SPDLOG_ERROR("Failed to create curl object.");
SPDLOG_ERROR("Failed to process forecast information from Home Assistant.");
return;
}
SPDLOG_DEBUG("Weather fetched, will try again in 1 minute.");
std::this_thread::sleep_for(std::chrono::milliseconds(60000)); // Fetch new forecast every minute TODO: Change this to something more reasonable

return;
} else if (std::string(event_data["event"]["data"]["entity_id"]).compare(MqttManagerConfig::home_assistant_sun_entity) == 0) {
std::string next_rising = event_data["event"]["data"]["new_state"]["attributes"]["next_rising"];
std::string next_setting = event_data["event"]["data"]["new_state"]["attributes"]["next_setting"];

// Get time parts from sunrise
std::string rising_date = next_rising.substr(0, next_rising.find("T"));
next_rising.erase(0, next_rising.find("T") + 1);
std::string rising_time = next_rising.substr(0, next_rising.find("+"));
next_rising.erase(0, next_rising.find("+") + 1);

std::string rising_hour = rising_time.substr(0, rising_time.find(":"));
rising_time.erase(0, rising_time.find(":") + 1);
std::string rising_minute = rising_time.substr(0, rising_time.find(":"));
rising_time.erase(0, rising_time.find(":") + 1);

// Get time parts from sunrise
std::string setting_date = next_setting.substr(0, next_setting.find("T"));
next_setting.erase(0, next_setting.find("T") + 1);
std::string setting_time = next_setting.substr(0, next_setting.find("+"));
next_setting.erase(0, next_setting.find("+") + 1);

std::string setting_hour = setting_time.substr(0, setting_time.find(":"));
setting_time.erase(0, setting_time.find(":") + 1);
std::string setting_minute = setting_time.substr(0, setting_time.find(":"));
setting_time.erase(0, setting_time.find(":") + 1);

this->_next_sunrise_hour = atoi(rising_hour.c_str());
this->_next_sunrise = fmt::format("{}:{}", rising_hour, rising_minute);
this->_next_sunset_hour = atoi(setting_hour.c_str());
this->_next_sunset = fmt::format("{}:{}", setting_hour, setting_minute);
} else if (MqttManagerConfig::outside_temp_sensor_provider.compare("home_assistant") == 0 && std::string(event_data["event"]["data"]["entity_id"]).compare(MqttManagerConfig::outside_temp_sensor_entity_id) == 0) {
nlohmann::json new_state = event_data["event"]["data"]["new_state"];
this->_current_temperature = atof(std::string(new_state["state"]).c_str());
this->send_state_update();
}
}

Expand Down
9 changes: 1 addition & 8 deletions docker/MQTTManager/include/weather/weather.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

class MQTTManagerWeather {
public:
static void init();
void update_config();
void home_assistant_event_callback(nlohmann::json event_data);
void openhab_current_weather_callback(nlohmann::json event_data);
Expand All @@ -17,16 +16,10 @@ class MQTTManagerWeather {

private:
std::string _get_icon_from_mapping(std::string &condition, uint8_t hour);
static void _update_forecast();
static void _process_forecast_data();
static void _update_current_weather();

static inline std::thread _update_current_weather_thread;
static inline std::thread _update_forecast_thread;

struct weather_info {
std::string condition;
int condition_id;
int condition_id; // Only used for OpenHAB/OpenWeatherMap
std::string day;
std::tm time;
std::tm sunrise;
Expand Down
2 changes: 0 additions & 2 deletions docker/MQTTManager/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,6 @@ int main(void) {
SPDLOG_WARN("OpenHAB address and/or token missing. Won't start OpenHAB component.");
}

MQTTManagerWeather::init();

// Wait for threads to exit
mqtt_manager_thread.join();
if (home_assistant_manager_thread.joinable()) {
Expand Down
Binary file modified docker/web/nspm_mqttmanager
Binary file not shown.

0 comments on commit 2e2e0f2

Please sign in to comment.