Skip to content

Commit 3d824e0

Browse files
committed
Revert API changes introduces with Bouni#86
1 parent 3009ca4 commit 3d824e0

File tree

4 files changed

+165
-100
lines changed

4 files changed

+165
-100
lines changed

README.md

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,27 +53,38 @@ The following example reads in data from the heat pump:
5353
from luxtronik import Luxtronik
5454

5555
l = Luxtronik('192.168.1.23', 8889)
56-
calculations, parameters, visibilities = l.read()
56+
heating_limit = l.parameters.get("ID_Einst_Heizgrenze_Temp")
5757

58-
t_forerun = calculations.get("ID_WEB_Temperatur_TVL")
58+
# Do something else here...
5959

60-
# alternatively get also works with numerical ID values
60+
# Read the values again
61+
l.read()
62+
t_forerun = l.calculations.get("ID_WEB_Temperatur_TVL")
63+
t_outside = l.calculations.get("ID_WEB_Temperatur_TA")
6164

62-
t_forerun = calculations.get(10)
65+
# alternatively get also works with numerical ID values
66+
t_forerun = l.calculations.get(10)
6367

6468
print(t_forerun) # this returns the temperature value of the forerun, 22.7 for example
6569
print(t_forerun.unit) # gives you the unit of the value if known, °C for example
6670

6771
# calculations holds measurement values
68-
# check https://github.com/Bouni/luxtronik/blob/master/luxtronik/calculations.py for values you might need
72+
# check https://github.com/Bouni/python-luxtronik/blob/master/luxtronik/calculations.py for values you might need
6973

7074
# parameters holds parameter values
71-
# check https://github.com/Bouni/luxtronik/blob/master/luxtronik/parameters.py for values you might need
75+
# check https://github.com/Bouni/python-luxtronik/blob/master/luxtronik/parameters.py for values you might need
7276

