CAS JBE Emulator
CAS JBE Emulator
python3
#
# pretend to be the E90 CAS and JBE
#
import argparse
import struct
import time
import can
ID_JBE = 0x00
ID_CAS = 0x40
ID_ALL = 0xef
CAS_responses = {
# HARDWARE_REFERENZ_LESEN / read hardware/firmware versions
(0x1a, 0x80): [
0x3C, 0x5A, 0x80, 0x00, 0x00, 0x09,
0x38, 0x91, 0x16, 0xC4, 0x09, 0x06,
0xA0, 0x53, 0x41, 0x20, 0x09, 0x05,
0x20, 0x04, 0x00, 0x00, 0x00, 0x02,
0x08, 0x01, 0x03, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x06, 0x94,
0x38, 0x06, 0x30, 0x31, 0x39, 0x30,
0x30, 0x30, 0x34, 0x32, 0x4E, 0x37,
0x44, 0x30, 0x30, 0x34, 0x32, 0x4E,
0x37, 0x44, 0x46, 0x32, 0x32, 0x39,
0x53, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
],
# VIN read
(0x22, 0x10, 0x10): [
0x14, 0x62, 0x10, 0x10, 0x57, 0x42,
0x41, 0x50, 0x4E, 0x37, 0x33, 0x35,
0x58, 0x39, 0x41, 0x32, 0x36, 0x36,
0x33, 0x38, 0x36, 0xFF, 0xFF, 0xFF
],
# C_FA_LESEN / read VO block 0
(0x22, 0x3f, 0x00): [
0x13, 0x62, 0x3F, 0x00, 0x02, 0x41,
0x34, 0x19, 0x95, 0x94, 0x3F, 0xC2,
0xE5, 0xD3, 0x41, 0x35, 0x54, 0xB2,
0x3C, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF
],
# C_FA_LESEN / read VO block 1
(0x22, 0x3f, 0x01): [
0x13, 0x62, 0x3F, 0x01, 0x41, 0x04,
0x10, 0x41, 0x04, 0x10, 0x41, 0x04,
0x10, 0x41, 0x04, 0x10, 0x41, 0x04,
0x10, 0x42, 0xFF, 0xFF, 0xFF, 0xFF
],
# C_FA_LESEN / read VO block 2
(0x22, 0x3f, 0x02): [
0x13, 0x62, 0x3F, 0x02, 0x11, 0x8E,
0x14, 0x90, 0x55, 0x2C, 0xFA, 0x51,
0x65, 0x54, 0x65, 0x75, 0x21, 0x89,
0x55, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF
],
# C_FA_LESEN / read VO block 3
(0x22, 0x3f, 0x03): [
0x13, 0x62, 0x3F, 0x03, 0x59, 0x15,
0x58, 0x49, 0x36, 0x15, 0x41, 0x85,
0x53, 0x61, 0x75, 0x99, 0x49, 0x53,
0x21, 0x41, 0xFF, 0xFF, 0xFF, 0xFF
],
# C_FA_LESEN / read VO block 4
(0x22, 0x3f, 0x04): [
0x13, 0x62, 0x3F, 0x04, 0x94, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
],
# XXX unidentified
(0x30, 0x01, 0x01): [
0x43, 0x70, 0x01, 0x01, 0x83, 0xC8,
0x00, 0x28, 0x97, 0x6C, 0x00, 0x00,
0x00, 0x00, 0x00, 0x6C, 0x01, 0x6C,
0x6D, 0x6E, 0x6C, 0x6A, 0x00, 0x00,
0x00, 0x01, 0xF0, 0x00, 0x02, 0x37,
0x00, 0x4B, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x98, 0x9E,
0x61, 0x00, 0xC1, 0x50, 0x06, 0x00,
0x1B, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x45, 0x40, 0x21,
0x8F, 0x36, 0x80, 0x00, 0x0D, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
],
}
JBE_responses = {
(0x1a, 0x80): [
0x1F, 0x5A, 0x80, 0x00, 0x00, 0x09,
0x18, 0x75, 0x46, 0x03, 0x0A, 0x0D,
0xD0, 0x4E, 0x52, 0x20, 0x05, 0x12,
0x21, 0x09, 0x00, 0x1D, 0x88, 0x08,
0x3F, 0x00, 0x03, 0x0A, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF
]
}
class MessageError(Exception):
"""a received message was not as expected"""
pass
class ModuleError(Exception):
"""the module did something unexpected"""
pass
class TXMessage(can.Message):
"""
Abstract for messages that will be sent.
class TX_response(TXMessage):
"""
Response message; <recipient> <sequence> <data...>
"""
_format = '8B'
class TX_terminal_status(TXMessage):
"""
D-CAN side Terminal Status; ignition is always on.
The relevant bit (0x04 in the first data byte) is the same
for both D-CAN and PT-CAN-side messages, so hopefully nothing
gets upset by this...
"""
_format = '5B'
def __init__(self):
super().__init__(0x130,
0xc5, 0x40, 0xff, 0xff, 0xff)
class RXMessage(object):
"""
Received request message
Will be of the format <id> <len> <req...> if it's a new request for <id>
or <id> 0x30 0x00 0x01 0x00 0x00 0x00 0x00 if asking <id> to finish
sending data from a previous request.
"""
def __init__(self, raw):
if (raw.arbitration_id & 0x600) != 0x600:
raise MessageError('not for us')
if raw.dlc != 8:
raise MessageError('bad length')
class CANInterface(object):
"""
Interface to the CAN probe
"""
def __init__(self, args):
self._bus = can.interface.Bus(bustype=args.interface_type,
channel=args.interface,
bitrate=500000)
self._verbose = args.verbose
class Responder(object):
"""
Emulates a module
"""
def __init__(self, interface, id, replies):
self._interface = interface
self._response_bytes = None
self._response_addressee = None
self._response_sequence = None
self._response_continue_requested = False
self._id = id
self._responses = replies
# new request?
elif (msg.recipient == self._id) or (msg.recipient == ID_ALL):
for request, response in self._responses.items():
if request == msg.args:
self._response_bytes = response
self._response_addressee = msg.sender
self._response_sequence = 0x10
self._response_continue_requested = False
return True
log(f'{self._id:02x}: unhandled request {msg.args}')
return False
@property
def response_pending(self):
return self._response_bytes is not None
def send_response(self):
if ((self._response_sequence == 0x10)
or self._response_continue_requested):
self._send_partial_response()
def _send_partial_response(self):
if self._response_bytes is not None:
msg = TX_response(self._id,
self._response_addressee,
self._response_sequence,
*self._response_bytes[0:6])
self._interface.send(msg)
self._response_bytes = self._response_bytes[6:]
if self._response_sequence == 0x10:
self._response_sequence = 0x21
else:
self._response_sequence += 1
if len(self._response_bytes) == 0:
self._response_bytes = None
self._response_addressee = None
self._response_sequence = None
self._response_continue_requested = False
def do_emulate(interface):
while True:
t = time.time()
# send a new Terminal Status every ~quarter second
if (t - terminal_status_sent) > 0.25:
interface.send(TX_terminal_status(), quiet=True)
terminal_status_sent = t
if CAS.response_pending:
CAS.send_response()
elif JBE.response_pending:
JBE.send_response()
args = parser.parse_args()
if args.verbose:
def log(msg):
print(msg)
else:
def log(msg):
pass
interface = CANInterface(args)
do_emulate(interface)