From 1bf37d16acbc8069b6d2c8feb847ec55e6352bc1 Mon Sep 17 00:00:00 2001 From: frankdpGH <60138601+frankdpGH@users.noreply.github.com> Date: Wed, 14 Apr 2021 11:12:20 +0200 Subject: [PATCH 01/61] Update slugify to unicode-slugify --- smarthome.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smarthome.py b/smarthome.py index fc09271..657528a 100644 --- a/smarthome.py +++ b/smarthome.py @@ -44,10 +44,10 @@ from slugify import slugify except ImportError as e: logger.error('Installing package slugify') - subprocess.call(['pip3', 'install', 'slugify']) + subprocess.call(['pip3', 'install', 'unicode-slugify']) import pychromecast - import socket ## + import socket from gtts import gTTS from slugify import slugify From 257acc3f2caf83f2002041b9ced3886cafa25286 Mon Sep 17 00:00:00 2001 From: frankdpGH <60138601+frankdpGH@users.noreply.github.com> Date: Wed, 14 Apr 2021 11:17:02 +0200 Subject: [PATCH 02/61] Update requirments Pychromecast and unicode-slugify --- requirements/pip-requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/pip-requirements.txt b/requirements/pip-requirements.txt index 898ad0c..e28cbed 100644 --- a/requirements/pip-requirements.txt +++ b/requirements/pip-requirements.txt @@ -10,6 +10,6 @@ GitPython>=3.0.5 google-auth>=1.8.1 wheel>=0.34.2 Jinja2==2.11.3 -PyChromecast==0.3.2 +PyChromecast==9.1.2 gTTS==2.2.1 -slugify==0.0.1 +unicode-slugify==0.1.3 From 3cfb431905277472f73b126e3c59bec41eabf3a4 Mon Sep 17 00:00:00 2001 From: frankdpGH <60138601+frankdpGH@users.noreply.github.com> Date: Wed, 14 Apr 2021 15:28:00 +0200 Subject: [PATCH 03/61] Delete pip3 installs in smarthome.py Depending on the install pip-requirements during update (Beta tester have to install requirements manually) --- smarthome.py | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/smarthome.py b/smarthome.py index 657528a..815a9e3 100644 --- a/smarthome.py +++ b/smarthome.py @@ -21,36 +21,13 @@ ATTRS_THERMSTATSETPOINT, ATTRS_COLOR_TEMP, ATTRS_PERCENTAGE, VERSION, DOMOTICZ_GET_VERSION) from helpers import (configuration, readFile, saveFile, SmartHomeError, SmartHomeErrorNoChallenge, AogState, uptime, getTunnelUrl, FILE_DIR, logger, ReportState, Auth, logfilepath) - -try: - from jinja2 import Environment, FileSystemLoader -except ImportError: - logger.info('Installing package jinja2') - subprocess.call(['pip3', 'install', 'jinja2']) - from jinja2 import Environment, FileSystemLoader +from jinja2 import Environment, FileSystemLoader if 'Chromecast_Name' in configuration and configuration['Chromecast_Name'] != 'add_chromecast_name': - try: - import pychromecast - except ImportError as e: - logger.error('Installing package pychromecast') - subprocess.call(['pip3', 'install', '--upgrade', '--force-reinstall', 'pychromecast']) - try: - from gtts import gTTS - except ImportError as e: - logger.error('Installing package gtts') - subprocess.call(['pip3', 'install', 'gtts']) - try: - from slugify import slugify - except ImportError as e: - logger.error('Installing package slugify') - subprocess.call(['pip3', 'install', 'unicode-slugify']) - import pychromecast import socket from gtts import gTTS from slugify import slugify - logger.info("Starting up chromecasts") try: chromecasts, _ = pychromecast.get_chromecasts() From c3117f6246d7321ca67d5453b053eceba630d894 Mon Sep 17 00:00:00 2001 From: DewGew Date: Thu, 15 Apr 2021 10:50:28 +0200 Subject: [PATCH 04/61] Update const.py --- const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const.py b/const.py index c17be4f..67ce1d8 100644 --- a/const.py +++ b/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Constants for Google Assistant.""" -VERSION = '1.10.9' +VERSION = '1.10.10' PUBLIC_URL = 'https://[your public url]' CONFIGFILE = 'config/config.yaml' LOGFILE = 'dzga.log' From 597606fcf9ac3c1c691c1e1184d64eec67bd6a6e Mon Sep 17 00:00:00 2001 From: frankdpGH <60138601+frankdpGH@users.noreply.github.com> Date: Sat, 1 May 2021 11:32:50 +0200 Subject: [PATCH 05/61] Update pip-requirements.txt --- requirements/pip-requirements.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/requirements/pip-requirements.txt b/requirements/pip-requirements.txt index e28cbed..9dac44a 100644 --- a/requirements/pip-requirements.txt +++ b/requirements/pip-requirements.txt @@ -9,7 +9,10 @@ urllib3>=1.25.7 GitPython>=3.0.5 google-auth>=1.8.1 wheel>=0.34.2 -Jinja2==2.11.3 -PyChromecast==9.1.2 -gTTS==2.2.1 -unicode-slugify==0.1.3 +Jinja2>=2.11.3 +PyChromecast>=9.1.2 +gTTS>=2.2.1 +unicode-slugify>=0.1.3 +protobuf>=3.0.0 +zeroconf>=0.25.1 +casttube>=0.2.0 From 26d438f24f5def28977bf5a86827e1975852d354 Mon Sep 17 00:00:00 2001 From: frankdpGH <60138601+frankdpGH@users.noreply.github.com> Date: Sat, 1 May 2021 11:47:26 +0200 Subject: [PATCH 06/61] New features chromecast notifications New features Notification on specific chomecast device with specific volume : "/say?bonjour/fr@60@Kitchen%20speaker" or "/play?doorbell1.mp3@40@Living%20room%20TV" (@volume level@target device). After the message the media and volume that was playing will be restored. An extra library of functions is available "/pycast?" will give access to new functions : status, devices, switchdevice, volume, setvolume, pause, play, stop, playmedia (see attached files for examples) Basically there are now structured json responses that can be used with the dzvents http events. See attached files : https://github.com/DewGew/Domoticz-Google-Assistant/issues/239#issuecomment-830144555 --- smarthome.py | 222 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 194 insertions(+), 28 deletions(-) diff --git a/smarthome.py b/smarthome.py index 815a9e3..5a4ac28 100644 --- a/smarthome.py +++ b/smarthome.py @@ -32,6 +32,7 @@ try: chromecasts, _ = pychromecast.get_chromecasts() cast = next(cc for cc in chromecasts if cc.device.friendly_name == configuration['Chromecast_Name']) + mc = cast.media_controller except Exception as e: logger.error('chromecasts init not succeeded, error : %s' % e) t = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -974,8 +975,14 @@ def smarthome_disconnect(self, payload, token): """ return None - def say(self, s): - itext = s.url.query.replace(" ","-") + def say(self, s): #command "/say?text-to-say/lang@volume@device" + answ, scomm, rdevice, rvol, rcontent, rtype, stime = SmartHomeReqHandler.read_input(s.url.query) + if answ=="Error": + if rcontent!="?": + answ, message = SmartHomeReqHandler.playmedia(rcontent, rtype, 'PLAYING', 40) + SmartHomeReqHandler.send_resp("Error", s.url.query, scomm, stime, s) + return + itext = scomm.replace(" ","-") itext=itext.split("/") text = itext[0] if not text: @@ -985,8 +992,6 @@ def say(self, s): else: lang = "en" slow = False - current_time = time.strftime("%d/%m/%y %H:%M:%S", time.localtime()) - message="say command on " + current_time + ", text : " + str(text) + ", lang : " + lang tts = gTTS(text=text, lang=lang, slow=slow) filename = slugify(text+"-"+lang+"-"+str(slow)) + ".mp3" cache_filename = FILE_DIR + "/sound/cache/" + filename @@ -994,44 +999,203 @@ def say(self, s): if not tts_file.is_file(): logger.info(tts) tts.save(cache_filename) - mp3_url = "http://" + IP_Address + ":" + str(s.server.server_port) + "/sound?cache/" + filename - #make a query request for Get /sound - logger.info(message) - SmartHomeReqHandler.play_mp3(mp3_url) - s.send_message(200, "OK " + message + "\n") - - def play(self, s): - filename = s.url.query + mp3_url = "http://" + IP_Address + ":" + str(s.server.server_port) + "/sound?cache/" + filename #make a query request for Get /sound + rstatus, rmessage = SmartHomeReqHandler.playmedia(mp3_url,'audio/mp3','IDLE', 20) + if rvol!="?": + answ, message = SmartHomeReqHandler.setvolume(str(round(rvol*100))) + rmessage = rmessage + " restore volume " + str(round(rvol*100)) + if rcontent!="?": + answ, message = SmartHomeReqHandler.playmedia(rcontent, rtype, 'PLAYING', 40) + rmessage = rmessage + " restore stream : " + rcontent + if rdevice!="?": + answ, message = SmartHomeReqHandler.switchdevice(rdevice) + rmessage = rmessage + " restore device '" + rdevice+ "'" + SmartHomeReqHandler.send_resp(rstatus, "say " + s.url.query, rmessage, stime, s) + + def play(self, s): #command "/play?soundfile.mp3@volume@device" + answ, scomm, rdevice, rvol, rcontent, rtype, stime = SmartHomeReqHandler.read_input(s.url.query) + if answ=="Error": + if rcontent!="?": + answ, message = SmartHomeReqHandler.playmedia(rcontent, rtype, 'PLAYING', 40) + SmartHomeReqHandler.send_resp("Error", s.url.query, scomm, stime, s) + return + filename = scomm mp3_filename = FILE_DIR + "/sound/" + filename mp3 = Path(mp3_filename) - current_time = time.strftime("%d/%m/%y %H:%M:%S", time.localtime()) - message = "play command on " + current_time + ", file : " + str(mp3_filename) - logger.info(message) if mp3.is_file(): mp3_url = "http://" + IP_Address + ":" + str(s.server.server_port) + "/sound?" + filename #make a query request for Get /sound - SmartHomeReqHandler.play_mp3(mp3_url) - s.send_message(200, "OK " + message + "\n") + rstatus, rmessage = SmartHomeReqHandler.playmedia(mp3_url,'audio/mp3','IDLE', 20) else: - s.send_message(200, "File not found\n") - - def play_mp3(mp3_url): - cast.wait() - mc = cast.media_controller - mc.play_media(mp3_url, 'audio/mp3') - logger.info("Play mp3 started") + rstatus="Error" + rmessage = str(mp3_filename) + ", file not found!" + if rvol!="?": + answ, message = SmartHomeReqHandler.setvolume(str(round(rvol*100))) + rmessage = rmessage + " restore volume " + str(round(rvol*100)) + if rcontent!="?": + answ, message = SmartHomeReqHandler.playmedia(rcontent, rtype, 'PLAYING', 40) + rmessage = rmessage + " restore stream : " + rcontent + if rdevice!="?": + answ, message = SmartHomeReqHandler.switchdevice(rdevice) + rmessage = rmessage + " restore device '" + rdevice+ "'" + SmartHomeReqHandler.send_resp(rstatus, "play " + s.url.query, rmessage, stime, s) def send_sound(self, s): filename = s.url.query cache_filename = FILE_DIR + "/sound/" + filename - logger.info("sound : " + cache_filename) f = open(cache_filename, 'rb') - s.send_response(200) # send_message (later in server.py) + s.send_response(200) s.send_header('Content-type', 'audio/mpeg3') s.end_headers() s.wfile.write(f.read()) f.close() + + def send_resp(rstatus, rcommand, rmessage, stime, s): + # time.sleep(1) + etime = time.strftime("%d/%m/%y %H:%M:%S", time.localtime()) + rvolume = "{:.0%}".format(cast.status.volume_level) + rcontent = mc.status.content_id + rtype = mc.status.content_type + rpstate = mc.status.player_state + if rpstate == "UNKNOWN": + rcontent="?" + rtype="?" + message='{"device":"'+ cast.device.friendly_name + '","status":"' + rstatus + '","command":"' + rcommand + '","volume":"' +rvolume +'","starttime":"' + stime + '","endtime":"' + etime + '","playstate":"' + rpstate + '","content":"' + rcontent + '","type":"' + rtype + '","message":"' + rmessage+ '"}' + s.send_json(200, message, False) + logger.info(message) + + def read_input(ctext): + global cast, mc, chromecasts + stime = time.strftime("%d/%m/%y %H:%M:%S", time.localtime()) + answ="OK" + message="" + rdevice = "?" + rvol = "?" + rcontent = "?" + rtype = "?" + ctext = ctext.split("@") + try: + svol=ctext[1] + except: + svol="" + try: + sdevice=ctext[2] + except: + sdevice="" + if sdevice!="": + rdevice = cast.device.friendly_name + answ, message = SmartHomeReqHandler.switchdevice(sdevice) + if answ == "Error": + return answ, message, rdevice, rvol, rcontent, rtype, stime + rpstate = mc.status.player_state + if rpstate != "UNKNOWN" and rpstate != "IDLE": + mc.stop() + rcontent = mc.status.content_id + rtype = mc.status.content_type + else: + rcontent = "?" + rtype = "?" + if svol!="": + cast.wait() + rvol = cast.status.volume_level + answ, message = SmartHomeReqHandler.setvolume(svol) + if answ == "Error": + return answ, message, rdevice, rvol, rcontent, rtype, stime + return answ, ctext[0], rdevice, rvol, rcontent, rtype, stime + + def switchdevice(sdevice): + global cast, mc, chromecasts + sdevice = sdevice.replace("%20"," ") + try: + cast = next(cc for cc in chromecasts if cc.device.friendly_name == sdevice) + cast.wait() + mc = cast.media_controller + return "OK","Switched to device " + str(cast.device.friendly_name) + except Exception as e: + logger.error('chromecasts init not succeeded, error : %s' % e) + return "Error","Not switched to device " + str(sdevice) + + def setvolume(svol): + global cast, mc, chromecasts + svol=svol.replace("%","") + try: + cast.wait() + cast.set_volume(int(svol)/100) + time.sleep(1) + cast.wait() + return "OK","Volume level set to : " + svol +"%" + except Exception as e: + logger.error('Chromecast setvolume unsuccesfull, error : %s' % e) + return "Error","Volume level not set to : " + svol +"%" + def playmedia(pmedia,ptype, wstate, tmax): + try: + mc.play_media(pmedia, ptype) + mc.block_until_active() + cast.wait() + pstate = "?" + i=1 #max x seconds + while (mc.status.player_state != wstate or pstate != wstate) and i Date: Sat, 1 May 2021 12:10:57 +0200 Subject: [PATCH 07/61] Update const.py --- const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const.py b/const.py index 67ce1d8..0df347f 100644 --- a/const.py +++ b/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Constants for Google Assistant.""" -VERSION = '1.10.10' +VERSION = '1.11.10' PUBLIC_URL = 'https://[your public url]' CONFIGFILE = 'config/config.yaml' LOGFILE = 'dzga.log' From cbbac45266f0c6080351d99e7d4b6bc383776cb8 Mon Sep 17 00:00:00 2001 From: frankdpGH <60138601+frankdpGH@users.noreply.github.com> Date: Fri, 28 May 2021 17:06:21 +0200 Subject: [PATCH 08/61] Update smarthome.py --- smarthome.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smarthome.py b/smarthome.py index 5a4ac28..62e8b59 100644 --- a/smarthome.py +++ b/smarthome.py @@ -993,7 +993,7 @@ def say(self, s): #command "/say?text-to-say/lang@volume@device" lang = "en" slow = False tts = gTTS(text=text, lang=lang, slow=slow) - filename = slugify(text+"-"+lang+"-"+str(slow)) + ".mp3" + filename = slugify(text+"-"+lang+"-"+str(slow), only_ascii=True) + ".mp3" cache_filename = FILE_DIR + "/sound/cache/" + filename tts_file = Path(cache_filename) if not tts_file.is_file(): From e23af1360f4792a1f776d15092f5a6f8a08772b4 Mon Sep 17 00:00:00 2001 From: frankdpGH <60138601+frankdpGH@users.noreply.github.com> Date: Thu, 3 Jun 2021 23:26:38 +0200 Subject: [PATCH 09/61] IP_Port from server configuration --- smarthome.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/smarthome.py b/smarthome.py index 62e8b59..7ba8da7 100644 --- a/smarthome.py +++ b/smarthome.py @@ -38,7 +38,8 @@ t = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) t.connect(("8.8.8.8", 80)) IP_Address = t.getsockname()[0] - t.close + t.close + IP_Port = str(configuration['port_number']) logger.info("IP_Address is : " + IP_Address) DOMOTICZ_URL = configuration['Domoticz']['ip'] + ':' + configuration['Domoticz']['port'] @@ -999,7 +1000,7 @@ def say(self, s): #command "/say?text-to-say/lang@volume@device" if not tts_file.is_file(): logger.info(tts) tts.save(cache_filename) - mp3_url = "http://" + IP_Address + ":" + str(s.server.server_port) + "/sound?cache/" + filename #make a query request for Get /sound + mp3_url = "http://" + IP_Address + ":" + IP_Port + "/sound?cache/" + filename #make a query request for Get /sound rstatus, rmessage = SmartHomeReqHandler.playmedia(mp3_url,'audio/mp3','IDLE', 20) if rvol!="?": answ, message = SmartHomeReqHandler.setvolume(str(round(rvol*100))) @@ -1023,7 +1024,7 @@ def play(self, s): #command "/play?soundfile.mp3@volume mp3_filename = FILE_DIR + "/sound/" + filename mp3 = Path(mp3_filename) if mp3.is_file(): - mp3_url = "http://" + IP_Address + ":" + str(s.server.server_port) + "/sound?" + filename + mp3_url = "http://" + IP_Address + ":" + IP_Port + "/sound?" + filename #make a query request for Get /sound rstatus, rmessage = SmartHomeReqHandler.playmedia(mp3_url,'audio/mp3','IDLE', 20) else: From 420c725c3720358c4efe0b4cbc626a3ef11d72c2 Mon Sep 17 00:00:00 2001 From: frankdpGH <60138601+frankdpGH@users.noreply.github.com> Date: Sat, 5 Jun 2021 13:59:24 +0200 Subject: [PATCH 10/61] IP_Port from Config and debug extension --- smarthome.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/smarthome.py b/smarthome.py index 7ba8da7..6fc42ff 100644 --- a/smarthome.py +++ b/smarthome.py @@ -1044,12 +1044,14 @@ def play(self, s): #command "/play?soundfile.mp3@volume def send_sound(self, s): filename = s.url.query cache_filename = FILE_DIR + "/sound/" + filename + logger.debug("Request for soundfile received, file = " + str(cache_filename)) f = open(cache_filename, 'rb') s.send_response(200) s.send_header('Content-type', 'audio/mpeg3') s.end_headers() s.wfile.write(f.read()) f.close() + logger.debug("File returned succesfully") def send_resp(rstatus, rcommand, rmessage, stime, s): # time.sleep(1) From 67ba80c969a2de7983eab8069208dee6b6da3144 Mon Sep 17 00:00:00 2001 From: DewGew Date: Wed, 20 Apr 2022 08:58:46 +0200 Subject: [PATCH 11/61] Update pip-requirements.txt --- requirements/pip-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/pip-requirements.txt b/requirements/pip-requirements.txt index 9dac44a..9b5f060 100644 --- a/requirements/pip-requirements.txt +++ b/requirements/pip-requirements.txt @@ -10,7 +10,7 @@ GitPython>=3.0.5 google-auth>=1.8.1 wheel>=0.34.2 Jinja2>=2.11.3 -PyChromecast>=9.1.2 +PyChromecast==9.1.2 gTTS>=2.2.1 unicode-slugify>=0.1.3 protobuf>=3.0.0 From 03c195646e07cefb1a1d1420c00d1aee871077b4 Mon Sep 17 00:00:00 2001 From: Brunet Cyril Date: Tue, 5 Jul 2022 13:29:38 +0200 Subject: [PATCH 12/61] adding blinds with stop as percentage blinds --- smarthome.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/smarthome.py b/smarthome.py index 6fc42ff..29a7128 100644 --- a/smarthome.py +++ b/smarthome.py @@ -101,9 +101,9 @@ def checkupdate(): def AogGetDomain(device): if device["Type"] in ['Light/Switch', 'Lighting 1', 'Lighting 2', 'Lighting 5', 'RFY', 'Value']: if device["SwitchType"] in ['Blinds', 'Venetian Blinds EU', 'Venetian Blinds US', - 'Blinds Percentage']: + 'Blinds Percentage', 'Blinds + Stop']: return domains['blinds'] - elif device["SwitchType"] in ['Blinds Inverted', 'Blinds Percentage Inverted']: + elif device["SwitchType"] in ['Blinds Inverted', 'Blinds Percentage Inverted', 'Blinds Inverted + Stop']: return domains['blindsinv'] elif 'Door Lock' == device["SwitchType"]: return domains['lock'] @@ -327,9 +327,9 @@ def getAog(device): aog.attributes = ATTRS_COLOR_TEMP if domains['thermostat'] == aog.domain and "Thermostat" == device["Type"]: aog.attributes = ATTRS_THERMSTATSETPOINT - if domains['blinds'] == aog.domain and "Blinds Percentage" == device["SwitchType"]: + if domains['blinds'] == aog.domain and ("Blinds Percentage" == device["SwitchType"] or "Blinds + Stop" == device["SwitchType"]): aog.attributes = ATTRS_PERCENTAGE - if domains['blindsinv'] == aog.domain and "Blinds Percentage Inverted" == device["SwitchType"]: + if domains['blindsinv'] == aog.domain and ("Blinds Percentage Inverted" == device["SwitchType"] or "Blinds Inverted + Stop" == device["SwitchType"]): aog.attributes = ATTRS_PERCENTAGE if domains['vacuum'] == aog.domain and "Selector" == device["SwitchType"]: aog.attributes = ATTRS_VACUUM_MODES From 5a578bafc32afc2d1609446ea5db95207be3db5e Mon Sep 17 00:00:00 2001 From: DewGew Date: Wed, 3 Aug 2022 11:50:21 +0200 Subject: [PATCH 13/61] [Skip travis] --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e3a4d4e..d2d31a8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ branches: python: - "3.5" - "3.6" # current default Python on Travis CI - - "3.7" - "3.8" # command to install dependencies install: From 8140575a25afaa75708a4b4a3d0547c9175ac91d Mon Sep 17 00:00:00 2001 From: DewGew Date: Mon, 21 Nov 2022 13:19:40 +0100 Subject: [PATCH 14/61] Update const.py --- const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const.py b/const.py index 0df347f..8a91a84 100644 --- a/const.py +++ b/const.py @@ -165,7 +165,7 @@ domains['smokedetector']: TYPE_SMOKE_DETECTOR, domains['speaker']: TYPE_SPEAKER, domains['switch']: TYPE_SWITCH, - domains['temperature']: TYPE_THERMOSTAT, + domains['temperature']: TYPE_SENSOR, domains['thermostat']: TYPE_THERMOSTAT, domains['vacuum']: TYPE_VACUUM, domains['valve']: TYPE_VALVE, From d46ca391d4ef02c4c19b95c5a0637e0d820f0f62 Mon Sep 17 00:00:00 2001 From: DewGew Date: Mon, 21 Nov 2022 13:24:43 +0100 Subject: [PATCH 15/61] Update trait.py Changes according to the latetest version of domoticz 2022.2 --- trait.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/trait.py b/trait.py index c61c12f..4235b20 100644 --- a/trait.py +++ b/trait.py @@ -382,10 +382,10 @@ def execute(self, command, params): if domain == domains['blindsinv']: if p == 0 and state in ['Closed', 'Stopped', 'On']: # open - url += 'Off' + url += 'Open' elif p == 100 and state in ['Open', 'Stopped', 'Off']: # close - url += 'On' + url += 'Close' else: raise SmartHomeError(ERR_ALREADY_IN_STATE, 'Unable to execute {} for {}. Already in state '.format(command, @@ -393,10 +393,10 @@ def execute(self, command, params): else: if p == 100 and state in ['Closed', 'Stopped', 'On']: # open - url += 'Off' + url += 'Open' elif p == 0 and state in ['Open', 'Stopped', 'Off']: # close - url += 'On' + url += 'Close' else: raise SmartHomeError(ERR_ALREADY_IN_STATE, 'Unable to execute {} for {}. Already in state '.format(command, From 17ddfa2ca88a8a6fb3b4688552f74366344a5bf5 Mon Sep 17 00:00:00 2001 From: EA4GKQ Date: Tue, 22 Nov 2022 12:43:04 +0100 Subject: [PATCH 16/61] Update trait.py Fix percet request with inverted blind --- trait.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/trait.py b/trait.py index 4235b20..8963ffb 100644 --- a/trait.py +++ b/trait.py @@ -343,7 +343,10 @@ def query_attributes(self): if features & ATTRS_PERCENTAGE: response['openPercent'] = self.state.level - + if domain == domains['blindsinv']: + response['openPercent'] = self.state.level + else: + response['openPercent'] = 100 - self.state.level elif domain == domains['blindsinv']: if self.state.state in ['Open', 'Off']: response['openPercent'] = 0 From 52f4e751dc8d22fe0565a37aad9cfd1a0edb4bfc Mon Sep 17 00:00:00 2001 From: DewGew Date: Fri, 25 Nov 2022 12:38:20 +0100 Subject: [PATCH 17/61] Update smarthome.py --- smarthome.py | 58 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/smarthome.py b/smarthome.py index 29a7128..6c0e1bc 100644 --- a/smarthome.py +++ b/smarthome.py @@ -38,7 +38,7 @@ t = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) t.connect(("8.8.8.8", 80)) IP_Address = t.getsockname()[0] - t.close + t.close IP_Port = str(configuration['port_number']) logger.info("IP_Address is : " + IP_Address) @@ -100,10 +100,10 @@ def checkupdate(): # some way to convert a domain type: Domoticz to google def AogGetDomain(device): if device["Type"] in ['Light/Switch', 'Lighting 1', 'Lighting 2', 'Lighting 5', 'RFY', 'Value']: - if device["SwitchType"] in ['Blinds', 'Venetian Blinds EU', 'Venetian Blinds US', - 'Blinds Percentage', 'Blinds + Stop']: + if device["SwitchType"] in ['Blinds', 'Blinds + Stop', 'Venetian Blinds EU', 'Venetian Blinds US', + 'Blinds Percentage']: return domains['blinds'] - elif device["SwitchType"] in ['Blinds Inverted', 'Blinds Percentage Inverted', 'Blinds Inverted + Stop']: + elif device["SwitchType"] in ['Blinds Inverted', 'Blinds Percentage Inverted', 'Blinds Inverted + Stop']: return domains['blindsinv'] elif 'Door Lock' == device["SwitchType"]: return domains['lock'] @@ -145,8 +145,10 @@ def AogGetDomain(device): return domains['group'] elif 'Scene' == device["Type"]: return domains['scene'] - elif device["Type"] in ['Temp', 'Temp + Humidity', 'Temp + Humidity + Baro']: + elif device["Type"] in ['Temp']: return domains['temperature'] + elif device["Type"] in ['Temp + Humidity', 'Temp + Humidity + Baro']: + return domains['tempHumidity'] elif 'Thermostat' == device['Type']: return domains['thermostat'] elif 'Color Switch' == device["Type"]: @@ -539,7 +541,7 @@ def query_serialize(self): attrs = {'online': True} for trt in self.traits(): deep_update(attrs, trt.query_attributes()) - + return attrs def execute(self, command, params, challenge): @@ -580,7 +582,6 @@ def execute(self, command, params, challenge): raise SmartHomeErrorNoChallenge(ERR_CHALLENGE_NEEDED, 'challengeFailedPinNeeded', 'Unable to execute {} for {} - challenge needed '.format( command, self.state.entity_id)) - if acknowledge: if challenge is None: raise SmartHomeErrorNoChallenge(ERR_CHALLENGE_NEEDED, 'ackNeeded', @@ -606,6 +607,7 @@ def async_update(self): getDevices('scene') else: getDevices('id', self.state.id) + class SmartHomeReqHandler(OAuthReqHandler): global smarthomeControlMappings @@ -640,6 +642,8 @@ def smarthome_process(self, message, token): } handler = smarthomeControlMappings.get(inputs[0].get('intent')) + + logger.info("Google Assistant requests an " + inputs[0].get('intent')) if handler is None: return {'requestId': request_id, 'payload': {'errorCode': ERR_PROTOCOL_ERROR}} @@ -671,8 +675,8 @@ def smarthome_post(self, s): message = json.loads(s.body) self._request_id = message.get('requestId') - - logger.info("Request " + json.dumps(message, indent=2, sort_keys=True, ensure_ascii=False)) + + logger.info(json.dumps(message, indent=2, sort_keys=True, ensure_ascii=False)) response = self.smarthome_process(message, token) try: @@ -680,6 +684,7 @@ def smarthome_post(self, s): logger.error('Error handling message %s: %s' % (message, response['payload'])) except: pass + s.send_json(200, json.dumps(response, ensure_ascii=False).encode('utf-8'), True) def smarthome(self, s): @@ -872,7 +877,6 @@ def smarthome_sync(self, payload, token): aogDevs.clear() getDevices() # sync all devices getSettings() - enableReport = ReportState.enable_report_state() agent_user_id = token.get('userAgentId', None) for state in aogDevs.values(): @@ -892,15 +896,14 @@ def smarthome_sync(self, payload, token): def smarthome_query(self, payload, token): """Handle action.devices.QUERY request. https://developers.google.com/actions/smarthome/create-app#actiondevicesquery - """ - enableReport = ReportState.enable_report_state() + """ response = {} devices = {} - getDevices() + #getDevices() for device in payload.get('devices', []): devid = device['id'] - #_GoogleEntity(aogDevs.get(devid, None)).async_update() + _GoogleEntity(aogDevs.get(devid, None)).async_update() state = aogDevs.get(devid, None) if not state: # If we can't find a state, the device is offline @@ -915,24 +918,24 @@ def smarthome_query(self, payload, token): devices[devid] = {"online": False} response = {'devices': devices} - logger.info("Response " + json.dumps(response, indent=2, sort_keys=True, ensure_ascii=False)) - - if state.report_state == True and enableReport == True: - self.report_state(devices, token) - + logger.info(json.dumps(response, indent=2, sort_keys=True, ensure_ascii=False)) + return {'devices': devices} def smarthome_exec(self, payload, token): """Handle action.devices.EXECUTE request. https://developers.google.com/actions/smarthome/create-app#actiondevicesexecute """ + enableReport = ReportState.enable_report_state() entities = {} results = {} + devices = {} for command in payload['commands']: for device, execution in product(command['devices'], command['execution']): entity_id = device['id'] + # Happens if error occurred. Skip entity for further processing if entity_id in results: continue @@ -943,10 +946,11 @@ def smarthome_exec(self, payload, token): getSettings() state = aogDevs.get(entity_id, None) + if state is None: results[entity_id] = {'ids': [entity_id], 'status': 'ERROR', 'errorCode': ERR_DEVICE_OFFLINE} continue - + entities[entity_id] = _GoogleEntity(state) try: @@ -961,13 +965,23 @@ def smarthome_exec(self, payload, token): 'challengeNeeded': {'type': err.desc}} logger.error(err) + if state.report_state == True and enableReport == True: + devices[entity_id] = execution.get('params', {}) + devices[entity_id].update({'online': True}) + if 'followUpToken' in devices[entity_id]: + devices[entity_id].pop('followUpToken') + self.report_state(devices, token) + final_results = list(results.values()) + for entity in entities.values(): if entity.entity_id in results: continue entity.async_update() - final_results.append({'ids': [entity.entity_id], 'status': 'SUCCESS', 'states': entity.query_serialize()}) - + newState = entity.query_serialize() + newState.update(execution.get('params', {})) + final_results.append({'ids': [entity.entity_id], 'status': 'SUCCESS', 'states': newState}) + return {'commands': final_results} def smarthome_disconnect(self, payload, token): From 30e4c2abb1389bd8f55af28cdf10cf89d3ec80b4 Mon Sep 17 00:00:00 2001 From: DewGew Date: Fri, 25 Nov 2022 12:46:14 +0100 Subject: [PATCH 18/61] Update trait.py --- trait.py | 102 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 88 insertions(+), 14 deletions(-) diff --git a/trait.py b/trait.py index 8963ffb..d322fa3 100644 --- a/trait.py +++ b/trait.py @@ -33,6 +33,7 @@ TRAIT_TOGGLES = PREFIX_TRAITS + 'Toggles' TRAIT_TIMER = PREFIX_TRAITS + 'Timer' TRAIT_ENERGY = PREFIX_TRAITS + 'EnergyStorage' +TRAIT_HUMIDITY = PREFIX_TRAITS + 'HumiditySetting' PREFIX_COMMANDS = 'action.devices.commands.' COMMAND_ONOFF = PREFIX_COMMANDS + 'OnOff' @@ -61,6 +62,8 @@ COMMAND_TIMER_START = PREFIX_COMMANDS + 'TimerStart' COMMAND_TIMER_CANCEL = PREFIX_COMMANDS + 'TimerCancel' COMMAND_CHARGE = PREFIX_COMMANDS + 'Charge' +COMMAND_SET_HUMIDITY = PREFIX_COMMANDS + 'SetHumidity' +COMMAND_SET_HUMIDITY_RELATIVE = PREFIX_COMMANDS + 'HumidityRelative' TRAITS = [] @@ -333,7 +336,13 @@ def supported(domain, features): def sync_attributes(self): """Return OpenClose attributes for a sync request.""" - return {} + features = self.state.attributes + response = {} + + if features & ATTRS_PERCENTAGE != True: + response['discreteOnlyOpenClose'] = True + + return response def query_attributes(self): """Return OpenClose query attributes.""" @@ -371,7 +380,7 @@ def execute(self, command, params): domain = self.state.domain if features & ATTRS_PERCENTAGE: - if domain == domains['blindsinv']: + if domain == domains['blinds']: url = DOMOTICZ_URL + '/json.htm?type=command¶m=switchlight&idx=' + self.state.id + '&switchcmd=Set%20Level&level=' + str( params['openPercent']) else: @@ -405,6 +414,7 @@ def execute(self, command, params): 'Unable to execute {} for {}. Already in state '.format(command, self.state.entity_id)) + if protected: url = url + '&passcode=' + configuration['Domoticz']['switchProtectionPass'] @@ -585,20 +595,23 @@ def supported(domain, features): if domain == domains['thermostat']: return features & ATTRS_THERMSTATSETPOINT else: - return domain in domains['temperature'] + return domain in [domains['temperature'], domains['tempHumidity']] def sync_attributes(self): """Return temperature point and modes attributes for a sync request.""" domain = self.state.domain units = self.state.tempunit response = {"thermostatTemperatureUnit": _google_temp_unit(units)} - # response["thermostatTemperatureRange"] = { + # response["thermostatTemperatureRange"] = { # 'minThresholdCelsius': -20, # 'maxThresholdCelsius': 40} - if domain == domains['temperature']: + if domain in [domains['temperature'], domains['tempHumidity']]: response["queryOnlyTemperatureSetting"] = True - response["availableThermostatModes"] = 'heat' + response["availableThermostatModes"] = 'off' + response["thermostatTemperatureRange"] = { + 'minThresholdCelsius': -2, + 'maxThresholdCelsius': 2} if domain == domains['thermostat']: if self.state.modes_idx is not None: @@ -616,16 +629,17 @@ def query_attributes(self): if self.state.battery <= configuration['Low_battery_limit']: response['exceptionCode'] = 'lowBattery' - if domain == domains['temperature']: + if domain in [domains['temperature'], domains['tempHumidity']]: + response['activeThermostatMode'] = 'none' response['thermostatMode'] = 'heat' + current_temp = float(self.state.temp) if current_temp is not None: - test_temp = round(tempConvert(current_temp, _google_temp_unit(units)), 1) + if round(tempConvert(current_temp, _google_temp_unit(units)), 1) <= 5: + response['thermostatMode'] = 'cool' response['thermostatTemperatureAmbient'] = round(tempConvert(current_temp, _google_temp_unit(units)), 1) response['thermostatTemperatureSetpoint'] = round(tempConvert(current_temp, _google_temp_unit(units)), 1) current_humidity = self.state.humidity - if current_humidity is not None: - response['thermostatHumidityAmbient'] = current_humidity if domain == domains['thermostat']: if self.state.modes_idx is not None: @@ -704,13 +718,14 @@ def sync_attributes(self): domain = self.state.domain units = self.state.tempunit response = {} + response = {"temperatureUnitForUX": _google_temp_unit(units)} + if self.state.merge_thermo_idx is not None: - response = {"temperatureUnitForUX": _google_temp_unit(units)} response = {"temperatureStepCelsius": 1} response["temperatureRange"] = { 'minThresholdCelsius': 30, 'maxThresholdCelsius': 300} - + return response def query_attributes(self): @@ -718,6 +733,7 @@ def query_attributes(self): domain = self.state.domain units = self.state.tempunit response = {} + if self.state.merge_thermo_idx is not None: if self.state.battery <= configuration['Low_battery_limit']: response['exceptionCode'] = 'lowBattery' @@ -1228,7 +1244,7 @@ def sync_attributes(self): """Return EnergyStorge attributes for a sync request.""" battery = self.state.battery response = {} - if battery is not None or battery is not 255: + if battery is not None or battery != 255: response['queryOnlyEnergyStorage'] = True return response @@ -1237,7 +1253,7 @@ def query_attributes(self): """Return EnergyStorge query attributes.""" battery = self.state.battery response = {} - if battery is not None or battery is not 255: + if battery is not None or battery != 255: if battery <= 99: response['capacityRemaining'] = [{ 'unit': 'PERCENTAGE', @@ -1268,3 +1284,61 @@ def execute(self, command, params): # raise SmartHomeError(ERR_WRONG_PIN, # 'Unable to execute {} for {} check your settings'.format(command, # self.state.entity_id)) + +@register_trait +class HumiditySettingTrait(_Trait): + """Trait to offer scene functionality. + https://developers.google.com/actions/smarthome/traits/scene + """ + + name = TRAIT_HUMIDITY + commands = [ + COMMAND_SET_HUMIDITY, + COMMAND_SET_HUMIDITY_RELATIVE + ] + + @staticmethod + def supported(domain, features): + """Test if state is supported.""" + return domain in domains['tempHumidity'] + + def sync_attributes(self): + """Return humidity attributes for a sync request.""" + # Neither supported domain can support sceneReversible + response = {} + response["humiditySetpointRange"] = { + "minPercent": 25, + "maxPercent": 75 + } + response['queryOnlyHumiditySetting'] = True + + return response + + def query_attributes(self): + """Return humidity query attributes.""" + current_humidity = self.state.humidity + response = {} + response['humidityAmbientPercent'] = current_humidity + #response['humiditySetpointPercent'] = current_humidity + + return response + + def execute(self, command, params): + """Execute a humidity command.""" + # domain = self.state.domain + # protected = self.state.protected + + # if domain == domains['humidity']: + # url = DOMOTICZ_URL + '/json.htm?type=command¶m=switchscene&idx=' + self.state.id + '&switchcmd=On' + + # if protected: + # url = url + '&passcode=' + configuration['Domoticz']['switchProtectionPass'] + + # r = requests.get(url, auth=CREDITS) + # if protected: + # status = r.json() + # err = status.get('status') + # if err == 'ERROR': + # raise SmartHomeError(ERR_WRONG_PIN, + # 'Unable to execute {} for {} check your settings'.format(command, + # self.state.entity_id)) From 3952f77cf2923f9577cf7ab6c081f107f9f88ede Mon Sep 17 00:00:00 2001 From: DewGew Date: Fri, 25 Nov 2022 12:48:05 +0100 Subject: [PATCH 19/61] Update const.py --- const.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/const.py b/const.py index 8a91a84..22dc223 100644 --- a/const.py +++ b/const.py @@ -22,6 +22,7 @@ DOMOTICZ_GET_SETTINGS_URL = '/json.htm?type=settings' DOMOTICZ_GET_CAMERAS_URL = '/json.htm?type=cameras' DOMOTICZ_GET_VERSION = '/json.htm?type=command¶m=getversion' +DOMOTICZ_SEND_COMMAND = 'json.htm?type=command¶m=' # https://developers.google.com/actions/smarthome/guides/ PREFIX_TYPES = 'action.devices.types.' @@ -114,6 +115,7 @@ 'speaker': 'Speaker', 'switch': 'Switch', 'temperature': 'Temperature', + 'tempHumidity': 'TempHumidity', 'thermostat': 'Thermostat', 'valve': 'Valve', 'vacuum': 'Vacuum', @@ -172,4 +174,5 @@ domains['washer']: TYPE_WASHER, domains['waterheater']: TYPE_WATERHEATER, domains['window']: TYPE_WINDOW, + domains['tempHumidity']:TYPE_SENSOR, } From b7804cf052bc7107b6a533a188d7fd733a17ef60 Mon Sep 17 00:00:00 2001 From: DewGew Date: Fri, 25 Nov 2022 12:57:43 +0100 Subject: [PATCH 20/61] Update const.py --- const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const.py b/const.py index 22dc223..3da7603 100644 --- a/const.py +++ b/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Constants for Google Assistant.""" -VERSION = '1.11.10' +VERSION = '1.22.11' PUBLIC_URL = 'https://[your public url]' CONFIGFILE = 'config/config.yaml' LOGFILE = 'dzga.log' From eccb2b0acd56ee0f88ad449b9b7f50925e69de0d Mon Sep 17 00:00:00 2001 From: DewGew Date: Sat, 26 Nov 2022 11:58:39 +0100 Subject: [PATCH 21/61] Update trait.py --- trait.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/trait.py b/trait.py index d322fa3..87f9aed 100644 --- a/trait.py +++ b/trait.py @@ -602,16 +602,13 @@ def sync_attributes(self): domain = self.state.domain units = self.state.tempunit response = {"thermostatTemperatureUnit": _google_temp_unit(units)} - # response["thermostatTemperatureRange"] = { - # 'minThresholdCelsius': -20, - # 'maxThresholdCelsius': 40} + response["thermostatTemperatureRange"] = { + 'minThresholdCelsius': -20, + 'maxThresholdCelsius': 40} if domain in [domains['temperature'], domains['tempHumidity']]: response["queryOnlyTemperatureSetting"] = True - response["availableThermostatModes"] = 'off' - response["thermostatTemperatureRange"] = { - 'minThresholdCelsius': -2, - 'maxThresholdCelsius': 2} + response["availableThermostatModes"] = 'heat, cool' if domain == domains['thermostat']: if self.state.modes_idx is not None: @@ -629,17 +626,16 @@ def query_attributes(self): if self.state.battery <= configuration['Low_battery_limit']: response['exceptionCode'] = 'lowBattery' - if domain in [domains['temperature'], domains['tempHumidity']]: - response['activeThermostatMode'] = 'none' - response['thermostatMode'] = 'heat' - + if domain in [domains['temperature'], domains['tempHumidity']]: current_temp = float(self.state.temp) if current_temp is not None: - if round(tempConvert(current_temp, _google_temp_unit(units)), 1) <= 5: + if round(tempConvert(current_temp, _google_temp_unit(units)),1) <= 3: response['thermostatMode'] = 'cool' + else: + response['thermostatMode'] = 'heat' response['thermostatTemperatureAmbient'] = round(tempConvert(current_temp, _google_temp_unit(units)), 1) response['thermostatTemperatureSetpoint'] = round(tempConvert(current_temp, _google_temp_unit(units)), 1) - current_humidity = self.state.humidity + #current_humidity = self.state.humidity if domain == domains['thermostat']: if self.state.modes_idx is not None: From 32d031185442da08359e15616cf30cd0916d615e Mon Sep 17 00:00:00 2001 From: DewGew Date: Sat, 26 Nov 2022 14:09:29 +0100 Subject: [PATCH 22/61] Update const.py --- const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const.py b/const.py index 3da7603..9391ea1 100644 --- a/const.py +++ b/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Constants for Google Assistant.""" -VERSION = '1.22.11' +VERSION = '1.22.12' PUBLIC_URL = 'https://[your public url]' CONFIGFILE = 'config/config.yaml' LOGFILE = 'dzga.log' From 04a0ebff5e261c3f2f9006459af15a8ed160eb35 Mon Sep 17 00:00:00 2001 From: DewGew Date: Tue, 6 Dec 2022 12:59:42 +0100 Subject: [PATCH 23/61] Add Doorbell type --- const.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/const.py b/const.py index 9391ea1..e0741fb 100644 --- a/const.py +++ b/const.py @@ -35,6 +35,7 @@ TYPE_CURTAIN = PREFIX_TYPES + 'CURTAIN' TYPE_DISHWASHER = PREFIX_TYPES + 'DISHWASHER' TYPE_DOOR = PREFIX_TYPES + 'DOOR' +TYPE_DOORBELL = PREFIX_TYPES + 'DOORBELL' TYPE_DRYER = PREFIX_TYPES + 'DRYER' TYPE_FAN = PREFIX_TYPES + 'FAN' TYPE_GARAGE = PREFIX_TYPES + 'GARAGE' @@ -86,6 +87,7 @@ 'color': 'ColorSwitch', 'cooktop': 'Cooktop', 'door': 'DoorSensor', + 'doorbell': 'Doorbell', 'dishwasher': 'Dishwasher', 'dryer': 'Dryer', 'fan': 'Fan', @@ -143,6 +145,7 @@ domains['cooktop']: TYPE_COOKTOP, domains['dishwasher']: TYPE_DISHWASHER, domains['door']: TYPE_DOOR, + domains['doorbell']: TYPE_DOORBELL, domains['dryer']: TYPE_DRYER, domains['fan']: TYPE_FAN, domains['garage']: TYPE_GARAGE, From 01aed4d57f41e1d2e3379e005ee88862374ae95f Mon Sep 17 00:00:00 2001 From: DewGew Date: Tue, 6 Dec 2022 13:14:32 +0100 Subject: [PATCH 24/61] Add Doorbell type Fix merged temp/humidity --- smarthome.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/smarthome.py b/smarthome.py index 6c0e1bc..0e70f4e 100644 --- a/smarthome.py +++ b/smarthome.py @@ -254,7 +254,7 @@ def getAog(device): if dt.lower() in ['window', 'gate', 'garage', 'door']: aog.domain = domains[dt.lower()] if aog.domain in [domains['light'], domains['switch']]: - if dt.lower() in ['window', 'door', 'gate', 'garage', 'light', 'ac_unit', 'bathtub', 'coffeemaker', 'dishwasher', 'dryer', 'fan', 'heater', 'kettle', 'media', 'microwave', 'outlet', 'oven', 'speaker', 'switch', 'vacuum', 'washer', 'waterheater']: + if dt.lower() in ['window', 'door', 'doorbell', 'gate', 'garage', 'light', 'ac_unit', 'bathtub', 'coffeemaker', 'dishwasher', 'dryer', 'fan', 'heater', 'kettle', 'media', 'microwave', 'outlet', 'oven', 'speaker', 'switch', 'vacuum', 'washer', 'waterheater']: aog.domain = domains[dt.lower()] if aog.domain in [domains['door']]: if dt.lower() in ['window', 'gate', 'garage']: @@ -262,6 +262,9 @@ def getAog(device): if aog.domain in [domains['selector']]: if dt.lower() in ['vacuum']: aog.domain = domains[dt.lower()] + if aog.domain in [domains['camera']]: + if dt.lower() in ['doorbell']: + aog.domain = domains[dt.lower()] pn = desc.get('name', None) if pn is not None: aog.name = pn @@ -280,12 +283,21 @@ def getAog(device): if not report_state: aog.report_state = report_state if domains['thermostat'] == aog.domain: + minT = desc.get('minThreehold', None) + if minT is not None: + aog.minThreehold = minT + maxT = desc.get('maxThreehold', None) + if maxT is not None: + aog.maxThreehold = maxT at_idx = desc.get('actual_temp_idx', None) if at_idx is not None: aog.actual_temp_idx = at_idx try: aog.state = str(aogDevs[domains['temperature'] + at_idx].temp) aogDevs[domains['temperature'] + at_idx].domain = domains['merged'] + aog.id + ')' + except Exception: + aog.state = str(aogDevs[domains['tempHumidity'] + at_idx].temp) + aogDevs[domains['temperature'] + at_idx].domain = domains['merged'] + aog.id + ')' except: logger.error('Merge Error, Cant find temperature device with idx %s', at_idx) modes_idx = desc.get('selector_modes_idx', None) @@ -526,6 +538,9 @@ def sync_serialize(self, agent_user_id): room = state.room if room: device['roomHint'] = room + + if state.domain == 'Doorbell': + device['traits'].append('action.devices.traits.ObjectDetection') return device From b207b7b2ff04f82ea2c4eb7430d0525cf094e64d Mon Sep 17 00:00:00 2001 From: DewGew Date: Tue, 6 Dec 2022 13:15:54 +0100 Subject: [PATCH 25/61] Update helpers.py --- helpers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/helpers.py b/helpers.py index ec15beb..b4334ef 100644 --- a/helpers.py +++ b/helpers.py @@ -135,7 +135,7 @@ def saveFile(filename, text): 'uid': '2345', 'accessToken': 'bfrrLnxxWdULSh3Y9IU2cA5pw8s4ub', 'refreshToken': 'bfrrLnxxWdULSh3Y9IU2cA5pw8s4ub', - 'userId': '2345' + 'userAgentId': '2345' }, }, 'users': { @@ -206,6 +206,8 @@ def __init__(self): self.lastupdate = '' self.selectorLevelName = '' self.merge_thermo_idx = None + self.minThreehold = -20 + self.maxThreehold = 40 def uptime(): From 63ed1099cd15d156d307ed05e56ea877b605e4a9 Mon Sep 17 00:00:00 2001 From: DewGew Date: Tue, 6 Dec 2022 13:24:12 +0100 Subject: [PATCH 26/61] Update trait.py --- trait.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/trait.py b/trait.py index 87f9aed..0dc491f 100644 --- a/trait.py +++ b/trait.py @@ -34,6 +34,7 @@ TRAIT_TIMER = PREFIX_TRAITS + 'Timer' TRAIT_ENERGY = PREFIX_TRAITS + 'EnergyStorage' TRAIT_HUMIDITY = PREFIX_TRAITS + 'HumiditySetting' +TRAIT_OBJECTDETECTION = PREFIX_TRAITS + 'ObjectDetection' PREFIX_COMMANDS = 'action.devices.commands.' COMMAND_ONOFF = PREFIX_COMMANDS + 'OnOff' @@ -131,6 +132,7 @@ def supported(domain, features): domains['color'], domains['cooktop'], domains['dishwasher'], + domains['doorbell'], domains['dryer'], domains['fan'], domains['group'], @@ -156,7 +158,7 @@ def sync_attributes(self): """Return OnOff attributes for a sync request.""" domain = self.state.domain response = {} - if domain in [domains['sensor'], domains['smokedetector']]: + if domain in [domains['sensor'], domains['smokedetector'], domains['doorbell']]: response['queryOnlyOnOff'] = True return response @@ -601,10 +603,13 @@ def sync_attributes(self): """Return temperature point and modes attributes for a sync request.""" domain = self.state.domain units = self.state.tempunit + minThree = self.state.minThreehold + maxThree = self.state.maxThreehold + response = {"thermostatTemperatureUnit": _google_temp_unit(units)} response["thermostatTemperatureRange"] = { - 'minThresholdCelsius': -20, - 'maxThresholdCelsius': 40} + 'minThresholdCelsius': minThree, + 'maxThresholdCelsius': maxThree} if domain in [domains['temperature'], domains['tempHumidity']]: response["queryOnlyTemperatureSetting"] = True @@ -635,7 +640,7 @@ def query_attributes(self): response['thermostatMode'] = 'heat' response['thermostatTemperatureAmbient'] = round(tempConvert(current_temp, _google_temp_unit(units)), 1) response['thermostatTemperatureSetpoint'] = round(tempConvert(current_temp, _google_temp_unit(units)), 1) - #current_humidity = self.state.humidity + #current_humidity = self.state.humidity if domain == domains['thermostat']: if self.state.modes_idx is not None: @@ -657,6 +662,7 @@ def query_attributes(self): def execute(self, command, params): """Execute a temperature point or mode command.""" # All sent in temperatures are always in Celsius + if command == COMMAND_THERMOSTAT_SET_MODE: if self.state.modes_idx is not None: levels = base64.b64decode(self.state.selectorLevelName).decode('UTF-8').split("|") @@ -713,14 +719,16 @@ def sync_attributes(self): """Return temperature point attributes for a sync request.""" domain = self.state.domain units = self.state.tempunit + minThree = -100 + maxThree = 100 response = {} response = {"temperatureUnitForUX": _google_temp_unit(units)} + response["temperatureRange"] = { + 'minThresholdCelsius': minThree, + 'maxThresholdCelsius': maxThree} if self.state.merge_thermo_idx is not None: response = {"temperatureStepCelsius": 1} - response["temperatureRange"] = { - 'minThresholdCelsius': 30, - 'maxThresholdCelsius': 300} return response @@ -1045,7 +1053,7 @@ def execute(self, command, params): self._execute_volume_relative(params) else: raise SmartHomeError(ERR_NOT_SUPPORTED, - 'Unable to execute {} for {} '.format(command, self.state.entity_id)) + 'Unable to execute {} for {} '.format(command, self.state.entity_id)) @register_trait @@ -1064,7 +1072,10 @@ class CameraStreamTrait(_Trait): @staticmethod def supported(domain, features): """Test if state is supported.""" - return domain in domains['camera'] + if configuration['Camera_Stream']['Enabled']: + return domain in [domains['camera'], domains['doorbell']] + + return False def sync_attributes(self): """Return stream attributes for a sync request.""" From 6a81274b62b0bc583962c8c5e344435f373806f1 Mon Sep 17 00:00:00 2001 From: DewGew Date: Tue, 6 Dec 2022 13:25:17 +0100 Subject: [PATCH 27/61] Update const.py --- const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const.py b/const.py index e0741fb..b49a685 100644 --- a/const.py +++ b/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Constants for Google Assistant.""" -VERSION = '1.22.12' +VERSION = '1.22.13' PUBLIC_URL = 'https://[your public url]' CONFIGFILE = 'config/config.yaml' LOGFILE = 'dzga.log' From c6c152bf9c86c03913a5b41423c2fd13f726ec54 Mon Sep 17 00:00:00 2001 From: DewGew Date: Tue, 6 Dec 2022 16:00:12 +0100 Subject: [PATCH 28/61] Update smarthome.py --- smarthome.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/smarthome.py b/smarthome.py index 0e70f4e..65673b0 100644 --- a/smarthome.py +++ b/smarthome.py @@ -295,9 +295,6 @@ def getAog(device): try: aog.state = str(aogDevs[domains['temperature'] + at_idx].temp) aogDevs[domains['temperature'] + at_idx].domain = domains['merged'] + aog.id + ')' - except Exception: - aog.state = str(aogDevs[domains['tempHumidity'] + at_idx].temp) - aogDevs[domains['temperature'] + at_idx].domain = domains['merged'] + aog.id + ')' except: logger.error('Merge Error, Cant find temperature device with idx %s', at_idx) modes_idx = desc.get('selector_modes_idx', None) From a214e67d12918a9cdff780448add84dfcd08383a Mon Sep 17 00:00:00 2001 From: DewGew Date: Tue, 6 Dec 2022 16:01:23 +0100 Subject: [PATCH 29/61] Revert merge temp/humidy --- const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const.py b/const.py index b49a685..a47fd95 100644 --- a/const.py +++ b/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Constants for Google Assistant.""" -VERSION = '1.22.13' +VERSION = '1.22.14' PUBLIC_URL = 'https://[your public url]' CONFIGFILE = 'config/config.yaml' LOGFILE = 'dzga.log' From edc3a100162a73a8c2cd80baa50992a2157e2cda Mon Sep 17 00:00:00 2001 From: DewGew Date: Tue, 6 Dec 2022 17:22:04 +0100 Subject: [PATCH 30/61] Update smarthome.py --- smarthome.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smarthome.py b/smarthome.py index 65673b0..7576e4b 100644 --- a/smarthome.py +++ b/smarthome.py @@ -323,7 +323,7 @@ def getAog(device): else: logger.error('Scenes and Groups does not support function "hide" yet') - if aog.domain in [domains['camera']]: + if aog.domain in [domains['camera'], domains['doorbell']]: aog.report_state = False if domains['light'] == aog.domain and "Dimmer" == device["SwitchType"]: From 5bd8a3b3535591d13e35c5cc30822e369efd8cda Mon Sep 17 00:00:00 2001 From: DewGew Date: Wed, 7 Dec 2022 08:48:24 +0100 Subject: [PATCH 31/61] Update const.py --- const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const.py b/const.py index a47fd95..f8a88a9 100644 --- a/const.py +++ b/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Constants for Google Assistant.""" -VERSION = '1.22.14' +VERSION = '1.22.15' PUBLIC_URL = 'https://[your public url]' CONFIGFILE = 'config/config.yaml' LOGFILE = 'dzga.log' From b576d6717ae563fe840a9389ec8985ac638d9d14 Mon Sep 17 00:00:00 2001 From: DewGew Date: Wed, 7 Dec 2022 09:23:39 +0100 Subject: [PATCH 32/61] Rewrite tempHumidity --- const.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/const.py b/const.py index f8a88a9..0effcfc 100644 --- a/const.py +++ b/const.py @@ -117,7 +117,6 @@ 'speaker': 'Speaker', 'switch': 'Switch', 'temperature': 'Temperature', - 'tempHumidity': 'TempHumidity', 'thermostat': 'Thermostat', 'valve': 'Valve', 'vacuum': 'Vacuum', @@ -133,6 +132,7 @@ ATTRS_PERCENTAGE = 1 ATTRS_FANSPEED = 1 ATTRS_VACUUM_MODES = 1 +ATTRS_HUMIDITY = 1 DOMOTICZ_TO_GOOGLE_TYPES = { domains['ac_unit']: TYPE_AC_UNIT, @@ -177,5 +177,4 @@ domains['washer']: TYPE_WASHER, domains['waterheater']: TYPE_WATERHEATER, domains['window']: TYPE_WINDOW, - domains['tempHumidity']:TYPE_SENSOR, } From 752a8905b5f8076308c585a7eb5f541150b6e9a2 Mon Sep 17 00:00:00 2001 From: DewGew Date: Wed, 7 Dec 2022 09:29:15 +0100 Subject: [PATCH 33/61] Rewrite tempHumidity --- smarthome.py | 55 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/smarthome.py b/smarthome.py index 7576e4b..b9fbae0 100644 --- a/smarthome.py +++ b/smarthome.py @@ -14,13 +14,48 @@ import trait from auth import * -from const import (DOMOTICZ_TO_GOOGLE_TYPES, ERR_FUNCTION_NOT_SUPPORTED, ERR_PROTOCOL_ERROR, ERR_DEVICE_OFFLINE, - ERR_UNKNOWN_ERROR, ERR_CHALLENGE_NEEDED, DOMOTICZ_GET_ALL_DEVICES_URL, domains, - DOMOTICZ_GET_SETTINGS_URL, DOMOTICZ_GET_ONE_DEVICE_URL, DOMOTICZ_GET_SCENES_URL, CONFIGFILE, LOGFILE, - REQUEST_SYNC_BASE_URL, REPORT_STATE_BASE_URL, ATTRS_BRIGHTNESS, ATTRS_FANSPEED, ATTRS_VACUUM_MODES, - ATTRS_THERMSTATSETPOINT, ATTRS_COLOR_TEMP, ATTRS_PERCENTAGE, VERSION, DOMOTICZ_GET_VERSION) -from helpers import (configuration, readFile, saveFile, SmartHomeError, SmartHomeErrorNoChallenge, AogState, uptime, - getTunnelUrl, FILE_DIR, logger, ReportState, Auth, logfilepath) +from const import ( + DOMOTICZ_TO_GOOGLE_TYPES, + ERR_FUNCTION_NOT_SUPPORTED, + ERR_PROTOCOL_ERROR, + ERR_DEVICE_OFFLINE, + ERR_UNKNOWN_ERROR, + ERR_CHALLENGE_NEEDED, + DOMOTICZ_GET_ALL_DEVICES_URL, + domains, + DOMOTICZ_GET_SETTINGS_URL, + DOMOTICZ_GET_ONE_DEVICE_URL, + DOMOTICZ_GET_SCENES_URL, + CONFIGFILE, + LOGFILE, + REQUEST_SYNC_BASE_URL, + REPORT_STATE_BASE_URL, + ATTRS_BRIGHTNESS, + ATTRS_FANSPEED, + ATTRS_VACUUM_MODES, + ATTRS_THERMSTATSETPOINT, + ATTRS_COLOR_TEMP, + ATTRS_PERCENTAGE, + ATTRS_HUMIDITY, + VERSION, + DOMOTICZ_GET_VERSION +) + +from helpers import ( + configuration, + readFile, + saveFile, + SmartHomeError, + SmartHomeErrorNoChallenge, + AogState, + uptime, + getTunnelUrl, + FILE_DIR, + logger, + ReportState, + Auth, + logfilepath +) from jinja2 import Environment, FileSystemLoader if 'Chromecast_Name' in configuration and configuration['Chromecast_Name'] != 'add_chromecast_name': @@ -145,10 +180,8 @@ def AogGetDomain(device): return domains['group'] elif 'Scene' == device["Type"]: return domains['scene'] - elif device["Type"] in ['Temp']: + elif device["Type"] in ['Temp', 'Temp + Humidity', 'Temp + Humidity + Baro']: return domains['temperature'] - elif device["Type"] in ['Temp + Humidity', 'Temp + Humidity + Baro']: - return domains['tempHumidity'] elif 'Thermostat' == device['Type']: return domains['thermostat'] elif 'Color Switch' == device["Type"]: @@ -344,6 +377,8 @@ def getAog(device): aog.attributes = ATTRS_PERCENTAGE if domains['vacuum'] == aog.domain and "Selector" == device["SwitchType"]: aog.attributes = ATTRS_VACUUM_MODES + if domains['temperature'] == aog.domain and device["Type"] in ['Temp + Humidity', 'Temp + Humidity + Baro']: + aog.attributes = ATTRS_HUMIDITY if aog.room == None: if aog.domain not in [domains['scene'], domains['group']]: From a4468d11212e837303e98808fafd8a22d3a60dc6 Mon Sep 17 00:00:00 2001 From: DewGew Date: Wed, 7 Dec 2022 09:35:13 +0100 Subject: [PATCH 34/61] Rewrite tempHumidty --- trait.py | 125 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 68 insertions(+), 57 deletions(-) diff --git a/trait.py b/trait.py index 0dc491f..d258628 100644 --- a/trait.py +++ b/trait.py @@ -2,69 +2,80 @@ import base64 import json -from datetime import datetime - import requests -from const import (ATTRS_BRIGHTNESS, ATTRS_THERMSTATSETPOINT, ATTRS_COLOR, ATTRS_COLOR_TEMP, ATTRS_PERCENTAGE, - ATTRS_VACUUM_MODES, domains, ERR_ALREADY_IN_STATE, ERR_WRONG_PIN, ERR_NOT_SUPPORTED, - ATTRS_FANSPEED) - +from datetime import datetime from helpers import SmartHomeError, configuration, logger, tempConvert +from const import ( + ATTRS_BRIGHTNESS, + ATTRS_THERMSTATSETPOINT, + ATTRS_COLOR, + ATTRS_COLOR_TEMP, + ATTRS_PERCENTAGE, + ATTRS_VACUUM_MODES, + domains, + ERR_ALREADY_IN_STATE, + ERR_WRONG_PIN, + ERR_NOT_SUPPORTED, + ATTRS_FANSPEED +) DOMOTICZ_URL = configuration['Domoticz']['ip'] + ':' + configuration['Domoticz']['port'] PREFIX_TRAITS = 'action.devices.traits.' -TRAIT_ONOFF = PREFIX_TRAITS + 'OnOff' -TRAIT_DOCK = PREFIX_TRAITS + 'Dock' -TRAIT_STARTSTOP = PREFIX_TRAITS + 'StartStop' -TRAIT_BRIGHTNESS = PREFIX_TRAITS + 'Brightness' -TRAIT_COLOR_SETTING = PREFIX_TRAITS + 'ColorSetting' -TRAIT_SCENE = PREFIX_TRAITS + 'Scene' -TRAIT_TEMPERATURE_CONTROL = PREFIX_TRAITS + 'TemperatureControl' -TRAIT_TEMPERATURE_SETTING = PREFIX_TRAITS + 'TemperatureSetting' -TRAIT_LOCKUNLOCK = PREFIX_TRAITS + 'LockUnlock' -TRAIT_FANSPEED = PREFIX_TRAITS + 'FanSpeed' -TRAIT_MODES = PREFIX_TRAITS + 'Modes' -TRAIT_OPEN_CLOSE = PREFIX_TRAITS + 'OpenClose' -TRAIT_ARM_DISARM = PREFIX_TRAITS + 'ArmDisarm' -TRAIT_VOLUME = PREFIX_TRAITS + 'Volume' -TRAIT_CAMERA_STREAM = PREFIX_TRAITS + 'CameraStream' -TRAIT_TOGGLES = PREFIX_TRAITS + 'Toggles' -TRAIT_TIMER = PREFIX_TRAITS + 'Timer' -TRAIT_ENERGY = PREFIX_TRAITS + 'EnergyStorage' -TRAIT_HUMIDITY = PREFIX_TRAITS + 'HumiditySetting' -TRAIT_OBJECTDETECTION = PREFIX_TRAITS + 'ObjectDetection' +TRAIT_ONOFF = f'{PREFIX_TRAITS}OnOff' +TRAIT_DOCK = f'{PREFIX_TRAITS}Dock' +TRAIT_STARTSTOP = f'{PREFIX_TRAITS}StartStop' +TRAIT_BRIGHTNESS = f'{PREFIX_TRAITS}Brightness' +TRAIT_COLOR_SETTING = f'{PREFIX_TRAITS}ColorSetting' +TRAIT_SCENE = f'{PREFIX_TRAITS}Scene' +TRAIT_TEMPERATURE_CONTROL = f'{PREFIX_TRAITS}TemperatureControl' +TRAIT_TEMPERATURE_SETTING = f'{PREFIX_TRAITS}TemperatureSetting' +TRAIT_LOCKUNLOCK = f'{PREFIX_TRAITS}LockUnlock' +TRAIT_FANSPEED = f'{PREFIX_TRAITS}FanSpeed' +TRAIT_MODES = f'{PREFIX_TRAITS}Modes' +TRAIT_OPEN_CLOSE = f'{PREFIX_TRAITS}OpenClose' +TRAIT_ARM_DISARM = f'{PREFIX_TRAITS}ArmDisarm' +TRAIT_VOLUME = f'{PREFIX_TRAITS}Volume' +TRAIT_CAMERA_STREAM = f'{PREFIX_TRAITS}CameraStream' +TRAIT_TOGGLES = f'{PREFIX_TRAITS}Toggles' +TRAIT_TIMER = f'{PREFIX_TRAITS}Timer' +TRAIT_ENERGY = f'{PREFIX_TRAITS}EnergyStorage' +TRAIT_HUMIDITY = f'{PREFIX_TRAITS}HumiditySetting' +TRAIT_OBJECTDETECTION = f'{PREFIX_TRAITS}ObjectDetection' +TRAIT_SENSOR_STATE = f'{PREFIX_TRAITS}SensorState' PREFIX_COMMANDS = 'action.devices.commands.' -COMMAND_ONOFF = PREFIX_COMMANDS + 'OnOff' -COMMAND_DOCK = PREFIX_COMMANDS + 'Dock' -COMMAND_STARTSTOP = PREFIX_COMMANDS + 'StartStop' -COMMAND_PAUSEUNPAUSE = PREFIX_COMMANDS + 'PauseUnpause' -COMMAND_BRIGHTNESS_ABSOLUTE = PREFIX_COMMANDS + 'BrightnessAbsolute' -COMMAND_COLOR_ABSOLUTE = PREFIX_COMMANDS + 'ColorAbsolute' -COMMAND_ACTIVATE_SCENE = PREFIX_COMMANDS + 'ActivateScene' +COMMAND_ONOFF = f'{PREFIX_COMMANDS}OnOff' +COMMAND_DOCK = f'{PREFIX_COMMANDS}Dock' +COMMAND_STARTSTOP = f'{PREFIX_COMMANDS}StartStop' +COMMAND_PAUSEUNPAUSE = f'{PREFIX_COMMANDS}PauseUnpause' +COMMAND_BRIGHTNESS_ABSOLUTE = f'{PREFIX_COMMANDS}BrightnessAbsolute' +COMMAND_COLOR_ABSOLUTE = f'{PREFIX_COMMANDS}ColorAbsolute' +COMMAND_ACTIVATE_SCENE = f'{PREFIX_COMMANDS}ActivateScene' COMMAND_THERMOSTAT_TEMPERATURE_SETPOINT = ( - PREFIX_COMMANDS + 'ThermostatTemperatureSetpoint') + f'{PREFIX_COMMANDS}ThermostatTemperatureSetpoint' + ) COMMAND_THERMOSTAT_TEMPERATURE_SET_RANGE = ( - PREFIX_COMMANDS + 'ThermostatTemperatureSetRange') -COMMAND_THERMOSTAT_SET_MODE = PREFIX_COMMANDS + 'ThermostatSetMode' -COMMAND_THERMOSTAT_TEMPERATURE_RELATIVE = PREFIX_COMMANDS + 'TemperatureRelative' -COMMAND_SET_TEMPERATURE = PREFIX_COMMANDS + 'SetTemperature' -COMMAND_LOCKUNLOCK = PREFIX_COMMANDS + 'LockUnlock' -COMMAND_FANSPEED = PREFIX_COMMANDS + 'SetFanSpeed' -COMMAND_MODES = PREFIX_COMMANDS + 'SetModes' -COMMAND_OPEN_CLOSE = PREFIX_COMMANDS + 'OpenClose' -COMMAND_ARM_DISARM = PREFIX_COMMANDS + 'ArmDisarm' -COMMAND_SET_VOLUME = PREFIX_COMMANDS + 'setVolume' -COMMAND_VOLUME_RELATIVE = PREFIX_COMMANDS + 'volumeRelative' -COMMAND_GET_CAMERA_STREAM = PREFIX_COMMANDS + 'GetCameraStream' -COMMAND_TOGGLES = PREFIX_COMMANDS + 'SetToggles' -COMMAND_TIMER_START = PREFIX_COMMANDS + 'TimerStart' -COMMAND_TIMER_CANCEL = PREFIX_COMMANDS + 'TimerCancel' -COMMAND_CHARGE = PREFIX_COMMANDS + 'Charge' -COMMAND_SET_HUMIDITY = PREFIX_COMMANDS + 'SetHumidity' -COMMAND_SET_HUMIDITY_RELATIVE = PREFIX_COMMANDS + 'HumidityRelative' + f'{PREFIX_COMMANDS}ThermostatTemperatureSetRange' + ) +COMMAND_THERMOSTAT_SET_MODE = f'{PREFIX_COMMANDS}ThermostatSetMode' +COMMAND_THERMOSTAT_TEMPERATURE_RELATIVE = f'{PREFIX_COMMANDS}TemperatureRelative' +COMMAND_SET_TEMPERATURE = f'{PREFIX_COMMANDS}SetTemperature' +COMMAND_LOCKUNLOCK = f'{PREFIX_COMMANDS}LockUnlock' +COMMAND_FANSPEED = f'{PREFIX_COMMANDS}SetFanSpeed' +COMMAND_MODES = f'{PREFIX_COMMANDS}SetModes' +COMMAND_OPEN_CLOSE = f'{PREFIX_COMMANDS}OpenClose' +COMMAND_ARM_DISARM = f'{PREFIX_COMMANDS}ArmDisarm' +COMMAND_SET_VOLUME = f'{PREFIX_COMMANDS}setVolume' +COMMAND_VOLUME_RELATIVE = f'{PREFIX_COMMANDS}volumeRelative' +COMMAND_GET_CAMERA_STREAM = f'{PREFIX_COMMANDS}GetCameraStream' +COMMAND_TOGGLES = f'{PREFIX_COMMANDS}SetToggles' +COMMAND_TIMER_START = f'{PREFIX_COMMANDS}TimerStart' +COMMAND_TIMER_CANCEL = f'{PREFIX_COMMANDS}TimerCancel' +COMMAND_CHARGE = f'{PREFIX_COMMANDS}Charge' +COMMAND_SET_HUMIDITY = f'{PREFIX_COMMANDS}SetHumidity' +COMMAND_SET_HUMIDITY_RELATIVE = f'{PREFIX_COMMANDS}HumidityRelative' TRAITS = [] @@ -597,7 +608,7 @@ def supported(domain, features): if domain == domains['thermostat']: return features & ATTRS_THERMSTATSETPOINT else: - return domain in [domains['temperature'], domains['tempHumidity']] + return domain in [domains['temperature']] def sync_attributes(self): """Return temperature point and modes attributes for a sync request.""" @@ -611,7 +622,7 @@ def sync_attributes(self): 'minThresholdCelsius': minThree, 'maxThresholdCelsius': maxThree} - if domain in [domains['temperature'], domains['tempHumidity']]: + if domain in [domains['temperature']]: response["queryOnlyTemperatureSetting"] = True response["availableThermostatModes"] = 'heat, cool' @@ -631,7 +642,7 @@ def query_attributes(self): if self.state.battery <= configuration['Low_battery_limit']: response['exceptionCode'] = 'lowBattery' - if domain in [domains['temperature'], domains['tempHumidity']]: + if domain in [domains['temperature']]: current_temp = float(self.state.temp) if current_temp is not None: if round(tempConvert(current_temp, _google_temp_unit(units)),1) <= 3: @@ -1307,11 +1318,11 @@ class HumiditySettingTrait(_Trait): @staticmethod def supported(domain, features): """Test if state is supported.""" - return domain in domains['tempHumidity'] + if domain == domains['temperature']: + return features & ATTRS_THERMSTATSETPOINT def sync_attributes(self): """Return humidity attributes for a sync request.""" - # Neither supported domain can support sceneReversible response = {} response["humiditySetpointRange"] = { "minPercent": 25, From c5d8125fca43bdd6bc4439b5beb78dd28e5f1041 Mon Sep 17 00:00:00 2001 From: DewGew Date: Thu, 8 Dec 2022 16:00:12 +0100 Subject: [PATCH 35/61] Update smarthome.py --- smarthome.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smarthome.py b/smarthome.py index b9fbae0..f119fd3 100644 --- a/smarthome.py +++ b/smarthome.py @@ -433,7 +433,7 @@ def getDevices(devices="all", idx="0"): req[aog.name]['willReportState'] = aog.report_state logger.debug(json.dumps(req, indent=2, sort_keys=False, ensure_ascii=False)) - devlist = [(d.name, int(d.id), d.domain, d.state, d.room, d.nicknames, d.report_state) for d in aogDevs.values()] + devlist = [(d.name, int(d.id), d.domain, d.state, d.room, d.nicknames, d.report_state, d.entity_id) for d in aogDevs.values()] devlist.sort(key=takeSecond) deviceList = json.dumps(devlist) From 6eabb0719f63639af8ec0b111704aa7a3c6c08c7 Mon Sep 17 00:00:00 2001 From: DewGew Date: Thu, 8 Dec 2022 16:01:11 +0100 Subject: [PATCH 36/61] Update functions.js --- templates/js/functions.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/templates/js/functions.js b/templates/js/functions.js index 48c7f0f..8625a23 100644 --- a/templates/js/functions.js +++ b/templates/js/functions.js @@ -124,7 +124,7 @@ function readDevices(devicelist){ if (devicelist[i][5] == undefined) { nicknames = " "; }else{ nicknames = " (" + devicelist[i][5] + ")"} - xl += "" + devicelist[i][1] + "" + xl += "" + devicelist[i][1] + " (" + devicelist[i][7] + ")" xl += "" + devicelist[i][0] + nicknames + ""; xl += "" + devicelist[i][2] + ""; if (devicelist[i][3] == "Off" | devicelist[i][3] == "Closed"){ @@ -269,6 +269,12 @@ $("#ngrok_tunnel").click(function(){ } }); +$(function() { + $("#logout").click(function(e) { + document.cookie = 'aog_session_id=; Max-Age=0; path=/'; + }); + }); + function getIssues(){ $.ajax({ From c15aed05e5a184967534e14a5aebcbac6cc05a81 Mon Sep 17 00:00:00 2001 From: DewGew Date: Thu, 8 Dec 2022 16:01:50 +0100 Subject: [PATCH 37/61] Update devices.html --- templates/devices.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/devices.html b/templates/devices.html index fbe26ae..f701d8b 100644 --- a/templates/devices.html +++ b/templates/devices.html @@ -13,7 +13,7 @@

