2626
2727import logging
2828
29- from lib .model .sdp .globals import (CONN_SER_DIR , PLUGIN_ATTR_CB_ON_CONNECT , PLUGIN_ATTR_CB_ON_DISCONNECT , PLUGIN_ATTR_CONNECTION , PLUGIN_ATTR_CONN_AUTO_CONN , PLUGIN_ATTR_CONN_BINARY , PLUGIN_ATTR_CONN_CYCLE , PLUGIN_ATTR_CONN_RETRIES , PLUGIN_ATTR_CONN_TIMEOUT , PLUGIN_ATTR_SERIAL_BAUD , PLUGIN_ATTR_SERIAL_BSIZE , PLUGIN_ATTR_SERIAL_PARITY , PLUGIN_ATTR_SERIAL_PORT , PLUGIN_ATTR_SERIAL_STOP )
29+ from lib .model .sdp .globals import (SDPError , SDPConnectionError , SDPProtocolError ,
30+ CONN_SER_DIR , PLUGIN_ATTR_CB_ON_CONNECT , PLUGIN_ATTR_CB_ON_DISCONNECT , PLUGIN_ATTR_CONNECTION , PLUGIN_ATTR_CONN_AUTO_CONN , PLUGIN_ATTR_CONN_BINARY , PLUGIN_ATTR_CONN_CYCLE , PLUGIN_ATTR_CONN_RETRIES , PLUGIN_ATTR_CONN_TIMEOUT , PLUGIN_ATTR_SERIAL_BAUD , PLUGIN_ATTR_SERIAL_BSIZE , PLUGIN_ATTR_SERIAL_PARITY , PLUGIN_ATTR_SERIAL_PORT , PLUGIN_ATTR_SERIAL_STOP )
3031from lib .model .sdp .protocol import SDPProtocol
3132
3233from time import sleep
@@ -223,8 +224,10 @@ def _send_init_on_send(self):
223224 self .__syncsent = False
224225 empty_replies = 0
225226
226- self .logger .debug (f'communication initialized: { self ._is_initialized } ' )
227- return self ._is_initialized
227+ if not self ._is_initialized :
228+ raise SDPProtocolError ('P300 protocol initialization failed after 10 attempts' )
229+ self .logger .debug ('P300 communication initialized successfully' )
230+ return True
228231
229232 elif self ._viess_proto == 'KW' :
230233
@@ -249,80 +252,79 @@ def _send_init_on_send(self):
249252 return True
250253 sleep (.8 )
251254 attempt = attempt + 1
252- self .logger .error (f'sync not acquired after { attempt } attempts' )
253- self ._close ()
254- return False
255+ self .logger .error (f'KW sync not acquired after { attempt } attempts' )
256+ raise SDPProtocolError (f'KW protocol sync failed after { attempt } attempts' )
255257
256258 return True
257259
258260 def _send (self , data_dict , ** kwargs ):
259261 """
260- send data. data_dict needs to contain the following information:
262+ Send payload and return parsed response, or raise on any failure.
261263
262264 data_dict['payload']: address from/to which to read/write (hex, str)
263265 data_dict['data']['len']: length of command to send
264266 data_dict['data']['value']: value bytes to write, None if reading
265267
266268 :param data_dict: send data
267- :param read_response: KW only: read response value (True) or only return status byte
268269 :type data_dict: dict
269- :type read_response: bool
270- :return: Response packet (bytearray) if no error occured, None otherwise
270+ :return: Response bytes if read command, None if write command
271+ :raises SDPConnectionError: serial I/O failure or no response from device
272+ :raises SDPProtocolError: unexpected or invalid device response
271273 """
272274 if kwargs :
273275 self .logger .debug (f'got additional kw args { kwargs } ' )
274276
275277 (packet , responselen ) = self ._build_payload (data_dict )
276278
277- # send payload
278- self ._lock .acquire ()
279279 try :
280- self ._send_bytes (packet )
281- self .logger .debug (f'successfully sent packet { self ._bytes2hexstring (packet )} ' )
282-
283- # receive response
284- response_packet = bytearray ()
285- self .logger .debug (f'trying to receive { responselen } bytes of the response' )
286- chunk = self ._read_bytes (responselen )
287- if self ._viess_proto == 'P300' :
288- self .logger .debug (f'received { len (chunk )} bytes chunk of response as hexstring { self ._bytes2hexstring (chunk )} and as bytes { chunk } ' )
289- if len (chunk ) != 0 :
280+ with self ._lock :
281+ self ._send_bytes (packet )
282+ self .logger .debug (f'sent packet { self ._bytes2hexstring (packet )} ' )
283+
284+ chunk = self ._read_bytes (responselen )
285+ self .logger .debug (
286+ f'received { len (chunk )} bytes: '
287+ f'{ self ._bytes2hexstring (chunk ) if chunk else "empty" } '
288+ )
289+
290+ if self ._viess_proto == 'P300' :
291+ if len (chunk ) == 0 :
292+ raise SDPConnectionError ('no response from device after P300 command' )
290293 if chunk [:1 ] == self ._int2bytes (self ._controlset ['error' ], 1 ):
291- self .logger .error (f'interface returned error, response was { chunk } ' )
292- elif len (chunk ) == 1 and chunk [:1 ] == self ._int2bytes (self ._controlset ['not_initiated' ], 1 ):
293- self .logger .error ('received invalid chunk, connection not initialized, forcing re-initialize...' )
294- self ._initialized = False
295- elif chunk [:1 ] != self ._int2bytes (self ._controlset ['acknowledge' ], 1 ):
296- self .logger .error (f'received invalid chunk, not starting with ACK, response was { chunk } ' )
297- self .logger .warning ('encountered invalid chunk, maybe communication was lost, forcing re-initialize' )
298- self ._close ()
299- sleep (1 )
300- self ._open ()
301- else :
302- response_packet .extend (chunk )
303- return self ._parse_response (response_packet )
304- else :
305- self .logger .debug (f'received 0 bytes chunk - ignoring response_packet, chunk was { chunk } ' )
306- elif self ._viess_proto == 'KW' :
307- self .logger .debug (f'received { len (chunk )} bytes chunk of response as hexstring { self ._bytes2hexstring (chunk )} and as bytes { chunk } ' )
308- if len (chunk ) != 0 :
309- response_packet .extend (chunk )
310- return self ._parse_response (response_packet , data_dict ['data' ]['value' ] is None )
311- else :
312- self .logger .error ('received 0 bytes chunk - this probably is a communication error, possibly a wrong datapoint address?' )
313- except IOError as e :
314- self .logger .error (f'send_command_packet failed with IO error, trying to reconnect. Error was: { e } ' )
315- self ._close ()
294+ raise SDPProtocolError (
295+ f'device reported protocol error, response: { self ._bytes2hexstring (chunk )} '
296+ )
297+ if (len (chunk ) == 1 and
298+ chunk [:1 ] == self ._int2bytes (self ._controlset ['not_initiated' ], 1 )):
299+ self ._is_initialized = False
300+ raise SDPProtocolError (
301+ 'device reports not initialized; will re-initialize on next send'
302+ )
303+ if chunk [:1 ] != self ._int2bytes (self ._controlset ['acknowledge' ], 1 ):
304+ raise SDPProtocolError (
305+ f'unexpected P300 response (no ACK): { self ._bytes2hexstring (chunk )} '
306+ )
307+ return self ._parse_response (bytearray (chunk ))
308+
309+ elif self ._viess_proto == 'KW' :
310+ if len (chunk ) == 0 :
311+ raise SDPConnectionError ('no response from device after KW command' )
312+ return self ._parse_response (bytearray (chunk ), data_dict ['data' ]['value' ] is None )
313+
314+ except SDPError :
315+ self ._is_initialized = False
316+ try :
317+ self ._close ()
318+ except Exception :
319+ pass
320+ raise
316321 except Exception as e :
317- self .logger .error (f'send_command_packet failed with error: { e } ' )
318- finally :
322+ self ._is_initialized = False
319323 try :
320- self ._lock . release ()
321- except RuntimeError :
324+ self ._close ()
325+ except Exception :
322326 pass
323-
324- # if we didn't return with data earlier, we hit an error. Act accordingly
325- return None
327+ raise SDPConnectionError (f'unexpected error during send: { e } ' ) from e
326328
327329 def _parse_response (self , response , read_response = True ):
328330 """
@@ -343,11 +345,10 @@ def _parse_response(self, response, read_response=True):
343345 checksum = self ._calc_checksum (response [1 :len (response ) - 1 ]) # first, cut first byte (ACK) and last byte (checksum) and then calculate checksum
344346 received_checksum = response [len (response ) - 1 ]
345347 if received_checksum != checksum :
346- self .logger .error (f'calculated checksum { checksum } does not match received checksum of { received_checksum } ! Ignoring reponse, cycling connection' )
347- self ._close ()
348- sleep (1 )
349- self ._open ()
350- return None
348+ raise SDPProtocolError (
349+ f'P300 checksum mismatch: expected { checksum :#04x} , '
350+ f'got { received_checksum :#04x} '
351+ )
351352
352353 # Extract command/address, valuebytes and valuebytecount out of response
353354 responsetypecode = response [3 ] # 0x00 = query, 0x01 = reply, 0x03 = error
@@ -357,7 +358,7 @@ def _parse_response(self, response, read_response=True):
357358 # Extract databytes out of response
358359 rawdatabytes = bytearray ()
359360 rawdatabytes .extend (response [8 :8 + (valuebytecount )])
360- elif self ._protocol == 'KW' :
361+ elif self ._viess_proto == 'KW' :
361362
362363 # imitate P300 response code data for easier combined handling afterwards
363364 # a read_response telegram consists only of the value bytes
0 commit comments