diff --git a/.coveragerc b/.coveragerc index 62f710f3680..ea30dec510a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -689,6 +689,7 @@ omit = homeassistant/components/sensor/netdata.py homeassistant/components/sensor/netdata_public.py homeassistant/components/sensor/neurio_energy.py + homeassistant/components/sensor/noaa_tides.py homeassistant/components/sensor/nsw_fuel_station.py homeassistant/components/sensor/nut.py homeassistant/components/sensor/nzbget.py diff --git a/homeassistant/components/sensor/noaa_tides.py b/homeassistant/components/sensor/noaa_tides.py new file mode 100644 index 00000000000..8527abf84aa --- /dev/null +++ b/homeassistant/components/sensor/noaa_tides.py @@ -0,0 +1,138 @@ +""" +Support for the NOAA Tides and Currents API. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.noaa_tides/ +""" +from datetime import datetime, timedelta +import logging + +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + ATTR_ATTRIBUTION, CONF_NAME, CONF_TIME_ZONE, CONF_UNIT_SYSTEM) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity + +REQUIREMENTS = ['py_noaa==0.3.0'] + +_LOGGER = logging.getLogger(__name__) + +CONF_STATION_ID = 'station_id' + +DEFAULT_ATTRIBUTION = "Data provided by NOAA" +DEFAULT_NAME = 'NOAA Tides' +DEFAULT_TIMEZONE = 'lst_ldt' + +SCAN_INTERVAL = timedelta(minutes=60) + +TIMEZONES = ['gmt', 'lst', 'lst_ldt'] +UNIT_SYSTEMS = ['english', 'metric'] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_STATION_ID): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_TIME_ZONE, default=DEFAULT_TIMEZONE): vol.In(TIMEZONES), + vol.Optional(CONF_UNIT_SYSTEM): vol.In(UNIT_SYSTEMS), +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the NOAA Tides and Currents sensor.""" + station_id = config[CONF_STATION_ID] + name = config.get(CONF_NAME) + timezone = config.get(CONF_TIME_ZONE) + + if CONF_UNIT_SYSTEM in config: + unit_system = config[CONF_UNIT_SYSTEM] + elif hass.config.units.is_metric: + unit_system = UNIT_SYSTEMS[1] + else: + unit_system = UNIT_SYSTEMS[0] + + noaa_sensor = NOAATidesAndCurrentsSensor( + name, station_id, timezone, unit_system) + + noaa_sensor.update() + if noaa_sensor.data is None: + _LOGGER.error("Unable to setup NOAA Tides Sensor") + return + add_devices([noaa_sensor], True) + + +class NOAATidesAndCurrentsSensor(Entity): + """Representation of a NOAA Tides and Currents sensor.""" + + def __init__(self, name, station_id, timezone, unit_system): + """Initialize the sensor.""" + self._name = name + self._station_id = station_id + self._timezone = timezone + self._unit_system = unit_system + self.data = None + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def device_state_attributes(self): + """Return the state attributes of this device.""" + attr = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} + if self.data is None: + return attr + if self.data['hi_lo'][1] == 'H': + attr['high_tide_time'] = \ + self.data.index[1].strftime('%Y-%m-%dT%H:%M') + attr['high_tide_height'] = self.data['predicted_wl'][1] + attr['low_tide_time'] = \ + self.data.index[2].strftime('%Y-%m-%dT%H:%M') + attr['low_tide_height'] = self.data['predicted_wl'][2] + elif self.data['hi_lo'][1] == 'L': + attr['low_tide_time'] = \ + self.data.index[1].strftime('%Y-%m-%dT%H:%M') + attr['low_tide_height'] = self.data['predicted_wl'][1] + attr['high_tide_time'] = \ + self.data.index[2].strftime('%Y-%m-%dT%H:%M') + attr['high_tide_height'] = self.data['predicted_wl'][2] + return attr + + @property + def state(self): + """Return the state of the device.""" + if self.data is None: + return None + api_time = self.data.index[0] + if self.data['hi_lo'][0] == 'H': + tidetime = api_time.strftime('%-I:%M %p') + return "High tide at {}".format(tidetime) + if self.data['hi_lo'][0] == 'L': + tidetime = api_time.strftime('%-I:%M %p') + return "Low tide at {}".format(tidetime) + return None + + def update(self): + """Get the latest data from NOAA Tides and Currents API.""" + from py_noaa import coops # pylint: disable=import-error + begin = datetime.now() + delta = timedelta(days=2) + end = begin + delta + try: + df_predictions = coops.get_data( + begin_date=begin.strftime("%Y%m%d %H:%M"), + end_date=end.strftime("%Y%m%d %H:%M"), + stationid=self._station_id, + product="predictions", + datum="MLLW", + interval="hilo", + units=self._unit_system, + time_zone=self._timezone) + self.data = df_predictions.head() + _LOGGER.debug("Data = %s", self.data) + _LOGGER.debug("Recent Tide data queried with start time set to %s", + begin.strftime("%m-%d-%Y %H:%M")) + except ValueError as err: + _LOGGER.error("Check NOAA Tides and Currents: %s", err.args) + self.data = None diff --git a/requirements_all.txt b/requirements_all.txt index 0c0409612c8..d29dff255a2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -738,6 +738,9 @@ pyTibber==0.4.1 # homeassistant.components.switch.dlink pyW215==0.6.0 +# homeassistant.components.sensor.noaa_tides +# py_noaa==0.3.0 + # homeassistant.components.cover.ryobi_gdo py_ryobi_gdo==0.0.10 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 61f39e3387f..e709abfc10b 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -34,6 +34,7 @@ COMMENT_REQUIREMENTS = ( 'credstash', 'bme680', 'homekit', + 'py_noaa', ) TEST_REQUIREMENTS = (