Skip to content

Commit

Permalink
Merge pull request #96 from DewGew/check_state
Browse files Browse the repository at this point in the history
Check state
  • Loading branch information
DewGew authored Feb 7, 2024
2 parents 62ce6b8 + f38d112 commit 5606027
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 30 deletions.
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

0 comments on commit 5606027

Please sign in to comment.