Skip to content

Commit

Permalink
Adds integration for Plaato Airlock (#23727)
Browse files Browse the repository at this point in the history
* Adds integration for Plaato Airlock

* Updates codeowners and coveragerc

* Fixes lint errors

* Fixers lint check error

* Removed sv translation file

* Adds en translation file

* Sets config flow to true in manifest

* Moves config flow and domain to seperate files

* Fixes lint errors

* Runs hassfest to regenerate config_flows.py

* Adds should poll property and fixes for loop

* Only log a warning when webhook data was broken

* Fixes static test failure

* Moves state update from async_update to state prop

* Unsubscribes the dispatch signal listener

* Update sensor.py
  • Loading branch information
JohNan authored and balloob committed Jun 18, 2019
1 parent f3e4e8d commit 266b3bc
Show file tree
Hide file tree
Showing 10 changed files with 327 additions and 0 deletions.
1 change: 1 addition & 0 deletions .coveragerc
Expand Up @@ -451,6 +451,7 @@ omit =
homeassistant/components/ping/device_tracker.py
homeassistant/components/pioneer/media_player.py
homeassistant/components/pjlink/media_player.py
homeassistant/components/plaato/*
homeassistant/components/plex/media_player.py
homeassistant/components/plex/sensor.py
homeassistant/components/plum_lightpad/*
Expand Down
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Expand Up @@ -191,6 +191,7 @@ homeassistant/components/panel_iframe/* @home-assistant/frontend
homeassistant/components/persistent_notification/* @home-assistant/core
homeassistant/components/philips_js/* @elupus
homeassistant/components/pi_hole/* @fabaff
homeassistant/components/plaato/* @JohNan
homeassistant/components/plant/* @ChristianKuehnel
homeassistant/components/point/* @fredrike
homeassistant/components/ps4/* @ktnrg45
Expand Down
18 changes: 18 additions & 0 deletions homeassistant/components/plaato/.translations/en.json
@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from Plaato Airlock.",
"one_instance_allowed": "Only a single instance is necessary."
},
"create_entry": {
"default": "To send events to Home Assistant, you will need to setup the webhook feature in Plaato Airlock.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details."
},
"step": {
"user": {
"description": "Are you sure you want to set up the Plaato Airlock?",
"title": "Set up the Plaato Webhook"
}
},
"title": "Plaato Airlock"
}
}
126 changes: 126 additions & 0 deletions homeassistant/components/plaato/__init__.py
@@ -0,0 +1,126 @@
"""Support for Plaato Airlock."""
import logging

from aiohttp import web
import voluptuous as vol

from homeassistant.components.sensor import DOMAIN as SENSOR
from homeassistant.const import (
CONF_WEBHOOK_ID, HTTP_OK,
TEMP_CELSIUS, TEMP_FAHRENHEIT, VOLUME_GALLONS, VOLUME_LITERS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)

DEPENDENCIES = ['webhook']

PLAATO_DEVICE_SENSORS = 'sensors'
PLAATO_DEVICE_ATTRS = 'attrs'

ATTR_DEVICE_ID = 'device_id'
ATTR_DEVICE_NAME = 'device_name'
ATTR_TEMP_UNIT = 'temp_unit'
ATTR_VOLUME_UNIT = 'volume_unit'
ATTR_BPM = 'bpm'
ATTR_TEMP = 'temp'
ATTR_SG = 'sg'
ATTR_OG = 'og'
ATTR_BUBBLES = 'bubbles'
ATTR_ABV = 'abv'
ATTR_CO2_VOLUME = 'co2_volume'
ATTR_BATCH_VOLUME = 'batch_volume'

SENSOR_UPDATE = '{}_sensor_update'.format(DOMAIN)
SENSOR_DATA_KEY = '{}.{}'.format(DOMAIN, SENSOR)

WEBHOOK_SCHEMA = vol.Schema({
vol.Required(ATTR_DEVICE_NAME): cv.string,
vol.Required(ATTR_DEVICE_ID): cv.positive_int,
vol.Required(ATTR_TEMP_UNIT): vol.Any(TEMP_CELSIUS, TEMP_FAHRENHEIT),
vol.Required(ATTR_VOLUME_UNIT): vol.Any(VOLUME_LITERS, VOLUME_GALLONS),
vol.Required(ATTR_BPM): cv.positive_int,
vol.Required(ATTR_TEMP): vol.Coerce(float),
vol.Required(ATTR_SG): vol.Coerce(float),
vol.Required(ATTR_OG): vol.Coerce(float),
vol.Required(ATTR_ABV): vol.Coerce(float),
vol.Required(ATTR_CO2_VOLUME): vol.Coerce(float),
vol.Required(ATTR_BATCH_VOLUME): vol.Coerce(float),
vol.Required(ATTR_BUBBLES): cv.positive_int,
}, extra=vol.ALLOW_EXTRA)


async def async_setup(hass, hass_config):
"""Set up the Plaato component."""
return True


async def async_setup_entry(hass, entry):
"""Configure based on config entry."""
if DOMAIN not in hass.data:
hass.data[DOMAIN] = {}

webhook_id = entry.data[CONF_WEBHOOK_ID]
hass.components.webhook.async_register(
DOMAIN, 'Plaato', webhook_id, handle_webhook)

hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, SENSOR)
)

return True


async def async_unload_entry(hass, entry):
"""Unload a config entry."""
hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
hass.data[SENSOR_DATA_KEY]()

await hass.config_entries.async_forward_entry_unload(entry, SENSOR)
return True


async def handle_webhook(hass, webhook_id, request):
"""Handle incoming webhook from Plaato."""
try:
data = WEBHOOK_SCHEMA(await request.json())
except vol.MultipleInvalid as error:
_LOGGER.warning("An error occurred when parsing webhook data <%s>",
error)
return

device_id = _device_id(data)

attrs = {
ATTR_DEVICE_NAME: data.get(ATTR_DEVICE_NAME),
ATTR_DEVICE_ID: data.get(ATTR_DEVICE_ID),
ATTR_TEMP_UNIT: data.get(ATTR_TEMP_UNIT),
ATTR_VOLUME_UNIT: data.get(ATTR_VOLUME_UNIT)
}

sensors = {
ATTR_TEMP: data.get(ATTR_TEMP),
ATTR_BPM: data.get(ATTR_BPM),
ATTR_SG: data.get(ATTR_SG),
ATTR_OG: data.get(ATTR_OG),
ATTR_ABV: data.get(ATTR_ABV),
ATTR_CO2_VOLUME: data.get(ATTR_CO2_VOLUME),
ATTR_BATCH_VOLUME: data.get(ATTR_BATCH_VOLUME),
ATTR_BUBBLES: data.get(ATTR_BUBBLES)
}

hass.data[DOMAIN][device_id] = {
PLAATO_DEVICE_ATTRS: attrs,
PLAATO_DEVICE_SENSORS: sensors
}

async_dispatcher_send(hass, SENSOR_UPDATE, device_id)

return web.Response(
text="Saving status for {}".format(device_id), status=HTTP_OK)


def _device_id(data):
"""Return name of device sensor."""
return "{}_{}".format(data.get(ATTR_DEVICE_NAME), data.get(ATTR_DEVICE_ID))
11 changes: 11 additions & 0 deletions homeassistant/components/plaato/config_flow.py
@@ -0,0 +1,11 @@
"""Config flow for GPSLogger."""
from homeassistant.helpers import config_entry_flow
from .const import DOMAIN

config_entry_flow.register_webhook_flow(
DOMAIN,
'Webhook',
{
'docs_url': 'https://www.home-assistant.io/components/plaato/'
}
)
3 changes: 3 additions & 0 deletions homeassistant/components/plaato/const.py
@@ -0,0 +1,3 @@
"""Const for GPSLogger."""

DOMAIN = 'plaato'
9 changes: 9 additions & 0 deletions homeassistant/components/plaato/manifest.json
@@ -0,0 +1,9 @@
{
"domain": "plaato",
"name": "Plaato Airlock",
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/plaato",
"dependencies": ["webhook"],
"codeowners": ["@JohNan"],
"requirements": []
}
139 changes: 139 additions & 0 deletions homeassistant/components/plaato/sensor.py
@@ -0,0 +1,139 @@
"""Support for Plaato Airlock sensors."""

import logging

from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity

from . import (
ATTR_ABV, ATTR_BATCH_VOLUME, ATTR_BPM, ATTR_CO2_VOLUME, ATTR_TEMP,
ATTR_TEMP_UNIT, ATTR_VOLUME_UNIT, DOMAIN as PLAATO_DOMAIN,
PLAATO_DEVICE_ATTRS, PLAATO_DEVICE_SENSORS, SENSOR_DATA_KEY, SENSOR_UPDATE)

_LOGGER = logging.getLogger(__name__)


async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the Plaato sensor."""


async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Plaato from a config entry."""
devices = {}

def get_device(device_id):
"""Get a device."""
return hass.data[PLAATO_DOMAIN].get(device_id, False)

def get_device_sensors(device_id):
"""Get device sensors."""
return hass.data[PLAATO_DOMAIN].get(device_id)\
.get(PLAATO_DEVICE_SENSORS)

async def _update_sensor(device_id):
"""Update/Create the sensors."""
if device_id not in devices and get_device(device_id):
entities = []
sensors = get_device_sensors(device_id)

for sensor_type in sensors:
entities.append(PlaatoSensor(device_id, sensor_type))

devices[device_id] = entities

async_add_entities(entities, True)
else:
for entity in devices[device_id]:
entity.async_schedule_update_ha_state()

hass.data[SENSOR_DATA_KEY] = async_dispatcher_connect(
hass, SENSOR_UPDATE, _update_sensor
)

return True


class PlaatoSensor(Entity):
"""Representation of a Sensor."""

def __init__(self, device_id, sensor_type):
"""Initialize the sensor."""
self._device_id = device_id
self._type = sensor_type
self._state = 0
self._name = "{} {}".format(device_id, sensor_type)
self._attributes = None

@property
def name(self):
"""Return the name of the sensor."""
return "{} {}".format(PLAATO_DOMAIN, self._name)

@property
def unique_id(self):
"""Return the unique ID of this sensor."""
return "{}_{}".format(self._device_id, self._type)

@property
def device_info(self):
"""Get device info."""
return {
'identifiers': {
(PLAATO_DOMAIN, self._device_id)
},
'name': self._device_id,
'manufacturer': 'Plaato',
'model': 'Airlock'
}

def get_sensors(self):
"""Get device sensors."""
return self.hass.data[PLAATO_DOMAIN].get(self._device_id)\
.get(PLAATO_DEVICE_SENSORS, False)

def get_sensors_unit_of_measurement(self, sensor_type):
"""Get unit of measurement for sensor of type."""
return self.hass.data[PLAATO_DOMAIN].get(self._device_id)\
.get(PLAATO_DEVICE_ATTRS, []).get(sensor_type, '')

@property
def state(self):
"""Return the state of the sensor."""
sensors = self.get_sensors()
if sensors is False:
_LOGGER.debug("Device with name %s has no sensors.", self.name)
return 0

if self._type == ATTR_ABV:
return round(sensors.get(self._type), 2)
if self._type == ATTR_TEMP:
return round(sensors.get(self._type), 1)
if self._type == ATTR_CO2_VOLUME:
return round(sensors.get(self._type), 2)
return sensors.get(self._type)

@property
def device_state_attributes(self):
"""Return the state attributes of the monitored installation."""
if self._attributes is not None:
return self._attributes

@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
if self._type == ATTR_TEMP:
return self.get_sensors_unit_of_measurement(ATTR_TEMP_UNIT)
if self._type == ATTR_BATCH_VOLUME or self._type == ATTR_CO2_VOLUME:
return self.get_sensors_unit_of_measurement(ATTR_VOLUME_UNIT)
if self._type == ATTR_BPM:
return 'bpm'
if self._type == ATTR_ABV:
return '%'

return ''

@property
def should_poll(self):
"""Return the polling state."""
return False
18 changes: 18 additions & 0 deletions homeassistant/components/plaato/strings.json
@@ -0,0 +1,18 @@
{
"config": {
"title": "Plaato Airlock",
"step": {
"user": {
"title": "Set up the Plaato Webhook",
"description": "Are you sure you want to set up the Plaato Airlock?"
}
},
"abort": {
"one_instance_allowed": "Only a single instance is necessary.",
"not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from Plaato Airlock."
},
"create_entry": {
"default": "To send events to Home Assistant, you will need to setup the webhook feature in Plaato Airlock.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details."
}
}
}
1 change: 1 addition & 0 deletions homeassistant/generated/config_flows.py
Expand Up @@ -37,6 +37,7 @@
"nest",
"openuv",
"owntracks",
"plaato",
"point",
"ps4",
"rainmachine",
Expand Down

0 comments on commit 266b3bc

Please sign in to comment.