0% found this document useful (0 votes)
10 views6 pages

CAS JBE Emulator

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
10 views6 pages

CAS JBE Emulator

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 6

#!

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.

Concrete classes set self._format and pass args to struct.pack()


that format to __init__.
"""
def __init__(self, id, *args):
super().__init__(arbitration_id=id,
is_extended_id=False,
dlc=struct.calcsize(self._format),
data=struct.pack(self._format, *args))

class TX_response(TXMessage):
"""
Response message; <recipient> <sequence> <data...>
"""
_format = '8B'

def __init__(self, sender, recipient, sequence, *args):


while len(args) < 6:
args += (0xff,)
super().__init__(0x600 | sender,
recipient, sequence, *args)

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

Typically from 0x6f1, but don't assume that.

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')

self.sender = raw.arbitration_id & 0xff


self.recipient = raw.data[0]
self.len = raw.data[1]
payload_len = min(self.len, 6)
self.args = struct.unpack_from(f'{payload_len}B', raw.data, 2)

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

# filter just the IDs we expect to be receiving


self._bus.set_filters([
{"can_id": 0x600, "can_mask": 0x600, "extended": False}
])

def send(self, message, quiet=False):


"""send the message"""
if not quiet:
log(f'CAN TX: {message}')
self._bus.send(message, 1)

def recv(self, timeout=2):


"""wait for a message"""
try:
msg = self._bus.recv(timeout)
if msg is not None:
log(f'CAN RX: {msg}')
except can.CanError as e:
return None
return msg

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

def handle_message(self, msg):


# continuation request?
if ((msg.recipient == self._id)
and (msg.len == 0x30)
and (msg.args == (0x00, 0x01, 0x00, 0x00, 0x00, 0x00))):
self._response_continue_requested = True
return True

# 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):

CAS = Responder(interface, ID_CAS, CAS_responses)


JBE = Responder(interface, ID_JBE, JBE_responses)
terminal_status_sent = 0

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

# Feed messages to the CAS and JBE emulation; this


# will update their internal state, but not generate
# any output.
#
# The behaviour of units in the all-call case is weird;
# only one replies, then it's asked to continue, then
# only after the continuation is complete does another
# reply. Perhaps there's some sort of time delay involved?
#
# Here, we don't let the JBE emulator talk unless the CAS
# emulator is completely finished. This seems to be gooed
# enough to make ProTool happy.
#
msg = interface.recv(0.01)
if msg is not None:
try:
req = RXMessage(msg)
CAS.handle_message(req)
JBE.handle_message(req)
except MessageError:
pass

if CAS.response_pending:
CAS.send_response()
elif JBE.response_pending:
JBE.send_response()

parser = argparse.ArgumentParser(description='E90 CAS/JBE emulator')


parser.add_argument('--interface',
type=str,
required=True,
metavar='INTERFACE_NAME',
help='interface name or path')
parser.add_argument('--interface-type',
type=str,
metavar='INTERFACE_TYPE',
default='slcan',
help='interface type')
parser.add_argument('--verbose',
action='store_true',
help='print verbose progress information')

args = parser.parse_args()
if args.verbose:
def log(msg):
print(msg)
else:
def log(msg):
pass

interface = CANInterface(args)
do_emulate(interface)

You might also like