Skip to content

Commit

Permalink
Updated pyatag
Browse files Browse the repository at this point in the history
  • Loading branch information
ArdKuijpers committed Dec 27, 2020
1 parent 53f290e commit f9d4536
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 149 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

# For more information, please refer to https://aka.ms/vscode-docker-python
FROM python:3.8-slim as base
FROM python:3.9-slim as base

FROM base as builder
RUN apt-get update \
Expand Down
11 changes: 5 additions & 6 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
"""The main app."""
import logging
from atagmqtt.atag_interaction import AtagInteraction
from atagmqtt.configuration import Settings
import asyncio

from atagmqtt.atag_interaction import main
from atagmqtt.configuration import Settings

LOGGER = logging.getLogger(__name__)
logging.basicConfig(level=Settings().loglevel)

if __name__ == "__main__":
LOOP = None
INTERACTION = AtagInteraction()
INTERACTION.main()
asyncio.run(main())

88 changes: 39 additions & 49 deletions atagmqtt/atag_interaction.py
Original file line number Diff line number Diff line change
@@ -1,60 +1,50 @@
"""Interaction with ATAG ONE."""
from typing import Optional
import asyncio
import logging
import aiohttp

from pyatag.gateway import AtagDataStore
from pyatag import AtagException, AtagOne
from pyatag.discovery import async_discover_atag
from .device_atagone import DeviceAtagOne
from .configuration import Settings


SETTINGS = Settings()
LOGGER = logging.getLogger(__name__)

class AtagInteraction:
"""Interaction with ATAG ONE as a datastore."""
def __init__(self):
"""Create an interaction with ATAG ONE."""
self.atag = AtagDataStore(host=SETTINGS.atag_host, hostname=SETTINGS.hostname, paired=True)
self.eventloop = None
self.device = None

def main(self):
"""The main processing function."""
self.eventloop = None
try:
self.eventloop = asyncio.get_event_loop()
LOGGER.info('Setup connection to ATAG ONE')
self.eventloop.run_until_complete(self.setup())
LOGGER.info('Starting the main loop for ATAG ONE')
self.eventloop.create_task(self.loop())
self.eventloop.run_forever()
except KeyboardInterrupt:
LOGGER.info('Closing connection to ATAG ONE')
finally:
LOGGER.info('Cleaning up')
if self.eventloop:
self.eventloop.run_until_complete(self.eventloop.shutdown_asyncgens())
self.eventloop.close()
self.eventloop = None

async def setup(self):
"""Setup the connection with the ATAG ONE device."""
if SETTINGS.atag_host:
LOGGER.info(f"Using configured ATAG ONE {SETTINGS.atag_host}")
else:
LOGGER.info(f"Discovering ATAG ONE")
await self.atag.async_host_search()

await self.atag.async_update()
self.device = DeviceAtagOne(self.atag, name="Atag One", eventloop=self.eventloop)
LOGGER.info(f"Connected to ATAG_ONE device {self.atag.device} @ {self.atag.config.host}")

async def loop(self):
"""The event loop."""
next_time = self.eventloop.time()
async def main():
"""The main processing function."""
async with aiohttp.ClientSession() as session:
await run(session)

async def run(session: aiohttp.ClientSession):
try:
LOGGER.info('Setup connection to ATAG ONE')
device = await setup(session)
LOGGER.info('Starting the main loop for ATAG ONE')
while True:
await asyncio.sleep(1)
if self.eventloop.time() > next_time:
await self.device.update()
LOGGER.info('Updated at: {}'.format(self.atag.sensordata['date_time']['state']))
next_time = self.eventloop.time() + SETTINGS.atag_update_interval
await asyncio.sleep(SETTINGS.atag_update_interval)
await device.update()
LOGGER.info('Updated at: {}'.format(device.atag.report.report_time))
except AtagException as atag_ex:
LOGGER.error(atag_ex)
return False
except KeyboardInterrupt:
LOGGER.info('Closing connection to ATAG ONE')

async def setup(session: aiohttp.ClientSession) -> DeviceAtagOne:

"""Setup the connection with the ATAG ONE device."""
if SETTINGS.atag_host:
LOGGER.info(f"Using configured ATAG ONE {SETTINGS.atag_host}")
else:
LOGGER.info(f"Discovering ATAG ONE")
atag_ip, atag_id = await async_discover_atag() # for auto discovery, requires access to UDP broadcast (hostnet)
SETTINGS.atag_host = atag_ip

