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

Merge setpoint devices to device types HEATER, WATERHEATER, OVEN #99

Merged
merged 14 commits into from
Feb 11, 2024
15 changes: 0 additions & 15 deletions .github/workflows/stale.yml

This file was deleted.

8 changes: 5 additions & 3 deletions VERSION.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
24.8
- Sync devices when saving device config
- Added more API
24.9
- Added Timer trait, a timer script is needed read in wiki
- Added temepeture control trait
- Added merge setpoint to Heater, waterheater, oven types.
- Small fixes
- Added already in state error message
- Added REST API read more in wiki
21 changes: 17 additions & 4 deletions modules/domoticz.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def getAog(device, user_id=None):
st = desc.get('devicetype', None)
if st is not None and st.lower() in config.TYPES:
aog.type = 'action.devices.types.' + st.upper()
if domain in ['Thermostat', 'Setpoint']:
if domain in ['Thermostat', 'Setpoint', 'OnOff']:
minT = desc.get('minThreehold', None)
if minT is not None:
minThreehold = minT
Expand All @@ -170,6 +170,10 @@ def getAog(device, user_id=None):
modes_idx = desc.get('selector_modes_idx', None)
if modes_idx is not None:
aog.customData['selector_modes_idx'] = modes_idx
if domain in ['OnOff']:
thermo_idx = desc.get('merge_thermo_idx', None)
if thermo_idx is not None:
aog.customData['merge_thermo_idx'] = thermo_idx
if domain in ['Doorbell', 'Camera']:
camurl = desc.get('camurl', None)
if camurl:
Expand Down Expand Up @@ -312,12 +316,12 @@ def getAog(device, user_id=None):
'queryOnlyTemperatureSetting': True,
'availableThermostatModes': ['heat', 'cool'],
}
if domain in ['Thermostat', 'Setpoint']:
if domain in ['Thermostat', 'Setpoint', 'Setpoint_Hidden']:
aog.traits.append('action.devices.traits.TemperatureSetting')
aog.attributes = {'thermostatTemperatureUnit': dbsettings.tempunit,
'thermostatTemperatureRange': {
'minThresholdCelsius': minThreehold,
'maxThresholdCelsius': maxThreehold},
'minThresholdCelsius': (minThreehold if dbsettings.tempunit == 'C' else (minThreehold-32)/1.8),
'maxThresholdCelsius': (maxThreehold if dbsettings.tempunit == 'C' else (maxThreehold-32)/1.8)},
'availableThermostatModes': ['heat', 'cool'],
}
if 'selector_modes_idx' in aog.customData:
Expand Down Expand Up @@ -351,6 +355,15 @@ def getAog(device, user_id=None):
'cameraStreamNeedAuthToken': False
}

if domain in ['OnOff'] and aog.type in ['action.devices.types.HEATER', 'action.devices.types.WATERHEATER', 'action.devices.types.KETTLE', 'action.devices.types.OVEN']:
if 'merge_thermo_idx' in aog.customData:
aog.traits.append('action.devices.traits.TemperatureControl')
aog.attributes['temperatureUnitForUX'] = dbsettings.tempunit
aog.attributes['temperatureRange'] = {
'minThresholdCelsius': (minThreehold if dbsettings.tempunit == 'C' else (minThreehold-32)/1.8),
'maxThresholdCelsius': (maxThreehold if dbsettings.tempunit == 'C' else (maxThreehold-32)/1.8)}
aog.attributes['temperatureStepCelsius'] = (5 if dbsettings.tempunit == 'C' else 2.778)

if domain in ['OnOff', 'Dimmer', 'ColorSwitch']:
aog.traits.append('action.devices.traits.Timer')
aog.attributes['maxTimerLimitSec'] = 7200
Expand Down
7 changes: 7 additions & 0 deletions modules/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def devices():
actual_temp_idx = request.form.get('actual_temp_idx')
selector_modes_idx = request.form.get('selector_modes_idx')
check_state = request.form.get('checkState')
merge_thermo_idx = request.form.get('merge_thermo_idx')

if idx not in deviceconfig.keys():
deviceconfig[idx] = {}
Expand Down Expand Up @@ -137,6 +138,12 @@ def devices():
deviceconfig[idx].update({'selector_modes_idx': selector_modes_idx})
elif idx in deviceconfig.keys() and 'selector_modes_idx' in deviceconfig[idx]:
deviceconfig[idx].pop('selector_modes_idx')

