Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
5e525d5
initial commit
cereal2nd Aug 4, 2021
7965e15
use new release
cereal2nd Aug 4, 2021
b4b189f
Update for sensors
cereal2nd Aug 4, 2021
73ddabe
big update
cereal2nd Aug 5, 2021
246e060
pylint fixes, bump dependancy to 2021.8.2
cereal2nd Aug 5, 2021
b4f9aec
New version to try to fix the tests
cereal2nd Aug 5, 2021
61d74e4
Fix a lot of errors, bump version
cereal2nd Aug 6, 2021
928b645
more work
cereal2nd Aug 9, 2021
dd729a1
Bump version
cereal2nd Aug 9, 2021
8cd2d2e
Adde dimmer support
cereal2nd Aug 10, 2021
8662d68
Make sure the counters are useable in the energy dashboard
cereal2nd Aug 10, 2021
2a7456b
bump version
cereal2nd Aug 10, 2021
4e95c4d
Fix testcases
cereal2nd Aug 11, 2021
4c0922b
Update after review
cereal2nd Aug 11, 2021
6561a0f
Bump version to be able to have some decent exception catches, add th…
cereal2nd Aug 11, 2021
41970fb
Readd the import of the platform from config file, but add a deprecat…
cereal2nd Aug 12, 2021
e9b5391
More comments updated
cereal2nd Aug 12, 2021
708298d
Fix lefover index
cereal2nd Aug 16, 2021
ee580d7
Fix unique id to be backwards compatible
cereal2nd Aug 16, 2021
efed4c9
Fix small bug in covers
cereal2nd Aug 16, 2021
c6ea7e1
Fix testcases
cereal2nd Aug 16, 2021
442b164
Changes for theenery dashboard
cereal2nd Aug 16, 2021
2047e23
Fixed services
cereal2nd Aug 18, 2021
75e2e31
Fix memo text
cereal2nd Aug 18, 2021
b0bca85
Make the interface for a service the port string instead of the devic…
cereal2nd Aug 19, 2021
0618b04
Fix set_memo_text
cereal2nd Aug 19, 2021
22d3c84
added an async scan task, more comments
cereal2nd Aug 22, 2021
61dd343
Accidently disabled some paltforms
cereal2nd Aug 22, 2021
1d1cac9
More comments, bump version
cereal2nd Aug 22, 2021
8652fff
Bump version, add extra attributes, enable mypy
cereal2nd Aug 23, 2021
9ee0f60
Removed new features
cereal2nd Sep 3, 2021
55f8e84
More comments
cereal2nd Sep 6, 2021
56d46bd
Bump version
cereal2nd Sep 7, 2021
38efe56
Update homeassistant/components/velbus/__init__.py
cereal2nd Sep 8, 2021
2824390
Readd the import step
cereal2nd Sep 10, 2021
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
157 changes: 87 additions & 70 deletions homeassistant/components/velbus/__init__.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
"""Support for Velbus devices."""
from __future__ import annotations

import logging

import velbus
from velbusaio.controller import Velbus
import voluptuous as vol

from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_PORT
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity

from .const import CONF_MEMO_TEXT, DOMAIN, SERVICE_SET_MEMO_TEXT
from .const import (
CONF_INTERFACE,
CONF_MEMO_TEXT,
DOMAIN,
SERVICE_SCAN,
SERVICE_SET_MEMO_TEXT,
SERVICE_SYNC,
)

_LOGGER = logging.getLogger(__name__)

VELBUS_MESSAGE = "velbus.message"

CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.Schema({vol.Required(CONF_PORT): cv.string})}, extra=vol.ALLOW_EXTRA
)
Expand All @@ -29,6 +35,9 @@ async def async_setup(hass, config):
# Import from the configuration file if needed
if DOMAIN not in config:
return True

_LOGGER.warning("Loading VELBUS via configuration.yaml is deprecated")

port = config[DOMAIN].get(CONF_PORT)
data = {}

Expand All @@ -39,64 +48,75 @@ async def async_setup(hass, config):
DOMAIN, context={"source": SOURCE_IMPORT}, data=data
)
)

return True


