Skip to content

Instantly share code, notes, and snippets.

@shanbs
Forked from gieljnssns/netatmo2.py
Last active February 15, 2019 11:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shanbs/526b6cd424e7f1f872cbd320887a2f4a to your computer and use it in GitHub Desktop.
Save shanbs/526b6cd424e7f1f872cbd320887a2f4a to your computer and use it in GitHub Desktop.
Custom climate component for testing new Netatmo Energy API on HA +0.72
"""
Support for Netatmo Smart Thermostat.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.netatmo/
"""
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE, STATE_UNKNOWN
from homeassistant.components.climate import (
STATE_HEAT, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA, SUPPORT_ON_OFF,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE,
STATE_OFF, STATE_MANUAL)
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
from custom_components import netatmo2
DEPENDENCIES = ['netatmo2']
_LOGGER = logging.getLogger(__name__)
CONF_HOME = 'home'
CONF_ROOMS = 'rooms'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOME): cv.string,
vol.Optional(CONF_ROOMS, default=[]):
vol.All(cv.ensure_list, [cv.string]),
})
STATE_AWAY = 'Away'
STATE_MAX = 'Max'
STATE_AUTO = 'Schedule'
STATE_ECO = 'Frost guard'
DICT_NETATMO_TO_HA = {
'schedule': STATE_AUTO,
'hg': STATE_ECO,
'max': STATE_MAX,
'off': STATE_OFF,
'away': STATE_AWAY,
'manual': STATE_MANUAL
}
DICT_HA_TO_NETATMO = {
STATE_AUTO: 'schedule',
STATE_ECO: 'hg',
STATE_MAX: 'max',
STATE_OFF: 'off',
STATE_AWAY: 'away',
STATE_MANUAL: 'manual'
}
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
SUPPORT_AWAY_MODE)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the NetAtmo Thermostat."""
_LOGGER.debug("Starting to setup platform climate.netatmo2...")
netatmo = hass.components.netatmo2
home = config.get(CONF_HOME)
import lnetatmo
try:
home_data = HomeData(netatmo.NETATMO_AUTH, home)
for home in home_data.get_home_names():
_LOGGER.debug("Setting up %s ...", home)
room_data = ThermostatData(netatmo.NETATMO_AUTH, home)
for room_id in room_data.get_room_ids():
room_name = room_data.homedata.rooms[home][room_id]['name']
_LOGGER.debug("Setting up %s (%s) ...", room_name, room_id)
if CONF_ROOMS in config:
_LOGGER.debug(config[CONF_ROOMS])
if config[CONF_ROOMS] != [] and \
room_name not in config[CONF_ROOMS]:
continue
_LOGGER.debug("Adding devices for room %s (%s) ...",
room_name, room_id)
add_entities([NetatmoThermostat(room_data, room_id)], True)
except lnetatmo.NoDevice:
return
class NetatmoThermostat(ClimateDevice):
"""Representation a Netatmo thermostat."""
def __init__(self, data, room_id):
"""Initialize the sensor."""
self._data = data
self._state = None
self._room_id = room_id
room_name = self._data.homedata.rooms[self._data.home][room_id]['name']
self._name = 'netatmo_' + room_name
self._target_temperature = None
self._away = None
self._module_type = self._data.room_status[room_id]['module_type']
self._support_flags = SUPPORT_FLAGS
self._operation_list = [STATE_AUTO, STATE_MANUAL, STATE_ECO]
if self._module_type == 'NATherm1':
self._operation_list += [STATE_MAX]
self._support_flags |= SUPPORT_ON_OFF
self._operation_mode = None
self.update_without_throttle = False
@property
def supported_features(self):
"""Return the list of supported features."""
return self._support_flags
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def current_temperature(self):
"""Return the current temperature."""
return self._data.room_status[self._room_id]['current_temperature']
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._data.room_status[self._room_id]['target_temperature']
@property
def current_operation(self):
"""Return the current state of the thermostat."""
state = self._data.room_status[self._room_id]['heating_status']
if state is False:
return STATE_IDLE
if state is True:
return STATE_HEAT
return STATE_UNKNOWN
@property
def operation_list(self):
"""Return the operation modes list."""
return self._operation_list
@property
def operation_mode(self):
"""Return current operation ie. heat, cool, idle."""
return self._operation_mode
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
if self._data.room_status[self._room_id]['module_type'] == \
'NATherm1':
return {
"home_id": self._data.homedata.gethomeId(self._data.home),
"room_id": self._room_id,
"setpoint_default_duration": self._data.setpoint_duration,
"away_temperature": self._data.away_temperature,
"hg_temperature": self._data.hg_temperature,
"operation_mode": self._operation_mode,
"boiler_status": self.current_operation,
"module_type":
self._data.room_status[self._room_id]['module_type'],
"module_id":
self._data.room_status[self._room_id]['module_id']
}
if self._data.room_status[self._room_id]['module_type'] == 'NRV':
return {
"home_id": self._data.homedata.gethomeId(self._data.home),
"room_id": self._room_id,
"setpoint_default_duration": self._data.setpoint_duration,
"away_temperature": self._data.away_temperature,
"hg_temperature": self._data.hg_temperature,
"operation_mode": self._operation_mode,
"module_type":
self._data.room_status[self._room_id]['module_type'],
"heating_power_request":
self._data.room_status[self._room_id]['heating_power_request'],
"module_id": self._data.room_status[self._room_id]['module_id']
}
return {}
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._away
@property
def is_on(self):
"""Return true if on."""
return self.target_temperature > 0
def turn_away_mode_on(self):
"""Turn away on."""
self.set_operation_mode(STATE_AWAY)
def turn_away_mode_off(self):
"""Turn away off."""
self.set_operation_mode(STATE_AUTO)
def turn_off(self):
"""Turn Netatmo off."""
_LOGGER.debug("Switching off ...")
self.set_operation_mode(STATE_OFF)
self.update_without_throttle = True
self.schedule_update_ha_state()
def turn_on(self):
"""Turn Netatmo on."""
_LOGGER.debug("Switching on ...")
_LOGGER.debug("Setting temperature first to %d ...",
self._data.hg_temperature)
self._data.homestatus.setroomThermpoint(
self._data.homedata.gethomeId(self._data.home),
self._room_id, STATE_MANUAL, self._data.hg_temperature)
_LOGGER.debug("Setting operation mode to schedule ...")
self._data.homestatus.setThermmode(
self._data.homedata.gethomeId(self._data.home),
DICT_HA_TO_NETATMO[STATE_AUTO])
self.update_without_throttle = True
self.schedule_update_ha_state()
def set_operation_mode(self, operation_mode):
"""Set HVAC mode (auto, auxHeatOnly, cool, heat, off)."""
if not self.is_on:
self.turn_on()
if operation_mode in [STATE_MAX, STATE_OFF]:
self._data.homestatus.setroomThermpoint(
self._data.homedata.gethomeId(self._data.home),
self._room_id, DICT_HA_TO_NETATMO[operation_mode])
elif operation_mode in [STATE_ECO, STATE_AUTO, STATE_AWAY]:
self._data.homestatus.setThermmode(
self._data.homedata.gethomeId(self._data.home),
DICT_HA_TO_NETATMO[operation_mode])
self.update_without_throttle = True
self.schedule_update_ha_state()
def set_temperature(self, **kwargs):
"""Set new target temperature for 2 hours."""
temp = kwargs.get(ATTR_TEMPERATURE)
if temp is None:
return
mode = STATE_MANUAL
self._data.homestatus.setroomThermpoint(
self._data.homedata.gethomeId(self._data.home),
self._room_id, DICT_HA_TO_NETATMO[mode], temp)
self.update_without_throttle = True
self.schedule_update_ha_state()
def update(self):
"""Get the latest data from NetAtmo API and updates the states."""
try:
if self.update_without_throttle:
self._data.update(no_throttle=True)
self.update_without_throttle = False
else:
self._data.update()
except AttributeError:
_LOGGER.error("NetatmoThermostat::update() "
"got exception.")
return
self._target_temperature = \
self._data.room_status[self._room_id]['target_temperature']
self._operation_mode = DICT_NETATMO_TO_HA[
self._data.room_status[self._room_id]['setpoint_mode']]
self._away = self._operation_mode == STATE_AWAY
class HomeData():
"""Representation Netatmo homes."""
def __init__(self, auth, home=None):
"""Initialize the HomeData object."""
self.auth = auth
self.homedata = None
self.home_names = []
self.room_names = []
self.schedules = []
self.home = home
self.home_id = None
def get_home_names(self):
"""Get all the home names returned by NetAtmo API."""
self.setup()
for home in self.homedata.homes:
if 'modules' in self.homedata.homes[home]:
self.home_names.append(self.homedata.homes[home]['name'])
return self.home_names
def setup(self):
"""Retrieve HomeData by NetAtmo API."""
import lnetatmo
try:
self.homedata = lnetatmo.HomeData(self.auth)
self.home_id = self.homedata.gethomeId(self.home)
except TypeError:
_LOGGER.error("Error when getting homedata. ")
class ThermostatData():
"""Get the latest data from Netatmo."""
def __init__(self, auth, home=None):
"""Initialize the data object."""
self.auth = auth
self.homedata = None
self.homestatus = None
self.room_ids = []
self.room_status = {}
self.schedules = []
self.home = home
self.away_temperature = None
self.hg_temperature = None
self.boilerstatus = None
self.setpoint_duration = None
self.home_id = None
def get_room_ids(self):
"""Return all module available on the API as a list."""
self.setup()
for key in self.homestatus.rooms:
self.room_ids.append(key)
return self.room_ids
def setup(self):
"""Retrieve HomeData and HomeStatus by NetAtmo API."""
import lnetatmo
try:
self.homedata = lnetatmo.HomeData(self.auth)
self.homestatus = lnetatmo.HomeStatus(self.auth, home=self.home)
self.home_id = self.homedata.gethomeId(self.home)
self.update()
except TypeError:
_LOGGER.error("ThermostatData::setup() got error.")
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Call the NetAtmo API to update the data."""
import lnetatmo
try:
self.homestatus = lnetatmo.HomeStatus(self.auth, home=self.home)
except TypeError:
_LOGGER.error("Error when getting homestatus.")
return
_LOGGER.debug("Following is the debugging output for homestatus:")
_LOGGER.debug(self.homestatus.rawData)
for key in self.homestatus.rooms:
roomstatus = {}
roomstatus['roomID'] = self.homestatus.rooms[key]['id']
roomstatus['roomname'] = \
self.homedata.rooms[self.home][key]['name']
roomstatus['target_temperature'] = \
self.homestatus.rooms[key]['therm_setpoint_temperature']
roomstatus['setpoint_mode'] = \
self.homestatus.rooms[key]['therm_setpoint_mode']
roomstatus['current_temperature'] = \
self.homestatus.rooms[key]['therm_measured_temperature']
roomstatus['module_type'] = \
self.homestatus.thermostatType(self.home, key)
roomstatus['module_id'] = None
roomstatus['heating_status'] = None
roomstatus['heating_power_request'] = None
for module_id in self.homedata.rooms[self.home][key]['module_ids']:
if self.homedata.modules[self.home][module_id]['type'] == \
"NATherm1" or roomstatus['module_id'] is None:
roomstatus['module_id'] = module_id
if roomstatus['module_type'] == 'NATherm1':
self.boilerstatus = self.homestatus.boilerStatus(
rid=roomstatus['module_id'])
roomstatus['heating_status'] = self.boilerstatus
elif roomstatus['module_type'] == 'NRV':
roomstatus['heating_power_request'] = \
self.homestatus.rooms[key]['heating_power_request']
roomstatus['heating_status'] = \
roomstatus['heating_power_request'] > 0
if self.boilerstatus is not None:
roomstatus['heating_status'] = \
self.boilerstatus and roomstatus['heating_status']
self.room_status[key] = roomstatus
self.away_temperature = self.homestatus.getAwaytemp(self.home)
self.hg_temperature = self.homestatus.getHgtemp(self.home)
self.setpoint_duration = self.homedata.setpoint_duration[self.home]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment