"""Support for magicseaweed data from magicseaweed.com.""" from datetime import timedelta import logging import magicseaweed import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, CONF_MONITORED_CONDITIONS, CONF_NAME, ) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) CONF_HOURS = "hours" CONF_SPOT_ID = "spot_id" CONF_UNITS = "units" DEFAULT_UNIT = "us" DEFAULT_NAME = "MSW" DEFAULT_ATTRIBUTION = "Data provided by magicseaweed.com" ICON = "mdi:waves" HOURS = ["12AM", "3AM", "6AM", "9AM", "12PM", "3PM", "6PM", "9PM"] SENSOR_TYPES = { "max_breaking_swell": ["Max"], "min_breaking_swell": ["Min"], "swell_forecast": ["Forecast"], } UNITS = ["eu", "uk", "us"] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_MONITORED_CONDITIONS): vol.All( cv.ensure_list, [vol.In(SENSOR_TYPES)] ), vol.Required(CONF_API_KEY): cv.string, vol.Required(CONF_SPOT_ID): vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_HOURS, default=None): vol.All( cv.ensure_list, [vol.In(HOURS)] ), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_UNITS): vol.In(UNITS), } ) # Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=30) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Magicseaweed sensor.""" name = config.get(CONF_NAME) spot_id = config[CONF_SPOT_ID] api_key = config[CONF_API_KEY] hours = config.get(CONF_HOURS) if CONF_UNITS in config: units = config.get(CONF_UNITS) elif hass.config.units.is_metric: units = UNITS[0] else: units = UNITS[2] forecast_data = MagicSeaweedData(api_key=api_key, spot_id=spot_id, units=units) forecast_data.update() # If connection failed don't setup platform. if forecast_data.currently is None or forecast_data.hourly is None: return sensors = [] for variable in config[CONF_MONITORED_CONDITIONS]: sensors.append(MagicSeaweedSensor(forecast_data, variable, name, units)) if "forecast" not in variable and hours is not None: for hour in hours: sensors.append( MagicSeaweedSensor(forecast_data, variable, name, units, hour) ) add_entities(sensors, True) class MagicSeaweedSensor(Entity): """Implementation of a MagicSeaweed sensor.""" def __init__(self, forecast_data, sensor_type, name, unit_system, hour=None): """Initialize the sensor.""" self.client_name = name self.data = forecast_data self.hour = hour self.type = sensor_type self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} self._name = SENSOR_TYPES[sensor_type][0] self._icon = None self._state = None self._unit_system = unit_system self._unit_of_measurement = None @property def name(self): """Return the name of the sensor.""" if self.hour is None and "forecast" in self.type: return f"{self.client_name} {self._name}" if self.hour is None: return f"Current {self.client_name} {self._name}" return f"{self.hour} {self.client_name} {self._name}" @property def state(self): """Return the state of the sensor.""" return self._state @property def unit_system(self): """Return the unit system of this entity.""" return self._unit_system @property def unit_of_measurement(self): """Return the unit of measurement of this entity, if any.""" return self._unit_of_measurement @property def icon(self): """Return the entity weather icon, if any.""" return ICON @property def device_state_attributes(self): """Return the state attributes.""" return self._attrs def update(self): """Get the latest data from Magicseaweed and updates the states.""" self.data.update() if self.hour is None: forecast = self.data.currently else: forecast = self.data.hourly[self.hour] self._unit_of_measurement = forecast.swell_unit if self.type == "min_breaking_swell": self._state = forecast.swell_minBreakingHeight elif self.type == "max_breaking_swell": self._state = forecast.swell_maxBreakingHeight elif self.type == "swell_forecast": summary = "{} - {}".format( forecast.swell_minBreakingHeight, forecast.swell_maxBreakingHeight ) self._state = summary if self.hour is None: for hour, data in self.data.hourly.items(): occurs = hour hr_summary = "{} - {} {}".format( data.swell_minBreakingHeight, data.swell_maxBreakingHeight, data.swell_unit, ) self._attrs[occurs] = hr_summary if self.type != "swell_forecast": self._attrs.update(forecast.attrs) class MagicSeaweedData: """Get the latest data from MagicSeaweed.""" def __init__(self, api_key, spot_id, units): """Initialize the data object.""" self._msw = magicseaweed.MSW_Forecast(api_key, spot_id, None, units) self.currently = None self.hourly = {} # Apply throttling to methods using configured interval self.update = Throttle(MIN_TIME_BETWEEN_UPDATES)(self._update) def _update(self): """Get the latest data from MagicSeaweed.""" try: forecasts = self._msw.get_future() self.currently = forecasts.data[0] for forecast in forecasts.data[:8]: hour = dt_util.utc_from_timestamp(forecast.localTimestamp).strftime( "%-I%p" ) self.hourly[hour] = forecast except ConnectionError: _LOGGER.error("Unable to retrieve data from Magicseaweed")