mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 10:47:10 +00:00
Rewrite JuiceNet for async and config flow (#34365)
* Add config flow to JuiceNet * Fix some lint issues * Fix imports * Abort on reconfigure / Allow multiple accounts Abort on bad API token Fix strings * Remove unused variable * Update strings * Remove import * Fix import order again * Update imports Remove some unused parameters * Add back ignore * Update config_flow.py * iSort * Update juicenet integration to be async * Update coverage for juicenet config flow * Update homeassistant/components/juicenet/entity.py Co-Authored-By: J. Nick Koston <nick@koston.org> * Black * Make imports relative * Rename translations folder Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
502afbe9c2
commit
e696c08db0
@ -353,7 +353,12 @@ omit =
|
|||||||
homeassistant/components/itach/remote.py
|
homeassistant/components/itach/remote.py
|
||||||
homeassistant/components/itunes/media_player.py
|
homeassistant/components/itunes/media_player.py
|
||||||
homeassistant/components/joaoapps_join/*
|
homeassistant/components/joaoapps_join/*
|
||||||
homeassistant/components/juicenet/*
|
homeassistant/components/juicenet/__init__.py
|
||||||
|
homeassistant/components/juicenet/const.py
|
||||||
|
homeassistant/components/juicenet/device.py
|
||||||
|
homeassistant/components/juicenet/entity.py
|
||||||
|
homeassistant/components/juicenet/sensor.py
|
||||||
|
homeassistant/components/juicenet/switch.py
|
||||||
homeassistant/components/kaiterra/*
|
homeassistant/components/kaiterra/*
|
||||||
homeassistant/components/kankun/switch.py
|
homeassistant/components/kankun/switch.py
|
||||||
homeassistant/components/keba/*
|
homeassistant/components/keba/*
|
||||||
|
@ -1,68 +1,115 @@
|
|||||||
"""Support for Juicenet cloud."""
|
"""The JuiceNet integration."""
|
||||||
|
import asyncio
|
||||||
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import pyjuicenet
|
import aiohttp
|
||||||
|
from pyjuicenet import Api, TokenError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
from homeassistant.helpers import discovery
|
from homeassistant.core import HomeAssistant
|
||||||
import homeassistant.helpers.config_validation as cv
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
|
from .const import DOMAIN, JUICENET_API, JUICENET_COORDINATOR
|
||||||
|
from .device import JuiceNetApi
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DOMAIN = "juicenet"
|
PLATFORMS = ["sensor", "switch"]
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{DOMAIN: vol.Schema({vol.Required(CONF_ACCESS_TOKEN): cv.string})},
|
{DOMAIN: vol.Schema({vol.Required(CONF_ACCESS_TOKEN): cv.string})},
|
||||||
extra=vol.ALLOW_EXTRA,
|
extra=vol.ALLOW_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
JUICENET_COMPONENTS = ["sensor", "switch"]
|
|
||||||
|
async def async_setup(hass: HomeAssistant, config: dict):
|
||||||
|
"""Set up the JuiceNet component."""
|
||||||
|
conf = config.get(DOMAIN)
|
||||||
|
hass.data.setdefault(DOMAIN, {})
|
||||||
|
|
||||||
|
if not conf:
|
||||||
|
return True
|
||||||
|
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_IMPORT}, data=conf
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
"""Set up the Juicenet component."""
|
"""Set up JuiceNet from a config entry."""
|
||||||
hass.data[DOMAIN] = {}
|
|
||||||
|
|
||||||
access_token = config[DOMAIN].get(CONF_ACCESS_TOKEN)
|
config = entry.data
|
||||||
hass.data[DOMAIN]["api"] = pyjuicenet.Api(access_token)
|
|
||||||
|
|
||||||
for component in JUICENET_COMPONENTS:
|
session = async_get_clientsession(hass)
|
||||||
discovery.load_platform(hass, component, DOMAIN, {}, config)
|
|
||||||
|
access_token = config[CONF_ACCESS_TOKEN]
|
||||||
|
api = Api(access_token, session)
|
||||||
|
|
||||||
|
juicenet = JuiceNetApi(api)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await juicenet.setup()
|
||||||
|
except TokenError as error:
|
||||||
|
_LOGGER.error("JuiceNet Error %s", error)
|
||||||
|
return False
|
||||||
|
except aiohttp.ClientError as error:
|
||||||
|
_LOGGER.error("Could not reach the JuiceNet API %s", error)
|
||||||
|
raise ConfigEntryNotReady
|
||||||
|
|
||||||
|
if not juicenet.devices:
|
||||||
|
_LOGGER.error("No JuiceNet devices found for this account")
|
||||||
|
return False
|
||||||
|
_LOGGER.info("%d JuiceNet device(s) found", len(juicenet.devices))
|
||||||
|
|
||||||
|
async def async_update_data():
|
||||||
|
"""Update all device states from the JuiceNet API."""
|
||||||
|
for device in juicenet.devices:
|
||||||
|
await device.update_state(True)
|
||||||
|
return True
|
||||||
|
|
||||||
|
coordinator = DataUpdateCoordinator(
|
||||||
|
hass,
|
||||||
|
_LOGGER,
|
||||||
|
name="JuiceNet",
|
||||||
|
update_method=async_update_data,
|
||||||
|
update_interval=timedelta(seconds=30),
|
||||||
|
)
|
||||||
|
|
||||||
|
hass.data[DOMAIN][entry.entry_id] = {
|
||||||
|
JUICENET_API: juicenet,
|
||||||
|
JUICENET_COORDINATOR: coordinator,
|
||||||
|
}
|
||||||
|
|
||||||
|
await coordinator.async_refresh()
|
||||||
|
|
||||||
|
for component in PLATFORMS:
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class JuicenetDevice(Entity):
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
"""Represent a base Juicenet device."""
|
"""Unload a config entry."""
|
||||||
|
unload_ok = all(
|
||||||
|
await asyncio.gather(
|
||||||
|
*[
|
||||||
|
hass.config_entries.async_forward_entry_unload(entry, component)
|
||||||
|
for component in PLATFORMS
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if unload_ok:
|
||||||
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
def __init__(self, device, sensor_type, hass):
|
return unload_ok
|
||||||
"""Initialise the sensor."""
|
|
||||||
self.hass = hass
|
|
||||||
self.device = device
|
|
||||||
self.type = sensor_type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the device."""
|
|
||||||
return self.device.name()
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
"""Update state of the device."""
|
|
||||||
self.device.update_state()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _manufacturer_device_id(self):
|
|
||||||
"""Return the manufacturer device id."""
|
|
||||||
return self.device.id()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _token(self):
|
|
||||||
"""Return the device API token."""
|
|
||||||
return self.device.token()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self):
|
|
||||||
"""Return a unique ID."""
|
|
||||||
return f"{self.device.id()}-{self.type}"
|
|
||||||
|
79
homeassistant/components/juicenet/config_flow.py
Normal file
79
homeassistant/components/juicenet/config_flow.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
"""Config flow for JuiceNet integration."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
from pyjuicenet import Api, TokenError
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries, core, exceptions
|
||||||
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
|
from .const import DOMAIN # pylint:disable=unused-import
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DATA_SCHEMA = vol.Schema({vol.Required(CONF_ACCESS_TOKEN): str})
|
||||||
|
|
||||||
|
|
||||||
|
async def validate_input(hass: core.HomeAssistant, data):
|
||||||
|
"""Validate the user input allows us to connect.
|
||||||
|
|
||||||
|
Data has the keys from DATA_SCHEMA with values provided by the user.
|
||||||
|
"""
|
||||||
|
session = async_get_clientsession(hass)
|
||||||
|
juicenet = Api(data[CONF_ACCESS_TOKEN], session)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await juicenet.get_devices()
|
||||||
|
except TokenError as error:
|
||||||
|
_LOGGER.error("Token Error %s", error)
|
||||||
|
raise InvalidAuth
|
||||||
|
except aiohttp.ClientError as error:
|
||||||
|
_LOGGER.error("Error connecting %s", error)
|
||||||
|
raise CannotConnect
|
||||||
|
|
||||||
|
# Return info that you want to store in the config entry.
|
||||||
|
return {"title": "JuiceNet"}
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a config flow for JuiceNet."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""Handle the initial step."""
|
||||||
|
errors = {}
|
||||||
|
if user_input is not None:
|
||||||
|
|
||||||
|
await self.async_set_unique_id(user_input[CONF_ACCESS_TOKEN])
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
|
try:
|
||||||
|
info = await validate_input(self.hass, user_input)
|
||||||
|
return self.async_create_entry(title=info["title"], data=user_input)
|
||||||
|
except CannotConnect:
|
||||||
|
errors["base"] = "cannot_connect"
|
||||||
|
except InvalidAuth:
|
||||||
|
errors["base"] = "invalid_auth"
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception("Unexpected exception")
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=DATA_SCHEMA, errors=errors
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_import(self, user_input):
|
||||||
|
"""Handle import."""
|
||||||
|
return await self.async_step_user(user_input)
|
||||||
|
|
||||||
|
|
||||||
|
class CannotConnect(exceptions.HomeAssistantError):
|
||||||
|
"""Error to indicate we cannot connect."""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidAuth(exceptions.HomeAssistantError):
|
||||||
|
"""Error to indicate there is invalid auth."""
|
6
homeassistant/components/juicenet/const.py
Normal file
6
homeassistant/components/juicenet/const.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
"""Constants used by the JuiceNet component."""
|
||||||
|
|
||||||
|
DOMAIN = "juicenet"
|
||||||
|
|
||||||
|
JUICENET_API = "juicenet_api"
|
||||||
|
JUICENET_COORDINATOR = "juicenet_coordinator"
|
23
homeassistant/components/juicenet/device.py
Normal file
23
homeassistant/components/juicenet/device.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
"""Adapter to wrap the pyjuicenet api for home assistant."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class JuiceNetApi:
|
||||||
|
"""Represent a connection to JuiceNet."""
|
||||||
|
|
||||||
|
def __init__(self, api):
|
||||||
|
"""Create an object from the provided API instance."""
|
||||||
|
self.api = api
|
||||||
|
self._devices = []
|
||||||
|
|
||||||
|
async def setup(self):
|
||||||
|
"""JuiceNet device setup.""" # noqa: D403
|
||||||
|
self._devices = await self.api.get_devices()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def devices(self) -> list:
|
||||||
|
"""Get a list of devices managed by this account."""
|
||||||
|
return self._devices
|
54
homeassistant/components/juicenet/entity.py
Normal file
54
homeassistant/components/juicenet/entity.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
"""Adapter to wrap the pyjuicenet api for home assistant."""
|
||||||
|
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
|
||||||
|
class JuiceNetDevice(Entity):
|
||||||
|
"""Represent a base JuiceNet device."""
|
||||||
|
|
||||||
|
def __init__(self, device, sensor_type, coordinator):
|
||||||
|
"""Initialise the sensor."""
|
||||||
|
self.device = device
|
||||||
|
self.type = sensor_type
|
||||||
|
self.coordinator = coordinator
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the device."""
|
||||||
|
return self.device.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""Return False, updates are controlled via coordinator."""
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return True if entity is available."""
|
||||||
|
return self.coordinator.last_update_success
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
|
"""Update the entity."""
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
"""Subscribe to updates."""
|
||||||
|
self.async_on_remove(
|
||||||
|
self.coordinator.async_add_listener(self.async_write_ha_state)
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return a unique ID."""
|
||||||
|
return f"{self.device.id}-{self.type}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self):
|
||||||
|
"""Return device information about this JuiceNet Device."""
|
||||||
|
return {
|
||||||
|
"identifiers": {(DOMAIN, self.device.id)},
|
||||||
|
"name": self.device.name,
|
||||||
|
"manufacturer": "JuiceNet",
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
"domain": "juicenet",
|
"domain": "juicenet",
|
||||||
"name": "JuiceNet",
|
"name": "JuiceNet",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/juicenet",
|
"documentation": "https://www.home-assistant.io/integrations/juicenet",
|
||||||
"requirements": ["python-juicenet==0.1.6"],
|
"requirements": ["python-juicenet==1.0.1"],
|
||||||
"codeowners": ["@jesserockz"]
|
"codeowners": ["@jesserockz"],
|
||||||
|
"config_flow": true
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,8 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
from . import DOMAIN, JuicenetDevice
|
from .const import DOMAIN, JUICENET_API, JUICENET_COORDINATOR
|
||||||
|
from .entity import JuiceNetDevice
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -25,38 +26,39 @@ SENSOR_TYPES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up the Juicenet sensor."""
|
"""Set up the JuiceNet Sensors."""
|
||||||
api = hass.data[DOMAIN]["api"]
|
entities = []
|
||||||
|
juicenet_data = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
api = juicenet_data[JUICENET_API]
|
||||||
|
coordinator = juicenet_data[JUICENET_COORDINATOR]
|
||||||
|
|
||||||
dev = []
|
for device in api.devices:
|
||||||
for device in api.get_devices():
|
for sensor in SENSOR_TYPES:
|
||||||
for variable in SENSOR_TYPES:
|
entities.append(JuiceNetSensorDevice(device, sensor, coordinator))
|
||||||
dev.append(JuicenetSensorDevice(device, variable, hass))
|
async_add_entities(entities)
|
||||||
|
|
||||||
add_entities(dev)
|
|
||||||
|
|
||||||
|
|
||||||
class JuicenetSensorDevice(JuicenetDevice, Entity):
|
class JuiceNetSensorDevice(JuiceNetDevice, Entity):
|
||||||
"""Implementation of a Juicenet sensor."""
|
"""Implementation of a JuiceNet sensor."""
|
||||||
|
|
||||||
def __init__(self, device, sensor_type, hass):
|
def __init__(self, device, sensor_type, coordinator):
|
||||||
"""Initialise the sensor."""
|
"""Initialise the sensor."""
|
||||||
super().__init__(device, sensor_type, hass)
|
super().__init__(device, sensor_type, coordinator)
|
||||||
self._name = SENSOR_TYPES[sensor_type][0]
|
self._name = SENSOR_TYPES[sensor_type][0]
|
||||||
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
|
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the device."""
|
"""Return the name of the device."""
|
||||||
return f"{self.device.name()} {self._name}"
|
return f"{self.device.name} {self._name}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self):
|
def icon(self):
|
||||||
"""Return the icon of the sensor."""
|
"""Return the icon of the sensor."""
|
||||||
icon = None
|
icon = None
|
||||||
if self.type == "status":
|
if self.type == "status":
|
||||||
status = self.device.getStatus()
|
status = self.device.status
|
||||||
if status == "standby":
|
if status == "standby":
|
||||||
icon = "mdi:power-plug-off"
|
icon = "mdi:power-plug-off"
|
||||||
elif status == "plugged":
|
elif status == "plugged":
|
||||||
@ -87,29 +89,19 @@ class JuicenetSensorDevice(JuicenetDevice, Entity):
|
|||||||
"""Return the state."""
|
"""Return the state."""
|
||||||
state = None
|
state = None
|
||||||
if self.type == "status":
|
if self.type == "status":
|
||||||
state = self.device.getStatus()
|
state = self.device.status
|
||||||
elif self.type == "temperature":
|
elif self.type == "temperature":
|
||||||
state = self.device.getTemperature()
|
state = self.device.temperature
|
||||||
elif self.type == "voltage":
|
elif self.type == "voltage":
|
||||||
state = self.device.getVoltage()
|
state = self.device.voltage
|
||||||
elif self.type == "amps":
|
elif self.type == "amps":
|
||||||
state = self.device.getAmps()
|
state = self.device.amps
|
||||||
elif self.type == "watts":
|
elif self.type == "watts":
|
||||||
state = self.device.getWatts()
|
state = self.device.watts
|
||||||
elif self.type == "charge_time":
|
elif self.type == "charge_time":
|
||||||
state = self.device.getChargeTime()
|
state = self.device.charge_time
|
||||||
elif self.type == "energy_added":
|
elif self.type == "energy_added":
|
||||||
state = self.device.getEnergyAdded()
|
state = self.device.energy_added
|
||||||
else:
|
else:
|
||||||
state = "Unknown"
|
state = "Unknown"
|
||||||
return state
|
return state
|
||||||
|
|
||||||
@property
|
|
||||||
def device_state_attributes(self):
|
|
||||||
"""Return the state attributes."""
|
|
||||||
attributes = {}
|
|
||||||
if self.type == "status":
|
|
||||||
man_dev_id = self.device.id()
|
|
||||||
if man_dev_id:
|
|
||||||
attributes["manufacturer_device_id"] = man_dev_id
|
|
||||||
return attributes
|
|
||||||
|
21
homeassistant/components/juicenet/strings.json
Normal file
21
homeassistant/components/juicenet/strings.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "This JuiceNet account is already configured"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect, please try again",
|
||||||
|
"invalid_auth": "Invalid authentication",
|
||||||
|
"unknown": "Unexpected error"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_token": "JuiceNet API Token"
|
||||||
|
},
|
||||||
|
"description": "You will need the API Token from https://home.juice.net/Manage.",
|
||||||
|
"title": "Connect to JuiceNet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,43 +3,45 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.components.switch import SwitchEntity
|
from homeassistant.components.switch import SwitchEntity
|
||||||
|
|
||||||
from . import DOMAIN, JuicenetDevice
|
from .const import DOMAIN, JUICENET_API, JUICENET_COORDINATOR
|
||||||
|
from .entity import JuiceNetDevice
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up the Juicenet switch."""
|
"""Set up the JuiceNet switches."""
|
||||||
api = hass.data[DOMAIN]["api"]
|
entities = []
|
||||||
|
juicenet_data = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
api = juicenet_data[JUICENET_API]
|
||||||
|
coordinator = juicenet_data[JUICENET_COORDINATOR]
|
||||||
|
|
||||||
devs = []
|
for device in api.devices:
|
||||||
for device in api.get_devices():
|
entities.append(JuiceNetChargeNowSwitch(device, coordinator))
|
||||||
devs.append(JuicenetChargeNowSwitch(device, hass))
|
async_add_entities(entities)
|
||||||
|
|
||||||
add_entities(devs)
|
|
||||||
|
|
||||||
|
|
||||||
class JuicenetChargeNowSwitch(JuicenetDevice, SwitchEntity):
|
class JuiceNetChargeNowSwitch(JuiceNetDevice, SwitchEntity):
|
||||||
"""Implementation of a Juicenet switch."""
|
"""Implementation of a JuiceNet switch."""
|
||||||
|
|
||||||
def __init__(self, device, hass):
|
def __init__(self, device, coordinator):
|
||||||
"""Initialise the switch."""
|
"""Initialise the switch."""
|
||||||
super().__init__(device, "charge_now", hass)
|
super().__init__(device, "charge_now", coordinator)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the device."""
|
"""Return the name of the device."""
|
||||||
return f"{self.device.name()} Charge Now"
|
return f"{self.device.name} Charge Now"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return true if switch is on."""
|
"""Return true if switch is on."""
|
||||||
return self.device.getOverrideTime() != 0
|
return self.device.override_time != 0
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
async def async_turn_on(self, **kwargs):
|
||||||
"""Charge now."""
|
"""Charge now."""
|
||||||
self.device.setOverride(True)
|
await self.device.set_override(True)
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
async def async_turn_off(self, **kwargs):
|
||||||
"""Don't charge now."""
|
"""Don't charge now."""
|
||||||
self.device.setOverride(False)
|
await self.device.set_override(False)
|
||||||
|
21
homeassistant/components/juicenet/translations/en.json
Normal file
21
homeassistant/components/juicenet/translations/en.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "This JuiceNet account is already configured"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect, please try again",
|
||||||
|
"invalid_auth": "Invalid authentication",
|
||||||
|
"unknown": "Unexpected error"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_token": "JuiceNet API Token"
|
||||||
|
},
|
||||||
|
"description": "You will need the API Token from https://home.juice.net/Manage.",
|
||||||
|
"title": "Connect to JuiceNet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -68,6 +68,7 @@ FLOWS = [
|
|||||||
"iqvia",
|
"iqvia",
|
||||||
"islamic_prayer_times",
|
"islamic_prayer_times",
|
||||||
"izone",
|
"izone",
|
||||||
|
"juicenet",
|
||||||
"konnected",
|
"konnected",
|
||||||
"life360",
|
"life360",
|
||||||
"lifx",
|
"lifx",
|
||||||
|
@ -1669,7 +1669,7 @@ python-izone==1.1.2
|
|||||||
python-join-api==0.0.4
|
python-join-api==0.0.4
|
||||||
|
|
||||||
# homeassistant.components.juicenet
|
# homeassistant.components.juicenet
|
||||||
python-juicenet==0.1.6
|
python-juicenet==1.0.1
|
||||||
|
|
||||||
# homeassistant.components.lirc
|
# homeassistant.components.lirc
|
||||||
# python-lirc==1.2.3
|
# python-lirc==1.2.3
|
||||||
|
@ -668,6 +668,9 @@ python-forecastio==1.4.0
|
|||||||
# homeassistant.components.izone
|
# homeassistant.components.izone
|
||||||
python-izone==1.1.2
|
python-izone==1.1.2
|
||||||
|
|
||||||
|
# homeassistant.components.juicenet
|
||||||
|
python-juicenet==1.0.1
|
||||||
|
|
||||||
# homeassistant.components.xiaomi_miio
|
# homeassistant.components.xiaomi_miio
|
||||||
python-miio==0.5.0.1
|
python-miio==0.5.0.1
|
||||||
|
|
||||||
|
1
tests/components/juicenet/__init__.py
Normal file
1
tests/components/juicenet/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the JuiceNet component."""
|
123
tests/components/juicenet/test_config_flow.py
Normal file
123
tests/components/juicenet/test_config_flow.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
"""Test the JuiceNet config flow."""
|
||||||
|
import aiohttp
|
||||||
|
from asynctest import patch
|
||||||
|
from asynctest.mock import MagicMock
|
||||||
|
from pyjuicenet import TokenError
|
||||||
|
|
||||||
|
from homeassistant import config_entries, setup
|
||||||
|
from homeassistant.components.juicenet.const import DOMAIN
|
||||||
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
|
||||||
|
|
||||||
|
def _mock_juicenet_return_value(get_devices=None):
|
||||||
|
juicenet_mock = MagicMock()
|
||||||
|
type(juicenet_mock).get_devices = MagicMock(return_value=get_devices)
|
||||||
|
return juicenet_mock
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form(hass):
|
||||||
|
"""Test we get the form."""
|
||||||
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.juicenet.config_flow.Api.get_devices",
|
||||||
|
return_value=MagicMock(),
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.juicenet.async_setup", return_value=True
|
||||||
|
) as mock_setup, patch(
|
||||||
|
"homeassistant.components.juicenet.async_setup_entry", return_value=True
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_ACCESS_TOKEN: "access_token"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == "create_entry"
|
||||||
|
assert result2["title"] == "JuiceNet"
|
||||||
|
assert result2["data"] == {CONF_ACCESS_TOKEN: "access_token"}
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_invalid_auth(hass):
|
||||||
|
"""Test we handle invalid auth."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.juicenet.config_flow.Api.get_devices",
|
||||||
|
side_effect=TokenError,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_ACCESS_TOKEN: "access_token"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["errors"] == {"base": "invalid_auth"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_cannot_connect(hass):
|
||||||
|
"""Test we handle cannot connect error."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.juicenet.config_flow.Api.get_devices",
|
||||||
|
side_effect=aiohttp.ClientError,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_ACCESS_TOKEN: "access_token"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["errors"] == {"base": "cannot_connect"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_catch_unknown_errors(hass):
|
||||||
|
"""Test we handle cannot connect error."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.juicenet.config_flow.Api.get_devices",
|
||||||
|
side_effect=Exception,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_ACCESS_TOKEN: "access_token"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["errors"] == {"base": "unknown"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_import(hass):
|
||||||
|
"""Test that import works as expected."""
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.juicenet.config_flow.Api.get_devices",
|
||||||
|
return_value=MagicMock(),
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.juicenet.async_setup", return_value=True
|
||||||
|
) as mock_setup, patch(
|
||||||
|
"homeassistant.components.juicenet.async_setup_entry", return_value=True
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data={CONF_ACCESS_TOKEN: "access_token"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == "JuiceNet"
|
||||||
|
assert result["data"] == {CONF_ACCESS_TOKEN: "access_token"}
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
Loading…
x
Reference in New Issue
Block a user