@@ -38,31 +38,38 @@ def is_socket_closed(sock: socket.socket) -> bool:
38
38
"""Check is socket closed."""
39
39
try :
40
40
# 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 )
44
42
if len (data ) == 0 :
45
43
return True
46
44
except BlockingIOError :
47
45
return False # socket is open and reading from it would block
48
46
except ConnectionResetError : # pylint: disable=broad-except
49
47
return True # socket was closed for some other reason
50
48
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 )
54
50
return False
55
51
return False
56
52
57
53
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
+ """
60
59
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 ):
62
70
self ._lock = threading .Lock ()
63
71
self ._host = host
64
72
self ._port = port
65
- self ._safe = safe
66
73
self ._socket = None
67
74
self ._connect ()
68
75
@@ -76,18 +83,15 @@ def _connect(self):
76
83
self ._socket = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
77
84
if is_none or is_socket_closed (self ._socket ):
78
85
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 )
82
87
83
88
def _disconnect (self ):
89
+ """Disconnect the socket if not already done."""
84
90
if self ._socket is not None :
85
91
if not is_socket_closed (self ._socket ):
86
92
self ._socket .close ()
87
93
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 )
91
95
92
96
def _with_lock_and_connect (self , func , * args , ** kwargs ):
93
97
"""
@@ -103,41 +107,73 @@ def _with_lock_and_connect(self, func, *args, **kwargs):
103
107
ret_val = func (* args , ** kwargs )
104
108
return ret_val
105
109
106
- def read (self ):
110
+ def read (self , data = None ):
107
111
"""
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.
110
115
"""
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 )
112
119
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 )
116
128
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 )
120
137
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 )
124
146
125
147
def write (self , parameters ):
126
148
"""
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.
130
150
:param Parameters() parameters Parameter dictionary to be written
131
151
to the heatpump before reading all available data
132
152
from the heat pump.
133
153
"""
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
135
171
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 )
141
177
142
178
def _write (self , parameters ):
143
179
for index , value in parameters .queue .items ():
@@ -157,12 +193,8 @@ def _write(self, parameters):
157
193
LOGGER .debug ("%s: Value %s" , self ._host , val )
158
194
# Flush queue after writing all values
159
195
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 ()
164
196
165
- def _read_parameters (self ):
197
+ def _read_parameters (self , parameters ):
166
198
data = []
167
199
self ._send_ints (LUXTRONIK_PARAMETERS_READ , 0 )
168
200
cmd = self ._read_int ()
@@ -176,11 +208,10 @@ def _read_parameters(self):
176
208
# not logging this as error as it would be logged on every read cycle
177
209
LOGGER .debug ("%s: %s" , self ._host , err )
178
210
LOGGER .info ("%s: Read %d parameters" , self ._host , length )
179
- parameters = Parameters (safe = self ._safe )
180
211
parameters .parse (data )
181
212
return parameters
182
213
183
- def _read_calculations (self ):
214
+ def _read_calculations (self , calculations ):
184
215
data = []
185
216
self ._send_ints (LUXTRONIK_CALCULATIONS_READ , 0 )
186
217
cmd = self ._read_int ()
@@ -196,11 +227,10 @@ def _read_calculations(self):
196
227
# not logging this as error as it would be logged on every read cycle
197
228
LOGGER .debug ("%s: %s" , self ._host , err )
198
229
LOGGER .info ("%s: Read %d calculations" , self ._host , length )
199
- calculations = Calculations ()
200
230
calculations .parse (data )
201
231
return calculations
202
232
203
- def _read_visibilities (self ):
233
+ def _read_visibilities (self , visibilities ):
204
234
data = []
205
235
self ._send_ints (LUXTRONIK_VISIBILITIES_READ , 0 )
206
236
cmd = self ._read_int ()
@@ -214,7 +244,6 @@ def _read_visibilities(self):
214
244
# not logging this as error as it would be logged on every read cycle
215
245
LOGGER .debug ("%s: %s" , self ._host , err )
216
246
LOGGER .info ("%s: Read %d visibilities" , self ._host , length )
217
- visibilities = Visibilities ()
218
247
visibilities .parse (data )
219
248
return visibilities
220
249
@@ -233,3 +262,40 @@ def _read_char(self):
233
262
"Low-level helper to receive a signed int"
234
263
reading = self ._socket .recv (LUXTRONIK_SOCKET_READ_SIZE_CHAR )
235
264
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