if merge_thermo_idx is not None:
if merge_thermo_idx != '':
deviceconfig[idx].update({'merge_thermo_idx': merge_thermo_idx})
elif idx in deviceconfig.keys() and 'merge_thermo_idx' in deviceconfig[idx]:
deviceconfig[idx].pop('merge_thermo_idx')

if camurl is not None:
if camurl != '':
Expand Down
34 changes: 25 additions & 9 deletions modules/trait.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ def query(custom_data, device, user_id):
if domain in ['Temp', 'TempHumidity', 'TempHumidityBaro']:
current_temp = float(state['Temp'])
response['temperatureAmbientCelsius'] = round(_tempConvert(current_temp, dbsettings.tempunit), 1)
if domain in ['OnOff'] and 'merge_thermo_idx' in custom_data:
merged_state = getDomoticzState(user_id, custom_data['merge_thermo_idx'])
current_temp = float(merged_state['Data'])
response['temperatureAmbientCelsius'] = round(_tempConvert(current_temp, dbsettings.tempunit), 1)

if 'action.devices.traits.Toggles' in device['traits']:
levelName = base64.b64decode(state.get("LevelNames")).decode('UTF-8').split("|")
Expand Down Expand Up @@ -124,12 +128,12 @@ def query(custom_data, device, user_id):
response["isArmed"] = state['Data'] != "Normal"
if response["isArmed"]:
response["currentArmLevel"] = state['Data']

if 'action.devices.traits.EnergyStorage' in device['traits']:
if state['BatteryLevel'] is not None:
battery = state['BatteryLevel']
if battery != 255:
if battery == 100:
if battery >= 100:
descriptive_capacity_remaining = "FULL"
elif 50 <= battery < 100:
descriptive_capacity_remaining = "HIGH"
Expand All @@ -139,13 +143,12 @@ def query(custom_data, device, user_id):
descriptive_capacity_remaining = "LOW"
elif 0 <= battery < 10:
descriptive_capacity_remaining = "CRITICALLY_LOW"

response['descriptiveCapacityRemaining'] = descriptive_capacity_remaining
response['capacityRemaining'] = [{
'unit': 'PERCENTAGE',
'rawValue': battery
}]


response['online'] = True
if domain not in ['Group', 'Scene'] and state['BatteryLevel'] is not None and state['BatteryLevel'] != 255:
Expand Down Expand Up @@ -178,7 +181,7 @@ def execute(device, command, params, user_id, challenge):
else:
data = state['Data']
url += 'switchlight&idx=' + idx + '&switchcmd='

if check_state:
if params['on'] is True and data == 'Off':
url += 'On'
Expand All @@ -187,15 +190,15 @@ def execute(device, command, params, user_id, challenge):
else:
raise SmartHomeError('alreadyInState',
'Unable to execute {} for {}. Already in state '.format(command, device['id']))
else:
else:
url += ('On' if params['on'] else 'Off')

response['on'] = params['on']

if command == 'action.devices.commands.LockUnlock':

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')
Expand Down Expand Up @@ -276,7 +279,7 @@ def execute(device, command, params, user_id, challenge):
else:
p = params.get('openPercent', 50)
url += 'switchlight&idx=' + idx + '&switchcmd='

if check_state:
if p == 100 and state['Data'] in ['Closed', 'Stopped']:
url += 'Open'
Expand Down Expand Up @@ -357,6 +360,19 @@ def execute(device, command, params, user_id, challenge):

url += 'customevent&event=TIMER&data={"idx":' + idx + ',"cancel":true}'

if command == 'action.devices.commands.TimerStart':

url += 'customevent&event=TIMER&data={"idx":' + idx + ',"time":' + str(params['timerTimeSec']) + ',"on":true}'

if command == 'action.devices.commands.TimerCancel':

url += 'customevent&event=TIMER&data={"idx":' + idx + ',"cancel":true}'

if command == 'action.devices.commands.SetTemperature':
if 'merge_thermo_idx' in custom_data:
url += 'setsetpoint&idx=' + custom_data['merge_thermo_idx'] + '&setpoint=' + str(
params['temperature'])

if state['Protected'] is True:
url = url + '&passcode=' + challenge.get('pin')

Expand Down
75 changes: 61 additions & 14 deletions templates/devices.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ <h5 class="card-title">Added devices <span>| {{ user.username }}</span></h5>
<th class="pointer" scope="row" data-bs-toggle="modal" data-bs-target="#devicesModal_{{ k }}">{{ k }}</th>

