直接暴露出实体便于LLM读取天气预报信息

This commit is contained in:
2026-02-13 14:02:07 +08:00
parent b36d77b4da
commit c3f40b7369
4 changed files with 196 additions and 4 deletions
+151 -4
View File
@@ -1,4 +1,4 @@
"""Sensor entities for Baidu Weather integration - Weather Alerts."""
"""Sensor entities for Baidu Weather integration - Weather Alerts & Forecasts."""
from __future__ import annotations
@@ -6,11 +6,10 @@ import logging
from typing import Any
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@@ -20,12 +19,18 @@ from .const import (
CONF_LOCATION_NAME,
DOMAIN,
KEY_ALERTS,
KEY_INDEXES,
KEY_FORECASTS,
KEY_NOW,
)
from .coordinator import BaiduWeatherCoordinator
_LOGGER = logging.getLogger(__name__)
# Day labels for forecast sensors (index 0 = today)
_DAY_LABELS_CN = ["今天", "明天", "后天", "大后天", "第五天"]
_DAY_LABELS_EN = ["Today", "Tomorrow", "Day After Tomorrow", "In 3 Days", "In 4 Days"]
_DAY_KEYS = ["forecast_today", "forecast_tomorrow", "forecast_day2", "forecast_day3", "forecast_day4"]
async def async_setup_entry(
hass: HomeAssistant,
@@ -41,6 +46,14 @@ async def async_setup_entry(
BaiduWeatherAqiSensor(coordinator, entry, location_name),
]
# Create daily forecast sensors (today + next 4 days = 5 sensors)
for day_index in range(5):
entities.append(
BaiduWeatherDailyForecastSensor(
coordinator, entry, location_name, day_index
)
)
async_add_entities(entities)
@@ -187,3 +200,137 @@ class BaiduWeatherAqiSensor(
attrs["aqi_level"] = "严重污染"
return attrs
class BaiduWeatherDailyForecastSensor(
CoordinatorEntity[BaiduWeatherCoordinator], SensorEntity
):
"""Sensor entity for a single day's weather forecast.
Each instance represents one day (today, tomorrow, day after tomorrow, etc.).
The sensor state is a human-readable summary like "多云 12~20°C".
Extra attributes contain detailed forecast fields so that voice assistants
and LLM integrations can read the forecast directly from entity state.
"""
_attr_has_entity_name = True
_attr_icon = "mdi:weather-partly-cloudy"
_attr_attribution = ATTRIBUTION
def __init__(
self,
coordinator: BaiduWeatherCoordinator,
entry: ConfigEntry,
location_name: str,
day_index: int,
) -> None:
"""Initialize the daily forecast sensor.
Args:
day_index: 0 = today, 1 = tomorrow, 2 = day after tomorrow, etc.
"""
super().__init__(coordinator)
self._entry = entry
self._day_index = day_index
self._attr_translation_key = _DAY_KEYS[day_index]
self._attr_unique_id = f"{DOMAIN}_{entry.entry_id}_{_DAY_KEYS[day_index]}"
self._attr_device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, entry.entry_id)},
manufacturer="百度地图",
name=f"百度天气 - {location_name}",
configuration_url="https://lbsyun.baidu.com/",
)
def _get_forecast_data(self) -> dict[str, Any] | None:
"""Get the forecast data for this day by index."""
if self.coordinator.data is None:
return None
forecasts = self.coordinator.data.get(KEY_FORECASTS, [])
if self._day_index < len(forecasts):
return forecasts[self._day_index]
return None
@property
def native_value(self) -> str | None:
"""Return a human-readable weather summary for this day.
Format: "多云 12~20°C" or "晴转多云 8~18°C"
This makes it easy for voice assistants to read aloud.
"""
fc = self._get_forecast_data()
if fc is None:
return None
text_day = fc.get("text_day", "")
text_night = fc.get("text_night", "")
high = fc.get("high")
low = fc.get("low")
# Build condition text
if text_day and text_night and text_day != text_night:
condition_text = f"{text_day}{text_night}"
elif text_day:
condition_text = text_day
elif text_night:
condition_text = text_night
else:
condition_text = "未知"
# Build temperature range
if low is not None and high is not None:
temp_text = f"{low}~{high}°C"
elif high is not None:
temp_text = f"最高{high}°C"
elif low is not None:
temp_text = f"最低{low}°C"
else:
temp_text = ""
if temp_text:
return f"{condition_text} {temp_text}"
return condition_text
@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return detailed forecast attributes for this day.
These attributes are designed to be easily readable by LLM integrations.
"""
attrs: dict[str, Any] = {}
fc = self._get_forecast_data()
if fc is None:
return attrs
# Date
if fc.get("date"):
attrs["date"] = fc["date"]
# Day label (今天/明天/后天...)
attrs["day_label"] = _DAY_LABELS_CN[self._day_index]
# Conditions
if fc.get("text_day"):
attrs["condition_day"] = fc["text_day"]
if fc.get("text_night"):
attrs["condition_night"] = fc["text_night"]
# Temperatures
if fc.get("high") is not None:
attrs["temperature_high"] = fc["high"]
if fc.get("low") is not None:
attrs["temperature_low"] = fc["low"]
# Wind - Day
if fc.get("wc_day"):
attrs["wind_class_day"] = fc["wc_day"]
if fc.get("wd_day"):
attrs["wind_direction_day"] = fc["wd_day"]
# Wind - Night
if fc.get("wc_night"):
attrs["wind_class_night"] = fc["wc_night"]
if fc.get("wd_night"):
attrs["wind_direction_night"] = fc["wd_night"]
return attrs
@@ -69,6 +69,21 @@
},
"aqi": {
"name": "空气质量指数"
},
"forecast_today": {
"name": "今天天气预报"
},
"forecast_tomorrow": {
"name": "明天天气预报"
},
"forecast_day2": {
"name": "后天天气预报"
},
"forecast_day3": {
"name": "大后天天气预报"
},
"forecast_day4": {
"name": "第五天天气预报"
}
}
}
@@ -69,6 +69,21 @@
},
"aqi": {
"name": "Air Quality Index"
},
"forecast_today": {
"name": "Today's Weather Forecast"
},
"forecast_tomorrow": {
"name": "Tomorrow's Weather Forecast"
},
"forecast_day2": {
"name": "Day After Tomorrow Forecast"
},
"forecast_day3": {
"name": "3-Day Forecast"
},
"forecast_day4": {
"name": "4-Day Forecast"
}
}
}
@@ -69,6 +69,21 @@
},
"aqi": {
"name": "空气质量指数"
},
"forecast_today": {
"name": "今天天气预报"
},
"forecast_tomorrow": {
"name": "明天天气预报"
},
"forecast_day2": {
"name": "后天天气预报"
},
"forecast_day3": {
"name": "大后天天气预报"
},
"forecast_day4": {
"name": "第五天天气预报"
}
}
}