got it working ! thank you!
This stopped working for me a while back and finally figured out why - I had to add a line saying
“version”: “1.0.0”
to manifest.json
Hey - I’ve tried adding this but I’m getting the same error message after adding the yaml to the config file. Can’t for the life of me figure out what I’m doing wrong.
@al.rdb how did you get this working?
This is the code from climote.py, I’m guessing something isn’t right in here that the integration isn’t being created?
import logging
import polling
import json
import xmljson
import lxml
from lxml import etree as ET
import voluptuous as vol
from datetime import timedelta
from bs4 import BeautifulSoup
import requests
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
from homeassistant.components.climate import (
ClimateEntity, PLATFORM_SCHEMA)
from homeassistant.components.climate.const import (SUPPORT_TARGET_TEMPERATURE, HVAC_MODE_OFF, HVAC_MODE_HEAT,CURRENT_HVAC_HEAT,CURRENT_HVAC_IDLE)
from homeassistant.const import (
CONF_ID, CONF_NAME, ATTR_TEMPERATURE, CONF_PASSWORD,
CONF_USERNAME, TEMP_CELSIUS, CONF_DEVICES)
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=60)
#: Interval in hours that module will try to refresh data from the climote.
CONF_REFRESH_INTERVAL = 'refresh_interval'
NOCHANGE = 'nochange'
DOMAIN = 'climote'
ICON = "mdi:thermometer"
MAX_TEMP = 35
MIN_TEMP = 5
#SUPPORT_FLAGS = (SUPPORT_ON_OFF | SUPPORT_TARGET_TEMPERATURE)
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
SUPPORT_MODES = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
#DEVICE_SCHEMA = vol.Schema({
# vol.Required(CONF_ID): cv.positive_int,
# vol.Optional(CONF_NAME): cv.string,
#}, extra=vol.ALLOW_EXTRA)
def validate_name(config):
"""Validate device name."""
if CONF_NAME in config:
return config
climoteid = config[CONF_ID]
name = 'climote_{}'.format(climoteid)
config[CONF_NAME] = name
return config
# CONFIG_SCHEMA = vol.Schema(
# {
# DOMAIN: vol.Schema(
# {
# vol.Required(CONF_USERNAME): cv.string,
# vol.Required(CONF_PASSWORD): cv.string,
# vol.Required(CONF_ID): cv.string,
# vol.Optional(CONF_REFRESH_INTERVAL, default=24): cv.string,
# }
# )
# },
# extra=vol.ALLOW_EXTRA,
# )
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_ID): cv.string,
vol.Optional(CONF_REFRESH_INTERVAL, default=24): cv.string,
# vol.Required(CONF_DEVICES):
# vol.Schema({cv.string: DEVICE_SCHEMA})
})
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the ephember thermostat."""
_LOGGER.info('Setting up climote platform')
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
climoteid = config.get(CONF_ID)
interval = int(config.get(CONF_REFRESH_INTERVAL))
# Add devices
climote = ClimoteService(username, password, climoteid)
if not (climote.initialize()):
return False
entities = []
for id, name in climote.zones.items():
entities.append(Climote(climote, id, name, interval))
add_entities(entities)
return
#new function added
# async def async_setup(hass, config):
# """Set up Climote components."""
# if DOMAIN not in config:
# return True
# conf = config[DOMAIN]
# config_flow.register_flow_implementation(
# hass, conf[CONF_CLIENT_ID], conf[CONF_CLIENT_SECRET]
# )
# return True
# async def async_setup_entry(hass, config, async_add_entities,
# discovery_info=None):
# """Set up the climote platform."""
# _LOGGER.info('Setting up climote platform')
# _LOGGER.info('usernamekey:%s', CONF_USERNAME)
# username = config.get(CONF_USERNAME)
# password = config.get(CONF_PASSWORD)
# interval = int(config.get(CONF_REFRESH_INTERVAL))
# # Add devices
# climote = ClimoteService(username, password, climoteid)
# if not (climote.initialize()):
# return False
# entities = []
# for id, name in climote.zones.items():
# entities.append(Climote(climote, id, name, interval))
# add_entities(entities)
class Climote(ClimateEntity):
"""Representation of a Climote device."""
def __init__(self, climoteService, zoneid, name, interval):
"""Initialize the thermostat."""
_LOGGER.info('Initialize Climote Entity')
self._climote = climoteService
self._zoneid = zoneid
self._name = name
self._force_update = False
self.throttled_update = Throttle(timedelta(hours=interval))(self._throttled_update)
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def hvac_mode(self):
# """Return current operation ie. heat, cool, idle."""
# return 'idle'
"""Return current operation. ie. heat, idle."""
zone = "zone" + str(self._zoneid)
return 'heat' if self._climote.data[zone]["status"] == '5' else 'idle'
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
return SUPPORT_MODES
@property
def name(self):
"""Return the name of the thermostat."""
return self._name
@property
def icon(self):
"""Return the icon to use in the frontend."""
return ICON
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
return 1
@property
def current_temperature(self):
zone = "zone" + str(self._zoneid)
_LOGGER.info("current_temperature: Zone: %s, Temp %s C",
zone, self._climote.data[zone]["temperature"])
return int(self._climote.data[zone]["temperature"]) \
if self._climote.data[zone]["temperature"] != '--' else 0
# @property
# def is_on(self):
# """Return current operation. ie. heat, idle."""
# zone = "zone" + str(self._zoneid)
# return True if self._climote.data[zone]["status"] == '5' else False
@property
def min_temp(self):
"""Return the minimum temperature."""
return MIN_TEMP
@property
def max_temp(self):
"""Return the maximum temperature."""
return MAX_TEMP
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
zone = "zone" + str(self._zoneid)
_LOGGER.info("target_temperature: %s",
self._climote.data[zone]["thermostat"])
return int(self._climote.data[zone]["thermostat"])
@property
def hvac_action(self):
"""Return current operation."""
zone = "zone" + str(self._zoneid)
return CURRENT_HVAC_HEAT if self._climote.data[zone]["status"] == '5' \
else CURRENT_HVAC_IDLE
def set_hvac_mode(self,hvac_mode):
if(hvac_mode==HVAC_MODE_HEAT):
"""Turn Heating Boost On."""
res = self._climote.boost(self._zoneid, 0.5)
self._force_update = True
return res
if(hvac_mode==HVAC_MODE_OFF):
# def turn_off(self):
"""Turn Heating Boost Off."""
res = self._climote.boost(self._zoneid, 0)
if(res):
self._force_update = True
return res
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
res = self._climote.set_target_temperature(1, temperature)
if(res):
self._force_update = True
return res
async def async_update(self):
"""Get the latest state from the thermostat."""
#if self._force_update:
# asyncio.run_coroutine_threadsafe(throttled_update(hass,self,no_throttle=True) , hass.loop)
# self._force_update = False
#else:
# asyncio.run_coroutine_threadsafe(throttled_update(hass,target,no_throttle=False) , hass.loop)
async def _throttled_update(self, **kwargs):
"""Get the latest state from the thermostat with a throttle."""
_LOGGER.info("_throttled_update Force: %s", self._force_update)
self._climote.updateStatus(self._force_update)
class IllegalStateException(RuntimeError):
def __init__(self, arg):
self.args = arg
_DEFAULT_JSON = (
'{ "holiday": "00", "hold": null, "updated_at": "00:00", '
'"unit_time": "00:00", "zone1": { "burner": 0, "status": null, '
'"temperature": "0", "thermostat": 0 }, "zone2": { "burner": 0, '
'"status": "0", "temperature": "0", "thermostat": 0 }, '
'"zone3": { "burner": 0, "status": null, "temperature": "0", '
'"thermostat": 0 } }')
_LOGIN_URL = 'https://climote.climote.ie/manager/login'
_LOGOUT_URL = 'https://climote.climote.ie/manager/logout'
_SCHEDULE_ELEMENT = '/manager/edit-heating-schedule?heatingScheduleId'
_STATUS_URL = 'https://climote.climote.ie/manager/get-status'
_STATUS_FORCE_URL = _STATUS_URL + '?force=1'
_STATUS_RESPONSE_URL = ('https://climote.climote.ie/manager/'
'waiting-get-status-response')
_BOOST_URL = 'https://climote.climote.ie/manager/boost'
_SET_TEMP_URL = 'https://climote.climote.ie/manager/temperature'
_GET_SCHEDULE_URL = ('https://climote.climote.ie/manager/'
'get-heating-schedule?heatingScheduleId=')
class ClimoteService:
def __init__(self, username, password, passcode):
self.s = requests.Session()
self.s.headers.update({'User-Agent':
'Mozilla/5.0 Home Assistant Climote Service'})
self.config_id = None
self.config = None
self.logged_in = False
self.creds = {'password': username, 'username': passcode, 'passcode':password}
self.data = json.loads(_DEFAULT_JSON)
self.zones = None
def initialize(self):
try:
self.__login()
self.__setConfig()
self.__setZones()
# if not self.__updateStatus(False):
# self.__updateStatus(True)
return True if(self.config is not None) else False
finally:
self.__logout()
def __login(self):
r = self.s.post(_LOGIN_URL, data=self.creds)
if(r.status_code == requests.codes.ok):
# Parse the content
soup = BeautifulSoup(r.content, "lxml")
input = soup.find("input") # First input has token "cs_token_rf"
if (len(input['value']) < 2):
return False
self.logged_in = True
self.token = input['value']
_LOGGER.info("Token: %s", self.token)
#anchors = soup.findAll("a", href=True)
#for a in anchors:
# href = a['href']
# str = href
# if (str.startswith(_SCHEDULE_ELEMENT)):
# cut = str.find('&startday')
# str2 = str[:-(len(str)-cut)]
# self.config_id = str2[49:]
# _LOGGER.debug('heatingScheduleId:%s', self.config_id)
#the link was commented in latest Climote page, doing simple read from page content
str = r.text
sched = str.find(_SCHEDULE_ELEMENT)
if (sched):
cut = str.find('&startday',sched)
str2 = str[sched:-(len(str)-cut)]
self.config_id = str2[49:]
_LOGGER.debug('heatingScheduleId:%s', self.config_id)
return self.logged_in
def __logout(self):
_LOGGER.info('Logging Out')
r = self.s.get(_LOGOUT_URL)
_LOGGER.debug('Logging Out Result: %s', r.status_code)
return r.status_code == requests.codes.ok
def boost(self, zoneid, time):
_LOGGER.info('Boosting Zone %s', zoneid)
return self.__boost(zoneid, time)
def updateStatus(self, force):
try:
self.__login()
self.__updateStatus(force)
finally:
self.__logout()
def __updateStatus(self, force):
def is_done(r):
return r.text != '0'
res = None
tmp = self.s.headers
try:
# Make the initial request (force the update)
if(force):
r = self.s.post(_STATUS_FORCE_URL, data=self.creds)
else:
r = self.s.post(_STATUS_URL, data=self.creds)
# Poll for the actual result. It happens over SMS so takes a while
self.s.headers.update({'X-Requested-With': 'XMLHttpRequest'})
r = polling.poll(
lambda: self.s.post(_STATUS_RESPONSE_URL,
data=self.creds),
step=10,
check_success=is_done,
poll_forever=False,
timeout=120
)
if(r.text == '0'):
res = False
else:
self.data = json.loads(r.text)
_LOGGER.info('Data Response %s', self.data)
res = True
except polling.TimeoutException:
res = False
finally:
self.s.headers = tmp
return res
def __setConfig(self):
if(self.logged_in is False):
raise IllegalStateException("Not logged in")
r = self.s.get(_GET_SCHEDULE_URL
+ self.config_id)
data = r.content
xml = ET.fromstring(data)
self.config = xmljson.parker.data(xml)
_LOGGER.debug('Config:%s', self.config)
def __setZones(self):
if(self.config is None):
return
zones = {}
i = 0
_LOGGER.debug('zoneInfo: %s', self.config["zoneInfo"]["zone"])
for zone in self.config["zoneInfo"]["zone"]:
i += 1
if(zone["active"] == 1):
zones[i] = zone["label"]
self.zones = zones
def set_target_temperature(self, zone, temp):
_LOGGER.debug('set_temperature zome:%s, temp:%s', zone, temp)
res = False
try:
self.__login()
data = {
'temp-set-input[' + str(zone) + ']': temp,
'do': 'Set',
'cs_token_rf': self.token
}
r = self.s.post(_SET_TEMP_URL, data=data)
_LOGGER.info('set_temperature: %d', r.status_code)
res = r.status_code == requests.codes.ok
finally:
self.__logout()
return res
def __boost(self, zoneid, time):
"""Turn on the heat for a given zone, for a given number of hours"""
res = False
try:
self.__login()
data = {
'zoneIds[' + str(zoneid) + ']': time,
'cs_token_rf': self.token
}
r = self.s.post(_BOOST_URL, data=data)
_LOGGER.info('Boosting Result: %d', r.status_code)
res = r.status_code == requests.codes.ok
finally:
self.__logout()
return res
I would say is a problem with configuration.yml. My config looks like this:
climate:
- platform: climote
username: !secret climote_user
password: !secret climote_pass
id: !secret climote_id
refresh_interval: 4
That’s how I have mine set up too. The error I’m receiving is pointing towards the integration not being set up by the .py file for some reason.
@andy73 how does your init.py file look? Is it the same as this or am I missing lines?
"""Setup for CLimote"""
I wish I would have some time to make a proper HACS integration out of this code
I can see now that your file name is correct, one thing that I do not have in my climote folder is the .gitignore. the pycache is probably created by the os…
I’ll see if removing the gitignore file helps. Outside of that, I’m stumped. Might be time to learn python properly and dig into the python code. *Cries in yaml"
https://drive.google.com/file/d/1f2DjbwI82o1oIQHfO7ve1OUxyrIVdpT8/view?usp=sharing
there is what I have so you can compare with yours.
There must have been something wrong with the code I pulled from Github. Using your files, I was finally able to add the config and restart. However, I’m not seeing any new entities or devices yet so I’ll need to look at that this evening. Thanks for your help @andy73
I do not use those entities directly - I have created my own helpers and manage all via automation to call services to start and stop specific area
alias: Boost_bedrooms_on
description: ""
trigger:
- platform: state
entity_id: input_boolean.boost_bedrooms
to: "on"
condition: []
action:
- service: timer.start
data:
duration: "0"
target:
entity_id: timer.heating_bedrooms
- service: climate.set_hvac_mode
data:
hvac_mode: heat
entity_id: climate.bedrooms
mode: single
alias: Boost_bedrooms_off
description: ""
trigger:
- platform: state
entity_id: input_boolean.boost_bedrooms
to: "off"
condition: []
action:
- service: timer.finish
data: {}
entity_id: timer.heating_bedrooms
- service: climate.set_hvac_mode
data:
hvac_mode: "off"
entity_id: climate.bedrooms
mode: single
the new changes in the Climote app broke also our custom integration. Trying to figure out what we can change to get it back to work. I have also contacted Climote maybe they put a new API in place…
WIll keep in touch
very odd, it started to work, it might be the high number of 504 gateway timeout errors that I see in the browser.
Also just to mention that the new Climote app is asking you to change the password. The password on the web and app seems different, I have set them both to be the same.
hi Andy73 and Criticalan,
I had this integration working a year ago. Since then I reinstalled HA and didn’t set Climote up again. I know it was working for me before, but by following github, I’m getting same issue as Criticalan reported above: Creating a custom integration (Climote) - #44 by Criticalan
I also compared the files provided by Andy73 with the Github ones and found some differences.
climate.py: Andy’s on the right
manifest.json: Andy’s on the right
So I replaced the above Github files with Andy’s and got the same issue
Your help would be appreciated guys.
I’ve updated the last code I have which has that version in the manifest. climote.zip - Google Drive
You need also to re-install the application and then reset the password, log in on www.climote.ie and log in there with the old password and update it to use the same password like the app (for consistency).
my yml is:
climate:
- platform: climote
username: !secret climote_user
password: !secret climote_pass
id: !secret climote_id
refresh_interval: 4
timer:
heating_living:
name: Boost first floor remaining
duration: '00:30:00'
heating_bedrooms:
name: Boost bedrooms remaining
duration: '00:30:00'
heating_water:
name: Boost water remaining
duration: '00:30:00'
input_boolean:
boost_living:
name: Boost ground floor heating
initial: off
boost_bedrooms:
name: Boost bedrooms heating
initial: off
boost_water:
name: Boost water heating
initial: off
Means you need to update your Climote app on the phone that would request a new password.