Besmart custom_components climate

the solution was simple…the platform is with a lower b so besmart not Besmart :crazy_face:

now I am checking why I am seeing -17.8 - Off and Idle even though I have 22 degrees and it’s on…I checked the logs and it was because of the room, you have to set the same as the one in your Besmart phone application
thanks for this integration @etreus

Hi @etreus, there is no way to control the thermostat locally and not through their cloud?
the WI-FI BOX does not expose a local server?

thanks.

1 Like

Hi, i just found that the only opened port of the box is 80.
You can reach it via browser but it asks for a user and password , nothing i tryed worked.

hello e treus thanks for having done this integration, I’m new to Home assistant, could you explain better how to upload your integration?
where should i create the folder?
Why can’t I find the home / homeassistant path …
thanks for the help

hello @nicolamodo88,

you can install the component in 2 ways:

  1. by using HACS (there is a pretty good step by step guide made by @Gutek here Besmart custom_components climate )
  2. manually by following the steps listed here Besmart custom_components climate

then you have to configure

climate:
  - platform: besmart
    name: Besmart Thermostat
    username: <my-username>
    password: <my-password>
    room: <name assigned using BeSmart Application>
    scan_interval: 10

@faxbio

I did several check but I did not found how manage the communication locally, sniffing the box traffic I have found that it is doing polling here http://api.besmart-home.com/fwUpgrade/PR06549/version.txt then I have found the firmware and using this you can find the username and password strings -n 10 be_smart_0654917080101.bin > string.out but is quite useless since the webUI is working bad and has no smart function

if someone would like play whit his thermostat herewith the standalone version just set CONF_USERNAME and CONF_PASSWORD copy it in a file (you need requests module installed)

#! /usr/bin/env python

import logging
from datetime import datetime, timedelta
import requests

_LOGGER = logging.getLogger(__name__)

logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)

