def cli(ctx, mac, debug): """ Tool to query and modify the state of EQ3 BT smart thermostat. """ if debug: logging.basicConfig(level=logging.DEBUG) else: logging.basicConfig(level=logging.INFO) thermostat = Thermostat(mac) thermostat.update() ctx.obj = thermostat if ctx.invoked_subcommand is None: ctx.invoke(state)
def get_status(self, radiator): if radiator in self.radiators.keys(): try: t = Thermostat(self.radiators[radiator]) t.update() answer = '\n' answer += ' Locked: ' + str(t.locked) + '\n' answer += ' Battery low: ' + str(t.low_battery) + '\n' answer += ' Window open: ' + str(t.window_open) + '\n' answer += ' Window open temp: ' + self.__print_temp( t.window_open_temperature) + '\n' answer += ' Window open time: ' + str( t.window_open_time) + '\n' answer += ' Boost: ' + str(t.boost) + '\n' answer += ' Current target temp: ' + self.__print_temp( t.target_temperature) + '\n' answer += ' Current comfort temp: ' + self.__print_temp( t.comfort_temperature) + '\n' answer += ' Current eco temp: ' + self.__print_temp( t.eco_temperature) + '\n' answer += ' Valve: ' + str(t.valve_state) return answer except: return 'timeout'
name=None, homie_settings=None, mqtt_settings=None): super().__init__(device_id, name, homie_settings, mqtt_settings) self.add_node(Node_EQ3BT(self)) self.start() def update(self, thermostat): self.get_node('eq3bt').update_state(thermostat) homie = Device_EQ3BT(device_id=device_id, name=device_name, mqtt_settings=mqtt_settings) while True: try: thermostat.update() except Exception as e: l.warning(e) l.warning("Error connecting to thermostat; trying again") time.sleep(10) continue if thermostat._raw_mode == None: l.warning("No reply received from thermostat... Strange!") time.sleep(10) continue #no message received within acceptable time... print(thermostat) homie.update(thermostat) time.sleep(300)
class TestThermostat(TestCase): def setUp(self): self.thermostat = Thermostat(_mac=None, connection_cls=FakeConnection) def test__verify_temperature(self): with self.assertRaises(TemperatureException): self.thermostat._verify_temperature(-1) with self.assertRaises(TemperatureException): self.thermostat._verify_temperature(35) self.thermostat._verify_temperature(8) self.thermostat._verify_temperature(25) def test_parse_schedule(self): self.fail() def test_handle_notification(self): self.fail() def test_query_id(self): self.thermostat.query_id() self.assertEqual(self.thermostat.firmware_version, 120) self.assertEqual(self.thermostat.device_serial, "PEQ2130075") def test_update(self): th = self.thermostat th._conn.set_status('auto') th.update() self.assertEqual(th.valve_state, 0) self.assertEqual(th.mode, Mode.Auto) self.assertEqual(th.target_temperature, 20.0) self.assertFalse(th.locked) self.assertFalse(th.low_battery) self.assertFalse(th.boost) self.assertFalse(th.window_open) th._conn.set_status('manual') th.update() self.assertTrue(th.mode, Mode.Manual) th._conn.set_status('away') th.update() self.assertEqual(th.mode, Mode.Away) self.assertEqual(th.target_temperature, 17.5) self.assertEqual(th.away_end, datetime(2019, 3, 29, 23, 00)) th._conn.set_status('boost') th.update() self.assertTrue(th.boost) self.assertEqual(th.mode, Mode.Boost) def test_presets(self): th = self.thermostat self.thermostat._conn.set_status('presets') self.thermostat.update() self.assertEqual(th.window_open_temperature, 12.0) self.assertEqual(th.window_open_time, timedelta(minutes=15.0)) self.assertEqual(th.comfort_temperature, 20.0) self.assertEqual(th.eco_temperature, 17.0) self.assertEqual(th.temperature_offset, 0) def test_query_schedule(self): self.fail() def test_schedule(self): self.fail() def test_set_schedule(self): self.fail() def test_target_temperature(self): self.fail() def test_mode(self): self.fail() def test_mode_readable(self): self.fail() def test_boost(self): self.fail() def test_valve_state(self): th = self.thermostat th._conn.set_status('valve_at_22') th.update() self.assertEqual(th.valve_state, 22) def test_window_open(self): th = self.thermostat th._conn.set_status('window') th.update() self.assertTrue(th.window_open) def test_window_open_config(self): self.fail() def test_locked(self): self.fail() def test_low_battery(self): th = self.thermostat th._conn.set_status('low_batt') th.update() self.assertTrue(th.low_battery) def test_temperature_offset(self): self.fail() def test_activate_comfort(self): self.fail() def test_activate_eco(self): self.fail() def test_min_temp(self): self.fail() def test_max_temp(self): self.fail() def test_away_end(self): self.fail() def test_decode_mode(self): self.fail()
class Plugin(plugin.PluginProto): PLUGIN_ID = 516 PLUGIN_NAME = "Thermostat - BLE EQ3 (EXPERIMENTAL)" PLUGIN_VALUENAME1 = "TargetTemp" PLUGIN_VALUENAME2 = "Mode" PLUGIN_VALUENAME3 = "TempOffset" def __init__(self, taskindex): # general init plugin.PluginProto.__init__(self, taskindex) self.dtype = rpieGlobals.DEVICE_TYPE_BLE self.vtype = rpieGlobals.SENSOR_TYPE_TRIPLE self.valuecount = 3 self.senddataoption = True self.recdataoption = False self.timeroption = True self.timeroptional = False self.formulaoption = True self.thermostat = None self.readinprogress = False self.battery = 0 self._lastdataservetime = 0 self._nextdataservetime = 0 self.blestatus = None def webform_load(self): # create html page for settings webserver.addFormTextBox("Device Address", "plugin_516_addr", str(self.taskdevicepluginconfig[0]), 20) webserver.addFormNote( "Enable blueetooth then <a href='blescanner'>scan EQ3 address</a> first." ) webserver.addFormNote( "!!!This plugin WILL NOT work with ble scanner plugin!!!") return True def webform_save(self, params): # process settings post reply self.taskdevicepluginconfig[0] = str( webserver.arg("plugin_516_addr", params)).strip().lower() self.plugin_init() return True def plugin_init(self, enableplugin=None): plugin.PluginProto.plugin_init(self, enableplugin) self.readinprogress = 0 if self.enabled: try: self.blestatus = BLEHelper.BLEStatus[ 0] # 0 is hardwired in library self.blestatus.registerdataprogress( self.taskindex) # needs continous access except: pass self.ports = str(self.taskdevicepluginconfig[0]) try: self.thermostat = Thermostat( str(self.taskdevicepluginconfig[0])) self.initialized = True time.sleep(1) except: self.initialized = False if self.interval > 2: nextr = self.interval - 2 else: nextr = 0 self._lastdataservetime = rpieTime.millis() - (nextr * 1000) else: self.ports = "" def plugin_exit(self): try: self.blestatus.unregisterdataprogress(self.taskindex) if self.thermostat._conn is not None: self.thermostat._conn.__exit__() except: pass def plugin_read(self): result = False if self.enabled and self.initialized and self.readinprogress == 0: self.readinprogress = 1 try: self.thermostat.update() time.sleep(0.1) if self.thermostat.low_battery: self.battery = 10 else: self.battery = 100 self.set_value(1, float(self.thermostat.target_temperature), False) self.set_value(2, int(self.thermostat.mode), False) self.set_value(3, float(self.thermostat.temperature_offset), False, susebattery=self.battery) self.plugin_senddata(pusebattery=self.battery) self._lastdataservetime = rpieTime.millis() result = True except Exception as e: print("EQ3 read error: " + str(e)) time.sleep(3) self.readinprogress = 0 return result def plugin_write(self, cmd): # handle incoming commands res = False cmdarr = cmd.split(",") cmdarr[0] = cmdarr[0].strip().lower() if self.initialized == False: return False if cmdarr[0] == "eq3": try: rname = cmdarr[1].strip() except: rname = "" if rname.lower() != self.gettaskname().lower(): return False # command arrived to another task, skip it if cmdarr[2] == "sync": if self.thermostat is not None: try: self.thermostat.update() jstruc = { "target temperature": self.thermostat.target_temperature, "mode": self.thermostat.mode_readable } res = str(jstruc).replace("'", '"').replace(', ', ',\n') res = res.replace("{", "{\n").replace("}", "\n}") except Exception as e: print(e) return res elif cmdarr[2] == "mode": mode = "" try: mode = str(cmdarr[3].strip()).lower() except: mode = "" tmode = -1 if mode == "closed": tmode = Mode.Closed elif mode == "open": tmode = Mode.Open elif mode == "auto": tmode = Mode.Auto elif mode == "manual": tmode = Mode.Manual elif mode == "away": tmode = Mode.Away elif mode == "boost": tmode = Mode.Boost if (self.thermostat is not None) and int(tmode) > -1: try: self.thermostat.mode = tmode misc.addLog(rpieGlobals.LOG_LEVEL_DEBUG, "EQ3 mode " + str(mode)) res = True except Exception as e: print(e) elif cmdarr[2] == "temp": temp = -1 try: temp = misc.str2num(cmdarr[3].strip()) except: temp = -1 if (self.thermostat is not None) and temp > 4 and temp < 31: try: self.thermostat.target_temperature = temp misc.addLog(rpieGlobals.LOG_LEVEL_DEBUG, "EQ3 target temperature " + str(temp)) res = True except Exception as e: print(e) return res
def setEQTemp(sMAC, sTemp): thermostat = Thermostat(sMAC) thermostat.target_temperature = float(sTemp) thermostat.update()
class myTRV: MAC = None gateTopic = None trvName = None trv = None mqttClient = None mqttGateTopic = None extTemp = 0 logger = None def __init__(self, MAC): self.MAC = MAC self.trv = Thermostat(MAC) def trvReadStateJSON(self): readSuccess = True try: self.trv.update() except: time.sleep(5) self.logger.error("RS1: Failed to communicate with " + self.trvName) try: self.trv.update() except: self.logger.error("RS2: Failed to communicate with " + self.trvName) readSuccess = False outputValues = "{\"trv\":\"" outputValues += self.MAC if readSuccess: outputValues += "\",\"temp\":\"" outputValues += str(self.trv.target_temperature) outputValues += "\",\"ctemp\":\"" outputValues += str( self.trv.target_temperature) if self.extTemp == 0 else str( self.extTemp) outputValues += "\",\"mode\":\"" outputValues += self.homeAssistantMode(self.trv.mode.value) outputValues += "\",\"boost\":\"" outputValues += "active" if self.trv.boost else "inactive" outputValues += "\",\"valve\":\"" outputValues += str(self.trv.valve_state) outputValues += "\",\"locked\":\"" outputValues += "locked" if self.trv.locked else "unlocked" outputValues += "\",\"battery\":\"" outputValues += "0" if self.trv.low_battery else "100" outputValues += "\",\"window\":\"" outputValues += "open" if self.trv.window_open else "closed" outputValues += "\",\"error\":\"\"}" else: outputValues += "\",\"error\":\"connection failed\"}" return outputValues def eq3Mode(self, homeAssistantModeIn): modeToSet = str(homeAssistantModeIn) modeToSet = modeToSet.lower() eq3ObjMode = Mode(3) if modeToSet == "auto": eq3ObjMode = Mode(2) if modeToSet == "off": eq3ObjMode = Mode(0) if modeToSet == "on": eq3ObjMode = Mode(1) if modeToSet == "boost": eq3ObjMode = Mode(5) return eq3ObjMode def homeAssistantMode(self, eq3ModeIn): switcher = { -1: "unknown", 0: "off", 1: "heat", 2: "auto", 3: "heat", 4: "off", 5: "heat" } return switcher.get(eq3ModeIn, "unknown") def PublishState(self): trvCurrentState = self.trvReadStateJSON() try: self.mqttClient.publish( self.mqttGateTopic + self.trvName + "/status", trvCurrentState) except: time.sleep(5) self.logger.error("Failed to publish state") try: self.mqttClient.publish( self.mqttGateTopic + self.trvName + "/status", trvCurrentState) except: self.logger.error("Failed to publish state") def ModeSet(self, newMode): try: self.trv.mode = self.eq3Mode(newMode) except: time.sleep(5) self.logger.error("SM1: Failed to communicate with " + self.trvName) try: self.trv.mode = self.eq3Mode(newMode) except: self.logger.error("SM2: Failed to communicate with " + self.trvName) self.PublishState() def TempSet(self, newTemp): if (float(str(newTemp)) <= 4.5): self.ModeSet("off") if (float(str(newTemp)) >= 30): self.ModeSet("on") try: self.trv.target_temperature = float(str(newTemp)) except: time.sleep(5) self.logger.error("ST1: Failed to communicate with " + self.trvName) try: self.trv.target_temperature = float(str(newTemp)) except: self.logger.error("ST2: Failed to communicate with " + self.trvName) self.PublishState()