From fac22293975c3b7574dd9b4b851f027e920fc761 Mon Sep 17 00:00:00 2001 From: mpex Date: Tue, 12 Apr 2016 20:32:53 +0200 Subject: [PATCH] Inital commit --- README.md | 30 ++++++++- eq3_control.py | 162 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 eq3_control.py diff --git a/README.md b/README.md index fc20562..de8b709 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,30 @@ # EQ3-Thermostat -Python script to control EQ3 Bluetooth Thermostat + + + +Library to control EQ3 BTLE Thermostats. + +It makes use of *gatttool* which is part of the *bluez* package. + +With the lib can do: + + * Read current temperature and lockstate from thermostat (if changed manually) + * Activate Boostmode: 300sec fully open valve + * Deactive Boostmode: Interrupt Boostmode earlier than 300sec + * Lock Thermostat: Disable manual mode + * Unlock Thermostat: Enable manual mode + * Switch between automatic, manual and eco mode (automatic schedule on thermostat side) + * Set Temperature Offset: Set an offset to measured temperature + * Set Day/Night Mode: Change between two preset values + * Change Window Open settings: Change temperature and duration + * Set Temperature: Self explanatory (given in celcius) + * Set Time: Set date/time on the thermostat + + +What the lib cannot do: + + * Check if device is really present. Right now no error handling is done, + as it is hard to determine wether the command was successful or not. + * Manipulation of the inbuilt programs. Not necessary for my needs + * The vacation function is not implemented + diff --git a/eq3_control.py b/eq3_control.py new file mode 100644 index 0000000..36f4371 --- /dev/null +++ b/eq3_control.py @@ -0,0 +1,162 @@ +#!/usr/bin/python3 +# -*-coding: utf-8 -*- + +import datetime +import subprocess +import time + + +class EQ3Thermostat(object): + + def __init__(self, address): + self.address = address + self.locked = False + self.temperature = 0 + self.update() + + def update(self): + """Reads the current temperature from the thermostat. We need to kill + the gatttool process as the --listen option puts it into an infinite + loop.""" + p = subprocess.Popen(["timeout", "-s", "INT", "2", "gatttool", "-b", + self.address, "--char-write-req", "-a", "0x0411", + "-n", "03", "--listen"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + value_string = out.decode("utf-8") + + if "Notification handle" in value_string: + value_string_splt = value_string.split() + temperature = value_string_splt[-1] + locked = value_string_splt[-4] + try: + subprocess.Popen.kill(p) + except ProcessLookupError: + pass + + if locked == "20": + self.locked = True + elif locked == "00": + self.locked = False + else: + print("Could not read lockstate of {}".format(self.address)) + + try: + self.temperature = int(temperature, 16) / 2 + except Exception as e: + print("Getting temperature of {} failed {}".format(self.address, e)) + + def activate_boostmode(self): + """Boostmode fully opens the thermostat for 300sec.""" + p = subprocess.Popen(["gatttool", "-b", self.address, "--char-write-req", + "-a", "0x0411", "-n", "4501"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + def deactivate_boostmode(self): + """Use only to stop boostmode before 300sec.""" + p = subprocess.Popen(["gatttool", "-b", self.address, "--char-write-req", + "-a", "0x0411", "-n", "4500"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + def set_automatic_mode(self): + """Put thermostat in automatic mode.""" + p = subprocess.Popen(["gatttool", "-b", self.address, "--char-write-req", + "-a", "0x0411", "-n", "4000"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + def set_manual_mode(self): + """Put thermostat in manual mode.""" + p = subprocess.Popen(["gatttool", "-b", self.address, "--char-write-req", + "-a", "0x0411", "-n", "4040"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + def set_eco_mode(self): + """Put thermostat in eco mode.""" + p = subprocess.Popen(["gatttool", "-b", self.address, "--char-write-req", + "-a", "0x0411", "-n", "4080"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + def lock_thermostat(self): + """Locks the thermostat for manual use.""" + p = subprocess.Popen(["gatttool", "-b", self.address, "--char-write-req", + "-a", "0x0411", "-n", "8001"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + def unlock_thermostat(self): + """Unlocks the thermostat for manual use.""" + p = subprocess.Popen(["gatttool", "-b", self.address, "--char-write-req", + "-a", "0x0411", "-n", "8000"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + def set_temperature(self, temperature): + """Transform the temperature in celcius to make it readable to the thermostat.""" + temperature = hex(int(2 * float(temperature)))[2:] + p = subprocess.Popen(["gatttool", "-b", self.address, "--char-write-req", + "-a", "0x0411", "-n", "41{}".format(temperature)], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + # Block for 3 secs to let the thermostat adjust the temperature + time.sleep(3) + + def set_temperature_offset(self, offset): + """Untested.""" + temperature = hex(int(2 * float(offset) + 7))[2:] + p = subprocess.Popen(["gatttool", "-b", self.address, "--char-write-req", + "-a", "0x0411", "-n", "13{}".format(temperature)], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + def set_day(self): + """Puts thermostat into day mode (sun icon).""" + p = subprocess.Popen(["gatttool", "-b", self.address, "--char-write-req", + "-a", "0x0411", "-n", "43"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + def set_night(self): + """Puts thermostat into night mode (moon icon).""" + p = subprocess.Popen(["gatttool", "-b", self.address, "--char-write-req", + "-a", "0x0411", "-n", "44"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + def set_day_night(self, night, day): + """Sets comfort temperature for day and night.""" + day = hex(int(2 * float(day)))[2:] + night = hex(int(2 * float(night)))[2:] + p = subprocess.Popen(["gatttool", "-b", self.address, "--char-write-req", + "-a", "0x0411", "-n", "11{}{}".format(day, night)], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + def set_windows_open(self, temperature, duration_min): + """Untested.""" + temperature = hex(int(2 * float(temperature)))[2:] + duration_min = hex(int(duration_min / 5.0))[2:] + p = subprocess.Popen(["gatttool", "-b", self.address, "--char-write-req", + "-a", "0x0411", "-n", "11{}{}".format(temperature, duration_min)], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + def set_time(self, datetimeobj): + """Takes a datetimeobj (like datetime.datetime.now()) and sets the time + in the thermostat.""" + command_prefix = "03" + year = "{:02X}".format(datetimeobj.year % 100) + month = "{:02X}".format(datetimeobj.month) + day = "{:02X}".format(datetimeobj.day) + hour = "{:02X}".format(datetimeobj.hour) + minute = "{:02X}".format(datetimeobj.minute) + second = "{:02X}".format(datetimeobj.second) + control_string = "{}{}{}{}{}{}{}".format( + command_prefix, year, month, day, hour, minute, second) + p = subprocess.Popen(["gatttool", "-b", self.address, "--char-write-req", + "-a", "0x0411", "-n", control_string], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + # Block for 3 secs to let the thermostat adjust the settings + time.sleep(3) + +if __name__ == '__main__': + h = EQ3Thermostat("00:AA:BB:CC:DD:EE") + # Take some time + time.sleep(5) + # Deactivate autonomous behavior on the thermostat + h.set_manual_mode() + # Set current date + h.set_time(datetime.datetime.now()) + # Set the current temperature + h.set_temperature(20)