diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 0748c19..0000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: "Close stale issues" -on: - schedule: - - cron: "0 0 * * *" - -jobs: - stale: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days' - days-before-stale: 30 - days-before-close: 5 diff --git a/VERSION.md b/VERSION.md index a376937..9f40b60 100644 --- a/VERSION.md +++ b/VERSION.md @@ -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 diff --git a/modules/domoticz.py b/modules/domoticz.py index dc4c4a5..47a1c14 100644 --- a/modules/domoticz.py +++ b/modules/domoticz.py @@ -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 @@ -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: @@ -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: @@ -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 diff --git a/modules/routes.py b/modules/routes.py index c278225..59bc29a 100644 --- a/modules/routes.py +++ b/modules/routes.py @@ -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] = {} @@ -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 != '': diff --git a/modules/trait.py b/modules/trait.py index 8c0f93f..8b90e6b 100644 --- a/modules/trait.py +++ b/modules/trait.py @@ -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("|") @@ -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" @@ -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: @@ -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' @@ -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') @@ -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' @@ -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') diff --git a/templates/devices.html b/templates/devices.html index baa5b1e..199af48 100644 --- a/templates/devices.html +++ b/templates/devices.html @@ -48,7 +48,7 @@
Added devices | {{ user.username }}
{{ k }} {% 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'] %} +
+ +
+ {% if dbsettings.tempunit == 'F'%} + + {% else %} + + {% endif %} +
+
+
+ +
+ {% if dbsettings.tempunit == 'F'%} + + {% else %} + + {% endif %} +
+
+ {% endif %} +
+ +
+ +
Control temp from a setpoint device.
+
+
+ {% endif %} + {% if v['customData']['domain'] in ['Thermostat', 'Setpoint', 'Setpoint_Hidden'] %}
@@ -272,12 +319,12 @@
Description | {{ v['name']['name'] }}
{% endif %} - {% if v['customData']['domain'] in ['Thermostat', 'Setpoint'] %} + {% if v['customData']['domain'] in ['Thermostat', 'Setpoint', 'Setpoint_Hidden'] %}
- + {% for n, p in devices.items() %} {% if 'Selector' in n %} @@ -288,7 +335,7 @@
Description | {{ v['name']['name'] }}
{% endif %} - {% endif %} + {% if (v['customData']['domain'] == 'Security' and user.admin) %}
@@ -354,7 +401,7 @@
Description | {{ v['name']['name'] }}
{% include 'footer.html' %}