Wifi thermostat (Beok, Floureon, Beca Energy) component

Maybe a copy/past error, make sure you have the 4 spaces (" ") before the line

def handle_set_schedule(service):

But that strage because the line number should be 150, not 149… have you updated to the last file ?

yes its 4 spaces. this is what I have copied

"""
    Support for Chinese wifi thermostats (Floureon, Beok, Beca Energy)
    For more details about this platform, please refer to the documentation at
    https://home-assistant.io/components/climate.broadlink/
    """
import asyncio
import json
import logging

import voluptuous as vol

from homeassistant.components.climate import (
                                              DOMAIN, ClimateDevice,
                                              SUPPORT_OPERATION_MODE,
                                              SUPPORT_TARGET_TEMPERATURE,
                                              PLATFORM_SCHEMA, STATE_HEAT, STATE_OFF, STATE_AUTO)
from homeassistant.const import (
                                 TEMP_CELSIUS, ATTR_TEMPERATURE,
                                 CONF_NAME, CONF_HOST, CONF_MAC)
import homeassistant.helpers.config_validation as cv

_LOGGER = logging.getLogger(__name__)

REQUIREMENTS = ['broadlink==0.9.0', 'BroadlinkWifiThermostat==1.0.1']

DEFAULT_NAME = 'broadlink'
POWER_ON = 1
POWER_OFF = 0
AUTO = 1
MANUAL = 0
CONF_MODE_LIST = 'modes'
CONF_MIN_TEMP = 'min_temp'
CONF_MAX_TEMP = 'max_temp'
CONF_ADVANCED_CONFIG = 'advanced_config'
CONF_SCHEDULE_WEEKDAY = 'schedule_week_day'
CONF_SCHEDULE_WEEKEND = 'schedule_week_end'
CONF_WEEKDAY = "weekday"
CONF_WEEKEND = "weekend"

# Validation of the user's configuration
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
                                         vol.Required(CONF_HOST): cv.string,
                                         vol.Required(CONF_MAC): cv.string,
                                         vol.Required(CONF_NAME): cv.string,
                                         vol.Optional(CONF_MIN_TEMP, default=5): cv.positive_int,
                                         vol.Optional(CONF_MAX_TEMP, default=35): cv.positive_int,
                                         vol.Optional(CONF_ADVANCED_CONFIG,
                                                      default='{"loop_mode": 0, '
                                                      '"sen": 0, '
                                                      '"osv": 42, '
                                                      '"dif": 2, '
                                                      '"svh": 35, '
                                                      '"svl": 5, '
                                                      '"adj": 0, '
                                                      '"fre": 1, '
                                                      '"pon": 0}'): cv.string,
                                         vol.Optional(CONF_SCHEDULE_WEEKDAY,
                                                      default='[{"start_hour":6, '
                                                      '"start_minute":30, '
                                                      '"temp":20}, '
                                                      '{"start_hour":9, '
                                                      '"start_minute":0, '
                                                      '"temp":17}, '
                                                      '{"start_hour":12, '
                                                      '"start_minute":0, '
                                                      '"temp":20}, '
                                                      '{"start_hour":14, '
                                                      '"start_minute":0, '
                                                      '"temp":17}, '
                                                      '{"start_hour":18, '
                                                      '"start_minute":0, '
                                                      '"temp":20}, '
                                                      '{"start_hour":22, '
                                                      '"start_minute":30, '
                                                      '"temp":17}]'): cv.string,
                                         vol.Optional(CONF_SCHEDULE_WEEKEND,
                                                      default='[{"start_hour":8, '
                                                      '"start_minute":30, '
                                                      '"temp":20}, '
                                                      '{"start_hour":23, '
                                                      '"start_minute":0, '
                                                      '"temp":17}]'): cv.string
                                         })

SET_SCHEDULE_SCHEMA = vol.Schema({
                                 vol.Required(CONF_WEEKDAY,
                                              default='[{'
                                              '"start_hour":6, '
                                              '"start_minute":30, '
                                              '"temp":20}, '
                                              '{"start_hour":9, '
                                              '"start_minute":0, '
                                              '"temp":17}, '
                                              '{"start_hour":12, '
                                              '"start_minute":0, '
                                              '"temp":20}, '
                                              '{"start_hour":14, '
                                              '"start_minute":0, '
                                              '"temp":17}, '
                                              '{"start_hour":18, '
                                              '"start_minute":0, '
                                              '"temp":20}, '
                                              '{"start_hour":22, '
                                              '"start_minute":30, '
                                              '"temp":17}]'): cv.string,
                                 vol.Required(CONF_WEEKEND,
                                              default='[{"start_hour":8, '
                                              '"start_minute":30, '
                                              '"temp":20}, '
                                              '{"start_hour":23, '
                                              '"start_minute":0, '
                                              '"temp":17}]'): cv.string
                                 })

