Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Magicseaweed API support #15132

Merged
merged 12 commits into from
Jul 27, 2018
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,7 @@ omit =
homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/luftdaten.py
homeassistant/components/sensor/lyft.py
homeassistant/components/sensor/magicseaweed.py
homeassistant/components/sensor/metoffice.py
homeassistant/components/sensor/miflora.py
homeassistant/components/sensor/mitemp_bt.py
Expand Down
201 changes: 201 additions & 0 deletions homeassistant/components/sensor/magicseaweed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
"""
Support for magicseaweed data from magicseaweed.com.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.magicseaweed/
"""
from datetime import timedelta
import logging
import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_API_KEY, CONF_NAME, CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION)
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle

REQUIREMENTS = ['magicseaweed==1.0.0']

_LOGGER = logging.getLogger(__name__)

CONF_HOURS = 'hours'
CONF_SPOT_ID = 'spot_id'
CONF_UNITS = 'units'
CONF_UPDATE_INTERVAL = 'update_interval'

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not used?



def setup_platform(hass, config, add_devices, 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' in variable and hours is not None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the logic correct now? It was if 'forecast' in variable before but then the else block had the body that we're using here now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, it is not. Thank you for your attention to detail. I was in a rush to complete before anything was released. I will fix.

for hour in hours:
sensors.append(MagicSeaweedSensor(
forecast_data, variable, name, units, hour))
add_devices(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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a small thing, but unless you have a reason for doing so, keep your private properties together and your public properties together.


@property
def name(self):
"""Return the name of the sensor."""
if self.hour is None and 'forecast' in self.type:
return "{} {}".format(self.client_name, self._name)
if self.hour is None:
return "Current {} {}".format(self.client_name, self._name)
return "{} {} {}".format(
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."""
import magicseaweed
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")
3 changes: 3 additions & 0 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,9 @@ luftdaten==0.1.3
# homeassistant.components.sensor.lyft
lyft_rides==0.2

# homeassistant.components.sensor.magicseaweed
magicseaweed==1.0.0

# homeassistant.components.matrix
matrix-client==0.2.0

Expand Down