Index: DialityCoreCanProtocol.py =================================================================== diff -u -r64f191141f9df20af78d559dc654ec0ad2aeda43 -r1351df158caa69c18c6a32d187ea305c71de8227 --- DialityCoreCanProtocol.py (.../DialityCoreCanProtocol.py) (revision 64f191141f9df20af78d559dc654ec0ad2aeda43) +++ DialityCoreCanProtocol.py (.../DialityCoreCanProtocol.py) (revision 1351df158caa69c18c6a32d187ea305c71de8227) @@ -21,6 +21,165 @@ from time import sleep +class DenaliMessage: + BYTE_ORDER = 'little' + + START_BYTE = 0xA5 + + START_INDEX = 0 + MSG_ID_INDEX = 1 + PAYLOAD_LENGTH_INDEX = 3 + PAYLOAD_START_INDEX = 4 + + + PAYLOAD_LENGTH_FIRST_PACKET = 4 + HEADER_LENGTH = 4 + + PACKET_LENGTH = 8 + + @staticmethod + def buildBasicMessage(channel_id=0, message=None): + """ + build a message using channel_id and dialin message + :param channel_id: integer with channel id + :param message: array of int with message + :return: dictionary with channel_id and message keys + """ + if message is None: + message = [] + return {'channel_id': channel_id, 'message': message} + + @staticmethod + def buildMessage(channel_id=0, message_id=0, payload=None): + """ + buildPacket builds a Diality Packet + + :param channel_id: is an integer with channel ID + :param message_id: is an integer indicating request ID + :param payload: list with payload. It does not include length + + :return dictionary with channel_id and 8-byte padded message + """ + + if payload is None: + payload = [] + message = [DenaliCanMessenger.START_BYTE] + + if 0 <= message_id <= (2 ** 16 - 1): + # Make sure an unsigned int was passed + message_id_in_bytes = message_id.to_bytes(2, byteorder=DenaliMessage.BYTE_ORDER) + + message += [message_id_in_bytes[0]] + message += [message_id_in_bytes[1]] + + else: + + return [] + + # Check payload length + payload_length = len(payload) + + # if payload is larger than 255 return nothing + if payload_length <= 255: + # payload has to be a list + message += [payload_length] + else: + return [] + + message += payload + + message = DenaliMessage.__padMessageWithZeros(message) + + return DenaliMessage.buildBasicMessage(channel_id=channel_id, message=message) + + @staticmethod + def __padMessageWithZeros(message): + """ + returns a packet padded with zeros that guarantees that the packet is a multiple of 8 bytes. + :param message: packet that may or may not be multiple of 8 bytes + + :return: packet that is 8-byte multiple + + """ + message_length = len(message) + + # message must be multiple of 8 + if message_length % DenaliMessage.PACKET_LENGTH != 0: + # We need to pad the message with trailing zeros + add_these_many_zeros = math.ceil(message_length / DenaliMessage.PACKET_LENGTH) * \ + DenaliMessage.PACKET_LENGTH - message_length + + message += [0] * add_these_many_zeros + + return message + + @staticmethod + def getChannelID(message): + """ + returns request ID from message + + :param message: dictionary with channel_id and message keys + :return: integer with channel id + + """ + + return message['channel_id'] + + @staticmethod + def getMessageID(message): + """ + returns request ID from packet + + :param message: complete Diality Packet + + :return: integer with request ID + + """ + msg_id_array = message['message'][DenaliMessage.MSG_ID_INDEX: + DenaliMessage.PAYLOAD_LENGTH_INDEX] + + return int.from_bytes(msg_id_array, byteorder=DenaliMessage.BYTE_ORDER) + + @staticmethod + def getPayloadLength(message): + """ + returns payload length from message + :param message: dictionary with channel_id and message keys + :return: a unsigned payload length + """ + + return message['message'][DenaliMessage.PAYLOAD_LENGTH_INDEX] + + @staticmethod + def getPayload(message): + """ + returns payload array from message + :param message: dictionary with channel_id and message keys + :return: a payload array if exist + """ + + payload_length = DenaliMessage.getPayloadLength(message) + + if payload_length == 0: + return None + else: + return message['message'][DenaliMessage.PAYLOAD_START_INDEX:] + + @staticmethod + def getTotalPackets(message, is_array=True): + """ + returns the number of packets needed to transmit Denali Message + :param message: dictionary with channel_id and message keys or raw message + :param is_array: True if message is an array and not a dictionary + :return: number of packets + """ + the_message = message if is_array else message['message'] + + return math.ceil((the_message[DenaliMessage.PAYLOAD_LENGTH_INDEX] + + DenaliMessage.HEADER_LENGTH) / + DenaliMessage.PACKET_LENGTH) + + class DenaliChannels: # This are all the channels available @@ -63,8 +222,8 @@ """ self.__message = can_message - self.__number_of_can_message_needed = math.ceil((can_message[3] + 4) / 8) - self.__number_of_can_message_up_to_now = 1 + self.__number_of_can_packets_needed = DenaliMessage.getTotalPackets(can_message) + self.__number_of_can_packets_up_to_now = 1 def push(self, can_message, first_packet=False): """ @@ -78,13 +237,13 @@ """ if first_packet: self.__message = can_message - self.__number_of_can_message_needed = math.ceil((can_message[3] + 4) / 8) - self.__number_of_can_message_up_to_now = 1 + self.__number_of_can_packets_needed = DenaliMessage.getTotalPackets(can_message) + self.__number_of_can_packets_up_to_now = 1 else: self.__message += can_message - self.__number_of_can_message_up_to_now += 1 + self.__number_of_can_packets_up_to_now += 1 - if self.__number_of_can_message_up_to_now == self.__number_of_can_message_needed: + if self.__number_of_can_packets_up_to_now == self.__number_of_can_packets_needed: return_message = self.__message self.__message = None return return_message @@ -93,8 +252,9 @@ class DenaliCanMessenger: - START_BYTE = 0xA5 + START_BYTE = DenaliMessage.START_BYTE + def __init__(self, can_interface='can0'): """ DenaliCanMessenger constructor @@ -158,10 +318,9 @@ while self.__run: - # message = self.__canConnection.recv(0.0) message = self.__listener_buffer.get_message(0.0) - if message is not None and message.dlc == 8: + if message is not None and message.dlc == DenaliMessage.PACKET_LENGTH: # We have received a legit can message of 8 bytes @@ -174,12 +333,14 @@ self.__dialinMessage = self.__longMessageBuilders[channel_id].push(can_data) - elif can_data[0] == self.START_BYTE and message_length <= 4: # This is a short packet + elif can_data[0] == self.START_BYTE and \ + message_length <= DenaliMessage.PAYLOAD_LENGTH_FIRST_PACKET: # This is a short packet # This is the first time that we are building a message self.__dialinMessage = can_data # deliver the packet - elif can_data[0] == self.START_BYTE and message_length > 4: # This is the start of a long packet + elif can_data[0] == self.START_BYTE and \ + message_length > DenaliMessage.PAYLOAD_LENGTH_FIRST_PACKET: # Long packet start # We are starting to build a long message, include it in the lonMsgChannelIDSet self.__longMsgChannelIDSet.add(channel_id) @@ -264,19 +425,18 @@ padded_can_message_array = built_message['message'] - padded_can_message_length = padded_can_message_array[3] - self.__sendPacketRequestID = DenaliMessage.getMessageID(built_message) # A message can be longer than 8 bytes, so we need to split it # into 8 bytes packets. - number_of_packets = math.ceil((padded_can_message_length + 5) / 8) + number_of_packets = DenaliMessage.getTotalPackets(padded_can_message_array) # We are sending one message at a time on CAN for n in range(number_of_packets): - packet = padded_can_message_array[n * 8:n * 8 + 8] + packet = padded_can_message_array[n * DenaliMessage.PACKET_LENGTH: + (n + 1) * DenaliMessage.PACKET_LENGTH] # Sending one packet at a time pckt = can.Message(arbitration_id=channel_id, @@ -301,139 +461,9 @@ return self.__dialinCommandResponseMessage -class DenaliMessage: - BYTE_ORDER = 'little' - - @staticmethod - def buildBasicMessage(channel_id=0, message=None): - """ - build a message using channel_id and dialin message - :param channel_id: integer with channel id - :param message: array of int with message - :return: dictionary with channel_id and message keys - """ - if message is None: - message = [] - return {'channel_id': channel_id, 'message': message} - - @staticmethod - def buildMessage(channel_id=0, message_id=0, payload=None): - """ - buildPacket builds a Diality Packet - - :param channel_id: is an integer with channel ID - :param message_id: is an integer indicating request ID - :param payload: list with payload. It does not include length - - :return dictionary with channel_id and 8-byte padded message - """ - - if payload is None: - payload = [] - message = [DenaliCanMessenger.START_BYTE] - - if 0 <= message_id <= (2 ** 16 - 1): - # Make sure an int was passed - message_id_in_bytes = message_id.to_bytes(2, byteorder=DenaliMessage.BYTE_ORDER) - - message += [message_id_in_bytes[0]] - message += [message_id_in_bytes[1]] - - else: - - return [] - - # Check payload length - payload_length = len(payload) - - # if payload is larger than 255 return nothing - if payload_length <= 255: - # payload has to be a list - message += [payload_length] - else: - return [] - - message += payload - - message = DenaliMessage.__padMessageWithZeros(message) - - return DenaliMessage.buildBasicMessage(channel_id=channel_id, message=message) - - @staticmethod - def __padMessageWithZeros(message): - """ - returns a packet padded with zeros that guarantees that the packet is a multiple of 8 bytes. - :param message: packet that may or may not be multiple of 8 bytes - - :return: packet that is 8-byte multiple - - """ - message_length = len(message) - - # message must be multiple of 8 - if message_length % 8 != 0: - # We need to pad the message with trailing zeros - add_these_many_zeros = math.ceil(message_length / 8) * 8 - message_length - - message += [0] * add_these_many_zeros - - return message - - @staticmethod - def getChannelID(message): - """ - returns request ID from message - - :param message: dictionary with channel_id and message keys - :return: integer with channel id - - """ - - return message['channel_id'] - - @staticmethod - def getMessageID(message): - """ - returns request ID from packet - - :param message: complete Diality Packet - - :return: integer with request ID - - """ - msg_id_array = message['message'][1:3] - - return int.from_bytes(msg_id_array, byteorder=DenaliMessage.BYTE_ORDER) - - @staticmethod - def getPayloadLength(message): - """ - returns payload length from message - :param message: dictionary with channel_id and message keys - :return: a unsigned payload length - """ - - return message['message'][4] - - @staticmethod - def getPayload(message): - """ - returns payload array from message - :param message: dictionary with channel_id and message keys - :return: a payload array if exist - """ - - payload_length = DenaliMessage.getPayloadLength(message) - - if payload_length == 0: - return None - else: - return message['message'][5:] - - def test_print_received_packet(messsage, sync=False): channel_id = messsage[0] - messsage[0] = 0xA5 + messsage[0] = DenaliMessage.START_BYTE introduction = "Received: "