Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 23 additions & 45 deletions luxtronik/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
LUXTRONIK_PARAMETERS_READ,
LUXTRONIK_CALCULATIONS_READ,
LUXTRONIK_VISIBILITIES_READ,
LUXTRONIK_SOCKET_READ_SIZE_PEEK,
LUXTRONIK_SOCKET_READ_SIZE_INTEGER,
LUXTRONIK_SOCKET_READ_SIZE_CHAR,
)
Expand All @@ -41,26 +40,6 @@
WAIT_TIME_AFTER_PARAMETER_WRITE = 1


def is_socket_closed(sock: socket.socket) -> bool:
"""Check is socket closed."""
# Alternative to socket.MSG_DONTWAIT in recv.
# Works on Windows and Linux.
sock.setblocking(False)
try:
# this will try to read bytes without blocking and also without removing them from buffer
data = sock.recv(LUXTRONIK_SOCKET_READ_SIZE_PEEK, socket.MSG_PEEK)
is_closed = len(data) == 0
except BlockingIOError:
is_closed = False # socket is open and reading from it would block
except ConnectionResetError: # pylint: disable=broad-except
is_closed = True # socket was closed for some other reason
except Exception as err: # pylint: disable=broad-except
LOGGER.exception("Unexpected exception when checking if socket is closed", exc_info=err)
is_closed = False
sock.setblocking(True)
return is_closed


class LuxtronikData:
"""
Collection of parameters, calculations and visiblities.
Expand All @@ -86,32 +65,11 @@ def __init__(self, host, port=LUXTRONIK_DEFAULT_PORT):
self._host = host
self._port = port
self._socket = None
self._connect()

def __del__(self):
self._disconnect()

@property
def lock(self):
return self._lock

def _connect(self):
"""Connect the socket if not already done."""
is_none = self._socket is None
if is_none:
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if is_none or is_socket_closed(self._socket):
self._socket.connect((self._host, self._port))
LOGGER.info("Connected to Luxtronik heat pump %s:%s", self._host, self._port)

def _disconnect(self):
"""Disconnect the socket if not already done."""
if self._socket is not None:
if not is_socket_closed(self._socket):
self._socket.close()
self._socket = None
LOGGER.info("Disconnected from Luxtronik heatpump %s:%s", self._host, self._port)

def _with_lock_and_connect(self, func, *args, **kwargs):
"""
Decorator around various read/write functions to connect first.
Expand All @@ -122,9 +80,29 @@ def _with_lock_and_connect(self, func, *args, **kwargs):
Luxtronik controller, which seems unstable otherwise.
"""
with self.lock:
self._connect()
ret_val = func(*args, **kwargs)
return ret_val
try:
ret_val = None
with socket.create_connection((self._host, self._port)) as sock:
self._socket = sock
LOGGER.info("Connected to Luxtronik heat pump %s:%s", self._host, self._port)
ret_val = func(*args, **kwargs)
except socket.gaierror as e:
LOGGER.error("Failed to connect to Luxtronik heat pump %s:%s. %s.",
self._host, self._port, f"Address-related error: {e}")
except socket.timeout as e:
LOGGER.error("Failed to connect to Luxtronik heat pump %s:%s. %s.",
self._host, self._port, f"Connection timed out: {e}")
except ConnectionRefusedError as e:
LOGGER.error("Failed to connect to Luxtronik heat pump %s:%s. %s.",
self._host, self._port, f"Connection refused: {e}")
except OSError as e:
LOGGER.error("Failed to connect to Luxtronik heat pump %s:%s. %s.",
self._host, self._port, f"OS error during connect: {e}")
except Exception as e:
LOGGER.error("Failed to connect to Luxtronik heat pump %s:%s. %s.",
self._host, self._port, f"Unknown exception: {e}")
self._socket = None
return ret_val

def read(self, data=None):
"""
Expand Down
8 changes: 0 additions & 8 deletions tests/test_Luxtronik.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,6 @@ def reset(cls):
FakeSocketInterface.write_counter = 0
FakeSocketInterface.read_counter = 0

def _connect(self):
pass

def _disconnect(self):
pass

def read(self, data):
FakeSocketInterface.read_parameters(self, data.parameters)
FakeSocketInterface.read_visibilities(self, data.visibilities)
Expand Down Expand Up @@ -88,8 +82,6 @@ def fake_resolve_version(modbus_interface):
###############################################################################

@patch("luxtronik.LuxtronikSocketInterface", FakeSocketInterface)
@patch("luxtronik.LuxtronikSocketInterface._connect", FakeSocketInterface._connect)
@patch("luxtronik.LuxtronikSocketInterface._disconnect", FakeSocketInterface._disconnect)
@patch("luxtronik.LuxtronikSocketInterface.read_parameters", FakeSocketInterface.read_parameters)
@patch("luxtronik.LuxtronikSocketInterface.read_visibilities", FakeSocketInterface.read_visibilities)
@patch("luxtronik.LuxtronikSocketInterface.read_calculations", FakeSocketInterface.read_calculations)
Expand Down
Loading