Add support for MAX!Cube thermostats and window shutter sensors (#6105)
This commit is contained in:
parent
6ea74ce740
commit
d7af43b87d
5 changed files with 392 additions and 0 deletions
|
@ -132,6 +132,9 @@ omit =
|
||||||
homeassistant/components/zabbix.py
|
homeassistant/components/zabbix.py
|
||||||
homeassistant/components/*/zabbix.py
|
homeassistant/components/*/zabbix.py
|
||||||
|
|
||||||
|
homeassistant/components/maxcube.py
|
||||||
|
homeassistant/components/*/maxcube.py
|
||||||
|
|
||||||
homeassistant/components/alarm_control_panel/alarmdotcom.py
|
homeassistant/components/alarm_control_panel/alarmdotcom.py
|
||||||
homeassistant/components/alarm_control_panel/concord232.py
|
homeassistant/components/alarm_control_panel/concord232.py
|
||||||
homeassistant/components/alarm_control_panel/nx584.py
|
homeassistant/components/alarm_control_panel/nx584.py
|
||||||
|
|
76
homeassistant/components/binary_sensor/maxcube.py
Normal file
76
homeassistant/components/binary_sensor/maxcube.py
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
"""
|
||||||
|
Support for MAX! Window Shutter via MAX! Cube.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation
|
||||||
|
https://home-assistant.io/components/maxcube/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
|
from homeassistant.components.maxcube import MAXCUBE_HANDLE
|
||||||
|
from homeassistant.const import STATE_UNKNOWN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
"""Iterate through all MAX! Devices and add window shutters to HASS."""
|
||||||
|
cube = hass.data[MAXCUBE_HANDLE].cube
|
||||||
|
|
||||||
|
# List of devices
|
||||||
|
devices = []
|
||||||
|
|
||||||
|
for device in cube.devices:
|
||||||
|
# Create device name by concatenating room name + device name
|
||||||
|
name = "%s %s" % (cube.room_by_id(device.room_id).name, device.name)
|
||||||
|
|
||||||
|
# Only add Window Shutters
|
||||||
|
if cube.is_windowshutter(device):
|
||||||
|
# add device to HASS
|
||||||
|
devices.append(MaxCubeShutter(hass, name, device.rf_address))
|
||||||
|
|
||||||
|
if len(devices) > 0:
|
||||||
|
add_devices(devices)
|
||||||
|
|
||||||
|
|
||||||
|
class MaxCubeShutter(BinarySensorDevice):
|
||||||
|
"""MAX! Cube BinarySensor device."""
|
||||||
|
|
||||||
|
def __init__(self, hass, name, rf_address):
|
||||||
|
"""Initialize MAX! Cube BinarySensorDevice."""
|
||||||
|
self._name = name
|
||||||
|
self._sensor_type = 'opening'
|
||||||
|
self._rf_address = rf_address
|
||||||
|
self._cubehandle = hass.data[MAXCUBE_HANDLE]
|
||||||
|
self._state = STATE_UNKNOWN
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""Polling is required."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the BinarySensorDevice."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_class(self):
|
||||||
|
"""Return the class of this sensor."""
|
||||||
|
return self._sensor_type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
"""Return true if the binary sensor is on/open."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Get latest data from MAX! Cube."""
|
||||||
|
self._cubehandle.update()
|
||||||
|
|
||||||
|
# Get the device we want to update
|
||||||
|
device = self._cubehandle.cube.device_by_rf(self._rf_address)
|
||||||
|
|
||||||
|
# Update our internal state
|
||||||
|
self._state = device.is_open
|
216
homeassistant/components/climate/maxcube.py
Normal file
216
homeassistant/components/climate/maxcube.py
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
"""
|
||||||
|
Support for MAX! Thermostats via MAX! Cube.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation
|
||||||
|
https://home-assistant.io/components/maxcube/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.climate import ClimateDevice, STATE_AUTO
|
||||||
|
from homeassistant.components.maxcube import MAXCUBE_HANDLE
|
||||||
|
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
|
||||||
|
from homeassistant.const import STATE_UNKNOWN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
STATE_MANUAL = "manual"
|
||||||
|
STATE_BOOST = "boost"
|
||||||
|
STATE_VACATION = "vacation"
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
"""Iterate through all MAX! Devices and add thermostats to HASS."""
|
||||||
|
cube = hass.data[MAXCUBE_HANDLE].cube
|
||||||
|
|
||||||
|
# List of devices
|
||||||
|
devices = []
|
||||||
|
|
||||||
|
for device in cube.devices:
|
||||||
|
# Create device name by concatenating room name + device name
|
||||||
|
name = "%s %s" % (cube.room_by_id(device.room_id).name, device.name)
|
||||||
|
|
||||||
|
# Only add thermostats and wallthermostats
|
||||||
|
if cube.is_thermostat(device) or cube.is_wallthermostat(device):
|
||||||
|
# Add device to HASS
|
||||||
|
devices.append(MaxCubeClimate(hass, name, device.rf_address))
|
||||||
|
|
||||||
|
# Add all devices at once
|
||||||
|
if len(devices) > 0:
|
||||||
|
add_devices(devices)
|
||||||
|
|
||||||
|
|
||||||
|
class MaxCubeClimate(ClimateDevice):
|
||||||
|
"""MAX! Cube ClimateDevice."""
|
||||||
|
|
||||||
|
def __init__(self, hass, name, rf_address):
|
||||||
|
"""Initialize MAX! Cube ClimateDevice."""
|
||||||
|
self._name = name
|
||||||
|
self._unit_of_measurement = TEMP_CELSIUS
|
||||||
|
self._operation_list = [STATE_AUTO, STATE_MANUAL, STATE_BOOST,
|
||||||
|
STATE_VACATION]
|
||||||
|
self._rf_address = rf_address
|
||||||
|
self._cubehandle = hass.data[MAXCUBE_HANDLE]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""Polling is required."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the ClimateDevice."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min_temp(self):
|
||||||
|
"""Return the minimum temperature."""
|
||||||
|
# Get the device we want (does not do any IO, just reads from memory)
|
||||||
|
device = self._cubehandle.cube.device_by_rf(self._rf_address)
|
||||||
|
|
||||||
|
# Map and return minimum temperature
|
||||||
|
return self.map_temperature_max_hass(device.min_temperature)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_temp(self):
|
||||||
|
"""Return the maximum temperature."""
|
||||||
|
# Get the device we want (does not do any IO, just reads from memory)
|
||||||
|
device = self._cubehandle.cube.device_by_rf(self._rf_address)
|
||||||
|
|
||||||
|
# Map and return maximum temperature
|
||||||
|
return self.map_temperature_max_hass(device.max_temperature)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def temperature_unit(self):
|
||||||
|
"""Return the unit of measurement."""
|
||||||
|
return self._unit_of_measurement
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_temperature(self):
|
||||||
|
"""Return the current temperature."""
|
||||||
|
# Get the device we want (does not do any IO, just reads from memory)
|
||||||
|
device = self._cubehandle.cube.device_by_rf(self._rf_address)
|
||||||
|
|
||||||
|
# Map and return current temperature
|
||||||
|
return self.map_temperature_max_hass(device.actual_temperature)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_operation(self):
|
||||||
|
"""Return current operation (auto, manual, boost, vacation)."""
|
||||||
|
# Get the device we want (does not do any IO, just reads from memory)
|
||||||
|
device = self._cubehandle.cube.device_by_rf(self._rf_address)
|
||||||
|
|
||||||
|
# Mode Mapping
|
||||||
|
return self.map_mode_max_hass(device.mode)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def operation_list(self):
|
||||||
|
"""List of available operation modes."""
|
||||||
|
return self._operation_list
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature(self):
|
||||||
|
"""Return the temperature we try to reach."""
|
||||||
|
# Get the device we want (does not do any IO, just reads from memory)
|
||||||
|
device = self._cubehandle.cube.device_by_rf(self._rf_address)
|
||||||
|
|
||||||
|
# Map and return target temperature
|
||||||
|
return self.map_temperature_max_hass(device.target_temperature)
|
||||||
|
|
||||||
|
def set_temperature(self, **kwargs):
|
||||||
|
"""Set new target temperatures."""
|
||||||
|
# Fail is target temperature has not been supplied as argument
|
||||||
|
if kwargs.get(ATTR_TEMPERATURE) is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Determine the new target temperature
|
||||||
|
target_temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||||
|
|
||||||
|
# Write the target temperature to the MAX! Cube.
|
||||||
|
device = self._cubehandle.cube.device_by_rf(self._rf_address)
|
||||||
|
|
||||||
|
cube = self._cubehandle.cube
|
||||||
|
|
||||||
|
with self._cubehandle.mutex:
|
||||||
|
try:
|
||||||
|
cube.set_target_temperature(device, target_temperature)
|
||||||
|
except (socket.timeout, socket.error):
|
||||||
|
_LOGGER.error("Setting target temperature failed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def set_operation_mode(self, operation_mode):
|
||||||
|
"""Set new operation mode."""
|
||||||
|
# Get the device we want to update
|
||||||
|
device = self._cubehandle.cube.device_by_rf(self._rf_address)
|
||||||
|
|
||||||
|
# Mode Mapping
|
||||||
|
mode = self.map_mode_hass_max(operation_mode)
|
||||||
|
|
||||||
|
# Write new mode to thermostat
|
||||||
|
if mode is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
with self._cubehandle.mutex:
|
||||||
|
try:
|
||||||
|
self._cubehandle.cube.set_mode(device, mode)
|
||||||
|
except (socket.timeout, socket.error):
|
||||||
|
_LOGGER.error("Setting operation mode failed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Get latest data from MAX! Cube."""
|
||||||
|
# Update the CubeHandle
|
||||||
|
self._cubehandle.update()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def map_temperature_max_hass(temperature):
|
||||||
|
"""Map Temperature from MAX! to HASS."""
|
||||||
|
if temperature is None:
|
||||||
|
return STATE_UNKNOWN
|
||||||
|
|
||||||
|
return temperature
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def map_mode_hass_max(operation_mode):
|
||||||
|
"""Map HASS Operation Modes to MAX! Operation Modes."""
|
||||||
|
from maxcube.device import \
|
||||||
|
MAX_DEVICE_MODE_AUTOMATIC, \
|
||||||
|
MAX_DEVICE_MODE_MANUAL, \
|
||||||
|
MAX_DEVICE_MODE_VACATION, \
|
||||||
|
MAX_DEVICE_MODE_BOOST
|
||||||
|
|
||||||
|
if operation_mode == STATE_AUTO:
|
||||||
|
mode = MAX_DEVICE_MODE_AUTOMATIC
|
||||||
|
elif operation_mode == STATE_MANUAL:
|
||||||
|
mode = MAX_DEVICE_MODE_MANUAL
|
||||||
|
elif operation_mode == STATE_VACATION:
|
||||||
|
mode = MAX_DEVICE_MODE_VACATION
|
||||||
|
elif operation_mode == STATE_BOOST:
|
||||||
|
mode = MAX_DEVICE_MODE_BOOST
|
||||||
|
else:
|
||||||
|
mode = None
|
||||||
|
|
||||||
|
return mode
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def map_mode_max_hass(mode):
|
||||||
|
"""Map MAX! Operation Modes to HASS Operation Modes."""
|
||||||
|
from maxcube.device import \
|
||||||
|
MAX_DEVICE_MODE_AUTOMATIC, \
|
||||||
|
MAX_DEVICE_MODE_MANUAL, \
|
||||||
|
MAX_DEVICE_MODE_VACATION, \
|
||||||
|
MAX_DEVICE_MODE_BOOST
|
||||||
|
|
||||||
|
if mode == MAX_DEVICE_MODE_AUTOMATIC:
|
||||||
|
operation_mode = STATE_AUTO
|
||||||
|
elif mode == MAX_DEVICE_MODE_MANUAL:
|
||||||
|
operation_mode = STATE_MANUAL
|
||||||
|
elif mode == MAX_DEVICE_MODE_VACATION:
|
||||||
|
operation_mode = STATE_VACATION
|
||||||
|
elif mode == MAX_DEVICE_MODE_BOOST:
|
||||||
|
operation_mode = STATE_BOOST
|
||||||
|
else:
|
||||||
|
operation_mode = None
|
||||||
|
|
||||||
|
return operation_mode
|
94
homeassistant/components/maxcube.py
Normal file
94
homeassistant/components/maxcube.py
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
"""
|
||||||
|
Platform for the MAX! Cube LAN Gateway.
|
||||||
|
|
||||||
|
For more details about this component, please refer to the documentation
|
||||||
|
https://home-assistant.io/components/maxcube/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from socket import timeout
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
|
from homeassistant.components.discovery import load_platform
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
REQUIREMENTS = ['maxcube-api==0.1.0']
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DOMAIN = 'maxcube'
|
||||||
|
MAXCUBE_HANDLE = 'maxcube'
|
||||||
|
|
||||||
|
DEFAULT_PORT = 62910
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
|
DOMAIN: vol.Schema({
|
||||||
|
vol.Required(CONF_HOST): cv.string,
|
||||||
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||||
|
}),
|
||||||
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
"""Establish connection to MAX! Cube."""
|
||||||
|
from maxcube.connection import MaxCubeConnection
|
||||||
|
from maxcube.cube import MaxCube
|
||||||
|
|
||||||
|
# Read Config
|
||||||
|
host = config.get(DOMAIN).get(CONF_HOST)
|
||||||
|
port = config.get(DOMAIN).get(CONF_PORT)
|
||||||
|
|
||||||
|
# Assign Cube Handle to global variable
|
||||||
|
try:
|
||||||
|
cube = MaxCube(MaxCubeConnection(host, port))
|
||||||
|
except timeout:
|
||||||
|
_LOGGER.error("Connection to Max!Cube could not be established")
|
||||||
|
cube = None
|
||||||
|
return False
|
||||||
|
|
||||||
|
hass.data[MAXCUBE_HANDLE] = MaxCubeHandle(cube)
|
||||||
|
|
||||||
|
# Load Climate (for Thermostats)
|
||||||
|
load_platform(hass, 'climate', DOMAIN)
|
||||||
|
|
||||||
|
# Load BinarySensor (for Window Shutter)
|
||||||
|
load_platform(hass, 'binary_sensor', DOMAIN)
|
||||||
|
|
||||||
|
# Initialization successfull
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class MaxCubeHandle(object):
|
||||||
|
"""Keep the cube instance in one place and centralize the update."""
|
||||||
|
|
||||||
|
def __init__(self, cube):
|
||||||
|
"""Initialize the Cube Handle."""
|
||||||
|
# Cube handle
|
||||||
|
self.cube = cube
|
||||||
|
|
||||||
|
# Instantiate Mutex
|
||||||
|
self.mutex = Lock()
|
||||||
|
|
||||||
|
# Update Timestamp
|
||||||
|
self._updatets = time.time()
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Pull the latest data from the MAX! Cube."""
|
||||||
|
# Acquire mutex to prevent simultaneous update from multiple threads
|
||||||
|
with self.mutex:
|
||||||
|
# Only update every 60s
|
||||||
|
if (time.time() - self._updatets) >= 60:
|
||||||
|
_LOGGER.debug("UPDATE: Updating")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.cube.update()
|
||||||
|
except timeout:
|
||||||
|
_LOGGER.error("Max!Cube connection failed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
self._updatets = time.time()
|
||||||
|
else:
|
||||||
|
_LOGGER.debug("UPDATE: Skipping")
|
|
@ -336,6 +336,9 @@ liveboxplaytv==1.4.9
|
||||||
# homeassistant.components.notify.matrix
|
# homeassistant.components.notify.matrix
|
||||||
matrix-client==0.0.5
|
matrix-client==0.0.5
|
||||||
|
|
||||||
|
# homeassistant.components.maxcube
|
||||||
|
maxcube-api==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.notify.message_bird
|
# homeassistant.components.notify.message_bird
|
||||||
messagebird==1.2.0
|
messagebird==1.2.0
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue