From 6115437ab0815ddc268949724104a4bffcc1fc49 Mon Sep 17 00:00:00 2001 From: DewGew Date: Wed, 10 Jun 2020 09:24:21 +0200 Subject: [PATCH 01/20] Add debug log when voicecontrol tag is found --- smarthome.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/smarthome.py b/smarthome.py index dbaab17a..e45d0fc2 100644 --- a/smarthome.py +++ b/smarthome.py @@ -224,6 +224,9 @@ def getAog(device): # Try to get device specific voice control configuration from Domoticz # Read it from the configuration file if not in Domoticz (for backward compatibility) desc = getDeviceConfig(device.get("Description")) + if desc is not None: + logger.debug(' tags found for idx ' + aog.id + ' in domoticz description.') + logger.debug('Device_Config for idx ' + aog.id + ' will be ignored in config.yaml!') if desc is None: desc = getDesc(aog) From f6b0208567afa54e47d11ec576c418fa8b5e19d7 Mon Sep 17 00:00:00 2001 From: DewGew Date: Thu, 18 Jun 2020 13:13:40 +0200 Subject: [PATCH 02/20] Remove/disable Image override in config.yaml - Image override is not needed anymore, use 'devicetype' instead - Added get plan name from domoticz if no room is added to config. --- smarthome.py | 111 ++++++++++++++++++++++++--------------------------- 1 file changed, 53 insertions(+), 58 deletions(-) diff --git a/smarthome.py b/smarthome.py index e45d0fc2..2004ac21 100644 --- a/smarthome.py +++ b/smarthome.py @@ -48,17 +48,15 @@ ReportState = ReportState() if not ReportState.enable_report_state(): - logger.error("Service account key is not found.") - logger.error("Report state will be unavailable") - + logger.error("Service account key is not found. Report state will be unavailable") def checkupdate(): if repo is not None and 'CheckForUpdates' in configuration and configuration['CheckForUpdates'] == True: try: r = requests.get( 'https://raw.githubusercontent.com/DewGew/Domoticz-Google-Assistant/' + branch + '/const.py') - text = r.text - if VERSION not in text: + response = r.text + if VERSION not in response: update = 1 logger.info("========") logger.info(" New version is availible on Github!") @@ -99,30 +97,18 @@ def AogGetDomain(device): elif 'Camera_Stream' in configuration and True == device["UsedByCamera"] and True == \ configuration['Camera_Stream']['Enabled']: return domains['camera'] - elif 'Image_Override' in configuration and 'Switch' in configuration['Image_Override'] and device["Image"] in \ - configuration['Image_Override']['Switch']: + elif device["Image"] == 'Generic': return domains['switch'] - elif 'Image_Override' in configuration and 'Light' in configuration['Image_Override'] and device["Image"] in \ - configuration['Image_Override']['Light']: - return domains['light'] - elif 'Image_Override' in configuration and 'Media' in configuration['Image_Override'] and device["Image"] in \ - configuration['Image_Override']['Media']: + elif device["Image"] in ['Media', 'TV']: return domains['media'] - elif 'Image_Override' in configuration and 'Outlet' in configuration['Image_Override'] and device["Image"] in \ - configuration['Image_Override']['Outlet']: + elif device["Image"] == 'WallSocket': return domains['outlet'] - elif 'Image_Override' in configuration and 'Speaker' in configuration['Image_Override'] and device["Image"] in \ - configuration['Image_Override']['Speaker']: + elif device["Image"] == 'Speaker': return domains['speaker'] - elif 'Image_Override' in configuration and 'Fan' in configuration['Image_Override'] and device["Image"] in \ - configuration['Image_Override']['Fan']: + elif device["Image"] == 'Fan': return domains['fan'] - elif 'Image_Override' in configuration and 'Heating' in configuration['Image_Override'] and device["Image"] in \ - configuration['Image_Override']['Heating']: + elif device["Image"] == 'Heating': return domains['heater'] - elif 'Image_Override' in configuration and 'Kettle' in configuration['Image_Override'] and device["Image"] in \ - configuration['Image_Override']['Kettle']: - return domains['kettle'] else: return domains['light'] elif 'Blinds' == device["Type"]: @@ -148,9 +134,8 @@ def AogGetDomain(device): return domains['security'] return None - def getDesc(state): - if state.domain == domains['scene'] or state.domain == 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 @@ -161,7 +146,6 @@ def getDesc(state): else: return None - def getDeviceConfig(descstr): ISLIST = ['nicknames'] rawconfig = re.findall(r'(.*?)', descstr, re.DOTALL) @@ -192,7 +176,6 @@ def getDeviceConfig(descstr): return cfgdict return None - def getAog(device): domain = AogGetDomain(device) if domain is None: @@ -203,6 +186,7 @@ def getAog(device): aog.domain = domain aog.id = device["idx"] aog.entity_id = domain + aog.id + aog.plan = device.get("PlanID") aog.state = device.get("Data", "Scene") aog.level = device.get("LevelInt", 0) aog.temp = device.get("Temp") @@ -225,8 +209,8 @@ def getAog(device): # Read it from the configuration file if not in Domoticz (for backward compatibility) desc = getDeviceConfig(device.get("Description")) if desc is not None: - logger.debug(' tags found for idx ' + aog.id + ' in domoticz description.') - logger.debug('Device_Config for idx ' + aog.id + ' will be ignored in config.yaml!') + logger.debug(' tags found for idx %s in domoticz description.', aog.id) + logger.debug('Device_Config for idx %s will be ignored in config.yaml!', aog.id) if desc is None: desc = getDesc(aog) @@ -312,11 +296,9 @@ def getAog(device): return aog - aogDevs = {} deviceList = {} - def getDevices(devices="all", idx="0"): global aogDevs global deviceList @@ -362,11 +344,9 @@ def getDevices(devices="all", idx="0"): devlist.sort(key=takeSecond) deviceList = json.dumps(devlist) - def takeSecond(elem): return elem[1] - def deep_update(target, source): """Update a nested dictionary with another nested dictionary.""" for key, value in source.items(): @@ -399,7 +379,7 @@ def getSettings(): logger.debug(json.dumps(settings, indent=2, sort_keys=False, ensure_ascii=False)) def getVersion(): - """Get domoticz settings.""" + """Get domoticz version.""" global settings url = DOMOTICZ_URL + DOMOTICZ_GET_VERSION @@ -410,6 +390,18 @@ def getVersion(): settings['dzversion'] = vers['version'] settings['dzVents'] = vers['dzvents_version'] +def getPlans(idx): + """Get domoticz plan name.""" + global settings + + url = DOMOTICZ_URL + '/json.htm?type=plans&order=name&used=true' + r = requests.get(url, auth=CREDITS) + + if r.status_code == 200: + rooms = r.json()['result'] + plan = [i for i in rooms if i['idx'] == idx][0] + return plan['Name'] + def restartServer(): """Restart.""" logger.info(' ') @@ -420,7 +412,6 @@ def restartServer(): os.execv(sys.executable, ['python'] + sys.argv) - class _GoogleEntity: """Adaptation of Entity expressed in Google's terms.""" @@ -442,7 +433,7 @@ def traits(self): if Trait.supported(domain, features)] return t - def sync_serialize(self): + def sync_serialize(self, agent_user_id): """Serialize entity for a SYNC response. https://developers.google.com/actions/smarthome/create-app#actiondevicessync """ @@ -477,16 +468,22 @@ def sync_serialize(self): # use aliases aliases = state.nicknames if aliases: - device['name']['nicknames'] = aliases - - # add room hint if annotated + device['name']['nicknames'] = [state.name] + aliases + + for trt in traits: + device['attributes'].update(trt.sync_attributes()) + + # Add room hint if annotated room = state.room if room: device['roomHint'] = room - - for trt in traits: - device['attributes'].update(trt.sync_attributes()) - + return device + + # Get plan name from domoticz + if state.domain not in [domains['scene'], domains['group']]: + if state.plan is not "0": + device['roomHint'] = getPlans(state.plan) + return device def query_serialize(self): @@ -512,8 +509,8 @@ def execute(self, command, params, challenge): for trt in self.traits(): if trt.can_execute(command, params): - ack = self.state.ack # ack is now stored in state - pin = False + acknowledge = self.state.ack # ack is now stored in state + pincode = False if configuration['Domoticz']['switchProtectionPass']: protect = self.state.protected @@ -521,10 +518,10 @@ def execute(self, command, params, challenge): protect = False if protect or self.state.domain == domains['security']: - pin = configuration['Domoticz']['switchProtectionPass'] + pincode = configuration['Domoticz']['switchProtectionPass'] if self.state.domain == domains['security']: - pin = self.state.seccode - ack = False + pincode = self.state.seccode + acknowledge = False if challenge is None: raise SmartHomeErrorNoChallenge(ERR_CHALLENGE_NEEDED, 'pinNeeded', 'Unable to execute {} for {} - challenge needed '.format( @@ -533,17 +530,17 @@ def execute(self, command, params, challenge): raise SmartHomeErrorNoChallenge(ERR_CHALLENGE_NEEDED, 'userCancelled', 'Unable to execute {} for {} - challenge needed '.format( command, self.state.entity_id)) - elif True == protect and pin != challenge.get('pin'): + elif True == protect and pincode != challenge.get('pin'): 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 pin != 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( command, self.state.entity_id)) - if ack: + if acknowledge: if challenge is None: raise SmartHomeErrorNoChallenge(ERR_CHALLENGE_NEEDED, 'ackNeeded', 'Unable to execute {} for {} - challenge needed '.format( @@ -569,7 +566,6 @@ def async_update(self): else: getDevices('id', self.state.id) - class SmartHomeReqHandler(OAuthReqHandler): global smarthomeControlMappings global aogDevs @@ -814,11 +810,12 @@ def smarthome_sync(self, payload, token): getDevices() # sync all devices getSettings() enableReport = ReportState.enable_report_state() + agent_user_id = token.get('userAgentId', None) for state in aogDevs.values(): entity = _GoogleEntity(state) - serialized = entity.sync_serialize() + serialized = entity.sync_serialize(agent_user_id) if serialized is None: continue @@ -833,10 +830,9 @@ def smarthome_sync(self, payload, token): if enableReport: t = threading.Thread(target=self.delay_report_state, args=(states, token)).start() - return { - 'agentUserId': token.get('userAgentId', None), - 'devices': devices, - } + response = {'agentUserId': agent_user_id, 'devices': devices} + + return response def smarthome_query(self, payload, token): """Handle action.devices.QUERY request. @@ -921,7 +917,6 @@ def smarthome_disconnect(self, payload, token): """ return None - if 'userinterface' in configuration and configuration['userinterface'] == True: smarthomeGetMappings = {"/smarthome": SmartHomeReqHandler.smarthome, "/sync": SmartHomeReqHandler.syncDevices, From 4480703fa08023f20156f56a63d09b764d84bdfb Mon Sep 17 00:00:00 2001 From: DewGew Date: Thu, 18 Jun 2020 13:24:04 +0200 Subject: [PATCH 03/20] Remove image override --- config/default_config | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/config/default_config b/config/default_config index 56d29546..0d76e812 100644 --- a/config/default_config +++ b/config/default_config @@ -41,23 +41,6 @@ Domoticz: # Report low battry Low_battery_limit: 9 - -# Ligths, switches, media, etc. are using domoticz's "Light/Switch" type. -# So to differentiate them additionaly image names are used. -Image_Override: - Switch: - - 'Generic' - Light: - - 'Light' - Media: - - 'Media' - - 'TV' - Outlet: - - 'WallSocket' - Speaker: - - 'Speaker' - Fan: - - 'Fan' #Additional nicknames and room configuration #Comment out or delete section below if not needed From e4e2a50e555951862ee3c478b40fda230907a438 Mon Sep 17 00:00:00 2001 From: DewGew Date: Thu, 18 Jun 2020 13:25:07 +0200 Subject: [PATCH 04/20] Update const.py --- const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const.py b/const.py index dbe54f1a..ba334077 100644 --- a/const.py +++ b/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Constants for Google Assistant.""" -VERSION = '1.7.12' +VERSION = '1.8.1' PUBLIC_URL = 'https://[your public url]' CONFIGFILE = 'config/config.yaml' LOGFILE = 'dzga.log' From 2f18b7dba688b30707e0778b3b80596c81813bb3 Mon Sep 17 00:00:00 2001 From: DewGew Date: Thu, 18 Jun 2020 14:31:12 +0200 Subject: [PATCH 05/20] Rewrite get room from domoticz --- smarthome.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/smarthome.py b/smarthome.py index 2004ac21..d06b424a 100644 --- a/smarthome.py +++ b/smarthome.py @@ -294,6 +294,11 @@ def getAog(device): if domains['vacuum'] == aog.domain and "Selector" == device["SwitchType"]: aog.attributes = ATTRS_VACCUM_MODES + if aog.room == None: + if aog.domain not in [domains['scene'], domains['group']]: + if aog.plan is not "0": + aog.room = getPlans(aog.plan) + return aog aogDevs = {} @@ -477,12 +482,6 @@ def sync_serialize(self, agent_user_id): room = state.room if room: device['roomHint'] = room - return device - - # Get plan name from domoticz - if state.domain not in [domains['scene'], domains['group']]: - if state.plan is not "0": - device['roomHint'] = getPlans(state.plan) return device From 4bc39654fb6b11774cc43ee7b2955da8dd54a5f5 Mon Sep 17 00:00:00 2001 From: DewGew Date: Thu, 18 Jun 2020 14:32:18 +0200 Subject: [PATCH 06/20] [skip travis] New version 1.8.2 --- const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const.py b/const.py index ba334077..625ca0c9 100644 --- a/const.py +++ b/const.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Constants for Google Assistant.""" -VERSION = '1.8.1' +VERSION = '1.8.2' PUBLIC_URL = 'https://[your public url]' CONFIGFILE = 'config/config.yaml' LOGFILE = 'dzga.log' From 5f2572c75393c51fb99396d102029c2dafe82b9d Mon Sep 17 00:00:00 2001 From: DewGew Date: Thu, 18 Jun 2020 14:38:45 +0200 Subject: [PATCH 07/20] Remove image override info --- const.py | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/const.py b/const.py index 625ca0c9..2fa4a3fd 100644 --- a/const.py +++ b/const.py @@ -446,27 +446,7 @@ Homegraph_API_Key: 'ADD_YOUR HOMEGRAPH_API_KEY_HERE' # Not required.
Homegraph API key from Google. The Request Sync feature allows a cloud integration to send a request to the Home Graph to send a new SYNC request.
** NOTE: This is not needed if you are using Service account (smart-home-key.json).

