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
+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
]