├── .gitignore ├── .sonarcloud.properties ├── README.md ├── freebox.py ├── freebox_certificates.pem └── plugin.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /.sonarcloud.properties: -------------------------------------------------------------------------------- 1 | sonar.python.version=3.8 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Domoticz Freebox Plugin 2 | 3 | Le Plugin "Freebox API" permet de piloter votre Freebox depuis votre serveur Domoticz. 4 | 5 | **L'objectif est de continuer à faire vivre et enrichir le développement initialement proposé par supermat (mais qu'il ne maintient plus suite à son déménagement)** 6 | 7 | **_Remarque importantes_ :** 8 | 9 | **- Si vous utilisiez le plugin initialement proposé par supermat, il sera nécessaire de supprimer la version "matiériel" ainsi que le dossier "PluginDomoticzFreebox)" avant de poursuivre...** 10 | 11 | **- Le plugin proposé a été testé dans un nombre limité de cas. Il peut comporter des "bugs" (merci dans ce cas de me le remonter). Il est fournis sans garentie.** 12 | 13 | ## Fonctionnalités 14 | 15 | * Création du token d'authentification lors du premier lancement (une validation est nécessaire via l'écran tactile du Freebox serveur) 16 | * Création d'un dispositif par partition (disques internes et externes) affichant son taux d'occupation 17 | * Création de dispositifs remontant l'ensemble des sondes de températures de la Freebox 18 | * Création d'un dispositif de type switch permettant de superviser la disponibilité d'un équipement connecté à la Freebox (via son adresse mac). Ainsi il est possible de déduire la présence d'une personne au domicile (via la supervision de son smartphone). 19 | * Création d'un dispositif switch de suivi et modification de l'état du wifi (actif/inactif) 20 | * Création d'un dispositif switch permettant le redémarrage du Freebox serveur 21 | * Création d'un dispositif switch d'état de la connexion Internet (WAN) 22 | * Création de deux dispositifs pour le suivi du debit montant et descendant (en Ko/s) 23 | * Création d'un dispositif switch pour suivre l’état ou activer/désactiver l’alarme (uniquement en option sur Freebox Delta) 24 | 25 | ## Installation 26 | 27 | Requis : Python version 3.8 or supérieur & Domoticz version 2022.1 ou supérieur. 28 | 29 | * En ligne de commande aller dans le répertoire plugin de Domoticz (domoticz/plugins) 30 | * Lancer la commande: ```git clone https://github.com/ilionel/PluginDomoticzFreebox.git``` 31 | * Redemmarrer le service Domoticz via la commande ```sudo service domoticz restart``` 32 | 33 | ## Updating 34 | 35 | Pour mettre à jour le plugin : 36 | 37 | * En ligne de commande aller dans le répertoire des plugins de Domoticz (domoticz/plugins) 38 | * Lancer la commande: ```git pull``` 39 | * Redemmarrer le service Domoticz via la commande ```sudo service domoticz restart``` 40 | 41 | ## Configuration 42 | 43 | | Field | Information| 44 | | ----- | ---------- | 45 | | URL | L'adresse d'accès à la Freebox (typiquement "https://mafreebox.freebox.fr") | 46 | | Port | Le port pour accéder à l'interface Web de la Freebox (généralement "443" pour de https en réseau local) | 47 | | Token | Le « Token » de connexion à la Freebox qui vous sera communiqué lors de la première connexion du plugin (et visible dans les logs de Domoticz) | 48 | | Liste @mac pour la présence (séparé par ;) | La liste d'adresse mac dont vous souhaitez monitorer la disponibilité (quand elles sont connectées à la Freebox) | 49 | | Option Debug | Pour obtenir des logs « très » détaillés (visible dans les journaux de Domoticz) | 50 | 51 | Depuis le menu « Matériel » de Domoticz : 52 | - Chercher 'Freebox (via API)' 53 | - Laisser les valeurs par défaut, si votre serveur Domoticz est en un réseau local, ou sinon paramétrer l’adresse et port réseau de votre Freebox. 54 | - Ajouter le Matériel puis consulter les journaux de logs (depuis le menu Paramètre de Domoticz). 55 | 56 | Note : Lors de la première utilisation le champ « Token » doit rester vide. 57 | 58 | Au démarrage du plugin, si aucun token n’est renseigné, le plugin va s’enregistrer auprès de la Freebox. Vous devrez alors valider la demande directement depuis l’écran de votre box. Après avoir répondu « oui » (il faudra vous déplacer jusqu’à votre Freebox), vous devrez « copier » (puis coller) le token qui s'affichera dans les logs du serveur Domoticz. 59 | Ce token sera à « coller » dans le champ « Token » de la configuration du plugin. 60 | 61 | Enfin, pour créer les dispositifs il faudra: 62 | - Désactiver le plugin 63 | - Autoriser l'ajout de nouveau dispositif pendant 5 minutes 64 | - Réactiver le plugin 65 | 66 | A chaque démarrage du plugin, les dispositifs nouveaux ou maquants seront ajoutés. 67 | Vous pouvez choisir d’inclure ou non ces dispositifs à depuis l'interface dediée. 68 | 69 | | Dispositifs | Description| 70 | | ----- | ---------- | 71 | | Système | Remontées des températures des sondes internes à la Freebox | 72 | | Disque | Affichage du taux d'occupation (en %) de chaque partitions du/des disque(s) connecté(s) à la Freebox | 73 | | Présence | Test la présence d'un équipement. Pour chaque adresse mac renseignée, si celle-ci est joignable par la Freebox, un dispositif de type Switch sera créé. Il indiquera la disponibilité (on) ou l'absence (off) de l'équipement. Cela permet de déduire la présence d'une personne au domicile en testant (par exemple) la présence de sont smartphone. | 74 | | Wifi | Affichage du statut du wifi (actif/inactif) et Switch permettant de modifier l'état (activation/désactivation) | 75 | | Reboot | Switch permettant le redémarrage de la Freebox | 76 | | WANStatus | État de la connexion Internet | 77 | | Debits | Débits montants et descendants en Ko/s | 78 | | Alarme | Dispositif de type Switch permettant de voir l'état de l'alarme (active/inactive) et de piloter son activation/désactivation | 79 | | Players | Dispositif(s) de type Switch permettant d'alumer / éteindre le(s) player(s) TV | 80 | | Precord | Dispositif indiquant dans combien de temps aura lieu le prochain enregistrement de programme TV. L'idée est de déterminer plus facilement si le serveur Freebox peut-être éteint ou non (exemple en pilotant une mutiprise la nuit) | 81 | 82 | IMPORTANT : Pour piloter le wifi, le redémarrage de la box, gérer le player ou l'alarme, il est nécessaire d'accorder des droits spécifiques au plugin. Effectivement après l'inscription, le plugin n'aura que de simples droits de consultations. 83 | Les droits de modification devront être positionnés manuellement via l'interface Freebox OS (depuis un navigateur http://mafreebox.freebox.fr) : menu "Paramètres de la Freebox" > "Gestion des accès" > "onglet Applications", sélectionner le plugin Domoticz puis cocher : 84 | - "Modifications des réglages de la Freebox" (pour permettre la gestion du wifi ou le redémarrage de la box) 85 | - "Gestion de l'alarme et maison connectée" (pour permettre la gestion de l'alarme "Freebox Delta") 86 | - "Contrôle du Freebox Player" (pour mettre la supervision du player TV) 87 | 88 | Note : Un fichier ```devicemapping.json``` est créé pour garder l'association des infos de la Freebox avec le bon device créé au moment du démarrage du Plugin. 89 | 90 | ## Change log 91 | 92 | | Version | Information| 93 | | ----- | ---------- | 94 | | 1.0 | Version initial : connexion (token), températures système, espace disque, présence (https://matdomotique.wordpress.com/2018/03/25/plugin-freebox-pour-domoticz/)| 95 | | 1.1 | Ajout des switch « WIFI » et « Reboot ». Ajout d'une pause au démarrage du plugin pour corriger certains pb | 96 | | 1.1.1 | Prise en compte de l'adresse MAC en Majuscule ou minuscule pour la présence | 97 | | 1.1.2 | Prise en compte de la Freebox mini 4K (qui n'a pas de disque interne) en conservant l'usage des disques externes | 98 | | 1.1.3 | Prise en compte de la Freebox POP pour les températures (slallemand) et Ajout des détails de connection debits montants et descendant en Ko/s (chupi33 et les tests des ViaudJV) | 99 | | 1.2.0 | Reprise et refonte du code pour un meilleur respect des standards de programmations Python | 100 | | 1.3.0 | Intégration de l'option alarme de la FreeBox Delta (sur base du développement proposé par nachonam) | 101 | | 1.4.0 | Ajout du support d'HTTPS/TLS (chiffrement des communications) | 102 | | 1.5.0 | Integration _*Expérimentale*_ du player TV (supervision de l’état allumé/éteint) | 103 | | 2.0.0 | Passage à l'API v8 + correctifs (sondes températures, alarme, disque...) | 104 | | 2.1.0 | Amélioration de la robustesse du code suite au passage en 2.0 | 105 | | 2.1.1 | Possibilitée de paramétrer la fréquence de rafraîchissement des valeurs | 106 | | 2.1.2 | Possibilité d'éteindre le Player via Domoticz | 107 | | 2.1.3 | Ajout compteur indiquant le délai (en seconde) avant le prochain enregistrement | 108 | | 2.1.4 | Suivi de recommandations Sonarcube (amélioration de la qualité du code) | -------------------------------------------------------------------------------- /freebox.py: -------------------------------------------------------------------------------- 1 | # Thanks to https://www.manatlan.com/blog/freeboxv6_api_v3_avec_python 2 | # and https://github.com/supermat/PluginDomoticzFreebox 3 | # Code under GPLv3 4 | # AUTHOR : supermat & ilionel 5 | # CONTRIBUTOR : https://github.com/ilionel/PluginDomoticzFreebox/graphs/contributors 6 | # Please not that supermat don't maintain this software anymore 7 | 8 | """ 9 | freebox.py is used by plugin.py 10 | """ 11 | 12 | 13 | import hashlib 14 | import hmac 15 | import json 16 | import os 17 | import re 18 | import ssl 19 | import time 20 | import urllib.request 21 | from urllib.request import urlopen, Request 22 | from socket import timeout 23 | import Domoticz 24 | 25 | # Globals CONSTANT 26 | HOST = 'https://mafreebox.freebox.fr' # FQDN of freebox 27 | API_VER = '8' # API version 28 | TV_API_VER = '8' # TV Player API version 29 | REGISTER_TMOUT = 30 # Timout in sec (for Obtain an app_token) 30 | API_TMOUT = 4 # Timout in sec (for API response) 31 | CA_FILE = 'freebox_certificates.pem' 32 | 33 | 34 | class FbxCnx: 35 | """ 36 | FbxCnx describes methods to communicate with Freebox 37 | """ 38 | 39 | def __init__(self, host=HOST, api=API_VER): 40 | self.host = host 41 | self.api_ver = int(float(api)) 42 | self.info = None 43 | self.secure = ssl.create_default_context() 44 | cert_path = os.path.join(os.path.dirname(__file__), CA_FILE) 45 | request = Request(host + '/api_version') 46 | try: 47 | self.secure.load_verify_locations(cafile=cert_path) 48 | response = urlopen(request, timeout=API_TMOUT, 49 | context=self.secure).read() 50 | self.info = json.loads(response.decode()) 51 | Domoticz.Debug('Supported API version: ' + 52 | f"{self.info['api_version']}") 53 | Domoticz.Debug('Freebox model: ' + f"{self.info['box_model']}") 54 | except (urllib.error.HTTPError, urllib.error.URLError) as error: 55 | Domoticz.Error('Init error ("/api_version"): ' + error.msg) 56 | except timeout: 57 | Domoticz.Error('Timeout when call ("/api_version")') 58 | if self.info is None: 59 | Domoticz.Error( 60 | 'Fatal error: Unable to initialize Freebox connection!') 61 | elif int(float(self.info['api_version'])) < self.api_ver: 62 | Domoticz.Error(f"You need to upgrade Freebox's firmware to use at last API version \ 63 | {self.api_ver} (current API version: {self.info['api_version']}).") 64 | 65 | def _request(self, path, method='GET', headers=None, data=None): 66 | """ Send a request to Freebox API 67 | 68 | Args: 69 | path (str): api_url 70 | method (str, optional): method used for each request GET|POST|PUT. Defaults to 'GET'. 71 | headers (dict of str: str, optional): HTTP HEADERS. Defaults to None. 72 | data (dict of str: str, optional): POST or PUT datas. Defaults to None. 73 | 74 | Returns: 75 | (dict of str: str): Freebox API Response as dictionary 76 | """ 77 | url = self.host + '/api/v' + str(self.api_ver) + '/' + path 78 | Domoticz.Debug('API REQUEST - URL: ' + url) 79 | Domoticz.Debug('API REQUEST - Method: ' + method) 80 | Domoticz.Debug('API REQUEST - Headers: ' + f"{headers}") 81 | Domoticz.Debug('API REQUEST - Data: ' + f"{data}") 82 | if data is not None: 83 | data = json.dumps(data) 84 | data = data.encode() 85 | request = Request(url=url, data=data, method=method) 86 | if headers is not None: 87 | request.headers.update(headers) 88 | api_response = urlopen(request, timeout=API_TMOUT, 89 | context=self.secure).read() 90 | Domoticz.Debug('<- API Response: ' + f"{api_response}") 91 | dict_response = json.loads(api_response.decode()) 92 | return dict_response 93 | 94 | def register(self, app_id, app_name, version, device_name, wait=REGISTER_TMOUT): 95 | """ 96 | register method is used to obtain a "app_token" (ak. grant access to Freebox) 97 | 98 | You must gain access to Freebox API before being able to use the api. 99 | 100 | This is the first step, the app will ask for an app_token using the following call. 101 | A message will be displayed on the Freebox LCD asking the user to grant/deny access 102 | to the requesting app. 103 | Once the app has obtained a valid app_token, it will not have to do this procedure again 104 | unless the user revokes the app_token. 105 | 106 | Args: 107 | app_id (str): A unique application identifier string 108 | app_name (str): A descriptive application name (will be displayed on lcd) 109 | version (str): app version 110 | device_name (str): The name of the device on which the app will be used 111 | wait (int, optional): seconds before timeout. Defaults to REGISTER_TMOUT. 112 | 113 | Returns: 114 | str: "app_token" if success else empty string 115 | """ 116 | data = { 117 | 'app_id': app_id, 118 | 'app_name': app_name, 119 | 'app_version': version, 120 | 'device_name': device_name 121 | } 122 | response = self._request('login/authorize/', 'POST', None, data) 123 | status = 'pending' 124 | if not response['success'] and response['msg']: 125 | Domoticz.Error(f"Registration error: {response['msg']}") 126 | else : 127 | track_id, app_token = response['result']['track_id'], response['result']['app_token'] 128 | while status != 'granted' and wait != 0: 129 | status = self._request(f"login/authorize/{track_id}") 130 | status = status['result']['status'] 131 | wait = wait - 1 132 | time.sleep(1) 133 | if status == 'granted': 134 | return app_token 135 | return "" 136 | 137 | def _mksession(self, app_id, app_token): 138 | """ 139 | Create a new session (to make an authenticated call to the API) 140 | 141 | To protect the "app_token" secret, it will never be used directly to authenticate 142 | the application, instead the API will provide a challenge the app will combine to 143 | its "app_token" to open a session and get a "session_token" 144 | 145 | The app will then have to include the session_token in the HTTP headers of the 146 | following requests 147 | 148 | Args: 149 | app_id (str): A unique application identifier string 150 | app_token (str): Secret application token 151 | 152 | Returns: 153 | str: "session_token" 154 | """ 155 | challenge = self._request('login/')['result']['challenge'] 156 | Domoticz.Debug("Challenge: " + challenge) 157 | data = { 158 | "app_id": app_id, 159 | "password": hmac.new(app_token.encode(), challenge.encode(), hashlib.sha1).hexdigest() 160 | } 161 | session_token = self._request( 162 | 'login/session/', 'POST', None, data)['result']['session_token'] 163 | Domoticz.Debug("Session Token: " + session_token) 164 | return session_token 165 | 166 | def _disconnect(self, session_token): 167 | """ 168 | Closing the current session 169 | 170 | Returns: 171 | (dict of str: str): Freebox API Response as dictionary 172 | """ 173 | result = self._request( 174 | 'login/logout/', 175 | 'POST', 176 | {'Content-Type': 'application/json', 'X-Fbx-App-Auth': session_token}) 177 | Domoticz.Debug("Disconnect" + result) 178 | return result 179 | 180 | 181 | class FbxApp(FbxCnx): 182 | """ 183 | FbxApp describe methodes to call specified Freebox API 184 | 185 | Args: 186 | FbxCnx (FbxCnx): Freebox connection 187 | """ 188 | tv_player = None 189 | 190 | def __init__(self, app_id, app_token, host=HOST, session_token=None): 191 | FbxCnx.__init__(self, host) 192 | self.app_id, self.app_token = app_id, app_token 193 | self.session_token = self._mksession( 194 | app_id, app_token) if session_token is None else session_token 195 | self.system = self.create_system() 196 | self.players = None # Server may be connected to a Freebox TV Player 197 | if self.tv_player is None: 198 | self.create_players() 199 | 200 | def __del__(self): 201 | self._disconnect(self.session_token) 202 | 203 | def post(self, path, data=None): 204 | """ 205 | HTTP POST Request to API 206 | 207 | Args: 208 | path (str): api_url 209 | data (dict of str: str, optional): _description_. Defaults to None. 210 | 211 | Returns: 212 | (dict of str: str): Freebox API Response as dictionary 213 | """ 214 | return self._request(path, 'POST', {"X-Fbx-App-Auth": self.session_token}, data) 215 | 216 | def put(self, path, data=None): 217 | """ 218 | HTTP PUT Request to API 219 | 220 | Args: 221 | path (str): api_url 222 | data (dict of str: str, optional): _description_. Defaults to None. 223 | 224 | Returns: 225 | (dict of str: str): Freebox API Response as dictionary 226 | """ 227 | return self._request(path, 'PUT', {"X-Fbx-App-Auth": self.session_token}, data) 228 | 229 | def get(self, path): 230 | """ 231 | HTTP GET Request to API 232 | 233 | Args: 234 | path (str): api_url 235 | 236 | Returns: 237 | (dict of str: str): Freebox API Response as dictionary 238 | """ 239 | return self._request(path, 'GET', {"X-Fbx-App-Auth": self.session_token}) 240 | 241 | def call(self, path): 242 | """ 243 | Call Freebox API 244 | 245 | Args: 246 | path (str): api_url 247 | 248 | Returns: 249 | (dict of str: str): Freebox API Response as dictionary 250 | """ 251 | result = {} 252 | try: 253 | api_result = self.get(path) 254 | if api_result['success'] and 'result' in api_result: 255 | result = api_result['result'] 256 | except (urllib.error.HTTPError, urllib.error.URLError) as error: 257 | Domoticz.Error('API Error ("' + path + '"): ' + error.msg) 258 | except timeout: 259 | Domoticz.Error('Timeout when call ("' + path + '")') 260 | return result 261 | 262 | def percent(self, value, total, around=2): 263 | """ 264 | Formula of percentage : value / total * 100 265 | 266 | Args: 267 | value (int): numerator 268 | total (int): denominator 269 | around (int, optional): decimal precision. Defaults to 2. 270 | 271 | Returns: 272 | float: calculated percent 273 | """ 274 | percent = 0 275 | if (total > 0) : 276 | percent = value / total * 100 277 | return round(percent, around) 278 | 279 | def ls_devices(self): 280 | """ 281 | List of devices connected to the Freebox server 282 | 283 | Returns: 284 | (dict of str: str): {device: values} 285 | """ 286 | return self.call('lan/browser/pub/') 287 | 288 | def ls_storage(self): 289 | """ 290 | List storages attached to the Freebox server 291 | 292 | Returns: 293 | (dict of str: str): {disk_label: % of usage} 294 | """ 295 | result = {} 296 | ls_disk = self.call('storage/disk/') 297 | for disk in ls_disk: 298 | if not 'partitions' in disk: # /!\ If disk don't have any partition 299 | continue 300 | for partition in disk['partitions']: 301 | label = partition['label'] 302 | used = partition['used_bytes'] 303 | total = partition['total_bytes'] 304 | Domoticz.Debug('Usage of disk "' + label + '": ' + 305 | str(used) + '/' + str(total) + ' bytes') 306 | 307 | result.update({str(label): str(self.percent(used, total))}) 308 | return result 309 | 310 | def get_name_from_macaddress(self, p_macaddress): 311 | """ 312 | Find device name by his mac-address 313 | 314 | Args: 315 | p_macaddress (str): @mac type 01:02:03:04:05:06 316 | 317 | Returns: 318 | str: device name if @mac is know or None 319 | """ 320 | result = None 321 | ls_devices = self.ls_devices() 322 | for device in ls_devices: 323 | macaddress = device['id'] 324 | if ("ETHER-" + p_macaddress.upper()) == macaddress.upper(): 325 | result = device['primary_name'] 326 | return result 327 | 328 | def reachable_macaddress(self, p_macaddress): 329 | """ 330 | Check if device is reachable by his mac-address 331 | 332 | Args: 333 | p_macaddress (str): @mac type 01:02:03:04:05:06 334 | 335 | Returns: 336 | bool: True if reachable else False 337 | """ 338 | result = False 339 | ls_devices = self.ls_devices() 340 | for device in ls_devices: 341 | macaddress = device['id'] 342 | if ("ETHER-" + p_macaddress.upper()) == macaddress.upper(): 343 | reachable = device['reachable'] 344 | if reachable: 345 | result = True 346 | break 347 | return result 348 | 349 | def online_devices(self): 350 | """ 351 | Get online devices 352 | 353 | Returns: 354 | (dict of str: str): {macaddress: name} 355 | """ 356 | result = {} 357 | ls_devices = self.ls_devices() 358 | for device in ls_devices: 359 | name = device['primary_name'] 360 | reachable = device['reachable'] 361 | macaddress = device['id'] 362 | if reachable: 363 | result.update({macaddress: name}) 364 | return result 365 | 366 | def alarminfo(self): # Only on Freebox Delta 367 | """ 368 | _summary_ 369 | 370 | Returns: 371 | _type_: _description_ 372 | """ 373 | result = {} 374 | prerequisite_pattern = '^fbxgw7-r[0-9]+/full$' 375 | if re.match(prerequisite_pattern, self.info['box_model']) is None: 376 | return result # Return an empty list if model of Freebox isn't compatible with alarm 377 | nodes = self.call('home/tileset/all') 378 | for node in nodes: 379 | device = {} 380 | label = '' 381 | if node["type"] == "alarm_control": 382 | device.update({"type": str(node["type"])}) 383 | for data in node["data"]: 384 | if (data["ep_id"] == 11) and node["type"] == "alarm_control": 385 | label = data["label"] 386 | if data['value'] == 'alarm1_armed': 387 | value = 1 388 | device.update( 389 | {"alarm1_status": str(value)}) 390 | elif data['value'] == 'alarm1_arming': 391 | value = -1 392 | device.update( 393 | {"alarm1_status": str(value)}) 394 | else: 395 | value = 0 396 | device.update( 397 | {"alarm1_status": str(value)}) 398 | if data['value'] == 'alarm2_armed': 399 | value = 1 400 | device.update( 401 | {"alarm2_status": str(value)}) 402 | elif data['value'] == 'alarm2_arming': 403 | value = -1 404 | device.update( 405 | {"alarm2_status": str(value)}) 406 | else: 407 | value = 0 408 | device.update( 409 | {"alarm2_status": str(value)}) 410 | device.update({"label": str(label)}) 411 | elif (data["ep_id"] == 13) and node["type"] == "alarm_control": # error 412 | status_error = data["value"] 413 | device.update( 414 | {"status_error": str(status_error)}) 415 | elif data["name"] == 'battery_warning': 416 | battery = data["value"] 417 | device.update({"battery": str(battery)}) 418 | device1 = device.copy() 419 | device2 = device.copy() 420 | if 'alarm1_status' in device1: 421 | device1['value'] = device1['alarm1_status'] 422 | device1['label'] = device1['label']+'1' 423 | if 'alarm2_status' in device2: 424 | device2['value'] = device2['alarm2_status'] 425 | device2['label'] = device2['label']+'2' 426 | result.update({device1['label']: device1}) 427 | result.update({device2['label']: device2}) 428 | 429 | nodes = self.call("home/nodes") 430 | for node in nodes: 431 | device = {} 432 | label = '' 433 | if ((node["category"] == "pir") or (node["category"] == "dws")): 434 | label = node["label"] 435 | device.update({"label": str(label)}) 436 | device.update({"type": str(node["category"])}) 437 | for endpoint in node["show_endpoints"]: 438 | if endpoint["name"] == 'battery': 439 | battery = endpoint["value"] 440 | device.update({"battery": str(battery)}) 441 | elif endpoint["name"] == 'trigger': 442 | if endpoint["value"]: 443 | device.update({"value": 0}) 444 | elif not endpoint["value"]: 445 | device.update({"value": 1}) 446 | result.update({label: device}) 447 | return result 448 | 449 | def connection_rate(self): 450 | """ 451 | Get upload and download speed rate (of WAN Interface) 452 | 453 | Returns: 454 | (dict of str: str): {rate_down: rate, rate_up: rate} (ko/s) 455 | """ 456 | result = {} 457 | connection = self.call('connection/') 458 | if connection['rate_down']: 459 | result.update({str('rate_down'): str(connection['rate_down']/1024)}) 460 | if connection['rate_up']: 461 | result.update({str('rate_up'): str(connection['rate_up']/1024)}) 462 | return result 463 | 464 | def wan_state(self): 465 | """ 466 | Is WAN link UP or DOWN 467 | 468 | Returns: 469 | bool: True if "UP" else False 470 | """ 471 | state = None 472 | connection = self.call('connection/') 473 | if connection['state'] == 'up': 474 | Domoticz.Debug('Connection is UP') 475 | state = True 476 | else: 477 | Domoticz.Debug('Connection is DOWN') 478 | state = False 479 | return state 480 | 481 | def wifi_state(self): 482 | """ 483 | Is WLAN state UP or DOWN 484 | 485 | Returns: 486 | bool: True if "UP" else False 487 | """ 488 | enabled = None 489 | wifi = self.call('wifi/config/') 490 | if wifi['enabled']: 491 | Domoticz.Debug('Wifi interface is UP') 492 | enabled = True 493 | else: 494 | Domoticz.Debug('Wifi interface is DOWN') 495 | enabled = False 496 | return enabled 497 | 498 | def wifi_enable(self, switch_on): 499 | """ 500 | Switch wifi ON or OFF 501 | 502 | Args: 503 | switch_on (bool): True to switch ON or False du switch OFF 504 | 505 | Raises: 506 | timeout: Catch error if you are disconnected due to wifi switch OFF 507 | 508 | Returns: 509 | bool: wifi state 510 | """ 511 | status = None 512 | if switch_on: 513 | data = {'enabled': True} 514 | else: 515 | data = {'enabled': False} 516 | try: 517 | response = self.put("wifi/config/", data) 518 | status = False 519 | if response['success']: 520 | if response['result']['enabled']: 521 | status = True 522 | Domoticz.Debug('Wifi is now ON') 523 | else: 524 | Domoticz.Debug('Wifi is now OFF') 525 | except (urllib.error.HTTPError, urllib.error.URLError) as error: 526 | Domoticz.Error('API Error ("wifi/config/"): ' + error.msg) 527 | except timeout as exc: 528 | if not switch_on: 529 | # If we are connected using wifi, disabling wifi will close connection 530 | # thus PUT response will never be received: a timeout is expected 531 | Domoticz.Error('Wifi disabled') 532 | status = False 533 | else: 534 | # Forward timeout exception as should not occur 535 | raise timeout from exc 536 | return status 537 | 538 | def reboot(self): 539 | """ 540 | Reboot the Freebox server 541 | """ 542 | Domoticz.Debug('Try to reboot with session : ' + self.session_token) 543 | response = self.post("system/reboot") 544 | if response['success']: 545 | Domoticz.Debug('Reboot initiated') 546 | else: 547 | Domoticz.Error('Error: You must grant reboot permission') 548 | 549 | def next_pvr_precord_timestamp(self, relative=True): 550 | """ 551 | Next schedule / programmed PVR record 552 | 553 | Args: 554 | relative (bool, optional): if True time result is relative else absolute. Defaults to True. 555 | 556 | Returns: 557 | int: start_timestamp 558 | """ 559 | precord = False 560 | now = int(time.time()) 561 | next_recording = now -1 # -1 if none programmed PVR record 562 | result = self.call('/pvr/programmed') 563 | Domoticz.Debug('PVR Programmed List: ' + f"{result}") 564 | for pvr in result: 565 | if pvr['state'] == 'waiting_start_time': 566 | recording_start = int(float(pvr['start'])) 567 | if not precord: 568 | next_recording = recording_start 569 | precord = True 570 | next_recording = recording_start if recording_start < next_recording else next_recording 571 | elif pvr['state'] == 'starting' or pvr['state'] == 'running' or pvr['state'] == 'running_error': 572 | next_recording = now 573 | break 574 | return (next_recording - now) if relative else next_recording 575 | 576 | def create_system(self): 577 | """ 578 | Create system information 579 | 580 | Returns: 581 | System: Freebox System Object 582 | """ 583 | self.system = FbxApp.System(self) 584 | return self.system 585 | 586 | def create_players(self): 587 | """ 588 | Create sub-objet TV Players 589 | 590 | Returns: 591 | Players: Freebox TV Players Object 592 | """ 593 | self.players = FbxApp.Players(self) 594 | return self.players 595 | 596 | class System: 597 | """ 598 | Class and method about System (°temp_sensor,...) 599 | """ 600 | def __init__(self, fbxapp): 601 | self.server = fbxapp # to access Outer's class instance "FbxApp" from System Objet 602 | self.info = self.getinfo() 603 | 604 | def getinfo(self): 605 | result = self.server.call('/system') 606 | Domoticz.Debug('Freebox Server Infos: ' + f"{result}") 607 | return result 608 | 609 | def sensors(self): 610 | result = {} 611 | if self.info["sensors"]: 612 | result = self.info["sensors"] 613 | return result 614 | 615 | 616 | class Players: 617 | """ 618 | Class and method for Freebox TV Player 619 | """ 620 | 621 | def __init__(self, fbxapp): 622 | self.server = fbxapp # to access Outer's class instance "FbxApp" from Players 623 | self.info = self.getinfo() 624 | 625 | def getinfo(self): 626 | result = self.server.call('/player') 627 | if result: 628 | self.server.tv_player = True 629 | Domoticz.Debug('Player(s) are registered on the local network') 630 | Domoticz.Debug('Player(s) Infos: ' + f"{result}") 631 | else: 632 | self.server.tv_player = False 633 | Domoticz.Error('Error: You must grant Player Control permission') 634 | return result 635 | 636 | def ls_uid(self): 637 | result = [] 638 | players = self.info 639 | for player in players: 640 | result.append(player['id']) 641 | Domoticz.Debug('Player(s) Id: ' + f"{result}") 642 | return result 643 | 644 | def state(self, uid): 645 | status = None 646 | try: 647 | response = self.server.get( 648 | f"/player/{uid}/api/v{TV_API_VER}/status") 649 | except (urllib.error.HTTPError, urllib.error.URLError) as error: 650 | # If player is shutdown : Error="Gateway Time-out" 651 | if error.code == 504: # error.msg != 'Gateway Time-out' 652 | status = False 653 | else: 654 | Domoticz.Error( 655 | 'API Error ("/player/' + uid + '/api/v' + TV_API_VER + '/status"): ' + error.msg) 656 | except timeout: 657 | Domoticz.Error('Timeout') 658 | else: 659 | if response['success'] and response['result']['power_state']: 660 | status = True if response['result']['power_state'] == 'running' else False 661 | Domoticz.Debug(f"Is watching TV{uid}? : {status}") 662 | return status 663 | 664 | def remote(self, uid, remote_code, key, long=False): 665 | url = f"http://hd{uid}.freebox.fr/pub/remote_control?code={remote_code}&key={key}" 666 | url = url + '&long=true' if long else url 667 | try: 668 | request = Request(url) 669 | response = urlopen(request, timeout=API_TMOUT).read() 670 | except (urllib.error.HTTPError, urllib.error.URLError) as error: 671 | Domoticz.Error('TV Remote error ("' + url + '"): ' + error.msg) 672 | except timeout: 673 | Domoticz.Error('Timeout') # None if Error occurred 674 | return response 675 | 676 | def shutdown(self, uid, remote_code): 677 | ## To Do http://hd{uid}.freebox.fr/pub/remote_control?code={remote_code}&key=power 678 | return self.remote(uid, remote_code, "power") 679 | -------------------------------------------------------------------------------- /freebox_certificates.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICWTCCAd+gAwIBAgIJAMaRcLnIgyukMAoGCCqGSM49BAMCMGExCzAJBgNVBAYT 3 | AkZSMQ8wDQYDVQQIDAZGcmFuY2UxDjAMBgNVBAcMBVBhcmlzMRMwEQYDVQQKDApG 4 | cmVlYm94IFNBMRwwGgYDVQQDDBNGcmVlYm94IEVDQyBSb290IENBMB4XDTE1MDkw 5 | MTE4MDIwN1oXDTM1MDgyNzE4MDIwN1owYTELMAkGA1UEBhMCRlIxDzANBgNVBAgM 6 | BkZyYW5jZTEOMAwGA1UEBwwFUGFyaXMxEzARBgNVBAoMCkZyZWVib3ggU0ExHDAa 7 | BgNVBAMME0ZyZWVib3ggRUNDIFJvb3QgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNi 8 | AASCjD6ZKn5ko6cU5Vxh8GA1KqRi6p2GQzndxHtuUmwY8RvBbhZ0GIL7bQ4f08ae 9 | JOv0ycWjEW0fyOnAw6AYdsN6y1eNvH2DVfoXQyGoCSvXQNAUxla+sJuLGICRYiZz 10 | mnijYzBhMB0GA1UdDgQWBBTIB3c2GlbV6EIh2ErEMJvFxMz/QTAfBgNVHSMEGDAW 11 | gBTIB3c2GlbV6EIh2ErEMJvFxMz/QTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB 12 | /wQEAwIBhjAKBggqhkjOPQQDAgNoADBlAjA8tzEMRVX8vrFuOGDhvZr7OSJjbBr8 13 | gl2I70LeVNGEXZsAThUkqj5Rg9bV8xw3aSMCMQCDjB5CgsLH8EdZmiksdBRRKM2r 14 | vxo6c0dSSNrr7dDN+m2/dRvgoIpGL2GauOGqDFY= 15 | -----END CERTIFICATE----- 16 | -----BEGIN CERTIFICATE----- 17 | MIIFmjCCA4KgAwIBAgIJAKLyz15lYOrYMA0GCSqGSIb3DQEBCwUAMFoxCzAJBgNV 18 | BAYTAkZSMQ8wDQYDVQQIDAZGcmFuY2UxDjAMBgNVBAcMBVBhcmlzMRAwDgYDVQQK 19 | DAdGcmVlYm94MRgwFgYDVQQDDA9GcmVlYm94IFJvb3QgQ0EwHhcNMTUwNzMwMTUw 20 | OTIwWhcNMzUwNzI1MTUwOTIwWjBaMQswCQYDVQQGEwJGUjEPMA0GA1UECAwGRnJh 21 | bmNlMQ4wDAYDVQQHDAVQYXJpczEQMA4GA1UECgwHRnJlZWJveDEYMBYGA1UEAwwP 22 | RnJlZWJveCBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA 23 | xqYIvq8538SH6BJ99jDlOPoyDBrlwKEp879oYplicTC2/p0X66R/ft0en1uSQadC 24 | sL/JTyfgyJAgI1Dq2Y5EYVT/7G6GBtVH6Bxa713mM+I/v0JlTGFalgMqamMuIRDQ 25 | tdyvqEIs8DcfGB/1l2A8UhKOFbHQsMcigxOe9ZodMhtVNn0mUyG+9Zgu1e/YMhsS 26 | iG4Kqap6TGtk80yruS1mMWVSgLOq9F5BGD4rlNlWLo0C3R10mFCpqvsFU+g4kYoA 27 | dTxaIpi1pgng3CGLE0FXgwstJz8RBaZObYEslEYKDzmer5zrU1pVHiwkjsgwbnuy 28 | WtM1Xry3Jxc7N/i1rxFmN/4l/Tcb1F7x4yVZmrzbQVptKSmyTEvPvpzqzdxVWuYi 29 | qIFSe/njl8dX9v5hjbMo4CeLuXIRE4nSq2A7GBm4j9Zb6/l2WIBpnCKtwUVlroKw 30 | NBgB6zHg5WI9nWGuy3ozpP4zyxqXhaTgrQcDDIG/SQS1GOXKGdkCcSa+VkJ0jTf5 31 | od7PxBn9/TuN0yYdgQK3YDjD9F9+CLp8QZK1bnPdVGywPfL1iztngF9J6JohTyL/ 32 | VMvpWfS/X6R4Y3p8/eSio4BNuPvm9r0xp6IMpW92V8SYL0N6TQQxzZYgkLV7TbQI 33 | Hw6v64yMbbF0YS9VjS0sFpZcFERVQiodRu7nYNC1jy8CAwEAAaNjMGEwHQYDVR0O 34 | BBYEFD2erMkECujilR0BuER09FdsYIebMB8GA1UdIwQYMBaAFD2erMkECujilR0B 35 | uER09FdsYIebMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqG 36 | SIb3DQEBCwUAA4ICAQAZ2Nx8mWIWckNY8X2t/ymmCbcKxGw8Hn3BfTDcUWQ7GLRf 37 | MGzTqxGSLBQ5tENaclbtTpNrqPv2k6LY0VjfrKoTSS8JfXkm6+FUtyXpsGK8MrLL 38 | hZ/YdADTfbbWOjjD0VaPUoglvo2N4n7rOuRxVYIij11fL/wl3OUZ7GHLgL3qXSz0 39 | +RGW+1oZo8HQ7pb6RwLfv42Gf+2gyNBckM7VVh9R19UkLCsHFqhFBbUmqwJgNA2/ 40 | 3twgV6Y26qlyHXXODUfV3arLCwFoNB+IIrde1E/JoOry9oKvF8DZTo/Qm6o2KsdZ 41 | dxs/YcIUsCvKX8WCKtH6la/kFCUcXIb8f1u+Y4pjj3PBmKI/1+Rs9GqB0kt1otyx 42 | Q6bqxqBSgsrkuhCfRxwjbfBgmXjIZ/a4muY5uMI0gbl9zbMFEJHDojhH6TUB5qd0 43 | JJlI61gldaT5Ci1aLbvVcJtdeGhElf7pOE9JrXINpP3NOJJaUSueAvxyj/WWoo0v 44 | 4KO7njox8F6jCHALNDLdTsX0FTGmUZ/s/QfJry3VNwyjCyWDy1ra4KWoqt6U7SzM 45 | d5jENIZChM8TnDXJzqc+mu00cI3icn9bV9flYCXLTIsprB21wVSMh0XeBGylKxeB 46 | S27oDfFq04XSox7JM9HdTt2hLK96x1T7FpFrBTnALzb7vHv9MhXqAT90fPR/8A== 47 | -----END CERTIFICATE----- 48 | -------------------------------------------------------------------------------- /plugin.py: -------------------------------------------------------------------------------- 1 | # Freebox Python Plugin 2 | # 3 | # Author: supermat & ilionel : https://github.com/ilionel/PluginDomoticzFreebox/ 4 | # Credit: https://matdomotique.wordpress.com/2018/03/25/plugin-freebox-pour-domoticz/ 5 | # 6 | """ 7 | 8 | 9 |
10 |

