File: canutils.py

package info (click to toggle)
python-can 3.0.0%2Bgithub-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 1,892 kB
  • sloc: python: 8,014; makefile: 29; sh: 12
file content (143 lines) | stat: -rw-r--r-- 4,922 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# coding: utf-8

"""
This module works with CAN data in ASCII log files (*.log).
It is is compatible with "candump -L" from the canutils program
(https://github.com/linux-can/can-utils).
"""

from __future__ import absolute_import, division

import time
import datetime
import logging

from can.message import Message
from can.listener import Listener
from .generic import BaseIOHandler


log = logging.getLogger('can.io.canutils')

CAN_MSG_EXT         = 0x80000000
CAN_ERR_FLAG        = 0x20000000
CAN_ERR_BUSERROR    = 0x00000080
CAN_ERR_DLC         = 8


class CanutilsLogReader(BaseIOHandler):
    """
    Iterator over CAN messages from a .log Logging File (candump -L).

    .. note::
        .log-format looks for example like this:

        ``(0.0) vcan0 001#8d00100100820100``
    """

    def __init__(self, file):
        """
        :param file: a path-like object or as file-like object to read from
                     If this is a file-like object, is has to opened in text
                     read mode, not binary read mode.
        """
        super(CanutilsLogReader, self).__init__(file, mode='r')

    def __iter__(self):
        for line in self.file:

            # skip empty lines
            temp = line.strip()
            if not temp:
                continue

            timestamp, channel, frame = temp.split()
            timestamp = float(timestamp[1:-1])
            canId, data = frame.split('#')
            if channel.isdigit():
                channel = int(channel)

            if len(canId) > 3:
                isExtended = True
            else:
                isExtended = False
            canId = int(canId, 16)

            if data and data[0].lower() == 'r':
                isRemoteFrame = True
                if len(data) > 1:
                    dlc = int(data[1:])
                else:
                    dlc = 0
            else:
                isRemoteFrame = False

                dlc = len(data) // 2
                dataBin = bytearray()
                for i in range(0, len(data), 2):
                    dataBin.append(int(data[i:(i + 2)], 16))

            if canId & CAN_ERR_FLAG and canId & CAN_ERR_BUSERROR:
                msg = Message(timestamp=timestamp, is_error_frame=True)
            else:
                msg = Message(timestamp=timestamp, arbitration_id=canId & 0x1FFFFFFF,
                              extended_id=isExtended, is_remote_frame=isRemoteFrame,
                              dlc=dlc, data=dataBin, channel=channel)
            yield msg

        self.stop()


class CanutilsLogWriter(BaseIOHandler, Listener):
    """Logs CAN data to an ASCII log file (.log).
    This class is is compatible with "candump -L".

    If a message has a timestamp smaller than the previous one (or 0 or None),
    it gets assigned the timestamp that was written for the last message.
    It the first message does not have a timestamp, it is set to zero.
    """

    def __init__(self, file, channel="vcan0", append=False):
        """
        :param file: a path-like object or as file-like object to write to
                     If this is a file-like object, is has to opened in text
                     write mode, not binary write mode.
        :param channel: a default channel to use when the message does not
                        have a channel set
        :param bool append: if set to `True` messages are appended to
                            the file, else the file is truncated
        """
        mode = 'a' if append else 'w'
        super(CanutilsLogWriter, self).__init__(file, mode=mode)

        self.channel = channel
        self.last_timestamp = None

    def on_message_received(self, msg):
        # this is the case for the very first message:
        if self.last_timestamp is None:
            self.last_timestamp = (msg.timestamp or 0.0)

        # figure out the correct timestamp
        if msg.timestamp is None or msg.timestamp < self.last_timestamp:
            timestamp = self.last_timestamp
        else:
            timestamp = msg.timestamp

        channel = msg.channel if msg.channel is not None else self.channel

        if msg.is_error_frame:
            self.file.write("(%f) %s %08X#0000000000000000\n" % (timestamp, channel, CAN_ERR_FLAG | CAN_ERR_BUSERROR))

        elif msg.is_remote_frame:
            if msg.is_extended_id:
                self.file.write("(%f) %s %08X#R\n" % (timestamp, channel, msg.arbitration_id))
            else:
                self.file.write("(%f) %s %03X#R\n" % (timestamp, channel, msg.arbitration_id))

        else:
            data = ["{:02X}".format(byte) for byte in msg.data]
            if msg.is_extended_id:
                self.file.write("(%f) %s %08X#%s\n" % (timestamp, channel, msg.arbitration_id, ''.join(data)))
            else:
                self.file.write("(%f) %s %03X#%s\n" % (timestamp, channel, msg.arbitration_id, ''.join(data)))