# pylint: disable=abstract-method
# pylint: disable=too-many-instance-attributes
class Besmart(object):
    """Representation of a Besmart thermostat."""

    BASE_URL = "http://www.besmart-home.com/Android_vokera_20160516/"
    LOGIN = "login.php"
    ROOM_MODE = "setRoomMode.php"
    ROOM_LIST = "getRoomList.php?deviceId={0}"
    ROOM_DATA = "getRoomData196.php?therId={0}&deviceId={1}"
    ROOM_PROGRAM = "getProgram.php?roomId={0}"
    ROOM_TEMP = "setRoomTemp.php"
    ROOM_ECON_TEMP = "setEconTemp.php"
    ROOM_FROST_TEMP = "setFrostTemp.php"
    ROOM_CONF_TEMP = "setComfTemp.php"
    GET_SETTINGS = "getSetting.php"
    SET_SETTINGS = "setSetting.php"

    def __init__(self, username, password):
        """Initialize the thermostat."""
        self._username = username
        self._password = password
        self._lastupdate = None
        self._device = None
        self._rooms = None
        self._timeout = 30
        self._s = requests.Session()

    def _fahToCent(self, temp):
        return str(round((temp - 32.0) / 1.8, 1))

    def _centToFah(self, temp):
        return str(round(32.0 + (temp * 1.8), 1))

    def login(self):
        try:
            resp = self._s.post(
                self.BASE_URL + self.LOGIN,
                data={"un": self._username, "pwd": self._password, "version": "32"},
                timeout=self._timeout,
            )
            if resp.ok:
                self._device = resp.json()
        except Exception as ex:
            _LOGGER.warning(ex)
            self._device = None

    def rooms(self):
        if not self._device:
            self.login()

        try:
            if self._device:
                resp = self._s.post(
                    self.BASE_URL + self.ROOM_LIST.format(self._device.get("deviceId")),
                    timeout=self._timeout,
                )
                if resp.ok:
                    self._lastupdate = datetime.now()
                    self._rooms = dict(
                        (y.get("name").lower(), y)
                        for y in filter(lambda x: x.get("id") != None, resp.json())
                    )
                    _LOGGER.debug("rooms: {}".format(self._rooms))
                    if len(self._rooms) == 0:
                        self._device = None
                        self._lastupdate = None
                        return None

                    return self._rooms
                else:
                    _LOGGER.debug("get rooms failed!")
        except Exception as ex:
            _LOGGER.warning(ex)
            self._device = None

        return None

    def roomdata(self, room):
        self.login()
        try:
            if self._device:
                resp = self._s.get(
                    self.BASE_URL
                    + self.ROOM_DATA.format(
                        room.get("therId"), self._device.get("deviceId")
                    ),
                    timeout=self._timeout,
                )
                if resp.ok:
                    return resp.json()
                else:
                    _LOGGER.debug("refresh roomdata failed for: {}".format(room))
        except Exception as ex:
            _LOGGER.warning(ex)
            self._device = None

        return None

    def program(self, room):
        self.login()
        try:
            resp = self._s.get(
                self.BASE_URL + self.ROOM_PROGRAM.format(room.get("id")),
                timeout=self._timeout,
            )
            if resp.ok:
                return resp.json()
        except Exception as ex:
            _LOGGER.warning(ex)
            self._device = None
        return None

    def roomByName(self, name):
        if self._lastupdate is None or datetime.now() - self._lastupdate > timedelta(
            seconds=120
        ):
            _LOGGER.debug("refresh rooms state")
            self.rooms()

        if self._rooms:
            return self.roomdata(self._rooms.get(name.lower()))
        return None

    def setRoomMode(self, room_name, mode):
        room = self.roomByName(room_name)

        if self._device and room:
            data = {
                "deviceId": self._device.get("deviceId"),
                "therId": room.get("roomMark"),
                "mode": mode,
            }

            resp = self._s.post(
                self.BASE_URL + self.ROOM_MODE, data=data, timeout=self._timeout
            )
            if resp.ok:
                msg = resp.json()
                _LOGGER.debug("resp: {}".format(msg))
                if msg.get("error") == 1:
                    return True

        return None

    def setRoomConfortTemp(self, room_name, new_temp):
        return self.setRoomTemp(room_name, new_temp, self.ROOM_CONF_TEMP)

    def setRoomECOTemp(self, room_name, new_temp):
        return self.setRoomTemp(room_name, new_temp, self.ROOM_ECON_TEMP)

    def setRoomFrostTemp(self, room_name, new_temp):
        return self.setRoomTemp(room_name, new_temp, self.ROOM_FROST_TEMP)

    def setRoomTemp(self, room_name, new_temp, url=None):
        url = url or self.ROOM_TEMP
        room = self.roomByName(room_name)
        if room and self._device.get("deviceId"):
            new_temp = round(new_temp, 1)
            _LOGGER.debug("room: {}".format(room))

            if room.get("tempUnit") in {"N/A", "0"}:
                tpCInt, tpCIntFloat = str(new_temp).split(".")
            else:
                tpCInt, tpCIntFloat = self._fahToCent(new_temp).split(".")

            _LOGGER.debug(
                "setRoomTemp: {} - {} - {}".format(new_temp, tpCInt, tpCIntFloat)
            )

            data = {
                "deviceId": self._device.get("deviceId"),
                "therId": room.get("roomMark"),
                "tempSet": tpCInt + "",
                "tempSetFloat": tpCIntFloat + "",
            }
            _LOGGER.debug("url: {}".format(self.BASE_URL + url))
            _LOGGER.debug("data: {}".format(data))
            resp = self._s.post(self.BASE_URL + url, data=data, timeout=self._timeout)
            if resp.ok:
                msg = resp.json()
                _LOGGER.debug("resp: {}".format(msg))
                if msg.get("error") == 1:
                    return True
        else:
            _LOGGER.warning("error on get the room by name: {}".format(room_name))

        return None

    def getSettings(self, room_name):
        room = self.roomByName(room_name)

        if self._device and room:
            data = {
                "deviceId": self._device.get("deviceId"),
                "therId": room.get("roomMark"),
            }

            resp = self._s.post(
                self.BASE_URL + self.GET_SETTINGS, data=data, timeout=self._timeout
            )
            if resp.ok:
                msg = resp.json()
                _LOGGER.debug("resp: {}".format(msg))
                if msg.get("error") == 0:
                    return msg

        return None

    def setSettings(self, room_name, season):
        room = self.roomByName(room_name)

        if self._device and room:
            old_data = self.getSettings(room_name)
            if old_data.get("error") == 0:
                min_temp_set_point_ip, min_temp_set_point_fp = str(
                    old_data.get("minTempSetPoint", "30.0")
                ).split(".")
                max_temp_set_point_ip, max_temp_set_point_fp = str(
                    old_data.get("maxTempSetPoint", "30.0")
                ).split(".")
                temp_curver_ip, temp_curver_fp = str(
                    old_data.get("tempCurver", "0.0")
                ).split(".")
                data = {
                    "deviceId": self._device.get("deviceId"),
                    "therId": room.get("roomMark"),
                    "minTempSetPointIP": min_temp_set_point_ip,
                    "minTempSetPointFP": min_temp_set_point_fp,
                    "maxTempSetPointIP": max_temp_set_point_ip,
                    "maxTempSetPointFP": max_temp_set_point_fp,
                    "sensorInfluence": old_data.get("sensorInfluence", "0"),
                    "tempCurveIP": temp_curver_ip,
                    "tempCurveFP": temp_curver_fp,
                    "unit": old_data.get("unit", "0"),
                    "season": season,
                    "boilerIsOnline": old_data.get("boilerIsOnline", "0"),
                }

                resp = self._s.post(
                    self.BASE_URL + self.SET_SETTINGS, data=data, timeout=self._timeout
                )
                if resp.ok:
                    msg = resp.json()
                    _LOGGER.debug("resp: {}".format(msg))
                    if msg.get("error") == 0:
                        return msg
        return None

