├── fakeDomoticz.py ├── localtest.py ├── README.md ├── bravia.py └── plugin.py /fakeDomoticz.py: -------------------------------------------------------------------------------- 1 | # 2 | # Sony Bravia - Domoticz Python plugin 3 | # 4 | # With thanks to Frank Fesevur, 2017 5 | # 6 | # Very simple module to make local testing easier 7 | # It "emulates" Domoticz.Log(), Domoticz.Error and Domoticz.Debug() 8 | # 9 | 10 | def Log(s): 11 | print(s) 12 | 13 | def Error(s): 14 | print(s) 15 | 16 | def Debug(s): 17 | print(s) 18 | -------------------------------------------------------------------------------- /localtest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Sony Bravia - Domoticz Python plugin 4 | # G3rard 5 | # 6 | # With thanks to Frank Fesevur for localtest 7 | # 8 | try: 9 | import Domoticz 10 | except ImportError: 11 | import fakeDomoticz as Domoticz 12 | 13 | import os 14 | from bravia import BraviaRC 15 | import sys 16 | 17 | ### Enter the IP address, PSK and MAC address of the TV below 18 | ip = '192.168.1.191' 19 | psk = 'sony' 20 | mac = 'AA:BB:CC:DD:EE:FF' 21 | ### 22 | 23 | x = BraviaRC(ip, psk, mac) 24 | 25 | try: 26 | tvstatus = x.get_power_status() 27 | #print('Status TV:', tvstatus) 28 | except KeyError: 29 | print('TV not found') 30 | sys.exit() 31 | 32 | if tvstatus == 'active': 33 | #zet op NLD1 34 | #NLD1 = x.send_req_ircc(HOST, PSK, 'AAAAAQAAAAEAAAAAAw==') 35 | #NLD1 = x.send_req_ircc(x.get_command_code('Num1')) 36 | #print(NLD1) 37 | #zet op NLD2 38 | #NLD2 = x.send_req_ircc(HOST, PSK, 'AAAAAQAAAAEAAAABAw==') 39 | #NLD2 = x.send_req_ircc(x.get_command_code('Num2')) 40 | #print(NLD2) 41 | 42 | #go = x.send_req_ircc("AAAAAgAAABoAAABaAw==") #HDMI1 43 | 44 | tvplaying = x.get_playing_info() 45 | #print(tvplaying) 46 | if tvplaying['programTitle'] != None: 47 | print(tvplaying['title'], '-', tvplaying['programTitle']) 48 | starttime, endtime, perc_playingtime = x.playing_time(tvplaying['startDateTime'], tvplaying['durationSec']) 49 | print('Playing:', starttime, '-', endtime, '[' + str(perc_playingtime) + '%]') 50 | else: 51 | print('Playing:', tvplaying['title']) 52 | #get starttime (2017-03-24T00:00:00+0100) and calculate endtime based with duration (secs) 53 | 54 | #print(json.dumps(test, indent=2)) 55 | tvinfo = x.get_system_info() 56 | print('TV model:', tvinfo['model']) 57 | #print(json.dumps(test2, indent=2)) 58 | #test3 = get_network_info() 59 | 60 | network = x.get_network_info() 61 | print('MAC address:', network['mac']) 62 | #print(network) 63 | 64 | #test_vol = x.set_volume_level("20") 65 | #print(test_vol) 66 | 67 | vol = x.get_volume_info() 68 | print('Volume:', vol['volume']) 69 | 70 | #test_info = x.get_test_info() 71 | 72 | #test_mute = x.get_mute_info() 73 | #print(test_mute) 74 | 75 | #test = x.get_source('tv:dvbc') 76 | #print('Source list:', test) 77 | 78 | #test2= x.load_source_list() 79 | #print(test2) 80 | #test3 = x.select_source('HDMI 2') 81 | #print(test3) 82 | 83 | else: 84 | print('TV status:', tvstatus) #status is standby net na het uitzetten, daarna niet meer bereikbaar 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *This plugin is no longer maintained.* 2 | 3 | # Sony Bravia TV - Domoticz Python plugin 4 | **Domoticz Python plugin for Sony Bravia TV** 5 | 6 | *This plugin uses the [Sony Bravia RC API](https://github.com/aparraga/braviarc) by Antonio Parraga Navarro. 7 | Also a lot of inspiration came from this [post](http://www.domoticz.com/forum/viewtopic.php?f=61&t=8301) by StefanPuntNL.* 8 | 9 | ## Table of Contents 10 | - [Information](#information) 11 | - [Sony Bravia plugin instructions](#sony-bravia-plugin-instructions) 12 | - [TV settings](#tv-settings) 13 | - [Domoticz settings](#domoticz-settings) 14 | - [Screenshots](#screenshots) 15 | - [Testing the plugin](#testing-the-plugin) 16 | - [Confirmed working on the following TV´s](#confirmed-working-on-the-following-tvs) 17 | 18 | ## Information 19 | The plugin will show information from your Sony Bravia TV in Domoticz. 20 | It will work on Sony Bravia models 2013 and newer. Also tested on a number of Android models, see the list below. When using an Android you can enter 'Android' in the MAC field, so you can also switch the TV on via Domoticz. 21 | 22 | **Information on the TV channel and program are only showed when you use the built-in TV tuner. Otherwise for example HDMI1 will be shown.** 23 | 24 | **The volume slides shows the volume of the built-in speakers. If you have an amplifier attached to your TV, that volume is not shown.** 25 | *You can use the on/off button of the slider switch though to mute/unmute the TV, also if you have the TV connected to an amplifier.* 26 | 27 | **The remote control in the Status switch gives some extra options. See the screenshot below for extra info on some of the buttons.** 28 | 29 | ## Sony Bravia plugin instructions 30 | Works with Pre-Shared Key (PSK). The default PSK you can use is 'sony', but you can change the PSK to something else. If you change it, remember to also change the PSK in the Domoticz hardware page. 31 | 32 | ### TV settings 33 | * Enable remote start on your TV: [Settings] => [Network] => [Home Network Setup] => [Remote Start] => [On] 34 | 35 | * Enable pre-shared key on your TV: [Settings] => [Network] => [Home Network Setup] => [IP Control] => [Authentication] => [Normal and Pre-Shared Key] 36 | 37 | * Set pre-shared key on your TV: [Settings] => [Network] => [Home Network Setup] => [IP Control] => [Pre-Shared Key] => sony 38 | 39 | * Give your TV a static IP address, or make a DHCP reservation for a specific IP address in your router. 40 | 41 | * Determine the MAC address of your TV: [Settings] => [Network] => [Network Setup] => [View Network Status] 42 | 43 | The IP address, PSK and MAC address need to be entered in the Domoticz hardware page, see screenshot below. 44 | 45 | ### Domoticz settings 46 | See this [link](https://www.domoticz.com/wiki/Using_Python_plugins) for more information on the Domoticz plugins. 47 | This plugin works with the old and new version of the Python framework in Domoticz. 48 | * SSH to your server on which Domoticz is installed 49 | 50 | * Enter the following commands 51 | ```bash 52 | cd domoticz/plugins 53 | git clone https://github.com/gerard33/sony-bravia.git 54 | ``` 55 | * *When updating to the latest version on Github enter the following commands* 56 | ```bash 57 | cd domoticz/plugins/sony-bravia 58 | git pull 59 | ``` 60 | 61 | * Restart the Domoticz service 62 | ```bash 63 | sudo service domoticz.sh restart 64 | ``` 65 | 66 | * Now go to **Setup**, **Hardware** in your Domoticz interface. There you add 67 | **Sony Bravia TV**. 68 | 69 | Make sure you enter all the required fields. 70 | 71 | | Field | Information| 72 | | ----- | ---------- | 73 | | IP address | Enter the IP address of your TV (see instructions above how to find the IP address, also make sure it is static) | 74 | | Pre-shared key | Enter the Pre-shared key here (default is sony). If you have issues using a PSK you can try to use the cookie method at the [cookie branch](https://github.com/gerard33/sony-bravia/tree/cookie-auth), thanks to markcame for implementing this | 75 | | MAC address | Enter the MAC address of your TV (see instructions above how to find the MAC address). If you have an Android TV you can enter 'Android' (without quotes) in the MAC field. This way the plugin will use another method to switch on the TV. | 76 | | Volume bar | Option to enable or disable a Domoticz device for the volume bar, this can be used to control the volume of the TV, but only for the built-in speakers of the TV | 77 | | Update interval | Enter the update interval in seconds, this determines with which interval the plugin polls the TV (must be between 10 and 30 seconds) | 78 | | Debug | When set to true the plugin shows additional information in the Domoticz log | 79 | 80 | After clicking on the Add button the devices are available in the **Switches** tab. 81 | 82 | ## Screenshots 83 | ![tv](https://cloud.githubusercontent.com/assets/11230573/25202175/bc1c9db8-2554-11e7-9a0f-39d182c700f5.png) 84 | ![tv2](https://cloud.githubusercontent.com/assets/11230573/25202176/bc332e0c-2554-11e7-821d-bd76c58f7bf1.png) 85 | 86 | ![sony_tv_control_channel](https://cloud.githubusercontent.com/assets/11230573/25483849/d0a9b4c4-2b57-11e7-9875-193567029e3b.png) 87 | 88 | ![tv3](https://cloud.githubusercontent.com/assets/11230573/25202177/bc3f921e-2554-11e7-842c-96c863f210dc.png) 89 | 90 | ![tvhw](https://cloud.githubusercontent.com/assets/11230573/25202178/bcfb2998-2554-11e7-80ec-9b2e85ee59f4.png) 91 | 92 | ![remote_functions](https://cloud.githubusercontent.com/assets/11230573/25874696/faddb72a-3513-11e7-9a43-f658de2eec4c.png) 93 | 94 | ## Testing the plugin 95 | To do some easy testing with the plugin there is a `localtest.py` script that can be run from the command line. 96 | Make sure you enter the IP address and PSK of your TV in `localtest.py` before executing it. 97 | After that you can check if your TV works with the plugin. 98 | 99 | ```bash 100 | cd domoticz/plugins/sony-bravia 101 | ./localtest.py 102 | ``` 103 | 104 | This will print some information regarding the TV to the console. 105 | 106 | Thanks to ffes for `localtest.py` which is part of his [Buienradar](https://github.com/ffes/domoticz-buienradar) Python script. 107 | 108 | ## Confirmed working on the following TV´s 109 | - [x] KDL-50W829B 110 | 111 | - [x] KDL-42W705B 112 | 113 | - [x] KDL-55W805C 114 | 115 | - [x] KD-55X9005B 116 | 117 | - [x] KDL-42W805A --> with the use of cookies, check [this](http://www.domoticz.com/forum/viewtopic.php?f=65&t=16910&p=128866#p128866) for more information 118 | 119 | - [x] KDL-60W605B 120 | 121 | - [x] KD-49XD8305 Android 122 | 123 | - [x] KD-55X8509C Android 124 | 125 | - [x] KD-55SD8505 Android 126 | 127 | - [x] KD-55XD8505 Android 128 | 129 | - [x] KD-55XD8005 Android 130 | 131 | - [x] KDL-50W755c Android 132 | -------------------------------------------------------------------------------- /bravia.py: -------------------------------------------------------------------------------- 1 | # Sony Bravia RC API 2 | # By Antonio Parraga Navarro https://github.com/aparraga/braviarc 3 | 4 | # ### Updated by G3rard for Domoticz - Python plugin ### 5 | # Changes: 6 | # * use Pre-shared key (PSK) instead of connecting with a pin and the use of a cookie 7 | # * added function to calculate the playing time in % 8 | # * changed requests module to urllib due to Domoticz issue with requests 9 | # * changes to print to Domoticz log 10 | # * some other small changes 11 | 12 | try: 13 | import Domoticz 14 | except ImportError: 15 | import fakeDomoticz as Domoticz 16 | 17 | import logging 18 | import base64 19 | import collections 20 | import json 21 | import socket 22 | import struct 23 | import urllib.parse 24 | import urllib.request 25 | import urllib.error 26 | 27 | from datetime import datetime 28 | import time 29 | import sys 30 | 31 | TIMEOUT = 5 # timeout in seconds 32 | 33 | class BraviaRC: 34 | 35 | def __init__(self, host, psk, mac=None): # mac address is optional but necessary if we want to turn on the TV 36 | """Initialize the Sony Bravia RC class.""" 37 | 38 | self._host = host 39 | self._psk = psk 40 | self._mac = mac 41 | self._cookies = None 42 | self._commands = [] 43 | 44 | def _jdata_build(self, method, params): 45 | if params: 46 | ret = json.dumps({"method": method, "params": [params], "id": 1, "version": "1.0"}) 47 | else: 48 | ret = json.dumps({"method": method, "params": [], "id": 1, "version": "1.0"}) 49 | return ret 50 | 51 | def connect(self, pin, clientid, nickname): 52 | """Connect to TV and get authentication cookie. 53 | Parameters 54 | --------- 55 | pin: str 56 | Pin code show by TV (or 0000 to get Pin Code). 57 | clientid: str 58 | Client ID. 59 | nickname: str 60 | Client human friendly name. 61 | Returns 62 | ------- 63 | bool 64 | True if connected. 65 | """ 66 | authorization = json.dumps( 67 | {"method": "actRegister", 68 | "params": [{"clientid": clientid, 69 | "nickname": nickname, 70 | "level": "private"}, 71 | [{"value": "yes", 72 | "function": "WOL"}]], 73 | "id": 1, 74 | "version": "1.0"} 75 | ).encode('utf-8') 76 | 77 | headers = {} 78 | if pin: 79 | username = '' 80 | base64string = base64.encodebytes(('%s:%s' % (username, pin)).encode()) \ 81 | .decode().replace('\n', '') 82 | headers['Authorization'] = "Basic %s" % base64string 83 | headers['Connection'] = "keep-alive" 84 | 85 | try: 86 | req = urllib.request.Request('http://'+self._host+'/sony/accessControl', 87 | data=authorization, 88 | headers=headers) 89 | #response = urllib.request.urlopen(req, timeout=TIMEOUT) 90 | #response.raise_for_status() 91 | with urllib.request.urlopen(req, timeout=TIMEOUT) as response: 92 | response = response.read() 93 | 94 | except urllib.error.HTTPError as exception_instance: 95 | Domoticz.Debug("[bravia_connect] HTTPError: " + str(exception_instance)) 96 | return False 97 | 98 | except Exception as exception_instance: # pylint: disable=broad-except 99 | Domoticz.Debug("[bravia_connect] Exception: " + str(exception_instance)) 100 | return False 101 | 102 | else: 103 | Domoticz.Debug(str(response)) 104 | #self._cookies = response.cookies 105 | return True 106 | 107 | return False 108 | 109 | def is_connected(self): 110 | if self._cookies is None: 111 | return False 112 | else: 113 | return True 114 | 115 | def _wakeonlan(self): 116 | if self._mac is not None: 117 | addr_byte = self._mac.split(':') 118 | hw_addr = struct.pack('BBBBBB', int(addr_byte[0], 16), 119 | int(addr_byte[1], 16), 120 | int(addr_byte[2], 16), 121 | int(addr_byte[3], 16), 122 | int(addr_byte[4], 16), 123 | int(addr_byte[5], 16)) 124 | msg = b'\xff' * 6 + hw_addr * 16 125 | socket_instance = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 126 | socket_instance.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) 127 | socket_instance.sendto(msg, ('', 9)) 128 | socket_instance.close() 129 | 130 | def send_req_ircc(self, params, log_errors=True): 131 | """Send an IRCC command via HTTP to Sony Bravia.""" 132 | headers = {'X-Auth-PSK': self._psk, 'SOAPACTION': '"urn:schemas-sony-com:service:IRCC:1#X_SendIRCC"'} 133 | data = ("" + 136 | "" + 138 | params+"").encode("UTF-8") 139 | try: 140 | req = urllib.request.Request('http://' + self._host + '/sony/IRCC', 141 | data=data, 142 | headers=headers) 143 | #response = urllib.request.urlopen(req, timeout=TIMEOUT) 144 | with urllib.request.urlopen(req, timeout=TIMEOUT) as response: 145 | response = response.read() 146 | except urllib.error.HTTPError as exception_instance: 147 | if log_errors: 148 | Domoticz.Debug("[bravia_send_req_ircc] HTTPError: " + str(exception_instance)) 149 | 150 | except Exception as exception_instance: # pylint: disable=broad-except 151 | if log_errors: 152 | Domoticz.Debug("[bravia_send_req_ircc] Exception: " + str(exception_instance)) 153 | else: 154 | #content = response.content 155 | #return content 156 | return response 157 | 158 | def bravia_req_json(self, url, params, log_errors=True): 159 | """Send request command via HTTP json to Sony Bravia.""" 160 | try: 161 | req = urllib.request.Request(url='http://'+self._host+'/'+url, 162 | data=params.encode("UTF-8"), 163 | headers={'X-Auth-PSK': self._psk}) 164 | #response = urllib.request.urlopen(req) 165 | with urllib.request.urlopen(req, timeout=TIMEOUT) as response: 166 | response = response.read() 167 | #print(response.decode('utf-8')) 168 | except urllib.error.HTTPError as exception_instance: 169 | if log_errors: 170 | Domoticz.Debug("[bravia_bravia_req_json] HTTPError: " + str(exception_instance)) 171 | Domoticz.Debug('No reaction of TV due to HTTPError') 172 | 173 | except Exception as exception_instance: # pylint: disable=broad-except 174 | if log_errors: 175 | Domoticz.Debug("[bravia_bravia_req_json] Exception: " + str(exception_instance)) 176 | Domoticz.Debug('No reaction of TV, assumed it is off') 177 | 178 | else: 179 | html = json.loads(response.decode('utf-8')) 180 | #print(html) 181 | return html 182 | 183 | def send_command(self, command): 184 | """Sends a command to the TV.""" 185 | self.send_req_ircc(self.get_command_code(command)) 186 | 187 | def get_source(self, source): 188 | """Returns list of Sources.""" 189 | original_content_list = [] 190 | content_index = 0 191 | while True: 192 | resp = self.bravia_req_json("sony/avContent", 193 | self._jdata_build("getContentList", {"source": source, "stIdx": content_index})) 194 | if not resp.get('error'): 195 | if len(resp.get('result')[0]) == 0: 196 | break 197 | else: 198 | content_index = resp.get('result')[0][-1]['index']+1 199 | original_content_list.extend(resp.get('result')[0]) 200 | else: 201 | break 202 | return original_content_list 203 | 204 | def load_source_list(self): 205 | """Load source list from Sony Bravia.""" 206 | original_content_list = [] 207 | resp = self.bravia_req_json("sony/avContent", 208 | self._jdata_build("getSourceList", {"scheme": "tv"})) 209 | if not resp.get('error'): 210 | results = resp.get('result')[0] 211 | for result in results: 212 | if result['source'] in ['tv:dvbc', 'tv:dvbt']: # tv:dvbc = via cable, tv:dvbt = via DTT 213 | original_content_list.extend(self.get_source(result['source'])) 214 | 215 | resp = self.bravia_req_json("sony/avContent", 216 | self._jdata_build("getSourceList", {"scheme": "extInput"})) 217 | if not resp.get('error'): 218 | results = resp.get('result')[0] 219 | for result in results: 220 | if result['source'] == 'extInput:hdmi': # hdmi input 221 | ###if result['source'] in ('extInput:hdmi', 'extInput:composite', 'extInput:component'): # physical inputs 222 | ###new version, see https://github.com/aparraga/braviarc/commit/d9d26b802ffd669bae40f26160689173b0ce77c8 223 | resp = self.bravia_req_json("sony/avContent", 224 | self._jdata_build("getContentList", {"source": "extInput:hdmi"})) 225 | ###self._jdata_build("getContentList", result)) 226 | if not resp.get('error'): 227 | original_content_list.extend(resp.get('result')[0]) 228 | 229 | return_value = collections.OrderedDict() 230 | for content_item in original_content_list: 231 | return_value[content_item['title']] = content_item['uri'] 232 | return return_value 233 | 234 | def get_playing_info(self): 235 | """Get information on program that is shown on TV.""" 236 | return_value = {} 237 | resp = self.bravia_req_json("sony/avContent", self._jdata_build("getPlayingContentInfo", None)) 238 | 239 | if resp is not None and not resp.get('error'): 240 | playing_content_data = resp.get('result')[0] 241 | return_value['programTitle'] = playing_content_data.get('programTitle') 242 | return_value['title'] = playing_content_data.get('title') 243 | return_value['programMediaType'] = playing_content_data.get('programMediaType') 244 | return_value['dispNum'] = playing_content_data.get('dispNum') 245 | return_value['source'] = playing_content_data.get('source') 246 | return_value['uri'] = playing_content_data.get('uri') 247 | return_value['durationSec'] = playing_content_data.get('durationSec') 248 | return_value['startDateTime'] = playing_content_data.get('startDateTime') 249 | return return_value 250 | 251 | def get_power_status(self): 252 | """Get power status: off, active, standby.""" 253 | return_value = 'off' # by default the TV is turned off 254 | try: 255 | resp = self.bravia_req_json("sony/system", self._jdata_build("getPowerStatus", None), False) 256 | if resp is not None and not resp.get('error'): 257 | power_data = resp.get('result')[0] 258 | return_value = power_data.get('status') 259 | except: # pylint: disable=broad-except 260 | pass 261 | return return_value 262 | 263 | def _refresh_commands(self): 264 | resp = self.bravia_req_json("sony/system", self._jdata_build("getRemoteControllerInfo", None)) 265 | if not resp.get('error'): 266 | self._commands = resp.get('result')[1] 267 | else: 268 | Domoticz.Debug("[bravia_refresh_commands] JSON request error: " + json.dumps(resp, indent=4)) 269 | 270 | def get_command_code(self, command_name): 271 | if len(self._commands) == 0: 272 | self._refresh_commands() 273 | for command_data in self._commands: 274 | if command_data.get('name') == command_name: 275 | return command_data.get('value') 276 | return None 277 | 278 | def get_volume_info(self): 279 | """Get volume info.""" 280 | resp = self.bravia_req_json("sony/audio", self._jdata_build("getVolumeInformation", None)) 281 | if not resp.get('error'): 282 | results = resp.get('result')[0] 283 | for result in results: 284 | if result.get('target') == 'speaker': 285 | return result 286 | else: 287 | Domoticz.Debug("[get_volume_info] JSON request error:" + json.dumps(resp, indent=4)) 288 | return None 289 | 290 | def get_system_info(self): 291 | return_value = {} 292 | resp = self.bravia_req_json("sony/system", self._jdata_build("getSystemInformation", None)) 293 | if resp is not None and not resp.get('error'): 294 | #print('=>', resp, '<=') 295 | system_content_data = resp.get('result')[0] 296 | return_value['name'] = system_content_data.get('name') 297 | return_value['model'] = system_content_data.get('model') 298 | return_value['language'] = system_content_data.get('language') 299 | return return_value 300 | 301 | def get_network_info(self): 302 | return_value = {} 303 | resp = self.bravia_req_json("sony/system", self._jdata_build("getNetworkSettings", None)) 304 | if resp is not None and not resp.get('error'): 305 | #print('=>', resp, '<=') 306 | network_content_data = resp.get('result')[0] 307 | return_value['mac'] = network_content_data[0]['hwAddr'] 308 | return_value['ip'] = network_content_data[0]['ipAddrV4'] 309 | return_value['gateway'] = network_content_data[0]['gateway'] 310 | return return_value 311 | 312 | def set_volume_level(self, volume): 313 | """Set volume level, range 0..100.""" 314 | self.bravia_req_json("sony/audio", self._jdata_build("setAudioVolume", {"target": "speaker", 315 | "volume": volume})) 316 | 317 | def turn_on(self): 318 | """Turn the media player on using WOL.""" 319 | self._wakeonlan() 320 | 321 | def turn_on_command(self): 322 | """Turn the media player on using command. Only confirmed working on Android, can be used when WOL is not available.""" 323 | if self.get_power_status() != 'active': 324 | self.send_req_ircc(self.get_command_code('TvPower')) 325 | self.bravia_req_json("sony/system", self._jdata_build("setPowerStatus", {"status": "true"})) 326 | 327 | def turn_off(self): 328 | """Turn off media player.""" 329 | self.send_req_ircc(self.get_command_code('PowerOff')) 330 | 331 | def volume_up(self): 332 | """Volume up the media player.""" 333 | self.send_req_ircc(self.get_command_code('VolumeUp')) 334 | 335 | def volume_down(self): 336 | """Volume down media player.""" 337 | self.send_req_ircc(self.get_command_code('VolumeDown')) 338 | 339 | def mute_volume(self): #--> def mute_volume(self, mute): 340 | """Send mute command.""" 341 | self.send_req_ircc(self.get_command_code('Mute')) 342 | 343 | def select_source(self, source): 344 | """Set the input source.""" 345 | if source in self._content_mapping: 346 | uri = self._content_mapping[source] 347 | self.play_content(uri) 348 | 349 | def play_content(self, uri): 350 | """Play content by URI.""" 351 | self.bravia_req_json("sony/avContent", self._jdata_build("setPlayContent", {"uri": uri})) 352 | 353 | def media_play(self): 354 | """Send play command.""" 355 | self.send_req_ircc(self.get_command_code('Play')) 356 | 357 | def media_pause(self): 358 | """Send media pause command to media player.""" 359 | self.send_req_ircc(self.get_command_code('Pause')) 360 | 361 | def media_tv_pause(self): 362 | """Send media pause command to TV.""" 363 | self.send_req_ircc(self.get_command_code('TvPause')) 364 | 365 | def media_stop(self): 366 | """Send stopcommand to media player.""" 367 | self.send_req_ircc(self.get_command_code('Stop')) 368 | 369 | def media_next_track(self): 370 | """Send next track command.""" 371 | self.send_req_ircc(self.get_command_code('Next')) 372 | 373 | def media_previous_track(self): 374 | """Send the previous track command.""" 375 | self.send_req_ircc(self.get_command_code('Prev')) 376 | 377 | def calc_time(self, *times): 378 | """Calculate the sum of times, value is returned in HH:MM.""" 379 | totalSecs = 0 380 | for tm in times: 381 | timeParts = [int(s) for s in tm.split(':')] 382 | totalSecs += (timeParts[0] * 60 + timeParts[1]) * 60 + timeParts[2] 383 | totalSecs, sec = divmod(totalSecs, 60) 384 | hr, min = divmod(totalSecs, 60) 385 | if hr >= 24: #set 24:10 to 00:10 386 | hr -= 24 387 | return ("%02d:%02d" % (hr, min)) 388 | 389 | def playing_time(self, startDateTime, durationSec): 390 | """Give starttime, endtime and percentage played.""" 391 | #get starttime (2017-03-24T00:00:00+0100) and calculate endtime with duration (secs) 392 | date_format = "%Y-%m-%dT%H:%M:%S" 393 | try: 394 | playingtime = datetime.now() - datetime.strptime(startDateTime[:-5], date_format) 395 | except TypeError: 396 | #https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior 397 | #playingtime = datetime.now() - datetime.fromtimestamp(time.mktime(time.strptime(startDateTime[:-5], date_format))) 398 | playingtime = datetime.now() - datetime(*(time.strptime(startDateTime[:-5], date_format)[0:6])) 399 | try: 400 | starttime = datetime.time(datetime.strptime(startDateTime[:-5], date_format)) 401 | except TypeError: 402 | #starttime = datetime.time(datetime.fromtimestamp(time.mktime(time.strptime(startDateTime[:-5], date_format)))) 403 | starttime = datetime.time(datetime(*(time.strptime(startDateTime[:-5], date_format)[0:6]))) 404 | 405 | duration = time.strftime('%H:%M:%S', time.gmtime(durationSec)) 406 | endtime = self.calc_time(str(starttime), str(duration)) 407 | starttime = starttime.strftime('%H:%M') 408 | #print(playingtime.seconds, tvplaying['durationSec']) 409 | perc_playingtime = int(round(((playingtime.seconds / durationSec) * 100),0)) 410 | return str(starttime), str(endtime), str(perc_playingtime) 411 | -------------------------------------------------------------------------------- /plugin.py: -------------------------------------------------------------------------------- 1 | # 2 | # Sony Bravia Plugin 3 | # Author: G3rard, 2017 4 | # 5 | """ 6 | 7 | 8 | Sony Bravia plugin.