<div class="modal fade" id="devicesModal_{{ k }}" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Sync request json for {{ k }}</h5>
Expand All @@ -57,15 +57,15 @@ <h5 class="modal-title">Sync request json for {{ k }}</h5>
<div class="modal-body">
<pre id="json_{{ v['customData']['idx'] }}">
</pre>
<!-- <div class="modal-footer">
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div> -->
<!-- <button type="button" class="btn btn-primary">Save changes</button> -->
</div>
</div>
</div>
</div><!-- End Vertically centered Modal-->

<td>{{ v['name']['name'] }}</td>
<td>{{ v['name']['name'] }} <small class="form-text">{% if v['customData']['merge_thermo_idx'] %}(Merged with idx:{{ v['customData']['merge_thermo_idx'] }}){% elif v['customData']['actual_temp_idx']%}(Merged with idx:{{ v['customData']['actual_temp_idx'] }}){% endif %}</small></td>
<td class="text-primary pointer" data-bs-toggle="modal" data-bs-target="#descriptionModal_{{ k }}" >{{ v['customData']['domain'] }}</td>
<div class="modal fade" id="descriptionModal_{{ k }}" tabindex="-1">
<div class="modal-dialog modal-lg">
Expand Down Expand Up @@ -234,17 +234,26 @@ <h5 class="card-title">Description <span>| {{ v['name']['name'] }}</span></h5>
</select>
</div>
</div>
{% if (v['customData']['domain'] in ['Thermostat', 'Setpoint']) %}
{% endif %}
{% if (v['customData']['domain'] in ['Thermostat', 'Setpoint', 'Setpoint_Hidden']) %}
<div class="row mb-3">
<label for="inputText" class="col-sm-3 col-form-label">Min Threehold</label>
<label for="inputText" class="col-sm-3 col-form-label">Min Threehold {{ dbsettings.tempunit }}&#176;</label>
<div class="col-sm-2">
{% if dbsettings.tempunit == 'F'%}
<input type="number" class="form-control" name="minThreehold" value="{{ ((v['attributes']['thermostatTemperatureRange']['minThresholdCelsius']*1.8)+32)|round|int }}">
{% else %}
<input type="number" class="form-control" name="minThreehold" value="{{ v['attributes']['thermostatTemperatureRange']['minThresholdCelsius'] }}">
{% endif %}
</div>
</div>
<div class="row mb-3">
<label for="inputText" class="col-sm-3 col-form-label">Max Threehold</label>
<label for="inputText" class="col-sm-3 col-form-label">Max Threehold {{ dbsettings.tempunit }}&#176;</label>
<div class="col-sm-2">
{% if dbsettings.tempunit == 'F'%}
<input type="number" class="form-control" name="maxThreehold" value="{{ ((v['attributes']['thermostatTemperatureRange']['maxThresholdCelsius']*1.8)+32)|round|int }}">
{% else %}
<input type="number" class="form-control" name="maxThreehold" value="{{ v['attributes']['thermostatTemperatureRange']['maxThresholdCelsius'] }}">
{% endif %}
</div>
</div>
{% endif %}
Expand All @@ -256,7 +265,45 @@ <h5 class="card-title">Description <span>| {{ v['name']['name'] }}</span></h5>
</div>
</div>
{% endif %}
{% if v['customData']['domain'] in ['Thermostat', 'Setpoint'] %}
{% if v['customData']['domain'] in ['OnOff'] and v['type'] in ['action.devices.types.HEATER', 'action.devices.types.WATERHEATER', 'action.devices.types.KETTLE', 'action.devices.types.OVEN'] %}
{% if v['customData']['merge_thermo_idx'] %}
<div class="row mb-3">
<label for="inputText" class="col-sm-3 col-form-label">Min Threehold {{ dbsettings.tempunit }}&#176;</label>
<div class="col-sm-2">
{% if dbsettings.tempunit == 'F'%}
<input type="number" class="form-control" name="minThreehold" readonly value="{{ ((devices['Setpoint_' + v['customData']['merge_thermo_idx']]['attributes']['thermostatTemperatureRange']['minThresholdCelsius']*1.8)+32)|round|int }}">
{% else %}
<input type="number" class="form-control" name="minThreehold" readonly value="{{ devices['Setpoint_' + v['customData']['merge_thermo_idx']]['attributes']['thermostatTemperatureRange']['minThresholdCelsius']|round|int }}">
{% endif %}
</div>
</div>
<div class="row mb-3">
<label for="inputText" class="col-sm-3 col-form-label">Max Threehold {{ dbsettings.tempunit }}&#176;</label>
<div class="col-sm-2">
{% if dbsettings.tempunit == 'F'%}
<input type="number" class="form-control" name="maxThreehold" readonly value="{{ ((devices['Setpoint_' + v['customData']['merge_thermo_idx']]['attributes']['thermostatTemperatureRange']['maxThresholdCelsius']*1.8)+32)|round|int }}">
{% else %}
<input type="number" class="form-control" name="maxThreehold" readonly value="{{ devices['Setpoint_' + v['customData']['merge_thermo_idx']]['attributes']['thermostatTemperatureRange']['maxThresholdCelsius']|round|int }}">
{% endif %}
</div>
</div>
{% endif %}
<div class="row mb-3">
<label for="inputText" class="col-sm-3 col-form-label">Get setpoint</label>
<div class="col-sm-6">
<select id="merge_thermo_idx" name="merge_thermo_idx" class="form-select" aria-describedby="actTempHelp">
<option value="" {% if v['customData']['merge_thermo_idx'] == None %}selected{% endif %} >None</option>
{% for n, p in devices.items() %}
{% if 'Setpoint' in n %}
<option value="{{ p['customData']['idx'] }}" {% if v['customData']['merge_thermo_idx'] == p['customData']['idx'] %}selected{% endif %}>{{ p['name']['name'].replace('_',' ') }}, idx: {{ p['customData']['idx'] }}</option>
{% endif %}
{% endfor %}
</select>
<div id="modesHelp" class="form-text">Control temp from a setpoint device.</div>
</div>
</div>
{% endif %}
{% if v['customData']['domain'] in ['Thermostat', 'Setpoint', 'Setpoint_Hidden'] %}
<div class="row mb-3">
<label for="inputText" class="col-sm-3 col-form-label">Get actual temp</label>
<div class="col-sm-6">
Expand All @@ -272,12 +319,12 @@ <h5 class="card-title">Description <span>| {{ v['name']['name'] }}</span></h5>
</div>
</div>
{% endif %}
{% if v['customData']['domain'] in ['Thermostat', 'Setpoint'] %}
{% if v['customData']['domain'] in ['Thermostat', 'Setpoint', 'Setpoint_Hidden'] %}
<div class="row mb-3">
<label for="inputText" class="col-sm-3 col-form-label">Get modes</label>
<div class="col-sm-6">
<select id="actual_temp_idx" name="actual_temp_idx" class="form-select" aria-describedby="modesHelp">
<option value="" {% if v['customData']['actual_temp_idx'] == None %}selected{% endif %} >None</option>
<select id="selector_modes_idx" name="selector_modes_idx" class="form-select" aria-describedby="modesHelp">
<option value="" {% if v['customData']['selector_modes_idx'] == None %}selected{% endif %} >None</option>
{% for n, p in devices.items() %}
{% if 'Selector' in n %}
<option value="{{ p['customData']['idx'] }}" {% if v['customData']['selector_modes_idx'] == p['customData']['idx'] %}selected{% endif %}>{{ p['name']['name'].replace('_',' ') }}, idx: {{ p['customData']['idx'] }}</option>
Expand All @@ -288,7 +335,7 @@ <h5 class="card-title">Description <span>| {{ v['name']['name'] }}</span></h5>
</div>
</div>
{% endif %}
{% endif %}