SET_ADVANCED_CONF_SCHEMA = vol.Schema({
                                      vol.Required(CONF_ADVANCED_CONFIG,
                                                   default='{"loop_mode": 0, '
                                                   '"sen": 0, '
                                                   '"osv": 42, '
                                                   '"dif": 2, '
                                                   '"svh": 35, '
                                                   '"svl": 5, '
                                                   '"adj": 0, '
                                                   '"fre": 1, '
                                                   '"pon": 0}'): cv.string,
                                      })


def setup_platform(hass, config, add_devices, discovery_info=None):
    """Set up the broadlink thermostat platform."""
    import BroadlinkWifiThermostat
    wifi_thermostat = BroadlinkWifiThermostat.\
        Thermostat(config[CONF_MAC],
                   config[CONF_HOST],
                   config[CONF_NAME],
                   config[CONF_ADVANCED_CONFIG],
                   config[CONF_SCHEDULE_WEEKDAY],
                   config[CONF_SCHEDULE_WEEKDAY],
                   config[CONF_MIN_TEMP],
                   config[CONF_MAX_TEMP],
                   STATE_OFF,
                   STATE_HEAT,
                   STATE_AUTO
                   )

    add_devices([BroadlinkThermostat(wifi_thermostat)], True)

@asyncio.coroutine
    def handle_set_schedule(service):
        """Handle data for the set_schedule service call."""
        schedule_wd = service.data.get(CONF_WEEKDAY)
        schedule_we = service.data.get(CONF_WEEKEND)
        wifi_thermostat.set_schedule(
                                     {CONF_WEEKDAY:
                                     json.loads(schedule_wd.replace("'", '"')),
                                     CONF_WEEKEND:
                                     json.loads(schedule_we.replace("'", '"'))})
    
    hass.services.register(DOMAIN,
                           'set_schedule',
                           handle_set_schedule,
                           schema=SET_SCHEDULE_SCHEMA)
        
                           @asyncio.coroutine
                           def handle_set_advanced_conf(service):
                               """Handle data for the set_advanced_conf service call."""
                                   advanced_conf = service.data.get(CONF_ADVANCED_CONFIG)
                                   wifi_thermostat.set_advanced_config(
                                                                       json.loads(advanced_conf.replace("'", '"')))

hass.services.register(DOMAIN,
                       'set_advanced_conf',
                       handle_set_advanced_conf,
                       schema=SET_ADVANCED_CONF_SCHEMA)

_LOGGER.debug("Wifi Thermostat: Component successfully added !")


class BroadlinkThermostat(ClimateDevice):
    """Representation of a Broadlink Thermostat device."""
    
    def __init__(self, device):
        """Initialize the climate device."""
        self._device = device
    
    @property
    def state(self):
        return self._device.current_operation
    
    @property
    def supported_features(self):
        """Return the list of supported features."""
        return SUPPORT_TARGET_TEMPERATURE \
            | SUPPORT_OPERATION_MODE

@property
    def should_poll(self):
        """Return the polling state."""
        return True
    
    @property
    def name(self):
        """Return the name of the thermostat."""
        return self._device.name
    
    @property
    def temperature_unit(self):
        """Return the unit of measurement."""
        return TEMP_CELSIUS
    
    @property
    def current_temperature(self):
        """Return the sensor temperature."""
        return self._device.current_temp
    
    @property
    def target_temperature(self):
        """Return the temperature we try to reach."""
        return self._device.target_temperature
    
    @property
    def target_temperature_high(self):
        """Return the highbound target temperature we try to reach."""
        return self._device.max_temp
    
    @property
    def target_temperature_low(self):
        """Return the lowbound target temperature we try to reach."""
        return self._device.min_temp
    
    @property
    def current_operation(self):
        """Return current operation."""
        return self._device.current_operation
    
    @property
    def operation_list(self):
        """List of available operation modes."""
        return [STATE_AUTO, STATE_HEAT, STATE_OFF]
    
    def set_temperature(self, **kwargs):
        """Set new target temperature."""
        if kwargs.get(ATTR_TEMPERATURE) is not None:
            self._device.set_temperature(kwargs.get(ATTR_TEMPERATURE))
        self.schedule_update_ha_state()
    
    def set_operation_mode(self, operation_mode):
        """Set operation mode."""
        self._device.set_operation_mode(operation_mode)
        self.schedule_update_ha_state()
    
    @property
    def is_on(self):
        """Return true if the device is on."""
        return False if self._device.current_operation == STATE_OFF else True
    
    def set_advance_config(self, config_json):
        """Set the thermostat advanced config"""
        self._device.set_advanced_config(json.loads(config_json))
        self.schedule_update_ha_state()
    
    def set_schedule(self, schedule_json):
        """Set the thermostat schedule"""
        self._device.set_schedule(json.loads(schedule_json))
        self.schedule_update_ha_state()
    
    def update(self):
        """Update component data"""
        self._device.read_status()

