Skip to content
This repository has been archived by the owner on Sep 8, 2024. It is now read-only.

autodiscovery.py: Change thermostat mode from 'heat' to 'auto' #21

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
13 changes: 12 additions & 1 deletion etrv2mqtt/autodiscovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ class Autodiscovery():
"min_temp":"10",
"max_temp":"40",
"temp_step":"0.5",
"modes":["heat"],
"suggested_display_precision": "1",
"modes":["auto"],
"mode_state_topic":"etrv2mqtt/state",
"mode_state_template":"{{ 'auto' }}",
"device": {
"identifiers":"0000",
"manufacturer": "Danfoss",
Expand All @@ -39,12 +42,15 @@ class Autodiscovery():

_battery_template = json.loads("""
{
"entity_category": "diagnostic",
"device_class": "battery",
"name": "kitchen battery",
"unique_id":"0000_battery",
"state_topic": "etrv/kitchen/state",
"value_template": "{{ value_json.battery }}",
"unit_of_measurement": "%",
"suggested_display_precision": "0",
"state_class": "measurement",
"device": {
"identifiers":"0000",
"manufacturer": "Danfoss",
Expand All @@ -58,6 +64,7 @@ class Autodiscovery():

_reported_name_template = json.loads("""
{
"entity_category": "diagnostic",
"name": "kitchen reported name",
"unique_id":"0000_rep_name",
"state_topic": "etrv/kitchen/state",
Expand All @@ -81,6 +88,8 @@ class Autodiscovery():
"state_topic": "etrv/kitchen/state",
"value_template": "{{ value_json.room_temp }}",
"unit_of_measurement": "°C",
"suggested_display_precision": "1",
"state_class": "measurement",
"device": {
"identifiers":"0000",
"manufacturer": "Danfoss",
Expand All @@ -94,6 +103,7 @@ class Autodiscovery():

_last_update_template = json.loads("""
{
"entity_category": "diagnostic",
"device_class": "timestamp",
"name": "Kitchen Last Update",
"unique_id":"0000_last_update",
Expand Down Expand Up @@ -139,6 +149,7 @@ def register_termostat(self, dev_name: str, dev_mac: str) -> AutodiscoveryResult
autodiscovery_msg = self._autodiscovery_payload(
self._termostat_template, dev_mac, dev_name, "Thermostat")
autodiscovery_msg['~'] = self._config.mqtt.base_topic+'/'+dev_name
autodiscovery_msg['mode_state_topic'] = self._config.mqtt.base_topic + "/state"

return AutodiscoveryResult(autodiscovery_topic, payload=json.dumps(autodiscovery_msg))

Expand Down
6 changes: 6 additions & 0 deletions etrv2mqtt/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class _MQTTConfig:
autodiscovery: bool
autodiscovery_topic: str
autodiscovery_retain: bool
device_data_retain: bool
hass_birth_topic: str
hass_birth_payload: str

Expand Down Expand Up @@ -70,11 +71,16 @@ def __init__(self, filename: str):
_config_json['mqtt']['autodiscovery'],
_config_json['mqtt']['autodiscovery_topic'],
_config_json['mqtt']['autodiscovery_retain'],
_config_json['mqtt']['device_data_retain'],
_config_json['mqtt']['hass_birth_topic'],
_config_json['mqtt']['hass_birth_payload'],
)
self.retry_limit: int = _config_json['options']['retry_limit']
self.retry_rerun: bool = _config_json['options']['retry_rerun']
self.idle_block_ble: bool = _config_json['options']['idle_block_ble']
self.poll_schedule: str = _config_json['options']['poll_schedule']
self.poll_interval: int = _config_json['options']['poll_interval']
self.poll_hour_minute: int = _config_json['options']['poll_hour_minute']
self.stay_connected: bool = _config_json['options']['stay_connected']
self.report_room_temperature: bool = _config_json['options']['report_room_temperature']
self.setpoint_debounce_time: int = _config_json['options']['setpoint_debounce_time']
Expand Down
51 changes: 47 additions & 4 deletions etrv2mqtt/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
from etrv2mqtt.etrvutils import eTRVUtils
from etrv2mqtt.mqtt import Mqtt
from typing import Type, Dict, NoReturn
import os
import schedule


class DeviceBase(ABC):
def __init__(self, thermostat_config: ThermostatConfig, config: Config):
super().__init__()
self._config = config

@abstractmethod
def poll(self, mqtt: Mqtt):
Expand All @@ -23,7 +25,6 @@ def poll(self, mqtt: Mqtt):
def set_temperature(self, mqtt: Mqtt, temperature: float):
pass


class TRVDevice(DeviceBase):
def __init__(self, thermostat_config: ThermostatConfig, config: Config):
super().__init__(thermostat_config, config)
Expand All @@ -47,8 +48,16 @@ def poll(self, mqtt: Mqtt):

if self._stay_connected == False:
self._device.disconnect()
return True
except btle.BTLEInternalError as e:
logger.error(e)
if self._device.is_connected():
self._device.disconnect()
except btle.BTLEDisconnectError as e:
logger.error(e)
if self._device.is_connected():
self._device.disconnect()
return False

def set_temperature(self, mqtt: Mqtt, temperature: float):
try:
Expand All @@ -57,10 +66,18 @@ def set_temperature(self, mqtt: Mqtt, temperature: float):
if not self._device.is_connected():
self._device.connect()
eTRVUtils.set_temperature(self._device, temperature)
if self._stay_connected == False:
self._device.disconnect()
# Home assistant needs to see updated temperature value to confirm change
self.poll(mqtt)
except btle.BTLEDisconnectError as e:
logger.error(e)
if self._device.is_connected():
self._device.disconnect()
except btle.BTLEInternalError as e:
logger.error(e)
if self._device.is_connected():
self._device.disconnect()


class DeviceManager():
Expand All @@ -78,12 +95,26 @@ def __init__(self, config: Config, deviceClass: Type[DeviceBase]):
self._mqtt.hass_birth_callback = self._hass_birth_callback

def _poll_devices(self):
self.ble_on()
rerun = []
for device in self._devices.values():
device.poll(self._mqtt)
if not device.poll(self._mqtt):
if self._config.retry_rerun:
rerun.append(device)
if len(rerun)>0:
logger.warning("Attempting re-run of failed sensors in 5 seconds")
time.sleep(5)
for device in rerun:
device.poll(self._mqtt)
self.ble_off()

def poll_forever(self) -> NoReturn:
schedule.every(self._config.poll_interval).seconds.do(
self._poll_devices)
if self._config.poll_schedule == "hour_minute":
schedule.every().hour.at(":{0:02d}".format(self._config.poll_hour_minute)).do(
self._poll_devices)
else: # for poll_schedule=interval and as fallback
schedule.every(self._config.poll_interval).seconds.do(
self._poll_devices)
mqtt_was_connected: bool = False

while True:
Expand All @@ -100,7 +131,9 @@ def poll_forever(self) -> NoReturn:
time.sleep(2)

def _set_temerature_task(self, device: DeviceBase, temperature: float):
self.ble_on()
device.set_temperature(self._mqtt, temperature)
self.ble_off()
# this will cause the task to be executed only once
return schedule.CancelJob

Expand All @@ -120,3 +153,13 @@ def _set_temperature_callback(self, mqtt: Mqtt, name: str, temperature: float):

def _hass_birth_callback(self, mqtt: Mqtt):
schedule.run_all(delay_seconds=1)

def ble_off(self):
if self._config.idle_block_ble:
logger.info("Disable Bluetooth")
os.system("sudo rfkill block bluetooth")

def ble_on(self):
if self._config.idle_block_ble:
logger.info("Enable Bluetooth")
os.system("sudo rfkill unblock bluetooth")
2 changes: 1 addition & 1 deletion etrv2mqtt/mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def __init__(self, config: Config):
def publish_device_data(self, name: str, data: str):
if self._client.is_connected():
self._client.publish(
self._config.mqtt.base_topic+'/'+name+'/state', payload=data)
self._config.mqtt.base_topic+'/'+name+'/state', payload=data, retain=self._config.mqtt.device_data_retain)

def _publish_autodiscovery_result(self, result: AutodiscoveryResult, retain: bool = False):
self._client.publish(
Expand Down
32 changes: 30 additions & 2 deletions etrv2mqtt/schemas/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
"default": true,
"description": "Set retain bit on autodiscovery related messages"
},
"device_data_retain": {
"type":"boolean",
"default": false,
"description": "Set retain bit on device data messages"
},
"hass_birth_topic": {
"type": "string",
"default": "hass/status",
Expand All @@ -71,23 +76,46 @@
"default": {},
"description": "Options common for all thermostats",
"properties": {
"poll_schedule": {
"type": "string",
"description": "Polling schedule to use (regular interval, every hour at specified minute)",
"enum": [ "interval", "hour_minute" ],
"default": "interval"
},
"poll_interval": {
"type":"integer",
"description": "Interval between thermostat data readouts in seconds",
"description": "Interval between thermostat data readouts in seconds (only used when poll_schedule is interval)",
"minimum": 1,
"default": 3600
},
"poll_hour_minute": {
"type": "integer",
"description": "The minute of the hour polling starts (only used when poll_schedule is hour_minute",
"minimum": 0,
"maximum": 59,
"default": 0
},
"retry_limit": {
"type":"integer",
"description": "Limit of BLE connect attempts",
"minimum": 0,
"default": 5
},
"retry_rerun": {
"type": "boolean",
"description": "Retry failed thermostats once again after the end of the queue",
"default": true
},
"stay_connected": {
"type": "boolean",
"description": "Set to true in order to leave BLE connection running after polling thermostat data or setting temperature. May drain battery.",
"default": false
},
"idle_block_ble": {
"type": "boolean",
"description": "Disable Bluetooth when idle (requires sudo permissions for rfkill) - EXPERIMENTAL, avoid battery draining",
"default": false
},
"report_room_temperature": {
"type": "boolean",
"description": "Set to false to disable reporting current room temperature as a separate Home Assistant sensor in MQTT auto discovery",
Expand Down Expand Up @@ -127,4 +155,4 @@
}
}
}
}
}