kopia lustrzana https://github.com/mpex/EQ3-Thermostat
Inital commit
rodzic
a5e904fdd5
commit
fac2229397
30
README.md
30
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
|
||||
|
||||
|
|
|
@ -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)
|
Ładowanie…
Reference in New Issue