if __name__ == "__main__":
    CONF_USERNAME=""
    CONF_PASSWORD=""
    client = Besmart(CONF_USERNAME, CONF_PASSWORD)
    client.rooms()

thanks i managed to install everything, now it works perfectly, it was my problem on hacs.
excellent application, I am a beretta technician forced to use BeSmart … unfortunately the owner app occasionally leaves something to be desired …

Since few HA updates I am getting this warning. Is this something that can be easly fixed by You etreus?

2021-04-26 11:29:25 WARNING (MainThread) [homeassistant.loader] No ‘version’ key in the manifest file for custom integration ‘Besmart’. As of Home Assistant 2021.6, this integration will no longer be loaded. Please report this to the maintainer of ‘Besmart’

I love Your component but I don’t want to be stuck on version 2021.5 forever :frowning:

Also did You have time to check if there is possibility to somehow track DHT heating time (indicated on besmart by blinking faucet and in mobile app by glowing faucet + fire) same as I am tracking central heating time?

You can add a line in the manifest to “bypass” the lack of the version, for example:

“version”: “0.9.9”

You wll not have the warning and you’ll be able to install new ha core version. Obviously is a workaround while waiting for the codeowner to update the “real” manifest with the “real” version number.

I have pushed a manifest with the version attribute let me know if it works, sorry but I have no enough time those days to play whit HA.

Salve a tutti, scusate la mia domanda da puro ignorante, ma ho installato il component solo che avendo 2 besmart, non riesco ad integrarli insieme, ma soltanto uno.

Mi sapreste indicare come fare?
Grazie mille

Hi guys, how can i set double climates with this component? The climates are under the same wifi and in the same house.

Thanks in advance

To configure more than 1 thermostat you just simply create 2 or more platform: besmart under the climate section of your yaml configuration.
Every section must have The room: parameter Like The name of the different room (thermostat) you gave in the besmart app.

@etreus how is Your free time nowadays? Any chance to dig into integration and try to pull DHT water heating notification/status which I mentioned Nov '20 It is also visible in besmart mobile app so the module definitely holds it somewhere

Hi @etreus ,
sorry for that, but to make it working locally, you suggested to change self._username and self._password, but I don’t get if CONF_USERNAME and CONF_PASSWORD are the value to set , that’s correct?

in order to connect locally do we need also to change the
BASE_URL = "http://www.besmart-home.com/Android_vokera_20160516/"
to point to local website?

regards

Hi guys, I’ve installed the integration some weeks ago and it worked great. Until today. I’ve updated home assistant to the last version (2022.2.0) and it stop working, I think that probably it’s a python issue. How can I fix the problem?
Thanks in Advance

This is what happens when I try to verify the configuration:

Platform error climate.besmart - cannot import name ‘ClimateDevice’ from ‘homeassistant.components.climate’ (/usr/src/homeassistant/homeassistant/components/climate/init.py)

Thanks

Did you solve it?
Or anyone else managed to upgrade to 2022.2.0 with no breaking change?

@Gianlu_Pi and @faxbio , ho ancora HA 2021, nel weekend provo ad aggiornare tutto e vedo se riesco a risolvere il problema