From ee3c084e935af2e87b171b7f61687ae4b17fb4c7 Mon Sep 17 00:00:00 2001 From: DewGew Date: Sun, 4 Feb 2024 17:23:13 +0100 Subject: [PATCH 01/12] check state Checks state if already in state --- modules/domoticz.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/domoticz.py b/modules/domoticz.py index bbe0165..cf18c5b 100644 --- a/modules/domoticz.py +++ b/modules/domoticz.py @@ -127,7 +127,8 @@ def getAog(device, user_id=None): aog.type = 'action.devices.types.SWITCH' if domain in ['DoorLock', 'DoorLockInverted']: aog.type = 'action.devices.types.LOCK' - + + aog.customData['check_state'] = True # Try to get device specific voice control configuration from Domoticz aog.customData['dzTags'] = False desc = getDesc(user_id, aog) @@ -147,6 +148,9 @@ def getAog(device, user_id=None): ack = desc.get('ack', False) if ack: aog.customData['acknowledge'] = ack + chkState = desc.get('check_state', True) + if not chkState: + aog.customData['check_State'] = chkState repState = desc.get('report_state', True) if not repState: aog.willReportState = repState From 546a0c270fdff9e85209efa8fc908fa271d67d43 Mon Sep 17 00:00:00 2001 From: DewGew Date: Sun, 4 Feb 2024 17:25:24 +0100 Subject: [PATCH 02/12] check state for on/off trait --- modules/trait.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/modules/trait.py b/modules/trait.py index 8311bc2..62f2139 100644 --- a/modules/trait.py +++ b/modules/trait.py @@ -20,7 +20,7 @@ def query(custom_data, device, user_id): response = {} if 'action.devices.traits.OnOff' in device['traits']: - if domain in ['Group', 'Scene']: + if domain in ['Group']: data = state['Status'] != 'Off' response['on'] = data else: @@ -160,6 +160,7 @@ def execute(device, command, params, user_id, challenge): custom_data = device['customData'] idx = device['customData']['idx'] domain = device['customData']['domain'] + check_state = customData['check_state'] if domain in ['Group', 'Scene']: state = getDomoticzState(user_id, idx, 'scene') @@ -172,15 +173,27 @@ def execute(device, command, params, user_id, challenge): if command == "action.devices.commands.OnOff": if domain in ['Group', 'Scene']: - url += 'switchscene&idx=' + idx + '&switchcmd=' + ( - 'On' if params['on'] else 'Off') + data = state['Status'] + url += 'switchscene&idx=' + idx + '&switchcmd=' else: - url += 'switchlight&idx=' + idx + '&switchcmd=' + ( - 'On' if params['on'] else 'Off') + data = state['Data'] + url += 'switchlight&idx=' + idx + '&switchcmd=' + + if check_state: + if params['on'] is True and data == 'Off': + url += 'On' + elif params['on'] is False and data != 'Off': + url += 'Off' + else: + raise SmartHomeError('alreadyInState', + 'Unable to execute {} for {}. Already in state '.format(command, device['id'])) + else: + url += ('On' if params['on'] else 'Off') response['on'] = params['on'] if command == 'action.devices.commands.LockUnlock': + if domain in ['DoorLockInverted']: url += 'switchlight&idx=' + idx + '&switchcmd=' + ( 'Off' if params['lock'] else 'On') From 37910d6936f89cc8d6b80d2f4c36ae197f40b0db Mon Sep 17 00:00:00 2001 From: DewGew Date: Mon, 5 Feb 2024 14:41:32 +0100 Subject: [PATCH 03/12] Add check state button --- templates/devices.html | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/templates/devices.html b/templates/devices.html index 56b5601..0408a15 100644 --- a/templates/devices.html +++ b/templates/devices.html @@ -93,9 +93,15 @@
Description | {{ v['name']['name'] }}
+
+ +
+ Turns on/off already in state error message +
- {% if v['notificationSupportedByAgent'] == True %} (User can turn on or off notifications in Google Home App){% endif %} +
+ {% if v['notificationSupportedByAgent'] == True %}User can turn on or off notifications in Google Home App{% endif %}
From c7bfb3da9231c85b4d5669f761bbc7beac7d6417 Mon Sep 17 00:00:00 2001 From: DewGew Date: Mon, 5 Feb 2024 14:47:22 +0100 Subject: [PATCH 04/12] Add check state --- modules/routes.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/routes.py b/modules/routes.py index 2ed809a..b9ac6a4 100644 --- a/modules/routes.py +++ b/modules/routes.py @@ -63,6 +63,7 @@ def devices(): camurl = request.form.get('camurl') actual_temp_idx = request.form.get('actual_temp_idx') selector_modes_idx = request.form.get('selector_modes_idx') + check_state = request.form.get('checkState') if idx not in deviceconfig.keys(): deviceconfig[idx] = {} @@ -76,6 +77,11 @@ def devices(): deviceconfig[idx].update({'report_state': False}) elif idx in deviceconfig.keys() and 'report_state' in deviceconfig[idx]: deviceconfig[idx].pop('report_state') + + if check_state != 'on': + deviceconfig[idx].update({'check_state': False}) + elif idx in deviceconfig.keys() and 'check_state' in deviceconfig[idx]: + deviceconfig[idx].pop('check_state') if challenge == 'ackNeeded': deviceconfig[idx].update({'ack': True}) From ffd8fd71b6219b0c94c582f61cbba7c55a2cd27d Mon Sep 17 00:00:00 2001 From: DewGew Date: Mon, 5 Feb 2024 14:50:55 +0100 Subject: [PATCH 05/12] Update domoticz.py --- modules/domoticz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/domoticz.py b/modules/domoticz.py index cf18c5b..b3ad4d2 100644 --- a/modules/domoticz.py +++ b/modules/domoticz.py @@ -150,7 +150,7 @@ def getAog(device, user_id=None): aog.customData['acknowledge'] = ack chkState = desc.get('check_state', True) if not chkState: - aog.customData['check_State'] = chkState + aog.customData['check_state'] = chkState repState = desc.get('report_state', True) if not repState: aog.willReportState = repState From 11cd16c255fbd16a40fd0541f2ca7806734e614f Mon Sep 17 00:00:00 2001 From: DewGew Date: Mon, 5 Feb 2024 14:53:43 +0100 Subject: [PATCH 06/12] Update trait.py --- modules/trait.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/trait.py b/modules/trait.py index 62f2139..9cc0f22 100644 --- a/modules/trait.py +++ b/modules/trait.py @@ -160,7 +160,7 @@ def execute(device, command, params, user_id, challenge): custom_data = device['customData'] idx = device['customData']['idx'] domain = device['customData']['domain'] - check_state = customData['check_state'] + check_state = custom_data['check_state'] if domain in ['Group', 'Scene']: state = getDomoticzState(user_id, idx, 'scene') From 0ddf54c7ced317ced263240643089e4025d84a56 Mon Sep 17 00:00:00 2001 From: DewGew Date: Tue, 6 Feb 2024 10:14:11 +0100 Subject: [PATCH 07/12] Sync device when click save --- modules/routes.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/routes.py b/modules/routes.py index b9ac6a4..6a3fc70 100644 --- a/modules/routes.py +++ b/modules/routes.py @@ -161,6 +161,12 @@ def devices(): db.session.add(dbsettings) db.session.commit() + + if dbuser.googleassistant is True: + if report_state.report_state_enabled(): + report_state.call_homegraph_api('sync', {"agentUserId": current_user.username}) + else: + getDomoticzDevices(current_user.username) logger.info("Device settings saved") From 15521a53f39a2d1817e4c390e38c3ea6eb027b43 Mon Sep 17 00:00:00 2001 From: DewGew Date: Tue, 6 Feb 2024 11:56:12 +0100 Subject: [PATCH 08/12] Add device config API --- modules/api.py | 158 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 127 insertions(+), 31 deletions(-) diff --git a/modules/api.py b/modules/api.py index 33b00d2..9504596 100644 --- a/modules/api.py +++ b/modules/api.py @@ -18,14 +18,30 @@ def modifyServerSettings(request): dbsettings = Settings.query.get_or_404(1) - dbsettings.client_id = request.args.get('aogclient', '') - dbsettings.client_secret = request.args.get('aogsecret', '') - dbsettings.api_key = request.args.get('aogapi', '') - dbsettings.tempunit = request.args.get('tempunit', '') - dbsettings.language = request.args.get('language', '') - dbsettings.use_ssl = (request.args.get('ssl', '') == 'true') - dbsettings.ssl_cert = request.args.get('sslcert', '') - dbsettings.ssl_key = request.args.get('sslkey', '') + client_id = request.args.get('aogclient', None) + if client_id: + dbsettings.client_id = client_id + client_secret = request.args.get('aogsecret', None) + if client_secret: + dbsettings.client_secret = client_secret + api_key = request.args.get('aogapi', None) + if api_key: + dbsettings.api_key = api_key + tempunit = request.args.get('tempunit', None) + if tempunit: + dbsettings.tempunit = tempunit + language = request.args.get('language', None) + if language: + dbsettings.language = language + use_ssl = request.args.get('ssl', None) + if use_ssl: + dbsettings.use_ssl =(True if use_ssl else False) + ssl_cert = request.args.get('sslcert', None) + if ssl_cert: + dbsettings.ssl_cert = ssl_cert + ssl_key = request.args.get('sslkey', None) + if ssl_key: + dbsettings.ssl_key = ssl_key db.session.add(dbsettings) db.session.commit() @@ -37,18 +53,91 @@ def modifyUserSettings(username, request): dbuser = User.query.filter_by(username=username).first() - dbuser.domo_url = request.args.get('domourl', '') - dbuser.domouser = request.args.get('domouser', '') - dbuser.domopass = request.args.get('domopass', '') - dbuser.roomplan = request.args.get('roomplan', '') - dbuser.password = request.args.get('uipassword', '') - dbuser.googleassistant = (request.args.get('googleassistant', '') == 'true') + domo_url = request.args.get('domourl', None) + if domo_url: + dbuser.domo_url = domo_url + domouser = request.args.get('domouser', None) + if domouser: + dbuser.domouser = domouser + domopass = request.args.get('domopass', None) + if domopass: + dbuser.domopass = domopass + roomplan = request.args.get('roomplan', None) + if roomplan: + dbuser.roomplan = roomplan + password = request.args.get('uipassword', None) + if password: + dbuser.password = password + googleassistant = request.args.get('googleassistant', None) + if googleassistant: + dbuser.googleassistant = (True if googleassistant == 'true' else False) db.session.add(dbuser) db.session.commit() logger.info("User settings updated") + +def modifyDeviceConfig(username, request): + + dbuser = User.query.filter_by(username=username).first() + deviceconfig = dbuser.device_config + + entityId = request.args.get('id', None) + hide = request.args.get('hide', None) + checkstate = request.args.get('check_state', None) + room = request.args.get('room', None) + ack = request.args.get('ack', None) + devicetype = request.args.get('devicetype', None) + nicknames = request.args.get('nicknames', None) + + if entityId: + if entityId not in deviceconfig.keys(): + deviceconfig[entityId] = {} + if hide == 'true': + deviceconfig[entityId].update({'hide': True}) + elif hide is not None and entityId in deviceconfig.keys() and 'hide' in deviceconfig[entityId]: + deviceconfig[entityId].pop('hide') + + if checkstate == 'false': + deviceconfig[entityId].update({'check_state': False}) + elif checkstate is not None and entityId in deviceconfig.keys() and 'check_state' in deviceconfig[entityId]: + deviceconfig[entityId].pop('check_state') + + if room: + deviceconfig[entityId].update({'room': room}) + + if ack == 'true': + deviceconfig[entityId].update({'ack': True}) + elif ack is not None and entityId in deviceconfig.keys() and 'ack' in deviceconfig[entityId]: + deviceconfig[entityId].pop('ack') + + if devicetype: + deviceconfig[entityId].update({'devicetype': devicetype}) + + if nicknames: + names = nicknames.split(",") + names = list(filter(None, names)) + deviceconfig[entityId].update({'nicknames': names}) + logger.info(names) + + dbuser.device_config.update(deviceconfig) + db.session.add(dbuser) + db.session.commit() + + if dbuser.googleassistant is True: + if rs.report_state_enabled(): + rs.call_homegraph_api('sync', {"agentUserId": username}) + else: + getDomoticzDevices(username) + + logger.info("User settings updated") + return '{"title": "UserSettingsChanged", "status": "OK"}' + else: + return '{"title": "UserSettingsChanged", "status": "ERR"}' + + + @flask_login.login_required def gateway(): @@ -60,8 +149,7 @@ def gateway(): if custom == "sync": if dbuser.googleassistant is True: if rs.report_state_enabled(): - payload = {"agentUserId": flask_login.current_user.username} - rs.call_homegraph_api('sync', payload) + rs.call_homegraph_api('sync', {"agentUserId": flask_login.current_user.username}) result = '{"title": "RequestedSync", "status": "OK"}' flash("Devices synced with domoticz") else: @@ -70,12 +158,12 @@ def gateway(): else: getDomoticzDevices(flask_login.current_user.username) flash("Devices synced with domoticz") - return "Devices synced with domoticz", 200 + result = '{"title": "RequestedSync", "status": "OK"}' elif custom == "restart": - - logger.info('Restarts smarthome server') - os.execv(sys.executable, ['python'] + sys.argv) + if dbuser.admin: + logger.info('Restarts smarthome server') + os.execv(sys.executable, ['python'] + sys.argv) elif custom == "setArmLevel": armLevel = request.args.get('armLevel', '') @@ -83,29 +171,37 @@ def gateway(): result = queryDomoticz(flask_login.current_user.username, '?type=command¶m=setsecstatus&secstatus=' + armLevel + '&seccode=' + hashlib.md5(str.encode(seccode)).hexdigest()) elif custom == "server_settings": - - modifyServerSettings(request) + if dbuser.admin: + modifyServerSettings(request) + result = '{"title": "ServerSettingsChanged", "status": "OK"}' elif custom == "user_settings": modifyUserSettings(flask_login.current_user.username, request) + result = '{"title": "UserSettingsChanged", "status": "OK"}' + + elif custom == "device_config": + + result = modifyDeviceConfig(flask_login.current_user.username, request) elif custom == "removeuser": - userToRemove = request.args.get('user', '') + if dbuser.admin: + userToRemove = request.args.get('user', '') - removeuser = User.query.filter_by(username=userToRemove).first() + removeuser = User.query.filter_by(username=userToRemove).first() - db.session.delete(removeuser) - db.session.commit() - remove_user(userToRemove) - logger.info("User " + userToRemove + " is deleted") + db.session.delete(removeuser) + db.session.commit() + remove_user(userToRemove) + logger.info("User " + userToRemove + " is deleted") - return "User removed", 200 + result = '{"title": "UserRemoved", "status": "OK"}' + elif '?type' in requestedUrl[1]: result = queryDomoticz(flask_login.current_user.username, requestedUrl[1]) try: - return json.loads(result) + return json.loads(result), 200 except Exception: - return "No results returned", 404 + return {"title": "No results returned", "status": "ERR"}, 404 From 84324a5c96c0760ab5a0aa953815f237bd26e7ae Mon Sep 17 00:00:00 2001 From: DewGew Date: Tue, 6 Feb 2024 13:36:38 +0100 Subject: [PATCH 09/12] Add check state lockunlock trait and openclose trait --- modules/trait.py | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/modules/trait.py b/modules/trait.py index 9cc0f22..8dce816 100644 --- a/modules/trait.py +++ b/modules/trait.py @@ -194,12 +194,21 @@ def execute(device, command, params, user_id, challenge): if command == 'action.devices.commands.LockUnlock': - if domain in ['DoorLockInverted']: - url += 'switchlight&idx=' + idx + '&switchcmd=' + ( - 'Off' if params['lock'] else 'On') + url += 'switchlight&idx=' + idx + '&switchcmd=' + + if check_state: + if params['lock'] is True and state['Data'] == 'Unlocked': + url += ('Off' if domain in ['DoorLockInverted'] else 'On') + elif params['lock'] is False and state['Data'] != 'Unlocked': + url += ('On' if domain in ['DoorLockInverted'] else 'Off') + else: + raise SmartHomeError('alreadyInState', + 'Unable to execute {} for {}. Already in state '.format(command, device['id'])) else: - url += 'switchlight&idx=' + idx + '&switchcmd=' + ( - 'On' if params['lock'] else 'Off') + if domain in ['DoorLockInverted']: + url += ('Off' if params['lock'] else 'On') + else: + url += ('On' if params['lock'] else 'Off') response['isLocked'] = params['lock'] @@ -267,10 +276,20 @@ def execute(device, command, params, user_id, challenge): else: p = params.get('openPercent', 50) url += 'switchlight&idx=' + idx + '&switchcmd=' - if p == 100: - url += 'Open' - if p == 0: - url += 'Close' + + if check_state: + if p == 100 and state['Data'] in ['Closed', 'Stopped']: + url += 'Open' + elif p == 0 and state['Data'] in ['Open', 'Stopped']: + url += 'Close' + else: + raise SmartHomeError('alreadyInState', + 'Unable to execute {} for {}. Already in state '.format(command, device['id'])) + else: + if p == 100: + url += 'Open' + if p == 0: + url += 'Close' response['openState'] = [{'openPercent': params['openPercent']}] From 3a8aebddea41a4a37d92d29ab17526d759bc227e Mon Sep 17 00:00:00 2001 From: DewGew Date: Tue, 6 Feb 2024 13:51:35 +0100 Subject: [PATCH 10/12] Add check state for armdisarm trait --- modules/trait.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/modules/trait.py b/modules/trait.py index 8dce816..d06ecbb 100644 --- a/modules/trait.py +++ b/modules/trait.py @@ -315,11 +315,22 @@ def execute(device, command, params, user_id, challenge): if params["arm"]: if params["armLevel"] == "Arm Home": - url += "setsecstatus&secstatus=1" - if params["armLevel"] == "Arm Away": - url += "setsecstatus&secstatus=2" + if state['Data'] == "Arm Home" and check_state: + raise SmartHomeError('alreadyInState', + 'Unable to execute {} for {}. Already in state '.format(command, device['id'])) + else: + url += "setsecstatus&secstatus=1" + if state['Data'] == "Arm Away" and check_state: + raise SmartHomeError('alreadyInState', + 'Unable to execute {} for {}. Already in state '.format(command, device['id'])) + else: + url += "setsecstatus&secstatus=2" else: - url += "setsecstatus&secstatus=0" + if state['Data'] == "Normal" and check_state: + raise SmartHomeError('alreadyInState', + 'Unable to execute {} for {}. Already in state '.format(command, device['id'])) + else: + url += "setsecstatus&secstatus=0" url += '&seccode=' + hashlib.md5(str.encode(challenge.get('pin'))).hexdigest() From e825ad0541fa9fcb2d8a35d2e1716cf4f60e9779 Mon Sep 17 00:00:00 2001 From: DewGew Date: Wed, 7 Feb 2024 07:26:46 +0100 Subject: [PATCH 11/12] Update devices.html --- templates/devices.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/devices.html b/templates/devices.html index 0408a15..28b8d38 100644 --- a/templates/devices.html +++ b/templates/devices.html @@ -304,14 +304,14 @@
Description | {{ v['name']['name'] }}
{% endif %}
- Sync devices after saving for the changes to take effect
Its possible to change several devices before syncing devices
+ When pressing 'SAVE' devices will be synced.
@@ -319,12 +319,12 @@
Description | {{ v['name']['name'] }}
{% if v['customData']['protected'] == False %} - - + {% endfor %} From f38d11281655c671f74b2c074e2b2d5276020b66 Mon Sep 17 00:00:00 2001 From: DewGew Date: Wed, 7 Feb 2024 19:01:04 +0100 Subject: [PATCH 12/12] Update VERSION.md --- VERSION.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/VERSION.md b/VERSION.md index 646dc82..a376937 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1,4 +1,5 @@ -24.7 +24.8 +- Sync devices when saving device config +- Added more API +- Added already in state error message - Added REST API read more in wiki -- Add Security device -- Add more selectable device types