|
31 | 31 | PLUGIN_ATTR_CB_ON_DISCONNECT, PLUGIN_ATTR_CONNECTION,
|
32 | 32 | PLUGIN_ATTR_CONN_AUTO_CONN, PLUGIN_ATTR_CONN_CYCLE, PLUGIN_ATTR_CONN_RETRIES,
|
33 | 33 | PLUGIN_ATTR_CONN_TIMEOUT, PLUGIN_ATTR_MSG_REPEAT, PLUGIN_ATTR_MSG_TIMEOUT,
|
34 |
| - PLUGIN_ATTR_NET_HOST, PLUGIN_ATTR_NET_PORT) |
| 34 | + PLUGIN_ATTR_NET_HOST, PLUGIN_ATTR_NET_PORT, PLUGIN_ATTR_SEND_RETRIES, PLUGIN_ATTR_SEND_RETRIES_CYCLE) |
35 | 35 | from lib.model.sdp.connection import SDPConnection
|
36 | 36 |
|
37 | 37 | from collections import OrderedDict
|
@@ -97,9 +97,12 @@ def _close(self):
|
97 | 97 | self._connection.close()
|
98 | 98 | self._is_connected = False
|
99 | 99 |
|
100 |
| - def _send(self, data_dict): |
| 100 | + def _send(self, data_dict, **kwargs): |
101 | 101 | self.logger.debug(f'{self.__class__.__name__} _send called with {data_dict}')
|
102 |
| - return self._connection.send(data_dict) |
| 102 | + return self._connection.send(data_dict, **kwargs) |
| 103 | + |
| 104 | + def _check_reply(self, command, value): |
| 105 | + return False |
103 | 106 |
|
104 | 107 | def _get_connection(self, use_callbacks=False, name=None):
|
105 | 108 | conn_params = self._params.copy()
|
@@ -192,7 +195,7 @@ def on_data_received(self, connection, response):
|
192 | 195 | """
|
193 | 196 | Handle received data
|
194 | 197 |
|
195 |
| - Data is handed over as byte/bytearray and needs to be converted to |
| 198 | + Data is handed over as byte/bytearray and needs to be converted to |
196 | 199 | utf8 strings. As packets can be fragmented, all data is written into
|
197 | 200 | a buffer and then checked for complete json expressions. Those are
|
198 | 201 | separated, converted to dict and processed with respect to saved
|
@@ -335,7 +338,7 @@ def check_chunk(data):
|
335 | 338 | else:
|
336 | 339 | self.logger.debug(f'Skipping stale check {time() - self._last_stale_check} seconds after last check')
|
337 | 340 |
|
338 |
| - def _send(self, data_dict): |
| 341 | + def _send(self, data_dict, **kwargs): |
339 | 342 | """
|
340 | 343 | wrapper to prepare json rpc message to send. extracts command, id, repeat and
|
341 | 344 | params (data) from data_dict and call send_rpc_message(command, params, id, repeat)
|
@@ -425,3 +428,146 @@ def _send_rpc_message(self, command, ddict=None, message_id=None, repeat=0):
|
425 | 428 | response = self._connection.send(ddict)
|
426 | 429 | if response:
|
427 | 430 | self.on_data_received('request', response)
|
| 431 | + |
| 432 | + |
| 433 | +class SDPProtocolResend(SDPProtocol): |
| 434 | + """ Protocol supporting resend of command and checking reply_pattern |
| 435 | +
|
| 436 | + This class implements a protocol to resend commands if reply does not align with reply_pattern |
| 437 | +
|
| 438 | + """ |
| 439 | + |
| 440 | + def __init__(self, data_received_callback, name=None, **kwargs): |
| 441 | + |
| 442 | + # init super, get logger |
| 443 | + super().__init__(data_received_callback, name, **kwargs) |
| 444 | + # get relevant plugin parameters |
| 445 | + self._send_retries = int(self._params.get(PLUGIN_ATTR_SEND_RETRIES) or 0) |
| 446 | + self._send_retries_cycle = int(self._params.get(PLUGIN_ATTR_SEND_RETRIES_CYCLE) or 1) |
| 447 | + self._sending = {} |
| 448 | + self._sending_retries = {} |
| 449 | + self._sending_lock = threading.Lock() |
| 450 | + |
| 451 | + # tell someone about our actual class |
| 452 | + self.logger.debug(f'protocol initialized from {self.__class__.__name__}') |
| 453 | + |
| 454 | + def on_connect(self, by=None): |
| 455 | + """ |
| 456 | + When connecting, remove resend scheduler first. If send_retries is set > 0, add new scheduler with given cycle |
| 457 | + """ |
| 458 | + super().on_connect(by) |
| 459 | + self.logger.info(f'connect called, resending queue is {self._sending}') |
| 460 | + if self._plugin.scheduler_get('resend'): |
| 461 | + self._plugin.scheduler_remove('resend') |
| 462 | + self._sending = {} |
| 463 | + if self._send_retries >= 1: |
| 464 | + self._plugin.scheduler_add('resend', self.resend, cycle=self._send_retries_cycle) |
| 465 | + self.logger.dbghigh( |
| 466 | + f"Adding resend scheduler with cycle {self._send_retries_cycle}.") |
| 467 | + |
| 468 | + def on_disconnect(self, by=None): |
| 469 | + """ |
| 470 | + Remove resend scheduler on disconnect |
| 471 | + """ |
| 472 | + if self._plugin.scheduler_get('resend'): |
| 473 | + self._plugin.scheduler_remove('resend') |
| 474 | + self._sending = {} |
| 475 | + self.logger.info(f'disconnect called.') |
| 476 | + super().on_disconnect(by) |
| 477 | + |
| 478 | + def _send(self, data_dict, **kwargs): |
| 479 | + """ |
| 480 | + Send data, possibly return response |
| 481 | +
|
| 482 | + :param data_dict: dict with raw data and possible additional parameters to send |
| 483 | + :type data_dict: dict |
| 484 | + :param kwargs: additional information needed for checking the reply_pattern |
| 485 | + :return: raw response data if applicable, None otherwise. |
| 486 | + """ |
| 487 | + self._store_commands(kwargs.get('resend_info'), data_dict) |
| 488 | + self.logger.debug(f'Sending {data_dict}, kwargs {kwargs}') |
| 489 | + return self._connection.send(data_dict, **kwargs) |
| 490 | + |
| 491 | + def _store_commands(self, resend_info, data_dict): |
| 492 | + """ |
| 493 | + Store the command in _sending dict and the number of retries is _sending_retries dict |
| 494 | +
|
| 495 | + :param resend_info: dict with command, returnvalue and read_command |
| 496 | + :type resend_info: dict |
| 497 | + :param data_dict: dict with raw data and possible additional parameters to send |
| 498 | + :type data_dict: dict |
| 499 | + :param kwargs: additional information needed for checking the reply_pattern |
| 500 | + :return: False by default, True if returnvalue is given in resend_info |
| 501 | + :rtype: bool |
| 502 | + """ |
| 503 | + if resend_info is None: |
| 504 | + resend_info = {} |
| 505 | + else: |
| 506 | + resend_info['data_dict'] = data_dict |
| 507 | + if resend_info.get('returnvalue') is not None: |
| 508 | + self._sending.update({resend_info.get('command'): resend_info}) |
| 509 | + if resend_info.get('command') not in self._sending_retries: |
| 510 | + self._sending_retries.update({resend_info.get('command'): 0}) |
| 511 | + self.logger.debug(f'Saving {resend_info}, resending queue is {self._sending}') |
| 512 | + return True |
| 513 | + return False |
| 514 | + |
| 515 | + def _check_reply(self, command, value): |
| 516 | + """ |
| 517 | + Check if the command is in _sending dict and if response is same as expected or not |
| 518 | +
|
| 519 | + :param command: name of command |
| 520 | + :type command: str |
| 521 | + :param value: value the command (item) should be set to |
| 522 | + :type value: str |
| 523 | + :return: False by default, True if received expected response |
| 524 | + :rtype: bool |
| 525 | + """ |
| 526 | + returnvalue = False |
| 527 | + if command in self._sending: |
| 528 | + with self._sending_lock: |
| 529 | + # getting current retries for current command |
| 530 | + retry = self._sending_retries.get(command) |
| 531 | + # compare the expected returnvalue with the received value after aligning the type of both values |
| 532 | + compare = self._sending[command].get('returnvalue') |
| 533 | + if type(compare)(value) == compare: |
| 534 | + # if received value equals expexted value, remove command from _sending dict |
| 535 | + self._sending.pop(command) |
| 536 | + self._sending_retries.pop(command) |
| 537 | + self.logger.debug(f'Got correct response for {command}, ' |
| 538 | + f'removing from send. Resending queue is {self._sending}') |
| 539 | + returnvalue = True |
| 540 | + elif retry is not None and retry <= self._send_retries: |
| 541 | + # return False and log info if response is not the same as the expected response |
| 542 | + self.logger.debug(f'Should send again {self._sending}...') |
| 543 | + return returnvalue |
| 544 | + |
| 545 | + def resend(self): |
| 546 | + """ |
| 547 | + Resend function that is scheduled with a given cycle. |
| 548 | + Send command again if response is not as expected and retries are < given retry parameter |
| 549 | + If expected response is not received after given retries, give up sending and query value by sending read_command |
| 550 | + """ |
| 551 | + if self._sending: |
| 552 | + self.logger.debug(f"Resending queue is {self._sending}, retries {self._sending_retries}") |
| 553 | + with self._sending_lock: |
| 554 | + remove_commands = [] |
| 555 | + # Iterate through resend queue |
| 556 | + for command in list(self._sending.keys()): |
| 557 | + retry = self._sending_retries.get(command, 0) |
| 558 | + sent = True |
| 559 | + if retry < self._send_retries: |
| 560 | + self.logger.debug(f'Resending {command}, retries {retry}.') |
| 561 | + sent = self._send(self._sending[command].get("data_dict")) |
| 562 | + self._sending_retries[command] = retry + 1 |
| 563 | + elif retry >= self._send_retries: |
| 564 | + sent = False |
| 565 | + if sent is False: |
| 566 | + remove_commands.append(command) |
| 567 | + self.logger.info(f"Giving up re-sending {command} after {retry} retries.") |
| 568 | + if self._sending[command].get("read_cmd") is not None: |
| 569 | + self.logger.info(f"Querying current value.") |
| 570 | + self._send(self._sending[command].get("read_cmd")) |
| 571 | + for command in remove_commands: |
| 572 | + self._sending.pop(command) |
| 573 | + self._sending_retries.pop(command) |
0 commit comments