Low_battery_limit: 9
Set threhold for report low battery.

-

Ligths, switches, media, etc. are using domoticz's "Light/Switch" type. To differentiate them additionaly add image name (e.g. - 'Light').
- Image_Override:
-   Switch:
-     - 'Generic'
-   Light:
-     - 'Light'
-     - 'custom_icon_name'
-   Media:
-     - 'Media'
-     - 'TV'
-   Outlet:
-     - 'WallSocket'
-   Speaker:
-     - 'Speaker'
-   Fan:
-     - 'Fan'
-   Heating:
-     - 'Heating'
-   Kettle:
-     - 'custom_icon_name'

- Support device types Switch Light Media Outlet Speaker Fan. Its possible to remove those that you don't need.

+

Camera_Stream:
In domoticz you need to attach a switch to your camera, Add switch idx and camera stream url. Read more below.

User-friendly name for the arm level in your language.
Armhome:
From 99a20204ca0252800c4901275b0c246cf0be910f Mon Sep 17 00:00:00 2001 From: DewGew Date: Thu, 18 Jun 2020 14:58:10 +0200 Subject: [PATCH 08/20] Update const.py --- const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const.py b/const.py index 2fa4a3fd..e76d65fc 100644 --- a/const.py +++ b/const.py @@ -321,7 +321,7 @@