├── .gitignore ├── CONTRIBUTING.rst ├── LICENSE.rst ├── MANIFEST.in ├── README.rst ├── docs └── getting-started.rst ├── examples ├── autodiscover-server.py ├── cantact.py ├── decode.py ├── example_db.json ├── killbus.py ├── monitorall.py ├── obd-get-supported.py ├── obd-read-pid.py ├── reset_mil.py ├── sample_uds_log.txt └── uds_log_decode.py ├── pyvit ├── __init__.py ├── bus.py ├── can.py ├── dispatch.py ├── file │ ├── __init__.py │ ├── db │ │ ├── __init__.py │ │ └── jsondb.py │ └── log │ │ ├── __init__.py │ │ └── candump.py ├── hw │ ├── __init__.py │ ├── cantact.py │ ├── logplayer.py │ ├── loopback.py │ ├── obdlinksx.py │ ├── peak.py │ └── socketcan.py ├── log.py ├── proto │ ├── __init__.py │ ├── isotp.py │ ├── isotpAddressing.py │ ├── obdii.py │ └── uds.py └── utils │ ├── __init__.py │ └── queue.py ├── requirements.txt ├── setup.py └── test ├── __init__.py ├── can_test.py ├── dispatch_test.py ├── file_candump_test.py ├── isotp_test.py ├── json_db_test.py ├── log_test.py ├── loopback_test.py ├── uds_test.py └── vector_jsondb.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.pyo 4 | *.swp 5 | *.swo 6 | *.egg 7 | *.egg-info 8 | build/* 9 | dist/* 10 | MANIFEST 11 | examples/*.log 12 | .idea/ 13 | .vscode/ 14 | venv/ -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | Contribution Guide 2 | ================== 3 | 4 | pyvit is a GPLv3 licensed product. 5 | 6 | We welcome and encourage community contributions to pyvit. 7 | 8 | 9 | Developer’s Certificate of Origin 10 | --------------------------------- 11 | 12 | All contributions must include acceptance of the DCO: 13 | 14 | .. code-block:: text 15 | Developer Certificate of Origin 16 | Version 1.1 17 | 18 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 19 | 660 York Street, Suite 102, 20 | San Francisco, CA 94110 USA 21 | 22 | Everyone is permitted to copy and distribute verbatim copies of this 23 | license document, but changing it is not allowed. 24 | 25 | 26 | Developer's Certificate of Origin 1.1 27 | 28 | By making a contribution to this project, I certify that: 29 | 30 | (a) The contribution was created in whole or in part by me and I 31 | have the right to submit it under the open source license 32 | indicated in the file; or 33 | 34 | (b) The contribution is based upon previous work that, to the best 35 | of my knowledge, is covered under an appropriate open source 36 | license and I have the right under that license to submit that 37 | work with modifications, whether created in whole or in part 38 | by me, under the same open source license (unless I am 39 | permitted to submit under a different license), as indicated 40 | in the file; or 41 | 42 | (c) The contribution was provided directly to me by some other 43 | person who certified (a), (b) or (c) and I have not modified 44 | it. 45 | 46 | (d) I understand and agree that this project and the contribution 47 | are public and that a record of the contribution (including all 48 | personal information I submit with it, including my sign-off) is 49 | maintained indefinitely and may be redistributed consistent with 50 | this project or the open source license(s) involved. 51 | 52 | To accept the DCO, simply add this line to each commit message with your name 53 | and email address (``git commit -s`` will do this for you): 54 | 55 | ``Signed-off-by: Jane Example `` 56 | -------------------------------------------------------------------------------- /LICENSE.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | License 3 | ======== 4 | 5 | pyvit is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | include *.rst 3 | recursive-include docs *.txt 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======================================= 2 | pyvit: Python Vehicle Interface Toolkit 3 | ======================================= 4 | 5 | .. image:: https://circleci.com/gh/linklayer/pyvit/tree/master.svg?style=shield 6 | :target: https://circleci.com/gh/linklayer/pyvit/tree/master 7 | 8 | .. image:: https://api.codacy.com/project/badge/Grade/3b486b7cae5d40bd95fbed7ae1ad59fd 9 | :target: https://www.codacy.com/app/linklayer/pyvit?utm_source=github.com&utm_medium=referral&utm_content=linklayer/pyvit&utm_campaign=Badge_Grade 10 | 11 | pyvit is a toolkit for interfacing with cars from Python. It aims to implement 12 | common hardware interfaces and protocols used in the automotive systems. 13 | 14 | Getting Started 15 | --------------- 16 | 17 | pyvit can be installed with pip: ``pip install pyvit``. 18 | 19 | See the `Getting Started`_ document for detailed instructions. 20 | 21 | .. _`Getting Started`: https://github.com/linklayer/pyvit/blob/master/docs/getting-started.rst 22 | 23 | Contributing 24 | ------------ 25 | 26 | For information on contributing, please see the `contribution guidelines`_. 27 | 28 | .. _`contribution guidelines`: https://github.com/linklayer/pyvit/blob/master/CONTRIBUTING.rst 29 | -------------------------------------------------------------------------------- /docs/getting-started.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Getting Started 3 | =============== 4 | 5 | Using a CANtact 6 | =============== 7 | 8 | The CANtact_ tool is directly supported by pyvit. It should work on Windows, 9 | OS X, and Linux. 10 | 11 | .. _CANtact: http://cantact.io/ 12 | 13 | Example 14 | ------- 15 | 16 | This examples goes on bus and prints received messages: 17 | 18 | .. code:: python 19 | 20 | from pyvit import can 21 | from pyvit.hw.cantact import CantactDev 22 | 23 | dev = CantactDev("/dev/cu.usbmodem1451") 24 | dev.set_bitrate(500000) 25 | dev.start() 26 | while True: 27 | print(dev.recv()) 28 | 29 | You will need to set the serial port (``/dev/cu.usbmodem1451`` in this example) 30 | correctly. 31 | 32 | SocketCAN 33 | ========= 34 | 35 | SocketCAN interfaces are supported, however they are only available on Linux. 36 | Using SocketCAN requires Python 3+ 37 | 38 | Example 39 | ------- 40 | 41 | The device can now be accessed as a ``SocketCanDev``. This examples goes on bus 42 | and prints received messages: 43 | 44 | .. code:: python 45 | 46 | from pyvit import can 47 | from pyvit.hw import socketcan 48 | 49 | dev = socketcan.SocketCanDev("can0") 50 | 51 | dev.start() 52 | while True: 53 | print(dev.recv()) 54 | 55 | .. _`Linux driver`: http://www.peak-system.com/fileadmin/media/linux/index.htm#download 56 | 57 | Using Peak CAN Tools 58 | ==================== 59 | 60 | Peak CAN tools (also known as GridConnect) are support through SocketCAN. This 61 | functionality is only available on Linux 62 | 63 | For kernels 3.6 and newer, skip to step 5. 64 | 65 | 1. Download the Peak `Linux driver`_. 66 | 67 | 2. Install dependancies:: 68 | 69 | sudo apt-get install libpopt-dev 70 | 71 | 3. Build the driver:: 72 | 73 | cd peak-linux-driver-x.xx 74 | make 75 | sudo make install 76 | 77 | 4. Enable the driver:: 78 | 79 | sudo modprobe pcan 80 | 81 | 5. Connect a Peak CAN tool, ensure it appears in ``/proc/pcan``. Note the 82 | network device name (ie, ``can0``) 83 | 84 | 6. Bring the corresponding network up:: 85 | 86 | sudo ifconfig can0 up 87 | -------------------------------------------------------------------------------- /examples/autodiscover-server.py: -------------------------------------------------------------------------------- 1 | from pyvit.hw.socketcan import SocketCanDev 2 | from pyvit import can 3 | 4 | d = SocketCanDev('vcan0') 5 | d.start() 6 | 7 | while True: 8 | frame = d.recv() 9 | if frame.arb_id == 0x705: 10 | resp = can.Frame(0x725) 11 | resp.data = [0x02, 0x41] 12 | d.send(resp) 13 | -------------------------------------------------------------------------------- /examples/cantact.py: -------------------------------------------------------------------------------- 1 | from pyvit.hw import cantact 2 | import sys 3 | 4 | dev = cantact.CantactDev(sys.argv[1]) 5 | 6 | dev.ser.write('S0\r'.encode()) 7 | dev.start() 8 | count = 0 9 | while True: 10 | count = count + 1 11 | frame = dev.recv() 12 | dev.send(frame) 13 | print("%d: %s" % (count, str(frame))) 14 | -------------------------------------------------------------------------------- /examples/decode.py: -------------------------------------------------------------------------------- 1 | from pyvit.file import jsondb 2 | from pyvit.hw import socketcan 3 | 4 | parser = jsondb.JsonDbParser() 5 | b = parser.parse('examples/example_db.json') 6 | 7 | dev = socketcan.SocketCanDev('vcan0') 8 | dev.start() 9 | 10 | while True: 11 | frame = dev.recv() 12 | signals = b.parse_frame(frame) 13 | if signals: 14 | for s in signals: 15 | print(s) 16 | -------------------------------------------------------------------------------- /examples/example_db.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages": [ 3 | { 4 | "name": "test msg", 5 | "id": "0x100", 6 | "signals": {"0": {"name": "Engine RPM", "bit_length": 16, 7 | "factor": 2, "offset": 1}, 8 | "16": {"name": "Gear", "bit_length": 3}, 9 | "19": {"name": "Battery Voltage", "bit_length": 5} 10 | } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /examples/killbus.py: -------------------------------------------------------------------------------- 1 | from pyvit import can 2 | from pyvit.hw import socketcan 3 | 4 | dev = socketcan.SocketCanDev("vcan0") 5 | 6 | dev.start() 7 | 8 | frame = can.Frame(0) 9 | frame.data = [0] * 8 10 | 11 | while True: 12 | dev.send(frame) 13 | -------------------------------------------------------------------------------- /examples/monitorall.py: -------------------------------------------------------------------------------- 1 | # from pyvit import can 2 | # from pyvit.hw.cantact import CantactDev 3 | 4 | from pyvit.hw import cantact 5 | 6 | dev = cantact.CantactDev("/dev/tty.usbmodemFA131") 7 | dev.set_bitrate(500000) 8 | 9 | dev.start() 10 | while True: 11 | print(dev.recv()) 12 | -------------------------------------------------------------------------------- /examples/obd-get-supported.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from pyvit.proto.obdii import ObdInterface 4 | from pyvit.hw.cantact import CantactDev 5 | from pyvit.dispatch import Dispatcher 6 | 7 | if len(sys.argv) != 2 and len(sys.argv) != 4: 8 | print("usage: %s CANtact_device [tx_arb_id] [rx_arb_id]" % 9 | sys.argv[0]) 10 | sys.exit(1) 11 | 12 | # set up a CANtact device 13 | dev = CantactDev(sys.argv[1]) 14 | dev.set_bitrate(500000) 15 | 16 | # create our dispatcher and OBD interface 17 | disp = Dispatcher(dev) 18 | 19 | # if we were given tx and rx ids, set them, otherwise use defaults 20 | if len(sys.argv) > 4: 21 | tx_arb_id = int(sys.argv[4], 0) 22 | rx_arb_id = int(sys.argv[5], 0) 23 | else: 24 | # functional address 25 | tx_arb_id = 0x7DF 26 | # physical response ID of ECM ECU, responses from other ECUs will be ignored 27 | rx_arb_id = 0x7E8 28 | 29 | obd = ObdInterface(disp, tx_arb_id, rx_arb_id) 30 | 31 | # setting debug to true will print all frames sent and received 32 | obd.debug = False 33 | disp.start() 34 | 35 | print("Supported PIDs (mode 1):", obd.get_supported_pids(1)) 36 | print("Supported PIDs (mode 9):", obd.get_supported_pids(9)) 37 | 38 | disp.stop() 39 | -------------------------------------------------------------------------------- /examples/obd-read-pid.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from pyvit.proto.obdii import ObdInterface 4 | from pyvit.hw.cantact import CantactDev 5 | from pyvit.dispatch import Dispatcher 6 | 7 | if len(sys.argv) != 4 and len(sys.argv) != 6: 8 | print("usage: %s CANtact_device mode PID [tx_arb_id] [rx_arb_id]" % 9 | sys.argv[0]) 10 | sys.exit(1) 11 | 12 | # set up a CANtact device 13 | dev = CantactDev(sys.argv[1]) 14 | dev.set_bitrate(500000) 15 | 16 | # create our dispatcher and OBD interface 17 | disp = Dispatcher(dev) 18 | 19 | # if we were given tx and rx ids, set them, otherwise use defaults 20 | if len(sys.argv) > 4: 21 | tx_arb_id = int(sys.argv[4], 0) 22 | rx_arb_id = int(sys.argv[5], 0) 23 | else: 24 | # functional address 25 | tx_arb_id = 0x7DF 26 | rx_arb_id = 0x7E8 27 | 28 | obd = ObdInterface(disp, tx_arb_id, rx_arb_id) 29 | 30 | # setting debug to true will print all frames sent and received 31 | obd.debug = False 32 | disp.start() 33 | 34 | # make request using provided mode and PID 35 | mode = int(sys.argv[2], 0) 36 | pid = int(sys.argv[3], 0) 37 | resp = obd.request(mode, pid) 38 | disp.stop() 39 | 40 | if resp is None: 41 | print("No data received") 42 | sys.exit(1) 43 | 44 | print("OBD response for Mode %d, PID 0x%X: %s" % (mode, pid, resp)) 45 | 46 | # print as hex and ASCII 47 | asc_str = "" 48 | hex_str = "" 49 | for c in resp: 50 | asc_str += chr(c) 51 | hex_str += "%02X " % c 52 | print("Hex: %s" % hex_str) 53 | print("ASCII: %s\n" % asc_str) 54 | -------------------------------------------------------------------------------- /examples/reset_mil.py: -------------------------------------------------------------------------------- 1 | from pyvit import can 2 | from pyvit.hw import socketcan 3 | 4 | dev = socketcan.SocketCanDev("vcan0") 5 | 6 | dev.start() 7 | 8 | frame = can.Frame(0x7DF) 9 | 10 | # frame data 11 | # byte 0: number of additional bytes (1) 12 | # byte 1: mode. mode 04 clears MIL 13 | # other bytes not used, set to 0x55 14 | frame.data = [1, 4, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55] 15 | 16 | dev.send(frame) 17 | -------------------------------------------------------------------------------- /examples/sample_uds_log.txt: -------------------------------------------------------------------------------- 1 | (37.167999) can0 6E0#0210030000000000 2 | (37.178001) can0 51C#065003002800C800 3 | (43.181999) can0 6E0#0210030000000000 4 | (43.194000) can0 51C#065003002800C800 5 | (43.222000) can0 6E0#0322F10000000000 6 | (43.234001) can0 51C#0762F10000050103 7 | (43.263000) can0 6E0#0322F13200000000 8 | (43.293999) can0 51C#037F227800050103 9 | (43.324001) can0 51C#100D62F132363832 10 | (43.342999) can0 6E0#3000000000000000 11 | (43.363998) can0 51C#2133333533354143 12 | (43.402000) can0 6E0#0322F15000000000 13 | (43.433998) can0 51C#037F227833354143 14 | (43.464001) can0 51C#0662F15013080043 15 | (43.502998) can0 6E0#0322F15100000000 16 | (43.534000) can0 51C#037F227813080043 17 | (43.584000) can0 51C#100C62F151123100 18 | (43.602001) can0 6E0#3000000000000000 19 | (43.625000) can0 51C#2114310014310000 20 | (43.783001) can0 6E0#0210030000000000 21 | (43.792999) can0 51C#065003002800C800 22 | (43.823002) can0 6E0#0322F10000000000 23 | (43.834000) can0 51C#0762F10000050103 24 | (43.862999) can0 6E0#0322F13200000000 25 | (43.895000) can0 51C#037F227800050103 26 | (43.924000) can0 51C#100D62F132363832 27 | (43.943001) can0 6E0#3000000000000000 28 | (43.955002) can0 51C#2133333533354143 29 | (43.984001) can0 6E0#0322F15000000000 30 | (44.014999) can0 51C#037F227833354143 31 | (44.046001) can0 51C#0662F15013080043 32 | (44.083000) can0 6E0#0322F15100000000 33 | (44.115002) can0 51C#037F227813080043 34 | (44.165001) can0 51C#100C62F151123100 35 | (44.182999) can0 6E0#3000000000000000 36 | (44.195000) can0 51C#2114310014310000 37 | (44.362999) can0 6E0#0210030000000000 38 | (44.374001) can0 51C#065003002800C800 39 | (44.403999) can0 6E0#0322F10000000000 40 | (44.416000) can0 51C#0762F10000050103 41 | (44.443001) can0 6E0#0322F13200000000 42 | (44.476002) can0 51C#037F227800050103 43 | (44.504002) can0 51C#100D62F132363832 44 | (44.523998) can0 6E0#3000000000000000 45 | (44.535000) can0 51C#2133333533354143 46 | (44.563000) can0 6E0#0322F15000000000 47 | (44.595001) can0 51C#037F227833354143 48 | (44.625000) can0 51C#0662F15013080043 49 | (44.662998) can0 6E0#0322F15100000000 50 | (44.695000) can0 51C#037F227813080043 51 | (44.744999) can0 51C#100C62F151123100 52 | (44.763000) can0 6E0#3000000000000000 53 | (44.775002) can0 51C#2114310014310000 54 | (44.806999) can0 6E0#0322F18C00000000 55 | (44.846001) can0 51C#037F227814310000 56 | (44.877998) can0 51C#101162F18C543039 57 | (44.886002) can0 6E0#3000000000000000 58 | (44.896000) can0 51C#214A463039393630 59 | (44.917999) can0 51C#2230313339393630 60 | (44.943001) can0 6E0#0322F15400000000 61 | (44.957001) can0 51C#0562F15400C63630 62 | (44.983002) can0 6E0#0322F19000000000 63 | (44.998001) can0 51C#101462F190324334 64 | (45.004002) can0 6E0#3000000000000000 65 | (45.016998) can0 51C#2152444744473347 66 | (45.037998) can0 51C#2252333635333831 67 | (45.063000) can0 6E0#0322F1A000000000 68 | (45.077999) can0 51C#101462F1A0324334 69 | (45.084999) can0 6E0#3000000000000000 70 | (45.097000) can0 51C#2152444744473347 71 | (45.117001) can0 51C#2252333635333831 72 | (45.143002) can0 6E0#0322025000000000 73 | (45.157001) can0 51C#1033620250005F00 74 | (45.164001) can0 6E0#3000000000000000 75 | (45.178001) can0 51C#2101F6AC00000000 76 | (45.198002) can0 51C#2200000000000000 77 | (45.217999) can0 51C#2300000000000000 78 | (45.237999) can0 51C#2400000000000000 79 | (45.257000) can0 51C#2500000000000000 80 | (45.278000) can0 51C#2600000000000000 81 | (45.297001) can0 51C#2700000000000000 82 | (45.323002) can0 6E0#0322FA0100000000 83 | (45.356998) can0 51C#037F227800000000 84 | (45.387001) can0 51C#10D662FA01FFFFFF 85 | (45.403000) can0 6E0#3000000000000000 86 | (45.417999) can0 51C#21FFFFFFFFFFFFFF 87 | (45.437000) can0 51C#22FFFFFFFFFFFFFF 88 | (45.457001) can0 51C#23FFFFFFFFFFFFFF 89 | (45.477001) can0 51C#24FFFFFFFFFFFFFF 90 | (45.497002) can0 51C#25FFFFFFFFFFFFFF 91 | (45.516998) can0 51C#26FFFFFFFFFFFFFF 92 | (45.536999) can0 51C#27FFFFFFFFFFFFFF 93 | (45.556999) can0 51C#28FFFFFFFFFFFFFF 94 | (45.580002) can0 51C#29FFFFFFFFFFFFFF 95 | (45.597000) can0 51C#2AFFFFFFFFFFFFFF 96 | (45.617001) can0 51C#2BFFFFFFFFFFFFFF 97 | (45.637001) can0 51C#2CFFFFFFFFFFFFFF 98 | (45.658001) can0 51C#2DFFFFFFFFFFFFFF 99 | (45.676998) can0 51C#2EFFFFFFFFFFFFFF 100 | (45.698002) can0 51C#2FFFFFFFFFFFFFFF 101 | (45.717999) can0 51C#20FFFFFFFFFFFFFF 102 | (45.736000) can0 51C#21FFFFFFFFFFFFFF 103 | (45.766998) can0 51C#22FFFFFFFFFFFFFF 104 | (45.807999) can0 51C#23FFFFFFFFFFFFFF 105 | (45.807999) can0 51C#24FFFFFFFFFFFFFF 106 | (45.817001) can0 51C#25FFFFFFFFFFFFFF 107 | (45.838001) can0 51C#26FFFFFFFFFFFFFF 108 | (45.856998) can0 51C#27FFFFFFFFFFFFFF 109 | (45.877998) can0 51C#28FFFFFFFFFFFFFF 110 | (45.897999) can0 51C#29FFFFFFFFFFFFFF 111 | (45.917999) can0 51C#2AFFFFFFFFFFFFFF 112 | (45.938000) can0 51C#2BFFFFFFFFFFFFFF 113 | (45.957001) can0 51C#2CFFFFFFFFFFFFFF 114 | (45.977001) can0 51C#2DFFFFFFFFFFFFFF 115 | (45.997002) can0 51C#2EFFFFFFFFFFFFFF 116 | (46.022999) can0 6E0#0322FA0200000000 117 | (46.058998) can0 51C#037F2278FFFFFFFF 118 | (46.089001) can0 51C#10D662FA02FFFFFF 119 | (46.103001) can0 6E0#3000000000000000 120 | (46.118999) can0 51C#21FFFFFFFFFFFFFF 121 | (46.138000) can0 51C#22FFFFFFFFFFFFFF 122 | (46.159000) can0 51C#23FFFFFFFFFFFFFF 123 | (46.179001) can0 51C#24FFFFFFFFFFFFFF 124 | (46.199001) can0 51C#25FFFFFFFFFFFFFF 125 | (46.217999) can0 51C#26FFFFFFFFFFFFFF 126 | (46.237999) can0 51C#27FFFFFFFFFFFFFF 127 | (46.257999) can0 51C#28FFFFFFFFFFFFFF 128 | (46.278999) can0 51C#29FFFFFFFFFFFFFF 129 | (46.299000) can0 51C#2AFFFFFFFFFFFFFF 130 | (46.319000) can0 51C#2BFFFFFFFFFFFFFF 131 | (46.338001) can0 51C#2CFFFFFFFFFFFFFF 132 | (46.359001) can0 51C#2DFFFFFFFFFFFFFF 133 | (46.380001) can0 51C#2EFFFFFFFFFFFFFF 134 | (46.397999) can0 51C#2FFFFFFFFFFFFFFF 135 | (46.442001) can0 51C#20FFFFFFFFFFFFFF 136 | (46.442001) can0 51C#21FFFFFFFFFFFFFF 137 | (46.459000) can0 51C#22FFFFFFFFFFFFFF 138 | (46.480000) can0 51C#23FFFFFFFFFFFFFF 139 | (46.498001) can0 51C#24FFFFFFFFFFFFFF 140 | (46.519001) can0 51C#25FFFFFFFFFFFFFF 141 | (46.539001) can0 51C#26FFFFFFFFFFFFFF 142 | (46.558998) can0 51C#27FFFFFFFFFFFFFF 143 | (46.577999) can0 51C#28FFFFFFFFFFFFFF 144 | (46.598999) can0 51C#29FFFFFFFFFFFFFF 145 | (46.618999) can0 51C#2AFFFFFFFFFFFFFF 146 | (46.639999) can0 51C#2BFFFFFFFFFFFFFF 147 | (46.659000) can0 51C#2CFFFFFFFFFFFFFF 148 | (46.678001) can0 51C#2DFFFFFFFFFFFFFF 149 | (46.699001) can0 51C#2EFFFFFFFFFFFFFF 150 | (46.723000) can0 6E0#0322FA0300000000 151 | (46.759998) can0 51C#037F2278FFFFFFFF 152 | (46.790001) can0 51C#10D662FA03FFFFFF 153 | (46.803001) can0 6E0#3000000000000000 154 | (46.820999) can0 51C#21FFFFFFFFFFFFFF 155 | (46.841000) can0 51C#22FFFFFFFFFFFFFF 156 | (46.862000) can0 51C#23FFFFFFFFFFFFFF 157 | (46.881001) can0 51C#24FFFFFFFFFFFFFF 158 | (46.901001) can0 51C#25FFFFFFFFFFFFFF 159 | (46.922001) can0 51C#26FFFFFFFFFFFFFF 160 | (46.941002) can0 51C#27FFFFFFFFFFFFFF 161 | (46.962002) can0 51C#28FFFFFFFFFFFFFF 162 | (46.983002) can0 51C#29FFFFFFFFFFFFFF 163 | (47.000999) can0 51C#2AFFFFFFFFFFFFFF 164 | (47.021999) can0 51C#2BFFFFFFFFFFFFFF 165 | (47.042999) can0 51C#2CFFFFFFFFFFFFFF 166 | (47.062000) can0 51C#2DFFFFFFFFFFFFFF 167 | (47.081001) can0 51C#2EFFFFFFFFFFFFFF 168 | (47.102001) can0 51C#2FFFFFFFFFFFFFFF 169 | (47.122002) can0 51C#20FFFFFFFFFFFFFF 170 | (47.140999) can0 51C#21FFFFFFFFFFFFFF 171 | (47.161999) can0 51C#22FFFFFFFFFFFFFF 172 | (47.181000) can0 51C#23FFFFFFFFFFFFFF 173 | (47.201000) can0 51C#24FFFFFFFFFFFFFF 174 | (47.222000) can0 51C#25FFFFFFFFFFFFFF 175 | (47.241001) can0 51C#26FFFFFFFFFFFFFF 176 | (47.261002) can0 51C#27FFFFFFFFFFFFFF 177 | (47.280998) can0 51C#28FFFFFFFFFFFFFF 178 | (47.301998) can0 51C#29FFFFFFFFFFFFFF 179 | (47.320999) can0 51C#2AFFFFFFFFFFFFFF 180 | (47.341999) can0 51C#2BFFFFFFFFFFFFFF 181 | (47.362000) can0 51C#2CFFFFFFFFFFFFFF 182 | (47.382000) can0 51C#2DFFFFFFFFFFFFFF 183 | (47.402000) can0 51C#2EFFFFFFFFFFFFFF 184 | (47.423000) can0 6E0#0322FA0400000000 185 | (47.462002) can0 51C#037F2278FFFFFFFF 186 | (47.493000) can0 51C#100962FA04000000 187 | (47.502998) can0 6E0#3000000000000000 188 | (47.522999) can0 51C#2100000004000000 189 | (47.542999) can0 6E0#0322F10B00000000 190 | (47.553001) can0 51C#105362F10B001401 191 | (47.563000) can0 6E0#3000000000000000 192 | (47.582001) can0 51C#2104ABA9F00FDC89 193 | (47.602001) can0 51C#225822484C440248 194 | (47.622002) can0 51C#234C4403484C4400 195 | (47.643002) can0 51C#24484C4400000000 196 | (47.661999) can0 51C#2500000701021510 197 | (47.681000) can0 51C#264DFD5647000008 198 | (47.702999) can0 51C#2700000700900180 199 | (47.722000) can0 51C#28E7190E00000C03 200 | (47.741001) can0 51C#290001D8FE040000 201 | (47.763000) can0 51C#2A0B1E0000010000 202 | (47.782001) can0 51C#2B00AD0103000000 203 | (47.801998) can0 6E0#0322A00E00000000 204 | (47.811001) can0 51C#103862A00E113401 205 | (47.823002) can0 6E0#3000000000000000 206 | (47.841999) can0 51C#21A301A100000000 207 | (47.907001) can0 51C#2200000000000000 208 | (47.907001) can0 51C#2300000000000000 209 | (47.907001) can0 51C#2400000000000000 210 | (47.935001) can0 51C#2500000000000000 211 | (47.942001) can0 51C#2600000000000000 212 | (47.962002) can0 51C#2700000000000000 213 | (47.981998) can0 51C#2800000000000000 214 | -------------------------------------------------------------------------------- /examples/uds_log_decode.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from pyvit.hw.logplayer import LogPlayer 3 | from pyvit.proto.uds import * 4 | 5 | 6 | def read_file(filename, req_arb_id, resp_arb_id, resp_timeout = 0.5): 7 | lp = LogPlayer(filename, realtime=False) 8 | disp = Dispatcher(lp) 9 | uds_req = UDSInterface(disp, 0, req_arb_id) 10 | uds_resp = UDSInterface(disp, 0, resp_arb_id) 11 | disp.start() 12 | 13 | session = [] 14 | 15 | while True: 16 | try: 17 | req = uds_req.decode_request() 18 | if req is None: 19 | break 20 | session.append(req) 21 | 22 | resp = None 23 | # wait until we get a response 24 | # this will return None if there is a response pending 25 | start = time.time() 26 | while resp is None: 27 | try: 28 | resp = uds_resp.decode_response() 29 | except ResponsePendingException as e: 30 | # response pending, go for next 31 | resp = None 32 | if time.time() - start > resp_timeout: 33 | break 34 | session.append(resp) 35 | 36 | except NegativeResponseException as e: 37 | session.append(e) 38 | except ValueError as e: 39 | # sometimes other errors could be present in the log 40 | session.append(e) 41 | 42 | disp.stop() 43 | return session 44 | 45 | if len(sys.argv) == 4: 46 | filename = sys.argv[1] 47 | uds_tx_id = int(sys.argv[2], 0) 48 | uds_rx_id = int(sys.argv[3], 0) 49 | else: 50 | print('using sample file') 51 | filename = 'sample_uds_log.txt' 52 | uds_tx_id = 0x6E0 53 | uds_rx_id = 0x51C 54 | 55 | print(filename) 56 | session = read_file(filename, uds_tx_id, uds_rx_id) 57 | 58 | for r in session: 59 | if isinstance(r, GenericRequest): 60 | print('\n[->] Request [%s / 0x%X]' % (r.name, r.SID)) 61 | for k in r.keys(): 62 | if isinstance(r[k],list): 63 | print('\t%s: %s' % (k,[hex(x) for x in r[k]])) 64 | else: 65 | print('\t%s: 0x%X' % (k, r[k])) 66 | 67 | elif isinstance(r, GenericResponse): 68 | print('[<-] Response [%s / 0x%X]' % (r.name, r.SID)) 69 | for k in r.keys(): 70 | if isinstance(r[k],list): 71 | print('\t%s: %s' % (k,[hex(x) for x in r[k]])) 72 | else: 73 | print('\t%s: %s' % (k, r[k])) 74 | 75 | elif isinstance(r, NegativeResponseException): 76 | print('\n[!!] %s' % r) 77 | elif r is None: 78 | print('\n[??] Unknown Service: %s' % r) 79 | else: 80 | print('\n[!?] Strange stuff: %s' % r) 81 | -------------------------------------------------------------------------------- /pyvit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linklayer/pyvit/0192c92f6888747e7daf3dc6c5aa4a6c4768f635/pyvit/__init__.py -------------------------------------------------------------------------------- /pyvit/bus.py: -------------------------------------------------------------------------------- 1 | from . import can 2 | 3 | 4 | class Bus: 5 | # message that belong to this bus 6 | _messages = [] 7 | 8 | def add_message(self, message): 9 | assert isinstance(message, Message), 'invalid message' 10 | if message in self._messages: 11 | raise ValueError('Message %s already in bus' % message) 12 | else: 13 | self._messages.append(message) 14 | 15 | def remove_message(self, message): 16 | assert isinstance(Message, message), 'invalid message' 17 | try: 18 | self._messages.remove(message) 19 | except ValueError: 20 | raise ValueError('Message %s is not in bus' % message) 21 | 22 | def parse_frame(self, frame): 23 | assert isinstance(frame, can.Frame), 'invalid frame' 24 | for message in self._messages: 25 | if message.arb_id == frame.arb_id: 26 | return message.parse_frame(frame) 27 | 28 | def __str__(self): 29 | s = "Bus:\n" 30 | for message in self._messages: 31 | s = s + message.__str__() 32 | return s 33 | 34 | 35 | class Message(object): 36 | def __init__(self, name, arb_id): 37 | self.name = name 38 | self.arb_id = arb_id 39 | # signals that belong to this message, indexed by start bit 40 | self._signals = {} 41 | 42 | def add_signal(self, signal, start_bit): 43 | assert isinstance(signal, Signal), 'invalid signal' 44 | assert(isinstance(start_bit, int) and 45 | (start_bit < 63, 'invalid start bit')) 46 | self._signals[start_bit] = signal 47 | 48 | def remove_signal(self, signal): 49 | pass 50 | 51 | def parse_frame(self, frame): 52 | assert isinstance(frame, can.Frame), 'invalid frame' 53 | assert frame.arb_id == self.arb_id, 'frame id does not match msg id' 54 | 55 | # combine 8 data bytes into single value 56 | frame_value = 0 57 | for i in range(0, frame.dlc): 58 | if frame.data[i] is not None: 59 | frame_value = frame_value + (frame.data[i] << (8 * i)) 60 | 61 | result_signals = [] 62 | 63 | # iterate over signals 64 | for start_bit, signal in self._signals.items(): 65 | 66 | # find the last bit of the singal 67 | end_bit = signal.bit_length + start_bit 68 | 69 | # compute the mask 70 | mask = 0 71 | for j in range(start_bit, end_bit): 72 | mask = mask + 2**j 73 | 74 | # apply the mask, then downshift 75 | value = (frame_value & mask) >> start_bit 76 | # pass the masked value to the signal 77 | signal.parse_value(value) 78 | 79 | # check if isSigned then apply two complement 80 | isneg = (value & 2 ** (signal.bit_length - 1)) > 0 81 | if signal.isSigned & isneg: 82 | value = value - (1 << signal.bit_length) 83 | 84 | result_signals.append(signal) 85 | 86 | return result_signals 87 | 88 | def __str__(self): 89 | s = "Message: %s, ID: 0x%X\n" % (self.name, self.arb_id) 90 | for _, signal in self._signals.items(): 91 | s = s + "\t" + signal.__str__() + "\n" 92 | return s 93 | 94 | 95 | class Signal: 96 | def __init__(self, name, bit_length, factor=1, offset=0): 97 | self.name = name 98 | self.bit_length = bit_length 99 | self.factor = factor 100 | self.offset = offset 101 | self.value = 0 102 | self.isSigned = True 103 | 104 | def parse_value(self, value): 105 | self.value = value * self.factor + self.offset 106 | return self 107 | 108 | def __str__(self): 109 | s = "Signal: %s\tValue = %d" % (self.name, self.value) 110 | return s 111 | -------------------------------------------------------------------------------- /pyvit/can.py: -------------------------------------------------------------------------------- 1 | """ can.py 2 | 3 | Defines the low-level implementation of CAN. 4 | 5 | """ 6 | 7 | 8 | class FrameType: 9 | """ Enumerates the types of CAN frames """ 10 | DataFrame = 1 11 | RemoteFrame = 2 12 | ErrorFrame = 3 13 | OverloadFrame = 4 14 | 15 | 16 | class Frame(object): 17 | """ Represents a CAN Frame 18 | 19 | Attributes: 20 | arb_id (int): Arbitration identifier of the Frame 21 | data (list of int): CAN data bytes 22 | frame_type (int): type of CAN frame 23 | """ 24 | 25 | def __init__(self, arb_id, data=None, frame_type=FrameType.DataFrame, 26 | interface=None, timestamp=None, extended=False): 27 | """ Initializer of Frame 28 | Args: 29 | arb_id (int): identifier of CAN frame 30 | data (list, optional): data of CAN frame, defaults to empty list 31 | frame_type (int, optional): type of frame, defaults to 32 | FrameType.DataFrame 33 | interface (string, optional): name of the interface the frame is on 34 | defaults to None 35 | ts (float, optional): time frame was received at 36 | defaults to None 37 | """ 38 | 39 | self.frame_type = frame_type 40 | self.interface = interface 41 | self.timestamp = timestamp 42 | self.is_extended_id = extended 43 | self.arb_id = arb_id 44 | if data: 45 | self.data = data 46 | else: 47 | self.data = [] 48 | 49 | @property 50 | def arb_id(self): 51 | return self._arb_id 52 | 53 | @arb_id.setter 54 | def arb_id(self, value): 55 | # ensure value is an integer 56 | assert isinstance(value, int), 'arbitration id must be an integer' 57 | # ensure id is in range 58 | if not self.is_extended_id and value >= 0 and value <= 0x7FF: 59 | self._arb_id = value 60 | elif self.is_extended_id and value >= 0 and value <= 0x1FFFFFFF: 61 | self._arb_id = value 62 | else: 63 | # otherwise, id is not valid 64 | raise ValueError('Arbitration ID out of range', "0x%x" % value) 65 | 66 | @property 67 | def data(self): 68 | return self._data 69 | 70 | @data.setter 71 | def data(self, value): 72 | # data should be a list 73 | assert isinstance(value, list), 'CAN data must be a list' 74 | # data can only be 8 bytes maximum 75 | assert not len(value) > 8, 'CAN data cannot contain more than 8 bytes' 76 | # each byte must be a valid byte, int between 0x0 and 0xFF 77 | for byte in value: 78 | assert isinstance(byte, int), 'CAN data must consist of bytes' 79 | assert byte >= 0 and byte <= 0xFF, 'CAN data must consist of bytes' 80 | # data is valid 81 | self._data = value 82 | 83 | @property 84 | def frame_type(self): 85 | return self._frame_type 86 | 87 | @frame_type.setter 88 | def frame_type(self, value): 89 | assert value == FrameType.DataFrame or value == FrameType.RemoteFrame \ 90 | or value == FrameType.ErrorFrame or \ 91 | value == FrameType.OverloadFrame, 'invalid frame type' 92 | self._frame_type = value 93 | 94 | @property 95 | def dlc(self): 96 | return len(self.data) 97 | 98 | def __str__(self): 99 | return ('ID=0x%X, DLC=%d, Data=[%s]' % 100 | (self.arb_id, self.dlc, ', '.join(('%02X' % b) 101 | for b in self.data))) 102 | 103 | def __eq__(self, other): 104 | return (self.arb_id == other.arb_id and 105 | self.data == other.data and 106 | self.frame_type == other.frame_type and 107 | self.is_extended_id == other.is_extended_id) 108 | -------------------------------------------------------------------------------- /pyvit/dispatch.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | from multiprocessing import Queue, Process 3 | 4 | """The class uses two processes (_send_process e _recv_process) in order to transmit and receive 5 | The first one transmits evrything from queue _tx_queue 6 | The second one puts evrything received in all the queues present in the list _rx_queues 7 | So in order to transmit a frame we have to add it to the queue _tx_queue with method send 8 | In order to receive we have to read from our queue added trought method add_receiver 9 | 10 | singleprocess if True indicates to use only one process for receiving and transmitting. These is implemented in function _communication_loop 11 | 12 | REMEMBER: start the dispatcher with method start 13 | """ 14 | 15 | 16 | class Dispatcher: 17 | def __init__(self, device, single_process = False): 18 | # ensure the device has the required method functions 19 | if not (hasattr(device, 'start') and hasattr(device, 'stop') and 20 | hasattr(device, 'send') and hasattr(device, 'recv')): 21 | raise ValueError('invalid device') 22 | 23 | self._device = device 24 | self._rx_queues = [] 25 | self._tx_queue = Queue() 26 | self._running = False 27 | self._single_process = single_process 28 | 29 | def add_receiver(self, rx_queue): 30 | if self.is_running: 31 | raise Exception('dispatcher must be stopped to add receiver') 32 | 33 | # ensure the receive queue is a queue 34 | if not isinstance(rx_queue, multiprocessing.queues.Queue): 35 | raise ValueError('invalid receive queue, %s' % type(rx_queue)) 36 | # ensure this queue is not already in the dispacher 37 | elif rx_queue in self._rx_queues: 38 | raise ValueError('queue already in dispatcher') 39 | 40 | self._rx_queues.append(rx_queue) 41 | 42 | def remove_receiver(self, rx_queue): 43 | if self.is_runnning(): 44 | raise Exception('dispatcher must be stopped to remove receiver') 45 | 46 | # check the receive queue is in the dispatcher 47 | if rx_queue not in self._rx_queues: 48 | raise ValueError('rx_queue not in dispatcher') 49 | else: 50 | self._rx_queue.remove(rx_queue) 51 | 52 | def start(self): 53 | if self.is_running: 54 | raise Exception('dispatcher already running') 55 | 56 | self._device.start() 57 | self._tx_queue = Queue() 58 | 59 | if self._single_process: 60 | self._comm_process = Process(target=self._communication_loop) 61 | self._comm_process.start() 62 | else: 63 | self._send_process = Process(target=self._send_loop) 64 | self._recv_process = Process(target=self._recv_loop) 65 | self._recv_process.start() 66 | self._send_process.start() 67 | self._running = True 68 | 69 | def stop(self): 70 | if not self.is_running: 71 | raise Exception('dispatcher not running') 72 | 73 | if self._single_process: 74 | self._comm_process.terminate() 75 | else: 76 | self._recv_process.terminate() 77 | self._send_process.terminate() 78 | self._device.stop() 79 | self._running = False 80 | 81 | @property 82 | def is_running(self): 83 | return self._running 84 | 85 | def send(self, data): 86 | if not self.is_running: 87 | raise Exception('dispatcher not running') 88 | self._tx_queue.put(data) 89 | 90 | def _send_loop(self): 91 | while True: 92 | data = self._tx_queue.get() 93 | self._device.send(data) 94 | 95 | def _recv_loop(self): 96 | while True: 97 | if not self._tx_queue.empty(): 98 | data = self._tx_queue.get() 99 | self._device.send(data) 100 | data = self._device.recv() 101 | if data is not None: 102 | for rx_queue in self._rx_queues: 103 | rx_queue.put_nowait(data) 104 | 105 | def _communication_loop(self): 106 | """ 107 | After each received frame check if there is something to send, in case send it and receive again 108 | :return: 109 | """ 110 | while True: 111 | while not self._tx_queue.empty(): 112 | data = self._tx_queue.get() 113 | self._device.send(data) 114 | data = self._device.recv() 115 | if data is not None: 116 | for rx_queue in self._rx_queues: 117 | rx_queue.put_nowait(data) 118 | -------------------------------------------------------------------------------- /pyvit/file/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linklayer/pyvit/0192c92f6888747e7daf3dc6c5aa4a6c4768f635/pyvit/file/__init__.py -------------------------------------------------------------------------------- /pyvit/file/db/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linklayer/pyvit/0192c92f6888747e7daf3dc6c5aa4a6c4768f635/pyvit/file/db/__init__.py -------------------------------------------------------------------------------- /pyvit/file/db/jsondb.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pyvit import bus 3 | 4 | 5 | class JsonDbParser(): 6 | def parse(self, filename): 7 | with open(filename, 'r') as f: 8 | db = json.load(f) 9 | 10 | # create a bus for this database 11 | b = bus.Bus() 12 | for msg in db['messages']: 13 | # create a message 14 | m = bus.Message(msg['name'], int(msg['id'], 0)) 15 | 16 | # iterate over signals 17 | for start_bit, sig in msg['signals'].items(): 18 | # create a signal 19 | s = bus.Signal(sig['name'], sig['bit_length']) 20 | 21 | # parse offset and factor if set 22 | if 'offset' in sig: 23 | s.offset = int(sig['offset']) 24 | if 'factor' in sig: 25 | s.factor = float(sig['factor']) 26 | 27 | # add this signal to the message 28 | m.add_signal(s, int(start_bit)) 29 | 30 | # add this message to the bus 31 | b.add_message(m) 32 | 33 | return b 34 | -------------------------------------------------------------------------------- /pyvit/file/log/__init__.py: -------------------------------------------------------------------------------- 1 | from .candump import CandumpFile 2 | -------------------------------------------------------------------------------- /pyvit/file/log/candump.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from pyvit import can 4 | 5 | 6 | class CandumpFile: 7 | def __init__(self, filename): 8 | self.filename = filename 9 | 10 | def _str_to_frame(self, string): 11 | # split by whitespace 12 | fields = re.split('\s+', string) 13 | # arb_id is the part before '#' in the 3rd field, represented as hex 14 | arb_id = int(fields[2].split('#')[0], 16) 15 | # the data strig follows the '#' 16 | datastr = fields[2].split('#')[1] 17 | 18 | # iterate over the data string and collect the bytes 19 | data = [] 20 | for i in range(0, len(datastr)): 21 | if i % 2 == 0: 22 | data.append(int(datastr[i:i+2], 16)) 23 | 24 | # assemble the frame 25 | return can.Frame(arb_id, data=data) 26 | 27 | def _frame_to_str(self, frame): 28 | string = '' 29 | 30 | # insert timestamp if set 31 | if frame.timestamp: 32 | string += '(%f) ' % frame.timestamp 33 | else: 34 | string += '(0.0) ' 35 | 36 | # insert interface if set 37 | if frame.interface: 38 | string += '%s ' % frame.interface 39 | else: 40 | string += 'can0 ' 41 | 42 | # add ID and '#' character 43 | string += ('%03X' % frame.arb_id) + '#' 44 | 45 | # add data 46 | for b in frame.data: 47 | string += '%02X' % b 48 | 49 | string += '\n' 50 | return string 51 | 52 | def import_frames(self): 53 | frames = [] 54 | with open(self.filename, 'r') as f: 55 | for line in f.readlines(): 56 | frames.append(self._str_to_frame(line)) 57 | return frames 58 | 59 | def export_frames(self, frames): 60 | with open(self.filename, 'w') as f: 61 | for frame in frames: 62 | f.write(self._frame_to_str(frame)) 63 | -------------------------------------------------------------------------------- /pyvit/hw/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linklayer/pyvit/0192c92f6888747e7daf3dc6c5aa4a6c4768f635/pyvit/hw/__init__.py -------------------------------------------------------------------------------- /pyvit/hw/cantact.py: -------------------------------------------------------------------------------- 1 | import serial 2 | 3 | from .. import can 4 | 5 | 6 | class CantactDev: 7 | debug = False 8 | 9 | def __init__(self, port, baudrate=9600): 10 | # opening the serial connection with the device in attribute ser 11 | self.ser = serial.Serial(port, baudrate) 12 | 13 | def _dev_write(self, string): 14 | self.ser.write(string.encode()) 15 | 16 | def start(self): 17 | # activate CANtact standard operations 18 | self._dev_write('O\r') 19 | if self.debug: 20 | print("CantactDev started") 21 | 22 | def stop(self): 23 | # terminates CANtact standard operations 24 | self._dev_write('C\r') 25 | if self.debug: 26 | print("CantactDev stopped") 27 | 28 | def set_bitrate(self, bitrate): 29 | # set the CAN bus bitrate 30 | if bitrate == 10000: 31 | self._dev_write('S0\r') 32 | elif bitrate == 20000: 33 | self._dev_write('S1\r') 34 | elif bitrate == 50000: 35 | self._dev_write('S2\r') 36 | elif bitrate == 100000: 37 | self._dev_write('S3\r') 38 | elif bitrate == 125000: 39 | self._dev_write('S4\r') 40 | elif bitrate == 250000: 41 | self._dev_write('S5\r') 42 | elif bitrate == 500000: 43 | self._dev_write('S6\r') 44 | elif bitrate == 750000: 45 | self._dev_write('S7\r') 46 | elif bitrate == 1000000: 47 | self._dev_write('S8\r') 48 | elif bitrate == 83000: 49 | self._dev_write('S9\r') 50 | elif bitrate == 800000: 51 | self._dev_write('Sa\r') 52 | elif bitrate == 0: 53 | self._dev_write('Su\r') 54 | else: 55 | raise ValueError("Bitrate not supported") 56 | 57 | def recv(self): 58 | # receive characters until a newline (\r) is hit 59 | rx_str = "" 60 | if self.debug: 61 | print("CantactDev recv called") 62 | while rx_str == "" or rx_str[-1] != '\r': 63 | rx_str = rx_str + self.ser.read().decode('ascii') 64 | 65 | # check frame type 66 | if rx_str[0] == 'T': 67 | ext_id = True 68 | remote = False 69 | elif rx_str[0] == 't': 70 | ext_id = False 71 | remote = False 72 | elif rx_str[0] == 'R': 73 | ext_id = True 74 | remote = True 75 | elif rx_str[0] == 'r': 76 | ext_id = False 77 | remote = True 78 | else: 79 | # If I read somthing meaningless read next packet 80 | return self.recv() 81 | 82 | # parse the id and DLC 83 | if ext_id: 84 | arb_id = int(rx_str[1:9], 16) 85 | dlc = int(rx_str[9]) 86 | data_offset = 10 87 | else: 88 | arb_id = int(rx_str[1:4], 16) 89 | dlc = int(rx_str[4]) 90 | data_offset = 5 91 | 92 | # create the frame 93 | frame = can.Frame(arb_id, extended=ext_id) 94 | if remote: 95 | frame.frame_type = can.FrameType.RemoteFrame 96 | 97 | # parse the data bytes 98 | data = [] 99 | for i in range(0, dlc): 100 | data.append(int(rx_str[data_offset+i*2:(data_offset+2)+i*2], 16)) 101 | frame.data = data 102 | 103 | if self.debug: 104 | print("RECV: %s" % frame) 105 | return frame 106 | 107 | def send(self, frame): 108 | # add type, id, and dlc to string 109 | if frame.is_extended_id: 110 | tx_str = "T%08X%d" % (frame.arb_id, frame.dlc) 111 | else: 112 | tx_str = "t%03X%d" % (frame.arb_id, frame.dlc) 113 | 114 | # add data bytes to string 115 | for i in range(0, frame.dlc): 116 | tx_str = tx_str + ("%02X" % frame.data[i]) 117 | 118 | # add newline (\r) to string 119 | tx_str = tx_str + '\r' 120 | 121 | # send it 122 | self._dev_write(tx_str) 123 | if self.debug: 124 | print("SENT: %s" % frame) 125 | 126 | def set_filter_id(self, filter_id): 127 | # set CAN filter identifier 128 | self._dev_write('F%X\r' % filter_id) 129 | 130 | def set_filter_mask(self, filter_mask): 131 | # set CAN filter mask 132 | self._dev_write('K%X\r' % filter_mask) 133 | -------------------------------------------------------------------------------- /pyvit/hw/logplayer.py: -------------------------------------------------------------------------------- 1 | import time 2 | from .. import can 3 | 4 | 5 | class LogPlayer: 6 | running = False 7 | debug = False 8 | 9 | def __init__(self, log_filename, realtime=True): 10 | self.log_filename = log_filename 11 | self.realtime = realtime 12 | 13 | def start(self): 14 | assert not self.running, 'cannot start, already running' 15 | 16 | self.logfile = open(self.log_filename, 'r') 17 | self.start_timestamp = None 18 | self.running = True 19 | self.linenumber = 0 20 | 21 | def __enter__(self): 22 | self.start() 23 | return self 24 | 25 | def stop(self): 26 | self.running = False 27 | self.logfile.close() 28 | 29 | def __exit__(self, exception_type, exception_value, traceback): 30 | self.stop() 31 | 32 | def send(self, data): 33 | if self.debug: 34 | print("DEV SEND: %s " % data) 35 | 36 | def recv(self): 37 | assert self.running, 'not running' 38 | 39 | line = self.logfile.readline() 40 | self.linenumber = self.linenumber + 1 41 | if line == '': 42 | # out of frames 43 | return None 44 | if line in ['\n', '\n\r']: 45 | # seams to be an empty line, just go for next one 46 | return self.recv() 47 | 48 | # convert line to frame 49 | frame = self._log_to_frame(line) 50 | 51 | # make the first frame's timestamp now 52 | if self.start_timestamp is None: 53 | self.start_timestamp = time.time() - frame.timestamp 54 | # sleep until message occurs 55 | if self.realtime: 56 | time.sleep(max((self.start_timestamp - time.time() + 57 | frame.timestamp), 0)) 58 | if self.debug: 59 | print("DEV RECV: %s " % frame) 60 | return frame 61 | 62 | def recv_all(self): 63 | frames = [] 64 | 65 | line = self.logfile.readline() 66 | while line != '': 67 | frame = self._log_to_frame(line) 68 | frames.append(frame) 69 | line = self.logfile.readline() 70 | 71 | return frames 72 | 73 | def _log_to_frame(self, line): 74 | fields = line.split(' ') 75 | 76 | arb_id_str = fields[2].split('#')[0] 77 | arb_id = int(arb_id_str, 16) 78 | 79 | extended_id = len(arb_id_str) > 3 80 | frame = can.Frame(arb_id, extended=extended_id) 81 | 82 | frame.timestamp = float(fields[0][1:-1]) 83 | 84 | datastr = fields[2].split('#')[1] 85 | dlc = int(len(datastr) / 2) 86 | 87 | frame.data = [] 88 | for i in range(0, dlc): 89 | frame.data.append(int(datastr[i*2:i*2+2], 16)) 90 | 91 | return frame 92 | 93 | def set_bitrate(self, value): 94 | pass -------------------------------------------------------------------------------- /pyvit/hw/loopback.py: -------------------------------------------------------------------------------- 1 | from multiprocessing import Queue 2 | 3 | 4 | class LoopbackDev: 5 | def __init__(self): 6 | self._queue = Queue() 7 | self.running = False 8 | 9 | def start(self): 10 | if self.running: 11 | raise Exception('device already started') 12 | 13 | self.running = True 14 | 15 | def stop(self): 16 | if not self.running: 17 | raise Exception('device not started') 18 | 19 | self.running = False 20 | 21 | def send(self, data): 22 | if not self.running: 23 | raise Exception('device not started') 24 | if self.debug: 25 | print("SENT: %s" % data) 26 | self._queue.put(data) 27 | 28 | def recv(self): 29 | if not self.running: 30 | raise Exception('device not started') 31 | dt = self._queue.get() 32 | if self.debug: 33 | print("RECV: %s" % dt) 34 | return dt 35 | -------------------------------------------------------------------------------- /pyvit/hw/obdlinksx.py: -------------------------------------------------------------------------------- 1 | import serial, sys 2 | 3 | from pyvit import can 4 | 5 | 6 | class OBDLinkSXDev: 7 | # It instantiates a new object, 8 | # opens a serial connection with the device 9 | # (whose serial port must be declared by the user, whose bitrate is set to 2 Mbps - 10 | # maximum device serial communication speed, which must previously have been set on the device by the user following the procedure defined in Listing 5.1 - 11 | # and whose read timeout is set at 1 second again in order to avoid a device hang up in the case of a target frame) and sets the device serial port attribute to point to that serial connection. 12 | # 13 | # Commands for Communication baud rate setup, Listing 5.1 14 | # ST BRT 5000 15 | # ST SBR 2000000 16 | # ATI 17 | # ST WBR QUESTO RENDE DEFINITIVE LE IMPOSTAZIONI 18 | debug = False 19 | debugPower = True 20 | bitrate = 500000 21 | 22 | def __init__(self, portInput, baudrate=115200, timeout=10): 23 | self.serialImpl = serial.Serial (port=portInput, baudrate=baudrate, timeout=timeout) 24 | self.reset() 25 | self.dev_running = False 26 | 27 | def start(self): 28 | self.reset() 29 | self.set_bitrate(self.bitrate) 30 | self.setup() 31 | self.startLogging() 32 | self.dev_running = True 33 | 34 | def stop(self): 35 | self.stopLogging() 36 | self.dev_running = False 37 | 38 | def set_bitrate(self, bitrate): 39 | if self.dev_running: 40 | raise RuntimeError("Can't set bitret with device running") 41 | self.bitrate = bitrate 42 | # set the CAN bus bitrate 43 | if bitrate == 0: 44 | # Autodetect speed 45 | command = 'ATSP0' 46 | # elif bitrate == 20000: 47 | # self._dev_write('S1\r') 48 | # elif bitrate == 50000: 49 | # self._dev_write('S2\r') 50 | # elif bitrate == 100000: 51 | # self._dev_write('S3\r') 52 | # elif bitrate == 125000: 53 | # self._dev_write('S4\r') 54 | # elif bitrate == 250000: 55 | # self._dev_write('S5\r') 56 | elif bitrate == 500000: 57 | command = 'ATSP6' 58 | # elif bitrate == 750000: 59 | # self._dev_write('S7\r') 60 | # elif bitrate == 1000000: 61 | # self._dev_write('S8\r') 62 | # elif bitrate == 83000: 63 | # self._dev_write('S9\r') 64 | # elif bitrate == 800000: 65 | # self._dev_write('Sa\r') 66 | else: 67 | raise ValueError("Bitrate not supported") 68 | self._dev_write(command) 69 | #TODO: memorize if is used 11b or 29b IDs 70 | # interrogare con STPRSa per sapere il protocollo usato 71 | if not self.receiveUntilGreaterThan().endswith("%s\rOK\r\r>" % command): 72 | raise RuntimeError("%s failed" % command) 73 | 74 | 75 | # listens to the serial connection and automatically concatenates all the received 76 | # UTF-8 de- coded characters until a «»> symbol is observed, denoting the end of a 77 | # configuration command acknowledgment. Else, if no character has been received in a 78 | # 1 second time, it returns a «NULL\r» string; 79 | def receiveUntilGreaterThan(self): 80 | incomingString = "" 81 | while not(incomingString.endswith(">")): 82 | incomingString = incomingString + self.serialImpl.read().decode() 83 | if incomingString == "": 84 | incomingString = "NULL>" 85 | break 86 | if self.debugPower: 87 | print("RECEIVEDUNTILGREATERTHAN: %s" % incomingString.encode()) 88 | return incomingString 89 | 90 | def receiveUntilNewLine (self): 91 | incomingString = "" 92 | while not(incomingString.endswith("\r")): 93 | # self._lock.acquire() 94 | # self._lock.release() 95 | incomingString = incomingString + self.serialImpl.read().decode() 96 | if incomingString == "": 97 | incomingString = "NULL\r" 98 | break 99 | elif incomingString == "\r": 100 | incomingString = "" 101 | 102 | if self.debugPower: 103 | print("RECEIVEDUNTILNEWLINE: %s" % incomingString.encode()) 104 | if "ER" in incomingString or "FUL" in incomingString: 105 | incomingString = "NULL\r" 106 | return incomingString 107 | 108 | # automatically inserts the carriage return symbol, 109 | # then sends the UTF-8 encoded string into the serial connection 110 | # with the OBDLink SX, 111 | # actually transmitting the command to the device. 112 | def _dev_write(self, outgoingString): 113 | outgoingString = outgoingString + "\r" 114 | if self.debugPower: 115 | print("OUTGOINGSTRING: %s" % outgoingString.encode()) 116 | self.serialImpl.write(outgoingString.encode()) 117 | 118 | def reset(self): 119 | # reset OBDLink SX 120 | self._dev_write("ATZ") 121 | strrec = self.receiveUntilGreaterThan() 122 | if not strrec.endswith("ATZ\r\r\rELM327 v1.3a\r\r>"): 123 | raise RuntimeError("ATZ failed") 124 | sys.stdout.flush() 125 | 126 | # Commands for ELM327 custom user1 CAN setup 127 | # ATPP2CSV60 128 | # ATPP2CON 129 | # ATPP2DSV0A 130 | # ATPP2DON 131 | def setup(self): 132 | # self._dev_write("ATSPB") 133 | # if self.receiveUntilGreaterThan()!="ATSPB\rOK\r\r>": 134 | # raise RuntimeError("ATSPB failed") 135 | # return 136 | # sets the previously tuned custom user1 CAN profile 137 | self._dev_write("ATD1") 138 | if not self.receiveUntilGreaterThan().endswith("ATD1\rOK\r\r>"): 139 | raise RuntimeError("ATD1 failed") 140 | self._dev_write("ATV1") 141 | if not self.receiveUntilGreaterThan().endswith("ATV1\rOK\r\r>"): 142 | raise RuntimeError("ATV1 failed") 143 | self._dev_write("ATCAF0") 144 | if not self.receiveUntilGreaterThan().endswith("ATCAF0\rOK\r\r>"): 145 | raise RuntimeError("ATCAF0 failed") 146 | self._dev_write("ATH1") 147 | if not self.receiveUntilGreaterThan().endswith("ATH1\rOK\r\r>"): 148 | raise RuntimeError("ATH1 failed") 149 | self._dev_write("ATAL") 150 | if not self.receiveUntilGreaterThan().endswith("ATAL\rOK\r\r>"): 151 | raise RuntimeError("ATAL failed") 152 | self._dev_write("STCMM1") 153 | if not self.receiveUntilGreaterThan().endswith("STCMM1\rOK\r\r>"): 154 | raise RuntimeError("STCMM1 failed") 155 | 156 | def startLogging(self): 157 | self._dev_write("STMA") 158 | if self.receiveUntilNewLine()!="STMA\r": 159 | raise RuntimeError("StartLogging failed") 160 | 161 | def recv(self): 162 | incomingString = self.receiveUntilNewLine() 163 | withoutSpaces = incomingString.replace(" ", "") 164 | withoutNewLine = withoutSpaces.replace("\r", "") 165 | if withoutNewLine == 'NULL': 166 | return None 167 | arb_id = withoutNewLine[:3] 168 | # skip one position because it contains the dlc 169 | data = [int(withoutNewLine[i:i+2], 16) for i in range(4, len(withoutNewLine), 2)] 170 | frame = can.Frame(int(arb_id, 16), data) 171 | if self.debug: 172 | print("RECV: %s" % frame) 173 | return frame 174 | 175 | def stopLogging(self): 176 | self._dev_write("") 177 | ret = self.receiveUntilGreaterThan() 178 | if not ret.endswith("\r>") and not ret.endswith(">"): 179 | raise RuntimeError("StopLogging failed") 180 | 181 | def send(self, frame): 182 | if self.debug: 183 | print("SEND: %s" % frame) 184 | self.sendFrame("%x%s" % (frame.arb_id, ''.join(('%02X' % b) for b in frame.data))) 185 | 186 | def sendFrame(self, frame): 187 | self.stopLogging() 188 | #TODO: handle 29b IDs 189 | # first2hexesId = frame[:2] 190 | # last6hexesId = frame[2:8] 191 | last6hexesId = frame[:3] 192 | dataField = frame[3:] 193 | # self._dev_write("ATCP"+first2hexesId) 194 | # if self.receiveUntilGreaterThan()!="ATCP"+first2hexesId+"\rOK\r\r>": 195 | # raise RuntimeError("First2hexesId set failed") 196 | # return 197 | self._dev_write("ATSH"+last6hexesId) 198 | if not self.receiveUntilGreaterThan().endswith("ATSH"+last6hexesId+"\rOK\r\r>"): 199 | raise RuntimeError("Last6hexesId set failed") 200 | self._dev_write(dataField) 201 | if not self.receiveUntilGreaterThan()[:len(dataField)].endswith(dataField): 202 | raise RuntimeError("DataField send failed") 203 | 204 | self.startLogging() 205 | -------------------------------------------------------------------------------- /pyvit/hw/peak.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .socketcan import SocketCanDev 4 | 5 | 6 | class PcanDev(SocketCanDev): 7 | def __init__(self, minor_number=32, ndev="can0"): 8 | SocketCanDev.__init__(self, ndev=ndev) 9 | self.device_filename = "/dev/pcan%d" % minor_number 10 | 11 | def _write_to_chardev(self, string): 12 | os.system("echo '%s' > %s" % (string, self.device_filename)) 13 | 14 | def set_btr(self, value): 15 | self._write_to_chardev('i 0x%X e' % value) 16 | -------------------------------------------------------------------------------- /pyvit/hw/socketcan.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import socket 3 | import time 4 | 5 | from .. import can 6 | 7 | 8 | class SocketCanDev: 9 | def __init__(self, ndev): 10 | self.running = False 11 | 12 | if not hasattr(socket, 'PF_CAN') or not hasattr(socket, 'CAN_RAW'): 13 | print("Python 3.3 or later is needed for native SocketCan") 14 | raise SystemExit(1) 15 | 16 | self.socket = socket.socket(socket.PF_CAN, socket.SOCK_RAW, 17 | socket.CAN_RAW) 18 | self.ndev = ndev 19 | 20 | def start(self): 21 | self.socket.bind((self.ndev,)) 22 | self.start_time = time.time() 23 | self.running = True 24 | 25 | def stop(self): 26 | pass 27 | 28 | def recv(self): 29 | assert self.running, 'device not running' 30 | frame_format = "=IB3xBBBBBBBB" 31 | frame_size = struct.calcsize(frame_format) 32 | 33 | frame_raw = self.socket.recv(frame_size) 34 | arb_id, dlc, d0, d1, d2, d3, d4, d5, d6, d7 = ( 35 | struct.unpack(frame_format, frame_raw)) 36 | 37 | # adjust the id and set the extended id flag 38 | is_extended = False 39 | if arb_id & 0x80000000: 40 | arb_id &= 0x7FFFFFFF 41 | is_extended = True 42 | 43 | frame = can.Frame(arb_id, extended=is_extended) 44 | # select the data bytes up to the DLC value 45 | frame.data = [d0, d1, d2, d3, d4, d5, d6, d7][0:dlc] 46 | frame.timestamp = time.time() - self.start_time 47 | 48 | return frame 49 | 50 | def send(self, frame): 51 | assert self.running, 'device not running' 52 | frame_format = "=IBBBBBBBBBBBB" 53 | 54 | # set the extended bit if a extended id is used 55 | arb_id = frame.arb_id 56 | if frame.is_extended_id: 57 | arb_id |= 0x80000000 58 | 59 | # get data, padded to 8 bytes 60 | data = frame.data + [0] * (8 - len(frame.data)) 61 | packet = struct.pack(frame_format, arb_id, frame.dlc, 0xff, 0xff, 0xff, 62 | data[0], data[1], data[2], data[3], 63 | data[4], data[5], data[6], data[7]) 64 | self.socket.send(packet) 65 | -------------------------------------------------------------------------------- /pyvit/log.py: -------------------------------------------------------------------------------- 1 | from . import can 2 | import time 3 | 4 | 5 | class Logger: 6 | def __init__(self, filename, if_name='can0'): 7 | self.if_name = if_name 8 | self.filename = filename 9 | self.started = False 10 | 11 | def start(self): 12 | self.start_timestamp = time.time() 13 | self.started = True 14 | self._file = open(self.filename, 'w') 15 | 16 | def __enter__(self): 17 | self.start() 18 | return self 19 | 20 | def stop(self): 21 | self.started = False 22 | self._file.close() 23 | 24 | def __exit__(self, exception_type, exception_value, traceback): 25 | self.stop() 26 | 27 | def log_frame(self, frame): 28 | assert isinstance(frame, can.Frame), 'invalid frame' 29 | 30 | if not self.started: 31 | raise Exception('logger not started') 32 | 33 | ts = time.time() - self.start_timestamp 34 | 35 | line = ("(%f) %s %03X#%02X%02X%02X%02X%02X%02X%02X%02X\n" % 36 | (ts, 37 | self.if_name, 38 | frame.arb_id, 39 | frame.data[0], 40 | frame.data[1], 41 | frame.data[2], 42 | frame.data[3], 43 | frame.data[4], 44 | frame.data[5], 45 | frame.data[6], 46 | frame.data[7])) 47 | self._file.write(line) 48 | 49 | def clear(self): 50 | self._buffer = [] 51 | -------------------------------------------------------------------------------- /pyvit/proto/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linklayer/pyvit/0192c92f6888747e7daf3dc6c5aa4a6c4768f635/pyvit/proto/__init__.py -------------------------------------------------------------------------------- /pyvit/proto/isotp.py: -------------------------------------------------------------------------------- 1 | import time 2 | from multiprocessing import Queue 3 | from queue import Empty 4 | 5 | from .. import can 6 | 7 | 8 | class IsotpInterface: 9 | debug = False 10 | 11 | # From standard 15765-3 default padding value should be 0x55 12 | def __init__(self, dispatcher, tx_arb_id, rx_arb_id = False, padding=0x55, extended_id=False, rx_filter_func=False): 13 | 14 | self._dispatcher = dispatcher 15 | self.tx_arb_id = tx_arb_id 16 | self.rx_arb_id = rx_arb_id 17 | self.padding_value = padding 18 | self._recv_queue = Queue() 19 | self.block_size_counter = 0 20 | self.extended_id = extended_id 21 | self.rx_filter_func = rx_filter_func 22 | self.last_arb_id = None 23 | self.arb_ids_blacklist = [] 24 | 25 | # depending of the addressing type the data len limit for using a single frame may change, in most cases is 7 26 | self.sf_data_len_limit = 7 27 | 28 | self._dispatcher.add_receiver(self._recv_queue) 29 | 30 | def _pad_data(self, data): 31 | # pad data to 8 bytes 32 | return data + ([self.padding_value] * (8 - len(data))) 33 | 34 | def _start_msg(self, arb_id=0): 35 | # initialize reading of a message 36 | self.data = [] 37 | self.data_len = 0 38 | self.data_byte_count = 0 39 | self.sequence_number = 0 40 | 41 | def _end_msg(self): 42 | # finish reading a message 43 | tmp = self.data 44 | self.data = [] 45 | self.data_len = 0 46 | return tmp 47 | 48 | def set_filter(self,arb_id, mask): 49 | if (hasattr(self._dispatcher._device, "set_filter_id") and 50 | hasattr(self._dispatcher._device, "set_filter_mask")): 51 | self._dispatcher._device.set_filter_id(arb_id) 52 | self._dispatcher._device.set_filter_mask(mask) 53 | 54 | def unset_filter(self): 55 | if (hasattr(self._dispatcher._device, "set_filter_id") and 56 | hasattr(self._dispatcher._device, "set_filter_mask")): 57 | self._dispatcher._device.set_filter_mask(0) 58 | 59 | def _set_filter(self): 60 | if (self.rx_arb_id): 61 | self.set_filter(self.rx_arb_id, 0xFFF) 62 | 63 | def _unset_filter(self): 64 | self.unset_filter() 65 | 66 | def reset(self): 67 | # abort reading a message 68 | self.data = [] 69 | self.data_len = 0 70 | 71 | def parse_frame(self, frame): 72 | 73 | # save frame arbitration id 74 | self.last_arb_id = frame.arb_id 75 | # pci type is upper nybble of first byte 76 | pci_type = (frame.data[0] & 0xF0) >> 4 77 | 78 | if pci_type == 0: 79 | # single frame 80 | 81 | self._start_msg() 82 | 83 | # data length is lower nybble for first byte 84 | sf_dl = frame.data[0] & 0xF 85 | 86 | # check that the data length is valid for a SF, the max length can change depending on addressing type 87 | if not (sf_dl > 0 and sf_dl <= self.sf_data_len_limit): 88 | raise ValueError('invalid SF_DL parameter for single frame %s', frame) 89 | 90 | self.data_len = sf_dl 91 | 92 | # get data bytes from this frame 93 | self.data = frame.data[1:sf_dl+1] 94 | 95 | # single frame, we're done! 96 | return self._end_msg() 97 | 98 | elif pci_type == 1: 99 | # first frame 100 | 101 | self._start_msg() 102 | 103 | # data length is lower nybble of byte 0 and byte 1 104 | ff_dl = ((frame.data[0] & 0xF) << 8) + frame.data[1] 105 | 106 | self.data_len = ff_dl 107 | 108 | # retrieve data bytes from first frame 109 | for i in range(2, min(ff_dl+2, 8)): 110 | self.data.append(frame.data[i]) 111 | self.data_byte_count = self.data_byte_count + 1 112 | 113 | self.sequence_number = self.sequence_number + 1 114 | 115 | # send a flow control frame 116 | self._send_control_frame(frame.is_extended_id) 117 | 118 | elif pci_type == 2: 119 | # consecutive frame, data_len should be already specified by previous FF frame 120 | 121 | # check that a FF has been sent 122 | if not hasattr(self, 'data_len') or self.data_len == 0: 123 | raise ValueError('consecutive frame before first frame') 124 | 125 | # frame's sequence number is lower nybble of byte 0 126 | frame_sequence_number = frame.data[0] & 0xF 127 | 128 | # check the sequence number 129 | if frame_sequence_number != self.sequence_number: 130 | raise ValueError('invalid sequence number!') 131 | 132 | bytes_remaining = self.data_len - self.data_byte_count 133 | 134 | # grab data bytes from this message 135 | for i in range(1, min(bytes_remaining, 7) + 1): 136 | self.data.append(frame.data[i]) 137 | self.data_byte_count = self.data_byte_count + 1 138 | 139 | if self.data_byte_count == self.data_len: 140 | return self._end_msg() 141 | elif self.data_byte_count > self.data_len: 142 | raise ValueError('data length mismatch') 143 | 144 | # wrap around when sequence number reaches 0xF 145 | self.sequence_number = self.sequence_number + 1 146 | if self.sequence_number > 0xF: 147 | self.sequence_number = 0 148 | 149 | elif pci_type == 3: 150 | # ignore received control frames 151 | pass 152 | 153 | else: 154 | # Not an ISOTP conform frame, return None so we wail for another 155 | return None 156 | 157 | if self.block_size_counter > 0: 158 | self.block_size_counter -= 1 159 | if self.block_size_counter == 0: 160 | # need to send flow control 161 | self._send_control_frame(frame.is_extended_id) 162 | 163 | def recv(self, timeout=1, bs=0, st_min=0): 164 | self.last_arb_id = None 165 | data = None 166 | start = time.time() 167 | 168 | self._set_filter() 169 | 170 | self.block_size = bs 171 | 172 | if not st_min <= 0x7F and not (st_min >= 0xF1 and st_min <= 0xF9): 173 | raise ValueError( 174 | "st_min must be beween 0x00 and 0x7F or 0xF1 and 0xF9") 175 | self.st_min = st_min 176 | 177 | while data is None: 178 | # attempt to get data, returning None if we timeout 179 | try: 180 | rx_frame = self._recv_queue.get(timeout=timeout) 181 | except Empty: 182 | if self.debug: 183 | print ('timeout NO FRAME') 184 | return None 185 | 186 | if rx_frame is None: 187 | return None 188 | 189 | if self.filter_received_frame(rx_frame): 190 | if self.debug: 191 | print("ISOTP RECV: %s" % rx_frame) 192 | data = self.parse_frame(rx_frame) 193 | # check timeout, since we may be receiving messages that do not 194 | # pass the receiving filter criterion 195 | if time.time() - start > timeout: 196 | if self.debug: 197 | print ('timeout ISOTP') 198 | return data 199 | 200 | self._unset_filter() 201 | return data 202 | 203 | def send(self, data): 204 | if len(data) > 4095: 205 | raise ValueError('ISOTP data must be <= 4095 bytes long') 206 | 207 | self._set_filter() 208 | 209 | if len(data) <= self.sf_data_len_limit: 210 | # message is less than sf_data_len_limit bytes, use single frame 211 | 212 | sf = can.Frame(self.tx_arb_id, extended = self.extended_id) 213 | 214 | frame_data = self.get_base_frame_data() 215 | # first byte is data length, remainder is data 216 | frame_data.append(len(data)) 217 | frame_data = frame_data + data 218 | sf.data = self._pad_data(frame_data) 219 | 220 | if self.debug: 221 | print("ISOTP SEND: %s " % sf) 222 | self._dispatcher.send(sf) 223 | 224 | else: 225 | # message must be composed of FF and CF 226 | 227 | # first frame 228 | ff = can.Frame(self.tx_arb_id, extended = self.extended_id) 229 | 230 | frame_data = self.get_base_frame_data() 231 | # FF pci type and msb of length 232 | frame_data.append(0x10 + (len(data) >> 8)) 233 | # lower byte of data 234 | frame_data.append(len(data) & 0xFF) 235 | # first 6 bytes of data 236 | frame_data = frame_data + data[0:6] 237 | 238 | ff.data = self._pad_data(frame_data) 239 | if self.debug: 240 | print("ISOTP SEND: %s " % ff) 241 | self._dispatcher.send(ff) 242 | 243 | bytes_sent = 6 244 | sequence_number = 1 245 | 246 | # force to wait for a flow control frame 247 | fc_bs = 1 248 | 249 | while bytes_sent < len(data): 250 | if fc_bs > 0: 251 | fc_bs -= 1 252 | if fc_bs == 0: 253 | # must wait for a flow control frame 254 | # Just in case, theoretically, since we've already started comunicating, we should never go timeout 255 | timeout = 10 256 | start = time.time() 257 | 258 | while True: 259 | try: 260 | rx_frame = self._recv_queue.get(timeout=timeout) 261 | except Empty: 262 | if self.debug: 263 | print('timeout NO FRAME waiting CONTROL frame') 264 | raise TimeoutError("No control frame received") 265 | 266 | if (self.filter_received_frame(rx_frame) and 267 | rx_frame.data[0] == 0x30): 268 | if self.debug: 269 | print(rx_frame) 270 | # flow control frame received, get parameters 271 | fc_bs = rx_frame.data[1] 272 | fc_stmin = rx_frame.data[2] 273 | break 274 | # check timeout, since we may be receiving messages that are not control frame 275 | if time.time() - start > timeout: 276 | if self.debug: 277 | print('timeout ISOTP waiting CONTROL frame') 278 | raise TimeoutError("No control frame received") 279 | 280 | # wait for fc_stmin ms/us 281 | if fc_stmin < 0x80: 282 | # fc_stmin equal to ms to wait 283 | time_to_wait = fc_stmin/1000.0 284 | time.sleep(time_to_wait) 285 | elif fc_stmin >= 0xF1 and fc_stmin <= 0xF9: 286 | # fc_stmin equal to 100 - 900 us to wait 287 | time_to_wait = (fc_stmin-0xF0)/1000000.0 288 | time.sleep(time_to_wait) 289 | 290 | cf = can.Frame(self.tx_arb_id, extended = self.extended_id) 291 | data_bytes_in_msg = min(len(data) - bytes_sent, self.sf_data_len_limit) 292 | 293 | frame_data = self.get_base_frame_data() 294 | frame_data.append(0x20 + sequence_number) 295 | frame_data = (frame_data + 296 | data[bytes_sent:bytes_sent+data_bytes_in_msg]) 297 | cf.data = self._pad_data(frame_data) 298 | 299 | if self.debug: 300 | print("ISOTP SEND: %s " % cf) 301 | self._dispatcher.send(cf) 302 | 303 | sequence_number = sequence_number + 1 304 | # wrap around when sequence number reaches 0xF 305 | if sequence_number > 0xF: 306 | sequence_number = 0 307 | 308 | bytes_sent = bytes_sent + data_bytes_in_msg 309 | 310 | self._unset_filter() 311 | 312 | def _send_control_frame(self, is_extended_id): 313 | data = [0x30, self.block_size, self.st_min] 314 | fc = can.Frame(self.tx_arb_id, data=self._pad_data(data), 315 | extended=is_extended_id) 316 | if self.debug: 317 | print("ISOTP Control Frame: %s" % fc) 318 | self._dispatcher.send(fc) 319 | # reset block size counter 320 | self.block_size_counter = self.block_size 321 | 322 | def filter_received_frame(self,rx_frame): 323 | """ 324 | function establish if received frame has to be considered or not 325 | the next criterion are applied in and logic: 326 | - received frame CAN ID should not be in blacklist 327 | - if rx_arb_id is set received frame has to be on that CAN ID 328 | - if rx_filter_func is set the result applying it to the received frame should be true 329 | :param rx_frame: received frame 330 | :return: true if frame has to be accepted 331 | """ 332 | if rx_frame.arb_id in self.arb_ids_blacklist: 333 | return False 334 | 335 | if self.rx_arb_id: 336 | let_frame_pass = rx_frame.arb_id == self.rx_arb_id 337 | else: 338 | let_frame_pass = True 339 | 340 | if not self.rx_filter_func or not callable(self.rx_filter_func): 341 | return let_frame_pass 342 | else: 343 | return let_frame_pass and self.rx_filter_func(rx_frame) 344 | 345 | def get_base_frame_data(self): 346 | return [] -------------------------------------------------------------------------------- /pyvit/proto/isotpAddressing.py: -------------------------------------------------------------------------------- 1 | """ 2 | Details on how addressing works for ISOTP can be seen in ISO 15765-2 3 | """ 4 | 5 | from pyvit.proto.isotp import IsotpInterface 6 | from abc import ABC, abstractmethod 7 | from enum import Enum 8 | 9 | 10 | class Mtype(Enum): 11 | diagnostics = 1 12 | remote_diagnostics = 2 13 | 14 | 15 | class N_TAtype(Enum): 16 | functional = 1 17 | physical = 2 18 | 19 | 20 | class AddressingType(Enum): 21 | # CAN ID 11b, for each combination of N_SA, N_TA, N_TAtype, Mtype a unique CAN identifier is asigned 22 | NormalAddressing = 1 23 | # CAN ID 29b, mapping between addressing fields and CAN ID bits is specified 24 | # 28-26: priority bits, usually (110)bin -> (6)hex 25 | # 25,24: bits R (reserved) and DP (data page), set to 0 26 | # 23-16: bits PF (protocol data unit format) depends on N_TAtype. 27 | # if N_TAtype.physical then (218)dec, if N_TAtype.functional then (219)dec 28 | # 15-8: N_TA 29 | # 7-0: N_SA 30 | NormalFixedAddressing = 2 31 | # suppose CAN ID 11b, for each combination of N_SA, N_TAtype, Mtype a unique CAN identifier is assigned 32 | # N_TA is placed in the first byte of data, N_PCI info and N_Data are placed after 33 | ExtendedAddressing = 3 34 | # MType is set to Mtype.remote_diagnostics 35 | # if CAN ID 29b 36 | # 28-26: priority bits, usually (110)bin -> (6)hex 37 | # 25,24: bits R (reserved) and DP (data page), set to 0 38 | # 23-16: bits PF (protocol data unit format) depends on N_TAtype. 39 | # if N_TAtype.physical then (206)dec, if N_TAtype.functional then (205)dec 40 | # 15-8: N_TA 41 | # 7-0: N_SA 42 | # N_AE is placed in the first byte of data, N_PCI info and N_Data are placed after 43 | # if CAN ID 11b 44 | # for each combination of N_SA, N_TA, N_TAtype a unique CAN identifier is assigned 45 | # N_AE is placed in the first byte of data, N_PCI info and N_Data are placed after 46 | MixedAddressing = 4 47 | 48 | 49 | class IsotpAddressing(IsotpInterface,ABC): 50 | # protocol data unit format according to addressing mode 51 | PF = { 52 | AddressingType.NormalFixedAddressing: {N_TAtype.functional: 219, N_TAtype.physical: 218}, 53 | AddressingType.MixedAddressing: {N_TAtype.functional: 205, N_TAtype.physical: 206}, 54 | } 55 | EXTENDED_ID = { 56 | AddressingType.NormalAddressing: False, 57 | AddressingType.NormalFixedAddressing: True, 58 | AddressingType.ExtendedAddressing: False, 59 | AddressingType.MixedAddressing: False # in this case has to be interpreted as default value, could be also True 60 | } 61 | 62 | def __init__(self, dispatcher, n_sa = 0x00, n_ta = 0x00, n_ae = False, mtype = Mtype.diagnostics, n_tatype = N_TAtype.physical, 63 | addressingType = AddressingType.NormalAddressing, extended_id = False, rx_filter_func = False): 64 | self.addressingType = addressingType 65 | self.N_SA = n_sa 66 | self.N_AE = n_ae 67 | self.MType = mtype 68 | self.N_TAtype = n_tatype 69 | self.extended_id = extended_id or self.EXTENDED_ID[self.addressingType] 70 | self.N_TA = n_ta 71 | if dispatcher is None: 72 | # If dispatcher is None means that I don't really need to communicate, upper level is not needed 73 | return 74 | super().__init__(dispatcher, self.compute_tx_arb_id(), self.compute_rx_arb_id(), extended_id = self.extended_id, rx_filter_func = rx_filter_func) 75 | 76 | @property 77 | def N_TA(self): 78 | return self.__N_TA 79 | 80 | @N_TA.setter 81 | def N_TA(self,value): 82 | self.__N_TA = value 83 | # If target address changes I have to recompute the arbitration IDs 84 | self.tx_arb_id = self.compute_tx_arb_id() 85 | self.rx_arb_id = self.compute_rx_arb_id() 86 | 87 | @abstractmethod 88 | def compute_tx_arb_id(self): 89 | pass 90 | 91 | @abstractmethod 92 | def compute_rx_arb_id(self): 93 | pass 94 | 95 | 96 | class IsotpMixedAddressing (IsotpAddressing): 97 | def __init__(self, dispatcher, n_sa=0x00, n_ta=0x00, n_ae=False, mtype=Mtype.diagnostics, 98 | n_tatype=N_TAtype.physical, rx_filter_func=False): 99 | # in this case first byte is used for addressing purpose, space for data is only 6 bytes 100 | self.sf_data_len_limit = 6 101 | super().__init__(dispatcher, n_sa, n_ta, n_ae, mtype, n_tatype, AddressingType.MixedAddressing, True, 102 | rx_filter_func) 103 | 104 | def compute_tx_arb_id(self): 105 | # compose the N_AI fields to get the 29b CAN ID 106 | # print("%X" % (self.P & 0b111)) 107 | # print("%X" % ((self.P & 0b111) << 1)) 108 | # print("%X" % ((((self.P & 0b111) << 1)| self.R) << 1 | self.DP)) 109 | # print("%X" % (((((self.P & 0b111) << 1) | self.R) << 1) | self.DP)) 110 | return (((((((((((self.P & 0b111) << 1) | self.R) << 1) | self.DP) << 8) | self.PF[self.addressingType][ 111 | self.N_TAtype]) << 8) | self.N_TA) << 8) | self.N_SA) 112 | 113 | def compute_rx_arb_id(self): 114 | if self.N_TAtype == N_TAtype.functional: 115 | # If functional addressing is used I can have responses from multiple ECUs 116 | # Havin a rx_arb_id has no meaning 117 | return False 118 | # compose the N_AI fields to get the 29b CAN ID of the response message 119 | # N_TAtype should be always physical 120 | # N_TA and N_SA are reversed respect to tx_arb_id 121 | return (((((((((((self.P & 0b111) << 1) | self.R) << 1) | self.DP) << 8) | self.PF[self.addressingType][ 122 | self.N_TAtype]) << 8) | self.N_SA) << 8) | self.N_TA) 123 | 124 | def parse_frame(self, frame): 125 | # In case of mixed addressing first byte of data is the remote address 126 | frame.data.pop(0) 127 | return super(IsotpMixedAddressing, self).parse_frame(frame) 128 | 129 | def get_base_frame_data(self): 130 | # In case of mixed addressing first byte of data is the remote address 131 | return [self.N_AE] 132 | 133 | 134 | class IsotpExtendedAddressing (IsotpAddressing): 135 | def __init__(self, dispatcher, n_sa=0x00, n_ta=0x00, n_ae=False, mtype=Mtype.diagnostics, 136 | n_tatype=N_TAtype.physical, rx_filter_func=False): 137 | # in this case first byte is used for addressing purpose, space for data is only 6 bytes 138 | self.sf_data_len_limit = 6 139 | super().__init__(dispatcher, n_sa, n_ta, n_ae, mtype, n_tatype, AddressingType.ExtendedAddressing, False, 140 | rx_filter_func) 141 | 142 | def compute_tx_arb_id(self): 143 | return self._tx_arb_id 144 | 145 | 146 | def compute_rx_arb_id(self): 147 | return self._rx_arb_id 148 | 149 | def parse_frame(self, frame): 150 | # In case of extended addressing first byte of data is the target address 151 | frame.data.pop(0) 152 | return super(IsotpExtendedAddressing, self).parse_frame(frame) 153 | 154 | def get_base_frame_data(self): 155 | # In case of extended addressing first byte of data is the target address 156 | return [self.N_TA] 157 | 158 | class IsotpNormalFixedAddressing (IsotpAddressing): 159 | # priority 3 bits 160 | P = 6 161 | # reserved 1 bit 162 | R = 0 163 | # data page 1 bit 164 | DP = 0 165 | def __init__(self, dispatcher, n_sa=0x00, n_ta=0x00, n_ae=False, mtype=Mtype.diagnostics, 166 | n_tatype=N_TAtype.physical, rx_filter_func=False): 167 | super().__init__(dispatcher, n_sa, n_ta, n_ae, mtype, n_tatype, AddressingType.NormalFixedAddressing, True, rx_filter_func) 168 | 169 | def compute_tx_arb_id(self): 170 | # compose the N_AI fields to get the 29b CAN ID 171 | return (((((((((((self.P & 0b111) << 1) | self.R) << 1) | self.DP) << 8) | self.PF[self.addressingType][ 172 | self.N_TAtype]) << 8) | self.N_TA) << 8) | self.N_SA) 173 | 174 | def compute_rx_arb_id(self): 175 | if self.N_TAtype == N_TAtype.functional: 176 | # If functional addressing is used I can have responses from multiple ECUs 177 | # Havin a rx_arb_id has no meaning 178 | return False 179 | # compose the N_AI fields to get the 29b CAN ID of the response message 180 | # N_TAtype should be always physical 181 | # N_TA and N_SA are reversed respect to tx_arb_id 182 | return (((((((((((self.P & 0b111) << 1) | self.R) << 1) | self.DP) << 8) | self.PF[self.addressingType][ 183 | self.N_TAtype]) << 8) | self.N_SA) << 8) | self.N_TA) 184 | 185 | 186 | class IsotpNormalAddressing (IsotpAddressing): 187 | def __init__(self, dispatcher, tx_arb_id = 0x700, rx_arb_id = False, n_tatype = N_TAtype.physical, rx_filter_func = False): 188 | self._rx_arb_id = rx_arb_id 189 | self._tx_arb_id = tx_arb_id 190 | super().__init__(dispatcher, n_tatype = n_tatype, addressingType = AddressingType.NormalAddressing, 191 | extended_id = False, rx_filter_func = rx_filter_func) 192 | 193 | def compute_tx_arb_id(self): 194 | return self._tx_arb_id 195 | 196 | 197 | def compute_rx_arb_id(self): 198 | return self._rx_arb_id 199 | -------------------------------------------------------------------------------- /pyvit/proto/obdii.py: -------------------------------------------------------------------------------- 1 | from pyvit.proto.isotp import IsotpInterface 2 | 3 | 4 | class ObdException(Exception): 5 | pass 6 | 7 | 8 | class ObdInterface(IsotpInterface): 9 | def __init__(self, dispatcher, tx_arb_id=0x7DF, rx_arb_id=0x7E8): 10 | super().__init__(dispatcher, tx_arb_id, rx_arb_id) 11 | 12 | def request(self, mode, pid=None, timeout=0.25): 13 | # pack data according to OBD-II standard 14 | request = [mode] 15 | if pid is not None: 16 | request.append(pid) 17 | 18 | # send the request 19 | self.send(request) 20 | 21 | return self.recv(timeout=timeout) 22 | 23 | def get_supported_pids(self, mode=1, base_pid=0): 24 | if mode not in (1, 9): 25 | raise ValueError("Only modes 1 and 9 are supported") 26 | 27 | resp = self.request(mode, base_pid) 28 | 29 | # first byte is mode + 0x40, second byte is pid 30 | if resp is None or resp[0] != mode + 0x40 or len(resp) != 6: 31 | # no PID data received, return empty list 32 | return [] 33 | 34 | # remaining bytes are data, convert so we can do bitwise math 35 | bits = (resp[2] << 24) + (resp[3] << 16) + (resp[4] << 8) + resp[5] 36 | 37 | # PIDs are encoded bitwise, MSB is pid 1 38 | pids = [] 39 | pid = base_pid + 0x20 40 | while pid >= 0: 41 | if bits & 1: 42 | pids.append(pid) 43 | bits >>= 1 44 | pid -= 1 45 | 46 | # if the next range of PIDs is supported, get the list of 47 | # supported PIDs in that range recursively 48 | if base_pid + 0x20 in pids: 49 | pids += (self.get_supported_pids(mode=mode, 50 | base_pid=base_pid+0x20)) 51 | 52 | return sorted(pids) 53 | -------------------------------------------------------------------------------- /pyvit/proto/uds.py: -------------------------------------------------------------------------------- 1 | import operator 2 | import time 3 | import sys 4 | 5 | from math import log 6 | 7 | from pyvit.proto.isotpAddressing import N_TAtype, IsotpNormalAddressing, IsotpNormalFixedAddressing 8 | from pyvit.dispatch import Dispatcher 9 | 10 | 11 | def _byte_size(value): 12 | # return the number of bytes needed to represent a value 13 | if value == 0: 14 | return 1 15 | else: 16 | return int(log(value, 256)) + 1 17 | 18 | 19 | def _to_bytes(value, padding=0): 20 | # convert value to byte array, MSB first 21 | 22 | if isinstance(value, list): 23 | return value 24 | 25 | res = [] 26 | 27 | # return an empty list for NoneType 28 | if value is None: 29 | return [] 30 | 31 | while value > 0: 32 | res = [value & 0xFF] + res 33 | value = value >> 8 34 | 35 | # add '0' padding to the front of the list if specified 36 | while padding > 0: 37 | res = [0x00] + res 38 | padding -= 1 39 | 40 | return res 41 | 42 | 43 | def _from_bytes(bs): 44 | # convert byte array to value, MSB first 45 | res = 0 46 | for b in bs: 47 | res = res << 8 48 | res += b 49 | 50 | return res 51 | 52 | 53 | class UDSParameter: 54 | def __init__(self, name, data): 55 | self.name = name 56 | self.data = data 57 | 58 | for (k, v) in data.items(): 59 | setattr(self, k, v) 60 | 61 | def __dir__(self): 62 | # hack to make ipython completion nice 63 | return list(self.data.keys()) + ['to_str'] 64 | 65 | def to_str(self, value): 66 | for (k, v) in self.data.items(): 67 | if value == v: 68 | return k 69 | return "Unknown" 70 | 71 | def __repr__(self): 72 | result = self.name + ':\n' 73 | for (k, v) in sorted(self.data.items(), key=operator.itemgetter(1)): 74 | result += '\t%s:\t0x%X\n' % (k, v) 75 | return result 76 | 77 | 78 | NegativeResponse = UDSParameter('NegativeResponse', { 79 | 'positiveResponse': 0x00, 80 | # 0x01 - 0x0F ISOSAEReserved 81 | 'generalReject': 0x10, 82 | 'serviceNotSupported': 0x11, 83 | 'subFunctionNotSupported': 0x12, 84 | 'incorrectMessageLengthOrInvalidFormat': 0x13, 85 | 'responseTooLong': 0x14, 86 | # 0x15 - 0x20: ISOSAEReserved 87 | 'busyRepeatRequest': 0x21, 88 | 'conditionsNotCorrect': 0x22, 89 | # 0x23: ISOSAEReserved 90 | 'requestSequenceError': 0x24, 91 | 'noResponseFromSubnetComponent': 0x25, 92 | 'failurePreventsExecutionOfRequestedAction': 0x26, 93 | # 0x27 - 0x30 ISOSAEReserved 94 | 'requestOutOfRange': 0x31, 95 | # 0x32: ISOSAEReserved 96 | 'securityAccessDenied': 0x33, 97 | # 0x34: ISOSAEReserved 98 | 'invalidKey': 0x35, 99 | 'exceedNumebrOfAttempts': 0x36, 100 | 'requiredTimeDelayNotExpired': 0x37, 101 | # 0x38 - 0x4F: reservedByExtendedDataLinkSecurityDocument 102 | # 0x50 - 0x6F: ISOSAEReserved 103 | 'uploadDownloadNotAccepted': 0x70, 104 | 'transferDataSuspended': 0x71, 105 | 'generalProgrammingFailure': 0x72, 106 | 'wrongBlockSequenceCounter': 0x73, 107 | # 0x74 - 0x77: ISOSAEReserved 108 | 'responsePending': 0x78, 109 | # 0x79 - 0x7D: ISOSAEReserved 110 | 'subFunctionNotSupportedInActiveSession': 0x7E, 111 | 'serviceNotSupportedInActiveSession': 0x7F, 112 | # 0x80: ISOSAEReserved 113 | 'rpmTooHigh': 0x81, 114 | 'rpmTooLow': 0x82, 115 | 'engineIsRunning': 0x83, 116 | 'engineIsNotRunning': 0x84, 117 | 'engineRunTimeTooLow': 0x85, 118 | 'temperatureTooHigh': 0x86, 119 | 'temperatureTooLow': 0x87, 120 | 'vehicleSpeedTooHigh': 0x88, 121 | 'vehicleSpeedTooLow': 0x89, 122 | 'throttle/PedalTooHigh': 0x8A, 123 | 'throttle/PedalTooLow': 0x8B, 124 | 'transmissionRangeNotInNeutral': 0x8C, 125 | 'transmissionRangeNotInGear': 0x8D, 126 | # 0x8E: ISOSAEReserved 127 | 'brakeSwitch(es)NotClosed': 0x8F, 128 | 'shifterLeverNotInPark': 0x90, 129 | 'torqueConverterClutchLocked': 0x91, 130 | 'voltageTooHigh': 0x92, 131 | 'voltageTooLow': 0x93, 132 | # 0x94 - 0xFE: reservedForSpecificConditionsNotCorrect 133 | # 0xFF: ISOSAEReserved 134 | }) 135 | 136 | 137 | class NegativeResponseException(Exception): 138 | nrc_code = None 139 | 140 | @staticmethod 141 | def factory(nrc_data): 142 | """ 143 | For certain nrc_codes we have specific exception defined, this is a factory pattern which 144 | instantiate the right exception class depending of the nrc_code of the negative response 145 | :param nrc_data: 146 | :return: 147 | """ 148 | try: 149 | nrc_class_name = NegativeResponseException.negativeExceptionClassName(nrc_data[2]) 150 | thismodule = sys.modules[__name__] 151 | specific_except_class = getattr(thismodule,nrc_class_name) 152 | return specific_except_class(nrc_data) 153 | except (NameError,AttributeError): 154 | # Class does not exists for the exception, use general one 155 | return NegativeResponseException(nrc_data) 156 | 157 | def __init__(self, nrc_data): 158 | self.sid = nrc_data[1] 159 | self.nrc_code = nrc_data[2] 160 | 161 | def __str__(self): 162 | return 'NRC: SID = 0x%X: %s' % (self.sid, 163 | NegativeResponse.to_str(self.nrc_code)) 164 | 165 | @classmethod 166 | def negativeExceptionClassName(self, nrc_code): 167 | try: 168 | nrc_name = NegativeResponse.to_str(nrc_code) 169 | except ValueError: 170 | # Not able to identify a specific class for the negative response, use general one 171 | return 'NegativeResponseException' 172 | return nrc_name[:1].upper() + nrc_name[1:] + 'Exception' 173 | 174 | class ResponsePendingException(NegativeResponseException): 175 | """ 176 | This negative response type says to the client that the server is still computing the response, just wait more 177 | """ 178 | timeout = 0 179 | 180 | def __init__(self, nrc_data): 181 | super().__init__(nrc_data) 182 | # with these kind of negative response the server request the P2extende timeout to wait for the pendind response, see ISO 14229-1:2013 183 | # the maximum value for this parameter is 5000ms (5s), usually the exact one is comunicated in the response of the diagnosticSessionControl request 184 | self.timeout = 5 185 | 186 | class SecurityAccessDeniedException(NegativeResponseException): 187 | pass 188 | 189 | class ServiceNotSupportedException(NegativeResponseException): 190 | pass 191 | 192 | class SubFunctionNotSupportedInActiveSessionException(NegativeResponseException): 193 | pass 194 | 195 | class ServiceNotSupportedInActiveSessionException(NegativeResponseException): 196 | pass 197 | 198 | class SubFunctionNotSupportedException(NegativeResponseException): 199 | pass 200 | 201 | class TimeoutException(Exception): 202 | pass 203 | 204 | 205 | class GenericRequest(dict): 206 | SID = None 207 | service_name = None 208 | 209 | def __init__(self, name, sid): 210 | self.SID = sid 211 | self.name = name 212 | 213 | def _check_sid(self, data): 214 | if data[0] != self.SID: 215 | raise ValueError('Invalid SID for service' 216 | '(got 0x%X, expected 0x%X)' % 217 | (data[0], self.SID)) 218 | 219 | def __str__(self): 220 | return '%s Request: %s' % (self.name, super(GenericRequest, 221 | self).__str__()) 222 | 223 | 224 | class GenericResponse(dict): 225 | SID = None 226 | service_name = None 227 | 228 | def __init__(self, name, sid): 229 | self.SID = sid 230 | self.name = name 231 | 232 | def _check_nrc(self, data): 233 | """ Generic function for checking received data is valid. If data 234 | is not received, a timeout is raised. If an negative response was 235 | received an appropriate exception is raised """ 236 | 237 | if data is None: 238 | raise TimeoutException("No data received") 239 | 240 | if data[0] == 0x7F: 241 | raise NegativeResponseException.factory(data) 242 | elif data[0] != self.SID + 0x40: 243 | raise ValueError('Invalid SID for service' 244 | '(got 0x%X, expected 0x%X)' % 245 | (data[0], self.SID + 0x40)) 246 | 247 | def __str__(self): 248 | return '%s Response: %s' % (self.name, super(GenericResponse, 249 | self).__str__()) 250 | 251 | 252 | class DiagnosticSessionControl: 253 | """ DiagnosticSessionControl service """ 254 | SID = 0x10 255 | 256 | DiagnosticSessionType = UDSParameter('DiagnosticSessionType', { 257 | # 0x00: ISOASAEReserved 258 | 'defaultSession': 0x01, 259 | 'programmingSession': 0x02, 260 | 'extendedDiagnosticSession': 0x03, 261 | 'safetySystemDiagnosticSession': 0x04, 262 | # 0x05 - 0x3F: ISOSAEReserved 263 | # 0x40 - 0x5F: vehicleManufacturerSpecific 264 | # 0x60 - 0x7E: vehicleManufacturerSpecific 265 | # 0x7F: ISOASAEReserved 266 | }) 267 | 268 | class Response(GenericResponse): 269 | def __init__(self): 270 | super(DiagnosticSessionControl.Response, self).__init__( 271 | 'DiagnosticSessionControl', 272 | DiagnosticSessionControl.SID) 273 | 274 | def decode(self, data): 275 | self._check_nrc(data) 276 | try: 277 | self['diagnosticSessionType'] = data[1] 278 | self['sessionParameterRecord'] = data[2:] 279 | except IndexError: 280 | # I found on an Opel Corsa a positive response which does not include those info 281 | # clearly wrong according to the standard but it happens 282 | self['diagnosticSessionType'] = "" 283 | self['sessionParameterRecord'] = "" 284 | 285 | class Request(GenericRequest): 286 | def __init__(self, diagnostic_session_type=None): 287 | super(DiagnosticSessionControl.Request, self).__init__( 288 | 'DiagnosticSessionControl', 289 | DiagnosticSessionControl.SID) 290 | self['diagnosticSessionType'] = diagnostic_session_type 291 | 292 | def encode(self): 293 | return [self.SID, self['diagnosticSessionType']] 294 | 295 | def decode(self, data): 296 | self._check_sid(data) 297 | self['diagnosticSessionType'] = data[1] 298 | 299 | 300 | class ECUReset: 301 | """ ECUReset service """ 302 | SID = 0x11 303 | 304 | ResetType = UDSParameter('ResetType', { 305 | # 0x00: ISOASAEReserved 306 | 'hardReset': 0x01, 307 | 'keyOffOnReset': 0x02, 308 | 'softReset': 0x03, 309 | 'enableRapidPowerShutDown': 0x04, 310 | 'disableRapidPowerShutDown': 0x05 311 | # 0x06 - 0x3F: ISOSAEReserved 312 | # 0x40 - 0x5F: vehicleManufacturerSpecific 313 | # 0x60 - 0x7E: vehicleManufacturerSpecific 314 | # 0x7F: ISOASAEReserved 315 | }) 316 | 317 | class Response(GenericResponse): 318 | def __init__(self): 319 | super(ECUReset.Response, self).__init__('ECUReset', ECUReset.SID) 320 | 321 | def decode(self, data): 322 | self._check_nrc(data) 323 | self['resetType'] = data[1] 324 | 325 | # powerDownTime only present for enableRapidPowerShutDown 326 | if (self['resetType'] == 327 | ECUReset.ResetType.enableRapidPowerShutDown): 328 | self['powerDownTime'] = data[2] 329 | 330 | class Request(GenericRequest): 331 | def __init__(self, reset_type=0): 332 | super(ECUReset.Request, self).__init__('ECUReset', ECUReset.SID) 333 | self['resetType'] = reset_type 334 | 335 | def encode(self): 336 | return [self.SID, self['resetType']] 337 | 338 | def decode(self, data): 339 | self._check_sid(data) 340 | self['resetType'] = data[1] 341 | 342 | 343 | class SecurityAccess: 344 | """ SecurityAccess service """ 345 | SID = 0x27 346 | 347 | class Response(GenericResponse): 348 | def __init__(self): 349 | super(SecurityAccess.Response, self).__init__('SecurityAccess', 350 | SecurityAccess.SID) 351 | 352 | def decode(self, data): 353 | self._check_nrc(data) 354 | self['securityAccessType'] = data[1] 355 | 356 | # securitySeed is only present for seed requests, which are odd 357 | # values of securityAccessType 358 | if self['securityAccessType'] % 2 == 1: 359 | self['securitySeed'] = _from_bytes(data[2:]) 360 | 361 | class Request(GenericRequest): 362 | def __init__(self, security_access_type=0, security_key=None): 363 | super(SecurityAccess.Request, self).__init__('SecurityAccess', 364 | SecurityAccess.SID) 365 | self['securityAccessType'] = security_access_type 366 | self['securityKey'] = security_key 367 | 368 | def encode(self): 369 | return ([self.SID, self['securityAccessType']] + 370 | _to_bytes(self['securityKey'])) 371 | 372 | def decode(self, data): 373 | self._check_sid(data) 374 | self['securityAccessType'] = data[1] 375 | self['securityKey'] = _from_bytes(data[2:]) 376 | 377 | 378 | class CommunicationControl: 379 | """ CommunicationControl service """ 380 | SID = 0x28 381 | 382 | ControlType = UDSParameter('ControlType', { 383 | 'enableRxAndTx': 0x00, 384 | 'enableRxAndDisableTx': 0x01, 385 | 'disableRxandEnableTx': 0x02, 386 | 'disableRxAndTx': 0x03, 387 | 'enableRxAndDisableTxWithEnhancedAddressInformation': 0x4, 388 | 'enableRxAndTxWithEnhancedAddressInformation': 0x5, 389 | # 0x06 - 0x3F: ISOSAEReserved 390 | # 0x40 - 0x5F: vehicleManufacturerSpecific 391 | # 0x60 - 0x7E: vehicleManufacturerSpecific 392 | # 0x7F: ISOASAEReserved 393 | }) 394 | 395 | CommunicationType = UDSParameter('CommunicationType', { 396 | # Bit encoded parameter, B.1 of ISO14229 397 | # bits 0-1: 398 | # 0b00: ISOSAEReserved 399 | # 0b01: normalCommunicationMessages 400 | # 0b10: networkManagementCommunicationMessages 401 | # 0b11: networkManagementCommunicationMessages and 402 | # normalCommunicationMessages 403 | # bits 2-3: ISOSAEReserved 404 | 'normalCommunicationMessages': 0b01, 405 | 'networkManagementCommunicationMessages': 0b10, 406 | 'networkManagementCommunicationMessagesANDnormalCommunicationMessages': 0b11, 407 | }) 408 | 409 | class Response(GenericResponse): 410 | def __init__(self): 411 | super(CommunicationControl.Response, self).__init__( 412 | 'CommunicationControl', 413 | CommunicationControl.SID) 414 | 415 | def decode(self, data): 416 | self._check_nrc(data) 417 | self['controlType'] = data[1] 418 | 419 | class Request(GenericRequest): 420 | def __init__(self, control_type=0, communication_type=0, 421 | network_number=0): 422 | super(CommunicationControl.Request, self).__init__( 423 | 'CommunicationControl', 424 | CommunicationControl.SID) 425 | self['controlType'] = control_type 426 | self['communicationType'] = communication_type 427 | self['networkNumber'] = network_number 428 | 429 | def encode(self): 430 | # communicationType lower nybble is the type of communication. 431 | # upper nybble is which network to disable/enable, where 0x0 432 | # specifies all networks, 0xF specifies network the message was 433 | # received on, and 0x01-0x0E specify a network by network number 434 | return [self.SID, self['controlType'], 435 | self['communicationType'] + (self['networkNumber'] << 4)] 436 | 437 | def decode(self, data): 438 | self._check_sid(data) 439 | self['controlType'] = data[1] 440 | self['communicationType'] = data[2] & 0xF 441 | self['networkNumber'] = data[2] >> 4 442 | 443 | 444 | class TesterPresent: 445 | """ TesterPresent service """ 446 | SID = 0x3E 447 | 448 | ZeroSubFunction = UDSParameter('ZeroSubFunction', { 449 | 'requestPosRspMsg': 0x00, 450 | 'suppressPosRspMsg': 0x80, 451 | }) 452 | 453 | class Response(GenericResponse): 454 | def __init__(self): 455 | super(TesterPresent.Response, self).__init__('TesterPresent', 456 | TesterPresent.SID) 457 | 458 | def decode(self, data): 459 | self._check_nrc(data) 460 | 461 | class Request(GenericRequest): 462 | def __init__(self, suppress_response=False): 463 | super(TesterPresent.Request, self).__init__('TesterPresent', 464 | TesterPresent.SID) 465 | if suppress_response: 466 | self['subFunction'] = TesterPresent.ZeroSubFunction.suppressPosRspMsg 467 | else: 468 | self['subFunction'] = TesterPresent.ZeroSubFunction.requestPosRspMsg 469 | 470 | def encode(self): 471 | return [self.SID, self['subFunction']] 472 | 473 | def decode(self, data): 474 | self._check_sid(data) 475 | 476 | 477 | class AccessTimingParameter: 478 | """ AccessTimingParameter service """ 479 | SID = 0x83 480 | 481 | AccessType = UDSParameter('AccessType', { 482 | # 0x00: ISOASAEReserved 483 | 'readExtendedTimingParameterSet': 0x01, 484 | 'setTimingParametersToDefaultValues': 0x02, 485 | 'readCurrentlyActiveTimingParameters': 0x03, 486 | 'setTimingParametersToGivenValues': 0x04, 487 | # 0x05 - 0xFF: ISOSAEReserved 488 | }) 489 | 490 | class Response(GenericResponse): 491 | def __init__(self): 492 | super(AccessTimingParameter.Response, self).__init__( 493 | 'AccessTimingParameter', 494 | AccessTimingParameter.SID) 495 | 496 | def decode(self, data): 497 | self._check_nrc(data) 498 | self['timingParameterAccessType'] = data[1] 499 | if (self['timingParameterAccessType'] == 500 | AccessTimingParameter.AccessType 501 | .readExtendedTimingParameterSet or 502 | self['timingParameterAccessType'] == 503 | AccessTimingParameter.AccessType 504 | .readCurrentlyActiveTimingParameters): 505 | self['TimingParameterResponseRecord'] = data[2:] 506 | 507 | class Request(GenericRequest): 508 | def __init__(self, access_type=0, request_record=None): 509 | super(AccessTimingParameter.Request, self).__init__( 510 | 'AccessTimingParameter', 511 | AccessTimingParameter.SID) 512 | self['accessType'] = access_type 513 | self['requestRecord'] = request_record 514 | 515 | def encode(self): 516 | result = [self.SID, self['accessType']] 517 | 518 | # TimingParameterRequestRecord is present only when 519 | # timingParameterAccessType = setTimingParametersToGivenValues 520 | if (self['accessType'] == 521 | AccessTimingParameter.AccessType. 522 | setTimingParametersToGivenValues): 523 | result += self['requestRecord'] 524 | 525 | return result 526 | 527 | def decode(self, data): 528 | self._check_sid(data) 529 | self['accessType'] = data[1] 530 | self['requestRecord'] = data[2:] 531 | 532 | 533 | class SecuredDataTransmission: 534 | """ SecuredDataTransmission service """ 535 | SID = 0x84 536 | 537 | class Response(GenericResponse): 538 | def __init__(self): 539 | super(SecuredDataTransmission.Response, self).__init__( 540 | 'SecuredDataTransmission', 541 | SecuredDataTransmission.SID) 542 | 543 | def decode(self, data): 544 | self._check_nrc(data) 545 | self['securityDataResponseRecord'] = data[1:] 546 | 547 | class Request(GenericRequest): 548 | def __init__(self, data_record=None): 549 | super(SecuredDataTransmission.Request, self).__init__( 550 | 'SecuredDataTransmission', 551 | SecuredDataTransmission.SID) 552 | self['dataRecord'] = data_record 553 | 554 | def encode(self): 555 | return [self.SID] + self['dataRecord'] 556 | 557 | def decode(self, data): 558 | self._check_sid(data) 559 | self['dataRecord'] = data[1:] 560 | 561 | 562 | class ControlDTCSetting: 563 | """ ControlDTCSetting service """ 564 | SID = 0x85 565 | 566 | DTCSettingType = UDSParameter('DTCSettingType', { 567 | # 0x00: ISOASAEReserved 568 | 'on': 0x01, 569 | 'off': 0x02, 570 | # 0x03 - 0x3F: ISOSAEReserved 571 | # 0x40 - 0x5F: vehicleManufacturerSpecific 572 | # 0x60 - 0x7E: vehicleManufacturerSpecific 573 | # 0x05 - 0xFF: ISOSAEReserved 574 | }) 575 | 576 | class Response(GenericResponse): 577 | def __init__(self): 578 | super(ControlDTCSetting.Response, self).__init__( 579 | 'ControlDTCSetting', 580 | ControlDTCSetting.SID) 581 | 582 | def decode(self, data): 583 | self._check_nrc(data) 584 | self['DTCSettingType'] = data[1] 585 | 586 | class Request(GenericRequest): 587 | def __init__(self, dtc_setting_type=0, 588 | dtc_setting_control_option_record=None): 589 | super(ControlDTCSetting.Request, self).__init__( 590 | 'ControlDTCSetting', 591 | ControlDTCSetting.SID) 592 | if dtc_setting_control_option_record is None: 593 | dtc_setting_control_option_record = [] 594 | self['DTCSettingType'] = dtc_setting_type 595 | self['DTCSettingControlOptionRecord'] = ( 596 | dtc_setting_control_option_record) 597 | 598 | def encode(self): 599 | return ([self.SID, self['DTCSettingType']] + 600 | self['DTCSettingControlOptionRecord']) 601 | 602 | def decode(self, data): 603 | self._check_sid(data) 604 | self['DTCSettingType'] = data[1] 605 | self['DTCSettingControlOptionRecord'] = data[2:] 606 | 607 | 608 | class ResponseOnEvent: 609 | """ ResponseOnEvent service """ 610 | SID = 0x86 611 | 612 | EventType = UDSParameter('EventType', { 613 | 'stopResponseOnEvent': 0x00, 614 | 'onDTCStatusChange': 0x01, 615 | 'onTimerInterrupt': 0x02, 616 | 'onChangeOfDataIdentifier': 0x03, 617 | 'reportActivatedEvents': 0x04, 618 | 'startResponseOnEvent': 0x05, 619 | 'clearResponseOnEvent': 0x06, 620 | 'onComparisonOfValues': 0x07, 621 | # 0x08 - 0x1F: ISOSAEReserved 622 | # 0x20 - 0x2F: vehicleManufacturerSpecific 623 | # 0x30 - 0x3E: vehicleManufacturerSpecific 624 | # 0x3F: ISOSAEReserved 625 | # bit 6 is store/doNotStore flag 626 | }) 627 | 628 | class Response(GenericResponse): 629 | def __init__(self): 630 | super(ResponseOnEvent.Response, self).__init__('ResponseOnEvent', 631 | ResponseOnEvent.SID) 632 | 633 | def decode(self, data): 634 | self._check_nrc(data) 635 | # TODO 636 | self['data'] = data 637 | 638 | class Request(GenericRequest): 639 | def __init__(self, event_type=0, event_window_time=0x02, 640 | event_type_record=None, service_to_respond_to_record=None): 641 | super(ResponseOnEvent.Request, self).__init__( 642 | 'ResponseOnEvent', 643 | ResponseOnEvent.SID) 644 | if event_type_record is None: 645 | event_type_record = [] 646 | if service_to_respond_to_record is None: 647 | service_to_respond_to_record = [] 648 | # validate eventTypeRecord length for each eventType 649 | if (event_type == ResponseOnEvent.EventType.stopResponseOnEvent and 650 | len(event_type_record) != 0): 651 | raise ValueError('Invalid eventTypeRecord length') 652 | elif (event_type == ResponseOnEvent.EventType.onDTCStatusChange and 653 | len(event_type_record) != 1): 654 | raise ValueError('Invalid eventTypeRecord length') 655 | elif (event_type == ResponseOnEvent.EventType.onTimerInterrupt and 656 | len(event_type_record) != 1): 657 | raise ValueError('Invalid eventTypeRecord length') 658 | elif (event_type == ResponseOnEvent.EventType. 659 | onChangeOfDataIdentifier and len(event_type_record) != 2): 660 | raise ValueError('Invalid eventTypeRecord length') 661 | elif (event_type == ResponseOnEvent.EventType. 662 | reportActivatedEvents and len(event_type_record) != 0): 663 | raise ValueError('Invalid eventTypeRecord length') 664 | elif (event_type == ResponseOnEvent.EventType. 665 | startResponseOnEvent and len(event_type_record) != 0): 666 | raise ValueError('Invalid eventTypeRecord length') 667 | elif (event_type == ResponseOnEvent.EventType. 668 | clearResponseOnEvent and len(event_type_record) != 0): 669 | raise ValueError('Invalid eventTypeRecord length') 670 | elif (event_type == ResponseOnEvent.EventType. 671 | onComparisonOfValues and len(event_type_record) != 10): 672 | ReadDTCInformation 673 | 674 | self['eventType'] = event_type 675 | # event_window_type defaults to 0x02, specifying infinite time 676 | self['eventWindowTime'] = event_window_time 677 | self['eventTypeRecord'] = event_type_record 678 | self['serviceToRespondToRecord'] = service_to_respond_to_record 679 | 680 | def encode(self): 681 | return ([self.SID, self['eventType'], self['eventWindowTime']] + 682 | self['eventTypeRecord'] + self['serviceToRespondToRecord']) 683 | 684 | def decode(self, data): 685 | self._check_sid(data) 686 | self['eventType'] = data[1] 687 | self['eventWindowTime'] = data[2] 688 | 689 | if (self['eventType'] == 690 | ResponseOnEvent.EventType.stopResponseOnEvent): 691 | event_type_record_len = 0 692 | elif (self['eventType'] == 693 | ResponseOnEvent.EventType.onDTCStatusChange): 694 | event_type_record_len = 1 695 | elif (self['eventType'] == 696 | ResponseOnEvent.EventType.onTimerInterrupt): 697 | event_type_record_len = 1 698 | elif (self['eventType'] == 699 | ResponseOnEvent.EventType.onChangeOfDataIdentifier): 700 | event_type_record_len = 2 701 | elif (self['eventType'] == 702 | ResponseOnEvent.EventType.reportActivatedEvents): 703 | event_type_record_len = 0 704 | elif (self['eventType'] == 705 | ResponseOnEvent.EventType.startResponseOnEvent): 706 | event_type_record_len = 0 707 | elif (self['eventType'] == 708 | ResponseOnEvent.EventType.clearResponseOnEvent): 709 | event_type_record_len = 0 710 | elif (self['eventType'] == 711 | ResponseOnEvent.EventType.onComparisonOfValues): 712 | event_type_record_len = 10 713 | 714 | self['eventTypeRecord'] = data[3: 3 + event_type_length] 715 | try: 716 | self['serviceToRespondToRecord'] = data[3 + event_type_length: 717 | 3 + event_type_length + 718 | event_type_record_len] 719 | except IndexError: 720 | self['serviceToRespondToRecord'] = None 721 | 722 | 723 | class LinkControl: 724 | """ LinkControl service """ 725 | SID = 0x87 726 | 727 | LinkControlType = UDSParameter('LinkControlType', { 728 | # 0x0: ISOSAEReserved 729 | 'verifyBaudrateTransitionWithFixedBaudrate': 0x01, 730 | 'verifyBaudrateTransitionWithSpecificBaudrate': 0x02, 731 | 'transitionBaudrate': 0x03, 732 | # 0x04 - 0x3F: ISOSAEReserved 733 | # 0x40 - 0x5F: vehicleManufacturerSpecific 734 | # 0x60 - 0x7E: vehicleManufacturerSpecific 735 | # 0x7F: ISOSAEReserved 736 | }) 737 | 738 | BaudrateIdentifier = UDSParameter('BaudrateIdentifier', { 739 | # B.3 of ISO14229 740 | # 0x0: ISOSAEReserved 741 | 'PC9600Baud': 0x01, 742 | 'PC19200Baud': 0x02, 743 | 'PC38400Baud': 0x03, 744 | 'PC57600Baud': 0x04, 745 | 'PC115200Baud': 0x05, 746 | # 0x06 - 0x0F: ISOSAEReserved 747 | 'CAN125000Baud': 0x10, 748 | 'CAN250000Baud': 0x11, 749 | 'CAN500000Baud': 0x12, 750 | 'CAN1000000Baud': 0x13, 751 | # 0x14 - 0xFF: ISOSAEReserved 752 | }) 753 | 754 | class Response(GenericResponse): 755 | def __init__(self): 756 | super(LinkControl.Response, self).__init__('LinkControl', 757 | LinkControl.SID) 758 | 759 | def decode(self, data): 760 | self._check_nrc(data) 761 | self['linkControlType'] = data[1] 762 | 763 | class Request(GenericRequest): 764 | def __init__(self, link_control_type=0, link_baudrate_record=None): 765 | super(LinkControl.Request, self).__init__('LinkControl', 766 | LinkControl.SID) 767 | self['linkControlType'] = link_control_type 768 | 769 | if link_baudrate_record is None: 770 | link_baudrate_record = [] 771 | 772 | # baudrate can be a bit/s value (FixedBaudrate) 773 | # or a baudrateIdentifier (SpecificBaudrate) 774 | # validate and convert to an array of bytes 775 | if (link_control_type == 776 | LinkControl.LinkControlType. 777 | verifyBaudrateTransitionWithFixedBaudrate): 778 | if link_baudrate_record > 0xFF: 779 | raise ValueError('Invalid fixed baudrate') 780 | else: 781 | self['linkBaudrateRecord'] = [link_baudrate_record] 782 | elif (link_control_type == 783 | LinkControl.LinkControlType. 784 | verifyBaudrateTransitionWithSpecificBaudrate): 785 | if link_baudrate_record > 0xFFFFFF: 786 | raise ValueError('Invalid specific baudrate') 787 | else: 788 | self['linkBaudrateRecord'] = [baudrate >> 16, 789 | (baudrate >> 8) & 0xFF, 790 | baudrate & 0xFF] 791 | else: 792 | self['linkBaudrateRecord'] = link_baudrate_record 793 | 794 | def encode(self): 795 | return ([self.SID, self['linkControlType']] + 796 | self['linkBaudrateRecord']) 797 | 798 | def decode(self, data): 799 | self._check_sid(data) 800 | self['linkControlType'] = data[1] 801 | self['linkBaudrateRecord'] = data[2:] 802 | 803 | 804 | class ReadDataByIdentifier: 805 | """ ReadDataByIdentifier service """ 806 | # NB: this currently only supports reading one DID at a time 807 | SID = 0x22 808 | 809 | class Response(GenericResponse): 810 | def __init__(self): 811 | super(ReadDataByIdentifier.Response, self).__init__( 812 | 'ReadDataByIdentifier', 813 | ReadDataByIdentifier.SID) 814 | 815 | def decode(self, data): 816 | self._check_nrc(data) 817 | self['dataIdentifier'] = (data[1] << 8) + data[2] 818 | self['dataRecord'] = data[3:] 819 | self['dataRecordASCII'] = ''.join(chr(i) for i in self['dataRecord']) 820 | 821 | class Request(GenericRequest): 822 | def __init__(self, data_identifier=0): 823 | super(ReadDataByIdentifier.Request, self).__init__( 824 | 'ReadDataByIdentifier', 825 | ReadDataByIdentifier.SID) 826 | if data_identifier < 0 or data_identifier > 0xFFFF: 827 | raise ValueError('Invalid dataIdentifier, ' 828 | 'must be > 0 and < 0xFFFF') 829 | self['dataIdentifier'] = data_identifier 830 | 831 | def encode(self): 832 | return [self.SID, 833 | self['dataIdentifier'] >> 8, 834 | self['dataIdentifier'] & 0xFF] 835 | 836 | def decode(self, data): 837 | self._check_sid(data) 838 | self['dataIdentifier'] = _from_bytes(data[1:3]) 839 | 840 | 841 | class ReadMemoryByAddress: 842 | """ ReadMemoryByAddress service """ 843 | SID = 0x23 844 | 845 | class Response(GenericResponse): 846 | def __init__(self): 847 | super(ReadMemoryByAddress.Response, self).__init__( 848 | 'ReadMemoryByAddress', 849 | ReadMemoryByAddress.SID) 850 | 851 | def decode(self, data): 852 | self._check_nrc(data) 853 | self['dataRecord'] = data[1:] 854 | 855 | class Request(GenericRequest): 856 | def __init__(self, memory_address=0, memory_size=0): 857 | super(ReadMemoryByAddress.Request, self).__init__( 858 | 'ReadMemoryByAddress', 859 | ReadMemoryByAddress.SID) 860 | self['memoryAddress'] = memory_address 861 | self['memorySize'] = memory_size 862 | 863 | def encode(self): 864 | # addressAndLengthFormatIdentifier specifies length of 865 | # address and size 866 | # uppper nybble is byte length of size 867 | # lower nybble is byte length of address 868 | format_identifier = ((_byte_size(self['memorySize']) << 4) + 869 | _byte_size(self['memoryAddress'])) 870 | 871 | return ([self.SID, format_identifier] + 872 | _to_bytes(self['memoryAddress']) + 873 | _to_bytes(self['memorySize'])) 874 | 875 | def decode(self, data): 876 | self._check_sid(data) 877 | memory_size_size = data[1] >> 4 878 | memory_address_size = data[1] & 0x0F 879 | self['memoryAddress'] = _from_bytes(data[2: 880 | 2 + memory_address_size]) 881 | self['memorySize'] = _from_bytes(data[2 + memory_address_size: 882 | 2 + memory_address_size + 883 | memory_size_size]) 884 | 885 | 886 | class ReadScalingDataByIdentifier: 887 | """ ReadScalingDataByIdentifier service """ 888 | SID = 0x24 889 | 890 | class Response(GenericResponse): 891 | def __init__(self): 892 | super(ReadScalingDataByIdentifier.Response, self).__init__( 893 | 'ReadScalingDataByIdentifier', 894 | ReadScalingDataByIdentifier.SID) 895 | 896 | def decode(self, data): 897 | self._check_nrc(data) 898 | self['dataIdentifier'] = (data[1] << 8) + data[2] 899 | # TODO, decode more specifically 900 | self['scalingData'] = data[3:] 901 | 902 | class Request(GenericRequest): 903 | def __init__(self, data_identifier): 904 | super(ReadScalingDataByIdentifier.Request, self).__init__( 905 | 'ReadScalingDataByIdentifier', 906 | ReadScalingDataByIdentifier.SID) 907 | if data_identifier < 0 or data_identifier > 0xFFFF: 908 | raise ValueError('Invalid dataIdentifier, ' 909 | 'must be > 0 and < 0xFFFF') 910 | self['dataIdentifier'] = data_identifier 911 | 912 | def encode(self): 913 | return [self.SID, 914 | (self['dataIdentifier'] >> 8), 915 | self['dataIdentifier'] & 0xFF] 916 | 917 | def decode(self, data): 918 | self._check_sid(data) 919 | self['dataIdentifier'] = _from_bytes(data[1:3]) 920 | 921 | 922 | class ReadDataByPeriodicIdentifier: 923 | """ ReadDataByPeriodicIdentifier service """ 924 | SID = 0x2A 925 | 926 | TransmissionMode = UDSParameter('TransmissionMode', { 927 | # 0x00: ISOASAEReserved 928 | 'sendAtSlowRate': 0x01, 929 | 'sendAtMediumRate': 0x02, 930 | 'sendAtFastRate': 0x03, 931 | 'stopSending': 0x04, 932 | # 0x05 - 0xFF: ISOSAEReserved 933 | }) 934 | 935 | class Response(GenericResponse): 936 | def __init__(self): 937 | super(ReadDataByPeriodicIdentifier.Response, self).__init__( 938 | 'ReadDataByPeriodicIdentifier', 939 | ReadDataByPeriodicIdentifier.SID) 940 | 941 | def decode(self, data): 942 | self._check_nrc(data) 943 | # this response has a periodic response, in one of two formats 944 | # we can't know what format the server uses, so return data 945 | # unformatted 946 | self['periodicReponse'] = data[1:] 947 | 948 | class Request(GenericRequest): 949 | def __init__(self, transmission_mode=1, *data_identifiers): 950 | super(ReadDataByPeriodicIdentifier.Request, self).__init__( 951 | 'ReadDataByPeriodicIdentifier', 952 | ReadDataByPeriodicIdentifier.SID) 953 | if transmission_mode < 1 or transmission_mode > 4: 954 | raise ValueError('Invalid transmission mode') 955 | 956 | if (transmission_mode != ReadDataByPeriodicIdentifier. 957 | TransmissionMode.stopSending and len(data_identifiers) == 958 | 0): 959 | raise ValueError('At least one dataIdentifier must be ' 960 | 'provided for this transmission mode') 961 | self['transmissionMode'] = transmission_mode 962 | self['dataIdentifiers'] = list(data_identifiers) 963 | 964 | def encode(self): 965 | return ([self.SID, self['transmissionMode']] + 966 | self['dataIdentifiers']) 967 | 968 | def decode(self, data): 969 | self._check_sid(data) 970 | self['transmissionMode'] = data[1] 971 | self['dataIdentifiers'] = data[2:] 972 | 973 | 974 | class DynamicallyDefineDataIdentifier: 975 | """ DynamicallyDefineDataIdentifier service """ 976 | SID = 0x2C 977 | 978 | class Response(GenericResponse): 979 | def __init__(self): 980 | raise NotImplementedError("Service not implemented.") 981 | 982 | class Request(GenericRequest): 983 | def __init__(self): 984 | raise NotImplementedError("Service not implemented.") 985 | 986 | 987 | class WriteDataByIdentifier: 988 | """ WriteDataByIdentifier service """ 989 | SID = 0x2E 990 | 991 | class Response(GenericResponse): 992 | def __init__(self): 993 | super(WriteDataByIdentifier.Response, self).__init__( 994 | 'WriteDataByIdentifier', 995 | WriteDataByIdentifier.SID) 996 | 997 | def decode(self, data): 998 | self._check_nrc(data) 999 | self['dataIdentifier'] = _from_bytes(data[1:3]) 1000 | 1001 | class Request(GenericRequest): 1002 | def __init__(self, data_identifier=0, data_record=None): 1003 | super(WriteDataByIdentifier.Request, self).__init__( 1004 | 'WriteDataByIdentifier', 1005 | WriteDataByIdentifier.SID) 1006 | 1007 | if data_identifier < 0 or data_identifier > 0xFFFF: 1008 | raise ValueError('Invalid dataIdentifier, ' 1009 | 'must be > 0 and < 0xFFFF') 1010 | self['dataIdentifier'] = data_identifier 1011 | self['dataRecord'] = data_record 1012 | 1013 | def encode(self): 1014 | return ([self.SID] + _to_bytes(self['dataIdentifier']) + 1015 | self['dataRecord']) 1016 | 1017 | def decode(self, data): 1018 | self._check_sid(data) 1019 | self['dataIdentifier'] = _from_bytes(data[1:3]) 1020 | self['dataRecord'] = _from_bytes(data[3:]) 1021 | 1022 | 1023 | class WriteMemoryByAddress: 1024 | """ WriteMemoryByAddress service """ 1025 | SID = 0x3D 1026 | 1027 | class Response(GenericResponse): 1028 | def __init__(self): 1029 | super(WriteMemoryByAddress.Response, self).__init__( 1030 | 'WriteMemoryByAddress', 1031 | WriteMemoryByAddress.SID) 1032 | 1033 | def decode(self, data): 1034 | self._check_nrc(data) 1035 | format_identifier = data[1] 1036 | # get parameter lengts from format identifier 1037 | addr_bytes = format_identifier & 0x0F 1038 | size_bytes = format_identifier >> 4 1039 | 1040 | self['memoryAddress'] = _from_bytes(data[2: 2 + addr_bytes]) 1041 | self['memorySize'] = _from_bytes(data[2 + addr_bytes: 1042 | 2 + addr_bytes + size_bytes]) 1043 | 1044 | class Request(GenericRequest): 1045 | def __init__(self, memory_address=0, data_record=None, 1046 | memory_size=None): 1047 | super(WriteMemoryByAddress.Request, self).__init__( 1048 | 'WriteMemoryByAddress', 1049 | WriteMemoryByAddress.SID) 1050 | self['memoryAddress'] = memory_address 1051 | self['dataRecord'] = data_record 1052 | if memory_size is None: 1053 | self['memorySize'] = len(data_record) 1054 | else: 1055 | self['memorySize'] = memory_size 1056 | 1057 | def encode(self): 1058 | # addressAndLengthFormatIdentifier specifies length of address and 1059 | # size 1060 | # uppper nybble is byte length of size 1061 | # lower nybble is byte length of address 1062 | format_identifier = ((_byte_size(self['memorySize']) << 4) + 1063 | _byte_size(self['memoryAddress'])) 1064 | 1065 | return ([self.SID, format_identifier] + 1066 | _to_bytes(self['memoryAddress']) + 1067 | _to_bytes(self['memorySize']) + 1068 | self['dataRecord']) 1069 | 1070 | def decode(self, data): 1071 | addr_bytes = format_identifier & 0x0F 1072 | size_bytes = format_identifier >> 4 1073 | self['memoryAddress'] = _from_bytes(data[2: 2 + addr_bytes]) 1074 | self['memorySize'] = _from_bytes(data[2 + addr_bytes: 1075 | 2 + addr_bytes + size_bytes]) 1076 | self['dataRecord'] = data[2 + addr_bytes + size_bytes:] 1077 | 1078 | 1079 | class ClearDiagnosticInformation: 1080 | """ ClearDiagnosticInformation service """ 1081 | SID = 0x14 1082 | 1083 | class Response(GenericResponse): 1084 | def __init__(self): 1085 | super(ClearDiagnosticInformation.Response, self).__init__( 1086 | 'ClearDiagnosticInformation', 1087 | ClearDiagnosticInformation.SID) 1088 | 1089 | def decode(self, data): 1090 | self._check_nrc(data) 1091 | 1092 | class Request(GenericRequest): 1093 | def __init__(self, group_of_dtc=0): 1094 | super(ClearDiagnosticInformation.Request, self).__init__( 1095 | 'ClearDiagnosticInformation', 1096 | ClearDiagnosticInformation.SID) 1097 | self['groupOfDTC'] = group_of_dtc 1098 | 1099 | def encode(self): 1100 | return [self.SID] + _to_bytes(self['groupOfDTC']) 1101 | 1102 | def decode(self, data): 1103 | self._check_sid(data) 1104 | self['groupOfDTC'] = data[1:] 1105 | 1106 | 1107 | class ReadDTCInformation: 1108 | """ ReadDTCInformation service """ 1109 | SID = 0x19 1110 | 1111 | class Response(GenericResponse): 1112 | def __init__(self): 1113 | super(ReadDTCInformation.Response, self).__init__( 1114 | 'ReadDTCInformation', 1115 | ReadDTCInformation.SID) 1116 | 1117 | def encode(self): 1118 | return ([self.SID + 0x40] + 1119 | _to_bytes(self['data'])) 1120 | 1121 | def decode(self, data): 1122 | # self._check_nrc(data) 1123 | # TODO implement specific parameters decode 1124 | self['data'] = data[1:] 1125 | 1126 | class Request(GenericRequest): 1127 | def __init__(self): 1128 | super(ReadDTCInformation.Request, self).__init__( 1129 | 'ReadDTCInformation', 1130 | ReadDTCInformation.SID) 1131 | 1132 | def encode(self): 1133 | return ([self.SID] + 1134 | _to_bytes(self['data'])) 1135 | 1136 | def decode(self, data): 1137 | self._check_sid(data) 1138 | # TODO implement specific parameters decode 1139 | self['data'] = data[1:] 1140 | 1141 | 1142 | class InputOutputControlByIdentifier: 1143 | """ InputOutputControlByIdentifier service """ 1144 | SID = 0x2F 1145 | 1146 | class Response(GenericResponse): 1147 | def __init__(self): 1148 | super(InputOutputControlByIdentifier.Response, self).__init__( 1149 | 'InputOutputControlByIdentifier', 1150 | InputOutputControlByIdentifier.SID) 1151 | 1152 | def decode(self, data): 1153 | self._check_nrc(data) 1154 | self['dataIdentifier'] = _from_bytes(data[1:3]) 1155 | self['controlStatusRecord'] = data[3:] 1156 | 1157 | class Request(GenericRequest): 1158 | def __init__(self, data_identifier=0, control_option_record=0, 1159 | control_enable_mask_record=None): 1160 | super(InputOutputControlByIdentifier.Request, self).__init__( 1161 | 'InputOutputControlByIdentifier', 1162 | InputOutputControlByIdentifier.SID) 1163 | if data_identifier < 0 or data_identifier > 0xFFFF: 1164 | raise ValueError('Invalid dataIdentifier, ' 1165 | 'must be >= 0 and <= 0xFFFF') 1166 | self['dataIdentifier'] = data_identifier 1167 | self['controlOptionRecord'] = control_option_record 1168 | self['controlEnableMaskRecord'] = control_enable_mask_record 1169 | 1170 | def encode(self): 1171 | return ([self.SID] + 1172 | _to_bytes(self['dataIdentifier']) + 1173 | self['controlOptionRecord'] + 1174 | self['controlEnableMaskRecord']) 1175 | 1176 | def decode(self, data): 1177 | self._check_sid(data) 1178 | self['dataIdentifier'] = _from_bytes(data[1:3]) 1179 | self['controlOptionRecord'] = data[3] 1180 | self['controlEnableMaskRecord'] = data[4:] 1181 | 1182 | 1183 | class RoutineControl: 1184 | """ RoutineControl service """ 1185 | SID = 0x31 1186 | 1187 | RoutineControlType = UDSParameter('RoutineControlType', { 1188 | # 0x00: ISOASAEReserved 1189 | 'startRoutine': 0x01, 1190 | 'stopRoutine': 0x02, 1191 | 'requestRoutineResults': 0x03, 1192 | # 0x04 - 0x3F: ISOSAEReserved 1193 | }) 1194 | 1195 | class Response(GenericResponse): 1196 | def __init__(self): 1197 | super(RoutineControl.Response, self).__init__( 1198 | 'RoutineControl', 1199 | RoutineControl.SID) 1200 | 1201 | def decode(self, data): 1202 | self._check_nrc(data) 1203 | self['routineControlType'] = data[1] 1204 | self['routineIdentifier'] = _from_bytes(data[2:4]) 1205 | self['routineStatusRecord'] = data[4:] 1206 | 1207 | class Request(GenericRequest): 1208 | def __init__(self, routine_control_type=0, 1209 | routine_identifier=0, 1210 | routine_control_option_record=None): 1211 | super(RoutineControl.Request, self).__init__( 1212 | 'RoutineControl', 1213 | RoutineControl.SID) 1214 | if routine_identifier < 0 or routine_identifier > 0xFFFF: 1215 | raise ValueError('Invalid routineIdentifier, ' 1216 | 'must be > 0 and < 0xFFFF') 1217 | self['routineControlType'] = routine_control_type 1218 | self['routineIdentifier'] = routine_identifier 1219 | self['routineControlOptionRecord'] = routine_control_option_record 1220 | 1221 | def encode(self): 1222 | return ([self.SID, self['routineControlType']] + 1223 | _to_bytes(self['routineIdentifier']) + 1224 | self['routineControlOptionRecord']) 1225 | 1226 | def decode(self, data): 1227 | self._check_sid(data) 1228 | self['routineControlType'] = data[1] 1229 | self['routineIdentifier'] = _from_bytes(data[2:4]) 1230 | self['routineControlOptionRecord'] = _from_bytes(data[4:]) 1231 | 1232 | 1233 | class RequestTransfer: 1234 | """ Generic service for requesting transfers, used for both 1235 | RequestUpload and RequestDownload """ 1236 | 1237 | class Response(GenericResponse): 1238 | def __init__(self, name, sid): 1239 | super(RequestTransfer.Response, self).__init__(name, sid) 1240 | 1241 | def decode(self, data): 1242 | self._check_nrc(data) 1243 | # length of maxNumberOfBlockLength is upper nybble of first byte 1244 | length = data[1] >> 4 1245 | self['maxNumberOfBlockLength'] = _from_bytes(data[2: 2 + length]) 1246 | 1247 | class Request(GenericRequest): 1248 | def __init__(self, name, sid, memory_address, memory_size, 1249 | data_format_identifier, address_format_identifier=None, length_format_identifier=None): 1250 | super(RequestTransfer.Request, self).__init__(name, sid) 1251 | self['memoryAddress'] = memory_address 1252 | self['memorySize'] = memory_size 1253 | self['dataFormatIdentifier'] = data_format_identifier 1254 | self['addressFormatIdentifier'] = address_format_identifier 1255 | self['lengthFormatIdentifier'] = length_format_identifier 1256 | 1257 | def encode(self): 1258 | # addressAndLengthFormatIdentifier specifies length of address and 1259 | # size 1260 | # uppper nybble is byte length of size 1261 | # lower nybble is byte length of address 1262 | if self['addressFormatIdentifier'] is None: 1263 | self['addressFormatIdentifier'] = _byte_size(self['memoryAddress']) 1264 | 1265 | if self['lengthFormatIdentifier'] is None: 1266 | self['lengthFormatIdentifier'] = _byte_size(self['memorySize']) 1267 | 1268 | address_and_length_format_identifier = (self['addressFormatIdentifier'] & 0x0F) 1269 | address_and_length_format_identifier |= ((self['lengthFormatIdentifier'] << 4) & 0xF0) 1270 | 1271 | return ([self.SID, 1272 | self['dataFormatIdentifier'], 1273 | address_and_length_format_identifier] + 1274 | _to_bytes(self['memoryAddress'], self['addressFormatIdentifier'] - _byte_size(self['memoryAddress'])) + 1275 | _to_bytes(self['memorySize'], self['lengthFormatIdentifier'] - _byte_size(self['memorySize']))) 1276 | 1277 | def decode(self, data): 1278 | self._check_sid(data) 1279 | self['dataFormatIdentifier'] = data[1] 1280 | self['lengthFormatIdentifier'] = (data[2] >> 4) & 0x0F 1281 | self['addressFormatIdentifier'] = data[2] & 0x0F 1282 | self['memoryAddress'] = data[3: 3 + address_bytes] 1283 | self['memorySize'] = data[3 + address_bytes: 1284 | 3 + address_bytes + size_bytes] 1285 | 1286 | 1287 | class RequestDownload: 1288 | """ RequestUpload service """ 1289 | SID = 0x34 1290 | 1291 | class Response(RequestTransfer.Response): 1292 | def __init__(self): 1293 | super(RequestDownload.Response, self).__init__('RequestDownload', 1294 | RequestDownload.SID) 1295 | 1296 | class Request(RequestTransfer.Request): 1297 | def __init__(self, memory_address=0, 1298 | memory_size=0, 1299 | data_format_identifier=0, 1300 | address_format=None, 1301 | length_format=None): 1302 | super(RequestDownload.Request, self).__init__( 1303 | 'RequestDownload', 1304 | RequestDownload.SID, 1305 | memory_address, 1306 | memory_size, 1307 | data_format_identifier, 1308 | address_format_identifier=address_format, 1309 | length_format_identifier=length_format) 1310 | 1311 | 1312 | class RequestUpload(RequestTransfer): 1313 | """ RequestUpload service """ 1314 | SID = 0x35 1315 | 1316 | class Response(RequestTransfer.Response): 1317 | def __init__(self): 1318 | super(RequestUpload.Response, self).__init__('RequestUpload', 1319 | RequestUpload.SID) 1320 | 1321 | class Request(RequestTransfer.Request): 1322 | def __init__(self, memory_address=0, 1323 | memory_size=0, 1324 | data_format_identifier=0, 1325 | address_format=None, 1326 | length_format=None): 1327 | super(RequestUpload.Request, self).__init__( 1328 | 'RequestUpload', 1329 | RequestUpload.SID, 1330 | memory_address, 1331 | memory_size, 1332 | data_format_identifier, 1333 | address_format_identifier=address_format, 1334 | length_format_identifier=length_format) 1335 | 1336 | 1337 | class TransferData: 1338 | """ TransferData service """ 1339 | SID = 0x36 1340 | 1341 | class Response(GenericResponse): 1342 | def __init__(self): 1343 | super(TransferData.Response, self).__init__('TransferData', 1344 | TransferData.SID) 1345 | 1346 | def decode(self, data): 1347 | self._check_nrc(data) 1348 | self['blockSequenceCounter'] = data[1] 1349 | self['transferResponseParameterRecord'] = data[2:] 1350 | 1351 | class Request(GenericRequest): 1352 | def __init__(self, block_sequence_counter=0, 1353 | transfer_request_parameter_record=None): 1354 | super(TransferData.Request, self).__init__('TransferData', 1355 | TransferData.SID) 1356 | 1357 | if block_sequence_counter < 0 or block_sequence_counter > 0xFF: 1358 | raise ValueError('blockSequenceCounter ' 1359 | 'must be >= 0 and <= 0xFF') 1360 | 1361 | self['blockSequenceCounter'] = block_sequence_counter 1362 | self['transferRequestParameterRecord'] = ( 1363 | transfer_request_parameter_record) 1364 | 1365 | def encode(self): 1366 | return ([self.SID, self['blockSequenceCounter']] + 1367 | self['transferRequestParameterRecord']) 1368 | 1369 | def decode(self, data): 1370 | self._check_sid(data) 1371 | self['blockSequenceCounter'] = data[1] 1372 | self['transferRequestParameterRecord'] = data[2:] 1373 | 1374 | 1375 | class RequestTransferExit: 1376 | """ RequestTransferExit service """ 1377 | SID = 0x37 1378 | 1379 | class Response(GenericResponse): 1380 | def __init__(self): 1381 | super(RequestTransferExit.Response, self).__init__( 1382 | 'RequestTransferExit', 1383 | RequestTransferExit.SID) 1384 | 1385 | def decode(self, data): 1386 | self._check_nrc(data) 1387 | self['transferResponseParameterRecord'] = data[1:] 1388 | 1389 | class Request(GenericRequest): 1390 | def __init__(self, transfer_request_parameter_record=None): 1391 | super(RequestTransferExit.Request, self).__init__( 1392 | 'RequestTransferExit', 1393 | RequestTransferExit.SID) 1394 | self['transferRequestParameterRecord'] = ( 1395 | transfer_request_parameter_record) 1396 | 1397 | def encode(self): 1398 | return [self.SID] + self['transferRequestParameterRecord'] 1399 | 1400 | def decode(self, data): 1401 | self._check_sid(data) 1402 | self['transferRequestParameterRecord'] = data[1:] 1403 | 1404 | 1405 | class UDSInterface: 1406 | SERVICES = { 1407 | DiagnosticSessionControl.SID: DiagnosticSessionControl, 1408 | ECUReset.SID: ECUReset, 1409 | SecurityAccess.SID: SecurityAccess, 1410 | CommunicationControl.SID: CommunicationControl, 1411 | TesterPresent.SID: TesterPresent, 1412 | AccessTimingParameter.SID: AccessTimingParameter, 1413 | SecuredDataTransmission.SID: SecuredDataTransmission, 1414 | ControlDTCSetting.SID: ControlDTCSetting, 1415 | ResponseOnEvent.SID: ResponseOnEvent, 1416 | LinkControl.SID: LinkControl, 1417 | 1418 | ReadDataByIdentifier.SID: ReadDataByIdentifier, 1419 | ReadMemoryByAddress.SID: ReadMemoryByAddress, 1420 | ReadScalingDataByIdentifier.SID: ReadScalingDataByIdentifier, 1421 | ReadDataByPeriodicIdentifier.SID: ReadDataByPeriodicIdentifier, 1422 | DynamicallyDefineDataIdentifier.SID: DynamicallyDefineDataIdentifier, 1423 | WriteDataByIdentifier.SID: WriteDataByIdentifier, 1424 | WriteMemoryByAddress.SID: WriteMemoryByAddress, 1425 | 1426 | ReadDTCInformation.SID: ReadDTCInformation, 1427 | 1428 | InputOutputControlByIdentifier.SID: InputOutputControlByIdentifier, 1429 | 1430 | RoutineControl.SID: RoutineControl, 1431 | 1432 | RequestDownload.SID: RequestDownload, 1433 | RequestUpload.SID: RequestUpload, 1434 | TransferData.SID: TransferData, 1435 | RequestTransferExit.SID: RequestTransferExit, 1436 | } 1437 | 1438 | def __init__(self, dispatcher=False, tx_arb_id=0x7E0, rx_arb_id=0x7E8, extended_id = False, functional_timeout = 2): 1439 | self.functional_timeout = functional_timeout 1440 | if not isinstance(dispatcher, Dispatcher): 1441 | # no dispatcher passed, I can't set a transport layer. These will be set from who is using 1442 | pass 1443 | elif extended_id: 1444 | self.transport_layer = IsotpNormalFixedAddressing(dispatcher) 1445 | self.transport_layer.tx_arb_id = tx_arb_id 1446 | self.transport_layer.rx_arb_id = rx_arb_id 1447 | else: 1448 | self.transport_layer = IsotpNormalAddressing(dispatcher, tx_arb_id) 1449 | self.transport_layer.rx_arb_id = rx_arb_id 1450 | 1451 | def request(self, service, timeout=0.5): 1452 | self.transport_layer.send(service.encode()) 1453 | if self.transport_layer.N_TAtype == N_TAtype.physical: 1454 | try: 1455 | return self.decode_response(timeout=timeout) 1456 | except ResponsePendingException as e: 1457 | # If I get a response pending exception means that I have a new timeout to consider 1458 | return self.decode_response(timeout=e.timeout) 1459 | elif self.transport_layer.N_TAtype == N_TAtype.functional: 1460 | return self.decode_responses(timeout) 1461 | else: 1462 | raise Exception("Unknown N_TAtype") 1463 | 1464 | def response(self, service): 1465 | self.transport_layer.send(service.encode()) 1466 | 1467 | def decode_request(self, timeout=0.5): 1468 | data = self.transport_layer.recv(timeout=timeout) 1469 | if data is None: 1470 | return None 1471 | 1472 | try: 1473 | req = self.SERVICES[data[0]].Request() 1474 | req.decode(data) 1475 | except KeyError: 1476 | req = GenericRequest('Unknown Service', data[0]) 1477 | req['data'] = data[1:] 1478 | return req 1479 | 1480 | def decode_response(self, timeout=0.5): 1481 | data = self.transport_layer.recv(timeout=timeout) 1482 | if data is None: 1483 | return None 1484 | 1485 | # Data is already filtered from the PCI information by the transport_layer.recv() method 1486 | if data[0] == 0x7F: 1487 | raise NegativeResponseException.factory(data) 1488 | try: 1489 | resp = self.SERVICES[data[0] - 0x40].Response() 1490 | resp.decode(data) 1491 | except KeyError: 1492 | resp = GenericResponse('Unknown Service', data[0]) 1493 | resp['data'] = data[1:] 1494 | return resp 1495 | 1496 | def decode_responses(self,timeout): 1497 | """ 1498 | In some cases, for example when using functional addressing, we may have more responses from different ECUs 1499 | :param functional_timeout: 1500 | :return: 1501 | """ 1502 | 1503 | resps = {} 1504 | start = time.time() 1505 | while time.time() - start <= self.functional_timeout: 1506 | try: 1507 | resp = self.decode_response(timeout) 1508 | except ResponsePendingException as e: 1509 | # If I get a response pending exception means that I have a new timeout to consider 1510 | self.functional_timeout = e.timeout 1511 | except NegativeResponseException as e: 1512 | resp = self.SERVICES[e.sid].Response() 1513 | if resp is not None: 1514 | resps[self.transport_layer.last_arb_id] = resp 1515 | return resps 1516 | -------------------------------------------------------------------------------- /pyvit/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linklayer/pyvit/0192c92f6888747e7daf3dc6c5aa4a6c4768f635/pyvit/utils/__init__.py -------------------------------------------------------------------------------- /pyvit/utils/queue.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | 3 | try: 4 | import queue 5 | except ImportError: 6 | import Queue as queue 7 | 8 | import time 9 | 10 | 11 | class CanQueue: 12 | def __init__(self, can_dev): 13 | self.can_dev = can_dev 14 | self.recv_process = multiprocessing.Process(target=self.recv_task) 15 | self.send_process = multiprocessing.Process(target=self.send_task) 16 | self.recv_queue = multiprocessing.Queue() 17 | self.send_queue = multiprocessing.Queue() 18 | 19 | def start(self): 20 | self.can_dev.start() 21 | self.recv_process.start() 22 | self.send_process.start() 23 | 24 | def stop(self): 25 | self.recv_process.terminate() 26 | self.send_process.terminate() 27 | self.can_dev.stop() 28 | 29 | def send(self, msg): 30 | self.send_queue.put(msg) 31 | 32 | def recv(self, timeout=1, arb_id=None): 33 | try: 34 | start_time = time.time() 35 | while True: 36 | msg = self.recv_queue.get(timeout=timeout) 37 | if not arb_id: 38 | return msg 39 | elif arb_id == msg.id: 40 | return msg 41 | # ensure we haven't gone over the timeout 42 | if time.time() - start_time > timeout: 43 | return None 44 | 45 | except queue.Empty: 46 | return None 47 | 48 | def recv_task(self): 49 | while True: 50 | msg = self.can_dev.recv() 51 | self.recv_queue.put(msg) 52 | 53 | def send_task(self): 54 | while True: 55 | msg = self.send_queue.get() 56 | self.can_dev.send(msg) 57 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | future==0.16.0 2 | pyserial==3.2.1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='pyvit', 5 | version='0.2.2', 6 | packages=find_packages(), 7 | scripts=[], 8 | 9 | install_requires=[ 10 | 'pyserial>=3.2.1' 11 | ], 12 | 13 | test_suite='test', 14 | 15 | author='Eric Evenchick', 16 | author_email='eric@evenchick.com', 17 | url='http://github.com/linklayer/pyvit', 18 | license='GPLv3', 19 | description='Python Vehicle Inteface Toolkit', 20 | long_description=open('README.rst').read(), 21 | classifiers=['Development Status :: 3 - Alpha', 22 | ('License :: OSI Approved :: ' + 23 | 'GNU General Public License v3 (GPLv3)'), 24 | 'Topic :: Software Development :: Libraries', 25 | 'Topic :: Software Development :: Embedded Systems'] 26 | ) 27 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linklayer/pyvit/0192c92f6888747e7daf3dc6c5aa4a6c4768f635/test/__init__.py -------------------------------------------------------------------------------- /test/can_test.py: -------------------------------------------------------------------------------- 1 | import pyvit.can as can 2 | import unittest 3 | 4 | 5 | class CanTest(unittest.TestCase): 6 | def setUp(self): 7 | pass 8 | 9 | def test_arb_id(self): 10 | """ Test CAN frame arbitration ID validation """ 11 | 12 | std_frame = can.Frame(0) 13 | ext_frame = can.Frame(0, extended=True) 14 | 15 | # test non-integer fails 16 | with self.assertRaises(AssertionError): 17 | std_frame.arb_id = 'boo!' 18 | 19 | # test negative id fails 20 | with self.assertRaises(ValueError): 21 | std_frame.arb_id = -1 22 | 23 | # test non extended throws error when ID is too large 24 | with self.assertRaises(ValueError): 25 | std_frame.arb_id = 0x1234 26 | 27 | # test id out of range 28 | ext_frame = can.Frame(0, extended=True) 29 | with self.assertRaises(ValueError): 30 | ext_frame.arb_id = 0x2FFFFFFF 31 | 32 | # test id in range 33 | ext_frame.arb_id = 0x1FFFFFFF 34 | ext_frame.arb_id = 0x0 35 | std_frame.arb_id = 0x7FF 36 | std_frame.arb_id = 0x0 37 | 38 | def test_data(self): 39 | """ Test CAN frame data validation """ 40 | 41 | frame = can.Frame(0) 42 | 43 | # test too many bytes causes error 44 | with self.assertRaises(AssertionError): 45 | frame.data = [1, 2, 3, 4, 5, 6, 7, 8, 9] 46 | # test wrong datatype causes error 47 | with self.assertRaises(AssertionError): 48 | frame.data = 4 49 | # test out of range byte causes error 50 | with self.assertRaises(AssertionError): 51 | frame.data = [1, 2, 3, 4, 5, 6, 7, 0xFFFF] 52 | 53 | def test_frame_type(self): 54 | """ Test CAN frame type validation """ 55 | 56 | frame = can.Frame(0) 57 | 58 | # test invalid frame type causes error 59 | with self.assertRaises(AssertionError): 60 | frame.frame_type = 5 61 | # test valid frame types 62 | frame.frame_type = can.FrameType.DataFrame 63 | frame.frame_type = can.FrameType.RemoteFrame 64 | frame.frame_type = can.FrameType.ErrorFrame 65 | frame.frame_type = can.FrameType.OverloadFrame 66 | 67 | def test_init(self): 68 | """ Test CAN frame initialization """ 69 | frame = can.Frame(0x55, [1, 2, 3, 4, 5, 6], can.FrameType.ErrorFrame) 70 | self.assertEqual(frame.arb_id, 0x55) 71 | self.assertEqual(frame.data, [1, 2, 3, 4, 5, 6]) 72 | self.assertEqual(frame.frame_type, can.FrameType.ErrorFrame) 73 | 74 | def test_extended(self): 75 | """ Test extended arbitration IDs """ 76 | frame = can.Frame(0x1234, extended=True) 77 | self.assertEqual(frame.arb_id, 0x1234) 78 | self.assertTrue(frame.is_extended_id) 79 | 80 | if __name__ == '__main__': 81 | unittest.main() 82 | -------------------------------------------------------------------------------- /test/dispatch_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from multiprocessing import Queue 3 | 4 | from pyvit.dispatch import Dispatcher 5 | from pyvit.hw.loopback import LoopbackDev 6 | from pyvit import can 7 | 8 | 9 | class DispatchTest(unittest.TestCase): 10 | def setUp(self): 11 | dev = LoopbackDev() 12 | self.disp = Dispatcher(dev) 13 | 14 | def test_dispatcher_single(self): 15 | rx = Queue() 16 | self.disp.add_receiver(rx) 17 | 18 | self.disp.start() 19 | f = can.Frame(0x123) 20 | self.disp.send(f) 21 | f2 = rx.get() 22 | self.disp.stop() 23 | 24 | self.assertEqual(f, f2) 25 | 26 | def test_dispatcher_multiple(self): 27 | rx = Queue() 28 | self.disp.add_receiver(rx) 29 | 30 | tx1 = can.Frame(1, data=[1, 2, 3, 4, 5]) 31 | tx2 = can.Frame(2, data=[0xFF, 0xFF, 0xFF]) 32 | tx3 = can.Frame(3, data=[0xDE, 0xAD, 0xBE, 0xEF]) 33 | 34 | self.disp.start() 35 | self.disp.send(tx1) 36 | self.disp.send(tx2) 37 | self.disp.send(tx3) 38 | rx1 = rx.get() 39 | rx2 = rx.get() 40 | rx3 = rx.get() 41 | self.disp.stop() 42 | 43 | self.assertEqual(rx1, tx1) 44 | self.assertEqual(rx2, tx2) 45 | self.assertEqual(rx3, tx3) 46 | 47 | if __name__ == '__main__': 48 | unittest.main() 49 | -------------------------------------------------------------------------------- /test/file_candump_test.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | 3 | import pyvit.can as can 4 | from pyvit.file import log 5 | 6 | import unittest 7 | 8 | 9 | class FileCanDumpTest(unittest.TestCase): 10 | def test_defaults(self): 11 | """ Test write & readback of candump file with default timestamp and 12 | interface """ 13 | temp_file = tempfile.NamedTemporaryFile(delete=False) 14 | file_name = temp_file.name 15 | 16 | cdf = log.CandumpFile(file_name) 17 | 18 | frames = [can.Frame(0x123, [1, 2, 3, 4, 5, 6, 7, 8]), 19 | can.Frame(0x0), 20 | can.Frame(0x1, [0xFF] * 8)] 21 | 22 | cdf.export_frames(frames) 23 | frames2 = cdf.import_frames() 24 | 25 | temp_file.close() 26 | 27 | for i in range(0, len(frames)): 28 | self.assertEqual(frames[i], frames2[i]) 29 | 30 | def test_full(self): 31 | """ Test write & readback of candump file with defined timestamp and 32 | interface """ 33 | temp_file = tempfile.NamedTemporaryFile(delete=False) 34 | file_name = temp_file.name 35 | 36 | cdf = log.CandumpFile(file_name) 37 | 38 | frames = [can.Frame(0x1, 39 | [1, 2, 3, 4, 5, 6, 7, 8], 40 | interface='can0', 41 | timestamp=1.0), 42 | can.Frame(0x2, 43 | interface='can1', 44 | timestamp=2.0), 45 | can.Frame(0x3, 46 | [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], 47 | interface='can2', 48 | timestamp=3.0)] 49 | 50 | cdf.export_frames(frames) 51 | frames2 = cdf.import_frames() 52 | 53 | temp_file.close() 54 | 55 | for i in range(0, len(frames)): 56 | self.assertEqual(frames[i], frames2[i]) 57 | 58 | if __name__ == '__main__': 59 | unittest.main() 60 | -------------------------------------------------------------------------------- /test/isotp_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import threading 3 | from multiprocessing import Queue 4 | 5 | from pyvit.hw.loopback import LoopbackDev 6 | from pyvit.dispatch import Dispatcher 7 | from pyvit.proto.isotp import IsotpInterface 8 | 9 | 10 | class IsotpTest(unittest.TestCase): 11 | def setUp(self): 12 | self.dev = LoopbackDev() 13 | self.disp = Dispatcher(self.dev) 14 | self.recv_queue = Queue() 15 | self.disp.add_receiver(self.recv_queue) 16 | # set up an isotp interface that sends and receives the same arb id 17 | self.sender = IsotpInterface(self.disp, 0, 1) 18 | self.receiver = IsotpInterface(self.disp, 1, 0) 19 | self.disp.start() 20 | 21 | def tearDown(self): 22 | self.disp.stop() 23 | 24 | def test_single_frame_loopback(self): 25 | """ Test ISOTP transmission and reception of a single frame message """ 26 | payload = [0xDE, 0xAD, 0xBE, 0xEF] 27 | self.sender.send(payload) 28 | resp = self.receiver.recv() 29 | self.assertEqual(payload, resp) 30 | 31 | def test_multi_frame_loopback(self): 32 | """ Test ISOTP transmission and reception of a multi-frame message """ 33 | payload = [0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF]*50 34 | 35 | # we need to run this in a thread so that the flow control frame is 36 | # sent and received, as IsotpInterfaces block 37 | tx_thread = threading.Thread(target=self.sender.send, args=(payload, )) 38 | tx_thread.start() 39 | 40 | # we'll receive data in this thread 41 | resp = self.receiver.recv() 42 | 43 | # wait for the transmitting thread to finish, then verify the result 44 | tx_thread.join() 45 | self.assertEqual(payload, resp) 46 | 47 | def test_multi_frame_loopback_with_bs(self): 48 | """ Test ISOTP transmission and reception of a multi-frame message 49 | with a defined blocksize """ 50 | payload = [0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF]*50 51 | 52 | # we need to run this in a thread so that the flow control frame is 53 | # sent and received, as IsotpInterfaces block 54 | tx_thread = threading.Thread(target=self.sender.send, args=(payload, )) 55 | tx_thread.start() 56 | 57 | # we'll receive data in this thread 58 | resp = self.receiver.recv(bs=10, st_min=0) 59 | 60 | # wait for the transmitting thread to finish, then verify the result 61 | tx_thread.join() 62 | self.assertEqual(payload, resp) 63 | 64 | def test_tx_too_long(self): 65 | """ Ensure transmission of data that is too long raises exception """ 66 | with self.assertRaises(ValueError): 67 | self.sender.send([0xFF]*4096) 68 | 69 | def test_padding(self): 70 | """ Test padding of frames sent using ISOTP """ 71 | payload = [0xDE, 0xAD, 0xBE, 0xEF] 72 | self.sender.padding_value = 0x55 73 | self.sender.send(payload) 74 | resp = self.recv_queue.get() 75 | self.assertEqual(resp.data, 76 | [0x04, 0xDE, 0xAD, 0xBE, 0xEF, 0x55, 0x55, 0x55]) 77 | 78 | if __name__ == '__main__': 79 | unittest.main() 80 | -------------------------------------------------------------------------------- /test/json_db_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pyvit.can as can 3 | from pyvit.file.db.jsondb import JsonDbParser 4 | 5 | import unittest 6 | 7 | 8 | class FileDbJsonDbTest(unittest.TestCase): 9 | def test_defaults(self): 10 | """ Test parsing a json database """ 11 | uut = JsonDbParser() 12 | 13 | # Should not throw any exceptions or errors 14 | file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 15 | 'vector_jsondb.json') 16 | bus_db = uut.parse(file_path) 17 | 18 | # [600 RPM, 4th gear, 2 units of voltage, unit to be divided by 10, 19 | # unit with offset] 20 | frame_1 = can.Frame(0x123, [88, 2, 0b00010100, 50, 10]) 21 | 22 | signals = bus_db.parse_frame(frame_1) 23 | # TODO: check signal values 24 | 25 | if __name__ == '__main__': 26 | unittest.main() 27 | -------------------------------------------------------------------------------- /test/log_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from pyvit.log import Logger 5 | from pyvit import can 6 | from pyvit.hw.logplayer import LogPlayer 7 | 8 | 9 | class CanTest(unittest.TestCase): 10 | def setUp(self): 11 | self.log_filename = 'tmp.log' 12 | self.f = can.Frame(0x123) 13 | self.f.data = [1, 2, 3, 4, 5, 6, 7, 8] 14 | 15 | with Logger(self.log_filename) as log: 16 | for i in range(0, 10): 17 | log.log_frame(self.f) 18 | 19 | def test_log_and_readback_one(self): 20 | with LogPlayer('tmp.log') as lp: 21 | f2 = lp.recv() 22 | 23 | self.assertEqual(self.f, f2) 24 | 25 | def test_logplayer_iteration(self): 26 | with LogPlayer('tmp.log') as lp: 27 | count = 0 28 | 29 | for f in lp.recv_all(): 30 | self.assertEqual(self.f, f) 31 | count += 1 32 | 33 | self.assertEqual(count, 10) 34 | 35 | def tearDown(self): 36 | os.remove(self.log_filename) 37 | 38 | if __name__ == '__main__': 39 | unittest.main() 40 | -------------------------------------------------------------------------------- /test/loopback_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyvit.hw.loopback import LoopbackDev 4 | from pyvit import can 5 | 6 | 7 | class LoopbackTest(unittest.TestCase): 8 | def setUp(self): 9 | self.dev = LoopbackDev() 10 | self.dev.start() 11 | 12 | def tearDown(self): 13 | self.dev.stop() 14 | 15 | def test_tx_rx_one(self): 16 | """ Test transmission and reception of one frame """ 17 | f = can.Frame(0x123) 18 | self.dev.send(f) 19 | self.assertEqual(f, self.dev.recv()) 20 | 21 | def test_tx_rx_multiple(self): 22 | """ Test transmission and reception of multiple frames """ 23 | f1 = can.Frame(0x1) 24 | f2 = can.Frame(0x2) 25 | f3 = can.Frame(0x3) 26 | 27 | self.dev.send(f1) 28 | self.dev.send(f2) 29 | self.dev.send(f3) 30 | 31 | self.assertEqual(f1, self.dev.recv()) 32 | self.assertEqual(f2, self.dev.recv()) 33 | self.assertEqual(f3, self.dev.recv()) 34 | 35 | 36 | if __name__ == '__main__': 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /test/uds_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyvit.proto import uds 3 | 4 | 5 | class CanTest(unittest.TestCase): 6 | def test_nrc(self): 7 | resp = uds.DiagnosticSessionControl.Response() 8 | with self.assertRaises(uds.NegativeResponseException): 9 | resp.decode([0x7F, 0x21, 0x33]) 10 | try: 11 | resp.decode([0x7F, 0x21, 0x33]) 12 | except uds.NegativeResponseException as e: 13 | print('nrc:', e) 14 | 15 | def test_diagnostic_session_control(self): 16 | print('\n\nDiagnosticSessionControl') 17 | req = uds.DiagnosticSessionControl.Request( 18 | uds.DiagnosticSessionControl. 19 | DiagnosticSessionType.programmingSession 20 | ) 21 | print(req) 22 | valid = [uds.DiagnosticSessionControl.SID, 0x02] 23 | self.assertEqual(req.encode(), valid) 24 | 25 | resp = uds.DiagnosticSessionControl.Response() 26 | resp.decode([0x40 + uds.DiagnosticSessionControl.SID, 2]) 27 | print(resp) 28 | 29 | def test_ecu_reset(self): 30 | print('\n\nECUReset') 31 | req = uds.ECUReset.Request( 32 | uds.ECUReset.ResetType.hardReset 33 | ) 34 | print(req) 35 | valid = [uds.ECUReset.SID, 0x1] 36 | self.assertEqual(req.encode(), valid) 37 | resp = uds.ECUReset.Response() 38 | resp.decode([0x40 + uds.ECUReset.SID, 2]) 39 | print(resp) 40 | self.assertEqual(resp['resetType'], 2) 41 | 42 | def test_security_access(self): 43 | print('\n\nSecurityAccess') 44 | req = uds.SecurityAccess.Request(1) 45 | valid = [uds.SecurityAccess.SID, 1] 46 | print(req) 47 | self.assertEqual(req.encode(), valid) 48 | 49 | resp = uds.SecurityAccess.Response() 50 | resp.decode([0x40 + uds.SecurityAccess.SID, 1, 0x12, 0x34]) 51 | print(resp) 52 | self.assertEqual(resp['securityAccessType'], 1) 53 | self.assertEqual(resp['securitySeed'], 0x1234) 54 | 55 | req = uds.SecurityAccess.Request(2, 0x1234) 56 | valid = [uds.SecurityAccess.SID, 2, 0x12, 0x34] 57 | print(req) 58 | self.assertEqual(req.encode(), valid) 59 | 60 | resp.decode([0x40 + uds.SecurityAccess.SID, 2]) 61 | self.assertEqual(resp['securityAccessType'], 2) 62 | print(resp) 63 | 64 | def test_communication_control(self): 65 | print('\n\nCommunicationControl') 66 | req = uds.CommunicationControl.Request( 67 | uds.CommunicationControl.ControlType.disableRxAndTx, 68 | uds.CommunicationControl.CommunicationType 69 | .normalCommunicationMessages 70 | ) 71 | valid = [uds.CommunicationControl.SID, 72 | uds.CommunicationControl.ControlType.disableRxAndTx, 73 | 0x1] 74 | self.assertEqual(req.encode(), valid) 75 | 76 | resp = uds.CommunicationControl.Response() 77 | resp.decode([0x40 + uds.CommunicationControl.SID, 1]) 78 | print(resp) 79 | self.assertEqual(resp['controlType'], 1) 80 | 81 | def test_tester_present(self): 82 | print('\n\nTesterPresent') 83 | req = uds.TesterPresent.Request() 84 | print(req) 85 | valid = [uds.TesterPresent.SID, 0] 86 | self.assertEqual(req.encode(), valid) 87 | 88 | resp = uds.TesterPresent.Response() 89 | print(resp) 90 | resp.decode([0x40 + uds.TesterPresent.SID, 0]) 91 | 92 | def test_access_timing_parameter(self): 93 | print('\n\nAccessTimingParameter') 94 | at = (uds.AccessTimingParameter.AccessType 95 | .setTimingParametersToGivenValues) 96 | 97 | req = uds.AccessTimingParameter.Request(at, [1, 2, 3]) 98 | print(req) 99 | valid = [uds.AccessTimingParameter.SID, at, 1, 2, 3] 100 | self.assertEqual(req.encode(), valid) 101 | 102 | at = (uds.AccessTimingParameter.AccessType 103 | .setTimingParametersToGivenValues) 104 | 105 | resp = uds.AccessTimingParameter.Response() 106 | resp.decode([0x40 + uds.AccessTimingParameter.SID, at]) 107 | print(resp) 108 | self.assertEqual(resp['timingParameterAccessType'], at) 109 | 110 | at = (uds.AccessTimingParameter.AccessType 111 | .readCurrentlyActiveTimingParameters) 112 | req = uds.AccessTimingParameter.Request(at) 113 | print(req) 114 | valid = [uds.AccessTimingParameter.SID, at] 115 | self.assertEqual(req.encode(), valid) 116 | 117 | at = (uds.AccessTimingParameter.AccessType 118 | .readCurrentlyActiveTimingParameters) 119 | resp.decode([0x40 + uds.AccessTimingParameter.SID, at, 120 | 1, 2, 3]) 121 | print(resp) 122 | self.assertEqual(resp['TimingParameterResponseRecord'], [1, 2, 3]) 123 | self.assertEqual(resp['timingParameterAccessType'], at) 124 | 125 | def test_security_data_transmission(self): 126 | print('\n\nsSecuredDataTransmission') 127 | req = uds.SecuredDataTransmission.Request([1, 2, 3, 4]) 128 | print(req) 129 | valid = [uds.SecuredDataTransmission.SID, 1, 2, 3, 4] 130 | self.assertEqual(req.encode(), valid) 131 | 132 | resp = uds.SecuredDataTransmission.Response() 133 | resp.decode([0x40 + uds.SecuredDataTransmission.SID, 134 | 1, 2, 3, 4]) 135 | print(resp) 136 | self.assertEqual(resp['securityDataResponseRecord'], [1, 2, 3, 4]) 137 | 138 | def test_control_dtc_setting(self): 139 | print('\n\nControlDTCSetting') 140 | req = uds.ControlDTCSetting.Request(uds. 141 | ControlDTCSetting.DTCSettingType. 142 | on) 143 | print(req) 144 | valid = [uds.ControlDTCSetting.SID, 1] 145 | self.assertEqual(req.encode(), valid) 146 | 147 | resp = uds.ControlDTCSetting.Response() 148 | resp.decode([0x40 + uds.ControlDTCSetting.SID, 2]) 149 | print(resp) 150 | self.assertEqual(resp['DTCSettingType'], 2) 151 | 152 | def test_response_on_event(self): 153 | print('\n\nResponseOnEvent') 154 | req = uds.ResponseOnEvent.Request(uds.ResponseOnEvent.EventType 155 | .stopResponseOnEvent) 156 | print(req) 157 | valid = [uds.ResponseOnEvent.SID, 0x0, 0x02] 158 | self.assertEqual(req.encode(), valid) 159 | 160 | resp = uds.ResponseOnEvent.Response() 161 | resp.decode([0x40 + uds.ResponseOnEvent.SID, 2]) 162 | print(resp) 163 | 164 | req = uds.ResponseOnEvent.Request(uds.ResponseOnEvent.EventType 165 | .onComparisonOfValues, 166 | event_window_time=0x10, 167 | event_type_record=[5] * 10, 168 | service_to_respond_to_record=[1, 169 | 2, 170 | 3]) 171 | print(req) 172 | valid = [uds.ResponseOnEvent.SID, 0x07, 0x10, 5, 5, 5, 5, 5, 5, 5, 173 | 5, 5, 5, 1, 2, 3] 174 | self.assertEqual(req.encode(), valid) 175 | 176 | resp.decode([0x40 + uds.ResponseOnEvent.SID, 2]) 177 | print(resp) 178 | 179 | def test_link_control(self): 180 | print('\n\nLinkControl') 181 | req = uds.LinkControl.Request( 182 | uds.LinkControl.LinkControlType.transitionBaudrate) 183 | print(req) 184 | valid = [uds.LinkControl.SID, 3] 185 | self.assertEqual(req.encode(), valid) 186 | 187 | resp = uds.LinkControl.Response() 188 | resp.decode([0x40 + uds.LinkControl.SID, 3]) 189 | print(resp) 190 | self.assertEqual(resp['linkControlType'], 3) 191 | 192 | req = uds.LinkControl.Request( 193 | uds.LinkControl.LinkControlType. 194 | verifyBaudrateTransitionWithFixedBaudrate, 0xFF) 195 | print(req) 196 | valid = [uds.LinkControl.SID, 1, 0xFF] 197 | self.assertEqual(req.encode(), valid) 198 | 199 | resp.decode([0x40 + uds.LinkControl.SID, 2]) 200 | print(resp) 201 | self.assertEqual(resp['linkControlType'], 2) 202 | 203 | def test_rdbi(self): 204 | print('\n\nRDBI') 205 | req = uds.ReadDataByIdentifier.Request(0x1234) 206 | print(req) 207 | valid = [uds.ReadDataByIdentifier.SID, 0x12, 0x34] 208 | self.assertEqual(req.encode(), valid) 209 | 210 | resp = uds.ReadDataByIdentifier.Response() 211 | resp.decode([0x40 + uds.ReadDataByIdentifier.SID, 0x12, 0x34, 212 | 1, 2, 3]) 213 | print(resp) 214 | self.assertEqual(resp['dataIdentifier'], 0x1234) 215 | self.assertEqual(resp['dataRecord'], [1, 2, 3]) 216 | 217 | def test_rmba(self): 218 | print('\n\nRMBA') 219 | req = uds.ReadMemoryByAddress.Request(0x12345678, 0x10) 220 | print(req) 221 | valid = [uds.ReadMemoryByAddress.SID, 0x14, 0x12, 0x34, 0x56, 0x78, 222 | 0x10] 223 | self.assertEqual(req.encode(), valid) 224 | 225 | resp = uds.ReadMemoryByAddress.Response() 226 | resp.decode([0x40 + uds.ReadMemoryByAddress.SID, 0xAA, 0xBB, 227 | 1, 2, 3]) 228 | print(resp) 229 | self.assertEqual(resp['dataRecord'], [0xAA, 0xBB, 1, 2, 3]) 230 | 231 | def test_read_scaling(self): 232 | print('\n\nRScalingDBI') 233 | req = uds.ReadScalingDataByIdentifier.Request(0x1234) 234 | print(req) 235 | valid = [uds.ReadScalingDataByIdentifier.SID, 0x12, 0x34] 236 | self.assertEqual(req.encode(), valid) 237 | 238 | resp = uds.ReadScalingDataByIdentifier.Response() 239 | resp.decode([0x40 + uds.ReadScalingDataByIdentifier.SID, 240 | 0xAA, 0xBB, 1, 2, 3]) 241 | print(resp) 242 | self.assertEqual(resp['dataIdentifier'], 0xAABB) 243 | self.assertEqual(resp['scalingData'], [1, 2, 3]) 244 | 245 | def test_read_periodic(self): 246 | print('\n\nRDBPeriodicI') 247 | req = uds.ReadDataByPeriodicIdentifier.Request( 248 | uds.ReadDataByPeriodicIdentifier.TransmissionMode.sendAtSlowRate, 249 | 0xAA, 0xBB) 250 | print(req) 251 | valid = [uds.ReadDataByPeriodicIdentifier.SID, 0x1, 0xAA, 0xBB] 252 | self.assertEqual(req.encode(), valid) 253 | 254 | resp = uds.ReadDataByPeriodicIdentifier.Response() 255 | resp.decode([0x40 + uds.ReadDataByPeriodicIdentifier.SID]) 256 | print(resp) 257 | 258 | def test_wdbi(self): 259 | print('\n\nWDBI') 260 | req = uds.WriteDataByIdentifier.Request(0xBEEF, 261 | [1, 2, 3, 4, 5, 6, 7]) 262 | print(req) 263 | valid = [uds.WriteDataByIdentifier.SID, 0xBE, 0xEF, 264 | 1, 2, 3, 4, 5, 6, 7] 265 | self.assertEqual(req.encode(), valid) 266 | 267 | resp = uds.WriteDataByIdentifier.Response() 268 | resp.decode([0x40 + uds.WriteDataByIdentifier.SID, 269 | 0xBE, 0xEF]) 270 | print(resp) 271 | self.assertEqual(resp['dataIdentifier'], 0xBEEF) 272 | 273 | def test_wmba(self): 274 | print('\n\nWMBA') 275 | req = uds.WriteMemoryByAddress.Request(0xBEEF, [1, 2, 3, 4, 5, 6, 7]) 276 | print(req) 277 | valid = [uds.WriteMemoryByAddress.SID, 0x12, 0xBE, 0xEF, 7, 278 | 1, 2, 3, 4, 5, 6, 7] 279 | self.assertEqual(req.encode(), valid) 280 | 281 | resp = uds.WriteMemoryByAddress.Response() 282 | resp.decode([0x40 + uds.WriteMemoryByAddress.SID, 0x12, 283 | 0xBE, 0xEF, 7]) 284 | print(resp) 285 | self.assertEqual(resp['memoryAddress'], 0xBEEF) 286 | self.assertEqual(resp['memorySize'], 7) 287 | 288 | def test_clear_dtc(self): 289 | print('\n\nClearDiagnosticInformation') 290 | req = uds.ClearDiagnosticInformation.Request(0xAABBCC) 291 | print(req) 292 | valid = [uds.ClearDiagnosticInformation.SID, 0xAA, 0xBB, 0xCC] 293 | self.assertEqual(req.encode(), valid) 294 | 295 | resp = uds.ClearDiagnosticInformation.Response() 296 | resp.decode([0x40 + uds.ClearDiagnosticInformation.SID]) 297 | print(resp) 298 | 299 | def test_io_ctl(self): 300 | print('\n\nIO Control') 301 | req = uds.InputOutputControlByIdentifier.Request(0xABBA, 302 | [1, 2, 3], [1, 2, 3]) 303 | print(req) 304 | valid = [uds.InputOutputControlByIdentifier.SID, 0xAB, 0xBA, 1, 2, 3, 305 | 1, 2, 3] 306 | self.assertEqual(req.encode(), valid) 307 | 308 | resp = uds.InputOutputControlByIdentifier.Response() 309 | resp.decode([0x40 + uds.InputOutputControlByIdentifier.SID, 310 | 0xAB, 0xBA, 1, 2, 3]) 311 | self.assertEqual(resp['dataIdentifier'], 0xABBA) 312 | self.assertEqual(resp['controlStatusRecord'], [1, 2, 3]) 313 | print(resp) 314 | 315 | def test_routine_ctl(self): 316 | print('\n\nRoutine Control') 317 | req = uds.RoutineControl.Request( 318 | uds.RoutineControl.RoutineControlType.startRoutine, 319 | 0xABBA, [1, 2, 3, 4, 5]) 320 | print(req) 321 | valid = [uds.RoutineControl.SID, 0x1, 0xAB, 0xBA, 1, 2, 3, 4, 5] 322 | self.assertEqual(req.encode(), valid) 323 | 324 | resp = uds.RoutineControl.Response() 325 | resp.decode([0x40 + uds.RoutineControl.SID, 0x3, 326 | 0xAB, 0xBA, 1, 2, 3]) 327 | self.assertEqual(resp['routineControlType'], 328 | uds.RoutineControl.RoutineControlType. 329 | requestRoutineResults) 330 | self.assertEqual(resp['routineIdentifier'], 0xABBA) 331 | self.assertEqual(resp['routineStatusRecord'], [1, 2, 3]) 332 | print(resp) 333 | 334 | def test_request_dl(self): 335 | print('\n\nRequestDownload') 336 | req = uds.RequestDownload.Request(0x1000, 0x100) 337 | print(req) 338 | valid = [uds.RequestDownload.SID, 0, 0x22, 0x10, 0x0, 0x1, 0x0] 339 | self.assertEqual(req.encode(), valid) 340 | 341 | resp = uds.RequestDownload.Response() 342 | resp.decode([0x40 + uds.RequestDownload.SID, 0x20, 343 | 0xAB, 0xBA]) 344 | self.assertEqual(resp['maxNumberOfBlockLength'], 0xABBA) 345 | print(resp) 346 | 347 | def test_request_upl(self): 348 | print('\n\nRequestUpload') 349 | req = uds.RequestUpload.Request(0x1000, 0x100) 350 | valid = [uds.RequestUpload.SID, 0, 0x22, 0x10, 0x0, 0x1, 0x0] 351 | self.assertEqual(req.encode(), valid) 352 | 353 | resp = uds.RequestUpload.Response() 354 | resp.decode([0x40 + uds.RequestUpload.SID, 0x20, 355 | 0xAB, 0xBA]) 356 | self.assertEqual(resp['maxNumberOfBlockLength'], 0xABBA) 357 | print(resp) 358 | 359 | def test_transfer_data(self): 360 | print('\n\nTransferData') 361 | data = ['w', 'o', 'o', 'p'] 362 | req = uds.TransferData.Request(1, data) 363 | print(req) 364 | valid = [uds.TransferData.SID, 1] + data 365 | self.assertEqual(req.encode(), valid) 366 | 367 | resp = uds.TransferData.Response() 368 | resp.decode([0x40 + uds.TransferData.SID, 2, 369 | 0xAB, 0xBA]) 370 | self.assertEqual(resp['blockSequenceCounter'], 2) 371 | self.assertEqual(resp['transferResponseParameterRecord'], [0xAB, 0xBA]) 372 | print(resp) 373 | 374 | def test_transfer_exit(self): 375 | print('\n\nTransferExit') 376 | data = [1, 2, 3] 377 | req = uds.RequestTransferExit.Request(data) 378 | print(req) 379 | valid = [uds.RequestTransferExit.SID] + data 380 | self.assertEqual(req.encode(), valid) 381 | 382 | resp = uds.RequestTransferExit.Response() 383 | resp.decode([0x40 + uds.RequestTransferExit.SID, 384 | 0xAB, 0xBA]) 385 | self.assertEqual(resp['transferResponseParameterRecord'], [0xAB, 0xBA]) 386 | print(resp) 387 | 388 | 389 | if __name__ == '__main__': 390 | unittest.main() 391 | -------------------------------------------------------------------------------- /test/vector_jsondb.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages": [ 3 | { 4 | "name": "test vector msg", 5 | "id": "0x123", 6 | "signals": { 7 | "0": { 8 | "name": "Engine RPM", 9 | "bit_length": 16, 10 | "factor": 1, 11 | "offset": 0 12 | }, 13 | "16": { 14 | "name": "Gear", 15 | "bit_length": 3 16 | }, 17 | "19": { 18 | "name": "Battery Voltage", 19 | "bit_length": 5 20 | }, 21 | "24": { 22 | "name": "Fractional Factor", 23 | "bit_length": 8, 24 | "factor": 0.1 25 | }, 26 | "32": { 27 | "name": "Negative offset", 28 | "bit_length": 8, 29 | "offset": -100 30 | } 31 | } 32 | } 33 | ] 34 | } 35 | --------------------------------------------------------------------------------