|  |  |  | import time
 | 
					
						
							|  |  |  | from collections import defaultdict
 | 
					
						
							|  |  |  | from functools import partial
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import cereal.messaging as messaging
 | 
					
						
							|  |  |  | from selfdrive.swaglog import cloudlog
 | 
					
						
							|  |  |  | from selfdrive.boardd.boardd import can_list_to_can_capnp
 | 
					
						
							|  |  |  | from panda.python.uds import CanClient, IsoTpMessage, FUNCTIONAL_ADDRS, get_rx_addr_for_tx_addr
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class IsoTpParallelQuery():
 | 
					
						
							|  |  |  |   def __init__(self, sendcan, logcan, bus, addrs, request, response, functional_addr=False, debug=False):
 | 
					
						
							|  |  |  |     self.sendcan = sendcan
 | 
					
						
							|  |  |  |     self.logcan = logcan
 | 
					
						
							|  |  |  |     self.bus = bus
 | 
					
						
							|  |  |  |     self.request = request
 | 
					
						
							|  |  |  |     self.response = response
 | 
					
						
							|  |  |  |     self.debug = debug
 | 
					
						
							|  |  |  |     self.functional_addr = functional_addr
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     self.real_addrs = []
 | 
					
						
							|  |  |  |     for a in addrs:
 | 
					
						
							|  |  |  |       if isinstance(a, tuple):
 | 
					
						
							|  |  |  |         self.real_addrs.append(a)
 | 
					
						
							|  |  |  |       else:
 | 
					
						
							|  |  |  |         self.real_addrs.append((a, None))
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     self.msg_addrs = {tx_addr: get_rx_addr_for_tx_addr(tx_addr[0]) for tx_addr in self.real_addrs}
 | 
					
						
							|  |  |  |     self.msg_buffer = defaultdict(list)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def rx(self):
 | 
					
						
							|  |  |  |     """Drain can socket and sort messages into buffers based on address"""
 | 
					
						
							|  |  |  |     can_packets = messaging.drain_sock(self.logcan, wait_for_one=True)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for packet in can_packets:
 | 
					
						
							|  |  |  |       for msg in packet.can:
 | 
					
						
							|  |  |  |         if msg.src == self.bus:
 | 
					
						
							|  |  |  |           if self.functional_addr:
 | 
					
						
							|  |  |  |             if (0x7E8 <= msg.address <= 0x7EF) or (0x18DAF100 <= msg.address <= 0x18DAF1FF):
 | 
					
						
							|  |  |  |               fn_addr = next(a for a in FUNCTIONAL_ADDRS if msg.address - a <= 32)
 | 
					
						
							|  |  |  |               self.msg_buffer[fn_addr].append((msg.address, msg.busTime, msg.dat, msg.src))
 | 
					
						
							|  |  |  |           elif msg.address in self.msg_addrs.values():
 | 
					
						
							|  |  |  |             self.msg_buffer[msg.address].append((msg.address, msg.busTime, msg.dat, msg.src))
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def _can_tx(self, tx_addr, dat, bus):
 | 
					
						
							|  |  |  |     """Helper function to send single message"""
 | 
					
						
							|  |  |  |     msg = [tx_addr, 0, dat, bus]
 | 
					
						
							|  |  |  |     self.sendcan.send(can_list_to_can_capnp([msg], msgtype='sendcan'))
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def _can_rx(self, addr, sub_addr=None):
 | 
					
						
							|  |  |  |     """Helper function to retrieve message with specified address and subadress from buffer"""
 | 
					
						
							|  |  |  |     keep_msgs = []
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if sub_addr is None:
 | 
					
						
							|  |  |  |       msgs = self.msg_buffer[addr]
 | 
					
						
							|  |  |  |     else:
 | 
					
						
							|  |  |  |       # Filter based on subadress
 | 
					
						
							|  |  |  |       msgs = []
 | 
					
						
							|  |  |  |       for m in self.msg_buffer[addr]:
 | 
					
						
							|  |  |  |         first_byte = m[2][0]
 | 
					
						
							|  |  |  |         if first_byte == sub_addr:
 | 
					
						
							|  |  |  |           msgs.append(m)
 | 
					
						
							|  |  |  |         else:
 | 
					
						
							|  |  |  |           keep_msgs.append(m)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     self.msg_buffer[addr] = keep_msgs
 | 
					
						
							|  |  |  |     return msgs
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def _drain_rx(self):
 | 
					
						
							|  |  |  |     messaging.drain_sock(self.logcan)
 | 
					
						
							|  |  |  |     self.msg_buffer = defaultdict(list)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def get_data(self, timeout):
 | 
					
						
							|  |  |  |     self._drain_rx()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Create message objects
 | 
					
						
							|  |  |  |     msgs = {}
 | 
					
						
							|  |  |  |     request_counter = {}
 | 
					
						
							|  |  |  |     request_done = {}
 | 
					
						
							|  |  |  |     for tx_addr, rx_addr in self.msg_addrs.items():
 | 
					
						
							|  |  |  |       # rx_addr not set when using functional tx addr
 | 
					
						
							|  |  |  |       id_addr = rx_addr or tx_addr[0]
 | 
					
						
							|  |  |  |       sub_addr = tx_addr[1]
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       can_client = CanClient(self._can_tx, partial(self._can_rx, id_addr, sub_addr=sub_addr), tx_addr[0], rx_addr,
 | 
					
						
							|  |  |  |                              self.bus, sub_addr=sub_addr, debug=self.debug)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       max_len = 8 if sub_addr is None else 7
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       msg = IsoTpMessage(can_client, timeout=0, max_len=max_len, debug=self.debug)
 | 
					
						
							|  |  |  |       msg.send(self.request[0])
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       msgs[tx_addr] = msg
 | 
					
						
							|  |  |  |       request_counter[tx_addr] = 0
 | 
					
						
							|  |  |  |       request_done[tx_addr] = False
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     results = {}
 | 
					
						
							|  |  |  |     start_time = time.time()
 | 
					
						
							|  |  |  |     while True:
 | 
					
						
							|  |  |  |       self.rx()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if all(request_done.values()):
 | 
					
						
							|  |  |  |         break
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       for tx_addr, msg in msgs.items():
 | 
					
						
							|  |  |  |         dat = msg.recv()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if not dat:
 | 
					
						
							|  |  |  |           continue
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         counter = request_counter[tx_addr]
 | 
					
						
							|  |  |  |         expected_response = self.response[counter]
 | 
					
						
							|  |  |  |         response_valid = dat[:len(expected_response)] == expected_response
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if response_valid:
 | 
					
						
							|  |  |  |           if counter + 1 < len(self.request):
 | 
					
						
							|  |  |  |             msg.send(self.request[counter + 1])
 | 
					
						
							|  |  |  |             request_counter[tx_addr] += 1
 | 
					
						
							|  |  |  |           else:
 | 
					
						
							|  |  |  |             results[tx_addr] = dat[len(expected_response):]
 | 
					
						
							|  |  |  |             request_done[tx_addr] = True
 | 
					
						
							|  |  |  |         else:
 | 
					
						
							|  |  |  |           request_done[tx_addr] = True
 | 
					
						
							|  |  |  |           cloudlog.warning(f"iso-tp query bad response: 0x{bytes.hex(dat)}")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if time.time() - start_time > timeout:
 | 
					
						
							|  |  |  |         break
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return results
 |