Device list

- + From aabc1c716151044b8eb52e9f3c8c221962afc172 Mon Sep 17 00:00:00 2001 From: DewGew Date: Fri, 9 Dec 2022 10:42:43 +0100 Subject: [PATCH 38/61] Update trait.py --- trait.py | 172 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 88 insertions(+), 84 deletions(-) diff --git a/trait.py b/trait.py index d258628..5888395 100644 --- a/trait.py +++ b/trait.py @@ -13,7 +13,7 @@ ATTRS_COLOR_TEMP, ATTRS_PERCENTAGE, ATTRS_VACUUM_MODES, - domains, + DOMAINS, ERR_ALREADY_IN_STATE, ERR_WRONG_PIN, ERR_NOT_SUPPORTED, @@ -137,39 +137,39 @@ def supported(domain, features): """Test if state is supported.""" return domain in ( - domains['ac_unit'], - domains['bathtub'], - domains['coffeemaker'], - domains['color'], - domains['cooktop'], - domains['dishwasher'], - domains['doorbell'], - domains['dryer'], - domains['fan'], - domains['group'], - domains['heater'], - domains['kettle'], - domains['light'], - domains['media'], - domains['microwave'], - domains['mower'], - domains['outlet'], - domains['oven'], - domains['push'], - domains['sensor'], - domains['smokedetector'], - domains['speaker'], - domains['switch'], - #domains['vacuum'], - domains['washer'], - domains['waterheater'], + DOMAINS['ac_unit'], + DOMAINS['bathtub'], + DOMAINS['coffeemaker'], + DOMAINS['color'], + DOMAINS['cooktop'], + DOMAINS['dishwasher'], + DOMAINS['doorbell'], + DOMAINS['dryer'], + DOMAINS['fan'], + DOMAINS['group'], + DOMAINS['heater'], + DOMAINS['kettle'], + DOMAINS['light'], + DOMAINS['media'], + DOMAINS['microwave'], + DOMAINS['mower'], + DOMAINS['outlet'], + DOMAINS['oven'], + DOMAINS['push'], + DOMAINS['sensor'], + DOMAINS['smokedetector'], + DOMAINS['speaker'], + DOMAINS['switch'], + #DOMAINS['vacuum'], + DOMAINS['washer'], + DOMAINS['waterheater'], ) def sync_attributes(self): """Return OnOff attributes for a sync request.""" domain = self.state.domain response = {} - if domain in [domains['sensor'], domains['smokedetector'], domains['doorbell']]: + if domain in [DOMAINS['sensor'], DOMAINS['smokedetector'], DOMAINS['doorbell']]: response['queryOnlyOnOff'] = True return response @@ -179,11 +179,11 @@ def query_attributes(self): domain = self.state.domain response = {} - if domain == domains['push']: + if domain == DOMAINS['push']: response['on'] = False else: response['on'] = self.state.state != 'Off' - if domain != domains['group'] and self.state.battery <= configuration['Low_battery_limit']: + if domain != DOMAINS['group'] and self.state.battery <= configuration['Low_battery_limit']: response['exceptionCode'] = 'lowBattery' return response @@ -193,8 +193,8 @@ def execute(self, command, params): domain = self.state.domain protected = self.state.protected - if domain not in [domains['sensor'], domains['smokedetector']]: - if domain == domains['group']: + if domain not in [DOMAINS['sensor'], DOMAINS['smokedetector']]: + if domain == DOMAINS['group']: url = DOMOTICZ_URL + '/json.htm?type=command¶m=switchscene&idx=' + self.state.id + '&switchcmd=' + ( 'On' if params['on'] else 'Off') else: @@ -228,7 +228,7 @@ class SceneTrait(_Trait): @staticmethod def supported(domain, features): """Test if state is supported.""" - return domain in domains['scene'] + return domain in DOMAINS['scene'] def sync_attributes(self): """Return scene attributes for a sync request.""" @@ -272,7 +272,7 @@ class BrightnessTrait(_Trait): @staticmethod def supported(domain, features): """Test if state is supported.""" - if domain in (domains['light'], domains['color'], domains['outlet']): + if domain in (DOMAINS['light'], DOMAINS['color'], DOMAINS['outlet']): return features & ATTRS_BRIGHTNESS return False @@ -338,13 +338,13 @@ class OpenCloseTrait(_Trait): def supported(domain, features): """Test if state is supported.""" return domain in ( - domains['blinds'], - domains['blindsinv'], - domains['door'], - domains['window'], - domains['gate'], - domains['garage'], - domains['valve'] + DOMAINS['blinds'], + DOMAINS['blindsinv'], + DOMAINS['door'], + DOMAINS['window'], + DOMAINS['gate'], + DOMAINS['garage'], + DOMAINS['valve'] ) def sync_attributes(self): @@ -365,11 +365,11 @@ def query_attributes(self): if features & ATTRS_PERCENTAGE: response['openPercent'] = self.state.level - if domain == domains['blindsinv']: + if domain == DOMAINS['blindsinv']: response['openPercent'] = self.state.level else: response['openPercent'] = 100 - self.state.level - elif domain == domains['blindsinv']: + elif domain == DOMAINS['blindsinv']: if self.state.state in ['Open', 'Off']: response['openPercent'] = 0 else: @@ -393,7 +393,7 @@ def execute(self, command, params): domain = self.state.domain if features & ATTRS_PERCENTAGE: - if domain == domains['blinds']: + if domain == DOMAINS['blinds']: url = DOMOTICZ_URL + '/json.htm?type=command¶m=switchlight&idx=' + self.state.id + '&switchcmd=Set%20Level&level=' + str( params['openPercent']) else: @@ -404,7 +404,7 @@ def execute(self, command, params): url = DOMOTICZ_URL + '/json.htm?type=command¶m=switchlight&idx=' + self.state.id + '&switchcmd=' - if domain == domains['blindsinv']: + if domain == DOMAINS['blindsinv']: if p == 0 and state in ['Closed', 'Stopped', 'On']: # open url += 'Open' @@ -452,13 +452,13 @@ class StartStopTrait(_Trait): @staticmethod def supported(domain, features): """Test if state is supported.""" - if domain == domains['blinds']: + if domain == DOMAINS['blinds']: if features & ATTRS_PERCENTAGE: return False else: - return domain in domains['blinds'] + return domain in DOMAINS['blinds'] else: - return domain in domains['vacuum'] + return domain in DOMAINS['vacuum'] def sync_attributes(self): """Return StartStop attributes for a sync request.""" @@ -468,7 +468,7 @@ def query_attributes(self): """Return StartStop query attributes.""" domain = self.state.domain response = {} - if domain == domains['blinds']: + if domain == DOMAINS['blinds']: response['isRunning'] = True else: response['isRunning'] = self.state.state != 'Off' @@ -484,7 +484,7 @@ def execute(self, command, params): protected = self.state.protected if command == COMMAND_STARTSTOP: - if domain == domains['blinds']: + if domain == DOMAINS['blinds']: if params['start'] is False: url = DOMOTICZ_URL + '/json.htm?type=command¶m=switchlight&idx=' + self.state.id + '&switchcmd=Stop' else: @@ -526,7 +526,7 @@ def execute(self, command, params): # def supported(domain, features): # """Test if state is supported.""" # if domain in [ - # domains['fan'] + # DOMAINS['fan'] # ]: # return features & ATTRS_FANSPEED @@ -605,10 +605,10 @@ class TemperatureSettingTrait(_Trait): @staticmethod def supported(domain, features): """Test if state is supported.""" - if domain == domains['thermostat']: + if domain == DOMAINS['thermostat']: return features & ATTRS_THERMSTATSETPOINT else: - return domain in [domains['temperature']] + return domain in [DOMAINS['temperature']] def sync_attributes(self): """Return temperature point and modes attributes for a sync request.""" @@ -622,11 +622,11 @@ def sync_attributes(self): 'minThresholdCelsius': minThree, 'maxThresholdCelsius': maxThree} - if domain in [domains['temperature']]: + if domain in [DOMAINS['temperature']]: response["queryOnlyTemperatureSetting"] = True response["availableThermostatModes"] = 'heat, cool' - if domain == domains['thermostat']: + if domain == DOMAINS['thermostat']: if self.state.modes_idx is not None: response["availableThermostatModes"] = 'off,heat,cool,auto,eco' else: @@ -642,7 +642,7 @@ def query_attributes(self): if self.state.battery <= configuration['Low_battery_limit']: response['exceptionCode'] = 'lowBattery' - if domain in [domains['temperature']]: + if domain in [DOMAINS['temperature']]: current_temp = float(self.state.temp) if current_temp is not None: if round(tempConvert(current_temp, _google_temp_unit(units)),1) <= 3: @@ -651,9 +651,10 @@ def query_attributes(self): response['thermostatMode'] = 'heat' response['thermostatTemperatureAmbient'] = round(tempConvert(current_temp, _google_temp_unit(units)), 1) response['thermostatTemperatureSetpoint'] = round(tempConvert(current_temp, _google_temp_unit(units)), 1) - #current_humidity = self.state.humidity + # if current_humidity is not None: + # response['thermostatHumidityAmbient'] = current_humidity - if domain == domains['thermostat']: + if domain == DOMAINS['thermostat']: if self.state.modes_idx is not None: levelName = base64.b64decode(self.state.selectorLevelName).decode('UTF-8').split("|") level = self.state.level @@ -667,6 +668,9 @@ def query_attributes(self): setpoint = float(self.state.setpoint) if setpoint is not None: response['thermostatTemperatureSetpoint'] = round(tempConvert(setpoint, _google_temp_unit(units)), 1) + if current_humidity is not None: + response['thermostatHumidityAmbient'] = current_humidity + return response @@ -724,7 +728,7 @@ class TemperatureControlTrait(_Trait): @staticmethod def supported(domain, features): """Test if state is supported.""" - return domain in [domains['heater'], domains['kettle'], domains['waterheater'], domains['oven']] + return domain in [DOMAINS['heater'], DOMAINS['kettle'], DOMAINS['waterheater'], DOMAINS['oven']] def sync_attributes(self): """Return temperature point attributes for a sync request.""" @@ -788,8 +792,8 @@ class LockUnlockTrait(_Trait): @staticmethod def supported(domain, features): """Test if state is supported.""" - return domain in (domains['lock'], - domains['lockinv']) + return domain in (DOMAINS['lock'], + DOMAINS['lockinv']) def sync_attributes(self): """Return LockUnlock attributes for a sync request.""" @@ -811,7 +815,7 @@ def execute(self, command, params): state = self.state.state protected = self.state.protected - if domain == domains['lock']: + if domain == DOMAINS['lock']: if params['lock'] == True and state == 'Unlocked': url = DOMOTICZ_URL + '/json.htm?type=command¶m=switchlight&idx=' + self.state.id + '&switchcmd=On' elif params['lock'] == False and state == 'Locked': @@ -859,7 +863,7 @@ class ColorSettingTrait(_Trait): @staticmethod def supported(domain, features): """Test if state is supported.""" - if domain == domains['color']: + if domain == DOMAINS['color']: return (features & ATTRS_COLOR or features & ATTRS_COLOR_TEMP) @@ -932,7 +936,7 @@ class ArmDisarmTrait(_Trait): @staticmethod def supported(domain, features): """Test if state is supported.""" - return domain in domains['security'] + return domain in DOMAINS['security'] def sync_attributes(self): """Return ArmDisarm attributes for a sync request.""" @@ -1024,7 +1028,7 @@ class VolumeTrait(_Trait): @staticmethod def supported(domain, features): """Test if state is supported.""" - return domain in domains['speaker'] + return domain in DOMAINS['speaker'] def sync_attributes(self): """Return volume attributes for a sync request.""" @@ -1084,7 +1088,7 @@ class CameraStreamTrait(_Trait): def supported(domain, features): """Test if state is supported.""" if configuration['Camera_Stream']['Enabled']: - return domain in [domains['camera'], domains['doorbell']] + return domain in [DOMAINS['camera'], DOMAINS['doorbell']] return False @@ -1123,10 +1127,10 @@ class TooglesTrait(_Trait): @staticmethod def supported(domain, features): """Test if state is supported.""" - if domain == domains['vacuum']: + if domain == DOMAINS['vacuum']: return features & ATTRS_VACUUM_MODES else: - return domain in domains['selector'] + return domain in DOMAINS['selector'] def sync_attributes(self): """Return mode attributes for a sync request.""" @@ -1201,12 +1205,12 @@ class Timer(_Trait): @staticmethod def supported(domain, features): """Test if state is supported.""" - return domain in [domains['light'], - domains['color'], - domains['switch'], - domains['heater'], - domains['kettle'], - domains['fan'], + return domain in [DOMAINS['light'], + DOMAINS['color'], + DOMAINS['switch'], + DOMAINS['heater'], + DOMAINS['kettle'], + DOMAINS['fan'], ] def sync_attributes(self): @@ -1249,13 +1253,13 @@ class EnergyStorageTrait(_Trait): def supported(domain, features): """Test if state is supported.""" return domain in ( - domains['vacuum'], - domains['blinds'], - domains['smokedetector'], - domains['sensor'], - domains['mower'], - domains['thermostat'], - domains['temperature'] + DOMAINS['vacuum'], + DOMAINS['blinds'], + DOMAINS['smokedetector'], + DOMAINS['sensor'], + DOMAINS['mower'], + DOMAINS['thermostat'], + DOMAINS['temperature'] ) def sync_attributes(self): @@ -1287,7 +1291,7 @@ def execute(self, command, params): # domain = self.state.domain # protected = self.state.protected - # if domain in (domains['vacuum'], domains['mower']): + # if domain in (DOMAINS['vacuum'], DOMAINS['mower']): # url = DOMOTICZ_URL + '/json.htm?type=command¶m=switchlight&idx=' + self.state.id + '&switchcmd=' + ( # 'On' if params['charge'] else 'Off') @@ -1318,7 +1322,7 @@ class HumiditySettingTrait(_Trait): @staticmethod def supported(domain, features): """Test if state is supported.""" - if domain == domains['temperature']: + if domain == DOMAINS['temperature']: return features & ATTRS_THERMSTATSETPOINT def sync_attributes(self): @@ -1346,7 +1350,7 @@ def execute(self, command, params): # domain = self.state.domain # protected = self.state.protected - # if domain == domains['humidity']: + # if domain == DOMAINS['humidity']: # url = DOMOTICZ_URL + '/json.htm?type=command¶m=switchscene&idx=' + self.state.id + '&switchcmd=On' # if protected: From 059f4d5cfd6d1180c68bffb6ab3b19216b60e78a Mon Sep 17 00:00:00 2001 From: DewGew Date: Fri, 9 Dec 2022 10:43:31 +0100 Subject: [PATCH 39/61] Update smarthome.py --- smarthome.py | 136 +++++++++++++++++++++++++-------------------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/smarthome.py b/smarthome.py index f119fd3..2044270 100644 --- a/smarthome.py +++ b/smarthome.py @@ -22,7 +22,7 @@ ERR_UNKNOWN_ERROR, ERR_CHALLENGE_NEEDED, DOMOTICZ_GET_ALL_DEVICES_URL, - domains, + DOMAINS, DOMOTICZ_GET_SETTINGS_URL, DOMOTICZ_GET_ONE_DEVICE_URL, DOMOTICZ_GET_SCENES_URL, @@ -137,68 +137,68 @@ def AogGetDomain(device): if device["Type"] in ['Light/Switch', 'Lighting 1', 'Lighting 2', 'Lighting 5', 'RFY', 'Value']: if device["SwitchType"] in ['Blinds', 'Blinds + Stop', 'Venetian Blinds EU', 'Venetian Blinds US', 'Blinds Percentage']: - return domains['blinds'] + return DOMAINS['blinds'] elif device["SwitchType"] in ['Blinds Inverted', 'Blinds Percentage Inverted', 'Blinds Inverted + Stop']: - return domains['blindsinv'] + return DOMAINS['blindsinv'] elif 'Door Lock' == device["SwitchType"]: - return domains['lock'] + return DOMAINS['lock'] elif 'Door Lock Inverted' == device["SwitchType"]: - return domains['lockinv'] + return DOMAINS['lockinv'] elif "Door Contact" == device["SwitchType"]: - return domains['door'] + return DOMAINS['door'] elif device["SwitchType"] in ['Push On Button', 'Push Off Button']: - return domains['push'] + return DOMAINS['push'] elif 'Motion Sensor' == device["SwitchType"]: - return domains['sensor'] + return DOMAINS['sensor'] elif 'Selector' == device["SwitchType"]: if device['Image'] == 'Fan': - return domains['fan'] + return DOMAINS['fan'] else: - return domains['selector'] + return DOMAINS['selector'] elif 'Smoke Detector' == device["SwitchType"]: - return domains['smokedetector'] + return DOMAINS['smokedetector'] elif 'Camera_Stream' in configuration and True == device["UsedByCamera"] and True == \ configuration['Camera_Stream']['Enabled']: - return domains['camera'] + return DOMAINS['camera'] elif device["Image"] == 'Generic': - return domains['switch'] + return DOMAINS['switch'] elif device["Image"] in ['Media', 'TV']: - return domains['media'] + return DOMAINS['media'] elif device["Image"] == 'WallSocket': - return domains['outlet'] + return DOMAINS['outlet'] elif device["Image"] == 'Speaker': - return domains['speaker'] + return DOMAINS['speaker'] elif device["Image"] == 'Fan': - return domains['fan'] + return DOMAINS['fan'] elif device["Image"] == 'Heating': - return domains['heater'] + return DOMAINS['heater'] else: - return domains['light'] + return DOMAINS['light'] elif 'Blinds' == device["Type"]: - return domains['blinds'] + return DOMAINS['blinds'] elif 'Group' == device["Type"]: - return domains['group'] + return DOMAINS['group'] elif 'Scene' == device["Type"]: - return domains['scene'] + return DOMAINS['scene'] elif device["Type"] in ['Temp', 'Temp + Humidity', 'Temp + Humidity + Baro']: - return domains['temperature'] + return DOMAINS['temperature'] elif 'Thermostat' == device['Type']: - return domains['thermostat'] + return DOMAINS['thermostat'] elif 'Color Switch' == device["Type"]: if "Dimmer" == device["SwitchType"]: - return domains['color'] + return DOMAINS['color'] elif "On/Off" == device["SwitchType"]: logger.info('%s (Idx: %s) is a color switch. To get all functions, set this device as Dimmer in Domoticz', device["Name"], device[ "idx"]) - return domains['light'] + return DOMAINS['light'] elif device["SwitchType"] in ['Push On Button', 'Push Off Button']: - return domains['push'] + return DOMAINS['push'] elif 'Security' == device["Type"]: - return domains['security'] + return DOMAINS['security'] return None def getDesc(state): - if state.domain in [domains['scene'], domains['group']]: + if state.domain in [DOMAINS['scene'], DOMAINS['group']]: if 'Scene_Config' in configuration and configuration['Scene_Config'] is not None: desc = configuration['Scene_Config'].get(int(state.id), None) return desc @@ -255,7 +255,7 @@ def getAog(device): aog.temp = device.get("Temp") aog.humidity = device.get("Humidity") aog.setpoint = device.get("SetPoint") - if aog.domain is domains['color']: + if aog.domain is DOMAINS['color']: aog.color = device.get("Color") aog.protected = device.get("Protected") aog.maxdimlevel = device.get("MaxDimLevel") @@ -283,21 +283,21 @@ def getAog(device): if desc is not None: dt = desc.get('devicetype', None) if dt is not None: - if aog.domain in [domains['blinds']]: + if aog.domain in [DOMAINS['blinds']]: if dt.lower() in ['window', 'gate', 'garage', 'door']: - aog.domain = domains[dt.lower()] - if aog.domain in [domains['light'], domains['switch']]: + aog.domain = DOMAINS[dt.lower()] + if aog.domain in [DOMAINS['light'], DOMAINS['switch']]: if dt.lower() in ['window', 'door', 'doorbell', 'gate', 'garage', 'light', 'ac_unit', 'bathtub', 'coffeemaker', 'dishwasher', 'dryer', 'fan', 'heater', 'kettle', 'media', 'microwave', 'outlet', 'oven', 'speaker', 'switch', 'vacuum', 'washer', 'waterheater']: - aog.domain = domains[dt.lower()] - if aog.domain in [domains['door']]: + aog.domain = DOMAINS[dt.lower()] + if aog.domain in [DOMAINS['door']]: if dt.lower() in ['window', 'gate', 'garage']: - aog.domain = domains[dt.lower()] - if aog.domain in [domains['selector']]: + aog.domain = DOMAINS[dt.lower()] + if aog.domain in [DOMAINS['selector']]: if dt.lower() in ['vacuum']: - aog.domain = domains[dt.lower()] - if aog.domain in [domains['camera']]: + aog.domain = DOMAINS[dt.lower()] + if aog.domain in [DOMAINS['camera']]: if dt.lower() in ['doorbell']: - aog.domain = domains[dt.lower()] + aog.domain = DOMAINS[dt.lower()] pn = desc.get('name', None) if pn is not None: aog.name = pn @@ -315,7 +315,7 @@ def getAog(device): aog.report_state = False if not report_state: aog.report_state = report_state - if domains['thermostat'] == aog.domain: + if DOMAINS['thermostat'] == aog.domain: minT = desc.get('minThreehold', None) if minT is not None: aog.minThreehold = minT @@ -326,62 +326,62 @@ def getAog(device): if at_idx is not None: aog.actual_temp_idx = at_idx try: - aog.state = str(aogDevs[domains['temperature'] + at_idx].temp) - aogDevs[domains['temperature'] + at_idx].domain = domains['merged'] + aog.id + ')' + aog.state = str(aogDevs[DOMAINS['temperature'] + at_idx].temp) + aogDevs[DOMAINS['temperature'] + at_idx].domain = DOMAINS['merged'] + aog.id + ')' except: logger.error('Merge Error, Cant find temperature device with idx %s', at_idx) modes_idx = desc.get('selector_modes_idx', None) if modes_idx is not None: aog.modes_idx = modes_idx try: - aog.level = aogDevs[domains['selector'] + modes_idx].level - aog.selectorLevelName = aogDevs[domains['selector'] + modes_idx].selectorLevelName - aogDevs[domains['selector'] + modes_idx].domain = domains['merged'] + aog.id + ')' + aog.level = aogDevs[DOMAINS['selector'] + modes_idx].level + aog.selectorLevelName = aogDevs[DOMAINS['selector'] + modes_idx].selectorLevelName + aogDevs[DOMAINS['selector'] + modes_idx].domain = DOMAINS['merged'] + aog.id + ')' except: logger.error('Merge Error, Cant find selector device with idx %s', modes_idx) - if aog.domain in [domains['heater'], domains['kettle'], domains['waterheater'], domains['oven']]: + if aog.domain in [DOMAINS['heater'], DOMAINS['kettle'], DOMAINS['waterheater'], DOMAINS['oven']]: tc_idx = desc.get('merge_thermo_idx', None) if tc_idx is not None: aog.merge_thermo_idx = tc_idx try: - aog.temp = aogDevs[domains['thermostat'] + tc_idx].state - aog.setpoint = aogDevs[domains['thermostat'] + tc_idx].setpoint - aogDevs[domains['thermostat'] + tc_idx].domain = domains['merged'] + aog.id + ')' + aog.temp = aogDevs[DOMAINS['thermostat'] + tc_idx].state + aog.setpoint = aogDevs[DOMAINS['thermostat'] + tc_idx].setpoint + aogDevs[DOMAINS['thermostat'] + tc_idx].domain = DOMAINS['merged'] + aog.id + ')' except: logger.error('Merge Error, Cant find thermostat device with idx %s', tc_idx) hide = desc.get('hide', False) if hide: - if aog.domain not in [domains['scene'], domains['group']]: - aog.domain = domains['hidden'] + if aog.domain not in [DOMAINS['scene'], DOMAINS['group']]: + aog.domain = DOMAINS['hidden'] else: logger.error('Scenes and Groups does not support function "hide" yet') - if aog.domain in [domains['camera'], domains['doorbell']]: + if aog.domain in [DOMAINS['camera'], DOMAINS['doorbell']]: aog.report_state = False - if domains['light'] == aog.domain and "Dimmer" == device["SwitchType"]: + if DOMAINS['light'] == aog.domain and "Dimmer" == device["SwitchType"]: aog.attributes = ATTRS_BRIGHTNESS - if domains['fan'] == aog.domain and "Selector" == device["SwitchType"]: + if DOMAINS['fan'] == aog.domain and "Selector" == device["SwitchType"]: aog.attributes = ATTRS_FANSPEED - if domains['outlet'] == aog.domain and "Dimmer" == device["SwitchType"]: + if DOMAINS['outlet'] == aog.domain and "Dimmer" == device["SwitchType"]: aog.attributes = ATTRS_BRIGHTNESS - if domains['color'] == aog.domain and "Dimmer" == device["SwitchType"]: + if DOMAINS['color'] == aog.domain and "Dimmer" == device["SwitchType"]: aog.attributes = ATTRS_BRIGHTNESS - if domains['color'] == aog.domain and device["SubType"] in ["RGBWW", "RGBWZ", "White"]: + if DOMAINS['color'] == aog.domain and device["SubType"] in ["RGBWW", "RGBWZ", "White"]: aog.attributes = ATTRS_COLOR_TEMP - if domains['thermostat'] == aog.domain and "Thermostat" == device["Type"]: + if DOMAINS['thermostat'] == aog.domain and "Thermostat" == device["Type"]: aog.attributes = ATTRS_THERMSTATSETPOINT - if domains['blinds'] == aog.domain and ("Blinds Percentage" == device["SwitchType"] or "Blinds + Stop" == device["SwitchType"]): + if DOMAINS['blinds'] == aog.domain and ("Blinds Percentage" == device["SwitchType"] or "Blinds + Stop" == device["SwitchType"]): aog.attributes = ATTRS_PERCENTAGE - if domains['blindsinv'] == aog.domain and ("Blinds Percentage Inverted" == device["SwitchType"] or "Blinds Inverted + Stop" == device["SwitchType"]): + if DOMAINS['blindsinv'] == aog.domain and ("Blinds Percentage Inverted" == device["SwitchType"] or "Blinds Inverted + Stop" == device["SwitchType"]): aog.attributes = ATTRS_PERCENTAGE - if domains['vacuum'] == aog.domain and "Selector" == device["SwitchType"]: + if DOMAINS['vacuum'] == aog.domain and "Selector" == device["SwitchType"]: aog.attributes = ATTRS_VACUUM_MODES - if domains['temperature'] == aog.domain and device["Type"] in ['Temp + Humidity', 'Temp + Humidity + Baro']: + if DOMAINS['temperature'] == aog.domain and device["Type"] in ['Temp + Humidity', 'Temp + Humidity + Baro']: aog.attributes = ATTRS_HUMIDITY if aog.room == None: - if aog.domain not in [domains['scene'], domains['group']]: + if aog.domain not in [DOMAINS['scene'], DOMAINS['group']]: if aog.plan != "0": aog.room = getPlans(aog.plan) @@ -607,9 +607,9 @@ def execute(self, command, params, challenge): else: protect = False - if protect or self.state.domain == domains['security']: + if protect or self.state.domain == DOMAINS['security']: pincode = configuration['Domoticz']['switchProtectionPass'] - if self.state.domain == domains['security']: + if self.state.domain == DOMAINS['security']: pincode = self.state.seccode acknowledge = False if challenge is None: @@ -624,7 +624,7 @@ def execute(self, command, params, challenge): raise SmartHomeErrorNoChallenge(ERR_CHALLENGE_NEEDED, 'challengeFailedPinNeeded', 'Unable to execute {} for {} - challenge needed '.format( command, self.state.entity_id)) - elif self.state.domain == domains['security'] and pincode != hashlib.md5( + elif self.state.domain == DOMAINS['security'] and pincode != hashlib.md5( str.encode(challenge.get('pin'))).hexdigest(): raise SmartHomeErrorNoChallenge(ERR_CHALLENGE_NEEDED, 'challengeFailedPinNeeded', 'Unable to execute {} for {} - challenge needed '.format( @@ -650,7 +650,7 @@ def execute(self, command, params, challenge): def async_update(self): """Update the entity with latest info from Domoticz.""" - if self.state.domain == domains['group'] or self.state.domain == domains['scene']: + if self.state.domain == DOMAINS['group'] or self.state.domain == DOMAINS['scene']: getDevices('scene') else: getDevices('id', self.state.id) From b2a45034f67e0df0d49fd41f338f7cd82f11537a Mon Sep 17 00:00:00 2001 From: DewGew Date: Fri, 9 Dec 2022 10:44:08 +0100 Subject: [PATCH 40/61] Update helpers.py --- helpers.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/helpers.py b/helpers.py index b4334ef..a8e6202 100644 --- a/helpers.py +++ b/helpers.py @@ -14,7 +14,14 @@ import google.auth.crypt import google.auth.jwt -from const import (CONFIGFILE, LOGFILE, KEYFILE, HOMEGRAPH_SCOPE, HOMEGRAPH_TOKEN_URL, PUBLIC_URL) +from const import ( + CONFIGFILE, + LOGFILE, + KEYFILE, + HOMEGRAPH_SCOPE, + HOMEGRAPH_TOKEN_URL, + PUBLIC_URL +) FILE_PATH = os.path.abspath(__file__) FILE_DIR = os.path.split(FILE_PATH)[0] From b8727c5157925330638f878e0131255680a2b34c Mon Sep 17 00:00:00 2001 From: DewGew Date: Fri, 9 Dec 2022 10:44:57 +0100 Subject: [PATCH 41/61] Update const.py --- const.py | 88 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/const.py b/const.py index 0effcfc..96cccc0 100644 --- a/const.py +++ b/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Constants for Google Assistant.""" -VERSION = '1.22.15' +VERSION = '1.22.16' PUBLIC_URL = 'https://[your public url]' CONFIGFILE = 'config/config.yaml' LOGFILE = 'dzga.log' @@ -77,7 +77,7 @@ ERR_VALUE_OUT_OF_RANGE = "valueOutOfRange" ERR_WRONG_PIN = 'pinIncorrect' -domains = { +DOMAINS = { 'ac_unit': 'AcUnit', 'bathtub': 'Bathtub', 'blinds': 'Blind', @@ -135,46 +135,46 @@ ATTRS_HUMIDITY = 1 DOMOTICZ_TO_GOOGLE_TYPES = { - domains['ac_unit']: TYPE_AC_UNIT, - domains['bathtub']: TYPE_BATHTUB, - domains['blinds']: TYPE_BLINDS, - domains['blindsinv']: TYPE_BLINDS, - domains['camera']: TYPE_CAMERA, - domains['coffeemaker']: TYPE_COFFEE, - domains['color']: TYPE_LIGHT, - domains['cooktop']: TYPE_COOKTOP, - domains['dishwasher']: TYPE_DISHWASHER, - domains['door']: TYPE_DOOR, - domains['doorbell']: TYPE_DOORBELL, - domains['dryer']: TYPE_DRYER, - domains['fan']: TYPE_FAN, - domains['garage']: TYPE_GARAGE, - domains['gate']: TYPE_GATE, - domains['group']: TYPE_SWITCH, - domains['heater']: TYPE_HEATER, - domains['kettle']: TYPE_KETTLE, - domains['light']: TYPE_LIGHT, - domains['lock']: TYPE_LOCK, - domains['lockinv']: TYPE_LOCK, - domains['media']: TYPE_MEDIA, - domains['microwave']: TYPE_MICRO, - domains['mower']: TYPE_MOWER, - domains['outlet']: TYPE_OUTLET, - domains['oven']: TYPE_OVEN, - domains['push']: TYPE_SWITCH, - domains['scene']: TYPE_SCENE, - domains['screen']: TYPE_SCREEN, - domains['security']: TYPE_SECURITY, - domains['selector']: TYPE_SWITCH, - domains['sensor']: TYPE_SENSOR, - domains['smokedetector']: TYPE_SMOKE_DETECTOR, - domains['speaker']: TYPE_SPEAKER, - domains['switch']: TYPE_SWITCH, - domains['temperature']: TYPE_SENSOR, - domains['thermostat']: TYPE_THERMOSTAT, - domains['vacuum']: TYPE_VACUUM, - domains['valve']: TYPE_VALVE, - domains['washer']: TYPE_WASHER, - domains['waterheater']: TYPE_WATERHEATER, - domains['window']: TYPE_WINDOW, + DOMAINS['ac_unit']: TYPE_AC_UNIT, + DOMAINS['bathtub']: TYPE_BATHTUB, + DOMAINS['blinds']: TYPE_BLINDS, + DOMAINS['blindsinv']: TYPE_BLINDS, + DOMAINS['camera']: TYPE_CAMERA, + DOMAINS['coffeemaker']: TYPE_COFFEE, + DOMAINS['color']: TYPE_LIGHT, + DOMAINS['cooktop']: TYPE_COOKTOP, + DOMAINS['dishwasher']: TYPE_DISHWASHER, + DOMAINS['door']: TYPE_DOOR, + DOMAINS['doorbell']: TYPE_DOORBELL, + DOMAINS['dryer']: TYPE_DRYER, + DOMAINS['fan']: TYPE_FAN, + DOMAINS['garage']: TYPE_GARAGE, + DOMAINS['gate']: TYPE_GATE, + DOMAINS['group']: TYPE_SWITCH, + DOMAINS['heater']: TYPE_HEATER, + DOMAINS['kettle']: TYPE_KETTLE, + DOMAINS['light']: TYPE_LIGHT, + DOMAINS['lock']: TYPE_LOCK, + DOMAINS['lockinv']: TYPE_LOCK, + DOMAINS['media']: TYPE_MEDIA, + DOMAINS['microwave']: TYPE_MICRO, + DOMAINS['mower']: TYPE_MOWER, + DOMAINS['outlet']: TYPE_OUTLET, + DOMAINS['oven']: TYPE_OVEN, + DOMAINS['push']: TYPE_SWITCH, + DOMAINS['scene']: TYPE_SCENE, + DOMAINS['screen']: TYPE_SCREEN, + DOMAINS['security']: TYPE_SECURITY, + DOMAINS['selector']: TYPE_SWITCH, + DOMAINS['sensor']: TYPE_SENSOR, + DOMAINS['smokedetector']: TYPE_SMOKE_DETECTOR, + DOMAINS['speaker']: TYPE_SPEAKER, + DOMAINS['switch']: TYPE_SWITCH, + DOMAINS['temperature']: TYPE_SENSOR, + DOMAINS['thermostat']: TYPE_THERMOSTAT, + DOMAINS['vacuum']: TYPE_VACUUM, + DOMAINS['valve']: TYPE_VALVE, + DOMAINS['washer']: TYPE_WASHER, + DOMAINS['waterheater']: TYPE_WATERHEATER, + DOMAINS['window']: TYPE_WINDOW, } From 1e885a95d276f8e80f315be31c5312ed0b032b90 Mon Sep 17 00:00:00 2001 From: DewGew Date: Sat, 10 Dec 2022 11:29:31 +0100 Subject: [PATCH 42/61] Update trait.py --- trait.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/trait.py b/trait.py index 5888395..595dd53 100644 --- a/trait.py +++ b/trait.py @@ -651,6 +651,7 @@ def query_attributes(self): response['thermostatMode'] = 'heat' response['thermostatTemperatureAmbient'] = round(tempConvert(current_temp, _google_temp_unit(units)), 1) response['thermostatTemperatureSetpoint'] = round(tempConvert(current_temp, _google_temp_unit(units)), 1) + # current_humidity = self.state.humidity # if current_humidity is not None: # response['thermostatHumidityAmbient'] = current_humidity @@ -668,6 +669,7 @@ def query_attributes(self): setpoint = float(self.state.setpoint) if setpoint is not None: response['thermostatTemperatureSetpoint'] = round(tempConvert(setpoint, _google_temp_unit(units)), 1) + current_humidity = self.state.humidity if current_humidity is not None: response['thermostatHumidityAmbient'] = current_humidity From deaee0efa7c935c161d9ddb35bb62c6d65eda318 Mon Sep 17 00:00:00 2001 From: DewGew Date: Sat, 10 Dec 2022 11:29:54 +0100 Subject: [PATCH 43/61] Update const.py --- const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const.py b/const.py index 96cccc0..ba81d6a 100644 --- a/const.py +++ b/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Constants for Google Assistant.""" -VERSION = '1.22.16' +VERSION = '1.22.17' PUBLIC_URL = 'https://[your public url]' CONFIGFILE = 'config/config.yaml' LOGFILE = 'dzga.log' From a4a7fc3619fe04b09b607e34b0476f6758b833d7 Mon Sep 17 00:00:00 2001 From: DewGew Date: Sat, 10 Dec 2022 11:50:27 +0100 Subject: [PATCH 44/61] Update helpers.py --- helpers.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/helpers.py b/helpers.py index a8e6202..f4aa571 100644 --- a/helpers.py +++ b/helpers.py @@ -123,6 +123,8 @@ def saveFile(filename, text): configuration['ClientID'] = 'sampleClientId' if 'ClientSecret' not in configuration: configuration['ClientSecret'] = 'sampleClientSecret' +if 'authToken' not in configuration: + configuration['authToken'] = 'ZsokmCwKjdhk7qHLeYd2' Auth = { 'clients': { @@ -132,10 +134,10 @@ def saveFile(filename, text): }, }, 'tokens': { - 'ZsokmCwKjdhk7qHLeYd2': { + configuration['authToken']: { 'uid': '1234', - 'accessToken': 'ZsokmCwKjdhk7qHLeYd2', - 'refreshToken': 'ZsokmCwKjdhk7qHLeYd2', + 'accessToken': configuration['authToken'], + 'refreshToken': configuration['authToken'], 'userAgentId': '1234', }, 'bfrrLnxxWdULSh3Y9IU2cA5pw8s4ub': { @@ -150,18 +152,11 @@ def saveFile(filename, text): 'uid': '1234', 'name': configuration['auth_user'], 'password': configuration['auth_pass'], - 'tokens': ['ZsokmCwKjdhk7qHLeYd2'], + 'tokens': [configuration['authToken']], }, - # '2345': { - # 'uid': '2345', - # 'name': configuration['auth_user_2'], - # 'password': configuration['auth_pass_2'], - # 'tokens': ['bfrrLnxxWdULSh3Y9IU2cA5pw8s4ub'], - # }, }, 'usernames': { configuration['auth_user']: '1234', - # configuration['auth_user_2']: '2345', } } From 177ccafdfe8b6db66fe4a81d57b263c8433b7a7c Mon Sep 17 00:00:00 2001 From: DewGew Date: Sat, 10 Dec 2022 11:53:38 +0100 Subject: [PATCH 45/61] Update default_config --- config/default_config | 1 + 1 file changed, 1 insertion(+) diff --git a/config/default_config b/config/default_config index 793b779..8a53893 100644 --- a/config/default_config +++ b/config/default_config @@ -29,6 +29,7 @@ ssl_cert: # /path/to/fullchain.pem # Login on Google Home app and configuration interface auth_user: 'admin' auth_pass: 'admin' +authToken: 'ZsokmCwKjdhk7qHLeYd2' # Google Assistant Settings: ClientID: 'clientid_from aog' From dbd6135f937b4990c7d9db4ab357e3a0035cee4d Mon Sep 17 00:00:00 2001 From: DewGew Date: Sat, 10 Dec 2022 14:00:21 +0100 Subject: [PATCH 46/61] Add Notifications to Doorbell Add Notifications to Doorbell --- const.py | 2 +- smarthome.py | 66 ++++++++++++++++++++++++++++++++++++++++- templates/settings.html | 9 ++++++ 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/const.py b/const.py index ba81d6a..62e2e15 100644 --- a/const.py +++ b/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Constants for Google Assistant.""" -VERSION = '1.22.17' +VERSION = '1.22.20' PUBLIC_URL = 'https://[your public url]' CONFIGFILE = 'config/config.yaml' LOGFILE = 'dzga.log' diff --git a/smarthome.py b/smarthome.py index 2044270..919bf27 100644 --- a/smarthome.py +++ b/smarthome.py @@ -146,6 +146,8 @@ def AogGetDomain(device): return DOMAINS['lockinv'] elif "Door Contact" == device["SwitchType"]: return DOMAINS['door'] + elif "Doorbell" == device["SwitchType"]: + return DOMAINS['doorbell'] elif device["SwitchType"] in ['Push On Button', 'Push Off Button']: return DOMAINS['push'] elif 'Motion Sensor' == device["SwitchType"]: @@ -298,6 +300,9 @@ def getAog(device): if aog.domain in [DOMAINS['camera']]: if dt.lower() in ['doorbell']: aog.domain = DOMAINS[dt.lower()] + if aog.domain in [DOMAINS['push']]: + if dt.lower() in ['doorbell']: + aog.domain = DOMAINS[dt.lower()] pn = desc.get('name', None) if pn is not None: aog.name = pn @@ -545,6 +550,7 @@ def sync_serialize(self, agent_user_id): device = { 'id': state.entity_id, + 'notificationSupportedByAgent': (True if state.domain == 'Doorbell' else False), 'name': { 'name': state.name }, @@ -733,6 +739,62 @@ def smarthome_post(self, s): pass s.send_json(200, json.dumps(response, ensure_ascii=False).encode('utf-8'), True) + + def notification_post(self, s): + logger.debug(s.headers) + a = s.headers.get('Authorization', None) + + token = None + if a is not None: + types, tokenH = a.split() + if types.lower() == 'bearer': + token = Auth['tokens'].get(tokenH, None) + + if token is None: + raise SmartHomeError(ERR_PROTOCOL_ERROR, 'not authorized access!!') + + event_id = ''.join(random.choices(string.ascii_uppercase + string.ascii_lowercase + + string.digits, k=10)) + + request_id = ''.join(random.choices(string.digits, k=20)) + + message = s.body + message = message.replace('|>>', '').split() + devid = message[0] + state = message[1] + aog = aogDevs.get(devid, None) + if aog != None: + if aog.domain in DOMAINS['doorbell']: + data = { + 'requestId': str(request_id), + 'agentUserId': token.get('userAgentId', None), + 'eventId': str(event_id), + 'payload': { + 'devices': { + 'states': { + devid: { + 'on': (True if state == 'ON' else False) + }, + }, + 'notifications': { + devid: { + "ObjectDetection": { + "objects": { + "unfamiliar": 1 + }, + "priority": 0, + "detectionTimestamp": time.time() + } + } + } + } + } + } + ReportState.call_homegraph_api(REPORT_STATE_BASE_URL, data) + else: + logger.info('Notification is not supported for ' + message[0]) + else: + logger.error('Something went wrong, check your notification settings!') def smarthome(self, s): s.send_message(500, "not supported") @@ -1273,6 +1335,7 @@ def pycast(self, s): "/pycast": SmartHomeReqHandler.pycast} smarthomePostMappings = {"/smarthome": SmartHomeReqHandler.smarthome_post, + "/notification": SmartHomeReqHandler.notification_post, "/settings": SmartHomeReqHandler.settings_post} else: smarthomeGetMappings = {"/smarthome": SmartHomeReqHandler.smarthome, @@ -1283,7 +1346,8 @@ def pycast(self, s): "/sound": SmartHomeReqHandler.send_sound, "/pycast": SmartHomeReqHandler.pycast} - smarthomePostMappings = {"/smarthome": SmartHomeReqHandler.smarthome_post} + smarthomePostMappings = {"/smarthome": SmartHomeReqHandler.smarthome_post, + "/notification": SmartHomeReqHandler.notification_post} smarthomeControlMappings = {'action.devices.SYNC': SmartHomeReqHandler.smarthome_sync, 'action.devices.QUERY': SmartHomeReqHandler.smarthome_query, diff --git a/templates/settings.html b/templates/settings.html index ff8c686..9a7c8e9 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -302,6 +302,15 @@

Advanced Settings

Change pidfile path and name. + +
+
+
+ + + This is your authToken to use with notifications. +
+
From 4709649fc0b233cd4d3c4d7a4715a2b296c7fc6e Mon Sep 17 00:00:00 2001 From: DewGew Date: Sat, 10 Dec 2022 14:09:13 +0100 Subject: [PATCH 47/61] Update default_config --- config/default_config | 1 + 1 file changed, 1 insertion(+) diff --git a/config/default_config b/config/default_config index 8a53893..f38c722 100644 --- a/config/default_config +++ b/config/default_config @@ -29,6 +29,7 @@ ssl_cert: # /path/to/fullchain.pem # Login on Google Home app and configuration interface auth_user: 'admin' auth_pass: 'admin' +# If you change authToken you need to disconnect and reconnect to Google Assistant authToken: 'ZsokmCwKjdhk7qHLeYd2' # Google Assistant Settings: From 21a9ededbcac2cfb75b7fc91fa080dd888d313df Mon Sep 17 00:00:00 2001 From: DewGew Date: Sat, 10 Dec 2022 21:32:19 +0100 Subject: [PATCH 48/61] Missing imports for notification --- smarthome.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/smarthome.py b/smarthome.py index 919bf27..211eece 100644 --- a/smarthome.py +++ b/smarthome.py @@ -6,6 +6,8 @@ import subprocess import sys import yaml +import random +import string from collections.abc import Mapping from itertools import product from pid import PidFile From b8d4a9e5759b8123261e6b7a0d0b22dbabee35b6 Mon Sep 17 00:00:00 2001 From: DewGew Date: Sat, 10 Dec 2022 21:33:29 +0100 Subject: [PATCH 49/61] Update const.py --- const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const.py b/const.py index 62e2e15..3cc572f 100644 --- a/const.py +++ b/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Constants for Google Assistant.""" -VERSION = '1.22.20' +VERSION = '1.22.21' PUBLIC_URL = 'https://[your public url]' CONFIGFILE = 'config/config.yaml' LOGFILE = 'dzga.log' From aa9d2ba1369feed5d7d8a5dc74e902060dfb6eae Mon Sep 17 00:00:00 2001 From: DewGew Date: Sun, 11 Dec 2022 10:47:35 +0100 Subject: [PATCH 50/61] Update smarthome.py --- smarthome.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smarthome.py b/smarthome.py index 211eece..27e93cb 100644 --- a/smarthome.py +++ b/smarthome.py @@ -775,7 +775,7 @@ def notification_post(self, s): 'devices': { 'states': { devid: { - 'on': (True if state == 'ON' else False) + 'on': (True if state in ['PRESSED', 'ON'] else False) }, }, 'notifications': { From 9c92a76b00c6ebf7c3a222d4632928f4b078b89c Mon Sep 17 00:00:00 2001 From: DewGew Date: Sun, 11 Dec 2022 10:50:50 +0100 Subject: [PATCH 51/61] Update const.py --- const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const.py b/const.py index 3cc572f..b5ff22f 100644 --- a/const.py +++ b/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Constants for Google Assistant.""" -VERSION = '1.22.21' +VERSION = '1.22.22' PUBLIC_URL = 'https://[your public url]' CONFIGFILE = 'config/config.yaml' LOGFILE = 'dzga.log' From bb23a8d7adb095f21fe71ec9e4103147a90454e7 Mon Sep 17 00:00:00 2001 From: DewGew Date: Sun, 11 Dec 2022 11:02:24 +0100 Subject: [PATCH 52/61] Update smarthome.py --- smarthome.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smarthome.py b/smarthome.py index 27e93cb..df068b6 100644 --- a/smarthome.py +++ b/smarthome.py @@ -761,7 +761,7 @@ def notification_post(self, s): request_id = ''.join(random.choices(string.digits, k=20)) message = s.body - message = message.replace('|>>', '').split() + message = message.replace('|', '').split() devid = message[0] state = message[1] aog = aogDevs.get(devid, None) @@ -775,7 +775,7 @@ def notification_post(self, s): 'devices': { 'states': { devid: { - 'on': (True if state in ['PRESSED', 'ON'] else False) + 'on': (True if state in ['pressed', '>>ON'] else False) }, }, 'notifications': { From cccf56998e1024f5758cba9addce7bce2f145bad Mon Sep 17 00:00:00 2001 From: DewGew Date: Sun, 11 Dec 2022 11:03:03 +0100 Subject: [PATCH 53/61] Update const.py --- const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const.py b/const.py index b5ff22f..df95f30 100644 --- a/const.py +++ b/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Constants for Google Assistant.""" -VERSION = '1.22.22' +VERSION = '1.22.23' PUBLIC_URL = 'https://[your public url]' CONFIGFILE = 'config/config.yaml' LOGFILE = 'dzga.log' From c1b504e920600d09599eeffa40af813c8b558570 Mon Sep 17 00:00:00 2001 From: DewGew Date: Mon, 12 Dec 2022 08:38:17 +0100 Subject: [PATCH 54/61] Fix for notifications doorbell --- smarthome.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/smarthome.py b/smarthome.py index df068b6..acfbafb 100644 --- a/smarthome.py +++ b/smarthome.py @@ -761,9 +761,11 @@ def notification_post(self, s): request_id = ''.join(random.choices(string.digits, k=20)) message = s.body - message = message.replace('|', '').split() + message = message.replace('|', ' ').split() + if '>>' in message: message.remove('>>') devid = message[0] state = message[1] + aog = aogDevs.get(devid, None) if aog != None: if aog.domain in DOMAINS['doorbell']: @@ -775,7 +777,7 @@ def notification_post(self, s): 'devices': { 'states': { devid: { - 'on': (True if state in ['pressed', '>>ON'] else False) + 'on': (True if state.lower() in ['on', 'pressed'] else False) }, }, 'notifications': { From 92c27ef0f9279d1f5b7487f147da893f5d65547c Mon Sep 17 00:00:00 2001 From: DewGew Date: Mon, 12 Dec 2022 08:58:26 +0100 Subject: [PATCH 55/61] Update smarthome.py --- smarthome.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smarthome.py b/smarthome.py index acfbafb..15167e5 100644 --- a/smarthome.py +++ b/smarthome.py @@ -303,7 +303,7 @@ def getAog(device): if dt.lower() in ['doorbell']: aog.domain = DOMAINS[dt.lower()] if aog.domain in [DOMAINS['push']]: - if dt.lower() in ['doorbell']: + if dt.lower() in ['doorbell', 'outlet', 'light']: aog.domain = DOMAINS[dt.lower()] pn = desc.get('name', None) if pn is not None: From 6c88ce3ccd5284e5c655d74f1c75dd03b2eecae2 Mon Sep 17 00:00:00 2001 From: DewGew Date: Mon, 12 Dec 2022 16:01:04 +0100 Subject: [PATCH 56/61] Update trait.py --- trait.py | 243 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 148 insertions(+), 95 deletions(-) diff --git a/trait.py b/trait.py index 595dd53..04d6f86 100644 --- a/trait.py +++ b/trait.py @@ -504,89 +504,6 @@ def execute(self, command, params): self.state.entity_id)) -# @register_trait -# class FanSpeedTrait(_Trait): - # """Trait to control speed of Fan. - # https://developers.google.com/actions/smarthome/traits/fanspeed - # """ - - # name = TRAIT_FANSPEED - # commands = [COMMAND_FANSPEED] - - # speed_synonyms = { - # 'off': ["stop", "off"], - # 'speed_low': ["slow", "low", "slowest", "lowest"], - # 'speed_medium': ["medium", "mid", "middle"], - # 'speed_high': ["high", "max", "fast", "highest", "fastest", "maximum"], - # } - - # modes = ['off', 'speed_low', 'speed_medium','speed_high'] - - # @staticmethod - # def supported(domain, features): - # """Test if state is supported.""" - # if domain in [ - # DOMAINS['fan'] - # ]: - # return features & ATTRS_FANSPEED - - # return False - - # def sync_attributes(self): - # """Return speed point and modes attributes for a sync request.""" - # modes = self.modes - # speeds = [] - # for mode in modes: - # if mode not in self.speed_synonyms: - # continue - # speed = { - # "speed_name": mode, - # "speed_values": [ - # {"speed_synonym": self.speed_synonyms.get(mode), "lang": "en"} - # ], - # } - # speeds.append(speed) - - # return { - # "availableFanSpeeds": {"speeds": speeds, "ordered": True}, - # } - - # def query_attributes(self): - # """Return speed point and modes query attributes.""" - # response = {} - - # speed = self.state.state - # if speed is not None: - # response["on"] = speed != 'Off' - # response["online"] = True - # response["currentFanSpeedSetting"] = speed.lower() - - # return response - - # def execute(self, command, params): - # """Execute an SetFanSpeed command.""" - # modes = self.modes - # protected = self.state.protected - # for key in params['fanSpeed']: - # if key in modes: - # level = str(modes.index(key) * 10) - - # url = DOMOTICZ_URL + '/json.htm?type=command¶m=switchlight&idx=' + self.state.id + '&switchcmd=Set%20Level&level=' + level - - # if protected: - # url = url + '&passcode=' + configuration['Domoticz']['switchProtectionPass'] - - # r = requests.get(url, auth=CREDITS) - - # if protected: - # status = r.json() - # err = status.get('status') - # if err == 'ERROR': - # raise SmartHomeError(ERR_WRONG_PIN, - # 'Unable to execute {} for {} check your settings'.format(command, - # self.state.entity_id)) - - @register_trait class TemperatureSettingTrait(_Trait): """Trait to offer handling both temperature point and modes functionality. @@ -1097,9 +1014,7 @@ def supported(domain, features): def sync_attributes(self): """Return stream attributes for a sync request.""" return { - 'cameraStreamSupportedProtocols': [ - "hls", - ], + 'cameraStreamSupportedProtocols': ['hls'], 'cameraStreamNeedAuthToken': False, 'cameraStreamNeedDrmEncryption': False, } @@ -1270,6 +1185,7 @@ def sync_attributes(self): response = {} if battery is not None or battery != 255: response['queryOnlyEnergyStorage'] = True + response['isRechargeable'] = False return response @@ -1277,14 +1193,24 @@ def query_attributes(self): """Return EnergyStorge query attributes.""" battery = self.state.battery response = {} - if battery is not None or battery != 255: - if battery <= 99: - response['capacityRemaining'] = [{ - 'unit': 'PERCENTAGE', - 'rawValue': battery - }] - else: - response['descriptiveCapacityRemaining'] = 'FULL' + if battery == 255: + return {} + if battery == 100: + descriptive_capacity_remaining = "FULL" + elif 75 <= battery < 100: + descriptive_capacity_remaining = "HIGH" + elif 50 <= battery < 75: + descriptive_capacity_remaining = "MEDIUM" + elif 25 <= battery < 50: + descriptive_capacity_remaining = "LOW" + elif 0 <= battery < 25: + descriptive_capacity_remaining = "CRITICALLY_LOW" + if battery is not None: + response['descriptiveCapacityRemaining'] = descriptive_capacity_remaining + response['capacityRemaining'] = [{ + 'unit': 'PERCENTAGE', + 'rawValue': battery + }] return response @@ -1353,12 +1279,139 @@ def execute(self, command, params): # protected = self.state.protected # if domain == DOMAINS['humidity']: - # url = DOMOTICZ_URL + '/json.htm?type=command¶m=switchscene&idx=' + self.state.id + '&switchcmd=On' + # url = DOMOTICZ_URL + '/json.htm?type=command¶m=setsetpoint&idx=' + self.state.id + '&setpoint=' + str(params['humidity']) + + # if protected: + # url = url + '&passcode=' + configuration['Domoticz']['switchProtectionPass'] + + # r = requests.get(url, auth=CREDITS) + # if protected: + # status = r.json() + # err = status.get('status') + # if err == 'ERROR': + # raise SmartHomeError(ERR_WRONG_PIN, + # 'Unable to execute {} for {} check your settings'.format(command, + # self.state.entity_id)) + +@register_trait +class SensorStateTrait(_Trait): + """Trait to get sensor state. + https://developers.google.com/actions/smarthome/traits/sensorstate + """ + + name = TRAIT_SENSOR_STATE + commands = [] + + @staticmethod + def supported(domain, features): + """Test if state is supported.""" + return domain in [DOMAINS['smokedetector']] + + def sync_attributes(self): + """Return attributes for a sync request.""" + domain = self.state.domain + if domain == DOMAIN['smokedetector']: + return { + "sensorStatesSupported": [ + { + "name": "SmokeLevel", + "descriptiveCapabilities": { + "availableStates": [ + "smoke detected", + "no smoke detected" + ] + } + } + ] + } + + def query_attributes(self): + """Return the attributes of this trait for this entity.""" + domain = self.state.domain + if self.state.state is not None: + return { + "currentSensorStateData": [ + { + "name": "SmokeLevel", + "currentSensorState": "smoke detected", + } + ] + } + +# @register_trait +# class FanSpeedTrait(_Trait): + # """Trait to control speed of Fan. + # https://developers.google.com/actions/smarthome/traits/fanspeed + # """ + + # name = TRAIT_FANSPEED + # commands = [COMMAND_FANSPEED] + + # speed_synonyms = { + # 'off': ["stop", "off"], + # 'speed_low': ["slow", "low", "slowest", "lowest"], + # 'speed_medium': ["medium", "mid", "middle"], + # 'speed_high': ["high", "max", "fast", "highest", "fastest", "maximum"], + # } + + # modes = ['off', 'speed_low', 'speed_medium','speed_high'] + + # @staticmethod + # def supported(domain, features): + # """Test if state is supported.""" + # if domain in [ + # DOMAINS['fan'] + # ]: + # return features & ATTRS_FANSPEED + + # return False + + # def sync_attributes(self): + # """Return speed point and modes attributes for a sync request.""" + # modes = self.modes + # speeds = [] + # for mode in modes: + # if mode not in self.speed_synonyms: + # continue + # speed = { + # "speed_name": mode, + # "speed_values": [ + # {"speed_synonym": self.speed_synonyms.get(mode), "lang": "en"} + # ], + # } + # speeds.append(speed) + + # return { + # "availableFanSpeeds": {"speeds": speeds, "ordered": True}, + # } + + # def query_attributes(self): + # """Return speed point and modes query attributes.""" + # response = {} + + # speed = self.state.state + # if speed is not None: + # response["on"] = speed != 'Off' + # response["online"] = True + # response["currentFanSpeedSetting"] = speed.lower() + + # return response + + # def execute(self, command, params): + # """Execute an SetFanSpeed command.""" + # modes = self.modes + # protected = self.state.protected + # for key in params['fanSpeed']: + # if key in modes: + # level = str(modes.index(key) * 10) + + # url = DOMOTICZ_URL + '/json.htm?type=command¶m=switchlight&idx=' + self.state.id + '&switchcmd=Set%20Level&level=' + level # if protected: # url = url + '&passcode=' + configuration['Domoticz']['switchProtectionPass'] # r = requests.get(url, auth=CREDITS) + # if protected: # status = r.json() # err = status.get('status') From 509d2e3554258c53523f01f2d9f17711788a5aa7 Mon Sep 17 00:00:00 2001 From: DewGew Date: Mon, 12 Dec 2022 16:02:58 +0100 Subject: [PATCH 57/61] Fix for $value notifications --- const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const.py b/const.py index df95f30..bb1748f 100644 --- a/const.py +++ b/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Constants for Google Assistant.""" -VERSION = '1.22.23' +VERSION = '1.22.24' PUBLIC_URL = 'https://[your public url]' CONFIGFILE = 'config/config.yaml' LOGFILE = 'dzga.log' From d47ecbffd73bf557842efcc194b678dc6099087d Mon Sep 17 00:00:00 2001 From: DewGew Date: Tue, 13 Dec 2022 07:26:56 +0100 Subject: [PATCH 58/61] Remove Blinds inverted Not needed after Domoticz version 2022.2 --- trait.py | 46 ++++++++++------------------------------------ 1 file changed, 10 insertions(+), 36 deletions(-) diff --git a/trait.py b/trait.py index 04d6f86..39ca212 100644 --- a/trait.py +++ b/trait.py @@ -339,7 +339,6 @@ def supported(domain, features): """Test if state is supported.""" return domain in ( DOMAINS['blinds'], - DOMAINS['blindsinv'], DOMAINS['door'], DOMAINS['window'], DOMAINS['gate'], @@ -365,15 +364,6 @@ def query_attributes(self): if features & ATTRS_PERCENTAGE: response['openPercent'] = self.state.level - if domain == DOMAINS['blindsinv']: - response['openPercent'] = self.state.level - else: - response['openPercent'] = 100 - self.state.level - elif domain == DOMAINS['blindsinv']: - if self.state.state in ['Open', 'Off']: - response['openPercent'] = 0 - else: - response['openPercent'] = 100 else: if self.state.state in ['Open', 'Off']: response['openPercent'] = 100 @@ -396,37 +386,21 @@ def execute(self, command, params): if domain == DOMAINS['blinds']: url = DOMOTICZ_URL + '/json.htm?type=command¶m=switchlight&idx=' + self.state.id + '&switchcmd=Set%20Level&level=' + str( params['openPercent']) - else: - url = DOMOTICZ_URL + '/json.htm?type=command¶m=switchlight&idx=' + self.state.id + '&switchcmd=Set%20Level&level=' + str( - 100 - params['openPercent']) else: p = params.get('openPercent', 50) url = DOMOTICZ_URL + '/json.htm?type=command¶m=switchlight&idx=' + self.state.id + '&switchcmd=' - if domain == DOMAINS['blindsinv']: - if p == 0 and state in ['Closed', 'Stopped', 'On']: - # open - url += 'Open' - elif p == 100 and state in ['Open', 'Stopped', 'Off']: - # close - url += 'Close' - else: - raise SmartHomeError(ERR_ALREADY_IN_STATE, - 'Unable to execute {} for {}. Already in state '.format(command, - self.state.entity_id)) - else: - if p == 100 and state in ['Closed', 'Stopped', 'On']: - # open - url += 'Open' - elif p == 0 and state in ['Open', 'Stopped', 'Off']: - # close - url += 'Close' - else: - raise SmartHomeError(ERR_ALREADY_IN_STATE, - 'Unable to execute {} for {}. Already in state '.format(command, - self.state.entity_id)) - + if p == 100 and state in ['Closed', 'Stopped', 'On']: + # open + url += 'Open' + elif p == 0 and state in ['Open', 'Stopped', 'Off']: + # close + url += 'Close' + else: + raise SmartHomeError(ERR_ALREADY_IN_STATE, + 'Unable to execute {} for {}. Already in state '.format(command, + self.state.entity_id)) if protected: url = url + '&passcode=' + configuration['Domoticz']['switchProtectionPass'] From f417f689eedb3e64535581f40a1de10bac322d46 Mon Sep 17 00:00:00 2001 From: DewGew Date: Tue, 13 Dec 2022 07:32:46 +0100 Subject: [PATCH 59/61] Remove blindsinverted Not neede after domoticz version 2022.2 --- smarthome.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/smarthome.py b/smarthome.py index 15167e5..0dc9c40 100644 --- a/smarthome.py +++ b/smarthome.py @@ -140,8 +140,6 @@ def AogGetDomain(device): if device["SwitchType"] in ['Blinds', 'Blinds + Stop', 'Venetian Blinds EU', 'Venetian Blinds US', 'Blinds Percentage']: return DOMAINS['blinds'] - elif device["SwitchType"] in ['Blinds Inverted', 'Blinds Percentage Inverted', 'Blinds Inverted + Stop']: - return DOMAINS['blindsinv'] elif 'Door Lock' == device["SwitchType"]: return DOMAINS['lock'] elif 'Door Lock Inverted' == device["SwitchType"]: @@ -380,8 +378,6 @@ def getAog(device): aog.attributes = ATTRS_THERMSTATSETPOINT if DOMAINS['blinds'] == aog.domain and ("Blinds Percentage" == device["SwitchType"] or "Blinds + Stop" == device["SwitchType"]): aog.attributes = ATTRS_PERCENTAGE - if DOMAINS['blindsinv'] == aog.domain and ("Blinds Percentage Inverted" == device["SwitchType"] or "Blinds Inverted + Stop" == device["SwitchType"]): - aog.attributes = ATTRS_PERCENTAGE if DOMAINS['vacuum'] == aog.domain and "Selector" == device["SwitchType"]: aog.attributes = ATTRS_VACUUM_MODES if DOMAINS['temperature'] == aog.domain and device["Type"] in ['Temp + Humidity', 'Temp + Humidity + Baro']: From 7f4ce9e2083d7f919efa8ebf8da7a7a6728339cc Mon Sep 17 00:00:00 2001 From: DewGew Date: Tue, 13 Dec 2022 07:37:28 +0100 Subject: [PATCH 60/61] Remove blinds inverted Not needed after Domoticz version 2022.2 --- const.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/const.py b/const.py index bb1748f..5afd6df 100644 --- a/const.py +++ b/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Constants for Google Assistant.""" -VERSION = '1.22.24' +VERSION = '1.22.25' PUBLIC_URL = 'https://[your public url]' CONFIGFILE = 'config/config.yaml' LOGFILE = 'dzga.log' @@ -81,7 +81,6 @@ 'ac_unit': 'AcUnit', 'bathtub': 'Bathtub', 'blinds': 'Blind', - 'blindsinv': 'BlindInverted', 'camera': 'Camera', 'coffeemaker': 'Coffeemaker', 'color': 'ColorSwitch', @@ -138,7 +137,6 @@ DOMAINS['ac_unit']: TYPE_AC_UNIT, DOMAINS['bathtub']: TYPE_BATHTUB, DOMAINS['blinds']: TYPE_BLINDS, - DOMAINS['blindsinv']: TYPE_BLINDS, DOMAINS['camera']: TYPE_CAMERA, DOMAINS['coffeemaker']: TYPE_COFFEE, DOMAINS['color']: TYPE_LIGHT, From 4ee9c89e742377054660f50b05d360fec8f8be35 Mon Sep 17 00:00:00 2001 From: DewGew Date: Tue, 13 Dec 2022 07:45:22 +0100 Subject: [PATCH 61/61] Update pip-requirements.txt --- requirements/pip-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/pip-requirements.txt b/requirements/pip-requirements.txt index 9b5f060..8a6407e 100644 --- a/requirements/pip-requirements.txt +++ b/requirements/pip-requirements.txt @@ -13,6 +13,6 @@ Jinja2>=2.11.3 PyChromecast==9.1.2 gTTS>=2.2.1 unicode-slugify>=0.1.3 -protobuf>=3.0.0 +protobuf==3.20.0 zeroconf>=0.25.1 casttube>=0.2.0
IdxIdx (Entity id) Name (Nicknames) Type State