Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check state #96

Merged
merged 13 commits into from
Feb 7, 2024
7 changes: 4 additions & 3 deletions VERSION.md
Original file line number Diff line number Diff line change
@@ -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
68 changes: 66 additions & 2 deletions modules/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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:
Expand Down Expand Up @@ -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', '')
Expand Down
6 changes: 5 additions & 1 deletion modules/domoticz.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions modules/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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] = {}
Expand All @@ -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})
Expand Down Expand Up @@ -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")

Expand Down
79 changes: 61 additions & 18 deletions modules/trait.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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')
Expand All @@ -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']

Expand Down Expand Up @@ -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']}]

Expand All @@ -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()

Expand Down
18 changes: 12 additions & 6 deletions templates/devices.html
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,15 @@ <h5 class="card-title">Description <span>| {{ v['name']['name'] }}</span></h5>
<input class="form-check-input" type="checkbox" id="willReportState" name="willReportState" {% if v['willReportState'] == True %}checked{% endif %}>
<label class="form-check-label" for="willReportState">Report State</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="checkState" name="checkState" {% if v['customData']['check_state'] == True %}checked{% endif %}>
<label class="form-check-label" for="checkState">Check State</label></br>
<small class="form-text">Turns on/off already in state error message</small>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="notification" name="notification" {% if v['notificationSupportedByAgent'] == True %}checked {% endif %} disabled>
<label class="form-check-label" for="notification">Notification Supported</label>{% if v['notificationSupportedByAgent'] == True %} <small>(User can turn on or off notifications in Google Home App)</small>{% endif %}
<label class="form-check-label" for="notification">Notification Supported</label></br>
{% if v['notificationSupportedByAgent'] == True %}<small class="form-text">User can turn on or off notifications in Google Home App</small>{% endif %}
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="secondaryUserVerificationCheck_{{ v['id'] }}" {% if ('acknowledge' in v['customData'] or v['customData']['protected'] == True) %}checked{% endif %}{% if 'Hidden' in v['customData']['domain'] %} disabled{% endif %}>
Expand Down Expand Up @@ -298,27 +304,27 @@ <h5 class="card-title">Description <span>| {{ v['name']['name'] }}</span></h5>
</div>
{% endif %}
<div class="row mb-3">
<small class="text-center text-danger"><strong>Sync devices after saving for the changes to take effect</strong></br>Its possible to change several devices before syncing devices</small>
<small class="text-center text-danger">When pressing <strong>'SAVE'</strong> devices will be synced.</small>
</div>
</div>
</div>

<div class="modal-footer">
<button type="reset" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="submit" name="submit" value="device_settings" class="btn btn-primary">Save changes</button>
<button type="submit" name="submit" value="device_settings" class="btn btn-primary">Save and sync devices</button>
</div>
</form>
</div>
</div>
</div><!-- End Vertically centered Modal-->
<td>
{% if v['customData']['protected'] == False %}
<button class="btn btn-outline-success btn-sm" onclick="toogleProtected({{ v['customData']['idx'] }}, 'true')" >No
<button class="btn btn-outline-success btn-sm" onclick="toogleProtected({{ v['customData']['idx'] }}, 'true')" {% if not user.admin %}disabled{% endif %}>No
{% else %}
<button class="btn btn-outline-danger btn-sm" onclick="toogleProtected({{ v['customData']['idx'] }}, 'false')">Yes
<button class="btn btn-outline-danger btn-sm" onclick="toogleProtected({{ v['customData']['idx'] }}, 'false')" {% if not user.admin %}disabled{% endif %}>Yes
{% endif %}
</button></td>
<td><button class="btn btn-light btn-sm" id="switch_{{ v['customData']['idx'] }}">Not available</button></td>
<td><button class="btn btn-light btn-sm" id="switch_{{ v['customData']['idx'] }}" disabled>Not available</button></td>
</tr>
{% endfor %}
</tbody>
Expand Down
Loading