Since my Nest (gen1) was withdrawn from online being by Google, i bought myself a Bosch CT200 for my NEfit CV installation...
I struggled with all the plugin and easyhttp servers... Tried a bridge script, tried the mqtt, some easyhttp server usinge docker/portainer... Any way, your work inspired me to do a rework and make a plugin that only needs the BOSCH-XMPP installed, and starts its own bridge in a subprocess, and sends data using the client interface... So this plugin reads and writes to the CT200 thermostate...
Code: Select all
"""
<plugin key="bosch-ct200-plugin" name="Bosch CT200 plugin" author="Korrel" version="0.1.1">
<description>
<h2>Bosch Easycontrol (CT200) plugin</h2><br/>
Basic plugin that gets information from and puts information back to a Bosch Easycontrol CT200 thermostate using BOSCH-XMPP communication.<br/><br/>
<h3>Devices</h3><br/>
The following devices are created :
<ul style="list-style-type:square">
<li>Room thermostate</li>
<li>Room temperature</li>
<li>Room humidity indoor</li>
<li>Outdoor temperature</li>
<li>Actual supply temperature</li>
<li>Supply temperature setpoint</li>
<li>Away</li>
<li>Away temperature</li>
<li>Auto Away - enabled</li>
</ul>
<h3>Configuration</h3><br/>
The plugin is using Bosch XMPP in Bridge mode for getting information, device changes are PUT by using the BOSCH-XMPP cli interface.<br/>
If "run bridge" is set to 1 a subprocess running the bridge is started. Make sure the port is not used by another process.<br/>
If "run bridge" is set to 0, the plugin will make use of a running bridge.<br/>
The bosch-xmpp is mandatory for use with this plugin. Visit <a href="https://github.com/robertklep/bosch-xmpp">https://github.com/robertklep/bosch-xmpp</a> for more information or to install.<br/>
</description>
<params>
<param field="Address" label="IP Address" width="250px" required="true" default="127.0.0.1"/>
<param field="Port" label="Port" width="50px" required="true" default="8081"/>
<param field="Mode5" label="Run bridge" width="30px" required="true" default="0"/>
<param field="Mode1" label="Serial Key" width="250px" required="true" default="101xxxxxx"/>
<param field="Mode2" label="Access Key" width="250px" required="true" default="XxXxXxXxXxXxXxXx"/>
<param field="Password" label="Password" width="250px" required="true" default="secret" password="true"/>
<param field="Mode3" label="Path to bosch-xmpp" width="250px" required="true" default="/usr/local/bin/bosch-xmpp"/>
<param field="Mode4" label="Refresh rate" width="30px" required="true" default="6"/>
<param field="Mode6" label="Debug" width="75px">
<options>
<option label="True" value="Debug"/>
<option label="False" value="Normal" default="true" />
</options>
</param>
</params>
</plugin>
"""
import Domoticz
import requests
import os
import subprocess
class BoschCT200Plugin:
class dzCT200device:
def __init__(self, unit, endpoint, name, typename=None, type=None, subtype=None, image=None, switchtype=None):
if typename is not None:
if image is not None:
self.device = Domoticz.Device(Unit=unit, Name=name, TypeName=typename, Image=image)
else:
self.device = Domoticz.Device(Unit=unit, Name=name, TypeName=typename)
else:
if image is not None:
self.device = Domoticz.Device(Unit=unit, Name=name, Type=type, Subtype=subtype, Image=image)
else:
self.device = Domoticz.Device(Unit=unit, Name=name, Type=type, Subtype=subtype)
# self.device = Domoticz.Device(Unit=unit, Name=name, TypeName=typename, Type=type, Subtype=subtype, Image=image, Switchtype=switchtype)
self.endpoint = endpoint
runAgain = 6
CT200devices = ()
pMainBridge = None
def __init__(self):
return
def onStart(self):
Domoticz.Debug("onStart called")
if Parameters["Mode6"] == "Debug":
Domoticz.Debugging(1)
if int(Parameters["Mode4"]) >=0:
self.runAgain = int(Parameters["Mode4"])
self.CT200devices = (
self.dzCT200device(1, "/zones/zn1/manualTemperatureHeating", "Room thermostate", None, 242, 1, 103, None),
self.dzCT200device(2, "/zones/zn1/temperatureActual", "Room temperature", "Temperature", None, None),
self.dzCT200device(3, "/system/sensors/humidity/indoor_h1", "Room humidity indoor", "Humidity", None, None),
self.dzCT200device(4, "/system/sensors/temperatures/outdoor_t1", "Outdoor temperature", "Temperature", None, None),
self.dzCT200device(5, "/heatSources/actualSupplyTemperature", "Actual supply temperature", "Temperature"),
self.dzCT200device(6, "/heatingCircuits/hc1/supplyTemperatureSetpoint", "Supply temperature setpoint", None, 242, 1, None, None),
self.dzCT200device(7, "/system/awayMode/enabled", "Away mode", None, 17, 1, 102, None),
self.dzCT200device(8, "/system/awayMode/temperature", "Away temperature", None, 242, 1, None, None),
self.dzCT200device(9, "/system/autoAway/enabled", "Auto away", None, 17, 1, 102, None)
)
if (len(Devices) == 0):
for CT200device in self.CT200devices:
CT200device.device.Create()
Domoticz.Log("Created " + CT200device.device.Name + " device")
self.updateDeviceValue(CT200device)
else:
Domoticz.Debug("Devices already exist" + str(Devices))
if Parameters["Mode5"] == "1":
Parameters["Address"] = "127.0.0.1"
self.pMainBridge = subprocess.Popen([Parameters["Mode3"], '--serial=' + Parameters["Mode1"], '--access-key=' + Parameters["Mode2"], '--password=' + Parameters["Password"], 'easycontrol', 'bridge', Parameters["Port"], Parameters["Address"]])
Domoticz.Debug("Bridge is running in process : " + str(self.pMainBridge.pid))
def onStop(self):
if Parameters["Mode5"] == "1":
Domoticz.Debug("Going to kill process : " + str(self.pMainBridge.pid) + " (Bridge)")
self.pMainBridge.terminate()
return
def onConnect(self, Connection, Status, Description):
return
def onMessage(self, Connection, Data):
Domoticz.Debug("onMessage called")
Domoticz.Debug("onMessage called with Data: '"+str(strData)+"'")
return
def onCommand(self, Unit, Command, Level, Hue):
aCT200device = self.CT200devices[Unit-1]
Domoticz.Debug("onCommand called for unit : '" + str(Unit)+"' " + aCT200device.endpoint + " - Level : " +str(Level) + " - Command : " + Command)
match Command:
case "Set Level":
os.system(Parameters["Mode3"] + " --serial=" + Parameters["Mode1"] + " --access-key=" + Parameters["Mode2"] + " --password=" + Parameters["Password"] + " easycontrol put " + aCT200device.endpoint + " '{\"value\":" + str(Level) + "}'")
case "On":
os.system(Parameters["Mode3"] + " --serial=" + Parameters["Mode1"] + " --access-key=" + Parameters["Mode2"] + " --password=" + Parameters["Password"] + " easycontrol put " + aCT200device.endpoint + " '{\"value\":\"true\"}'")
case "Off":
os.system(Parameters["Mode3"] + " --serial=" + Parameters["Mode1"] + " --access-key=" + Parameters["Mode2"] + " --password=" + Parameters["Password"] + " easycontrol put " + aCT200device.endpoint + " '{\"value\":\"false\"}'")
case _:
Domoticz.Debug("No Message defined for this unit ....")
self.updateDeviceValue(aCT200device)
return
def onNotification(self, Name, Subject, Text, Status, Priority, Sound, ImageFile):
Domoticz.Log("Notification: " + Name + "," + Subject + "," + Text + "," + Status + "," + str(Priority) + "," + Sound + "," + ImageFile)
return
def onDisconnect(self, Connection):
Domoticz.Log("onDisconnect called")
self.isConnected = False
return
def onHeartbeat(self):
self.runAgain = self.runAgain - 1
if self.runAgain <= 0:
for CT200device in self.CT200devices:
self.updateDeviceValue(CT200device)
self.runAgain = int(Parameters["Mode4"])
else:
Domoticz.Debug("onHeartbeat called, run again in " + str(self.runAgain) + " heartbeats.")
def updateDeviceValue(self, aCT200device: dzCT200device):
url = "http://" + Parameters["Address"] + ":" + Parameters["Port"] + "/bridge" + aCT200device.endpoint
request = requests.get(url)
value = request.json()["value"]
if aCT200device.device.Type == 17:
if str(value) == "false": value = 0
else: value = 1
Domoticz.Debug("Going to set value " + str(value) + " on device " + aCT200device.device.Name)
aCT200device.device.Update(int(value), str(value))
global _plugin
_plugin = BoschCT200Plugin()
def onStart():
global _plugin
_plugin.onStart()
def onStop():
global _plugin
_plugin.onStop()
def onConnect(Connection, Status, Description):
global _plugin
_plugin.onConnect(Connection, Status, Description)
def onMessage(Connection, Data):
global _plugin
_plugin.onMessage(Connection, Data)
def onCommand(Unit, Command, Level, Hue):
global _plugin
_plugin.onCommand(Unit, Command, Level, Hue)
def onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile):
global _plugin
_plugin.onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile)
def onDisconnect(Connection):
global _plugin
_plugin.onDisconnect(Connection)
def onHeartbeat():
global _plugin
_plugin.onHeartbeat()
I'll be working on some extra devices (clock/program switch clock/timers if possible and some meters (gas use for water, gas use for cv heating))