├── Domoticz.py ├── README.md ├── TestCode.py └── plugin.py /Domoticz.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import chardet 4 | 5 | Parameters = {"Mode5": "Debug"} 6 | Devices = {} 7 | Images = {} 8 | debug_Level = 0 9 | 10 | 11 | def Debug(textStr): 12 | if debug_Level >= 62: 13 | print(u'Debug : {}'.format(textStr)) 14 | 15 | 16 | def Error(textStr): 17 | print(u'Error : {}'.format(textStr)) 18 | 19 | 20 | def Status(textStr): 21 | print(u'Status : {}'.format(textStr)) 22 | 23 | 24 | def Log(textStr): 25 | print(u'Log : {}'.format(textStr)) 26 | 27 | 28 | def Debugging(value): 29 | global debug_Level 30 | debug_Level = int(value) 31 | Log(u'debug_Level: {}'.format(debug_Level)) 32 | 33 | 34 | def Heartbeat(value): 35 | pass 36 | 37 | 38 | def UpdateDevice(num, Image='', nValue=0, sValue=''): 39 | pass 40 | 41 | 42 | class Connection: 43 | 44 | @property 45 | def Name(self): 46 | return self._name 47 | 48 | @property 49 | def data(self): 50 | return self._data 51 | 52 | @property 53 | def bp(self): 54 | return self._bp 55 | 56 | @bp.setter 57 | def bp(self, value): 58 | self._bp = value 59 | 60 | def __init__(self, Name="", Transport="", Protocol="", Address="", Port=""): 61 | self._name = Name 62 | self._transport = Transport 63 | self._ptrotocol = Protocol.lower() 64 | self._address = Address 65 | self._port = Port 66 | self._requestUrl = u'{}://{}:{}'.format(self._ptrotocol, self._address, self._port) 67 | self._data = None 68 | self._bp = None 69 | 70 | def Connect(self): 71 | Debug(u'requestUrl: {}'.format(self._requestUrl)) 72 | self._bp.onConnect('Connection', 0, 'Description') 73 | return None 74 | 75 | def Connecting(self): 76 | return True 77 | 78 | def Connected(self): 79 | return True 80 | 81 | def Send(self, params): 82 | params['Headers']['accept'] = 'application/json' 83 | if params['Verb'] == 'POST': 84 | url = u'{}/{}'.format(self._requestUrl, params['URL']) 85 | r = requests.post(url, data=params['Data'], headers=params['Headers']) 86 | 87 | # build onMessage params 88 | data = {} 89 | data["Status"] = r.status_code 90 | data["Data"] = bytes(json.dumps(r.json()), 'utf-8') 91 | 92 | r.encoding = 'utf-8' 93 | self._data = {} 94 | self._data["Status"] = r.status_code 95 | self._data["Data"] = bytes(json.dumps(r.json()), 'utf-8') 96 | 97 | self.bp.onMessage(self, data) 98 | return 99 | elif params['Verb'] == 'GET': 100 | url = u'{}/{}'.format(self._requestUrl, params['URL']) 101 | r = requests.get(url, data=params['Data'], headers=params['Headers']) 102 | 103 | # build onMessage params and onMessage call 104 | data = {} 105 | data["Status"] = r.status_code 106 | data["Data"] = bytes(json.dumps(r.json()), 'utf-8') 107 | self.bp.onMessage(self, data) 108 | 109 | r.encoding = 'utf-8' 110 | self._data = {} 111 | self._data["Status"] = r.status_code 112 | self._data["Data"] = bytes(json.dumps(r.json()), 'utf-8') 113 | return True 114 | 115 | 116 | class Device: 117 | global Devices 118 | 119 | @property 120 | def nValue(self): 121 | return self._nValue 122 | 123 | @nValue.setter 124 | def nValue(self, value): 125 | self._nValue = value 126 | 127 | @property 128 | def sValue(self): 129 | return self._sValue 130 | 131 | @sValue.setter 132 | def nValue(self, value): 133 | self._sValue = value 134 | 135 | @property 136 | def ID(self): 137 | return self._id 138 | 139 | @ID.setter 140 | def ID(self, value): 141 | self._id = value 142 | 143 | @property 144 | def DeviceID(self): 145 | return self._device_id 146 | 147 | @DeviceID.setter 148 | def DeviceID(self, value): 149 | self._device_id = value 150 | 151 | @property 152 | def Typename(self): 153 | return self._typeName 154 | 155 | @Typename.setter 156 | def Typename(self, value): 157 | self._typeName = value 158 | 159 | @property 160 | def Name(self): 161 | return self._name 162 | 163 | @Name.setter 164 | def Name(self, value): 165 | self._name = value 166 | 167 | @property 168 | def LastLevel(self): 169 | return 0 170 | 171 | @property 172 | def Image(self): 173 | return self._image 174 | 175 | @Image.setter 176 | def Image(self, value): 177 | self._image = value 178 | 179 | def __init__(self, Name="", Unit=0, TypeName="", Used=0, Type=0, Subtype=0, Image="", Options=""): 180 | self._nValue = 0 181 | self._sValue = '' 182 | self._name = Name 183 | self._unit = Unit 184 | self._typeName = TypeName 185 | self._used = Used 186 | self._type = Type 187 | self._subtype = Subtype 188 | self._image = Image 189 | self._options = Options 190 | self._id = len(Devices.keys()) + 101 191 | self._device_id = len(Devices.keys()) + 4001 192 | Debug(u'ID: {}'.format(self._id)) 193 | 194 | def Update(self, nValue=0, sValue='', Options='', Image=None): 195 | self._nvalue = nValue 196 | self._svalue = sValue 197 | self._image = Image 198 | txt_log = self.__str__() 199 | Log(txt_log) 200 | 201 | def __str__(self): 202 | txt_log = u'Info - Update device Name : {} nValue : {} sValue : {} Options : {} Image: {}\n' 203 | txt_log = txt_log.format(self._name, self._nvalue, self._svalue, self._options, self._image) 204 | return txt_log 205 | 206 | def Create(self): 207 | self._id = len(Devices.keys()) + 101 208 | self._device_id = len(Devices.keys()) + 4001 209 | txt_log = u'Info - Create device : \n\tName : {}\n\tUnit : {}\n\tTypeName : {}\n\tUsed : {}\n\tType : {}\n\tSubtype : {}' 210 | txt_log += u'\n\tImage : {}\n\tOptions : {}' 211 | txt_log = txt_log.format(self._name, self._unit, self._typeName, 212 | self._used, self._type, self._subtype, self._image, self._options) 213 | Log(txt_log) 214 | Devices[len(Devices.keys())] = self 215 | Debug(u'ID: {}'.format(self._id)) 216 | 217 | 218 | class Image: 219 | 220 | @property 221 | def Name(self): 222 | return self._name 223 | 224 | @Name.setter 225 | def ID(self, value): 226 | self._name = value 227 | 228 | @property 229 | def Base(self): 230 | return self._base 231 | 232 | @Base.setter 233 | def ID(self, value): 234 | self._base = value 235 | 236 | @property 237 | def ID(self): 238 | return self._filename 239 | 240 | @ID.setter 241 | def ID(self, value): 242 | self._filename = value 243 | 244 | def __init__(self, Filename=""): 245 | self._filename = Filename 246 | self._name = Filename 247 | self._base = Filename 248 | 249 | def Create(self): 250 | Images[self._filename.split(u' ')[0]] = self 251 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # domoticz-python-melcloud 2 | ## Installation 3 | 1. Clone repository into your domoticz plugins folder 4 | ``` 5 | cd domoticz/plugins 6 | git clone https://github.com/gysmo38/domoticz-python-melcloud.git 7 | ``` 8 | 2. Restart domoticz 9 | 3. Make sure that "Accept new Hardware Devices" is enabled in Domoticz settings 10 | 4. Go to "Hardware" page and add new item with type "MELCloud plugin" 11 | ## Plugin update 12 | 13 | ``` 14 | cd domoticz/plugins/Melcloud 15 | git pull 16 | ``` 17 | ## Testing without Domoticz 18 | 1. Clone repository 19 | ``` 20 | cd scripts/tests 21 | git clone https://github.com/gysmo38/domoticz-python-melcloud.git 22 | ``` 23 | 2. Edit TestCode.py 24 | ``` 25 | Parameters['Username'] = 'xxxxxxxxxxxxx@xxx.xxx' # your account mail 26 | Parameters['Password'] = 'xxxxxxxxxx' # your account password 27 | ``` 28 | 3. Run test 29 | ``` 30 | python3 plugin.py 31 | ``` 32 | -------------------------------------------------------------------------------- /TestCode.py: -------------------------------------------------------------------------------- 1 | 2 | import json 3 | from Domoticz import Connection 4 | from Domoticz import Device 5 | from Domoticz import Devices 6 | from Domoticz import Parameters 7 | 8 | # your params 9 | 10 | Parameters['Mode1'] = '0' 11 | Parameters['Username'] = 'xxxxxxxxxxxxx@xxx.xxx' # your account mail 12 | Parameters['Password'] = 'xxxxxxxxxx' # your account password 13 | Parameters['Mode6'] = 'Debug' # Debug or Normal 14 | 15 | 16 | def runtest(plugin): 17 | 18 | plugin.onStart() 19 | 20 | # First Heartbeat 21 | plugin.onHeartbeat() 22 | 23 | # Second Heartbeat 24 | plugin.onHeartbeat() 25 | 26 | exit(0) 27 | -------------------------------------------------------------------------------- /plugin.py: -------------------------------------------------------------------------------- 1 | # MELCloud Plugin 2 | # Author: Gysmo, 2017 3 | # Version: 0.7.7 4 | # 5 | # Release Notes: 6 | # v0.7.8: Code optimization 7 | # v0.7.7: Add test on domoticz dummy 8 | # v0.7.6: Fix Auto Mode added 9 | # v0.7.5: Fix somes bugs and improve https connection 10 | # v0.7.4: Sometimes update fail. Update function sync to avoid this 11 | # v0.7.3: Add test in login process and give message if there is some errors 12 | # v0.7.2: Correct bug for onDisconnect, add timeoffset and add update time for last command in switch text 13 | # v0.7.1: Correct bug with power on and power off 14 | # v0.7 : Use builtin https support to avoid urllib segmentation fault on binaries 15 | # v0.6.1 : Change Update function to not crash with RPI 16 | # v0.6 : Rewrite of the module to be easier to maintain 17 | # v0.5.1: Problem with device creation 18 | # v0.5 : Upgrade code to be compliant wih new functions 19 | # v0.4 : Search devices in floors, areas and devices 20 | # v0.3 : Add Next Update information, MAC Address and Serial Number 21 | # Add Horizontal vane 22 | # Add Vertival vane 23 | # Add Room Temp 24 | # v0.2 : Add sync between Domoticz devices and MELCloud devices 25 | # Usefull if you use your Mitsubishi remote 26 | # v0.1 : Initial release 27 | """ 28 | 29 | 30 | 31 | 32 | 33 | 34 | 60 | 61 | 62 | 63 | 72 | 73 | 74 | 75 | """ 76 | 77 | import time 78 | import json 79 | import Domoticz 80 | 81 | 82 | class BasePlugin: 83 | 84 | melcloud_conn = None 85 | melcloud_baseurl = "app.melcloud.com" 86 | melcloud_port = "443" 87 | melcloud_key = None 88 | melcloud_state = "Not Ready" 89 | 90 | melcloud_urls = {} 91 | melcloud_urls["login"] = "/Mitsubishi.Wifi.Client/Login/ClientLogin" 92 | melcloud_urls["list_unit"] = "/Mitsubishi.Wifi.Client/User/ListDevices" 93 | melcloud_urls["set_unit"] = "/Mitsubishi.Wifi.Client/Device/SetAta" 94 | melcloud_urls["unit_info"] = "/Mitsubishi.Wifi.Client/Device/Get" 95 | 96 | list_units = [] 97 | dict_devices = {} 98 | 99 | list_switchs = [] 100 | list_switchs.append({"id": 1, "name": "Mode", "typename": "Selector Switch", 101 | "image": 16, "levels": "Off|Warm|Cold|Vent|Dry|Auto"}) 102 | list_switchs.append({"id": 2, "name": "Fan", "typename": "Selector Switch", 103 | "image": 7, "levels": "Level1|Level2|Level3|Level4|Level5|Auto|Silence"}) 104 | list_switchs.append({"id": 3, "name": "Temp", "typename": "Selector Switch", 105 | "image": 15, "levels": "16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31"}) 106 | list_switchs.append({"id": 4, "name": "Vane Horizontal", "typename": "Selector Switch", 107 | "image": 7, "levels": "1|2|3|4|5|Swing|Auto"}) 108 | list_switchs.append({"id": 5, "name": "Vane Vertical", "typename": "Selector Switch", 109 | "image": 7, "levels": "1|2|3|4|5|Swing|Auto"}) 110 | list_switchs.append({"id": 6, "name": "Room Temp", "typename": "Temperature"}) 111 | list_switchs.append({"id": 7, "name": "Unit Infos", "typename": "Text"}) 112 | 113 | domoticz_levels = {} 114 | domoticz_levels["mode"] = {"0": 0, "10": 1, "20": 3, "30": 7, "40": 2, "50": 8} 115 | domoticz_levels["mode_pic"] = {"0": 9, "10": 15, "20": 16, "30": 7, "40": 11} 116 | domoticz_levels["fan"] = {"0": 1, "10": 2, "20": 3, "30": 4, "40": 255, "50": 0, "60": 1} 117 | domoticz_levels["temp"] = {"0": 16, "10": 17, "20": 18, "30": 19, "40": 20, "50": 21, 118 | "60": 22, "70": 23, "80": 24, "90": 25, "100": 26, 119 | "110": 27, "120": 28, "130": 29, "140": 30, "150": 31} 120 | domoticz_levels["vaneH"] = {"0": 1, "10": 2, "20": 3, "30": 4, "40": 5, "50": 12, "60": 0} 121 | domoticz_levels["vaneV"] = {"0": 1, "10": 2, "20": 3, "30": 4, "40": 5, "50": 7, "60": 0} 122 | 123 | runAgain = 6 124 | enabled = False 125 | 126 | def __init__(self): 127 | return 128 | 129 | def onStart(self): 130 | Domoticz.Heartbeat(25) 131 | if Parameters["Mode6"] == "Debug": 132 | Domoticz.Debugging(62) 133 | # Start connection to MELCloud 134 | self.melcloud_conn = Domoticz.Connection(Name="MELCloud", Transport="TCP/IP", 135 | Protocol="HTTPS", Address=self.melcloud_baseurl, 136 | Port=self.melcloud_port) 137 | if __name__ == "__main__": 138 | self.melcloud_conn.bp = self 139 | self.melcloud_conn.Connect() 140 | return True 141 | 142 | def onStop(self): 143 | Domoticz.Log("Goobye from MELCloud plugin.") 144 | 145 | def onConnect(self, Connection, Status, Description): 146 | if Status == 0: 147 | Domoticz.Log("MELCloud connection OK") 148 | self.melcloud_state = "READY" 149 | self.melcloud_login() 150 | else: 151 | Domoticz.Log("MELCloud connection FAIL: "+Description) 152 | 153 | def extractDeviceData(self, device): 154 | if device['DeviceName'] not in self.dict_devices.keys(): 155 | self.dict_devices['DeviceName'] = device 156 | # print('\n---device\n', device, '\n---\n') 157 | if 'HasEnergyConsumedMeter' in device['Device'].keys(): 158 | return device['Device']['CurrentEnergyConsumed']/1000 159 | else: 160 | return 0 161 | 162 | def searchUnits(self, building, scope, idoffset): 163 | # building["Structure"]["Devices"] 164 | # building["Structure"]["Areas"] 165 | # building["Structure"]["Floors"] 166 | nr_of_Units = 0 167 | cEnergyConsumed = 0 168 | # Search in scope 169 | 170 | def oneUnit(self, device, idoffset, nr_of_Units, cEnergyConsumed, building, scope): 171 | self.melcloud_add_unit(device, idoffset) 172 | idoffset += len(self.list_switchs) 173 | nr_of_Units += 1 174 | self.extractDeviceData(device) 175 | currentEnergyConsumed = self.extractDeviceData(device) 176 | cEnergyConsumed += currentEnergyConsumed 177 | text2log = "Found {} in building {} {} CurrentEnergyConsumed {} kWh" 178 | text2log = text2log.format(device['DeviceName'], 179 | building["Name"], 180 | scope, currentEnergyConsumed) 181 | Domoticz.Log(text2log) 182 | return (nr_of_Units, idoffset, cEnergyConsumed) 183 | 184 | for item in building["Structure"][scope]: 185 | if scope == u'Devices': 186 | if item["Type"] == 0: 187 | (nr_of_Units, idoffset, cEnergyConsumed) = oneUnit(self, item, idoffset, 188 | nr_of_Units, cEnergyConsumed, 189 | building, scope) 190 | elif scope in (u'Areas', u'Floors'): 191 | for device in item["Devices"]: 192 | (nr_of_Units, idoffset, cEnergyConsumed) = oneUnit(self, device, idoffset, 193 | nr_of_Units, cEnergyConsumed, 194 | building, scope) 195 | if scope == u'Floors': 196 | for device in item["Areas"]: 197 | (nr_of_Units, idoffset, cEnergyConsumed) = oneUnit(self, device, idoffset, 198 | nr_of_Units, cEnergyConsumed, 199 | building, scope) 200 | 201 | text2log = u'Found {} devices in building {} {} of the Type 0 (Aircondition) CurrentEnergyConsumed {:.0f} kWh' 202 | text2log = text2log.format(str(nr_of_Units), building["Name"], scope, cEnergyConsumed) 203 | Domoticz.Log(text2log) 204 | 205 | return (nr_of_Units, idoffset, cEnergyConsumed) 206 | 207 | def onMessage(self, Connection, Data): 208 | Status = int(Data["Status"]) 209 | if Status == 200: 210 | strData = Data["Data"].decode("utf-8", "ignore") 211 | response = json.loads(strData) 212 | Domoticz.Debug("JSON REPLY: "+str(response)) 213 | if self.melcloud_state == "LOGIN": 214 | if ("ErrorId" not in response.keys()) or (response["ErrorId"] is None): 215 | Domoticz.Log("MELCloud login successfull") 216 | self.melcloud_key = response["LoginData"]["ContextKey"] 217 | self.melcloud_units_init() 218 | elif response["ErrorId"] == 1: 219 | Domoticz.Log("MELCloud login fail: check login and password") 220 | self.melcloud_state = "LOGIN_FAILED" 221 | else: 222 | Domoticz.Log("MELCloud failed with unknown error "+str(response["ErrorId"])) 223 | self.melcloud_state = "LOGIN_FAILED" 224 | 225 | elif self.melcloud_state == "UNITS_INIT": 226 | idoffset = 0 227 | Domoticz.Log("Find " + str(len(response)) + " buildings") 228 | for building in response: 229 | Domoticz.Log("Find " + str(len(building["Structure"]["Areas"])) + 230 | " areas in building "+building["Name"]) 231 | Domoticz.Log("Find " + str(len(building["Structure"]["Floors"])) + 232 | " floors in building "+building["Name"]) 233 | # Search in devices 234 | (nr_of_Units, idoffset, cEnergyConsumed) = self.searchUnits(building, "Devices", idoffset) 235 | # Search in areas 236 | (nr_of_Units, idoffset, cEnergyConsumed) = self.searchUnits(building, "Areas", idoffset) 237 | # Search in floors 238 | (nr_of_Units, idoffset, cEnergyConsumed) = self.searchUnits(building, "Floors", idoffset) 239 | self.melcloud_create_units() 240 | elif self.melcloud_state == "UNIT_INFO": 241 | for unit in self.list_units: 242 | if unit['id'] == response['DeviceID']: 243 | Domoticz.Log("Update unit {0} information.".format(unit['name'])) 244 | unit['power'] = response['Power'] 245 | unit['op_mode'] = response['OperationMode'] 246 | unit['room_temp'] = response['RoomTemperature'] 247 | unit['set_temp'] = response['SetTemperature'] 248 | unit['set_fan'] = response['SetFanSpeed'] 249 | unit['vaneH'] = response['VaneHorizontal'] 250 | unit['vaneV'] = response['VaneVertical'] 251 | unit['next_comm'] = False 252 | Domoticz.Debug("Heartbeat unit info: "+str(unit)) 253 | self.domoticz_sync_switchs(unit) 254 | elif self.melcloud_state == "SET": 255 | for unit in self.list_units: 256 | if unit['id'] == response['DeviceID']: 257 | date, time = response['NextCommunication'].split("T") 258 | hours, minutes, sec = time.split(":") 259 | mode1 = Parameters["Mode1"] 260 | if mode1 == '0': 261 | mode1 = '+0' 262 | sign = mode1[0] 263 | value = mode1[1:] 264 | Domoticz.Debug("TIME OFFSSET :" + sign + value) 265 | if sign == "-": 266 | hours = int(hours) - int(value) 267 | if hours < 0: 268 | hours = hours + 24 269 | else: 270 | hours = int(hours) + int(value) 271 | if hours > 24: 272 | hours = hours - 24 273 | next_comm = date + " " + str(hours) + ":" + minutes + ":" + sec 274 | unit['next_comm'] = "Update for last command at "+next_comm 275 | Domoticz.Log("Next update for command: " + next_comm) 276 | self.domoticz_sync_switchs(unit) 277 | else: 278 | Domoticz.Log("State not implemented:" + self.melcloud_state) 279 | else: 280 | Domoticz.Log("MELCloud receive unknonw message with error code "+Data["Status"]) 281 | 282 | def onCommand(self, Unit, Command, Level, Hue): 283 | Domoticz.Log("onCommand called for Unit " + str(Unit) + 284 | ": Parameter '" + str(Command) + "', Level: " + str(Level)) 285 | # ~ Get switch function: mode, fan, temp ... 286 | switch_id = Unit 287 | while switch_id > 7: 288 | switch_id -= 7 289 | switch_type = self.list_switchs[switch_id-1]["name"] 290 | # ~ Get the unit in units array 291 | current_unit = False 292 | for unit in self.list_units: 293 | if (unit['idoffset'] + self.list_switchs[switch_id-1]["id"]) == Unit: 294 | current_unit = unit 295 | break 296 | if switch_type == 'Mode': 297 | if Level == 0: 298 | flag = 1 299 | current_unit['power'] = 'false' 300 | Domoticz.Log("Switch Off the unit "+current_unit['name'] + 301 | "with ID offset " + str(current_unit['idoffset'])) 302 | Devices[1+current_unit['idoffset']].Update(nValue=0, sValue=str(Level), Image=9) 303 | Devices[2+current_unit['idoffset']].Update(nValue=0, 304 | sValue=str(Devices[Unit + 1].sValue)) 305 | Devices[3+current_unit['idoffset']].Update(nValue=0, 306 | sValue=str(Devices[Unit + 2].sValue)) 307 | Devices[4+current_unit['idoffset']].Update(nValue=0, 308 | sValue=str(Devices[Unit + 3].sValue)) 309 | Devices[5+current_unit['idoffset']].Update(nValue=0, 310 | sValue=str(Devices[Unit + 4].sValue)) 311 | Devices[6+current_unit['idoffset']].Update(nValue=0, 312 | sValue=str(Devices[Unit + 5].sValue)) 313 | elif Level == 10: 314 | Domoticz.Log("Set to WARM the unit "+current_unit['name']) 315 | Devices[1+current_unit['idoffset']].Update(nValue=1, sValue=str(Level), Image=15) 316 | elif Level == 20: 317 | Domoticz.Log("Set to COLD the unit "+current_unit['name']) 318 | Devices[1+current_unit['idoffset']].Update(nValue=1, sValue=str(Level), Image=16) 319 | elif Level == 30: 320 | Domoticz.Log("Set to Vent the unit "+current_unit['name']) 321 | Devices[1+current_unit['idoffset']].Update(nValue=1, sValue=str(Level), Image=7) 322 | elif Level == 40: 323 | Domoticz.Log("Set to Dry the unit "+current_unit['name']) 324 | Devices[1+current_unit['idoffset']].Update(nValue=1, sValue=str(Level), Image=11) 325 | elif Level == 50: 326 | Domoticz.Log("Set to Auto the unit "+current_unit['name']) 327 | Devices[1+current_unit['idoffset']].Update(nValue=1, sValue=str(Level), Image=11) 328 | if Level != 0: 329 | flag = 1 330 | current_unit['power'] = 'true' 331 | self.melcloud_set(current_unit, flag) 332 | flag = 6 333 | current_unit['power'] = 'true' 334 | current_unit['op_mode'] = self.domoticz_levels['mode'][str(Level)] 335 | Devices[2+current_unit['idoffset']].Update(nValue=1, 336 | sValue=str(Devices[Unit + 1].sValue)) 337 | Devices[3+current_unit['idoffset']].Update(nValue=1, 338 | sValue=str(Devices[Unit + 2].sValue)) 339 | Devices[4+current_unit['idoffset']].Update(nValue=1, 340 | sValue=str(Devices[Unit + 3].sValue)) 341 | Devices[5+current_unit['idoffset']].Update(nValue=1, 342 | sValue=str(Devices[Unit + 4].sValue)) 343 | Devices[6+current_unit['idoffset']].Update(nValue=1, 344 | sValue=str(Devices[Unit + 5].sValue)) 345 | elif switch_type == 'Fan': 346 | flag = 8 347 | current_unit['set_fan'] = self.domoticz_levels['fan'][str(Level)] 348 | Domoticz.Log("Change FAN to value {0} for {1} ".format(self.domoticz_levels['temp'][str(Level)], current_unit['name'])) 349 | Devices[Unit].Update(nValue=Devices[Unit].nValue, sValue=str(Level)) 350 | elif switch_type == 'Temp': 351 | flag = 4 352 | setTemp = 16 353 | if Level != 0: 354 | setTemp = int(str(Level).strip("0")) + 16 355 | Domoticz.Log("Change Temp to " + str(setTemp) + " for "+unit['name']) 356 | current_unit['set_temp'] = self.domoticz_levels['temp'][str(Level)] 357 | Devices[Unit].Update(nValue=Devices[Unit].nValue, sValue=str(Level)) 358 | elif switch_type == 'Vane Horizontal': 359 | flag = 256 360 | current_unit['vaneH'] = self.domoticz_levels['vaneH'][str(Level)] 361 | Domoticz.Debug("Change Vane Horizontal to value {0} for {1}".format(self.domoticz_levels['vaneH'][str(Level)], current_unit['name'])) 362 | Devices[Unit].Update(Devices[Unit].nValue, str(Level)) 363 | elif switch_type == 'Vane Vertical': 364 | flag = 16 365 | current_unit['vaneV'] = self.domoticz_levels['vaneV'][str(Level)] 366 | Domoticz.Debug("Change Vane Vertical to value {0} for {1}".format(self.domoticz_levels['vaneV'][str(Level)], current_unit['name'])) 367 | Devices[Unit].Update(Devices[Unit].nValue, str(Level)) 368 | else: 369 | Domoticz.Log("Device not found") 370 | self.melcloud_set(current_unit, flag) 371 | return True 372 | 373 | def onNotification(self, Name, Subject, Text, Status, Priority, Sound, ImageFile): 374 | Domoticz.Log("Notification: " + Name + "," + Subject + "," + Text + "," + 375 | Status + "," + str(Priority) + "," + Sound + "," + ImageFile) 376 | 377 | def onDisconnect(self, Connection): 378 | self.melcloud_state = "Not Ready" 379 | Domoticz.Log("MELCloud has disconnected") 380 | self.runAgain = 1 381 | 382 | def onHeartbeat(self): 383 | if (self.melcloud_conn is not None and (self.melcloud_conn.Connecting() or self.melcloud_conn.Connected())): 384 | if self.melcloud_state != "LOGIN_FAILED": 385 | Domoticz.Debug("Current MEL Cloud Key ID:"+str(self.melcloud_key)) 386 | for unit in self.list_units: 387 | self.melcloud_get_unit_info(unit) 388 | else: 389 | self.runAgain = self.runAgain - 1 390 | if self.runAgain <= 0: 391 | if self.melcloud_conn is None: 392 | self.melcloud_conn = Domoticz.Connection(Name="MELCloud", Transport="TCP/IP", Protocol="HTTPS", 393 | Address=self.melcloud_baseurl, Port=self.melcloud_port) 394 | self.melcloud_conn.Connect() 395 | self.runAgain = 6 396 | else: 397 | Domoticz.Debug("MELCloud https failed. Reconnected in "+str(self.runAgain)+" heartbeats.") 398 | 399 | def melcloud_create_units(self): 400 | Domoticz.Log("Units infos " + str(self.list_units)) 401 | if len(Devices) == 0: 402 | # Init Devices 403 | # Creation of switches 404 | Domoticz.Log("Find " + str(len(self.list_units)) + " devices in MELCloud") 405 | for device in self.list_units: 406 | Domoticz.Log("Creating device: " + device['name'] + " with melID " + str(device['id'])) 407 | for switch in self.list_switchs: 408 | # Create switchs 409 | if switch["typename"] == "Selector Switch": 410 | switch_options = {"LevelNames": switch["levels"], "LevelOffHidden": "false", "SelectorStyle": "1"} 411 | Domoticz.Device(Name=device['name'] + " - "+switch["name"], Unit=switch["id"]+device['idoffset'], 412 | TypeName=switch["typename"], Image=switch["image"], Options=switch_options, Used=1).Create() 413 | else: 414 | Domoticz.Device(Name=device['name'] + " - "+switch["name"], Unit=switch["id"]+device['idoffset'], 415 | TypeName=switch["typename"], Used=1).Create() 416 | 417 | def melcloud_send_data(self, url, values, state): 418 | self.melcloud_state = state 419 | if self.melcloud_key is not None: 420 | headers = {'Content-Type': 'application/x-www-form-urlencoded;', 421 | 'Host': self.melcloud_baseurl, 422 | 'User-Agent': 'Domoticz/1.0', 423 | 'X-MitsContextKey': self.melcloud_key} 424 | if state == "SET": 425 | self.melcloud_conn.Send({'Verb': 'POST', 'URL': url, 'Headers': headers, 'Data': values}) 426 | else: 427 | self.melcloud_conn.Send({'Verb': 'GET', 'URL': url, 'Headers': headers, 'Data': values}) 428 | else: 429 | headers = {'Content-Type': 'application/x-www-form-urlencoded;', 430 | 'Host': self.melcloud_baseurl, 431 | 'User-Agent': 'Domoticz/1.0'} 432 | self.melcloud_conn.Send({'Verb': 'POST', 'URL': url, 'Headers': headers, 'Data': values}) 433 | return True 434 | 435 | def melcloud_login(self): 436 | data = "AppVersion=1.9.3.0&Email={0}&Password={1}".format(Parameters["Username"], Parameters["Password"]) 437 | self.melcloud_send_data(self.melcloud_urls["login"], data, "LOGIN") 438 | return True 439 | 440 | def melcloud_add_unit(self, device, idoffset): 441 | melcloud_unit = {} 442 | melcloud_unit['name'] = device["DeviceName"] 443 | melcloud_unit['id'] = device["DeviceID"] 444 | melcloud_unit['macaddr'] = device["MacAddress"] 445 | melcloud_unit['sn'] = device["SerialNumber"] 446 | melcloud_unit['building_id'] = device["BuildingID"] 447 | melcloud_unit['power'] = "" 448 | melcloud_unit['op_mode'] = "" 449 | melcloud_unit['room_temp'] = "" 450 | melcloud_unit['set_temp'] = "" 451 | melcloud_unit['set_fan'] = "" 452 | melcloud_unit['vaneH'] = "" 453 | melcloud_unit['vaneV'] = "" 454 | melcloud_unit['next_comm'] = False 455 | melcloud_unit['idoffset'] = idoffset 456 | self.list_units.append(melcloud_unit) 457 | 458 | def melcloud_units_init(self): 459 | self.melcloud_send_data(self.melcloud_urls["list_unit"], None, "UNITS_INIT") 460 | return True 461 | 462 | def melcloud_set(self, unit, flag): 463 | post_fields = "Power={0}&DeviceID={1}&OperationMode={2}&SetTemperature={3}&SetFanSpeed={4}&VaneHorizontal={5}&VaneVertical={6}&EffectiveFlags={7}&HasPendingCommand=true" 464 | post_fields = post_fields.format(unit['power'], unit['id'], unit['op_mode'], unit['set_temp'], unit['set_fan'], unit['vaneH'], unit['vaneV'], flag) 465 | Domoticz.Debug("SET COMMAND SEND {0}".format(post_fields)) 466 | self.melcloud_send_data(self.melcloud_urls["set_unit"], post_fields, "SET") 467 | 468 | def melcloud_get_unit_info(self, unit): 469 | url = self.melcloud_urls["unit_info"] + "?id=" + str(unit['id']) + "&buildingID=" + str(unit['building_id']) 470 | self.melcloud_send_data(url, None, "UNIT_INFO") 471 | 472 | def domoticz_sync_switchs(self, unit): 473 | # Default value in case of problem 474 | setDomFan = 0 475 | setDomTemp = 0 476 | setDomVaneH = 0 477 | setDomVaneV = 0 478 | if unit['next_comm'] is not False: 479 | Devices[self.list_switchs[6]["id"]+unit["idoffset"]].Update(nValue=1, sValue=str(unit['next_comm'])) 480 | else: 481 | if unit['power']: 482 | switch_value = 1 483 | for level, mode in self.domoticz_levels["mode"].items(): 484 | if mode == unit['op_mode']: 485 | setModeLevel = level 486 | else: 487 | switch_value = 0 488 | setModeLevel = '0' 489 | for level, pic in self.domoticz_levels["mode_pic"].items(): 490 | if level == setModeLevel: 491 | setPicID = pic 492 | Devices[self.list_switchs[0]["id"]+unit["idoffset"]].Update(nValue=switch_value, 493 | sValue=setModeLevel, 494 | Image=setPicID) 495 | for level, fan in self.domoticz_levels["fan"].items(): 496 | if fan == unit['set_fan']: 497 | setDomFan = level 498 | Devices[self.list_switchs[1]["id"]+unit["idoffset"]].Update(nValue=switch_value, 499 | sValue=setDomFan) 500 | for level, temp in self.domoticz_levels["temp"].items(): 501 | if temp == unit['set_temp']: 502 | setDomTemp = level 503 | Devices[self.list_switchs[2]["id"]+unit["idoffset"]].Update(nValue=switch_value, 504 | sValue=setDomTemp) 505 | for level, vaneH in self.domoticz_levels["vaneH"].items(): 506 | if vaneH == unit['vaneH']: 507 | setDomVaneH = level 508 | Devices[self.list_switchs[3]["id"]+unit["idoffset"]].Update(nValue=switch_value, 509 | sValue=setDomVaneH) 510 | for level, vaneV in self.domoticz_levels["vaneV"].items(): 511 | if vaneV == unit['vaneV']: 512 | setDomVaneV = level 513 | Devices[self.list_switchs[4]["id"]+unit["idoffset"]].Update(nValue=switch_value, 514 | sValue=setDomVaneV) 515 | Devices[self.list_switchs[5]["id"]+unit["idoffset"]].Update(nValue=switch_value, 516 | sValue=str(unit['room_temp'])) 517 | 518 | 519 | global _plugin 520 | _plugin = BasePlugin() 521 | 522 | 523 | def onStart(): 524 | """ On start """ 525 | global _plugin 526 | _plugin.onStart() 527 | 528 | 529 | def onStop(): 530 | global _plugin 531 | """ On stop """ 532 | _plugin.onStop() 533 | 534 | 535 | def onConnect(Connection, Status, Description): 536 | global _plugin 537 | """ On connect """ 538 | _plugin.onConnect(Connection, Status, Description) 539 | 540 | 541 | def onMessage(Connection, Data): 542 | global _plugin 543 | """ On message """ 544 | _plugin.onMessage(Connection, Data) 545 | 546 | 547 | def onCommand(Unit, Command, Level, Hue): 548 | global _plugin 549 | """ On command """ 550 | _plugin.onCommand(Unit, Command, Level, Hue) 551 | 552 | 553 | def onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile): 554 | """ On notification """ 555 | global _plugin 556 | _plugin.onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile) 557 | 558 | 559 | def onDisconnect(Connection): 560 | """ On disconnect """ 561 | global _plugin 562 | _plugin.onDisconnect(Connection) 563 | 564 | 565 | def onHeartbeat(): 566 | """ Heartbeat """ 567 | global _plugin 568 | _plugin.onHeartbeat() 569 | 570 | 571 | if __name__ == "__main__": 572 | from Domoticz import Parameters 573 | from Domoticz import Images 574 | from Domoticz import Devices 575 | from TestCode import runtest 576 | 577 | runtest(BasePlugin()) 578 | exit(0) 579 | --------------------------------------------------------------------------------