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 diff --git a/modules/api.py b/modules/api.py index 12acea6..9504596 100644 --- a/modules/api.py +++ b/modules/api.py @@ -77,6 +77,67 @@ def modifyUserSettings(username, request): 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(): @@ -88,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: @@ -120,6 +180,10 @@ def gateway(): 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": if dbuser.admin: userToRemove = request.args.get('user', '') diff --git a/modules/domoticz.py b/modules/domoticz.py index bbe0165..b3ad4d2 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 diff --git a/modules/routes.py b/modules/routes.py index 2ed809a..6a3fc70 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}) @@ -155,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") diff --git a/modules/trait.py b/modules/trait.py index 8311bc2..d06ecbb 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 = custom_data['check_state'] if domain in ['Group', 'Scene']: state = getDomoticzState(user_id, idx, 'scene') @@ -172,21 +173,42 @@ 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') + + 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'] @@ -254,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']}] @@ -283,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() diff --git a/templates/devices.html b/templates/devices.html index 56b5601..28b8d38 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 %}
@@ -298,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.
@@ -313,12 +319,12 @@
Description | {{ v['name']['name'] }}
{% if v['customData']['protected'] == False %} - - + {% endfor %}