New apps Added

This commit is contained in:
2026-07-08 10:43:39 -04:00
parent 3b1f4bbd75
commit fefc2c8b5c
1114 changed files with 406637 additions and 154 deletions
+213
View File
@@ -0,0 +1,213 @@
"""The Pirate Weather component."""
from __future__ import annotations
import logging
from datetime import timedelta
from typing import Any
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_API_KEY,
CONF_LANGUAGE,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_MODE,
CONF_MONITORED_CONDITIONS,
CONF_NAME,
CONF_SCAN_INTERVAL,
)
from homeassistant.core import HomeAssistant
from .const import (
CONF_ENDPOINT,
CONF_MODELS,
CONF_UNITS,
DEFAULT_ENDPOINT,
DEFAULT_SCAN_INTERVAL,
DOMAIN,
ENTRY_NAME,
ENTRY_WEATHER_COORDINATOR,
PLATFORMS,
PW_PLATFORM,
PW_PLATFORMS,
PW_ROUND,
UPDATE_LISTENER,
)
# from .weather_update_coordinator import WeatherUpdateCoordinator, DarkSkyData
from .weather_update_coordinator import WeatherUpdateCoordinator
CONF_FORECAST = "forecast"
CONF_HOURLY_FORECAST = "hourly_forecast"
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Powered by Pirate Weather"
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Pirate Weather as config entry."""
name = entry.data[CONF_NAME]
api_key = entry.data[CONF_API_KEY]
latitude = _get_config_value(entry, CONF_LATITUDE)
longitude = _get_config_value(entry, CONF_LONGITUDE)
forecast_mode = "daily"
conditions = _get_config_value(entry, CONF_MONITORED_CONDITIONS)
units = _get_config_value(entry, CONF_UNITS)
forecast_days = _get_config_value(entry, CONF_FORECAST)
forecast_hours = _get_config_value(entry, CONF_HOURLY_FORECAST)
pw_entity_platform = _get_config_value(entry, PW_PLATFORM)
pw_entity_rounding = _get_config_value(entry, PW_ROUND)
scan_interval = _get_config_value(entry, CONF_SCAN_INTERVAL)
language = _get_config_value(entry, CONF_LANGUAGE)
endpoint = _get_config_value(entry, CONF_ENDPOINT)
models = _get_config_value(entry, CONF_MODELS)
# If scan_interval config value is not configured fall back to the entry data config value
if not scan_interval:
scan_interval = entry.data.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
# If endpoint config value is not configured fall back to the default
if not endpoint:
endpoint = DEFAULT_ENDPOINT
_LOGGER.info("Using default Pirate Weather Endpoint")
# If latitude or longitude is not configured fall back to the HA location
if not latitude:
latitude = hass.config.latitude
if not longitude:
longitude = hass.config.longitude
scan_interval = max(scan_interval, 60)
# Extract list of int from forecast days/ hours string if present
# _LOGGER.warning('forecast_days_type: ' + str(type(forecast_days)))
# _LOGGER.warning(forecast_days)
if isinstance(forecast_days, str):
# If empty, set to none
if forecast_days in {"", "None"}:
forecast_days = None
else:
if forecast_days[0] == "[":
forecast_days = forecast_days[1:-1].split(",")
else:
forecast_days = forecast_days.split(",")
forecast_days = [int(i) for i in forecast_days]
if isinstance(forecast_hours, str):
# If empty, set to none
if forecast_hours in {"", "None"}:
forecast_hours = None
else:
if forecast_hours[0] == "[":
forecast_hours = forecast_hours[1:-1].split(",")
else:
forecast_hours = forecast_hours.split(",")
forecast_hours = [int(i) for i in forecast_hours]
unique_location = f"pw-{latitude}-{longitude}"
hass.data.setdefault(DOMAIN, {})
# Create and link weather WeatherUpdateCoordinator
weather_coordinator = WeatherUpdateCoordinator(
api_key,
latitude,
longitude,
timedelta(seconds=scan_interval),
language,
endpoint,
units,
hass,
entry,
models,
)
hass.data[DOMAIN][unique_location] = weather_coordinator
# await weather_coordinator.async_refresh()
await weather_coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id] = {
ENTRY_NAME: name,
ENTRY_WEATHER_COORDINATOR: weather_coordinator,
CONF_API_KEY: api_key,
CONF_LATITUDE: latitude,
CONF_LONGITUDE: longitude,
CONF_UNITS: units,
CONF_MONITORED_CONDITIONS: conditions,
CONF_MODE: forecast_mode,
CONF_FORECAST: forecast_days,
CONF_HOURLY_FORECAST: forecast_hours,
PW_PLATFORM: pw_entity_platform,
PW_ROUND: pw_entity_rounding,
CONF_SCAN_INTERVAL: scan_interval,
CONF_LANGUAGE: language,
CONF_ENDPOINT: endpoint,
CONF_MODELS: models,
}
# Setup platforms
# If both platforms
if (PW_PLATFORMS[0] in pw_entity_platform) and (
PW_PLATFORMS[1] in pw_entity_platform
):
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
# If only sensor
elif PW_PLATFORMS[0] in pw_entity_platform:
await hass.config_entries.async_forward_entry_setups(entry, [PLATFORMS[0]])
# If only weather
elif PW_PLATFORMS[1] in pw_entity_platform:
await hass.config_entries.async_forward_entry_setups(entry, [PLATFORMS[1]])
update_listener = entry.add_update_listener(async_update_options)
hass.data[DOMAIN][entry.entry_id][UPDATE_LISTENER] = update_listener
return True
async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Update options."""
await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
pw_entity_prevplatform = hass.data[DOMAIN][entry.entry_id][PW_PLATFORM]
# If both
if (PW_PLATFORMS[0] in pw_entity_prevplatform) and (
PW_PLATFORMS[1] in pw_entity_prevplatform
):
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
# If only sensor
elif PW_PLATFORMS[0] in pw_entity_prevplatform:
unload_ok = await hass.config_entries.async_unload_platforms(
entry, [PLATFORMS[0]]
)
# If only Weather
elif PW_PLATFORMS[1] in pw_entity_prevplatform:
unload_ok = await hass.config_entries.async_unload_platforms(
entry, [PLATFORMS[1]]
)
_LOGGER.info("Unloading Pirate Weather")
if unload_ok:
update_listener = hass.data[DOMAIN][entry.entry_id][UPDATE_LISTENER]
update_listener()
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
def _get_config_value(config_entry: ConfigEntry, key: str) -> Any:
if config_entry.options and key in config_entry.options:
return config_entry.options[key]
# Check if key exists
if config_entry.data and key in config_entry.data:
return config_entry.data[key]
return None
def _filter_domain_configs(elements, domain):
return list(filter(lambda elem: elem["platform"] == domain, elements))
@@ -0,0 +1,335 @@
"""Config flow for Pirate Weather."""
from __future__ import annotations
import logging
from datetime import timedelta
import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from aiohttp import ClientError
from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.const import (
CONF_API_KEY,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_MODE,
CONF_MONITORED_CONDITIONS,
CONF_NAME,
CONF_SCAN_INTERVAL,
)
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import (
ALL_CONDITIONS,
CONF_ENDPOINT,
CONF_LANGUAGE,
CONF_MODELS,
CONF_UNITS,
CONFIG_FLOW_VERSION,
DEFAULT_ENDPOINT,
DEFAULT_FORECAST_MODE,
DEFAULT_LANGUAGE,
DEFAULT_NAME,
DEFAULT_SCAN_INTERVAL,
DEFAULT_UNITS,
DOMAIN,
LANGUAGES,
PW_PLATFORM,
PW_PLATFORMS,
PW_PREVPLATFORM,
PW_ROUND,
)
ATTRIBUTION = "Powered by Pirate Weather"
_LOGGER = logging.getLogger(__name__)
CONF_FORECAST = "forecast"
CONF_HOURLY_FORECAST = "hourly_forecast"
class PirateWeatherConfigFlow(ConfigFlow, domain=DOMAIN):
"""Config flow for PirateWeather."""
VERSION = CONFIG_FLOW_VERSION
@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
) -> PirateWeatherOptionsFlow:
"""Get the options flow for this handler."""
return PirateWeatherOptionsFlow()
async def async_step_user(self, user_input=None) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
errors = {}
schema = vol.Schema(
{
vol.Required(CONF_API_KEY): str,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): str,
vol.Optional(
CONF_LATITUDE, default=self.hass.config.latitude
): cv.latitude,
vol.Optional(
CONF_LONGITUDE, default=self.hass.config.longitude
): cv.longitude,
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): int,
vol.Required(PW_PLATFORM, default=[PW_PLATFORMS[1]]): cv.multi_select(
PW_PLATFORMS
),
vol.Required(CONF_LANGUAGE, default=DEFAULT_LANGUAGE): vol.In(
LANGUAGES
),
vol.Optional(CONF_MODELS, default=""): str,
vol.Optional(CONF_FORECAST, default=""): str,
vol.Optional(CONF_HOURLY_FORECAST, default=""): str,
vol.Optional(CONF_MONITORED_CONDITIONS, default=[]): cv.multi_select(
ALL_CONDITIONS
),
vol.Optional(PW_ROUND, default="No"): vol.In(["Yes", "No"]),
vol.Optional(CONF_UNITS, default=DEFAULT_UNITS): vol.In(
["si", "us", "ca", "uk"]
),
vol.Optional(CONF_ENDPOINT, default=DEFAULT_ENDPOINT): str,
}
)
if user_input is not None:
latitude = user_input[CONF_LATITUDE]
longitude = user_input[CONF_LONGITUDE]
forecastMode = "daily"
forecastPlatform = user_input[PW_PLATFORM]
entityNamee = user_input[CONF_NAME]
endpoint = user_input[CONF_ENDPOINT]
# Convert scan interval to timedelta
if isinstance(user_input[CONF_SCAN_INTERVAL], str):
user_input[CONF_SCAN_INTERVAL] = cv.time_period_str(
user_input[CONF_SCAN_INTERVAL]
)
# Convert scan interval to number of seconds
if isinstance(user_input[CONF_SCAN_INTERVAL], timedelta):
user_input[CONF_SCAN_INTERVAL] = user_input[
CONF_SCAN_INTERVAL
].total_seconds()
# Unique value includes the location and forcastHours/ forecastDays to seperate WeatherEntity/ Sensor
# await self.async_set_unique_id(f"pw-{latitude}-{longitude}-{forecastDays}-{forecastHours}-{forecastMode}-{entityNamee}")
await self.async_set_unique_id(
f"pw-{latitude}-{longitude}-{forecastPlatform}-{forecastMode}-{entityNamee}"
)
self._abort_if_unique_id_configured()
try:
api_status = await _is_pw_api_online(
self.hass, user_input[CONF_API_KEY], latitude, longitude, endpoint
)
if api_status == 403:
_LOGGER.warning(
"Pirate Weather Setup Error: Invalid API Key, Ensure that you've subscribed to API at https://pirate-weather.apiable.io/"
)
errors["base"] = (
"Invalid API Key, Ensure that you've subscribed to API at https://pirate-weather.apiable.io/"
)
except ClientError:
_LOGGER.warning(
"Pirate Weather Setup Error: API HTTP Error: %s", api_status
)
errors["base"] = "API Error: %s", api_status
if errors:
_LOGGER.warning(errors)
else:
return self.async_create_entry(
title=user_input[CONF_NAME], data=user_input
)
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
async def async_step_import(self, import_input=None):
"""Set the config entry up from yaml."""
config = import_input.copy()
if CONF_NAME not in config:
config[CONF_NAME] = DEFAULT_NAME
if CONF_LATITUDE not in config:
config[CONF_LATITUDE] = self.hass.config.latitude
if CONF_LONGITUDE not in config:
config[CONF_LONGITUDE] = self.hass.config.longitude
if CONF_MODE not in config:
config[CONF_MODE] = DEFAULT_FORECAST_MODE
if CONF_LANGUAGE not in config:
config[CONF_LANGUAGE] = DEFAULT_LANGUAGE
if CONF_UNITS not in config:
config[CONF_UNITS] = DEFAULT_UNITS
if CONF_MONITORED_CONDITIONS not in config:
config[CONF_MONITORED_CONDITIONS] = []
if CONF_FORECAST not in config:
config[CONF_FORECAST] = ""
if CONF_HOURLY_FORECAST not in config:
config[CONF_HOURLY_FORECAST] = ""
if CONF_API_KEY not in config:
config[CONF_API_KEY] = None
if PW_PLATFORM not in config:
config[PW_PLATFORM] = None
if PW_PREVPLATFORM not in config:
config[PW_PREVPLATFORM] = None
if PW_ROUND not in config:
config[PW_ROUND] = "No"
if CONF_SCAN_INTERVAL not in config:
config[CONF_SCAN_INTERVAL] = DEFAULT_SCAN_INTERVAL
if CONF_ENDPOINT not in config:
config[CONF_ENDPOINT] = DEFAULT_ENDPOINT
return await self.async_step_user(config)
class PirateWeatherOptionsFlow(OptionsFlow):
"""Handle options."""
async def async_step_init(self, user_input: dict | None = None) -> ConfigFlowResult:
"""Manage the options."""
if user_input is not None:
# if self.config_entry.options:
# user_input[PW_PREVPLATFORM] = self.config_entry.options[PW_PLATFORM]
# else:
# user_input[PW_PREVPLATFORM] = self.hass.data[DOMAIN][entry.entry_id][PW_PLATFORM]
# self.hass.data[DOMAIN][entry.entry_id][PW_PREVPLATFORM] = self.hass.data[DOMAIN][entry.entry_id][PW_PLATFORM]
# user_input[PW_PREVPLATFORM] = self.hass.data[DOMAIN][entry.entry_id][PW_PLATFORM]
# _LOGGER.warning('async_step_init_Options')
return self.async_create_entry(title=user_input[CONF_NAME], data=user_input)
return self.async_show_form(
step_id="init",
data_schema=self._get_options_schema(),
)
def _get_options_schema(self):
return vol.Schema(
{
vol.Optional(
CONF_NAME,
default=self.config_entry.options.get(
CONF_NAME,
self.config_entry.data.get(CONF_NAME, DEFAULT_NAME),
),
): str,
vol.Optional(
CONF_LATITUDE,
default=self.config_entry.options.get(
CONF_LATITUDE,
self.config_entry.data.get(
CONF_LATITUDE, self.hass.config.latitude
),
),
): cv.latitude,
vol.Optional(
CONF_LONGITUDE,
default=self.config_entry.options.get(
CONF_LONGITUDE,
self.config_entry.data.get(
CONF_LONGITUDE, self.hass.config.longitude
),
),
): cv.longitude,
vol.Optional(
CONF_SCAN_INTERVAL,
default=self.config_entry.options.get(
CONF_SCAN_INTERVAL,
self.config_entry.data.get(
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
),
),
): int,
vol.Required(
PW_PLATFORM,
default=self.config_entry.options.get(
PW_PLATFORM,
self.config_entry.data.get(PW_PLATFORM, []),
),
): cv.multi_select(PW_PLATFORMS),
vol.Optional(
CONF_LANGUAGE,
default=self.config_entry.options.get(
CONF_LANGUAGE,
self.config_entry.data.get(CONF_LANGUAGE, DEFAULT_LANGUAGE),
),
): vol.In(LANGUAGES),
vol.Optional(
CONF_MODELS,
default=str(
self.config_entry.options.get(
CONF_MODELS,
self.config_entry.data.get(CONF_MODELS, ""),
),
),
): str,
vol.Optional(
CONF_FORECAST,
default=str(
self.config_entry.options.get(
CONF_FORECAST,
self.config_entry.data.get(CONF_FORECAST, ""),
),
),
): str,
vol.Optional(
CONF_HOURLY_FORECAST,
default=str(
self.config_entry.options.get(
CONF_HOURLY_FORECAST,
self.config_entry.data.get(CONF_HOURLY_FORECAST, ""),
),
),
): str,
vol.Optional(
CONF_MONITORED_CONDITIONS,
default=self.config_entry.options.get(
CONF_MONITORED_CONDITIONS,
self.config_entry.data.get(CONF_MONITORED_CONDITIONS, []),
),
): cv.multi_select(ALL_CONDITIONS),
vol.Optional(
CONF_UNITS,
default=self.config_entry.options.get(
CONF_UNITS,
self.config_entry.data.get(CONF_UNITS, DEFAULT_UNITS),
),
): vol.In(["si", "us", "ca", "uk"]),
vol.Optional(
PW_ROUND,
default=self.config_entry.options.get(
PW_ROUND,
self.config_entry.options.get(PW_ROUND, "No"),
),
): vol.In(["Yes", "No"]),
vol.Optional(
CONF_ENDPOINT,
default=str(
self.config_entry.options.get(
CONF_ENDPOINT,
self.config_entry.data.get(CONF_ENDPOINT, DEFAULT_ENDPOINT),
),
),
): str,
}
)
async def _is_pw_api_online(hass, api_key, lat, lon, endpoint):
forecastString = endpoint + "/forecast/" + api_key + "/" + str(lat) + "," + str(lon)
session = async_get_clientsession(hass)
async with session.get(forecastString) as resp:
return resp.status
+358
View File
@@ -0,0 +1,358 @@
"""Consts for the Pirate Weather."""
from __future__ import annotations
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.components.weather import (
ATTR_FORECAST_CONDITION,
ATTR_FORECAST_PRECIPITATION,
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_FORECAST_PRESSURE,
ATTR_FORECAST_TEMP,
ATTR_FORECAST_TEMP_LOW,
ATTR_FORECAST_TIME,
)
from homeassistant.const import (
DEGREE,
PERCENTAGE,
UV_INDEX,
Platform,
UnitOfLength,
UnitOfPressure,
UnitOfSpeed,
UnitOfTemperature,
)
DOMAIN = "pirateweather"
DEFAULT_NAME = "PirateWeather"
DEFAULT_LANGUAGE = "en"
DEFAULT_UNITS = "us"
DEFAULT_SCAN_INTERVAL = 1200
DEFAULT_ENDPOINT = "https://api.pirateweather.net"
ATTRIBUTION = "Data provided by Pirate Weather GUI"
MANUFACTURER = "PirateWeather"
CONF_LANGUAGE = "language"
CONF_UNITS = "units"
CONF_ENDPOINT = "endpoint"
CONF_MODELS = "models"
CONFIG_FLOW_VERSION = 2
ENTRY_NAME = "name"
ENTRY_WEATHER_COORDINATOR = "weather_coordinator"
ATTR_API_PRECIPITATION = "precipitation"
ATTR_API_PRECIPITATION_KIND = "precipitation_kind"
ATTR_API_DATETIME = "datetime"
ATTR_API_DEW_POINT = "dew_point"
ATTR_API_WEATHER = "weather"
ATTR_API_TEMPERATURE = "temperature"
ATTR_API_FEELS_LIKE_TEMPERATURE = "feels_like_temperature"
ATTR_API_WIND_SPEED = "wind_speed"
ATTR_API_WIND_BEARING = "wind_bearing"
ATTR_API_HUMIDITY = "humidity"
ATTR_API_PRESSURE = "pressure"
ATTR_API_CONDITION = "condition"
ATTR_API_CLOUDS = "clouds"
ATTR_API_RAIN = "rain"
ATTR_API_SNOW = "snow"
ATTR_API_UV_INDEX = "uv_index"
ATTR_API_WEATHER_CODE = "weather_code"
ATTR_API_FORECAST = "forecast"
UPDATE_LISTENER = "update_listener"
PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
PW_PLATFORMS = ["Sensor", "Weather"]
PW_PLATFORM = "pw_platform"
PW_PREVPLATFORM = "pw_prevplatform"
PW_ROUND = "pw_round"
ATTR_FORECAST_CLOUD_COVERAGE = "cloud_coverage"
ATTR_FORECAST_HUMIDITY = "humidity"
ATTR_FORECAST_NATIVE_VISIBILITY = "native_visibility"
FORECAST_MODE_HOURLY = "hourly"
FORECAST_MODE_DAILY = "daily"
FORECAST_MODES = [
FORECAST_MODE_HOURLY,
FORECAST_MODE_DAILY,
]
DEFAULT_FORECAST_MODE = FORECAST_MODE_DAILY
FORECASTS_HOURLY = "forecasts_hourly"
FORECASTS_DAILY = "forecasts_daily"
ALL_CONDITIONS = {
"summary": "Summary",
"icon": "Icon",
"precip_type": "Precipitation Type",
"precip_intensity": "Precipitation Intensity",
"precip_probability": "Precipitation Probability",
"precip_accumulation": "Precipitation Accumulation",
"temperature": "Temperature",
"apparent_temperature": "Apparent Temperature",
"dew_point": "Dew Point",
"humidity": "Humidity",
"wind_speed": "Wind Speed",
"wind_gust": "Wind Gust",
"wind_bearing": "Wind Bearing",
"cloud_cover": "Cloud Cover",
"pressure": "Pressure",
"visibility": "Visibility",
"ozone": "Ozone",
"minutely_summary": "Minutely Summary",
"hourly_summary": "Hourly Summary",
"daily_summary": "Daily Summary",
"temperature_high": "Temperature High",
"temperature_low": "Temperature Low",
"apparent_temperature_high": "Apparent Temperature High",
"apparent_temperature_low": "Apparent Temperature Low",
"precip_intensity_max": "Precip Intensity Max",
"uv_index": "UV Index",
"moon_phase": "Moon Phase",
"sunrise_time": "Sunrise Time",
"sunset_time": "Sunset Time",
"nearest_storm_distance": "Nearest Storm Distance",
"nearest_storm_bearing": "Nearest Storm Bearing",
"alerts": "Alerts",
"time": "Time",
"fire_index": "Fire Index",
"fire_index_max": "Fire Index Max",
"fire_risk_level": "Fire Risk Level",
"smoke": "Smoke",
"smoke_max": "Smoke Max",
"liquid_accumulation": "Liquid Accumulation",
"snow_accumulation": "Snow Accumulation",
"ice_accumulation": "Ice Accumulation",
"apparent_temperature_high_time": "Daytime High Apparent Temperature Time",
"apparent_temperature_low_time": "Overnight Low Apparent Temperature Time",
"temperature_high_time": "Daytime High Temperature Time",
"temperature_min_time": "Low Temperature Time",
"hrrr_subh_update_time": "HRRR SubH Update Time",
"hrrr_0_18_update_time": "HRRR 0-18 Update Time",
"nbm_update_time": "NBM Update Time",
"nbm_fire_update_time": "NBM Fire Update Time",
"hrrr_18_48_update_time": "HRRR 18-48 Update Time",
"gfs_update_time": "GFS Update Time",
"gefs_update_time": "GEFS Update Time",
"current_day_liquid": "Current Day Liquid Accumulation",
"current_day_snow": "Current Day Snow Accumulation",
"current_day_ice": "Current Day Ice Accumulation",
"cape": "Convective Available Potential Energy",
"cape_max": "Convective Available Potential Energy Max",
"solar": "Downward Short-Wave Radiation Flux",
"solar_max": "Downward Short-Wave Radiation Flux Max",
"rain_intensity": "Rain Intensity",
"rain_intensity_max": "Rain Intensity Max",
"snow_intensity": "Snow Intensity",
"snow_intensity_max": "Snow Intensity Max",
"ice_intensity": "Ice Intensity",
"ice_intensity_max": "Ice Intensity Max",
}
LANGUAGES = [
"ar",
"az",
"be",
"bg",
"bn",
"bs",
"ca",
"cs",
"cy",
"da",
"de",
"el",
"en",
"eo",
"es",
"et",
"fa",
"fi",
"fr",
"ga",
"gd",
"he",
"hi",
"hr",
"hu",
"id",
"is",
"it",
"ja",
"ka",
"kn",
"ko",
"kw",
"lv",
"ml",
"mr",
"nl",
"no",
"pa",
"pl",
"pt",
"ro",
"ru",
"sk",
"sl",
"sr",
"sv",
"ta",
"te",
"tet",
"tr",
"uk",
"ur",
"vi",
"x-pig-latin",
"zh",
"zh-tw",
]
WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key=ATTR_API_WEATHER,
name="Weather",
),
SensorEntityDescription(
key=ATTR_API_DEW_POINT,
name="Dew Point",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_TEMPERATURE,
name="Temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_FEELS_LIKE_TEMPERATURE,
name="Feels like temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_WIND_SPEED,
name="Wind speed",
native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_WIND_BEARING,
name="Wind bearing",
native_unit_of_measurement=DEGREE,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_HUMIDITY,
name="Humidity",
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_PRESSURE,
name="Pressure",
native_unit_of_measurement=UnitOfPressure.HPA,
device_class=SensorDeviceClass.PRESSURE,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_CLOUDS,
name="Cloud coverage",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_RAIN,
name="Rain",
native_unit_of_measurement=UnitOfLength.MILLIMETERS,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_SNOW,
name="Snow",
native_unit_of_measurement=UnitOfLength.MILLIMETERS,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_PRECIPITATION_KIND,
name="Precipitation kind",
),
SensorEntityDescription(
key=ATTR_API_UV_INDEX,
name="UV Index",
native_unit_of_measurement=UV_INDEX,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_CONDITION,
name="Condition",
),
SensorEntityDescription(
key=ATTR_API_WEATHER_CODE,
name="Weather Code",
),
)
FORECAST_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key=ATTR_FORECAST_CONDITION,
name="Condition",
),
SensorEntityDescription(
key=ATTR_FORECAST_PRECIPITATION,
name="Precipitation",
native_unit_of_measurement=UnitOfLength.MILLIMETERS,
),
SensorEntityDescription(
key=ATTR_FORECAST_PRECIPITATION_PROBABILITY,
name="Precipitation probability",
native_unit_of_measurement=PERCENTAGE,
),
SensorEntityDescription(
key=ATTR_FORECAST_PRESSURE,
name="Pressure",
native_unit_of_measurement=UnitOfPressure.HPA,
device_class=SensorDeviceClass.PRESSURE,
),
SensorEntityDescription(
key=ATTR_FORECAST_TEMP,
name="Temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
),
SensorEntityDescription(
key=ATTR_FORECAST_TEMP_LOW,
name="Temperature Low",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
),
SensorEntityDescription(
key=ATTR_FORECAST_TIME,
name="Time",
device_class=SensorDeviceClass.TIMESTAMP,
),
SensorEntityDescription(
key=ATTR_API_WIND_BEARING,
name="Wind bearing",
native_unit_of_measurement=DEGREE,
),
SensorEntityDescription(
key=ATTR_API_WIND_SPEED,
name="Wind speed",
native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
),
SensorEntityDescription(
key=ATTR_API_CLOUDS,
name="Cloud coverage",
native_unit_of_measurement=PERCENTAGE,
),
)
@@ -0,0 +1,188 @@
"""Taken from the fantastic Dark Sky library: https://github.com/ZeevG/python-forecast.io in October 2024. Updated in April 2025 using the Pirate Weather library: https://github.com/cloneofghosts/python-pirate-weather."""
import datetime
import requests
class UnicodeMixin:
"""Provide string representation for Python 2/3 compatibility."""
def __str__(self):
"""Return the unicode representation of the object for Python 2/3 compatibility."""
return self.__unicode__()
"""Models used in the Pirate Weather library."""
class Forecast(UnicodeMixin):
"""Represent the forecast data and provide methods to access weather blocks."""
def __init__(self, data, response, headers):
"""Initialize the Forecast with data, HTTP response, and headers."""
self.response = response
self.http_headers = headers
self.json = data
self._alerts = []
for alertJSON in self.json.get("alerts", []):
self._alerts.append(Alert(alertJSON))
def update(self):
"""Update the forecast data by making a new request to the same URL."""
r = requests.get(self.response.url)
self.json = r.json()
self.response = r
def currently(self):
"""Return the current weather data block."""
return self._pirateweather_data("currently")
def minutely(self):
"""Return the minutely weather data block."""
return self._pirateweather_data("minutely")
def hourly(self):
"""Return the hourly weather data block."""
return self._pirateweather_data("hourly")
def day_night(self):
"""Return the day_night weather data block."""
return self._pirateweather_data("day_night")
def daily(self):
"""Return the daily weather data block."""
return self._pirateweather_data("daily")
def flags(self):
"""Return the flags data block."""
return self._pirateweather_data("flags")
def offset(self):
"""Return the time zone offset for the forecast location."""
return self.json["offset"]
def alerts(self):
"""Return the list of alerts issued for this forecast."""
return self._alerts
def _pirateweather_data(self, key):
"""Fetch and return specific weather data (currently, minutely, hourly, daily, flags and day_night)."""
keys = ["minutely", "currently", "hourly", "daily", "flags", "day_night"]
try:
if key not in self.json:
keys.remove(key)
url = "{}&exclude={}{}".format(
self.response.url.split("&")[0],
",".join(keys),
",alerts",
)
response = requests.get(url).json()
self.json[key] = response[key]
if key == "currently":
return PirateWeatherDataPoint(self.json[key])
if key == "flags":
return PirateWeatherFlagsBlock(self.json[key])
return PirateWeatherDataBlock(self.json[key])
except KeyError:
if key == "currently":
return PirateWeatherDataPoint()
return PirateWeatherDataBlock()
class PirateWeatherDataBlock(UnicodeMixin):
"""Represent a block of weather data such as minutely, hourly, or daily summaries."""
def __init__(self, d=None):
"""Initialize the data block with summary and icon information."""
d = d or {}
self.summary = d.get("summary")
self.icon = d.get("icon")
self.data = [
PirateWeatherDataPoint(datapoint) for datapoint in d.get("data", [])
]
def __unicode__(self):
"""Return a string representation of the data block."""
return f"<PirateWeatherDataBlock instance: {self.summary} with {len(self.data)} PirateWeatherDataPoints>"
class PirateWeatherFlagsBlock(UnicodeMixin):
"""Represent a block of flags data."""
def __init__(self, d=None):
"""Initialize the data block with flags information."""
d = d or {}
self.units = d.get("units")
self.version = d.get("version")
self.nearestStation = d.get("nearest-station")
self.sources = list(d.get("sources"))
self.sourceTimes = d.get("sourceTimes")
self.processTime = d.get("processTime")
self.ingestVersion = d.get("ingestVersion")
self.nearestCity = d.get("nearestCity")
self.nearestCountry = d.get("nearestCountry")
self.nearestSubNational = d.get("nearestSubNational")
def __unicode__(self):
"""Return a string representation of the data block."""
return f"<PirateWeatherFlagsDataBlock instance: {self.version}>"
class PirateWeatherDataPoint(UnicodeMixin):
"""Represent a single data point in a weather forecast, such as an hourly or daily data point."""
def __init__(self, d={}):
"""Initialize the data point with timestamp and weather information."""
self.d = d
try:
self.time = datetime.datetime.fromtimestamp(int(d["time"]))
self.utime = d["time"]
except KeyError:
pass
try:
sr_time = int(d["sunriseTime"])
self.sunriseTime = datetime.datetime.fromtimestamp(sr_time)
except KeyError:
self.sunriseTime = None
try:
ss_time = int(d["sunsetTime"])
self.sunsetTime = datetime.datetime.fromtimestamp(ss_time)
except KeyError:
self.sunsetTime = None
def __getattr__(self, name):
"""Return the weather property dynamically or return None if missing."""
try:
return self.d[name]
except KeyError:
return None
def __unicode__(self):
"""Return a string representation of the data point."""
return f"<PirateWeatherDataPoint instance: {self.summary} at {self.time}>"
class Alert(UnicodeMixin):
"""Represent a weather alert, such as a storm warning or flood alert."""
def __init__(self, json):
"""Initialize the alert with the raw JSON data."""
self.json = json
def __getattr__(self, name):
"""Return the alert property dynamically or return None if missing."""
try:
return self.json[name]
except KeyError:
return None
def __unicode__(self):
"""Return a string representation of the alert."""
return f"<Alert instance: {self.title} at {self.time}>"
@@ -0,0 +1,14 @@
{
"domain": "pirateweather",
"name": "Pirate Weather",
"codeowners": [
"@alexander0042"
],
"config_flow": true,
"dependencies": [],
"documentation": "https://github.com/alexander0042/pirate-weather-ha",
"integration_type": "service",
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/alexander0042/pirate-weather-ha/issues",
"version": "1.8.9"
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,64 @@
{
"config": {
"abort": {
"already_configured": "Místo je již nakonfigurováno"
},
"error": {
"cannot_connect": "Nepodařilo se připojit",
"invalid_api_key": "Neplatný API klíč"
},
"step": {
"user": {
"data": {
"api_key": "API klíč",
"language": "Jazyk",
"latitude": "Zeměpisná šířka",
"longitude": "Zeměpisná délka",
"mode": "Režim předpovědi pro entitu Počasí",
"name": "Název integrace",
"units": "Jednotky.",
"models": "Modely počasí k vyloučení (csv)",
"forecast": "Denní předpovědi senzorů ve formátu csv od 0 do 7 (např. '0,1,3'). Používá se pouze pokud jsou požadovány senzory.",
"hourly_forecast": "Hodinové předpovědi senzorů ve formátu csv od 0 do 168 (např. '0,1,10'). Používá se pouze pokud jsou požadovány senzory.",
"monitored_conditions": "Monitorované podmínky pro vytváření senzorů. Používá se pouze pokud jsou požadovány senzory.",
"pw_platform": "Entita počasí a/nebo entita senzoru. Senzor vytvoří entity pro každou podmínku v každém čase. Pokud si nejste jisti, vyberte pouze Počasí!",
"pw_round": "Zaokrouhlit hodnoty na nejbližší celé číslo. Ujistěte se, že vybrané jednotky odpovídají systémovým jednotkám.",
"scan_interval": "Sekundy čekání mezi aktualizacemi. Snížení této hodnoty pod 900 sekund (15 minut) se nedoporučuje.",
"endpoint": "Koncový bod pro použití vývojářského nebo lokálního zdroje s https://. Výchozí nastavení je api.pirateweather.net"
},
"description": "Nastavte integraci Pirate Weather. Pro generování API klíče jděte na pirateweather.net",
"data_description": {
"forecast": "např. '0,1,4' (bez uvozovek)",
"hourly_forecast": "např. '0,1,4' (bez uvozovek)"
}
}
}
},
"options": {
"step": {
"init": {
"data": {
"api_key": "API klíč",
"language": "Jazyk",
"latitude": "Zeměpisná šířka",
"longitude": "Zeměpisná délka",
"mode": "Režim předpovědi pro Entitu Počasí",
"name": "Název integrace",
"units": "Jednotky.",
"models": "Modely počasí k vyloučení (csv)",
"forecast": "Denní předpovědi senzorů ve formátu csv od 0 do 7 (např. '0,1,3'). Používá se pouze pokud jsou požadovány senzory.\n POZNÁMKA: Odebrání senzorů způsobí, že vzniknou nedostupné entity, které je potřeba smazat.",
"hourly_forecast": "Hodinové předpovědi senzorů ve formátu csv od 0 do 168 (např. '0,1,10'). Používá se pouze pokud jsou požadovány senzory.\n POZNÁMKA: Odebrání senzorů způsobí, že vzniknou nedostupné entity, které je potřeba smazat.",
"monitored_conditions": "Monitorované podmínky pro vytváření senzorů. Používá se pouze pokud jsou požadovány senzory.\n POZNÁMKA: Odebrání senzorů způsobí, že vzniknou nedostupné entity, které je potřeba smazat.",
"pw_platform": "Entita Počasí a/nebo Entita Senzoru. Senzor vytvoří entity pro každou podmínku v každém čase. Pokud si nejste jisti, vyberte pouze Počasí!",
"pw_round": "Zaokrouhlit hodnoty na nejbližší celé číslo. Ujistěte se, že vybrané jednotky odpovídají systémovým jednotkám.",
"endpoint": "Koncový bod pro použití vývojářského nebo lokálního zdroje s https://. Výchozí nastavení je api.pirateweather.net"
},
"description": "Nastavte integraci Pirate Weather. Pro vygenerování API klíče jděte na pirateweather.net",
"data_description": {
"forecast": "např. '0,1,4'.\n Pro odebrání existujících senzorů napište 'None' (bez uvozovek).",
"hourly_forecast": "např. '0,1,4'.\n Pro odebrání existujících senzorů napište 'None' (bez uvozovek)."
}
}
}
}
}
@@ -0,0 +1,65 @@
{
"config": {
"abort": {
"already_configured": "Standort ist bereits konfiguriert"
},
"error": {
"cannot_connect": "Fehler beim Verbinden",
"invalid_api_key": "Ungültiger API-Schlüssel"
},
"step": {
"user": {
"data": {
"api_key": "API-Schlüssel",
"language": "Sprache",
"latitude": "Breitengrad. Auf 0 setzen um aktuelle Position zu verwenden",
"longitude": "Längengerad. Auf 0 setzen um aktuelle Position zu verwenden",
"mode": "Vorhersage-Modus für die Wetter-Entität",
"name": "Name der Integration",
"units": "Einheiten.",
"models": "Wettermodelle zum Ausschließen (csv)",
"forecast": "Tägliche Vorhersage-Sensoren im csv-Format von 0-7 (z. B. '0,1,3'). Wird nur verwendet, wenn Sensoren angefordert werden.",
"hourly_forecast": "Stündliche Vorhersage-Sensoren im csv-Format von 0-168 (z. B. '0,1,10'). Nur verwendet, wenn Sensoren angefordert werden.",
"monitored_conditions": "Überwachte Bedingungen, für die Sensoren erstellt werden sollen. Wird nur verwendet, wenn Sensoren angefordert werden.",
"pw_platform": "Wetter- und/oder Sensor-Entität. Sensor wird für jede Bedingung zu jeder Zeit Entitäten erstellen. Wenn Sie unsicher sind, wählen Sie nur Wetter!",
"pw_round": "Rundet die Werte auf die nächstliegende Ganzzahl. Stellen Sie sicher, dass die gewählten Einheiten mit den Systemeinheiten übereinstimmen.",
"scan_interval": "Sekunden, die zwischen den Aktualisierungen gewartet werden. Es wird nicht empfohlen, diesen Wert unter 900 Sekunden (15 Minuten) zu senken.",
"endpoint": "Endpunkt zur Verwendung der Entwicklungs- oder lokalen Quelle mit https://. Standard ist api.pirateweather.net"
},
"description": "Pirate Weather-Integration einrichten. Besuche pirateweather.net um einen API-Schlüssel zu generieren.",
"data_description": {
"forecast": "z.B. '0,1,4' (ohne Anführungszeichen)",
"hourly_forecast": "z.B. '0,1,4' (ohne Anführungszeichen)"
}
}
}
},
"options": {
"step": {
"init": {
"data": {
"api_key": "API-Schlüssel",
"language": "Sprache",
"latitude": "Breitengrad. Auf 0 setzen um aktuelle Position zu verwenden",
"longitude": "Längengerad. Auf 0 setzen um aktuelle Position zu verwenden",
"scan_interval": "Sekunden, die zwischen den Aktualisierungen gewartet werden. Es wird nicht empfohlen, diesen Wert unter 900 Sekunden (15 Minuten) zu senken.",
"mode": "Vorhersage-Modus für die Wetter-Entität",
"name": "Name der Integration",
"units": "Einheiten.",
"models": "Wettermodelle zum Ausschließen (csv)",
"forecast": "Tägliche Vorhersage-Sensoren im csv-Format von 0-7 (z. B. '0,1,3'). Wird nur verwendet, wenn Sensoren angefordert werden.\n HINWEIS: Das Entfernen von Sensoren führt zu verwaisten Einheiten, die gelöscht werden müssen.",
"hourly_forecast": "Stündliche Vorhersage-Sensoren im csv-Format von 0-168 (z. B. '0,1,10'). Nur verwendet, wenn Sensoren angefordert werden.\n HINWEIS: Das Entfernen von Sensoren führt zu verwaisten Einheiten, die gelöscht werden müssen.",
"monitored_conditions": "Überwachte Bedingungen, für die Sensoren erstellt werden sollen. Wird nur verwendet, wenn Sensoren angefordert werden.",
"pw_platform": "Wetter- und/oder Sensor-Entität. Sensor wird für jede Bedingung zu jeder Zeit Entitäten erstellen. Wenn Sie unsicher sind, wählen Sie nur Wetter!",
"pw_round": "Rundet die Werte auf die nächstliegende Ganzzahl. Stellen Sie sicher, dass die gewählten Einheiten mit den Systemeinheiten übereinstimmen.",
"endpoint": "Endpunkt zur Verwendung der Entwicklungs- oder lokalen Quelle mit https://. Standard ist api.pirateweather.net"
},
"description": "Pirate Weather-Integration einrichten. Besuche pirateweather.net um einen API-Schlüssel zu generieren.",
"data_description": {
"forecast": "z.B '0,1,4'.\n Gib 'None' (ohne Anführungszeichen) ein, um bestehende Sensoren zu entfernen.",
"hourly_forecast": "z.B. '0,1,4'.\n Gib 'None' (ohne Anführungszeichen) ein, um bestehende Sensoren zu entfernen."
}
}
}
}
}
@@ -0,0 +1,65 @@
{
"config": {
"abort": {
"already_configured": "Location is already configured"
},
"error": {
"cannot_connect": "Failed to connect",
"invalid_api_key": "Invalid API key"
},
"step": {
"user": {
"data": {
"api_key": "API Key",
"language": "Language",
"latitude": "Latitude. Set as 0 to use current location",
"longitude": "Longitude. Set as 0 to use current location",
"mode": "Forecast mode for the Weather entity",
"name": "Integration Name",
"units": "Units.",
"models": "Weather models to exclude (csv)",
"forecast": "Daily forecasts sensors in csv form from 0-7 (ex. '0,1,3'). Only used if sensors are requested.",
"hourly_forecast": "Hourly forecast sensors in csv form from 0-168 (ex. '0,1,10'). Only used if sensors are requested.",
"monitored_conditions": "Monitored conditions to create sensors for. Only used if sensors are requested.",
"pw_platform": "Weather Entity and/or Sensor Entity. Sensor will create entities for each condition at each time. If unsure, only select Weather!",
"pw_round": "Round values to the nearest integer. Ensure that the selected units match the system units.",
"scan_interval": "Seconds to wait between updates. Reducing this below 900 seconds (15 minutes) is not recomended.",
"endpoint": "Endpoint to use dev or local source, with https://. Default is api.pirateweather.net"
},
"description": "Set up Pirate Weather integration. To generate API key visit pirateweather.net",
"data_description": {
"forecast": "ex. '0,1,4' (without quotes)",
"hourly_forecast": "ex. '0,1,4' (without quotes)"
}
}
}
},
"options": {
"step": {
"init": {
"data": {
"api_key": "API Key",
"language": "Language",
"latitude": "Latitude. Set as 0 to use current location",
"longitude": "Longitude. Set as 0 to use current location",
"scan_interval": "Seconds to wait between updates. Reducing this below 900 seconds (15 minutes) is not recomended.",
"mode": "Forecast mode for the Weather entity",
"name": "Integration Name",
"units": "Units.",
"models": "Weather models to exclude (csv)",
"forecast": "Daily forecasts sensors in csv form from 0-7 (ex. '0,1,3'). Only used if sensors are requested.\n NOTE: Removing sensors will produce orphaned entities that need to be deleted.",
"hourly_forecast": "Hourly forecast sensors in csv form from 0-168 (ex. '0,1,10'). Only used if sensors are requested.\n NOTE: Removing sensors will produce orphaned entities that need to be deleted.",
"monitored_conditions": "Monitored conditions to create sensors for. Only used if sensors are requested.\n NOTE: Removing sensors will produce orphaned entities that need to be deleted.",
"pw_platform": "Weather Entity and/or Sensor Entity. Sensor will create entities for each condition at each time. If unsure, only select Weather!",
"pw_round": "Round values to the nearest integer. Ensure that the selected units match the system units.",
"endpoint": "Endpoint to use dev or local source, with https://. Default is api.pirateweather.net"
},
"description": "Set up Pirate Weather integration. To generate API key visit pirateweather.net",
"data_description": {
"forecast": "ex. '0,1,4'.\n To remove existing sensors, type 'None' (without quotes).",
"hourly_forecast": "ex. '0,1,4'.\n To remove existing sensors, type 'None' (without quotes)."
}
}
}
}
}
@@ -0,0 +1,65 @@
{
"config": {
"abort": {
"already_configured": "La localisation est déjà configurée"
},
"error": {
"cannot_connect": "Échec de la connexion",
"invalid_api_key": "Clé API invalide"
},
"step": {
"user": {
"data": {
"api_key": "Clé API",
"language": "Langue",
"latitude": "Latitude. Définir à 0 pour utiliser la localisation actuelle",
"longitude": "Longitude. Définir à 0 pour utiliser la localisation actuelle",
"mode": "Mode de prévision pour l'entité Météo",
"name": "Nom de l'intégration",
"units": "Unités.",
"models": "Modèles météo à exclure (CSV)",
"forecast": "Capteurs de prévisions quotidiennes au format CSV de 0 à 7 (ex. '0,1,3'). Uniquement utilisé si les capteurs sont demandés.",
"hourly_forecast": "Capteurs de prévisions horaires au format CSV de 0 à 168 (ex. '0,1,10'). Uniquement utilisé si les capteurs sont demandés.",
"monitored_conditions": "Conditions surveillées pour créer des capteurs. Uniquement utilisé si les capteurs sont demandés.",
"pw_platform": "Entité Météo et/ou Entité Capteur. Le capteur créera des entités pour chaque condition à chaque heure. Si vous n'êtes pas sûr, sélectionnez uniquement Météo !",
"pw_round": "Arrondir les valeurs à l'entier le plus proche. Assurez-vous que les unités sélectionnées correspondent aux unités du système.",
"scan_interval": "Secondes à attendre entre les mises à jour. Réduire cela en dessous de 900 secondes (15 minutes) n'est pas recommandé.",
"endpoint": "Point de terminaison à utiliser pour la source dev ou locale, avec https://. La valeur par défaut est api.pirateweather.net"
},
"description": "Configurer l'intégration Pirate Weather. Pour générer une clé API, visitez pirateweather.net",
"data_description": {
"forecast": "ex. '0,1,4' (sans guillemets)",
"hourly_forecast": "ex. '0,1,4' (sans guillemets)"
}
}
}
},
"options": {
"step": {
"init": {
"data": {
"api_key": "Clé API",
"language": "Langue",
"latitude": "Latitude. Définir à 0 pour utiliser la localisation actuelle",
"longitude": "Longitude. Définir à 0 pour utiliser la localisation actuelle",
"scan_interval": "Secondes à attendre entre les mises à jour. Réduire cela en dessous de 900 secondes (15 minutes) n'est pas recommandé.",
"mode": "Mode de prévision pour l'entité Météo",
"name": "Nom de l'intégration",
"units": "Unités.",
"models": "Modèles météo à exclure (CSV)",
"forecast": "Capteurs de prévisions quotidiennes au format CSV de 0 à 7 (ex. '0,1,3'). Uniquement utilisé si les capteurs sont demandés.\n NOTE: La suppression de capteurs produira des entités orphelines qui devront être supprimées.",
"hourly_forecast": "Capteurs de prévisions horaires au format CSV de 0 à 168 (ex. '0,1,10'). Uniquement utilisé si les capteurs sont demandés.\n NOTE: La suppression de capteurs produira des entités orphelines qui devront être supprimées.",
"monitored_conditions": "Conditions surveillées pour créer des capteurs. Uniquement utilisé si les capteurs sont demandés.\n NOTE: La suppression de capteurs produira des entités orphelines qui devront être supprimées.",
"pw_platform": "Entité Météo et/ou Entité Capteur. Le capteur créera des entités pour chaque condition à chaque heure. Si vous n'êtes pas sûr, sélectionnez uniquement Météo !",
"pw_round": "Arrondir les valeurs à l'entier le plus proche. Assurez-vous que les unités sélectionnées correspondent aux unités du système.",
"endpoint": "Point de terminaison à utiliser pour la source dev ou locale, avec https://. La valeur par défaut est api.pirateweather.net"
},
"description": "Configurer l'intégration Pirate Weather. Pour générer une clé API, visitez pirateweather.net",
"data_description": {
"forecast": "ex. '0,1,4'.\n Pour supprimer les capteurs existants, tapez 'None' (sans guillemets).",
"hourly_forecast": "ex. '0,1,4'.\n Pour supprimer les capteurs existants, tapez 'None' (sans guillemets)."
}
}
}
}
}
@@ -0,0 +1,65 @@
{
"config": {
"abort": {
"already_configured": "Locatie is al ingesteld"
},
"error": {
"cannot_connect": "Verbinding Mislukt",
"invalid_api_key": "Foutieve API-sleutel"
},
"step": {
"user": {
"data": {
"api_key": "API-sleutel",
"language": "Taal",
"latitude": "Breedtegraad",
"longitude": "Lengtegraad",
"mode": "Voorspellingsmodus voor de Weer entiteit",
"name": "Integratie Naam",
"units": "Eenheden.",
"models": "Weermodellen om uit te sluiten (csv)",
"forecast": "Dagelijkse prognoses sensoren in csv-vorm van 0-7 (bijv. '0,1,3'). Wordt alleen gebruikt als sensoren worden aangeroepen.",
"hourly_forecast": "Uurlijkse prognoses sensoren in csv-vorm van 0-168 (bijv. '0,1,10'). Wordt alleen gebruikt als sensoren worden aangeroepen.",
"monitored_conditions": "Gecontroleerde omstandigheden om sensoren voor te creëren. Wordt alleen gebruikt als sensoren worden aangeroepen.",
"pw_platform": "Weer Entiteit en/of Sensor Entiteit. Sensor maakt op elk moment entiteiten voor elke voorwaarde. Als u het niet zeker weet, selecteert u alleen Weer!",
"pw_round": "Rond waarden af op het dichtstbijzijnde gehele getal. Zorg ervoor dat de geselecteerde eenheden overeenkomen met de systeemeenheden.",
"scan_interval": "Seconden wachten tussen updates. Het wordt niet aanbevolen om dit onder de 900 seconden (15 minuten) te zetten." ,
"endpoint": "Eindpunt voor gebruik van dev of lokale bron, met https://. Standaard is dit api.pirateweather.net"
},
"description": "Stel Pirate Weather-integratie in. Ga naar pirateweather.net om een API-sleutel te genereren.",
"data_description": {
"forecast": "bijv. '0,1,4' (zonder aanhalingstekens)",
"hourly_forecast": "bijv. '0,1,4' (zonder aanhalingstekens)"
}
}
}
},
"options": {
"step": {
"init": {
"data": {
"api_key": "API-sleutel",
"language": "Taal",
"latitude": "Breedtegraad",
"longitude": "Lengtegraad",
"mode": "Voorspellingsmodus voor de Weer entiteit",
"name": "Integratie Naam",
"units": "Eenheden.",
"models": "Weermodellen om uit te sluiten (csv)",
"forecast": "Dagelijkse prognoses sensoren in csv-vorm van 0-7 (bijv. '0,1,3'). Wordt alleen gebruikt als sensoren worden aangeroepen.",
"hourly_forecast": "Uurlijkse prognoses sensoren in csv-vorm van 0-168 (bijv. '0,1,10'). Wordt alleen gebruikt als sensoren worden aangeroepen.",
"monitored_conditions": "Gecontroleerde omstandigheden om sensoren voor te creëren. Wordt alleen gebruikt als sensoren worden aangeroepen.",
"pw_platform": "Weer Entiteit en/of Sensor Entiteit. Sensor maakt op elk moment entiteiten voor elke voorwaarde. Als u het niet zeker weet, selecteert u alleen Weer!",
"pw_round": "Rond waarden af op het dichtstbijzijnde gehele getal. Zorg ervoor dat de geselecteerde eenheden overeenkomen met de systeemeenheden.",
"scan_interval": "Seconden wachten tussen updates. Het wordt niet aanbevolen om dit onder de 900 seconden (15 minuten) te zetten.",
"endpoint": "Eindpunt voor gebruik van dev of lokale bron, met https://. Standaard is dit api.pirateweather.net"
},
"description": "Stel Pirate Weather-integratie in. Ga naar pirateweather.net om een API-sleutel te genereren.",
"data_description": {
"forecast": "bijv. '0,1,4' (zonder aanhalingstekens)",
"hourly_forecast": "bijv. '0,1,4.\n om bestaande sensoren te verwijderen, type 'None' (zonder aanhalingstekens)"
}
}
}
}
}
@@ -0,0 +1,64 @@
{
"config": {
"abort": {
"already_configured": "Lokalizacja jest już skonfigurowana"
},
"error": {
"cannot_connect": "Nie udało się połączyć",
"invalid_api_key": "Nieprawidłowy klucz API"
},
"step": {
"user": {
"data": {
"api_key": "Klucz API",
"language": "Język",
"latitude": "Szerokość geograficzna",
"longitude": "Długość geograficzna",
"mode": "Tryb prognozy dla encji Pogoda",
"name": "Nazwa integracji",
"units": "Jednostki.",
"models": "Modele pogodowe do wykluczenia (csv)",
"forecast": "Czujniki prognozy dziennej w postaci csv od 0 do 7 (np. '0,1,3'). Używane tylko wtedy, gdy wymagane są sensory.",
"hourly_forecast": "Sensory prognozy godzinowej w formie csv od 0 do 168 (np. '0,1,10'). Używane tylko wtedy, gdy wymagane są sensory.",
"monitored_conditions": "Monitorowane warunki do tworzenia czujników. Używane tylko wtedy, gdy wymagane są sensory.",
"pw_platform": "Encja Pogody i/lub Encja Sensora. Sensor będzie tworzył encjei dla każdego warunku w każdym czasie. Jeśli nie jesteś pewien, wybierz tylko Pogoda!",
"pw_round": "Zaokrąglij wartości do najbliższej liczby całkowitej. Upewnij się, że wybrane jednostki pasują do jednostek systemowych.",
"scan_interval": "Sekundy oczekiwania między aktualizacjami. Nie zaleca się skracania tego poniżej 900 sekund (15 minut).",
"endpoint": "Punkt końcowy do użycia dev lub lokalnego źródła, z https://. Domyślnie jest to api.pirateweather.net"
},
"description": "Skonfiguruj integrację Pirate Weather. Aby wygenerować klucz API, przejdź do pirateweather.net",
"data_description": {
"forecast": "np. '0,1,4' (bez cudzysłowów)",
"hourly_forecast": "np. '0,1,4' (bez cudzysłowów)"
}
}
}
},
"options": {
"step": {
"init": {
"data": {
"api_key": "Klucz API",
"language": "Język",
"latitude": "Szerokość geograficzna",
"longitude": "Długość geograficzna",
"mode": "Tryb prognozy dla encji Pogoda",
"name": "Nazwa integracji",
"units": "Jednostki.",
"models": "Modele pogodowe do wykluczenia (csv)",
"forecast": "Czujniki prognozy dziennej w postaci csv od 0 do 7 (np. '0,1,3'). Używane tylko wtedy, gdy wymagane są sensory. UWAGA: Usunięcie czujników spowoduje utworzenie osieroconych encji, które należy usunąć.",
"hourly_forecast": "Sensory prognozy godzinowej w formie csv od 0 do 168 (np. '0,1,10'). Używane tylko wtedy, gdy wymagane są sensory. UWAGA: Usunięcie czujników spowoduje utworzenie osieroconych encji, które należy usunąć.",
"monitored_conditions": "Monitorowane warunki do tworzenia czujników. Używane tylko wtedy, gdy wymagane są sensory. UWAGA: Usunięcie czujników spowoduje utworzenie osieroconych encji, które należy usunąć.",
"pw_platform": "Encja Pogody i/lub Encja Sensora. Sensor będzie tworzył encjei dla każdego warunku w każdym czasie. Jeśli nie jesteś pewien, wybierz tylko Pogoda!",
"pw_round": "Zaokrąglij wartości do najbliższej liczby całkowitej. Upewnij się, że wybrane jednostki pasują do jednostek systemowych.",
"endpoint": "Punkt końcowy do użycia dev lub lokalnego źródła, z https://. Domyślnie jest to api.pirateweather.net"
},
"description": "Skonfiguruj integrację Pirate Weather. Aby wygenerować klucz API, przejdź do pirateweather.net",
"data_description": {
"forecast": "np. '0,1,4'",
"hourly_forecast": "np. '0,1,4'"
}
}
}
}
}
@@ -0,0 +1,64 @@
{
"config": {
"abort": {
"already_configured": "Miesto je už nakonfigurované"
},
"error": {
"cannot_connect": "Nepodarilo sa pripojiť",
"invalid_api_key": "Neplatný API kľúč"
},
"step": {
"user": {
"data": {
"api_key": "API kľúč",
"language": "Jazyk",
"latitude": "Zemepisná šírka",
"longitude": "Zemepisná dĺžka",
"mode": "Režim predpovede pre entitu Počasie",
"name": "Názov integrácie",
"units": "Jednotky.",
"models": "Modely počasia na vylúčenie (csv)",
"forecast": "Denné predpovede senzorov vo formáte csv od 0 do 7 (napr. '0,1,3'). Používa sa iba ak sú požadované senzory.",
"hourly_forecast": "Hodinové predpovede senzorov vo formáte csv od 0 do 168 (napr. '0,1,10'). Používa sa iba ak sú požadované senzory.",
"monitored_conditions": "Monitorované podmienky pre vytváranie senzorov. Používa sa iba ak sú požadované senzory.",
"pw_platform": "Entita počasia a/alebo entita senzoru. Senzor vytvorí entity pre každú podmienku v každom čase. Ak si nie ste istí, vyberte iba Počasie!",
"pw_round": "Zaokrúhliť hodnoty na najbližšie celé číslo. Uistite sa, že vybrané jednotky zodpovedajú systémovým jednotkám.",
"scan_interval": "Sekundy čakania medzi aktualizáciami. Zníženie tejto hodnoty pod 900 sekúnd (15 minút) sa neodporúča.",
"endpoint": "Koncový bod na použitie vývojového alebo lokálneho zdroja s https://. Predvolená hodnota je api.pirateweather.net"
},
"description": "Nastavte integráciu Pirate Weather. Pre generovanie API kľúča choďte na pirateweather.net",
"data_description": {
"forecast": "napr. '0,1,4' (bez úvodzoviek)",
"hourly_forecast": "napr. '0,1,4' (bez úvodzoviek)"
}
}
}
},
"options": {
"step": {
"init": {
"data": {
"api_key": "API kľúč",
"language": "Jazyk",
"latitude": "Zemepisná šírka",
"longitude": "Zemepisná dĺžka",
"mode": "Režim predpovede pre Entitu Počasie",
"name": "Názov integrácie",
"units": "Jednotky.",
"models": "Modely počasia na vylúčenie (csv)",
"forecast": "Denné predpovede senzorov vo formáte csv od 0 do 7 (napr. '0,1,3'). Používa sa iba ak sú požadované senzory.\n POZNÁMKA: Odstránenie senzorov spôsobí, že vzniknú nedostupné entity, ktoré je potrebné vymazať.",
"hourly_forecast": "Hodinové predpovede senzorov vo formáte csv od 0 do 168 (napr. '0,1,10'). Používa sa iba ak sú požadované senzory.\n POZNÁMKA: Odstránenie senzorov spôsobí, že vzniknú nedostupné entity, ktoré je potrebné vymazať.",
"monitored_conditions": "Monitorované podmienky pre vytváranie senzorov. Používa sa iba ak sú požadované senzory.\n POZNÁMKA: Odstránenie senzorov spôsobí, že vzniknú nedostupné entity, ktoré je potrebné vymazať.",
"pw_platform": "Entita Počasie a/alebo Entita Senzoru. Senzor vytvorí entity pre každú podmienku v každom čase. Ak si nie ste istí, vyberte iba Počasie!",
"pw_round": "Zaokrúhliť hodnoty na najbližšie celé číslo. Uistite sa, že vybrané jednotky zodpovedajú systémovým jednotkám.",
"endpoint": "Koncový bod na použitie vývojového alebo lokálneho zdroja s https://. Predvolená hodnota je api.pirateweather.net"
},
"description": "Nastavte integráciu Pirate Weather. Pre vygenerovanie API kľúča choďte na pirateweather.net",
"data_description": {
"forecast": "napr. '0,1,4'.\n Pre odstránenie existujúcich senzorov napíšte 'None' (bez úvodzoviek).",
"hourly_forecast": "napr. '0,1,4'.\n Pre odstránenie existujúcich senzorov napíšte 'None' (bez úvodzoviek)."
}
}
}
}
}
+471
View File
@@ -0,0 +1,471 @@
"""Support for the Pirate Weather service."""
from __future__ import annotations
import logging
import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT,
ATTR_CONDITION_CLOUDY,
ATTR_CONDITION_EXCEPTIONAL,
ATTR_CONDITION_FOG,
ATTR_CONDITION_HAIL,
ATTR_CONDITION_LIGHTNING,
ATTR_CONDITION_PARTLYCLOUDY,
ATTR_CONDITION_RAINY,
ATTR_CONDITION_SNOWY,
ATTR_CONDITION_SNOWY_RAINY,
ATTR_CONDITION_SUNNY,
ATTR_CONDITION_WINDY,
PLATFORM_SCHEMA,
Forecast,
SingleCoordinatorWeatherEntity,
WeatherEntityFeature,
)
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import (
CONF_API_KEY,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_MODE,
CONF_NAME,
CONF_SCAN_INTERVAL,
UnitOfLength,
UnitOfPrecipitationDepth,
UnitOfPressure,
UnitOfSpeed,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import DiscoveryInfoType
from homeassistant.util.dt import utc_from_timestamp
from .const import (
CONF_UNITS,
DEFAULT_NAME,
DEFAULT_SCAN_INTERVAL,
DOMAIN,
ENTRY_WEATHER_COORDINATOR,
FORECAST_MODES,
PW_PLATFORM,
PW_PLATFORMS,
PW_PREVPLATFORM,
PW_ROUND,
)
from .weather_update_coordinator import WeatherUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Powered by Pirate Weather"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_LATITUDE): cv.latitude,
vol.Optional(CONF_LONGITUDE): cv.longitude,
vol.Optional(PW_PLATFORM): cv.string,
vol.Optional(PW_PREVPLATFORM): cv.string,
vol.Optional(CONF_MODE, default="hourly"): vol.In(FORECAST_MODES),
vol.Optional(CONF_UNITS): vol.In(["si", "us", "ca", "uk", "uk2"]),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): cv.time_period,
}
)
MAP_CONDITION = {
"clear-day": ATTR_CONDITION_SUNNY,
"clear-night": ATTR_CONDITION_CLEAR_NIGHT,
"rain": ATTR_CONDITION_RAINY,
"snow": ATTR_CONDITION_SNOWY,
"sleet": ATTR_CONDITION_SNOWY_RAINY,
"wind": ATTR_CONDITION_WINDY,
"fog": ATTR_CONDITION_FOG,
"cloudy": ATTR_CONDITION_CLOUDY,
"partly-cloudy-day": ATTR_CONDITION_PARTLYCLOUDY,
"partly-cloudy-night": ATTR_CONDITION_PARTLYCLOUDY,
"hail": ATTR_CONDITION_HAIL,
"thunderstorm": ATTR_CONDITION_LIGHTNING,
"tornado": ATTR_CONDITION_EXCEPTIONAL,
"none": ATTR_CONDITION_EXCEPTIONAL,
"mixed": ATTR_CONDITION_SNOWY_RAINY,
}
WEATHER_UNITS = {
"si": {
"temperature": UnitOfTemperature.CELSIUS,
"wind_speed": UnitOfSpeed.METERS_PER_SECOND,
"pressure": UnitOfPressure.MBAR,
"precipitation": UnitOfPrecipitationDepth.MILLIMETERS,
"visibility": UnitOfLength.KILOMETERS,
},
"us": {
"temperature": UnitOfTemperature.FAHRENHEIT,
"wind_speed": UnitOfSpeed.MILES_PER_HOUR,
"pressure": UnitOfPressure.MBAR,
"precipitation": UnitOfPrecipitationDepth.INCHES,
"visibility": UnitOfLength.MILES,
},
"ca": {
"temperature": UnitOfTemperature.CELSIUS,
"wind_speed": UnitOfSpeed.KILOMETERS_PER_HOUR,
"pressure": UnitOfPressure.MBAR,
"precipitation": UnitOfPrecipitationDepth.MILLIMETERS,
"visibility": UnitOfLength.KILOMETERS,
},
"uk": {
"temperature": UnitOfTemperature.CELSIUS,
"wind_speed": UnitOfSpeed.MILES_PER_HOUR,
"pressure": UnitOfPressure.MBAR,
"precipitation": UnitOfPrecipitationDepth.MILLIMETERS,
"visibility": UnitOfLength.MILES,
},
"uk2": {
"temperature": UnitOfTemperature.CELSIUS,
"wind_speed": UnitOfSpeed.MILES_PER_HOUR,
"pressure": UnitOfPressure.MBAR,
"precipitation": UnitOfPrecipitationDepth.MILLIMETERS,
"visibility": UnitOfLength.MILES,
},
}
CONF_UNITS = "units"
DEFAULT_NAME = "Pirate Weather"
async def async_setup_platform(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Import the platform into a config entry."""
_LOGGER.warning(
"Configuration of Pirate Weather (weather entity) in YAML is deprecated "
"Your existing configuration has been imported into the UI automatically "
"and can be safely removed from your configuration.yaml file"
)
# Add source to config
config_entry[PW_PLATFORM] = [PW_PLATFORMS[1]]
# Set as no rounding for compatability
config_entry[PW_ROUND] = "No"
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=config_entry
)
)
def _get_precip(forecast, unit_system: str) -> float | None:
"""Get and convert precipitation accumulation."""
precip = forecast.d.get("precipAccumulation")
if precip is not None and unit_system != "us":
return precip * 10
return precip
def _map_daily_forecast(forecast, unit_system) -> Forecast:
precip = _get_precip(forecast, unit_system)
return {
"datetime": utc_from_timestamp(forecast.d.get("time")).isoformat(),
"condition": MAP_CONDITION.get(forecast.d.get("icon")),
"native_temperature": forecast.d.get("temperatureHigh"),
"native_templow": forecast.d.get("temperatureLow"),
"native_precipitation": precip,
"precipitation_probability": round(
forecast.d.get("precipProbability") * 100, 0
),
"humidity": round(forecast.d.get("humidity") * 100, 2),
"cloud_coverage": round(forecast.d.get("cloudCover") * 100, 0),
"native_wind_speed": round(forecast.d.get("windSpeed"), 2),
"native_wind_gust_speed": round(forecast.d.get("windGust"), 2),
"wind_bearing": round(forecast.d.get("windBearing"), 0),
"native_dew_point": forecast.d.get("dewPoint"),
"native_pressure": forecast.d.get("pressure"),
"uv_index": round(forecast.d.get("uvIndex"), 2),
}
def _map_day_night_forecast(
forecast, unit_system, is_day: bool | None = None
) -> Forecast:
precip = _get_precip(forecast, unit_system)
# If caller provided an `is_day` hint (we'll pass parity from the list),
# prefer that. Otherwise fall back to a minimal inference from the icon.
is_daytime: bool | None = is_day
if is_daytime is None:
icon = forecast.d.get("icon")
if isinstance(icon, str):
if "night" in icon:
is_daytime = False
elif "day" in icon:
is_daytime = True
return {
"is_daytime": is_daytime,
"datetime": utc_from_timestamp(forecast.d.get("time")).isoformat(),
"condition": MAP_CONDITION.get(forecast.d.get("icon")),
"native_temperature": forecast.d.get("temperature"),
"native_apparent_temperature": forecast.d.get("apparentTemperature"),
"native_dew_point": forecast.d.get("dewPoint"),
"native_pressure": forecast.d.get("pressure"),
"native_wind_speed": round(forecast.d.get("windSpeed"), 2),
"wind_bearing": round(forecast.d.get("windBearing"), 0),
"native_wind_gust_speed": round(forecast.d.get("windGust"), 2),
"humidity": round(forecast.d.get("humidity") * 100, 2),
"native_precipitation": precip,
"precipitation_probability": round(
forecast.d.get("precipProbability") * 100, 0
),
"cloud_coverage": round(forecast.d.get("cloudCover") * 100, 0),
"uv_index": round(forecast.d.get("uvIndex"), 2),
}
def _map_hourly_forecast(forecast, unit_system) -> Forecast:
precip = _get_precip(forecast, unit_system)
return {
"datetime": utc_from_timestamp(forecast.d.get("time")).isoformat(),
"condition": MAP_CONDITION.get(forecast.d.get("icon")),
"native_temperature": forecast.d.get("temperature"),
"native_apparent_temperature": forecast.d.get("apparentTemperature"),
"native_dew_point": forecast.d.get("dewPoint"),
"native_pressure": forecast.d.get("pressure"),
"native_wind_speed": round(forecast.d.get("windSpeed"), 2),
"wind_bearing": round(forecast.d.get("windBearing"), 0),
"native_wind_gust_speed": round(forecast.d.get("windGust"), 2),
"humidity": round(forecast.d.get("humidity") * 100, 2),
"native_precipitation": precip,
"precipitation_probability": round(
forecast.d.get("precipProbability") * 100, 0
),
"cloud_coverage": round(forecast.d.get("cloudCover") * 100, 0),
"uv_index": round(forecast.d.get("uvIndex"), 2),
}
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Pirate Weather entity based on a config entry."""
domain_data = hass.data[DOMAIN][config_entry.entry_id]
name = domain_data[CONF_NAME]
weather_coordinator = domain_data[ENTRY_WEATHER_COORDINATOR]
forecast_mode = domain_data[CONF_MODE]
unique_id = f"{config_entry.unique_id}"
# Round Output
outputRound = domain_data[PW_ROUND]
pw_weather = PirateWeather(
name, unique_id, forecast_mode, weather_coordinator, outputRound
)
async_add_entities([pw_weather], False)
# _LOGGER.info(pw_weather.__dict__)
class PirateWeather(SingleCoordinatorWeatherEntity[WeatherUpdateCoordinator]):
"""Implementation of an Pirate Weather sensor."""
_attr_attribution = ATTRIBUTION
_attr_should_poll = False
_attr_supported_features = (
WeatherEntityFeature.FORECAST_DAILY
| WeatherEntityFeature.FORECAST_TWICE_DAILY
| WeatherEntityFeature.FORECAST_HOURLY
)
def __init__(
self,
name: str,
unique_id,
forecast_mode: str,
weather_coordinator: WeatherUpdateCoordinator,
outputRound: str,
) -> None:
"""Initialize the sensor."""
super().__init__(weather_coordinator)
self._attr_name = name
self._weather_coordinator = weather_coordinator
self._name = name
self._mode = forecast_mode
self._unique_id = unique_id
self._ds_data = self._weather_coordinator.data
self._ds_currently = self._weather_coordinator.data.currently()
self._ds_hourly = self._weather_coordinator.data.hourly()
self._ds_daily = self._weather_coordinator.data.daily()
self.outputRound = outputRound
units = WEATHER_UNITS.get(
self._weather_coordinator.requested_units, WEATHER_UNITS["si"]
)
self._attr_native_temperature_unit = units["temperature"]
self._attr_native_wind_speed_unit = units["wind_speed"]
self._attr_native_pressure_unit = units["pressure"]
self._attr_native_precipitation_unit = units["precipitation"]
self._attr_native_visibility_unit = units["visibility"]
@property
def unique_id(self):
"""Return a unique_id for this entity."""
return self._unique_id
@property
def available(self):
"""Return if weather data is available from Pirate Weather."""
return self._weather_coordinator.data is not None
@property
def attribution(self):
"""Return the attribution."""
return ATTRIBUTION
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def native_temperature(self):
"""Return the temperature."""
temperature = self._weather_coordinator.data.currently().d.get("temperature")
return round(temperature, 2) if temperature != -999 else None
@property
def native_apparent_temperature(self):
"""Return the apparent temperature."""
native_apparent_temperature = self._weather_coordinator.data.currently().d.get(
"apparentTemperature"
)
return (
round(native_apparent_temperature, 2)
if native_apparent_temperature != -999
else None
)
@property
def cloud_coverage(self):
"""Return the cloud coverage."""
cloudCover = (
self._weather_coordinator.data.currently().d.get("cloudCover") * 100.0
)
return round(cloudCover, 2) if cloudCover != -999 else None
@property
def humidity(self):
"""Return the humidity."""
humidity = self._weather_coordinator.data.currently().d.get("humidity") * 100.0
return round(humidity, 2) if humidity != -999 else None
@property
def native_dew_point(self):
"""Return the dew point."""
native_dew_point = self._weather_coordinator.data.currently().d.get("dewPoint")
return round(native_dew_point, 2) if native_dew_point != -999 else None
@property
def native_wind_speed(self):
"""Return the wind speed."""
windspeed = self._weather_coordinator.data.currently().d.get("windSpeed")
return round(windspeed, 2) if windspeed != -999 else None
@property
def native_wind_gust_speed(self):
"""Return the wind gust speed."""
windGust = self._weather_coordinator.data.currently().d.get("windGust")
return round(windGust, 2) if windGust != -999 else None
@property
def wind_bearing(self):
"""Return the wind bearing."""
windBearing = self._weather_coordinator.data.currently().d.get("windBearing")
return windBearing if windBearing != -999 else None
@property
def ozone(self):
"""Return the ozone level."""
ozone = self._weather_coordinator.data.currently().d.get("ozone")
return round(ozone, 2) if ozone != -999 else None
@property
def native_pressure(self):
"""Return the pressure."""
pressure = self._weather_coordinator.data.currently().d.get("pressure")
return round(pressure, 2) if pressure != -999 else None
@property
def native_visibility(self):
"""Return the visibility."""
visibility = self._weather_coordinator.data.currently().d.get("visibility")
return round(visibility, 2) if visibility != -999 else None
@property
def condition(self):
"""Return the weather condition."""
return MAP_CONDITION.get(
self._weather_coordinator.data.currently().d.get("icon")
)
@callback
def _async_forecast_daily(self) -> list[Forecast] | None:
"""Return the daily forecast."""
daily_forecast = self._weather_coordinator.data.daily().data
if not daily_forecast:
return None
return [
_map_daily_forecast(f, self._weather_coordinator.requested_units)
for f in daily_forecast
]
@callback
def _async_forecast_twice_daily(self) -> list[Forecast] | None:
"""Return the twice daily forecast."""
day_night_forecast = self._weather_coordinator.data.day_night().data
if not day_night_forecast:
_LOGGER.debug("No twice daily forecast")
return None
# The API returns twice-daily blocks in alternating order: day, night,
# day, night, ... so infer daytime by index parity (even index = day).
return [
_map_day_night_forecast(
f, self._weather_coordinator.requested_units, (i % 2) == 0
)
for i, f in enumerate(day_night_forecast)
]
@callback
def _async_forecast_hourly(self) -> list[Forecast] | None:
"""Return the hourly forecast."""
hourly_forecast = self._weather_coordinator.data.hourly().data
if not hourly_forecast:
return None
return [
_map_hourly_forecast(f, self._weather_coordinator.requested_units)
for f in hourly_forecast
]
@@ -0,0 +1,115 @@
"""Weather data coordinator for the Pirate Weather service."""
import logging
import async_timeout
from aiohttp import ClientError
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import (
DOMAIN,
)
from .forecast_models import Forecast
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Powered by Pirate Weather"
class WeatherUpdateCoordinator(DataUpdateCoordinator):
"""Weather data update coordinator."""
def __init__(
self,
api_key,
latitude,
longitude,
scan_interval,
language,
endpoint,
units,
hass,
config_entry: ConfigEntry,
models: str | None,
):
"""Initialize coordinator."""
self._api_key = api_key
self.latitude = latitude
self.longitude = longitude
self.scan_interval = scan_interval
self.language = language
self.endpoint = endpoint
self.requested_units = units or "si"
self.models = models
self.data = None
self.currently = None
self.hourly = None
self.daily = None
self._connect_error = False
super().__init__(
hass,
_LOGGER,
name=DOMAIN,
update_interval=scan_interval,
config_entry=config_entry,
)
async def _async_update_data(self):
"""Update the data."""
data = {}
async with async_timeout.timeout(60):
try:
data = await self._get_pw_weather()
except ClientError as err:
raise UpdateFailed(f"Error communicating with API: {err}") from err
return data
async def _get_pw_weather(self):
"""Poll weather data from PW."""
if self.latitude == 0.0:
requestLatitude = self.hass.config.latitude
else:
requestLatitude = self.latitude
if self.longitude == 0.0:
requestLongitude = self.hass.config.longitude
else:
requestLongitude = self.longitude
_LOGGER.debug("Request coordinates: %s, %s", requestLatitude, requestLongitude)
forecastString = (
self.endpoint
+ "/forecast/"
+ self._api_key
+ "/"
+ str(requestLatitude)
+ ","
+ str(requestLongitude)
+ "?units="
+ self.requested_units
+ "&extend=hourly"
+ "&version=2"
+ "&lang="
+ self.language
+ "&include=day_night_forecast"
)
if self.models:
exclusions = ",".join(
m.strip() for m in self.models.split(",") if m.strip()
)
if exclusions:
forecastString += "&exclude=" + exclusions
session = async_get_clientsession(self.hass)
async with session.get(forecastString) as resp:
resp.raise_for_status()
jsonText = await resp.json()
headers = resp.headers
_LOGGER.debug("Pirate Weather data update from: %s", self.endpoint)
return Forecast(jsonText, resp, headers)