Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,44 @@ PRs to expand the functionality or fix bugs are very welcome!
This issue is due to the `forecast` attribute being removed from weather entities. There is currently no alternative way to fetch this data with the current ESPHome functionality but I hope to get this fixed (see [this feature request](https://github.com/esphome/feature-requests/issues/2703)).
More info on the issue can be [found here](https://github.com/olicooper/esphome-nspanel-lovelace-native/issues/8).

As a workaround, please add the following to your Home Assistant configuration (changing `weather.home` to your actual weather entity_id) then update the `weather` `entity_id` in your esphome config to the `unique_id` seen below (i.e. `sensor.weather_forecast_daily`).
**Solution**: Use the `service` forecast method to manually push weather data to the panel.
1. Update your ESPHome configuration:
```yaml
screensaver:
weather:
entity_id: weather.home # Required for current temperature
forecast_method: service
```
2. Add an automation to Home Assistant to push the forecast:
```yaml
alias: NSPanel Lovelace Weather Forecast Sync
description: >-
This automation fetches the weather forecast and pushes only the upcoming
entries to the NSPanel. It filters the forecast to only include entries
starting from the current hour.
triggers:
- minutes: /15
trigger: time_pattern
- entity_id: weather.home # change this to your weather entity
trigger: state
conditions: []
actions:
- target:
entity_id: weather.home # change this to your weather entity
data:
type: hourly # or daily
response_variable: forecast_data
action: weather.get_forecasts
- data:
forecast: >- # change the next line to your weather entity
{% set forecast = forecast_data['weather.home'].forecast %}
{% set filtered = forecast | selectattr('datetime', 'ge', now().isoformat()) | list %}
{{ filtered[:5] | to_json }}
action: esphome.nspanel_set_weather_forecast_data # change this to your NSPanel service name
mode: single
```

**Alternative Workaround**: Add a template sensor to your Home Assistant configuration (changing `weather.home` to your actual weather entity_id) then update the `weather` `entity_id` in your esphome config to the `unique_id` seen below (i.e. `sensor.weather_forecast_daily`).

```yaml
template:
Expand Down
2 changes: 2 additions & 0 deletions advanced-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ wifi:
password: !secret wifi_password

api:
# ## This is required when using `forecast_method: service` for weather updates
# custom_services: true
services:
## Service to update the TFT firmware using a URL that is accessible to the nspanel
- service: upload_tft
Expand Down
12 changes: 9 additions & 3 deletions components/nspanel_lovelace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ def AUTO_LOAD():
CONF_SCREENSAVER_STATUS_ICON_RIGHT = "status_icon_right"
CONF_SCREENSAVER_STATUS_ICON_ALT_FONT = "alt_font" # todo: to_code
CONF_SCREENSAVER_DOUBLE_TAP_TO_UNLOCK = "double_tap_to_unlock"
CONF_SCREENSAVER_FORECAST_METHOD = "forecast_method"

CONF_CARDS = "cards"
CONF_CARD_TYPE = "type"
Expand Down Expand Up @@ -337,7 +338,8 @@ def ensure_unique(value: list):
cv.Optional(CONF_SCREENSAVER_TIME_FORMAT, default="%H:%M"): valid_clock_format('Time format'),
cv.Optional(CONF_SCREENSAVER_DOUBLE_TAP_TO_UNLOCK, default=False): cv.boolean,
cv.Optional(CONF_SCREENSAVER_WEATHER): cv.Schema({
cv.Required(CONF_ENTITY_ID): valid_entity_id()
cv.Required(CONF_ENTITY_ID): valid_entity_id(),
cv.Optional(CONF_SCREENSAVER_FORECAST_METHOD, default="template_sensor"): cv.one_of("template_sensor", "service"),
}),
cv.Optional(CONF_SCREENSAVER_STATUS_ICON_LEFT): SCHEMA_STATUS_ICON,
cv.Optional(CONF_SCREENSAVER_STATUS_ICON_RIGHT): SCHEMA_STATUS_ICON,
Expand Down Expand Up @@ -768,8 +770,12 @@ async def to_code(config):
cg.add(screensaver_class.set_icon_right(iconright_variable_class))

if CONF_SCREENSAVER_WEATHER in screensaver_config:
entity_id = screensaver_config[CONF_SCREENSAVER_WEATHER][CONF_ENTITY_ID]
cg.add(nspanel.set_weather_entity_id(entity_id))
weather_config = screensaver_config[CONF_SCREENSAVER_WEATHER]
if CONF_ENTITY_ID in weather_config:
cg.add(nspanel.set_weather_entity_id(weather_config[CONF_ENTITY_ID]))
if CONF_SCREENSAVER_FORECAST_METHOD in weather_config:
if weather_config[CONF_SCREENSAVER_FORECAST_METHOD] == "service":
cg.add_define("USE_NSPANEL_CUSTOM_SERVICES")
screensaver_items = []
# 1 main weather item + 4 forecast items
for i in range(0,5):
Expand Down
47 changes: 40 additions & 7 deletions components/nspanel_lovelace/nspanel_lovelace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,18 @@ void NSPanelLovelace::setup() {
this->subscribe_homeassistant_state(
&NSPanelLovelace::on_weather_temperature_unit_update_,
this->weather_entity_id_, to_string(ha_attr_type::temperature_unit));
this->subscribe_homeassistant_state(
&NSPanelLovelace::on_weather_forecast_update_, this->weather_entity_id_,
to_string(ha_attr_type::forecast));

#ifndef USE_NSPANEL_CUSTOM_SERVICES
this->subscribe_homeassistant_state(
&NSPanelLovelace::on_weather_forecast_update_,
this->weather_entity_id_, to_string(ha_attr_type::forecast));
#endif
}


#ifdef USE_NSPANEL_CUSTOM_SERVICES
this->register_service(&NSPanelLovelace::set_weather_forecast_data, "set_weather_forecast_data", {"forecast"});
#endif

for (auto &entity : this->entities_) {
auto &entity_id = entity->get_entity_id();
ESP_LOGV(TAG, "Adding subscriptions for entity '%s'", entity_id.c_str());
Expand Down Expand Up @@ -2482,6 +2489,19 @@ void NSPanelLovelace::on_weather_temperature_unit_update_(
this->send_weather_update_command_();
}

#ifdef USE_NSPANEL_CUSTOM_SERVICES
void NSPanelLovelace::set_weather_forecast_data(std::string forecast_json) {
this->on_weather_forecast_update_(this->weather_entity_id_, forecast_json);
}
#endif

#ifdef USE_NSPANEL_CUSTOM_SERVICES
void NSPanelLovelace::set_weather_forecast_data(std::string forecast_json) {
this->on_weather_forecast_update_(this->weather_entity_id_, forecast_json);
}
#endif


void NSPanelLovelace::on_weather_forecast_update_(
#if ESPHOME_VERSION_CODE >= VERSION_CODE(2026,1,0)
const std::string &entity_id, esphome::StringRef forecast_json
Expand Down Expand Up @@ -2528,14 +2548,27 @@ void NSPanelLovelace::on_weather_forecast_update_(
}

this->command_buffer_.clear();
ArduinoJson::JsonArray docArr = doc.as<ArduinoJson::JsonArray>();

ArduinoJson::JsonArray docArr;
if (doc.is<ArduinoJson::JsonArray>()) {
docArr = doc.as<ArduinoJson::JsonArray>();
} else if (doc.is<ArduinoJson::JsonObject>() && doc["forecast"].is<ArduinoJson::JsonArray>()) {
docArr = doc["forecast"].as<ArduinoJson::JsonArray>();
} else {
ESP_LOGW(
TAG,
"Weather forecast JSON is not an array or object with 'forecast' key");
return;
}

ESP_LOGV(TAG, "Weather forecast update s=%u", docArr.size());

// check if forecast is hourly or daily
auto weather_entity_is_hourly = false;

if (docArr.size() > 1) {
const char * date1 = docArr[0]["datetime"];
const char * date2 = docArr[1]["datetime"];
const char *date1 = docArr[0]["datetime"];
const char *date2 = docArr[1]["datetime"];
tm t{};
if (iso8601_to_tm(date1, t)) {
uint8_t hr = t.tm_hour;
Expand Down
3 changes: 3 additions & 0 deletions components/nspanel_lovelace/nspanel_lovelace.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ class NSPanelLovelace : public Component, public uart::UARTDevice, protected api
void set_display_inactive_dim(uint8_t inactive);
// Note: this can be used without parameters to update the display without changing the levels
void set_display_dim(uint8_t inactive = UINT8_MAX, uint8_t active = UINT8_MAX);
#ifdef USE_NSPANEL_CUSTOM_SERVICES
void set_weather_forecast_data(std::string forecast_json);
#endif
void set_weather_entity_id(const std::string &weather_entity_id) { this->weather_entity_id_ = weather_entity_id; }

bool get_double_tap_to_unlock() const { return this->double_tap_to_unlock_; }
Expand Down