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 @@