def start_session(self): self.CURRENT_WINDOW = 0 total_size = len(self.MESSAGE) current_size = 0 logging = self.LOGGER is not None and self.LOGGER.JSON_FILE is not None if logging: self.LOGGER.START_SENDING_TIME = self.LOGGER.CHRONO.read() self.LOGGER.TOTAL_SIZE = total_size self.LOGGER.FINISHED = False while self.FRAGMENT_INDEX < len(self.FRAGMENTS): fragment = Fragment(self.PROFILE, self.FRAGMENTS[self.FRAGMENT_INDEX]) current_size += len(self.FRAGMENTS[self.FRAGMENT_INDEX][1]) percent = round(float(current_size) / float(total_size) * 100, 2) if logging: self.LOGGER.info("Sending...") self.LOGGER.LAPS.append(self.LOGGER.CHRONO.read()) self.LOGGER.debug("laps - > {}".format(self.LOGGER.LAPS)) self.LOGGER.debug("--------------------------") self.LOGGER.debug("{} fragment:".format(ordinal(self.FRAGMENT_INDEX))) self.LOGGER.debug("RuleID:{}, DTAG:{}, WINDOW:{}, FCN:{}".format(fragment.HEADER.RULE_ID, fragment.HEADER.DTAG, fragment.HEADER.W, fragment.HEADER.FCN)) self.LOGGER.debug("SCHC Fragment: {}".format(fragment.to_string())) self.LOGGER.debug("SCHC Fragment Payload: {}".format(fragment.PAYLOAD)) self.LOGGER.debug("{} / {}, {}%".format(current_size, total_size, percent)) try: self.schc_send(fragment) except SCHCError: if logging: self.LOGGER.FINISHED = False break if logging: self.LOGGER.END_SENDING_TIME = self.LOGGER.CHRONO.read() self.LOGGER.save()
def schc_send(self, fragment_sent, retransmit=False): ack = None current_fragment = {} logging = self.LOGGER is not None and self.LOGGER.JSON_FILE is not None self.TIMER.wait(timeout=self.DELAY, raise_exception=False) if logging: self.LOGGER.LOGGING_TIME += self.DELAY current_fragment = {'RULE_ID': fragment_sent.HEADER.RULE_ID, 'W': fragment_sent.HEADER.W, 'FCN': fragment_sent.HEADER.FCN, 'data': fragment_sent.to_bytes(), 'fragment_size': len(fragment_sent.to_bytes()), 'abort': False, 'sending_start': 0, 'sending_end': 0, 'send_time': 0, 'downlink_enable': False, 'timeout': 0, 'ack_received': False, 'ack': "", 'rssi': 0, 'receiver_abort_received': False, 'receiver_abort_message': ""} if fragment_sent.is_all_0() and not retransmit: self.LOGGER.debug("[POST] This is an All-0. Using All-0 SIGFOX_DL_TIMEOUT.") self.set_timeout(self.PROFILE.SIGFOX_DL_TIMEOUT) if logging: current_fragment["timeout"] = self.PROFILE.SIGFOX_DL_TIMEOUT elif fragment_sent.is_all_1(): self.LOGGER.debug("[POST] This is an All-1. Using RETRANSMISSION_TIMER_VALUE. Increasing ACK attempts.") self.ATTEMPTS += 1 self.set_timeout(self.PROFILE.RETRANSMISSION_TIMER_VALUE) if logging: current_fragment["timeout"] = self.PROFILE.RETRANSMISSION_TIMER_VALUE else: self.set_timeout(60) if logging: current_fragment["timeout"] = 60 # LoPy should use to_bytes() data = fragment_sent.to_hex().decode() self.LOGGER.info("[POST] Posting fragment {} ({})".format(fragment_sent.HEADER.to_string(), fragment_sent.to_hex())) if fragment_sent.expects_ack() and not retransmit: current_fragment['downlink_enable'] = True else: current_fragment['downlink_enable'] = False try: if logging: current_fragment['sending_start'] = self.LOGGER.CHRONO.read() self.send(data, loss=True) # self.send_mask(fragment_sent) if fragment_sent.expects_ack() and not retransmit: if logging: current_fragment['ack_received'] = False ack = self.recv(self.PROFILE.DOWNLINK_MTU // 8, loss=True) # ack = self.recv_mask() if logging: current_fragment['sending_end'] = self.LOGGER.CHRONO.read() current_fragment['send_time'] = current_fragment['sending_end'] - current_fragment['sending_start'] if ack is not None: current_fragment['rssi'] = self.PROTOCOL.rssi() self.LOGGER.debug("Response received at: {}: ".format(self.LOGGER.CHRONO.read())) self.LOGGER.debug('ack -> {}'.format(ack)) self.LOGGER.debug('message RSSI: {}'.format(self.PROTOCOL.rssi())) if fragment_sent.is_sender_abort(): self.LOGGER.debug("--- senderAbort:{}".format(fragment_sent.to_string())) self.LOGGER.debug("--- senderAbort:{}".format(fragment_sent.to_bytes())) if logging: current_fragment['abort'] = True self.LOGGER.FRAGMENTS_INFO_ARRAY.append(current_fragment) self.LOGGER.error("Sent Sender-Abort. Goodbye") self.LOGGER.SENDER_ABORTED = True raise SenderAbortError if not fragment_sent.expects_ack(): if not retransmit: self.FRAGMENT_INDEX += 1 if logging: self.LOGGER.FRAGMENTS_INFO_ARRAY.append(current_fragment) return if ack is not None: if logging: current_fragment['ack'] = ack current_fragment['ack_received'] = True self.LOGGER.info("[ACK] Bytes: {}. Ressetting attempts counter to 0.".format(ack)) self.ATTEMPTS = 0 # Parse ACK # ack_object = ACK.parse_from_bytes(self.PROFILE, ack) ack_object = ACK.parse_from_hex(self.PROFILE, ack) if ack_object.is_receiver_abort(): if logging: current_fragment['receiver_abort_message'] = ack current_fragment['receiver_abort_received'] = True self.LOGGER.FRAGMENTS_INFO_ARRAY.append(current_fragment) self.LOGGER.error("ERROR: Receiver Abort received. Aborting communication.") self.LOGGER.RECEIVER_ABORTED = True raise ReceiverAbortError if not fragment_sent.expects_ack(): self.LOGGER.error("ERROR: ACK received but not requested ({}).".format(ack)) self.LOGGER.FRAGMENTS_INFO_ARRAY.append(current_fragment) raise UnrequestedACKError # Extract data from ACK ack_window = ack_object.HEADER.W ack_window_number = ack_object.HEADER.WINDOW_NUMBER c = ack_object.HEADER.C bitmap = ack_object.BITMAP self.LOGGER.debug("ACK: {}".format(ack)) self.LOGGER.debug("ACK window: {}".format(ack_window)) self.LOGGER.debug("ACK bitmap: {}".format(bitmap)) self.LOGGER.debug("ACK C bit: {}".format(c)) self.LOGGER.debug("last window: {}".format(self.LAST_WINDOW)) # Save ACKREQ data self.LOGGER.FRAGMENTS_INFO_ARRAY.append(current_fragment) # If the W field in the SCHC ACK corresponds to the last window of the SCHC Packet: if ack_window_number == self.LAST_WINDOW: # If the C bit is set, the sender MAY exit successfully. if c == '1': self.LOGGER.info("Last ACK received, fragments reassembled successfully. End of transmission.") self.LOGGER.FINISHED = True self.FRAGMENT_INDEX += 1 return # Otherwise, else: # If the Profile mandates that the last tile be sent in an All-1 SCHC Fragment # (we are in the last window), .is_all_1() should be true: if fragment_sent.is_all_1(): # This is the last bitmap, it contains the data before the All-1 fragment. # The leftmost bit of this bitmap should always be 1, as the All-1 gets to the network # to request the ACK. last_bitmap = bitmap[:(len(self.FRAGMENTS) - 1) % self.PROFILE.WINDOW_SIZE] self.LOGGER.debug("last bitmap {}".format(last_bitmap)) # If the SCHC ACK shows no missing tile at the receiver, abort. # (C = 0 but transmission complete) if last_bitmap == '' or (last_bitmap[0] == '1' and is_monochar(last_bitmap)): self.LOGGER.error("ERROR: SCHC ACK shows no missing tile at the receiver.") self.schc_send(SenderAbort(fragment_sent.PROFILE, fragment_sent.HEADER)) # Otherwise (fragments are lost), else: # Check for lost fragments. for j in range(len(last_bitmap)): # If the j-th bit of the bitmap is 0, then the j-th fragment was lost. if last_bitmap[j] == '0': self.LOGGER.info("The {} ({} / {}) fragment was lost! " "Sending again...".format(ordinal(j), self.PROFILE.WINDOW_SIZE * ack_window_number + j, len(self.FRAGMENTS))) # Try sending again the lost fragment. fragment_to_be_resent = Fragment(self.PROFILE, self.FRAGMENTS[ self.PROFILE.WINDOW_SIZE * ack_window_number + j]) self.LOGGER.debug("Lost fragment: {}".format(fragment_to_be_resent.to_string())) self.schc_send(fragment_to_be_resent, retransmit=True) # Send All-1 again to end communication or check again for lost data. self.schc_send(fragment_sent) else: self.LOGGER.error("ERROR: While being at the last window, the ACK-REQ was not an All-1. " "This is outside of the Sigfox scope.") raise BadProfileError # Otherwise, there are lost fragments in a non-final window. else: # Check for lost fragments. for j in range(len(bitmap)): # If the j-th bit of the bitmap is 0, then the j-th fragment was lost. if bitmap[j] == '0': self.LOGGER.info("The {} ({} / {}) fragment was lost! " "Sending again...".format(ordinal(j), self.PROFILE.WINDOW_SIZE * ack_window_number + j, len(self.FRAGMENTS))) # Try sending again the lost fragment. fragment_to_be_resent = Fragment(self.PROFILE, self.FRAGMENTS[ self.PROFILE.WINDOW_SIZE * ack_window_number + j]) self.LOGGER.debug("Lost fragment: {}".format(fragment_to_be_resent.to_string())) self.schc_send(fragment_to_be_resent, retransmit=True) if fragment_sent.is_all_1(): # Send All-1 again to end communication. self.schc_send(fragment_sent) elif fragment_sent.is_all_0(): # Continue with next window self.FRAGMENT_INDEX += 1 self.CURRENT_WINDOW += 1 except SCHCTimeoutError as e: if logging: current_fragment['sending_end'] = self.LOGGER.CHRONO.read() current_fragment['send_time'] = current_fragment['sending_end'] - current_fragment['sending_start'] current_fragment['rssi'] = self.PROTOCOL.rssi() current_fragment['ack'] = "" current_fragment['ack_received'] = False self.LOGGER.info("OSError at: {}: ".format(self.LOGGER.CHRONO.read())) self.LOGGER.info('OSError number {}, {}'.format(e.args[0], e)) # Save ACKREQ data if logging: self.LOGGER.FRAGMENTS_INFO_ARRAY.append(current_fragment) # If an ACK was expected if fragment_sent.is_all_1(): # If the attempts counter is strictly less than MAX_ACK_REQUESTS, try again self.LOGGER.debug("attempts:{}".format(self.ATTEMPTS)) if self.ATTEMPTS < self.PROFILE.MAX_ACK_REQUESTS: self.LOGGER.info("SCHC Timeout reached while waiting for an ACK. Sending the ACK Request again...") self.schc_send(fragment_sent, retransmit=False) # Else, exit with an error. else: self.LOGGER.error("ERROR: MAX_ACK_REQUESTS reached. Sending Sender-Abort.") abort = SenderAbort(self.PROFILE, fragment_sent.HEADER) self.schc_send(abort) # If the ACK can be not sent (Sigfox only) if fragment_sent.is_all_0(): self.LOGGER.info("All-0 timeout reached. Proceeding to next window.") self.FRAGMENT_INDEX += 1 self.CURRENT_WINDOW += 1 # Else, Sigfox communication failed. else: self.LOGGER.error("ERROR: Timeout reached.") raise NetworkDownError