Initialisation


11 |

Au premier démarrage :


12 |
   1.  Laisser le champ "token" vide
13 |
   2.  Accepter la demande directement depuis l'écran de la Freebox (vous aurez 30 secondes)
14 |
   3a. Copier la clé "Token" affichée dans les logs de Domoticz ("Menu Configuration" > "Log" )
15 |
   3b. Coller la clé dans le champ "Token" de la configuration ci-dessous
16 |
   4.  Enregistrer les modifications puis redémarrer le serveur Domoticz
17 |
   5.  Ajouter les nouveaux dispositifs depuis le menu adéquat de Domoticz

18 |
Remarque : Les codes de télécommandes sont visibles depuis le Player TV : "Menu Système" > "Informations" > "Player"

19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 37 | 38 | 39 | 40 | 43 | 44 | 45 |
46 | """ 47 | 48 | import freebox 49 | import json 50 | import os 51 | import datetime 52 | import time 53 | import traceback 54 | # Only enable when debug mode add "from data import" 55 | from enum import Enum 56 | import Domoticz 57 | 58 | SCHEME = 'https://' 59 | HOST = 'mafreebox.freebox.fr' 60 | PORT = '443' 61 | JSON_FILE = 'devicemapping.json' 62 | 63 | POWER_STATE = ("OFF", "ON") 64 | LINK_STATE = ("DOWN", "UP") 65 | PRESENCE_STATE = ("hors ligne", "en ligne") 66 | RATE_TYPE = {"rate_down": "download", "rate_up": "upload"} 67 | SWITCH_CMD = {"Off": 0, "On": 1} 68 | 69 | class FreeboxPlugin: 70 | """ 71 | Domoticz plugin for Freebox. Freebox is an appliance provided by Free ISP (Iliad group) 72 | """ 73 | class Device(Enum): 74 | """ 75 | List of constants type for devices 76 | """ 77 | DISK = 'Disk' 78 | CONNECTION_RATE = 'ConnectionRate' 79 | SYSTEM_SENSOR = 'SystemTemp' 80 | PRESENCE = 'Presence' 81 | COMMAND = 'Commande' 82 | ALARM = 'Alarme' 83 | PLAYER = 'TVPlayer' 84 | PRECORD = 'ProgrammedRecord' 85 | 86 | enabled = False 87 | token = "" 88 | freebox_url = SCHEME + HOST + ":" + PORT 89 | remote_code_tv1 = "" 90 | remote_code_tv2 = "" 91 | 92 | _refresh_interval = 60 93 | _tick = 0 94 | 95 | def __init__(self): 96 | return 97 | 98 | def get_all_devices_dict(self): 99 | """ 100 | List all Domoticz devices created by the Freebox plugin 101 | 102 | Returns: 103 | (dict of str: str): {Type:{List},...} 104 | """ 105 | dictionary = {} 106 | if os.path.isfile(Parameters["HomeFolder"] + JSON_FILE): 107 | with open(Parameters["HomeFolder"] + JSON_FILE, encoding='utf-8') as data_file: 108 | dictionary = json.load(data_file) 109 | return dictionary 110 | 111 | def save_all_devices_dict(self, dictionary): 112 | """ 113 | Save the list of Domoticz devices (create by Freebox plugin) to file 114 | 115 | Args: 116 | dictionary (dict of str: str): List of all devices as dictionary 117 | """ 118 | with open(Parameters["HomeFolder"] + JSON_FILE, 'w', encoding='utf-8') as data_file: 119 | json.dump(dictionary, data_file) 120 | 121 | def get_first_unused_unit_id(self, dictionary): 122 | """ 123 | Position of first available ID of dictionary 124 | 125 | Args: 126 | dictionary (dict of str: str): List of all devices as dictionary 127 | 128 | Returns: 129 | int: Unused position 130 | """ 131 | position = 0 132 | for device in dictionary: 133 | position = position + len(dictionary[device]) 134 | return position + 1 135 | 136 | def return_unit_id(self, device, name): 137 | """ 138 | Find device ID number from is type and name 139 | 140 | Args: 141 | device (str): device type as Device(Enum) 142 | name (str): device name 143 | 144 | Returns: 145 | str: unit id (integer as string) 146 | """ 147 | dict_devices = self.get_all_devices_dict() 148 | dict_types = {} 149 | if device.value in dict_devices: 150 | dict_types = dict_devices[device.value] 151 | else: 152 | dict_devices.update({device.value: dict_types}) 153 | if name in dict_types: 154 | return dict_types[name] 155 | else: 156 | uid = self.get_first_unused_unit_id(dict_devices) 157 | dict_types.update({name: uid}) 158 | self.save_all_devices_dict(dict_devices) 159 | return uid 160 | 161 | def return_properties_from_id(self, uid): 162 | """ 163 | Find from device ID number the type and name of device 164 | 165 | Args: 166 | uid (int): device id 167 | 168 | Returns: 169 | tuple: (type,name) 170 | """ 171 | dict_devices = self.get_all_devices_dict() 172 | 173 | for device in dict_devices: 174 | for name, value in dict_devices[device].items(): 175 | if value == uid: 176 | return (device, name) 177 | 178 | def return_device_from_properties(self, properties): 179 | """ 180 | Return device type from properties 181 | 182 | Args: 183 | tuple (type,name): properties 184 | 185 | Returns: 186 | str: Device.value 187 | """ 188 | if len(properties) == 2 : 189 | return properties[0] 190 | 191 | def return_name_from_properties(self, properties): 192 | """ 193 | Return name from properties 194 | 195 | Args: 196 | tuple (type,name): properties 197 | 198 | Returns: 199 | str: name 200 | """ 201 | if len(properties) == 2 : 202 | return properties[1] 203 | 204 | def unit_exist(self, device, name): 205 | """ 206 | Find device ID number from is type and name 207 | 208 | Args: 209 | device (str): device type as Device(Enum) 210 | name (str): device name 211 | 212 | Returns: 213 | bool: True if device exist else False 214 | """ 215 | dict_devices = self.get_all_devices_dict() 216 | if device.value in dict_devices: 217 | dict_types = dict_devices[device.value] 218 | if name in dict_types: 219 | return True 220 | return False 221 | 222 | def _update_device(self, unit_id, category, name, action='update', n_value=None, s_value=None, battery_level=None): 223 | log = "Le dipositif de type " + category.value + " associé à " + name 224 | if action == 'update': 225 | log = log + " a été mis à jour " + str(n_value) + "/" + str(s_value) 226 | if battery_level is not None: 227 | log = log + \ 228 | " - battery level: " + \ 229 | str(Devices[unit_id].BatteryLevel) + "/" + str(battery_level) 230 | Devices[unit_id].Update(nValue=n_value, sValue=s_value, BatteryLevel=battery_level) 231 | else: 232 | Devices[unit_id].Update(nValue=n_value, sValue=s_value) 233 | elif action == 'up-to-date': 234 | log = log + " est déjà à jour." 235 | elif action == 'delete': 236 | log = log + " a été supprimé dans Domoticz." 237 | elif action == 'unknown': 238 | log = log + " n'a pas été créé dans Domoticz. Veuillez désactiver et réactiver le plugin, en autorisant l'ajout de nouveaux dispositifs." 239 | Domoticz.Debug(log) 240 | 241 | def update_device(self, device, name, n_value, s_value, battery_level=None): 242 | """ 243 | Update Domoticz value of a Freebox device 244 | 245 | Args: 246 | device (str): device type as Device(Enum) 247 | name (str): device name 248 | n_value (int): Domoticz numeric value 249 | s_value (str): Domoticz string value 250 | battery_level (int, optional: 251 | l): Domoticz battery level. Defaults to None. 252 | """ 253 | unit_id = None 254 | if not self.unit_exist(device, name): 255 | self._update_device(unit_id, device, name, 'unknown') 256 | # Device not exist 257 | return 258 | unit_id = self.return_unit_id(device, name) 259 | if not unit_id in Devices: 260 | self._update_device(unit_id, device, name, 'delete') 261 | # Device as been deleted 262 | return 263 | if ((device.value == self.Device.ALARM.value) and ( 264 | (Devices[unit_id].sValue != s_value) or ( 265 | Devices[unit_id].BatteryLevel != battery_level) 266 | ) 267 | ): 268 | self._update_device(unit_id, device, name, 'update', n_value, s_value, battery_level) 269 | 270 | # Test if PRESENCE are already up-to-date 271 | elif device.value != self.Device.PRESENCE.value \ 272 | or ( 273 | device.value == self.Device.PRESENCE.value 274 | and 275 | Devices[unit_id].sValue != s_value 276 | ): 277 | self._update_device(unit_id, device, name, 'update', n_value, s_value) 278 | else: 279 | self._update_device(unit_id, device, name, 'up-to-date') 280 | 281 | def init(self): 282 | """ 283 | Initialize the connection with Freebox server. 284 | 285 | Returns: 286 | bool: True is success else False 287 | """ 288 | url = Parameters["Address"] 289 | port = Parameters["Port"] 290 | token = Parameters["Mode1"] # Parameters["Mode1"] == Token field 291 | debug = Parameters["Mode6"] # Parameters["Mode6"] == Debug field [Debug|Normal] 292 | self.remote_code_tv1 = Parameters["Mode3"] # Parameters["Mode3"] == TV Player 1 remote code field 293 | self.remote_code_tv2 = Parameters["Mode4"] # Parameters["Mode4"] == TV Player 2 remote code field 294 | self._refresh_interval = int(float(Parameters["Mode5"])) # Parameters["Mode5"] == Refresh interval field 295 | 296 | if url != "": 297 | if port != "": 298 | url = url + ":" + port 299 | self.freebox_url = url 300 | else: 301 | self.freebox_url = SCHEME + HOST + ":" + PORT 302 | 303 | # If Debug checked 304 | if debug == "Debug": 305 | Domoticz.Debugging(1) 306 | DumpConfigToLog() 307 | 308 | # If Token field is empty 309 | if token == "": 310 | Domoticz.Log( 311 | "C'est votre première connexion, le token n'est pas renseigné.") 312 | Domoticz.Log( 313 | "Vous devez autoriser le plugin sur l'écran de la Freebox.") 314 | Domoticz.Log( 315 | "Une fois autorisé sur la Freebox, le token s'affichera ici.") 316 | token = freebox.FbxCnx(self.freebox_url).register( 317 | "idPluginDomoticz", "Plugin Freebox", "1", "Domoticz") 318 | # We got a Token thanks to freebox.FbxCnx 319 | if token != "": 320 | Domoticz.Log( 321 | "------------------------------------------------------------------------------") 322 | Domoticz.Log( 323 | "Veuillez copier ce token dans la configuration du plugin Reglages > Matériel") 324 | Domoticz.Log(token) 325 | Domoticz.Log( 326 | "------------------------------------------------------------------------------") 327 | else: 328 | Domoticz.Log( 329 | "Vous avez été trop long (ou avez refusé), veuillez désactiver et réactiver le matériel Reglages > Matériel.") 330 | else: 331 | self.token = token 332 | Domoticz.Log("Token déjà présent. OK.") 333 | return True if self.token else False 334 | 335 | def new_device(self, device, category, name): 336 | """ 337 | Add a new device in Domoticz list 338 | 339 | Args: 340 | device (device): Domoticz device 341 | category (str): name of device's category 342 | value (str): name of device 343 | """ 344 | device.Create() 345 | Domoticz.Log(f"Nouveau dispositif: '{category}' -> '{name}'") 346 | 347 | def _create_devices_reboot(self): 348 | # Create Reboot server switch 349 | unit_id = self.return_unit_id( 350 | self.Device.COMMAND, "REBOOT") 351 | if unit_id not in Devices: 352 | device = Domoticz.Device( 353 | Unit=unit_id, 354 | Name="Reboot Server", 355 | TypeName="Switch" 356 | ) 357 | self.new_device(device, self.Device.COMMAND.value, 'REBOOT') 358 | 359 | def _create_devices_storages(self, f): 360 | # Creation of disk devices 361 | disks = f.ls_storage() 362 | for disk, value in disks.items(): 363 | unit_id = self.return_unit_id(self.Device.DISK, disk) 364 | if unit_id not in Devices: 365 | device = Domoticz.Device( 366 | Unit=unit_id, 367 | Name="Occupation " + disk, 368 | TypeName="Percentage") 369 | self.new_device(device, self.Device.DISK.value, disk) 370 | # Unfortunately the image in the Percentage device can not be changed. Use Custom device! 371 | # Domoticz.Device(Unit=_UNIT_USAGE, Name=Parameters["Address"], TypeName="Custom", Options={"Custom": "1;%"}, Image=3, Used=1).Create() 372 | Domoticz.Log(f"L'espace disque de '{disk}' est occupé à {value}%") 373 | self.update_device(self.Device.DISK, disk, int(float(value)), str(value)) 374 | 375 | def _create_devices_rates(self, f): 376 | # Connection rates of WAN Freebox interface 377 | connection_rates = f.connection_rate() 378 | for rate, value in connection_rates.items(): 379 | unit_id = self.return_unit_id(self.Device.CONNECTION_RATE, rate) 380 | if unit_id not in Devices: 381 | device = Domoticz.Device( 382 | Unit=unit_id, 383 | Name="Débit " + RATE_TYPE[rate], 384 | TypeName="Custom", 385 | Options={"Custom": "1;ko/s"}, 386 | Used=1 387 | ) 388 | self.new_device(device, self.Device.CONNECTION_RATE.value, RATE_TYPE[rate]) 389 | Domoticz.Log(f"Le débit WAN en '{RATE_TYPE[rate]}' est de {value} ko/s") 390 | self.update_device(self.Device.CONNECTION_RATE, rate, int(float(value)), str(value)) 391 | 392 | def _create_devices_sensors(self, f): 393 | # Create °C temp devices 394 | sensors = f.system.sensors() 395 | for sensor in sensors: 396 | uid = str(sensor['id']) 397 | name = sensor['name'] 398 | value = int(sensor['value']) 399 | unit_id = self.return_unit_id(self.Device.SYSTEM_SENSOR, uid) 400 | if unit_id not in Devices: 401 | device = Domoticz.Device( 402 | Unit=unit_id, 403 | Name=name, 404 | TypeName="Temperature" 405 | ) 406 | self.new_device(device, self.Device.SYSTEM_SENSOR.value, name) 407 | Domoticz.Log(f"La sonde '{name}' affiche {value}°C") 408 | self.update_device(self.Device.SYSTEM_SENSOR, uid, value, str(value)) 409 | 410 | def _create_devices_alarm(self, f): 411 | # Create alarms devices 412 | alarminfo = f.alarminfo() 413 | for alarm_device in alarminfo: 414 | Domoticz.Debug("Label " + alarminfo[alarm_device]['label']) 415 | keyunit = self.return_unit_id( 416 | self.Device.ALARM, alarminfo[alarm_device]['label']) 417 | if (keyunit not in Devices): 418 | if (alarminfo[alarm_device]['type']) == 'alarm_control' or (alarminfo[alarm_device]['type']) == 'dws': 419 | device = Domoticz.Device( 420 | Unit=keyunit, Name=alarminfo[alarm_device]['label'], TypeName="Switch", Switchtype=0) 421 | self.new_device(device, self.Device.ALARM.value, alarminfo[alarm_device]['label']) 422 | elif (alarminfo[alarm_device]["type"]) == 'pir': 423 | device = Domoticz.Device( 424 | Unit=keyunit, Name=alarminfo[alarm_device]["label"], TypeName="Switch", Switchtype=8) 425 | self.new_device(device, self.Device.ALARM.value, alarminfo[alarm_device]['label']) 426 | 427 | def _create_devices_presence(self, f): 428 | # Create presence sensor 429 | str_ls_macaddr = Parameters["Mode2"] 430 | if str_ls_macaddr != "": 431 | ls_macaddr = str_ls_macaddr.split(";") 432 | for macaddress in ls_macaddr: 433 | name = f.get_name_from_macaddress(macaddress) 434 | if name is None: 435 | Domoticz.Log( 436 | f"L'adresse mac: '{macaddress}' est inconnue de la Freebox et sera ignorée" 437 | ) 438 | else: 439 | unit_id = self.return_unit_id(self.Device.PRESENCE, macaddress) 440 | if unit_id not in Devices: 441 | device = Domoticz.Device( 442 | Unit=unit_id, 443 | Name="Presence " + name, 444 | TypeName="Switch") 445 | self.new_device(device, self.Device.PRESENCE.value, name) 446 | presence = 1 if f.reachable_macaddress(macaddress) else 0 447 | Domoticz.Log( 448 | f"L'équipement '{name}' est actuellement {PRESENCE_STATE[presence]}" 449 | ) 450 | self.update_device(self.Device.PRESENCE, macaddress, presence, str(presence)) 451 | 452 | def _create_devices_wifi(self, f): 453 | # Create ON/OFF WIFI switch 454 | wifi_state = 1 if f.wifi_state() else 0 455 | unit_id = self.return_unit_id( 456 | self.Device.COMMAND, "WIFI") 457 | if unit_id not in Devices: 458 | device = Domoticz.Device( 459 | Unit=unit_id, 460 | Name="WIFI On/Off", 461 | TypeName="Switch" 462 | ) 463 | self.new_device(device, self.Device.COMMAND.value, 'WIFI') 464 | Domoticz.Log("Le WIFI est " + LINK_STATE[wifi_state]) 465 | self.update_device(self.Device.COMMAND, "WIFI", wifi_state, str(wifi_state)) 466 | 467 | def _create_devices_wan(self, f): 468 | # Create WAN status item 469 | wan_state = 1 if f.wan_state() else 0 470 | unit_id = self.return_unit_id( 471 | self.Device.COMMAND, "WANStatus") 472 | if unit_id not in Devices: 473 | device = Domoticz.Device( 474 | Unit=unit_id, 475 | Name="WAN Status", 476 | TypeName="Switch" 477 | ) 478 | self.new_device(device, self.Device.COMMAND.value, 'WANStatus') 479 | Domoticz.Log("La connexion Internet est " + LINK_STATE[wan_state]) 480 | self.update_device(self.Device.COMMAND, "WANStatus", wan_state, str(wan_state)) 481 | 482 | def _create_devices_players(self, f): 483 | # Create FreeboxPlayer 484 | players = f.players.info 485 | for player in players: 486 | uid = str(player['id']) 487 | player_state = 1 if f.players.state(uid) else 0 488 | name = player['device_name'] + ' ' + uid 489 | unit_name = player['device_model'] + '_' + uid 490 | unit_id = self.return_unit_id( 491 | self.Device.PLAYER, unit_name) 492 | if unit_id not in Devices: 493 | device = Domoticz.Device( 494 | Unit=unit_id, 495 | Name=name, 496 | TypeName="Switch" 497 | ) 498 | self.new_device(device, self.Device.PLAYER.value, name) 499 | Domoticz.Log(f"Le player TV{uid} est " + POWER_STATE[player_state]) 500 | self.update_device(self.Device.PLAYER, unit_name, player_state, str(player_state)) 501 | 502 | def _create_devices_precord(self,f): 503 | # Create Next Programmed Record counter item 504 | timestamp = f.next_pvr_precord_timestamp() 505 | unit_id = self.return_unit_id( 506 | self.Device.PRECORD, "NextPrecord") 507 | if unit_id not in Devices: 508 | device = Domoticz.Device( 509 | Unit=unit_id, 510 | Name="Next Record In", 511 | TypeName="Custom", 512 | Options={"Custom": "1;s"}, 513 | Used=1 514 | ) 515 | self.new_device(device, self.Device.PRECORD.value, 'NextPrecord') 516 | Domoticz.Log(self._str_precode_state(timestamp)) 517 | self.update_device(self.Device.PRECORD, "NextPrecord", timestamp, str(timestamp)) 518 | 519 | def _refresh_devices_storages(self, f): 520 | # Update Disk metics 521 | disks = f.ls_storage() 522 | for disk, value in disks.items(): 523 | Domoticz.Debug(f"L'espace disque de '{disk}' est occupé à {value}%") 524 | self.update_device(self.Device.DISK, disk, int(float(value)), str(value)) 525 | 526 | def _refresh_devices_rates(self, f): 527 | # Update WAN UP/DL Rates 528 | connection_rates = f.connection_rate() 529 | for rate, value in connection_rates.items(): 530 | Domoticz.Debug(f"Le débit WAN en '{RATE_TYPE[rate]}' est de {value} ko/s") 531 | self.update_device(self.Device.CONNECTION_RATE, rate, int(float(value)), str(value)) 532 | 533 | def _refresh_devices_sensors(self, f): 534 | # Update °C temp devices 535 | sensors = f.system.sensors() 536 | for sensor in sensors: 537 | uid = str(sensor['id']) 538 | value = int(sensor['value']) 539 | Domoticz.Debug(f"La sonde '{sensor['name']}' affiche {value}°C") 540 | self.update_device(self.Device.SYSTEM_SENSOR, uid, value, str(value)) 541 | 542 | def _refresh_devices_alarm(self, f): 543 | # Update Alarm informations (Only in option with the Frebox Delta) 544 | alarminfo = f.alarminfo() 545 | for alarm_device in alarminfo: 546 | self.update_device( 547 | self.Device.ALARM, 548 | alarminfo[alarm_device]["label"], 549 | int(alarminfo[alarm_device]["value"]), 550 | str(alarminfo[alarm_device]["value"]), 551 | int(alarminfo[alarm_device]["battery"]) 552 | ) 553 | 554 | def _refresh_devices_presence(self, f): 555 | # Update "Presence" Domoticz values 556 | str_ls_macaddr = Parameters["Mode2"] 557 | ls_macaddr = str_ls_macaddr.split(";") 558 | for macaddress in ls_macaddr: 559 | name = f.get_name_from_macaddress(macaddress) 560 | if name is not None: 561 | presence = 1 if f.reachable_macaddress(macaddress) else 0 562 | Domoticz.Debug( 563 | f"L'équipement '{name}' est actuellement {PRESENCE_STATE[presence]}" 564 | ) 565 | self.update_device(self.Device.PRESENCE, macaddress, presence, str(presence)) 566 | 567 | def _refresh_devices_wifi(self, f): 568 | # Update "Wifi" Domoticz switch state 569 | wifi_state = 1 if f.wifi_state() else 0 570 | Domoticz.Debug("Le WIFI est " + LINK_STATE[wifi_state]) 571 | self.update_device(self.Device.COMMAND, "WIFI", wifi_state, str(wifi_state)) 572 | 573 | def _refresh_devices_wan(self, f): 574 | # Update "WAN interface" Domoticz switch state 575 | wan_state = 1 if f.wan_state() else 0 576 | Domoticz.Debug("La connexion Internet est " + LINK_STATE[wan_state]) 577 | self.update_device(self.Device.COMMAND, "WANStatus", wan_state, str(wan_state)) 578 | 579 | def _refresh_devices_players(self, f): 580 | # Update "Players" Domoticz values 581 | players = f.players.info 582 | for player in players: 583 | uid = str(player['id']) 584 | player_state = 1 if f.players.state(uid) else 0 585 | unit_name = player['device_model'] + '_' + uid 586 | Domoticz.Debug(f"Le player TV{uid} est " + POWER_STATE[player_state]) 587 | self.update_device(self.Device.PLAYER, unit_name, player_state, str(player_state)) 588 | 589 | def _refresh_devices_precord(self,f): 590 | # Update "Next Programmed record In" value 591 | timestamp = f.next_pvr_precord_timestamp() 592 | Domoticz.Debug(self._str_precode_state(timestamp)) 593 | self.update_device(self.Device.PRECORD, "NextPrecord", timestamp, str(timestamp)) 594 | 595 | def _switch_reboot(self, f): 596 | f.reboot() 597 | 598 | def _switch_wifi(self, f, command): 599 | f.wifi_enable(SWITCH_CMD[command]) 600 | time.sleep(1) 601 | # Update Wifi state 602 | self._refresh_devices_wifi(f) 603 | 604 | def _switch_player(self, f, command, player_id="1"): 605 | Domoticz.Log(f"Switch 'TV Player{player_id}'") 606 | if player_id=="1": 607 | remote_code = self.remote_code_tv1 608 | elif player_id=="2": 609 | remote_code = self.remote_code_tv2 610 | if remote_code is not None and command=="Off" : 611 | f.players.shutdown(player_id, remote_code) 612 | time.sleep(1) 613 | # Update Player state 614 | self._refresh_devices_players(f) 615 | 616 | def _str_precode_state(self, timestamp): 617 | if timestamp == -1: 618 | return "Aucun enregistrement n'est programmé" 619 | elif timestamp == 0: 620 | return "Un enregistrement est en cours" 621 | else: 622 | return "Prochain enregistrement TV dans " + str(timestamp) + " secondes" 623 | 624 | def onStart(self): 625 | """ 626 | Called when the hardware is started, either after Domoticz start, hardware creation or update. 627 | """ 628 | Domoticz.Log("onStart called") 629 | try: 630 | if self.init() : 631 | f = freebox.FbxApp("idPluginDomoticz", self.token, self.freebox_url) 632 | self._create_devices_reboot() 633 | self._create_devices_storages(f) 634 | self._create_devices_rates(f) 635 | self._create_devices_sensors(f) 636 | self._create_devices_alarm(f) 637 | self._create_devices_presence(f) 638 | self._create_devices_wifi(f) 639 | self._create_devices_wan(f) 640 | self._create_devices_players(f) 641 | self._create_devices_precord(f) 642 | DumpConfigToLog() 643 | except Exception as e: 644 | Domoticz.Error(f"OnStart error: {e}") 645 | Domoticz.Error(traceback.format_exc()) 646 | 647 | def onStop(self): 648 | """ 649 | Called when the hardware is stopped or deleted from Domoticz. 650 | """ 651 | Domoticz.Log("onStop called") 652 | 653 | def onConnect(self, connection, status, description): 654 | """ 655 | Called when connection to remote device either succeeds or fails, 656 | or when a connection is made to a listening Address:Port. 657 | Connection is the Domoticz Connection object associated with the event. 658 | Zero Status indicates success. If Status is not zero then the Description will describe 659 | the failure. 660 | This callback is not called for connectionless Transports such as UDP/IP. 661 | """ 662 | Domoticz.Log(f"onConnect called: \ 663 | Connection={connection}, Status={status}, Description={description}") 664 | 665 | def onMessage(self, connection, data, status, extra): 666 | """ 667 | Called when a single, complete message is received from the external hardware 668 | (as defined by the Protocol setting). This callback should be used to interpret 669 | messages from the device and set the related Domoticz devices as required. 670 | Connection is the Domoticz Connection object associated with the event. 671 | Data is normally a ByteArray except where the Protocol for the Connection has 672 | structure (such as HTTP or ICMP), in that case Data will be a Dictionary containing 673 | Protocol specific details such as Status and Headers. 674 | """ 675 | Domoticz.Log(f"onMessage called for Connection={connection}, \ 676 | Data={data}, Status={status}, Extra={extra}") 677 | 678 | def onCommand(self, unit, command, level, hue): 679 | """ 680 | Called by Domoticz in response to a script or Domoticz Web API call sending a command 681 | to a Unit in the Device's Units dictionary 682 | """ 683 | Domoticz.Log(f"onCommand called for Unit={unit}, Command={command}, Level={level}, Hue={hue}") 684 | properties = self.return_properties_from_id(unit) 685 | device = self.return_device_from_properties(properties) 686 | name = self.return_name_from_properties(properties) 687 | try: 688 | f = freebox.FbxApp("idPluginDomoticz", self.token, host=self.freebox_url) 689 | if device == self.Device.COMMAND.value: 690 | if name == "REBOOT": self._switch_reboot(f) 691 | elif name == "WIFI": self._switch_wifi(f, command) 692 | elif device == self.Device.PLAYER.value: 693 | self._switch_player(f, command, str(name)[-1:]) 694 | 695 | except Exception as e: 696 | Domoticz.Error(f"onHeartbeat error: {e}") 697 | Domoticz.Error(traceback.format_exc()) 698 | 699 | def onNotification(self, name, subject, text, status, priority, sound, image): 700 | """ 701 | Called when any Domoticz device generates a notification. 702 | Name parameter is the device that generated the notification, the other parameters 703 | contain the notification details. Hardware that can handle notifications should 704 | be notified as required. 705 | """ 706 | Domoticz.Log(f"Notification: {name}, Subject={subject}, Text={text}, Status={status}, \ 707 | Priority={priority}, Sound={sound}, ImageFile={image}") 708 | 709 | def onDisconnect(self, connection): 710 | """ 711 | Called after the remote device is disconnected. 712 | Connection is the Domoticz Connection object associated with the event. 713 | This callback is not called for connectionless Transports such as UDP/IP. 714 | """ 715 | Domoticz.Log(f"onDisconnect called for {connection}") 716 | 717 | def onHeartbeat(self): 718 | """ 719 | Called every 'heartbeat' seconds (default 10) regardless of connection status. 720 | Heartbeat interval can be modified by the Heartbeat command. 721 | Allows the Plugin to do periodic tasks including request reconnection if the connection 722 | has failed. 723 | """ 724 | Domoticz.Debug("onHeartbeat called") 725 | 726 | self._tick = self._tick + 10 # Add 10s (i.e. the default heartbeat) 727 | self._tick = self._tick % self._refresh_interval 728 | if self._tick != 0: 729 | return # To skip beacause refresh interval doesn't reach 730 | 731 | if self.token == "": 732 | Domoticz.Log("Pas de token défini.") 733 | return 734 | 735 | try: 736 | f = freebox.FbxApp("idPluginDomoticz", self.token, host=self.freebox_url) 737 | self._refresh_devices_storages(f) 738 | self._refresh_devices_rates(f) 739 | self._refresh_devices_sensors(f) 740 | self._refresh_devices_alarm(f) 741 | self._refresh_devices_presence(f) 742 | self._refresh_devices_wifi(f) 743 | self._refresh_devices_wan(f) 744 | self._refresh_devices_players(f) 745 | self._refresh_devices_precord(f) 746 | except Exception as e: 747 | Domoticz.Error(f"onHeartbeat error: {e}") 748 | Domoticz.Error(traceback.format_exc()) 749 | 750 | global _plugin 751 | _plugin = FreeboxPlugin() 752 | 753 | 754 | def onStart(): 755 | global _plugin 756 | # on fait une pause de 10 secondes au démarrage pour attendre la Freebox si besoin 757 | # correction apporté par Gells qui avait des erreur au démarrage 758 | # https://easydomoticz.com/forum/viewtopic.php?f=10&t=6222&p=55468#p55442 759 | time.sleep(5) 760 | _plugin.onStart() 761 | 762 | 763 | def onStop(): 764 | """ 765 | Called when the hardware is stopped or deleted from Domoticz. 766 | """ 767 | global _plugin 768 | _plugin.onStop() 769 | 770 | 771 | def onConnect(Connection, Status, Description): 772 | global _plugin 773 | _plugin.onConnect(Connection, Status, Description) 774 | 775 | 776 | def onMessage(Connection, Data, Status, Extra): 777 | global _plugin 778 | _plugin.onMessage(Connection, Data, Status, Extra) 779 | 780 | 781 | def onCommand(Unit, Command, Level, Hue): 782 | global _plugin 783 | _plugin.onCommand(Unit, Command, Level, Hue) 784 | 785 | 786 | def onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile): 787 | global _plugin 788 | _plugin.onNotification(Name, Subject, Text, Status, 789 | Priority, Sound, ImageFile) 790 | 791 | 792 | def onDisconnect(Connection): 793 | global _plugin 794 | _plugin.onDisconnect(Connection) 795 | 796 | 797 | def onHeartbeat(): 798 | global _plugin 799 | _plugin.onHeartbeat() 800 | 801 | 802 | def DumpConfigToLog(): 803 | for x in Parameters: 804 | if Parameters[x] != "": 805 | Domoticz.Debug("'" + x + "':'" + str(Parameters[x]) + "'") 806 | Domoticz.Debug("Device count: " + str(len(Devices))) 807 | for x in Devices: 808 | Domoticz.Debug("Device: " + str(x) + " - " + str(Devices[x])) 809 | Domoticz.Debug("Device ID: '" + str(Devices[x].ID) + "'") 810 | Domoticz.Debug("Device Name: '" + Devices[x].Name + "'") 811 | Domoticz.Debug("Device nValue: " + str(Devices[x].nValue)) 812 | Domoticz.Debug("Device sValue: '" + Devices[x].sValue + "'") 813 | Domoticz.Debug("Device LastLevel: " + str(Devices[x].LastLevel)) 814 | Domoticz.Debug("Options: '" + str(Devices[x].Options) + "'") 815 | return 816 | --------------------------------------------------------------------------------