atag = AtagOne(SETTINGS.atag_host, session)
await atag.authorize()
await atag.update(force=True)
device = DeviceAtagOne(atag, asyncio.get_running_loop(), name="Atag One")
LOGGER.info(f"Connected to ATAG ONE device @ {atag.host}")
return device
123 changes: 65 additions & 58 deletions atagmqtt/device_atagone.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""ATAG ONE device module."""
import logging
import asyncio

from homie.device_base import Device_Base
from homie.node.node_base import Node_Base
Expand All @@ -10,7 +11,7 @@
from homie.node.property.property_float import Property_Float
from homie.node.property.property_enum import Property_Enum

from pyatag import AtagDataStore
from pyatag import AtagOne
from .configuration import Settings


Expand All @@ -35,12 +36,12 @@

class DeviceAtagOne(Device_Base):
"""The ATAG ONE device."""
def __init__(self, atag: AtagDataStore, eventloop, device_id="atagone", name=None):
def __init__(self, atag: AtagOne, eventloop, device_id="atagone", name=None):
"""Create an ATAG ONE device."""
super().__init__(device_id, name, TRANSLATED_HOMIE_SETTINGS, TRANSLATED_MQTT_SETTINGS)
self.atag: AtagOne = atag
self.temp_unit = atag.climate.temp_unit
self._eventloop = eventloop
self.atag: AtagDataStore = atag
self.temp_unit = "°C"

node = (Node_Base(self, 'burner', 'Burner', 'status'))
self.add_node(node)
Expand All @@ -62,27 +63,32 @@ def __init__(self, atag: AtagDataStore, eventloop, device_id="atagone", name=Non

self.ch_temperature = Property_Temperature(
node, id="temperature", name="CH temperature",
settable=False, value=self.atag.temperature)
settable=False, value=self.atag.climate.temperature,
unit=self.temp_unit)
node.add_property(self.ch_temperature)

self.ch_water_temperature = Property_Temperature(
node, id="water-temperature", name="CH water temperature",
settable=False, value=self.atag.sensordata['ch_water_temp'].get('state', 0))
settable=False, value=self.atag.report["CH Water Temperature"].state,
unit=self.temp_unit)
node.add_property(self.ch_water_temperature)

self.ch_target_water_temperature = Property_Temperature(
node, id="target-water-temperature", name="CH target water temperature",
settable=False, value=self.atag.sensordata['ch_setpoint'].get('state', 0))
settable=False, value=self.atag.climate.target_temperature,
unit=self.temp_unit)
node.add_property(self.ch_target_water_temperature)

self.ch_return_water_temperature = Property_Temperature(
node, id="return-water-temperature", name="CH return water temperature",
settable=False, value=self.atag.sensordata['ch_return_temp'].get('state', 0))
settable=False, value=self.atag.report["CH Return Temperature"].state,
unit=self.temp_unit)
node.add_property(self.ch_return_water_temperature)

self.ch_water_pressure = Property_Float(
node, id="water-pressure", name="CH water pressure",
settable=False, value=self.atag.sensordata['ch_water_pres'].get('state', 0))
settable=False, value=self.atag.report["CH Water Pressure"].state,
unit=self.temp_unit)
node.add_property(self.ch_water_pressure)

# Domestic hot water status properties
Expand All @@ -95,15 +101,17 @@ def __init__(self, atag: AtagDataStore, eventloop, device_id="atagone", name=Non

self.dhw_temperature = Property_Temperature(
node, id="temperature", name="DHW temperature",
settable=False, value=self.atag.dhw_temperature)
settable=False, value=self.atag.dhw.temperature,
unit=self.temp_unit)
node.add_property(self.dhw_temperature)

node = (Node_Base(self, 'weather', 'Weather', 'status'))
self.add_node(node)

self.weather_temperature = Property_Temperature(
node, id="temperature", name="Weather temperature",
settable=False, value=self.atag.sensordata['weather_temp'].get('state', 0))
settable=False, value=self.atag.report["weather_temp"].state,
unit=self.temp_unit)
node.add_property(self.weather_temperature)

# Control properties
Expand All @@ -117,91 +125,90 @@ def __init__(self, atag: AtagDataStore, eventloop, device_id="atagone", name=Non
node, id='ch-target-temperature', name='CH Target temperature',
data_format=ch_target_temperature_limits,
unit=self.temp_unit,
value=self.atag.target_temperature,
value=self.atag.climate.target_temperature,
set_value=self.set_ch_target_temperature)
node.add_property(self.ch_target_temperature)

dhw_min_temp = self.atag.dhw_min_temp
dhw_max_temp = self.atag.dhw_max_temp
dhw_min_temp = self.atag.dhw.min_temp
dhw_max_temp = self.atag.dhw.max_temp
dhw_target_temperature_limits = f'{dhw_min_temp}:{dhw_max_temp}'
self.dhw_target_temperature = Property_Setpoint(
node, id='dhw-target-temperature', name='DHW Target temperature',
data_format=dhw_target_temperature_limits,
unit=self.temp_unit, value=self.atag.dhw_target_temperature,
unit=self.temp_unit, value=self.atag.dhw.target_temperature,
set_value=self.set_dhw_target_temperature)
node.add_property(self.dhw_target_temperature)

hvac_values = "auto,heat"
self.hvac_mode = Property_Enum(
node, id='hvac-mode', name='HVAC mode',
data_format=hvac_values, value=self.atag.hvac_mode,
data_format=hvac_values,
unit=self.temp_unit,
value=self.atag.climate.hvac_mode,
set_value=self.set_hvac_mode)
node.add_property(self.hvac_mode)

self.start()

def set_ch_target_temperature(self, value):
"""Set target central heating temperature."""
oldvalue = self.atag.target_temperature
oldvalue = self.atag.climate.target_temperature
LOGGER.info(f"Setting target CH temperature from {oldvalue} to {value} {self.temp_unit}")
self.ch_target_temperature.value = value
self._eventloop.create_task(self._async_set_ch_target_temperature(value))
self._run_task(self._async_set_ch_target_temperature(value))

async def _async_set_ch_target_temperature(self, value):
success = await self.atag.set_temp(value)
if success:
LOGGER.info(f"Succeeded setting target CH temperature to {value} {self.temp_unit}")

await self.atag.climate.set_temp(value)
LOGGER.info(f"Succeeded setting target CH temperature to {value} {self.temp_unit}")

def set_dhw_target_temperature(self, value):
"""Set target domestic hot water temperature."""
oldvalue = self.atag.dhw_target_temperature
oldvalue = self.atag.dhw.target_temperature
LOGGER.info(f"Setting target DHW temperature from {oldvalue} to {value} {self.temp_unit}")
self.dhw_target_temperature.value = value
self._eventloop.create_task(self._async_set_dhw_target_temperature(value))
self._run_task(self._async_set_dhw_target_temperature(value))

async def _async_set_dhw_target_temperature(self, value):
success = await self.atag.dhw_set_temp(value)
if success:
LOGGER.info(f"Succeeded setting target DHW temperature to {value} {self.temp_unit}")

await self.atag.dhw.set_temp(value)
LOGGER.info(f"Succeeded setting target DHW temperature to {value} {self.temp_unit}")

def set_hvac_mode(self, value):
"""Set HVAC mode."""
oldvalue = self.atag.hvac_mode
oldvalue = self.atag.climate.hvac_mode
LOGGER.info(f"Setting HVAC mode from {oldvalue} to {value}")
self.hvac_mode.value = value
self._eventloop.create_task(self._async_set_hvac_mode(value))
self._run_task(self._async_set_hvac_mode(value))

async def _async_set_hvac_mode(self, value):
success = await self.atag.set_hvac_mode(value)
if success:
LOGGER.info(f"Succeeded setting HVAC mode to {value}")

await self.atag.climate.set_hvac_mode(value)
LOGGER.info(f"Succeeded setting HVAC mode to {value}")

async def update(self):
"""Update device status from atag datastorage."""
await self.atag.async_update()
"""Update device status from atag device."""
await self.atag.update()
LOGGER.debug("Updating from latest device report")
self.burner_modulation.value = \
self.atag.burner_status[1].get('state', 0) if self.atag.burner_status[0] else 0
self.hvac_mode.value = self.atag.hvac_mode

self.ch_target_temperature.value = self.atag.target_temperature
self.ch_status.value = "true" if self.atag.ch_status else "false"
self.ch_temperature.value = self.atag.temperature
self.ch_water_temperature.value = self.atag.sensordata['ch_water_temp'].get('state', 0)
self.ch_water_pressure.value = self.atag.sensordata['ch_water_pres'].get('state', 0)
self.ch_target_water_temperature.value = self.atag.sensordata['ch_setpoint'].get('state', 0)
self.ch_return_water_temperature.value = \
self.atag.sensordata['ch_return_temp'].get('state', 0)

self.dhw_target_temperature.value = self.atag.dhw_target_temperature
self.dhw_temperature.value = self.atag.dhw_temperature
self.dhw_status.value = "true" if self.atag.dhw_status else "false"

self.weather_temperature.value = self.atag.sensordata['weather_temp'].get('state', 0)

if self.atag.dhw_status:
self.burner_modulation.value = self.atag.climate.flame
self.hvac_mode.value = self.atag.climate.hvac_mode

self.ch_target_temperature.value = self.atag.climate.target_temperature
self.ch_temperature.value = self.atag.climate.temperature
self.ch_target_water_temperature.value = self.atag.climate.target_temperature
self.ch_water_temperature.value = self.atag.report["CH Water Temperature"].state
self.ch_water_pressure.value = self.atag.report["CH Water Pressure"].state
self.ch_return_water_temperature.value = self.atag.report["CH Return Temperature"].state
self.ch_status.value = "true" if self.atag.climate.status else "false"

self.dhw_target_temperature.value = self.atag.dhw.target_temperature
self.dhw_temperature.value = self.atag.dhw.temperature
self.dhw_status.value = "true" if self.atag.dhw.status else "false"

self.weather_temperature.value = self.atag.report["weather_temp"].state

if self.atag.dhw.status:
self.burner_target.value = "dhw"
elif self.atag.ch_status:
elif self.atag.climate.status:
self.burner_target.value = "ch"
else:
self.burner_target.value = "none"

def _run_task(self, coroutine):
asyncio.run_coroutine_threadsafe(coroutine, self._eventloop)
Loading

0 comments on commit f9d4536

Please sign in to comment.