Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d57bedc
Added Environment Canada weather platform
michaeldavie Feb 16, 2019
73f8ce1
Migrate to new folder structure
michaeldavie Feb 21, 2019
795be43
Fix updates
michaeldavie Feb 22, 2019
4610c1d
Fix updates again
michaeldavie Feb 22, 2019
083efdd
Bump env_canada to 0.0.4
michaeldavie Feb 25, 2019
fdea590
Change daily forecast timestamp and high/low test
michaeldavie Feb 26, 2019
eb1fe67
Bump env_canada to 0.0.5
michaeldavie Mar 2, 2019
d95ac29
Break alerts into multiple sensors, bump env_canada to 0.0.6
michaeldavie Mar 4, 2019
27ea8ae
Bump env_canada to 0.0.7
michaeldavie Mar 5, 2019
2f119c6
Added Environment Canada weather platform
michaeldavie Feb 16, 2019
c24dbe9
Migrate to new folder structure
michaeldavie Feb 21, 2019
28653e3
Bump env_canada to 0.0.4
michaeldavie Feb 25, 2019
c2f5e40
Bump env_canada to 0.0.4 in requirements_all.txt
michaeldavie Feb 25, 2019
67bb466
Change daily forecast timestamp and high/low test
michaeldavie Feb 26, 2019
664f9c0
Remove blank line
michaeldavie Mar 9, 2019
53d449a
Remove 'ec' sensor prefix, bump env_canada to 0.0.8
michaeldavie Mar 16, 2019
b390c57
Corrections
michaeldavie Apr 12, 2019
953afa9
Change to manifests.json
michaeldavie Apr 13, 2019
6ea5d01
Add docstring to __init.py__
michaeldavie Apr 13, 2019
e7848e2
Update CODEOWNERS
michaeldavie Apr 13, 2019
88d6122
pylint correction
michaeldavie Apr 14, 2019
8e2250e
pylint correction
michaeldavie Apr 14, 2019
39a7523
Add alert details, bump env_canada to 0.0.9
michaeldavie Apr 20, 2019
786b7aa
Update requirements_all.txt
michaeldavie Apr 20, 2019
75ffd4c
Update .coveragerc
michaeldavie Apr 24, 2019
cc7259a
Bump env_canada to 0.0.10
michaeldavie May 10, 2019
fea2b8d
Update requirements_all.txt
michaeldavie May 10, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ omit =
homeassistant/components/enocean/*
homeassistant/components/enphase_envoy/sensor.py
homeassistant/components/entur_public_transport/*
homeassistant/components/environment_canada/*
homeassistant/components/envirophat/sensor.py
homeassistant/components/envisalink/*
homeassistant/components/ephember/climate.py
Expand Down
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ homeassistant/components/eight_sleep/* @mezz64
homeassistant/components/emby/* @mezz64
homeassistant/components/enigma2/* @fbradyirl
homeassistant/components/enocean/* @bdurrer
homeassistant/components/environment_canada/* @michaeldavie
homeassistant/components/ephember/* @ttroy50
homeassistant/components/epsonworkforce/* @ThaStealth
homeassistant/components/eq3btsmart/* @rytilahti
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/environment_canada/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""A component for Environment Canada weather."""
101 changes: 101 additions & 0 deletions homeassistant/components/environment_canada/camera.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""
Support for the Environment Canada radar imagery.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.environment_canada/
"""
import datetime
import logging

import voluptuous as vol

from homeassistant.components.camera import (
PLATFORM_SCHEMA, Camera)
from homeassistant.const import (
CONF_NAME, CONF_LATITUDE, CONF_LONGITUDE, ATTR_ATTRIBUTION)
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv

_LOGGER = logging.getLogger(__name__)

ATTR_STATION = 'station'
ATTR_LOCATION = 'location'

CONF_ATTRIBUTION = "Data provided by Environment Canada"
CONF_STATION = 'station'
CONF_LOOP = 'loop'
CONF_PRECIP_TYPE = 'precip_type'

MIN_TIME_BETWEEN_UPDATES = datetime.timedelta(minutes=10)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_LOOP, default=True): cv.boolean,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_STATION): cv.string,
vol.Inclusive(CONF_LATITUDE, 'latlon'): cv.latitude,
vol.Inclusive(CONF_LONGITUDE, 'latlon'): cv.longitude,
vol.Optional(CONF_PRECIP_TYPE): ['RAIN', 'SNOW'],
})


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Environment Canada camera."""
from env_canada import ECRadar

if config.get(CONF_STATION):
radar_object = ECRadar(station_id=config[CONF_STATION],
precip_type=config.get(CONF_PRECIP_TYPE))
elif config.get(CONF_LATITUDE) and config.get(CONF_LONGITUDE):
radar_object = ECRadar(coordinates=(config[CONF_LATITUDE],
config[CONF_LONGITUDE]),
precip_type=config.get(CONF_PRECIP_TYPE))
else:
radar_object = ECRadar(coordinates=(hass.config.latitude,
hass.config.longitude),
precip_type=config.get(CONF_PRECIP_TYPE))

add_devices([ECCamera(radar_object, config.get(CONF_NAME))], True)


class ECCamera(Camera):
"""Implementation of an Environment Canada radar camera."""

def __init__(self, radar_object, camera_name):
"""Initialize the camera."""
super().__init__()

self.radar_object = radar_object
self.camera_name = camera_name
self.content_type = 'image/gif'
self.image = None

def camera_image(self):
"""Return bytes of camera image."""
self.update()
return self.image

@property
def name(self):
"""Return the name of the camera."""
if self.camera_name is not None:
return self.camera_name
return ' '.join([self.radar_object.station_name, 'Radar'])

@property
def device_state_attributes(self):
"""Return the state attributes of the device."""
attr = {
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
ATTR_LOCATION: self.radar_object.station_name,
ATTR_STATION: self.radar_object.station_code
}

return attr

@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Update radar image."""
if CONF_LOOP:
self.image = self.radar_object.get_loop()
else:
self.image = self.radar_object.get_latest_frame()
12 changes: 12 additions & 0 deletions homeassistant/components/environment_canada/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"domain": "environment_canada",
"name": "Environment Canada",
"documentation": "https://www.home-assistant.io/components/environment_canada",
"requirements": [
"env_canada==0.0.10"
],
"dependencies": [],
"codeowners": [
"@michaeldavie"
]
}
178 changes: 178 additions & 0 deletions homeassistant/components/environment_canada/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
"""
Support for the Environment Canada weather service.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.environment_canada/
"""
import datetime
import logging
import re

import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_MONITORED_CONDITIONS, TEMP_CELSIUS, CONF_NAME, CONF_LATITUDE,
CONF_LONGITUDE, ATTR_ATTRIBUTION, ATTR_LOCATION, ATTR_HIDDEN)
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import homeassistant.util.dt as dt
import homeassistant.helpers.config_validation as cv

_LOGGER = logging.getLogger(__name__)

ATTR_UPDATED = 'updated'
ATTR_STATION = 'station'
ATTR_DETAIL = 'alert detail'
ATTR_TIME = 'alert time'

CONF_ATTRIBUTION = "Data provided by Environment Canada"
CONF_STATION = 'station'

MIN_TIME_BETWEEN_UPDATES = datetime.timedelta(minutes=10)

SENSOR_TYPES = {
'temperature': {'name': 'Temperature',
'unit': TEMP_CELSIUS},
'dewpoint': {'name': 'Dew Point',
'unit': TEMP_CELSIUS},
'wind_chill': {'name': 'Wind Chill',
'unit': TEMP_CELSIUS},
'humidex': {'name': 'Humidex',
'unit': TEMP_CELSIUS},
'pressure': {'name': 'Pressure',
'unit': 'kPa'},
'tendency': {'name': 'Tendency'},
'humidity': {'name': 'Humidity',
'unit': '%'},
'visibility': {'name': 'Visibility',
'unit': 'km'},
'condition': {'name': 'Condition'},
'wind_speed': {'name': 'Wind Speed',
'unit': 'km/h'},
'wind_gust': {'name': 'Wind Gust',
'unit': 'km/h'},
'wind_dir': {'name': 'Wind Direction'},
'high_temp': {'name': 'High Temperature',
'unit': TEMP_CELSIUS},
'low_temp': {'name': 'Low Temperature',
'unit': TEMP_CELSIUS},
'pop': {'name': 'Chance of Precip.',
'unit': '%'},
'warnings': {'name': 'Warnings'},
'watches': {'name': 'Watches'},
'advisories': {'name': 'Advisories'},
'statements': {'name': 'Statements'},
'endings': {'name': 'Ended'}
}


def validate_station(station):
"""Check that the station ID is well-formed."""
if station is None:
return
if not re.fullmatch(r'[A-Z]{2}/s0000\d{3}', station):
raise vol.error.Invalid('Station ID must be of the form "XX/s0000###"')
return station


PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_STATION): validate_station,
vol.Inclusive(CONF_LATITUDE, 'latlon'): cv.latitude,
vol.Inclusive(CONF_LONGITUDE, 'latlon'): cv.longitude,
})


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Environment Canada sensor."""
from env_canada import ECData

if config.get(CONF_STATION):
ec_data = ECData(station_id=config[CONF_STATION])
elif config.get(CONF_LATITUDE) and config.get(CONF_LONGITUDE):
ec_data = ECData(coordinates=(config[CONF_LATITUDE],
config[CONF_LONGITUDE]))
else:
ec_data = ECData(coordinates=(hass.config.latitude,
hass.config.longitude))

add_devices([ECSensor(sensor_type, ec_data, config.get(CONF_NAME))
for sensor_type in config[CONF_MONITORED_CONDITIONS]],
True)


class ECSensor(Entity):
"""Implementation of an Environment Canada sensor."""

def __init__(self, sensor_type, ec_data, platform_name):
"""Initialize the sensor."""
self.sensor_type = sensor_type
self.ec_data = ec_data
self.platform_name = platform_name
self._state = None
self._attr = None

@property
def name(self):
"""Return the name of the sensor."""
if self.platform_name is None:
return SENSOR_TYPES[self.sensor_type]['name']

return ' '.join([self.platform_name,
SENSOR_TYPES[self.sensor_type]['name']])

@property
def state(self):
"""Return the state of the sensor."""
return self._state

@property
def device_state_attributes(self):
"""Return the state attributes of the device."""
return self._attr

@property
def unit_of_measurement(self):
"""Return the units of measurement."""
return SENSOR_TYPES[self.sensor_type].get('unit')

@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Update current conditions."""
self.ec_data.update()
self.ec_data.conditions.update(self.ec_data.alerts)

self._attr = {}

sensor_data = self.ec_data.conditions.get(self.sensor_type)
if isinstance(sensor_data, list):
self._state = ' | '.join([str(s.get('title'))
for s in sensor_data])
self._attr.update({
ATTR_DETAIL: ' | '.join([str(s.get('detail'))
for s in sensor_data]),
ATTR_TIME: ' | '.join([str(s.get('date'))
for s in sensor_data])
})
else:
self._state = sensor_data

timestamp = self.ec_data.conditions.get('timestamp')
if timestamp:
updated_utc = datetime.datetime.strptime(timestamp, '%Y%m%d%H%M%S')
updated_local = dt.as_local(updated_utc).isoformat()
else:
updated_local = None

hidden = bool(self._state is None or self._state == '')

self._attr.update({
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
ATTR_UPDATED: updated_local,
ATTR_LOCATION: self.ec_data.conditions.get('location'),
ATTR_STATION: self.ec_data.conditions.get('station'),
ATTR_HIDDEN: hidden
})
Loading