7377
# visibilitys holds visibility values, the function of visibilities is not clear at this point
74-
# check https://github.com/Bouni/luxtronik/blob/master/luxtronik/visibilities.py for values you might need
78+
# check https://github.com/Bouni/python-luxtronik/blob/master/luxtronik/visibilities.py for values you might need
7579
```
7680

81+
The method `read()` reads the calculations, parameters and
82+
visibilities from the heat pump.
83+
Alternatively `read_parameters()`, `read_calculations()` or `read_visibilities()`
84+
can be used.
85+
86+
Note that an initial read operation is carried out in the constructor.
87+
7788
### SCRIPTS AND COMMAND LINE INTERFACE (CLI)
7889

7990
Once installed, the luxtronik package provides several scripts that can be used
@@ -188,8 +199,16 @@ from luxtronik import Luxtronik, Parameters
188199

189200
l = Luxtronik('192.168.1.23', 8889)
190201

202+
# Queue a parameter change
203+
# In this example, the domestic hot water temperature is set to 45 degrees.
204+
l.parameters.set("ID_Soll_BWS_akt", 45.0)
205+
206+
# Write all queued changes to the heat pump
207+
l.write()
208+
209+
# Another possibility to write parameters
191210
parameters = Parameters()
192-
heating_mode = parameters.set("ID_Ba_Hz_akt", "Party")
211+
parameters.set("ID_Ba_Hz_akt", "Party")
193212
l.write(parameters)
194213

195214
# If you're not sure what values to write, you can get all available options:

luxtronik/__init__.py

Lines changed: 114 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -38,31 +38,38 @@ def is_socket_closed(sock: socket.socket) -> bool:
3838
"""Check is socket closed."""
3939
try:
4040
# this will try to read bytes without blocking and also without removing them from buffer
41-
data = sock.recv(
42-
LUXTRONIK_SOCKET_READ_SIZE_PEEK, socket.MSG_DONTWAIT | socket.MSG_PEEK
43-
)
41+
data = sock.recv(LUXTRONIK_SOCKET_READ_SIZE_PEEK, socket.MSG_DONTWAIT | socket.MSG_PEEK)
4442
if len(data) == 0:
4543
return True
4644
except BlockingIOError:
4745
return False # socket is open and reading from it would block
4846
except ConnectionResetError: # pylint: disable=broad-except
4947
return True # socket was closed for some other reason
5048
except Exception as err: # pylint: disable=broad-except
51-
LOGGER.exception(
52-
"Unexpected exception when checking if socket is closed", exc_info=err
53-
)
49+
LOGGER.exception("Unexpected exception when checking if socket is closed", exc_info=err)
5450
return False
5551
return False
5652

5753

58-
class Luxtronik:
59-
"""Main luxtronik class."""
54+
class LuxtronikData:
55+
"""
56+
Collection of parameters, calculations and visiblities.
57+
Also provide some high level access functions to their data values.
58+
"""
6059

61-
def __init__(self, host, port=LUXTRONIK_DEFAULT_PORT, safe=True):
60+
def __init__(self, parameters=None, calculations=None, visibilities=None, safe=True):
61+
self.parameters = Parameters(safe) if parameters is None else parameters
62+
self.calculations = Calculations() if calculations is None else calculations
63+
self.visibilities = Visibilities() if visibilities is None else visibilities
64+
65+
66+
class LuxtronikSocketInterface:
67+
"""Luxtronik read/write interface via socket."""
68+
69+
def __init__(self, host, port=LUXTRONIK_DEFAULT_PORT):
6270
self._lock = threading.Lock()
6371
self._host = host
6472
self._port = port
65-
self._safe = safe
6673
self._socket = None
6774
self._connect()
6875

@@ -76,18 +83,15 @@ def _connect(self):
7683
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
7784
if is_none or is_socket_closed(self._socket):
7885
self._socket.connect((self._host, self._port))
79-
LOGGER.info(
80-
"Connected to Luxtronik heat pump %s:%s", self._host, self._port
81-
)
86+
LOGGER.info("Connected to Luxtronik heat pump %s:%s", self._host, self._port)
8287

8388
def _disconnect(self):
89+
"""Disconnect the socket if not already done."""
8490
if self._socket is not None:
8591
if not is_socket_closed(self._socket):
8692
self._socket.close()
8793
self._socket = None
88-
LOGGER.info(
89-
"Disconnected from Luxtronik heatpump %s:%s", self._host, self._port
90-
)
94+
LOGGER.info("Disconnected from Luxtronik heatpump %s:%s", self._host, self._port)
9195

9296
def _with_lock_and_connect(self, func, *args, **kwargs):
9397
"""
@@ -103,41 +107,73 @@ def _with_lock_and_connect(self, func, *args, **kwargs):
103107
ret_val = func(*args, **kwargs)
104108
return ret_val
105109

106-
def read(self):
110+
def read(self, data=None):
107111
"""
108-
Read data from heat pump.
109-
All available data will be read from the heat pump.
112+
All available data will be read from the heat pump
113+
and integrated to the passed data object.
114+
This data object is returned afterwards, mainly for access to a newly created.
110115
"""
111-
return self._with_lock_and_connect(self._read)
116+
if data is None:
117+
data = LuxtronikData()
118+
return self._with_lock_and_connect(self._read, data)
112119

113-
def read_parameters(self):
114-
"""Read parameters from heat pump."""
115-
return self._with_lock_and_connect(self._read_parameters)
120+
def read_parameters(self, parameters=None):
121+
"""
122+
Read parameters from heat pump and integrate them to the passed dictionary.
123+
This dictionary is returned afterwards, mainly for access to a newly created.
124+
"""
125+
if parameters is None:
126+
parameters = Parameters()
127+
return self._with_lock_and_connect(self._read_parameters, parameters)
116128

117-
def read_calculations(self):
118-
"""Read calculations from heat pump."""
119-
return self._with_lock_and_connect(self._read_calculations)
129+
def read_calculations(self, calculations=None):
130+
"""
131+
Read calculations from heat pump and integrate them to the passed dictionary.
132+
This dictionary is returned afterwards, mainly for access to a newly created.
133+
"""
134+
if calculations is None:
135+
calculations = Calculations()
136+
return self._with_lock_and_connect(self._read_calculations, calculations)
120137

121-
def read_visibilities(self):
122-
"""Read visibilities from heat pump."""
123-
return self._with_lock_and_connect(self._read_visibilities)
138+
def read_visibilities(self, visibilities=None):
139+
"""
140+
Read visibilities from heat pump and integrate them to the passed dictionary.
141+
This dictionary is returned afterwards, mainly for access to a newly created.
142+
"""
143+
if visibilities is None:
144+
visibilities = Visibilities()
145+
return self._with_lock_and_connect(self._read_visibilities, visibilities)
124146

125147
def write(self, parameters):
126148
"""
127-
Write parameter to heat pump.
128-
All parameters will be written to the heat pump
129-
prior to reading back in all data from the heat pump.
149+
Write all set parameters to the heat pump.
130150
:param Parameters() parameters Parameter dictionary to be written
131151
to the heatpump before reading all available data
132152
from the heat pump.
133153
"""
134-
return self._with_lock_and_connect(self._write, parameters)
154+
self._with_lock_and_connect(self._write, parameters)
155+
156+
def write_and_read(self, parameters, data=None):
157+
"""
158+
Write all set parameter to the heat pump (see write())
159+
prior to reading back in all data from the heat pump (see read())
160+
after a short wait time
161+
"""
162+
if data is None:
163+
data = LuxtronikData()
164+
return self._with_lock_and_connect(self._write_and_read, parameters, data)
165+
166+
def _read(self, data):
167+
self._read_parameters(data.parameters)
168+
self._read_calculations(data.calculations)
169+
self._read_visibilities(data.visibilities)
170+
return data
135171

136-
def _read(self):
137-
parameters = self._read_parameters()
138-
calculations = self._read_calculations()
139-
visibilities = self._read_visibilities()
140-
return calculations, parameters, visibilities
172+
def _write_and_read(self, parameters, data):
173+
self._write(parameters)
174+
# Give the heatpump a short time to handle the value changes/calculations:
175+
time.sleep(WAIT_TIME_AFTER_PARAMETER_WRITE)
176+
return self._read(data)
141177

142178
def _write(self, parameters):
143179
for index, value in parameters.queue.items():
@@ -157,12 +193,8 @@ def _write(self, parameters):
157193
LOGGER.debug("%s: Value %s", self._host, val)
158194
# Flush queue after writing all values
159195
parameters.queue = {}
160-
# Give the heatpump a short time to handle the value changes/calculations:
161-
time.sleep(WAIT_TIME_AFTER_PARAMETER_WRITE)
162-
# Read the new values based on our parameter changes:
163-
return self._read()
164196

165-
def _read_parameters(self):
197+
def _read_parameters(self, parameters):
166198
data = []
167199
self._send_ints(LUXTRONIK_PARAMETERS_READ, 0)
168200
cmd = self._read_int()
@@ -176,11 +208,10 @@ def _read_parameters(self):
176208
# not logging this as error as it would be logged on every read cycle
177209
LOGGER.debug("%s: %s", self._host, err)
178210
LOGGER.info("%s: Read %d parameters", self._host, length)
179-
parameters = Parameters(safe=self._safe)
180211
parameters.parse(data)
181212
return parameters
182213

183-
def _read_calculations(self):
214+
def _read_calculations(self, calculations):
184215
data = []
185216
self._send_ints(LUXTRONIK_CALCULATIONS_READ, 0)
186217
cmd = self._read_int()
@@ -196,11 +227,10 @@ def _read_calculations(self):
196227
# not logging this as error as it would be logged on every read cycle
197228
LOGGER.debug("%s: %s", self._host, err)
198229
LOGGER.info("%s: Read %d calculations", self._host, length)
199-
calculations = Calculations()
200230
calculations.parse(data)
201231
return calculations
202232

203-
def _read_visibilities(self):
233+
def _read_visibilities(self, visibilities):
204234
data = []
205235
self._send_ints(LUXTRONIK_VISIBILITIES_READ, 0)
206236
cmd = self._read_int()
@@ -214,7 +244,6 @@ def _read_visibilities(self):
214244
# not logging this as error as it would be logged on every read cycle
215245
LOGGER.debug("%s: %s", self._host, err)
216246
LOGGER.info("%s: Read %d visibilities", self._host, length)
217-
visibilities = Visibilities()
218247
visibilities.parse(data)
219248
return visibilities
220249

@@ -233,3 +262,40 @@ def _read_char(self):
233262
"Low-level helper to receive a signed int"
234263
reading = self._socket.recv(LUXTRONIK_SOCKET_READ_SIZE_CHAR)
235264
return struct.unpack(">b", reading)[0]
265+
266+
267+
class Luxtronik(LuxtronikData):
268+
"""
269+
Wrapper around the data and the read/write interface.
270+
Mainly to ensure backwards compatibility
271+
of the read/write interface to other projects.
272+
"""
273+
274+
def __init__(self, host, port=LUXTRONIK_DEFAULT_PORT, safe=True):
275+
super().__init__(safe=safe)
276+
self.interface = LuxtronikSocketInterface(host, port)
277+
self.read()
278+
279+
def read(self):
280+
return self.interface.read(self)
281+
282+
def read_parameters(self):
283+
return self.interface.read_parameters(self.parameters)
284+
285+
def read_calculations(self):
286+
return self.interface.read_calculations(self.calculations)
287+
288+
def read_visibilities(self):
289+
return self.interface.read_visibilities(self.visibilites)
290+
291+
def write(self, parameters=None):
292+
if parameters is None:
293+
self.interface.write(self.parameters)
294+
else:
295+
self.interface.write(parameters)
296+
297+
def write_and_read(self, parameters=None):
298+
if parameters is None:
299+
return self.interface.write_and_read(self.parameters, self)
300+
else:
301+
return self.interface.write_and_read(parameters, self)

0 commit comments

Comments
 (0)