Ejemplo n.º 1
0
    def __init__(self, tx_id, rx_id, connection_mode=0, channel=None):
        """Sets up a CAN bus instance with a filter and a notifier.

        :param tx_id:
            Id for outgoing messages
        :param rx_id:
            Id for incoming messages
        :param connection_mode:
            Connection mode for the xcp-protocol. Only set if a custom mode is needed.
        :param channel:
            The channel for the can adapter. Only needed for Usb2can on Windows.
        """

        self._reader = BufferedReader()
        if platform.system() == "Windows":
            from can.interfaces.usb2can import Usb2canBus
            self._bus = Usb2canBus(channel=channel)
        else:
            from can.interface import Bus
            self._bus = Bus()
        self._bus.set_filters([{
            "can_id": rx_id,
            "can_mask": rx_id + 0x100,
            "extended": False
        }])
        self._notifier = Notifier(self._bus, [self._reader])
        self._tx_id = tx_id
        self._rx_id = rx_id
        self._conn_mode = connection_mode
Ejemplo n.º 2
0
class XCPFlash:
    """A Tool for flashing devices like electronic control units via the xcp-protocol."""

    _reader = None
    _bus = None
    _notifier = None
    _tx_id = 0
    _rx_id = 0
    _conn_mode = 0
    _data_len = 0
    _max_data_prg = 0
    _max_data = 8

    def __init__(self, tx_id, rx_id, connection_mode=0, channel=None):
        """Sets up a CAN bus instance with a filter and a notifier.

        :param tx_id:
            Id for outgoing messages
        :param rx_id:
            Id for incoming messages
        :param connection_mode:
            Connection mode for the xcp-protocol. Only set if a custom mode is needed.
        :param channel:
            The channel for the can adapter. Only needed for Usb2can on Windows.
        """

        self._reader = BufferedReader()
        if platform.system() == "Windows":
            from can.interfaces.usb2can import Usb2canBus
            self._bus = Usb2canBus(channel=channel)
        else:
            from can.interface import Bus
            self._bus = Bus()
        self._bus.set_filters([{
            "can_id": rx_id,
            "can_mask": rx_id + 0x100,
            "extended": False
        }])
        self._notifier = Notifier(self._bus, [self._reader])
        self._tx_id = tx_id
        self._rx_id = rx_id
        self._conn_mode = connection_mode

    @staticmethod
    def print_progress_bar(iteration,
                           total,
                           prefix='',
                           suffix='',
                           decimals=1,
                           length=50,
                           fill='█'):
        """Print a progress bar to the console.

        :param iteration:
            Actual iteration of the task
        :param total:
            Total iteration for completing the task
        :param prefix:
            Text before the progress bar
        :param suffix:
            Text after the progress bar
        :param decimals:
            Number of decimal places for displaying the percentage
        :param length:
            Line length for the progress bar
        :param fill:
            Char to fill the bar
        """
        percent = ("{0:." + str(decimals) + "f}").format(
            100 * (iteration / float(total)))
        filled_length = int(length * iteration // total)
        bar = fill * filled_length + '-' * (length - filled_length)
        sys.stdout.write('\r{} |{}| {}% {}'.format(prefix, bar, percent,
                                                   suffix))
        if iteration == total:
            print()

    def send_can_msg(self, msg, wait=True, timeout=30):
        """Send a message via can

        Send a message over the can-network and may wait for a given timeout for a response.

        :param msg:
            Message to send
        :param wait:
            True if the program should be waiting for a response, False otherwise
        :param timeout:
            Timeout for waiting for a response
        :return:
            The response if wait is set to True, nothing otherwise
        """

        self._bus.send(msg)
        if wait:
            try:
                return self.wait_for_response(timeout, msg)
            except ConnectionAbortedError as err:
                raise err

    def wait_for_response(self, timeout, msg=None):
        """Waiting for a response

        :param timeout:
            Time to wait
        :param msg:
            The message which was send. Set this only if a retry should be made in case of a timeout.
        :return:
            The response from the device
        :raises: ConnectionAbortedError
            if the response is an error
        """

        tries = 1
        while True:
            if tries == 5:
                raise ConnectionAbortedError("Timeout")
            received = self._reader.get_message(timeout)
            if received is None and msg is not None:
                self.send_can_msg(msg, False)
                tries += 1
                continue
            if received is None:
                continue
            if received.arbitration_id == self._rx_id and received.data[
                    0] == XCPResponses.SUCCESS.value:
                return received
            elif received.arbitration_id == self._rx_id and received.data[
                    0] == XCPResponses.ERROR.value:
                raise ConnectionAbortedError(received.data[1])
            elif msg is not None:
                self.send_can_msg(msg, False)

    def execute(self, command, **kwargs):
        """Execute a command

        Builds the can-message to execute the given command and sends it to the device.

        :param command:
            The xcp-command to be executed
        :param kwargs:
            Needed arguments for the command.
                :command SET_MTA:
                    'addr_ext' and 'addr'
                :command PROGRAM_CLEAR:
                    'range'
                :command PROGRAM:
                    'size' and 'data'
        :return: response of the command if waited for
        """

        msg = Message(arbitration_id=self._tx_id,
                      is_extended_id=False,
                      data=bytes(8))
        msg.data[0] = command.value
        if command == XCPCommands.CONNECT:
            msg.data[1] = self._conn_mode
            response = self.send_can_msg(msg)
            self._max_data = response.data[4] << 8
            self._max_data += response.data[5]
            return response
        if command == XCPCommands.DISCONNECT:
            return self.send_can_msg(msg)
        if command == XCPCommands.SET_MTA:
            msg.data[3] = kwargs.get('addr_ext', 0)
            for i in range(4, 8):
                msg.data[i] = (kwargs['addr'] &
                               (0xFF000000 >> (8 * (i - 4)))) >> (8 * (7 - i))
            return self.send_can_msg(msg)
        if command == XCPCommands.PROGRAM_START:
            response = self.send_can_msg(msg)
            max_dto_prg = response.data[3]
            max_bs = response.data[4]
            self._data_len = (max_dto_prg - 2) * max_bs
            self._max_data_prg = max_dto_prg - 2
            return response
        if command == XCPCommands.PROGRAM_CLEAR:
            for i in range(4, 8):
                msg.data[i] = (kwargs['range'] &
                               (0xFF000000 >> (8 * (i - 4)))) >> (8 * (7 - i))
            return self.send_can_msg(msg)
        if command == XCPCommands.PROGRAM or command == XCPCommands.PROGRAM_NEXT:
            msg.data[1] = kwargs["size"]
            position = 2
            for data in kwargs["data"]:
                msg.data[position] = data
                position += 1
            return self.send_can_msg(msg, kwargs['size'] <= self._max_data_prg)
        if command == XCPCommands.PROGRAM_RESET:
            return self.send_can_msg(msg)

    def program(self, data):
        """Program the device

        Program the device with the given firmware

        :param data:
            the firmware as byte-array
        """
        print("flashing new firmware...")
        bytes_send = 0
        while bytes_send < len(data):
            send_length = self._data_len
            if bytes_send % 10000 <= self._data_len:
                self.print_progress_bar(bytes_send,
                                        len(data),
                                        prefix="Progress:",
                                        suffix="Complete")
                sys.stdout.flush()
            if send_length > len(data) - bytes_send:
                send_length = len(data) - bytes_send
            self.execute(XCPCommands.PROGRAM,
                         size=send_length,
                         data=data[bytes_send:bytes_send + self._max_data_prg])
            send_length -= self._max_data_prg
            bytes_send += min(send_length, self._max_data_prg)
            while send_length > 0:
                self.execute(XCPCommands.PROGRAM_NEXT,
                             size=send_length,
                             data=data[bytes_send:bytes_send +
                                       self._max_data_prg])
                bytes_send += min(send_length, self._max_data_prg)
                send_length -= self._max_data_prg
        self.print_progress_bar(bytes_send,
                                len(data),
                                prefix="Progress:",
                                suffix="Complete")
        self.execute(XCPCommands.PROGRAM_RESET)

    def clear(self, start_addr, length):
        """Clear the memory of the device

        Erase all contents of a given range in the device memory.

        :param start_addr:
            Start address of the range
        :param length:
            Length of the range
        """
        print("erasing device (this may take several minutes)...")
        self.execute(XCPCommands.PROGRAM_START)
        self.execute(XCPCommands.SET_MTA, addr=start_addr)
        self.execute(XCPCommands.PROGRAM_CLEAR, range=length)

    def connect(self):
        """Connect to the device

        :raises: ConnectionError
            if the device doesn't support flash programming or the address granularity is > 1
        """
        print("connecting...")
        response = self.execute(XCPCommands.CONNECT)
        if not response.data[1] & 0b00010000:
            raise ConnectionError(
                "Flash programming not supported by the connected device")
        if response.data[2] & 0b00000110:
            raise ConnectionError("Address granularity > 1 not supported")

    def disconnect(self):
        """Disconnect from the device"""
        print("disconnecting..")
        self.execute(XCPCommands.DISCONNECT)

    def __call__(self, start_addr, data):
        """Flash the device

        Do all the necessary steps for flashing, including connecting to the device and clearing the memory.

        :param start_addr:
            Start address for the firmware
        :param data:
            The firmware as byte-array
        """

        try:
            self.connect()
            self.clear(start_addr, len(data))
            self.program(data)
        except ConnectionAbortedError as err:
            if err.args[0] == "Timeout":
                print("\nConnection aborted: Timeout")
            else:
                print("\nConnection aborted: {}".format(
                    XCPErrors.error_messages[err.args[0]]))
        except ConnectionError as err:
            print("\nConnection error: {}".format(err))
        finally:
            try:
                self.disconnect()
            except ConnectionAbortedError as err:
                if err.args[0] == "Timeout":
                    print("\nConnection aborted: Timeout")
                else:
                    print("\nConnection aborted: {}".format(
                        XCPErrors.error_messages[err.args[0]]))
Ejemplo n.º 3
0
    def run(self):
        need_run = True
        while need_run:
            bus_notifier = None
            poller = None
            try:
                interface = self.__bus_conf["interface"]
                channel = self.__bus_conf["channel"]
                kwargs = self.__bus_conf["backend"]
                self.__bus = ThreadSafeBus(interface=interface,
                                           channel=channel,
                                           **kwargs)
                reader = BufferedReader()
                bus_notifier = Notifier(self.__bus, [reader])

                log.info("[%s] Connected to CAN bus (interface=%s,channel=%s)",
                         self.get_name(), interface, channel)

                if self.__polling_messages:
                    poller = Poller(self)
                    # Poll once to check if network is not down.
                    # It would be better to have some kind of a ping message to check a bus state.
                    poller.poll()

                # Initialize the connected flag and reconnect count only after bus creation and sending poll messages.
                # It is expected that after these operations most likely the bus is up.
                self.__connected = True
                self.__reconnect_count = 0

                while not self.__stopped:
                    message = reader.get_message()
                    if message is not None:
                        # log.debug("[%s] New CAN message received %s", self.get_name(), message)
                        self.__process_message(message)
                    self.__check_if_error_happened()
            except Exception as e:
                log.error("[%s] Error on CAN bus: %s", self.get_name(), str(e))
            finally:
                try:
                    if poller is not None:
                        poller.stop()
                    if bus_notifier is not None:
                        bus_notifier.stop()
                    if self.__bus is not None:
                        log.debug(
                            "[%s] Shutting down connection to CAN bus (state=%s)",
                            self.get_name(), self.__bus.state)
                        self.__bus.shutdown()
                except Exception as e:
                    log.error(
                        "[%s] Error on shutdown connection to CAN bus: %s",
                        self.get_name(), str(e))

                self.__connected = False

                if not self.__stopped:
                    if self.__is_reconnect_enabled():
                        retry_period = self.__reconnect_conf["period"]
                        log.info(
                            "[%s] Next attempt to connect will be in %f seconds (%s attempt left)",
                            self.get_name(), retry_period, "infinite"
                            if self.__reconnect_conf["maxCount"] is None else
                            self.__reconnect_conf["maxCount"] -
                            self.__reconnect_count + 1)
                        time.sleep(retry_period)
                    else:
                        need_run = False
                        log.info(
                            "[%s] Last attempt to connect has failed. Exiting...",
                            self.get_name())
                else:
                    need_run = False
        log.info("[%s] Stopped", self.get_name())