async def velbus_connect_task(
controller: Velbus, hass: HomeAssistant, entry_id: str
) -> None:
"""Task to offload the long running connect."""
await controller.connect()


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Establish connection with velbus."""
hass.data.setdefault(DOMAIN, {})

def callback():
modules = controller.get_modules()
discovery_info = {"cntrl": controller}
for platform in PLATFORMS:
discovery_info[platform] = []
for module in modules:
for channel in range(1, module.number_of_channels() + 1):
for platform in PLATFORMS:
if platform in module.get_categories(channel):
discovery_info[platform].append(
(module.get_module_address(), channel)
)
hass.data[DOMAIN][entry.entry_id] = discovery_info

for platform in PLATFORMS:
hass.add_job(hass.config_entries.async_forward_entry_setup(entry, platform))

try:
controller = velbus.Controller(entry.data[CONF_PORT])
controller.scan(callback)
except velbus.util.VelbusException as err:
_LOGGER.error("An error occurred: %s", err)
raise ConfigEntryNotReady from err

def syn_clock(self, service=None):
try:
controller.sync_clock()
except velbus.util.VelbusException as err:
_LOGGER.error("An error occurred: %s", err)

hass.services.async_register(DOMAIN, "sync_clock", syn_clock, schema=vol.Schema({}))

def set_memo_text(service):
controller = Velbus(entry.data[CONF_PORT])
hass.data[DOMAIN][entry.entry_id] = {}
hass.data[DOMAIN][entry.entry_id]["cntrl"] = controller
hass.data[DOMAIN][entry.entry_id]["tsk"] = hass.async_create_task(
velbus_connect_task(controller, hass, entry.entry_id)
)

hass.config_entries.async_setup_platforms(entry, PLATFORMS)

if hass.services.has_service(DOMAIN, SERVICE_SCAN):
return True

def check_entry_id(interface: str):
for entry in hass.config_entries.async_entries(DOMAIN):
if "port" in entry.data and entry.data["port"] == interface:
return entry.entry_id
raise vol.Invalid(
"The interface provided is not defined as a port in a Velbus integration"
)

async def scan(call):
await hass.data[DOMAIN][call.data[CONF_INTERFACE]]["cntrl"].scan()

hass.services.async_register(
DOMAIN,
SERVICE_SCAN,
scan,
vol.Schema({vol.Required(CONF_INTERFACE): vol.All(cv.string, check_entry_id)}),
)

async def syn_clock(call):
await hass.data[DOMAIN][call.data[CONF_INTERFACE]]["cntrl"].sync_clock()

hass.services.async_register(
DOMAIN,
SERVICE_SYNC,
syn_clock,
vol.Schema({vol.Required(CONF_INTERFACE): vol.All(cv.string, check_entry_id)}),
)

async def set_memo_text(call):
"""Handle Memo Text service call."""
module_address = service.data[CONF_ADDRESS]
memo_text = service.data[CONF_MEMO_TEXT]
memo_text = call.data[CONF_MEMO_TEXT]
memo_text.hass = hass
try:
controller.get_module(module_address).set_memo_text(
memo_text.async_render()
)
except velbus.util.VelbusException as err:
_LOGGER.error("An error occurred while setting memo text: %s", err)
await hass.data[DOMAIN][call.data[CONF_INTERFACE]]["cntrl"].get_module(
call.data[CONF_ADDRESS]
).set_memo_text(memo_text.async_render())

hass.services.async_register(
DOMAIN,
SERVICE_SET_MEMO_TEXT,
set_memo_text,
vol.Schema(
{
vol.Required(CONF_INTERFACE): vol.All(cv.string, check_entry_id),
vol.Required(CONF_ADDRESS): vol.All(
vol.Coerce(int), vol.Range(min=0, max=255)
),
Expand All @@ -111,35 +131,34 @@ def set_memo_text(service):
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Remove the velbus connection."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
hass.data[DOMAIN][entry.entry_id]["cntrl"].stop()
await hass.data[DOMAIN][entry.entry_id]["cntrl"].stop()
hass.data[DOMAIN].pop(entry.entry_id)
if not hass.data[DOMAIN]:
hass.data.pop(DOMAIN)
hass.services.async_remove(DOMAIN, SERVICE_SCAN)
hass.services.async_remove(DOMAIN, SERVICE_SYNC)
hass.services.async_remove(DOMAIN, SERVICE_SET_MEMO_TEXT)
return unload_ok


class VelbusEntity(Entity):
"""Representation of a Velbus entity."""

def __init__(self, module, channel):
def __init__(self, channel):
"""Initialize a Velbus entity."""
self._module = module
self._channel = channel

@property
def unique_id(self):
"""Get unique ID."""
serial = 0
if self._module.serial == 0:
serial = self._module.get_module_address()
else:
serial = self._module.serial
return f"{serial}-{self._channel}"
if (serial := self._channel.get_module_serial()) == 0:
serial = self._channel.get_module_address()
return f"{serial}-{self._channel.get_channel_number()}"

@property
def name(self):
"""Return the display name of this entity."""
return self._module.get_name(self._channel)
return self._channel.get_name()

@property
def should_poll(self):
Expand All @@ -148,26 +167,24 @@ def should_poll(self):

async def async_added_to_hass(self):
"""Add listener for state changes."""
self._module.on_status_update(self._channel, self._on_update)
self._channel.on_status_update(self._on_update)

def _on_update(self, state):
self.schedule_update_ha_state()
async def _on_update(self):
self.async_write_ha_state()

@property
def device_info(self):
"""Return the device info."""
return {
"identifiers": {
(DOMAIN, self._module.get_module_address(), self._module.serial)
(
DOMAIN,
self._channel.get_module_address(),
self._channel.get_module_serial(),
)
},
"name": "{} ({})".format(
self._module.get_module_name(), self._module.get_module_address()
),
"name": self._channel.get_full_name(),
"manufacturer": "Velleman",
"model": self._module.get_module_type_name(),
"sw_version": "{}.{}-{}".format(
self._module.memory_map_version,
self._module.build_year,
self._module.build_week,
),
"model": self._channel.get_module_type_name(),
"sw_version": self._channel.get_module_sw_version(),
}
13 changes: 6 additions & 7 deletions homeassistant/components/velbus/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,19 @@


async def async_setup_entry(hass, entry, async_add_entities):
"""Set up Velbus binary sensor based on config_entry."""
"""Set up Velbus switch based on config_entry."""
await hass.data[DOMAIN][entry.entry_id]["tsk"]
cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"]
modules_data = hass.data[DOMAIN][entry.entry_id]["binary_sensor"]
entities = []
for address, channel in modules_data:
module = cntrl.get_module(address)
entities.append(VelbusBinarySensor(module, channel))
for channel in cntrl.get_all("binary_sensor"):
entities.append(VelbusBinarySensor(channel))
async_add_entities(entities)


class VelbusBinarySensor(VelbusEntity, BinarySensorEntity):
"""Representation of a Velbus Binary Sensor."""

@property
def is_on(self):
def is_on(self) -> bool:
"""Return true if the sensor is on."""
return self._module.is_closed(self._channel)
return self._channel.is_closed()
29 changes: 10 additions & 19 deletions homeassistant/components/velbus/climate.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
"""Support for Velbus thermostat."""
import logging

from velbus.util import VelbusException

from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
HVAC_MODE_HEAT,
SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS

from . import VelbusEntity
from .const import DOMAIN
Expand All @@ -17,13 +15,12 @@


async def async_setup_entry(hass, entry, async_add_entities):
"""Set up Velbus binary sensor based on config_entry."""
"""Set up Velbus switch based on config_entry."""
await hass.data[DOMAIN][entry.entry_id]["tsk"]
cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"]
modules_data = hass.data[DOMAIN][entry.entry_id]["climate"]
entities = []
for address, channel in modules_data:
module = cntrl.get_module(address)
entities.append(VelbusClimate(module, channel))
for channel in cntrl.get_all("climate"):
entities.append(VelbusClimate(channel))
async_add_entities(entities)


Expand All @@ -37,15 +34,13 @@ def supported_features(self):

@property
def temperature_unit(self):
"""Return the unit this state is expressed in."""
if self._module.get_unit(self._channel) == TEMP_CELSIUS:
return TEMP_CELSIUS
return TEMP_FAHRENHEIT
"""Return the unit."""
return TEMP_CELSIUS

@property
def current_temperature(self):
"""Return the current temperature."""
return self._module.get_state(self._channel)
return self._channel.get_state()

@property
def hvac_mode(self):
Expand All @@ -66,18 +61,14 @@ def hvac_modes(self):
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._module.get_climate_target()
return self._channel.get_climate_target()

def set_temperature(self, **kwargs):
"""Set new target temperatures."""
temp = kwargs.get(ATTR_TEMPERATURE)
if temp is None:
return
try:
self._module.set_temp(temp)
except VelbusException as err:
_LOGGER.error("A Velbus error occurred: %s", err)
return
self._channel.set_temp(temp)
self.schedule_update_ha_state()

def set_hvac_mode(self, hvac_mode):
Expand Down
14 changes: 8 additions & 6 deletions homeassistant/components/velbus/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Config flow for the Velbus platform."""
from __future__ import annotations

