├── LICENSE ├── README.md └── uds.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Felix Domke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uds 2 | Tools to talk to UDS ECUs via socketcan 3 | -------------------------------------------------------------------------------- /uds.py: -------------------------------------------------------------------------------- 1 | import time 2 | import can 3 | import sys 4 | from can.interfaces.interface import Bus 5 | can_interface = 'can0' 6 | 7 | # 8 | # TODO: keep sending 700/02 3e 80 9 | # 10 | # 11 | 12 | bus = Bus(can_interface) 13 | 14 | receivers = [] 15 | 16 | def hexdump(x): 17 | return ' '.join("%02x" % y for y in bytearray(x)) 18 | 19 | class IsoTp: 20 | PAD = bytes([0x55]) 21 | def __init__(self, bus, id_source, id_target, add_address_info = None): 22 | global receivers 23 | self.id_source = id_source 24 | self.id_target = id_target 25 | self.add_address_info = add_address_info 26 | self.bus = bus 27 | receivers.append(self) 28 | self.rx_pdu = None 29 | self.rx_dl = None 30 | self.rx_exp_sn = None 31 | self.debug = True 32 | 33 | self.received_pdus = [] 34 | self.received_fctrl = [] 35 | 36 | def close(self): 37 | receivers.remove(self) 38 | 39 | def send_pdu(self, data): 40 | prefix = bytes() if self.add_address_info is None else bytes([self.add_address_info]) 41 | 42 | if self.debug: 43 | print("IsoTp> SEND PDU", hexdump(data)) 44 | 45 | if len(data) <= 8 - 1 - len(prefix): 46 | frame = prefix + bytes([len(data)]) + data 47 | frame = frame + self.PAD * (8 - len(frame)) 48 | msg = can.Message(arbitration_id = self.id_source, data = frame, extended_id = False) 49 | if self.debug: 50 | print(">", msg) 51 | self.bus.send(msg) 52 | else: 53 | if self.debug: 54 | print("sending %d bytes" % len(data)) 55 | frame = prefix + bytes([0x10 | (len(data) >> 8), len(data) & 0xFF]) 56 | db = 8-len(frame) 57 | frame += data[:db] 58 | frame = frame + self.PAD * (8 - len(frame)) 59 | msg = can.Message(arbitration_id = self.id_source, data = frame, extended_id = False) 60 | if self.debug: 61 | print(">", msg) 62 | self.bus.send(msg) 63 | data = data[db:] 64 | 65 | sn = 1 66 | 67 | self.received_fctrl = [] 68 | poll(self.bus, self.received_fctrl) 69 | 70 | while len(data): 71 | frame = prefix + bytearray([0x20 | sn]) 72 | db = 8-len(frame) 73 | if self.debug: 74 | print("getting rid of %d" % db) 75 | frame += data[:db] 76 | frame = frame + self.PAD * (8 - len(frame)) 77 | msg = can.Message(arbitration_id = self.id_source, data = frame, extended_id = False) 78 | if self.debug: 79 | print(">", msg) 80 | self.bus.send(msg) 81 | data = data[db:] 82 | if self.debug: 83 | print("remaining", hexdump(data)) 84 | sn += 1 85 | sn &= 0xF 86 | 87 | 88 | def receive(self, msg): 89 | if msg.arbitration_id != self.id_target: 90 | return 91 | 92 | if self.debug: 93 | print("IsoTp> RECEIVE FRAME: ", hexdump(msg.data)) 94 | frame = msg.data 95 | if self.add_address_info: 96 | add_address_info = frame[0] 97 | frame = frame[1:] 98 | 99 | frame_type = frame[0] >> 4 100 | 101 | if frame_type == 1: # FF 102 | PCI = frame[:2] 103 | frame = frame[2:] 104 | else: # CF/FF 105 | PCI = frame[:1] 106 | frame = frame[1:] 107 | 108 | if frame_type == 0: # SF 109 | DL = PCI[0] & 0x0F 110 | assert len(frame) >= DL 111 | self.receive_pdu(frame[:DL]) 112 | elif frame_type == 1: # FF 113 | assert self.rx_pdu is None 114 | self.rx_pdu = frame 115 | self.rx_dl = ((PCI[0] & 0xF) << 8) | PCI[1] 116 | self.rx_exp_sn = 1 117 | fcmsg = can.Message(arbitration_id = self.id_source, data = bytearray([0x30, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55, 0x55]), extended_id = False) 118 | self.bus.send(fcmsg) 119 | if self.debug: 120 | print("sending flow control message", fcmsg) 121 | elif frame_type == 2: # CF 122 | assert self.rx_pdu is not None 123 | SN = PCI[0] &0xF 124 | assert SN == self.rx_exp_sn 125 | 126 | self.rx_pdu += frame 127 | if len(self.rx_pdu) >= self.rx_dl: 128 | self.receive_pdu(self.rx_pdu[:self.rx_dl]) 129 | self.rx_pdu = None 130 | else: 131 | self.rx_exp_sn = (SN + 1) & 0xF 132 | elif frame_type == 3: # FC 133 | if self.debug: 134 | print("got flow control message", hexdump(frame)) 135 | self.received_fctrl.append(frame) 136 | else: 137 | assert False, "invalid IsoTp frame type %02x" % frame_type 138 | 139 | def receive_pdu(self, pdu): 140 | if self.debug: 141 | print("IsoTp> Receive PDU %s" % hexdump(pdu)) 142 | self.received_pdus.append(pdu) 143 | 144 | def get_pdu(self): 145 | while True: 146 | if not self.received_pdus: 147 | poll(self.bus, self.received_pdus) 148 | r = self.received_pdus.pop(0) 149 | if r[0] == 0x7F and r[2] == 0x78: 150 | print("pending!") 151 | else: 152 | return r 153 | 154 | def poll(bus, l): 155 | while not len(l): 156 | message = bus.recv() 157 | for r in receivers: 158 | r.receive(message) 159 | 160 | class UdsException(Exception): 161 | def __init__(self, error, pdu): 162 | Exception.__init__(self, error + ": " + hexdump(pdu)) 163 | self.pdu = pdu 164 | 165 | class Uds: 166 | def __init__(self, bus, id_source, id_dest): 167 | self.tp = IsoTp(bus, id_source, id_dest) 168 | 169 | 170 | DiagnServi_DiagnSessiContrDevelSessi = 0x4F 171 | DiagnServi_DiagnSessiContrECUProgrSessi = 0x02 172 | DiagnServi_DiagnSessiContrExtenDiagnSessi = 0x03 173 | DiagnServi_DiagnSessiContrOBDIIAndVWDefauSessi = 0x01 174 | DiagnServi_DiagnSessiContrVWEndOfLineSessi = 0x40 175 | 176 | Req_SecurityAccess = 0x27 177 | Req_ReadDataByIdent = 0x22 178 | Resp_DiagnSessiContr = 0x50 179 | Resp_ReadDataByIdent = 0x62 180 | 181 | def DiagnosticSessionControl(self, LEV): 182 | self.tp.send_pdu(bytearray([0x10, LEV])) 183 | result = self.tp.get_pdu() 184 | if result[0] != self.Resp_DiagnSessiContr: 185 | raise UdsException("DiagnosticSessionControl failed", result) 186 | 187 | def ReadDataByIdent(self, ident): 188 | ident = bytearray([(ident >> 8) & 0xFF, ident & 0xFF]) 189 | uds.tp.send_pdu(bytearray([self.Req_ReadDataByIdent]) + ident) 190 | result = self.tp.get_pdu() 191 | if result[0] == self.Resp_ReadDataByIdent and result[1:3] == ident: 192 | return result[3:] 193 | else: 194 | return None 195 | 196 | def SecurAccesRequeSeed(self, fnc = 3): 197 | uds.tp.send_pdu(bytearray([0x27, fnc])) 198 | result = self.tp.get_pdu() 199 | if result[0:2] != bytearray([0x67, fnc]): 200 | raise UdsException("SecurAccesRequeSeed", result) 201 | return result[2:] 202 | 203 | def SecurAccesResp(self, seed, fnc = 4): 204 | uds.tp.send_pdu(bytearray([0x27, fnc]) + seed) 205 | result = self.tp.get_pdu() 206 | if result[0:2] != bytearray([0x67, fnc]): 207 | raise UdsException("SecurAccesResp", result) 208 | 209 | def RoutiContrCheckProgrPreco(self): 210 | uds.tp.send_pdu(bytearray([0x31, 0x01, 0x02, 0x03])) 211 | res = self.tp.get_pdu() 212 | if res != bytearray([0x71, 0x01, 0x02, 0x03]): 213 | raise UdsException("RoutiContrCheckProgrPreco failed", res) 214 | 215 | def DiagnServi_ContrDTCSetti(self): 216 | uds.tp.send_pdu(bytearray([0x85, 0x02, 0xFF, 0xFF, 0xFF])) 217 | res = self.tp.get_pdu() 218 | if res != bytearray([0xC5, 0x02]): 219 | raise UdsException("DiagnServi_ContrDTCSetti failed", res) 220 | 221 | def CommuContr(self, ctrl0, ctrl1): 222 | uds.tp.send_pdu(bytearray([0x28, ctrl0, ctrl1])) 223 | # res = self.tp.get_pdu() 224 | # print "CommuContr", hexdump(res) 225 | # if res != bytearray([0xC5, 0x02]): 226 | # raise UdsException("CommuContr failed", res) 227 | 228 | uds = Uds(bus, 0x7e0, 0x7e8) # ecu 229 | 230 | print(uds.ReadDataByIdent(0xf19e)) 231 | 232 | import struct 233 | 234 | #uds.DiagnosticSessionControl(uds.DiagnServi_DiagnSessiContrExtenDiagnSessi) 235 | # 236 | #def security_access(): 237 | # seed = struct.unpack(">I", uds.SecurAccesRequeSeed(3))[0] 238 | # resp = seed + 12233 239 | # uds.SecurAccesResp(struct.pack(">I", resp)) 240 | # 241 | #security_access() 242 | # 243 | #uds.DiagnosticSessionControl(uds.DiagnServi_DiagnSessiContrDevelSessi) 244 | # 245 | #security_access() 246 | 247 | 248 | def read(v, len = 4): 249 | uds.tp.send_pdu(struct.pack(">BBIH", 0x23, 0x24, v, len)) 250 | r = uds.tp.get_pdu() 251 | if r[0] == 0x7f and r[2] == 0x31: 252 | return None 253 | assert r[0] == 0x63, r 254 | return r[1:] 255 | 256 | def read8(v): 257 | return struct.unpack("