9 | It will work on Sony Bravia models 2013 and newer.
10 | Works with pre-shared key.

11 | Prerequisites:
12 | * Enable remote start on your TV: [Settings] => [Network] => [Home Network Setup] => [Remote Start] => [On]
13 | * Enable pre-shared key on your TV: [Settings] => [Network] => [Home Network Setup] => [IP Control] => [Authentication] => [Normal and Pre-Shared Key]
14 | * Set pre-shared key on your TV: [Settings] => [Network] => [Home Network Setup] => [IP Control] => [Pre-Shared Key] => sony
15 | * Give your TV a static IP address, or make a DHCP reservation for a specific IP address in your router.
16 | * Determine the MAC address of your TV: [Settings] => [Network] => [Network Setup] => [View Network Status]
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 |
37 | """ 38 | import Domoticz 39 | import datetime 40 | 41 | from bravia import BraviaRC 42 | 43 | class BasePlugin: 44 | powerOn = False 45 | tvState = 0 46 | tvVolume = 0 47 | tvSource = 0 48 | tvControl = 0 49 | tvChannel = 10 50 | tvPlaying = {} 51 | SourceOptions3 = {} 52 | SourceOptions4 = {} 53 | SourceOptions5 = {} 54 | startTime = '' 55 | endTime = '' 56 | perc_playingTime = 0 57 | _tv = None 58 | 59 | # Executed once at reboot/update, can create up to 255 devices 60 | def onStart(self): 61 | global _tv 62 | 63 | if Parameters["Mode6"] == "Debug": 64 | Domoticz.Debugging(1) 65 | DumpConfigToLog() 66 | 67 | _tv = BraviaRC(Parameters["Address"], Parameters["Mode1"], Parameters["Mode2"]) #IP, PSK, MAC 68 | 69 | self.SourceOptions3 = { "LevelActions" : "||||||", 70 | "LevelNames" : "Off|TV|HDMI1|HDMI2|HDMI3|HDMI4|Netflix", 71 | "LevelOffHidden": "true", 72 | "SelectorStyle" : "0" 73 | } 74 | self.SourceOptions4 = { "LevelActions" : "|||||", 75 | "LevelNames" : "Off|Play|Stop|Pause|TV Pause|Exit", 76 | "LevelOffHidden": "true", 77 | "SelectorStyle" : "0" 78 | } 79 | self.SourceOptions5 = { "LevelActions" : "||||||||||", 80 | "LevelNames" : "Off|CH1|CH2|CH3|CH4|CH5|CH6|CH7|CH8|CH9|--Choose a channel--", 81 | "LevelOffHidden": "true", 82 | "SelectorStyle" : "1" 83 | } 84 | 85 | if len(Devices) == 0: 86 | Domoticz.Device(Name="Status", Unit=1, Type=17, Image=2, Switchtype=17, Used=1).Create() 87 | if Parameters["Mode3"] == "Volume": Domoticz.Device(Name="Volume", Unit=2, Type=244, Subtype=73, Switchtype=7, Image=8, Used=1).Create() 88 | Domoticz.Device(Name="Source", Unit=3, TypeName="Selector Switch", Switchtype=18, Image=2, Options=self.SourceOptions3, Used=1).Create() 89 | Domoticz.Device(Name="Control", Unit=4, TypeName="Selector Switch", Switchtype=18, Image=2, Options=self.SourceOptions4, Used=1).Create() 90 | Domoticz.Device(Name="Channel", Unit=5, TypeName="Selector Switch", Switchtype=18, Image=2, Options=self.SourceOptions5, Used=1).Create() 91 | Domoticz.Log("Devices created") 92 | elif Parameters["Mode3"] == "Volume" and 2 not in Devices: 93 | Domoticz.Device(Name="Volume", Unit=2, Type=244, Subtype=73, Switchtype=7, Image=8, Used=1).Create() 94 | Domoticz.Log("Volume device created") 95 | elif Parameters["Mode3"] != "Volume" and 2 in Devices: 96 | Devices[2].Delete() 97 | Domoticz.Log("Volume device deleted") 98 | elif 1 not in Devices: 99 | Domoticz.Device(Name="Status", Unit=1, Type=17, Image=2, Switchtype=17, Used=1).Create() 100 | Domoticz.Log("TV device created") 101 | elif 3 not in Devices: 102 | Domoticz.Device(Name="Source", Unit=3, TypeName="Selector Switch", Switchtype=18, Image=2, Options=self.SourceOptions3, Used=1).Create() 103 | Domoticz.Log("Source device created") 104 | elif 4 not in Devices: 105 | Domoticz.Device(Name="Control", Unit=4, TypeName="Selector Switch", Switchtype=18, Image=2, Options=self.SourceOptions4, Used=1).Create() 106 | Domoticz.Log("Control device created") 107 | elif 5 not in Devices: 108 | Domoticz.Device(Name="Channel", Unit=5, TypeName="Selector Switch", Switchtype=18, Image=2, Options=self.SourceOptions5, Used=1).Create() 109 | Domoticz.Log("Channel device created") 110 | else: 111 | if 1 in Devices: self.tvState = Devices[1].nValue #--> of sValue 112 | if 2 in Devices: self.tvVolume = Devices[2].nValue #--> of sValue 113 | if 3 in Devices: self.tvSource = Devices[3].sValue 114 | if 4 in Devices: self.tvControl = Devices[4].sValue 115 | if 5 in Devices: self.tvChannel = Devices[5].sValue 116 | 117 | # Set update interval, values below 10 seconds are not allowed due to timeout of 5 seconds in bravia.py script 118 | updateInterval = int(Parameters["Mode5"]) 119 | if updateInterval < 30: 120 | if updateInterval < 10: updateInterval == 10 121 | Domoticz.Log("Update interval set to " + str(updateInterval) + " (minimum is 10 seconds)") 122 | Domoticz.Heartbeat(updateInterval) 123 | else: 124 | Domoticz.Heartbeat(30) 125 | 126 | return #--> return True 127 | 128 | # Executed each time we click on device through Domoticz GUI 129 | def onCommand(self, Unit, Command, Level, Hue): 130 | Domoticz.Log("onCommand called for Unit " + str(Unit) + ": Parameter '" + str(Command) + "', Level: " + str(Level)) 131 | 132 | Command = Command.strip() 133 | action, sep, params = Command.partition(' ') 134 | action = action.capitalize() 135 | params = params.capitalize() 136 | 137 | if self.powerOn == False: 138 | if Unit == 1: # TV power switch 139 | if action == "On": 140 | # Start TV when WOL is not available, only works on Android 141 | if Parameters["Mode2"] == "Android": 142 | Domoticz.Log("No MAC address configured, TV will be started with setPowerStatus command (Android only)") 143 | try: 144 | _tv.turn_on_command() 145 | self.tvPlaying = "TV starting" # Show that the TV is starting, as booting the TV takes some time 146 | self.SyncDevices() 147 | except Exception as err: 148 | Domoticz.Log("Error when starting TV with set PowerStatus, Android only (" + str(err) + ")") 149 | # Start TV using WOL 150 | else: 151 | try: 152 | _tv.turn_on() 153 | self.tvPlaying = "TV starting" # Show that the TV is starting, as booting the TV takes some time 154 | self.SyncDevices() 155 | except Exception as err: 156 | Domoticz.Log("Error when starting TV using WOL (" + str(err) + ")") 157 | else: 158 | if Unit == 1: # TV power switch 159 | if action == "Off": 160 | _tv.turn_off() 161 | self.tvPlaying = "Off" 162 | self.SyncDevices() 163 | # Remote buttons (action is capitalized so chosen for Command) 164 | elif Command == "ChannelUp": _tv.send_req_ircc("AAAAAQAAAAEAAAAQAw==") # ChannelUp 165 | elif Command == "ChannelDown": _tv.send_req_ircc("AAAAAQAAAAEAAAARAw==") # ChannelDown 166 | elif Command == "Channels": _tv.send_req_ircc("AAAAAQAAAAEAAAA6Aw==") # Display, shows information on what is playing 167 | elif Command == "VolumeUp": _tv.send_req_ircc("AAAAAQAAAAEAAAASAw==") # VolumeUp 168 | elif Command == "VolumeDown": _tv.send_req_ircc("AAAAAQAAAAEAAAATAw==") # VolumeDown 169 | elif Command == "Mute": _tv.send_req_ircc("AAAAAQAAAAEAAAAUAw==") # Mute 170 | elif Command == "Select": _tv.send_req_ircc("AAAAAQAAAAEAAABlAw==") # Confirm 171 | elif Command == "Up": _tv.send_req_ircc("AAAAAQAAAAEAAAB0Aw==") # Up 172 | elif Command == "Down": _tv.send_req_ircc("AAAAAQAAAAEAAAB1Aw==") # Down 173 | elif Command == "Left": _tv.send_req_ircc("AAAAAQAAAAEAAAA0Aw==") # Left 174 | elif Command == "Right": _tv.send_req_ircc("AAAAAQAAAAEAAAAzAw==") # Right 175 | elif Command == "Home": _tv.send_req_ircc("AAAAAQAAAAEAAABgAw==") # Home 176 | elif Command == "Info": _tv.send_req_ircc("AAAAAgAAAKQAAABbAw==") # EPG 177 | elif Command == "Back": _tv.send_req_ircc("AAAAAgAAAJcAAAAjAw==") # Return 178 | elif Command == "ContextMenu": _tv.send_req_ircc("AAAAAgAAAJcAAAA2Aw==") # Options 179 | elif Command == "FullScreen": _tv.send_req_ircc("AAAAAQAAAAEAAABjAw==") # Exit 180 | elif Command == "ShowSubtitles": _tv.send_req_ircc("AAAAAQAAAAEAAAAlAw==") # Input 181 | elif Command == "Stop": _tv.send_req_ircc("AAAAAgAAAJcAAAAYAw==") # Stop 182 | elif Command == "BigStepBack": _tv.send_req_ircc("AAAAAgAAAJcAAAAZAw==") # Pause 183 | elif Command == "Rewind": _tv.send_req_ircc("AAAAAgAAAJcAAAAbAw==") # Rewind 184 | elif Command == "PlayPause": _tv.send_req_ircc("AAAAAgAAABoAAABnAw==") # TV pause 185 | elif Command == "FastForward": _tv.send_req_ircc("AAAAAgAAAJcAAAAcAw==") # Forward 186 | elif Command == "BigStepForward": _tv.send_req_ircc("AAAAAgAAAJcAAAAaAw==") # Play 187 | 188 | if Unit == 2: # TV volume 189 | if action == 'Set': 190 | self.tvVolume = str(Level) 191 | _tv.set_volume_level(self.tvVolume) 192 | elif action == "Off": 193 | _tv.mute_volume() 194 | UpdateDevice(2, 0, str(self.tvVolume)) 195 | elif action == "On": 196 | _tv.mute_volume() 197 | UpdateDevice(2, 1, str(self.tvVolume)) 198 | 199 | if Unit == 3: # TV source 200 | if Command == 'Set Level': 201 | if Level == 10: 202 | _tv.send_req_ircc("AAAAAQAAAAEAAAAAAw==") #TV Num1 203 | self.GetTVInfo() 204 | if Level == 20: 205 | _tv.send_req_ircc("AAAAAgAAABoAAABaAw==") #HDMI1 206 | self.tvPlaying = "HDMI 1" 207 | if Level == 30: 208 | _tv.send_req_ircc("AAAAAgAAABoAAABbAw==") #HDMI2 209 | self.tvPlaying = "HDMI 2" 210 | if Level == 40: 211 | _tv.send_req_ircc("AAAAAgAAABoAAABcAw==") #HDMI3 212 | self.tvPlaying = "HDMI 3" 213 | if Level == 50: 214 | _tv.send_req_ircc("AAAAAgAAABoAAABdAw==") #HDMI4 215 | self.tvPlaying = "HDMI 4" 216 | if Level == 60: 217 | _tv.send_req_ircc("AAAAAgAAABoAAAB8Aw==") #Netflix 218 | self.tvPlaying = "Netflix" 219 | self.tvSource = Level 220 | self.SyncDevices() 221 | 222 | if Unit == 4: # TV control 223 | if Command == 'Set Level': 224 | if Level == 10: _tv.send_req_ircc("AAAAAgAAAJcAAAAaAw==") #Play 225 | if Level == 20: _tv.send_req_ircc("AAAAAgAAAJcAAAAYAw==") #Stop 226 | if Level == 30: _tv.send_req_ircc("AAAAAgAAAJcAAAAZAw==") #Pause 227 | if Level == 40: _tv.send_req_ircc("AAAAAgAAABoAAABnAw==") #Pause TV 228 | if Level == 50: _tv.send_req_ircc("AAAAAQAAAAEAAABjAw==") #Exit 229 | self.tvControl = Level 230 | self.SyncDevices() 231 | 232 | if Unit == 5: # TV channels 233 | if Command == 'Set Level': 234 | if Level == 10: _tv.send_req_ircc("AAAAAQAAAAEAAAAAAw==") #1 235 | if Level == 20: _tv.send_req_ircc("AAAAAQAAAAEAAAABAw==") #2 236 | if Level == 30: _tv.send_req_ircc("AAAAAQAAAAEAAAACAw==") #3 237 | if Level == 40: _tv.send_req_ircc("AAAAAQAAAAEAAAADAw==") #4 238 | if Level == 50: _tv.send_req_ircc("AAAAAQAAAAEAAAAEAw==") #5 239 | if Level == 60: _tv.send_req_ircc("AAAAAQAAAAEAAAAFAw==") #6 240 | if Level == 70: _tv.send_req_ircc("AAAAAQAAAAEAAAAGAw==") #7 241 | if Level == 80: _tv.send_req_ircc("AAAAAQAAAAEAAAAHAw==") #8 242 | if Level == 90: _tv.send_req_ircc("AAAAAQAAAAEAAAAIAw==") #9 243 | # Level 100 = --Choose a channel-- 244 | self.tvChannel = Level 245 | self.SyncDevices() 246 | 247 | return 248 | 249 | # Execution depend of Domoticz.Heartbeat(x) x in seconds 250 | def onHeartbeat(self): 251 | try: 252 | tvStatus = _tv.get_power_status() 253 | Domoticz.Debug("Status TV: " + str(tvStatus)) 254 | except Exception as err: 255 | Domoticz.Log("Not connected to TV (" + str(err) + ")") 256 | 257 | if tvStatus == 'active': # TV is on 258 | self.powerOn = True 259 | try: 260 | self.GetTVInfo() 261 | except Exception as err: 262 | Domoticz.Error("No data received from TV, probably it has just been turned off (" + str(err) + ")") 263 | else: # TV is off or standby 264 | self.powerOn = False 265 | self.SyncDevices() 266 | 267 | return 268 | 269 | def SyncDevices(self): 270 | # TV is off 271 | if self.powerOn == False: 272 | if self.tvPlaying == "TV starting": # TV is booting and not yet responding to get_power_status 273 | UpdateDevice(1, 1, self.tvPlaying) 274 | #UpdateDevice(3, 1, self.tvSource) 275 | else: # TV is off so set devices to off 276 | self.ClearDevices() 277 | # TV is on 278 | else: 279 | if self.tvPlaying == "Off": # TV is set to off in Domoticz, but self.powerOn is still true 280 | self.ClearDevices() 281 | else: # TV is on so set devices to on 282 | if not self.tvPlaying: 283 | Domoticz.Debug("No information from TV received (TV was paused and then continued playing from disk) - SyncDevices") 284 | else: 285 | UpdateDevice(1, 1, self.tvPlaying) 286 | UpdateDevice(3, 1, str(self.tvSource)) 287 | if Parameters["Mode3"] == "Volume": UpdateDevice(2, 2, str(self.tvVolume)) 288 | UpdateDevice(4, 1, str(self.tvControl)) 289 | UpdateDevice(5, 1, str(self.tvChannel)) 290 | 291 | return 292 | 293 | def ClearDevices(self): 294 | self.tvPlaying = "Off" 295 | UpdateDevice(1, 0, self.tvPlaying) #Status 296 | if Parameters["Mode3"] == "Volume": UpdateDevice(2, 0, str(self.tvVolume)) #Volume 297 | self.tvSource = 0 298 | self.tvControl = 0 299 | self.tvChannel = 0 300 | UpdateDevice(3, 0, str(self.tvSource)) #Source 301 | UpdateDevice(4, 0, str(self.tvControl)) #Control 302 | UpdateDevice(5, 0, str(self.tvChannel)) #Channel 303 | 304 | return 305 | 306 | def GetTVInfo(self): 307 | self.tvPlaying = _tv.get_playing_info() 308 | if not self.tvPlaying: # Dict is empty 309 | Domoticz.Debug("No information from TV received (TV was paused and then continued playing from disk)") 310 | else: 311 | if self.tvPlaying['programTitle'] != None: # Get information on channel and program title if tuner of TV is used 312 | if self.tvPlaying['startDateTime'] != None: # Show start time and end time of program 313 | self.startTime, self.endTime, self.perc_playingTime = _tv.playing_time(self.tvPlaying['startDateTime'], self.tvPlaying['durationSec']) 314 | self.tvPlaying = str(int(self.tvPlaying['dispNum'])) + ': ' + self.tvPlaying['title'] + ' - ' + self.tvPlaying['programTitle'] + ' [' + str(self.startTime) + ' - ' + str(self.endTime) +']' 315 | Domoticz.Debug("Program information: " + str(self.startTime) + "-" + str(self.endTime) + " [" + str(self.perc_playingTime) + "%]") 316 | else: 317 | self.tvPlaying = str(int(self.tvPlaying['dispNum'])) + ': ' + self.tvPlaying['title'] + ' - ' + self.tvPlaying['programTitle'] 318 | UpdateDevice(1, 1, self.tvPlaying) 319 | self.tvSource = 10 320 | UpdateDevice(3, 1, str(self.tvSource)) # Set source device to TV 321 | else: # No program info found 322 | if self.tvPlaying['title'] != '': 323 | self.tvPlaying = self.tvPlaying['title'] 324 | else: 325 | self.tvPlaying = "Netflix" # When TV plays apps, no title information (in this case '') is available, so assume Netflix is playing 326 | if "/MHL" in self.tvPlaying: # Source contains /MHL, that can be removed 327 | self.tvPlaying = self.tvPlaying.replace("/MHL", "") 328 | UpdateDevice(1, 1, self.tvPlaying) 329 | if "HDMI 1" in self.tvPlaying: 330 | self.tvSource = 20 331 | UpdateDevice(3, 1, str(self.tvSource)) # Set source device to HDMI1 332 | elif "HDMI 2" in self.tvPlaying: 333 | self.tvSource = 30 334 | UpdateDevice(3, 1, str(self.tvSource)) # Set source device to HDMI2 335 | elif "HDMI 3" in self.tvPlaying: 336 | self.tvSource = 40 337 | UpdateDevice(3, 1, str(self.tvSource)) # Set source device to HDMI3 338 | elif "HDMI 4" in self.tvPlaying: 339 | self.tvSource = 50 340 | UpdateDevice(3, 1, str(self.tvSource)) # Set source device to HDMI4 341 | elif "Netflix" in self.tvPlaying: 342 | self.tvSource = 60 343 | UpdateDevice(3, 1, str(self.tvSource)) # Set source device to Netflix 344 | 345 | # Get volume information of TV 346 | if Parameters["Mode3"] == "Volume": 347 | self.tvVolume = _tv.get_volume_info() 348 | self.tvVolume = self.tvVolume['volume'] 349 | if self.tvVolume != None: UpdateDevice(2, 2, str(self.tvVolume)) 350 | 351 | # Update control and channel devices 352 | UpdateDevice(4, 1, str(self.tvControl)) 353 | UpdateDevice(5, 1, str(self.tvChannel)) 354 | 355 | return 356 | 357 | _plugin = BasePlugin() 358 | 359 | def onStart(): 360 | _plugin.onStart() 361 | 362 | def onCommand(Unit, Command, Level, Hue): 363 | _plugin.onCommand(Unit, Command, Level, Hue) 364 | 365 | def onHeartbeat(): 366 | _plugin.onHeartbeat() 367 | 368 | # Update Device into database 369 | def UpdateDevice(Unit, nValue, sValue, AlwaysUpdate=False): 370 | # Make sure that the Domoticz device still exists (they can be deleted) before updating it 371 | if Unit in Devices: 372 | if ((Devices[Unit].nValue != nValue) or (Devices[Unit].sValue != sValue) or (AlwaysUpdate == True)): 373 | Devices[Unit].Update(nValue, str(sValue)) 374 | Domoticz.Log("Update " + Devices[Unit].Name + ": " + str(nValue) + " - '" + str(sValue) + "'") 375 | return 376 | 377 | # Generic helper functions 378 | def DumpConfigToLog(): 379 | for x in Parameters: 380 | if Parameters[x] != "": 381 | Domoticz.Debug( "'" + x + "':'" + str(Parameters[x]) + "'") 382 | Domoticz.Debug("Device count: " + str(len(Devices))) 383 | for x in Devices: 384 | Domoticz.Debug("Device: " + str(x) + " - " + str(Devices[x])) 385 | Domoticz.Debug("Internal ID: '" + str(Devices[x].ID) + "'") 386 | Domoticz.Debug("External ID: '" + str(Devices[x].DeviceID) + "'") 387 | Domoticz.Debug("Device Name: '" + Devices[x].Name + "'") 388 | Domoticz.Debug("Device nValue: " + str(Devices[x].nValue)) 389 | Domoticz.Debug("Device sValue: '" + Devices[x].sValue + "'") 390 | Domoticz.Debug("Device LastLevel: " + str(Devices[x].LastLevel)) 391 | return 392 | --------------------------------------------------------------------------------