import velbus
import velbusaio
from velbusaio.exceptions import VelbusConnectionFailed
import voluptuous as vol

from homeassistant import config_entries
Expand Down Expand Up @@ -33,14 +34,15 @@ def _create_device(self, name: str, prt: str):
"""Create an entry async."""
return self.async_create_entry(title=name, data={CONF_PORT: prt})

def _test_connection(self, prt):
async def _test_connection(self, prt):
"""Try to connect to the velbus with the port specified."""
try:
controller = velbus.Controller(prt)
except Exception: # pylint: disable=broad-except
controller = velbusaio.controller.Velbus(prt)
await controller.connect(True)
await controller.stop()
except VelbusConnectionFailed:
self._errors[CONF_PORT] = "cannot_connect"
return False
controller.stop()
return True

def _prt_in_configuration_exists(self, prt: str) -> bool:
Expand All @@ -56,7 +58,7 @@ async def async_step_user(self, user_input=None):
name = slugify(user_input[CONF_NAME])
prt = user_input[CONF_PORT]
if not self._prt_in_configuration_exists(prt):
if self._test_connection(prt):
if await self._test_connection(prt):
return self._create_device(name, prt)
else:
self._errors[CONF_PORT] = "already_configured"
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/velbus/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

DOMAIN = "velbus"

CONF_INTERFACE = "interface"
CONF_MEMO_TEXT = "memo_text"

SERVICE_SCAN = "scan"
SERVICE_SYNC = "sync_clock"
SERVICE_SET_MEMO_TEXT = "set_memo_text"
Loading