still receiving errors

2018-09-24 21:27:46 ERROR (MainThread) [aiohttp.server] Error handling request
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/aiohttp/web_protocol.py", line 390, in start
    resp = await self._request_handler(request)
  File "/usr/local/lib/python3.6/site-packages/aiohttp/web_app.py", line 366, in _handle
    resp = await handler(request)
  File "/usr/local/lib/python3.6/site-packages/aiohttp/web_middlewares.py", line 106, in impl
    return await handler(request)
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/http/static.py", line 66, in staticresource_middleware
    return await handler(request)
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/http/real_ip.py", line 34, in real_ip_middleware
    return await handler(request)
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/http/ban.py", line 67, in ban_middleware
    return await handler(request)
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/http/auth.py", line 68, in auth_middleware
    return await handler(request)
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/http/real_ip.py", line 34, in real_ip_middleware
    return await handler(request)
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/http/ban.py", line 67, in ban_middleware
    return await handler(request)
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/http/auth.py", line 68, in auth_middleware
    return await handler(request)
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/http/view.py", line 113, in handle
    result = await result
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/config/core.py", line 24, in post
    errors = yield from async_check_ha_config_file(request.app['hass'])
  File "/usr/local/lib/python3.6/site-packages/homeassistant/config.py", line 793, in async_check_ha_config_file
    check_ha_config_file, hass)
  File "/usr/local/lib/python3.6/concurrent/futures/thread.py", line 56, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/local/lib/python3.6/site-packages/homeassistant/scripts/check_config.py", line 372, in check_ha_config_file
    platform = loader.get_platform(hass, domain, p_name)
  File "/usr/local/lib/python3.6/site-packages/homeassistant/loader.py", line 62, in get_platform
    return get_component(hass, PLATFORM_FORMAT.format(domain, platform))
  File "/usr/local/lib/python3.6/site-packages/homeassistant/loader.py", line 94, in get_component
    module = importlib.import_module(path)
  File "/usr/local/lib/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 674, in exec_module
  File "<frozen importlib._bootstrap_external>", line 781, in get_code
  File "<frozen importlib._bootstrap_external>", line 741, in source_to_code
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/config/custom_components/climate/broadlink.py", line 150
    def handle_set_schedule(service):
    ^
IndentationError: unexpected indent

This is my folder structure.

@mufidsyed:
It seems you have somsming wierd with your copy/past, maybe i’ts from github,
Try whith this: https://raw.githubusercontent.com/clementTal/home-assistant/add_broadlink_climate/homeassistant/components/climate/broadlink.py
(right click, save as)

you were right. some weird copy paste error. now all that is done it is starting up. but now I have a new problem.

2018-09-24 22:03:23 WARNING (MainThread) [homeassistant.helpers.entity] Update of climate.main_hall is taking over 10 seconds
2018-09-24 22:03:23 ERROR (SyncWorker_18) [BroadlinkWifiThermostat] read_status timeout
2018-09-24 22:03:34 WARNING (MainThread) [homeassistant.helpers.entity] Update of climate.master_bedroom is taking over 10 seconds
2018-09-24 22:03:34 ERROR (SyncWorker_5) [BroadlinkWifiThermostat] read_status timeout

I am trying to fix this but do you have any idea??

I have the below in my config,yaml.

climate:
  - platform: broadlink
    name: "main_hall"
    mac: "XX:XX:XX:XX:XX:XX"
    host: "192.168.1.106"
  - platform: broadlink
    name: "master_bedroom"
    mac: "XX:XX:XX:XX:XX:XX"
    host: "192.168.1.204"

the Mac address is hidden and I have used the ip for the thermostat from my router for the host address

It’s happend sometimes, maybe it’s the thermostat that could not handle request…
Anyways, it work with it (requests are sends every 30 seconds).

Can you control the temp/mode ?

I tried doing set temperature through developer service and I got timeout on my logs… still the status shows unavailable…

What is the thermostat you use ?
Are you sure about the Mac and ip address ?
It seems they’re communication errors…