{% if (v['customData']['domain'] == 'Security' and user.admin) %}
<div class="row mb-3" {% if dbsettings.language == 'en' %}style="display: none;"{% endif %}>
<div id="armhome_div" class="col-md-12">
Expand Down Expand Up @@ -354,7 +401,7 @@ <h5 class="card-title">Description <span>| {{ v['name']['name'] }}</span></h5>
{% include 'footer.html' %}

<script>
var updateInterval = 15000
var updateInterval = 30000
var updateSwitches_block = [];
var updateScenes_block = [];
var updateDimmers_block = [];
Expand Down
4 changes: 2 additions & 2 deletions templates/serveradmin.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ <h5 class="card-title">Server settings <span>| Admin</span></h5>
<div class="col-6">
<label for="tempunit" class="form-label">Temperture unit</label>
<select id="tempunit" name="tempunit" class="form-select">
<option {% if dbsettings.language == 'C' %} selected {% endif %}value="C">Celsius</option>
<option {% if dbsettings.language == 'F' %} selected {% endif %}value="F">Fahrenheit</option>
<option {% if dbsettings.tempunit == 'C' %} selected {% endif %}value="C">Celsius</option>
<option {% if dbsettings.tempunit == 'F' %} selected {% endif %}value="F">Fahrenheit</option>
</select>
</div>
<div class="col-md-6">
Expand Down
Loading