It’s beca. the model number is BAC-002ALW. For the Mac address does caps matter. Also in the beca app it shows the external IP… I’m using internal IP

:thinking:
Can you send debug logs ?
(Select debug on config.yaml)

This is what the log shows.

2018-09-25 10:53:46 DEBUG (SyncWorker_12) [BroadlinkWifiThermostat] read_status

2018-09-25 10:53:56 WARNING (MainThread) [homeassistant.helpers.entity] Update of climate.master_bedroom is taking over 10 seconds

2018-09-25 10:53:56 ERROR (SyncWorker_12) [BroadlinkWifiThermostat] read_status timeout

2018-09-25 10:53:56 DEBUG (SyncWorker_5) [BroadlinkWifiThermostat] read_status

2018-09-25 10:54:06 WARNING (MainThread) [homeassistant.helpers.entity] Update of climate.main_hall is taking over 10 seconds

2018-09-25 10:54:06 ERROR (SyncWorker_5) [BroadlinkWifiThermostat] read_status timeout

The same here

2018-09-25 09:52:46 WARNING (MainThread) [homeassistant.components.climate] Setup of platform broadlink is taking over 10 seconds.
2018-09-25 09:52:46 ERROR (SyncWorker_2) [BroadlinkWifiThermostat] set_advanced_config timeout
2018-09-25 09:52:57 ERROR (SyncWorker_2) [BroadlinkWifiThermostat] set_schedule timeout
2018-09-25 09:53:07 ERROR (SyncWorker_39) [BroadlinkWifiThermostat] read_status timeout

Thermostat BHT-002-GBLW http://bit.ly/2zscERh
Beok application under ios working, firmware check returns
Connection Module: 2.2.6
Device Control Module: 1.0.0
Any idea?
Can soumeone post the working enviroment (hass.io/linux/python virtualenv …, versions etc.) so I can reproduce it here? I have linux server, windows/linux desktops, rpi3 and I really want to make it working :slight_smile:

I’ve checked the component in details, so basically it’s a broadlink switch that operates just like the MP1 or the SC1 ? It works on basic HTTP ports, no other need ?

I already have several Broadlink devices (I love the SC1 switches they work fine, integrate perfectly to HA and are inexpensive !).
Couldn’t buy the device before, I’m placing the order right now. I expect to test in 2 weeks now…

Has anyone found a solution for this

Hi all,

First let me say that clementTal did a very good job with his custom component for these chinese thermostats. I’m using it with a BEOK TOL-313 for electric heating and it’s running pretty well.

My question is: Does anyone know how to dynamically change the schedules config of the thermostat (week and week end periods), for example using the climate.set_schedule service? There is no identity argument witth this service, and I cannot figure out what could be the JSON data format do to it.

Any idea?
Thanks

Although this code is awesome, I don’t think it works with all Broadlink thermostats

So after a long wait for the parcel from China, I could finally test this yesterday.
I was ready for a while : custom_component installed, configuration prepared, just like I do with the Broadlink components (I love the SC1 switches).

So yes it works with the component, and it seems that it will work with my boiler (Wiessman that has over 15 years), as it’s actually a dry contact closed when it goes in heating mode. Too bad Broadlink doesn’t sell a SC1 version with this dry contact.

As I don’t need the logic in the thermostat but I want to control the beginning and the end of the heating from HA only, I still have to understand how to tweak the climate control to turn it dumb !

the component doesn’t seem to react to the turn_on and turn_off services, so I used the set_temperature service that contains also the operation_mode option.
Right now, I came up with this logic that seems to work :

- alias: 'Heating Start'
  trigger: 
     <many !>
  condition:
      <about the same !>
  action:
    - service: climate.set_temperature
      data:
        entity_id: climate.thermostat
        operation_mode: 'heat'
        temperature: 25
- alias: 'Stop Heating'
  trigger:
   ...
  condition:
  ...
  action:
    - service: climate.set_operation_mode
      data:
        entity_id: climate.thermostat
        operation_mode: 'off'

For the Beok 313 - In addition to this tip, I got rid of the all the component errors and finally got the Climate control to appear on the front end by deleting the folder “–pycache–” that gets generated in the custom component/climate folder.
And, also NOT using the the advanced part of the broadlink platform in the config. :slight_smile:

Nice! :+1:

This is also handy for anyone using Alexa - Simple script to enable Amazon Echo / Alexa to set the temperature on a climate / thermostat device via the emulated hue component

Hi guys,

Thanks for writing this up. I was wondering if you had any other documentation lying around regarding how the HEX actually needs to be composed? I’d like to try and write something else in another language and this would be extremely useful.

Many thanks

,
HI, i dont know why dont work for me.