Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python
- #
- # BMW e87 CAN Lib-ish thingy
- #
- #
- ### Working (or close to) ###
- # - RPM
- # - Speed
- # - Throttle Position
- # - Coolant Temperature
- # - Steering Wheel Position
- #
- # - Key Status
- # - Engine Status
- #
- # - Reverse Gear / Light
- # - Handbrake Status
- #
- # - Brake Light
- # - Dip/Main/Full Beams
- # - Front/Rear Fog Lights
- # - Turn signals/ Hazard lights
- #
- # - Odometer
- # - Average Speed
- # - Average Fuel Level
- # - Average Fuel Consumation
- # - Range (Expected range with current fuel/consumation)
- #
- # - SZL buttons
- # - Window position
- # - Open Door Alarm
- # - Door Open/Close Lock/Unlock Status
- #
- # - Rear Wiper Status
- # - Front Wipers Speed / Status
- #
- # - Outside Temp
- # - Rear Demister Status
- # - Internal Temp / Internal Light
- # - Aircondition Left/Right Temperature Setting
- #
- # - Individual Wheel Speed
- #
- # - Production Date
- # - Last Battery Reset
- import re
- import os
- import sys
- __version__ = '0.99.11'
- __license__ = 'GPLv3'
- # Expected message syntax:
- # ID HEX (11bit long value)
- # RTR Integer (0 or 1)
- # Len Integer (message len)
- # Data HEX (Up to 8 bytes)
- #
- # ID RTR Len Data
- # ID: fff -0- Data: [8] ff ff ff ff ff ff ff ff
- # ID: fff -0- Data: [3] ff ff ff
- #
- # Change 'msg_match' regex for another syntax
- msg_match = r'ID: (\w+) -(\d{1})- Data: \[(\d{1})\] (.+)'
- split_match = r'ID.*ID.* Data.*'
- class CanMsg(object):
- def __init__(self, debug=False):
- self.mid = 0
- self.rtr = 0
- self.mlen = 0
- self.debug = debug
- self.message = []
- def check_id(self, mid):
- try:
- mid = int(mid, 16)
- if mid > 0xFFF:
- return False
- return True
- except:
- return False
- def check_msg(self, m, l):
- try:
- msg = m.split()
- for n in xrange(0, len(msg)):
- msg[n] = int(msg[n], 16)
- if int(l) <> len(msg):
- return False
- return msg
- except:
- return False
- def null(self):
- self.mid = 0
- self.rtr = 0
- self.mlen = 0
- self.message = []
- def parse(self, m):
- matchObj = re.match(msg_match, m, re.I)
- if matchObj:
- match_array = matchObj.groups()
- if len(match_array) > 4:
- # This means multiple messages with no \n to split 'em
- # So we'll work only with the last one in the line.
- # (Others are usualy only partaly recived)
- return self.parse('ID: ' + m.split('ID:')[-1])
- if self.check_id(match_array[0]):
- self.mid = int(match_array[0], 16)
- if self.check_id(match_array[1]):
- self.rtr = int(match_array[1])
- if self.check_id(match_array[2]):
- self.mlen = int(match_array[2])
- self.message = self.check_msg(
- match_array[3], match_array[2])
- if self.mid and self.mlen and self.message:
- return True
- return False
- class CarStatus(object):
- def __init__(self, debug=False):
- # Debug
- self.debug = debug
- # Main dict
- self.mdict = {}
- # Unknown IDs
- self.uids = {}
- # Time & Date
- self.['hours'] = 1
- self.['minutes'] = 0
- self.['seconds'] = 0
- self.['day'] = 1
- self.['month'] = 1
- self.['year'] = 2015
- self.result = {}
- def get(self, arg):
- if arg in self.mdict:
- return self.mdict[arg]
- def set(self, var, val):
- change = True
- if var in self.mdict:
- if self.mdict[var] == val:
- change = False
- self.mdict[var] = val
- self.result[var] = [val, change]
- def decode_vin(self, msg):
- try: vin = map(chr, msg[::-1])
- except: return
- self.set('vin', vin)
- def get_time(self):
- return '%s:%s:%s' % (self.mdict['hours'],
- self.mdict['minutes'], self.mdict['seconds'])
- def get_date(self):
- return '%s.%s.%s' % (self.mdict['day'],
- self.mdict['month'], self.mdict['year'])
- def add_second(self):
- # We sync the clock every 30s, so even
- # if we go to hours=25 it will be fixed fast
- # Its just a waste of time to go futher
- if self.mdict['seconds'] == 59:
- if self.mdict['minutes'] == 59:
- self.mdict['hours'] += 1
- self.mdict['minutes'] = 0
- self.mdict['seconds'] = 0
- else:
- self.mdict['minutes'] += 1
- self.mdict['seconds'] = 0
- else:
- self.mdict['seconds'] += 1
- return
- def msg(self, m):
- self.result = {}
- # Message sanity checks
- if not (hasattr(m, 'mid') and hasattr(m, 'mlen')):
- return False
- if m.mlen < 1:
- return False
- if (m.mid == 0x130) and (m.mlen > 1):
- # ID: 100110000
- bmsg0 = biny(m.message[0], 8)
- self.set('engine_ready', bmsg[5])
- self.set('engine_running', bmsg[3])
- if m.message[1] == 0:
- self.set('key', 0)
- else:
- self.set('key', 1)
- # Other parts of the message are part of
- # the ignition sequence. For more info check:
- # http://www.loopybunny.co.uk/CarPC/can/130.html
- elif (m.mid == 0x1B4) and (m.mlen > 1):
- # ID : 110110100
- self.mph = (((m.message[1] - 0xC0) * 256 ) + m.message[0]) / 16
- self.kph = (self.mph * 1.61)
- self.set('kph', self.kph)
- # Unchecked!
- if m.message[5] == 50:
- self.set('handbrake', 1)
- elif m.message[5] == 48:
- self.set('handbrake', 0)
- elif (m.mid == 0x1D0):
- # ID: 111010000
- # http://www.loopybunny.co.uk/CarPC/can/1D0.html
- # Clutch status and (glow plugs?) data avaiable in this message.
- self.set('coolant_temp', (m.message[0] - 48))
- elif (m.mid == 0x1D6) and (m.mlen > 1):
- # ID: 111010110
- if (m.message[0] == 0xC0) and (m.mlen > 1):
- if m.message[1] == 0xD:
- self.set('voice_button', 1)
- elif m.message[1] == 0x1C:
- self.set('rotate', 1)
- elif m.message[1] == 0x4C:
- self.set('disk_button', 1)
- elif (m.message[0] == 0xC1) and (m.message[1] == 0xC):
- self.set('telephone_button', 1)
- elif (m.message[0] == 0xC4) and (m.message[1] == 0xC):
- self.set('volumedown_down', 1)
- elif (m.message[0] == 0xC8) and (m.message[1] == 0xC):
- self.set('volumeup_button', 1)
- elif (m.message[0] == 0xD0) and (m.message[1] == 0xC):
- self.set('down_button', 1)
- elif (m.message[0] == 0xE0) and (m.message[1] == 0xC):
- self.set('up_button', 1)
- elif (m.mid == 0x1F6) and (m.mlen > 1):
- # ID: 111110110
- if m.message[0] == 0x80:
- self.set('hazard_lights', 0)
- self.set('left_turnsignal', 0)
- self.set('right_turnsignal', 0)
- elif (m.message[0] == 0x91):
- self.set('left_turnsignal', 1)
- elif (m.message[0] == 0xA1):
- self.set('right_turnsignal', 1)
- elif (m.message[0] == 0xB1):
- self.set('hazard_lights', 1)
- elif (m.mid == 0x1EE):
- # ID: 111101110
- # Light Stick position
- # http://www.loopybunny.co.uk/CarPC/can/1EE.html
- if m.message[0] == 0x00:
- self.set('full_beam', 0)
- self.set('left_turnsignal', 0)
- self.set('right_turnsignal', 0)
- elif m.message[0] == 0x02:
- self.set('left_turnsignal', 0)
- self.set('right_turnsignal', 1)
- elif m.message[0] == 0x08:
- self.set('left_turnsignal', 1)
- self.set('right_turnsignal', 0)
- elif m.message[0] == 0x10:
- self.set('full_beam', 1)
- elif m.message[0] == 0x01:
- self.set('right_blink', 1)
- elif m.message[0] == 0x04:
- self.set('left_blink', 1)
- elif m.message[0] == 0x20:
- self.set('full_beam', 0)
- self.set('beam_flash', 1)
- elif (m.mid == 0x24B):
- # ID: 1001001011
- if m.message[0]:
- self.set('door_alarm', 1)
- else:
- self.set('door_alarm', 0)
- elif (m.mid == 0x202):
- # ID: 1000000010
- # http://www.loopybunny.co.uk/CarPC/can/202.html
- self.dashboard_dimmer = int((255 - m.message[0]) / 25)
- self.set('dashboard_dimmer', self.dashboard_dimmer) # Umh?
- elif (m.mid == 0x21A) and (m.mlen > 1):
- # ID: 1000011010
- # http://www.loopybunny.co.uk/CarPC/can/21A.html
- bmsg1 = biny(m.message[0], 8)
- bmsg2 = biny(m.message[1], 8)
- self.set('rear_fog', bmsg1[1])
- self.set('dip_beam', bmsg1[5])
- self.set('main_beam', bmsg1[7])
- self.set('front_fog', bmsg1[2])
- self.set('brake_light', bmsg1[0])
- self.set('dashboard_light', bmsg2[6])
- elif m.mid == 0x1E3:
- # ID: 111100011
- # Internal light switch
- # http://www.loopybunny.co.uk/CarPC/can/1E3.html
- # Send Can-Bus command:- ID:1E3, Length:02, DATA:F1 FF.
- # 50mS later send the command again:- ID:1E3, Length:02, DATA:F1 FF.
- # Finally send Can-Bus command:- ID:1E3, Length:02, DATA:F0 FF.
- pass
- elif (m.mid == 0x328) and (m.mlen > 5):
- # ID: 1100101000
- pd = '0x%s%s%s%s' % (m.message[3], m.message[2],
- m.message[1], m.message[0])
- br = '0x%s%s' % (m.message[5], m.message[4])
- # Battery reset is in days since 01/01/2000
- # Production date is seconds since the production date
- self.set('battery_reset', int(br, 16))
- self.set('production_date', (int(pd, 16)/(60*24)))
- elif (m.mid == 0x246) and m.mlen:
- # ID: 1001000110
- # Check message for more AC information
- if m.message[0] == 0x3F:
- self.set('rear_demister', 0)
- elif m.message[0] == 0x7F:
- self.set('rear_demister', 1)
- elif m.mid == 0x26E:
- # ID: 1001101110
- # Key relayed stuff...
- # We already know key status from 0x130
- # Maybe move here if it helps filtering?
- pass
- elif m.mid == 0x2A6:
- # ID: 1010100110
- # Wipers Stick
- # More info: http://www.loopybunny.co.uk/CarPC/can/2A6.html
- if m.message[0] == 0x00:
- self.set('wipers', 0)
- self.set('rear_wiper', 0)
- elif (m.message[0] == 0x01) and (m.mlen > 1):
- self.set('wipers', 1)
- self.set('wipers_speed', (m.message[1] - 247))
- elif (m.message[0] in [0x40, 0x81, 0x10]) and (m.mlen > 1):
- self.set('rear_wiper', 1)
- elif (m.message[0] == 0x08) and (m.mlen > 1):
- self.set('wipers_single', 1)
- elif (m.mid == 0x2CA):
- # ID: 1011001010
- self.set('outside_temp',((m.message[0] - 80) / 2))
- elif (m.mid == 0x2E6) and (m.mlen == 8):
- # ID: 1011100110
- self.set('left_temp', (m.message[7] / 2.0))
- self.set('fan_speed', (m.message[5]))
- elif (m.mid == 0x2EA) and (m.mlen == 8):
- # ID: 1011101010
- self.set('right_temp', (m.message[7] / 2.0))
- elif (m.mid == 0x366) and (m.mlen > 2):
- # ID: 1101100110
- etemp = (m.message[0] - 80) / 2.0
- erange = '0x%s%s' % (hex(m.message[2])[2:], hex(m.message[1])[2:])
- self.set('external_temp', etemp)
- self.set('range0', int(erange, 16)/16)
- elif (m.mid == 0x330) and (m.mlen == 8):
- # ID: 1100110000
- odm = '0x%s%s%s' % (hex(m.message[2])[2:].upper().rjust(2), \
- hex(m.message[1])[2:].upper().rjust(2), \
- hex(m.message[0])[2:].upper().rjust(2))
- odm = odm.replace(' ', '0')
- range0 = '0x%s%s' % (hex(m.message[7])[2:].upper().rjust(2), \
- hex(m.message[6])[2:].upper().rjust(2))
- range0 = range0.replace(' ', '0')
- self.set('odometer', int(odm, 16))
- self.set('avr_fuel', m.message[3])
- self.set('range', int(range0, 16) / 16)
- elif (m.mid == 0x34F):
- # ID: 1101001111
- if m.message[0] == 0xFE:
- self.set('handbrake', 1)
- elif m.message[0] == 0xFD:
- self.set('handbrake', 0)
- elif (m.mid == 0x2FC) and (m.mlen > 2):
- # ID: 1011111100
- # Doors
- bmsg = biny(m.message[1], 8)
- bmsg2= biny(m.message[2], 8)
- result.append(self.set('rr_door', bmsg[1]))
- result.append(self.set('rl_door', bmsg[3]))
- result.append(self.set('fr_door', bmsg[5]))
- result.append(self.set('fl_door', bmsg[7]))
- # Bonnet & Boot
- # It appears e87 dosnt have hood status here
- # It is probably populated by another message.
- #result.append(self.set('bonnet_open', bmsg2[1]))
- result.append(self.set('boot_open', bmsg2[7]))
- #elif (m.mid == 0x2D6) and (m.mlen > 1):
- # Looks like e87 donst use this for message for
- # the aircondition status...probably another message?
- elif (m.mid == 0x32E) and (m.mlen > 3):
- # ID: 1100101110
- self.internal_light = m.message[0]
- self.set('internal_temp', ((m.message[3] / 10.0) + 6))
- elif (m.mid == 0x349) and (m.mlen > 3):
- # ID : 1101001001
- self.left_fuel = binc(m.message[0], m.message[1]) / 160
- self.right_fuel = binc(m.message[2], m.message[3]) / 160
- elif (m.mid == 0x362) and (m.mlen > 2):
- # ID : 1101100010
- # Constant: 235.214
- hb, lb = hl_bits(m.message[1])
- afc = binc(m.message[2], hb)
- akmph = binc(m.message[0], lb)
- # Fuel consumation error!!! close but ...
- self.set('avr_fc', afc / 10.0)
- self.set('avr_speed', akmph / 10.0)
- self.set('avr_fc100', round((235.214 / self.avr_fc), 2))
- elif (m.mid == 0x380) and (m.mlen == 7):
- # ID: 1110000000
- self.decode_vin(m.message)
- elif m.mid == 0xF2:
- # ID: 0011110010
- if m.message[0] == 241:
- self.set('boot_locked', 0)
- elif m.message[0] > 241:
- self.set('boot_locked', 1)
- # if m.message[3] == 192:
- # result.append(self.set('boot_open', 0))
- # elif m.message[3] == 193:
- # result.append(self.set('boot_open', 1))
- elif m.mid == 0xEE:
- # ID: 0011101110
- if m.message[0] == 0x81:
- self.set('rl_lock', 0)
- elif m.message[0] > 0x81:
- self.set('rl_lock', 1)
- # if m.message[3] == 0xFC:
- # result.append(self.set('rl_door', 1))
- # elif m.message[3] == 0xFD:
- # result.append(self.set('rl_door', 0))
- elif m.mid == 0xEA:
- # ID: 0011101010
- if m.message[0] == 0x81:
- self.set('rr_lock', 0)
- elif m.message[0] > 0x81:
- self.set('rr_lock', 1)
- # if m.message[3] == 0xFC:
- # result.append(self.set('rr_door', 1))
- # elif m.message[3] == 0xFD:
- # result.append(self.set('rr_door', 0))
- elif m.mid == 0xE6:
- # ID: 11100110
- if m.message[0] == 0x81:
- self.set('fr_lock', 0)
- elif m.message[0] > 0x81:
- self.set('fr_lock', 1)
- # if m.message[3] == 0xFC:
- # result.append(self.set('fr_door', 1))
- # elif m.message[3] == 0xFD:
- # result.append(self.set('fr_door', 0))
- elif m.mid == 0xE2:
- # ID: 0011100010
- if m.message[0] == 0x81:
- self.set('fl_lock', 0)
- elif m.message[0] > 0x81:
- self.set('fl_lock', 1)
- # if m.message[3] == 0xFC:
- # result.append(self.set('fl_door', 1))
- # elif m.message[3] == 0xFD:
- # result.append(self.set('fl_door', 0))
- elif (m.mid == 0x3B0):
- # ID: 1110110000
- if m.message[0] == 0xFD:
- self.set('reverse', 0)
- elif m.message[0] == 0xFE:
- self.set('reverse', 1)
- elif (m.mid == 0x3B6):
- # ID: 1110110110
- # e87 Front Windows Position
- # is (0-95), for rear - 0-85?
- #
- # On more info how to emulate button press
- # http://www.loopybunny.co.uk/CarPC/can/0FA.html
- self.set('fl_window', m.message[0])
- elif (m.mid == 0x3B7):
- self.set('rl_window', m.message[0])
- elif (m.mid == 0x3B8):
- self.set('fr_window', m.message[0])
- elif (m.mid == 0x3B9):
- self.set('rr_window', m.message[0])
- elif (m.mid == 0x2F8) and (m.mlen > 6):
- # ID: 1011111000
- hb, lb = hl_bits(m.message[4])
- month = '0x%s' % hb
- month = int(month, 16)
- year = binc(m.message[5], m.message[6])
- self.set('day', m.message[3])
- self.set('month', month)
- self.set('hours', m.message[0])
- self.set('minutes', m.message[1])
- self.set('seconds', m.message[2])
- self.set('year', year)
- elif m.mid == 0x2B6:
- # ID: 1010110110
- # Not yet sure what this message is
- # But its transmitted every 1 seconds, so i'll use it
- # to keep track the clock in check.
- self.add_second()
- elif (m.mid == 0x3B4) and (m.mlen > 1):
- # ID: 1110110100
- # Seems like constant for e87...
- # Check for this elsewhere
- pass
- #cv = (((m.message[1] - 240) * 256) + m.message[0]) / 68
- #self.set('voltage', cv)
- elif (m.mid == 0x581) and (m.mlen > 3):
- # ID: 10110000001
- if m.message[3] == 0x28:
- self.set('seat_belt_alarm', 0)
- elif (m.mid == 0x394) and (m.mlen > 3):
- # ID: 1110010100
- if m.message[3] == 0x29:
- self.set('seat_belt_alarm', 1)
- elif (m.mid == 0xA8) and (m.mlen == 8):
- # ID: 0010101000
- # Trorque dosnt seem to be computable
- # the way its on e83 :( Gatta find it!
- self.set('brake_power', m.message[7])
- torque = ((m.message[2] * 256) + m.message[1]) / 32
- # self.set('torque', torque/100.0)
- # self.calc_kw()
- if m.message[5] == 0xF0:
- self.set('clutch', 0)
- elif m.message[5] == 0xF1:
- self.set('clutch', 1)
- if m.message[7] > 20:
- self.set('brakes', 1)
- else:
- self.set('brakes', 0)
- elif (m.mid == 0xAA) and (m.mlen > 6):
- # ID: 0010101010
- rpm = binc(m.message[4], m.message[5]) / 4
- throttle = binc(m.message[2], m.message[3]) / 256
- self.set('rpm', rpm)
- self.set('throttle', throttle)
- elif (m.mid == 0xC8) and (m.mlen > 1):
- # ID: 11001000
- bm = binc(m.message[0], m.message[1])
- if (bm / 23) > 360:
- wheel_position = (bm - 65535) / 23
- else:
- wheel_position = bm/23
- if (wheel_position < 360) and (wheel_position > -360):
- self.set('wheel_position', wheel_position)
- else:
- # Make sure we're not comming here
- print 'ERR-wheel_position %s' % wheel_position
- elif (m.mid == 0xCE) and (m.mlen == 8):
- # ID: 11001110
- # 1.6 to get in km/h not mph
- self.set('fl_wheel', (binc(m.message[0], m.message[1]) / 24 * 1.6))
- self.set('fr_wheel', (binc(m.message[3], m.message[3]) / 24 * 1.6))
- self.set('rl_wheel', (binc(m.message[4], m.message[5]) / 24 * 1.6))
- self.set('rr_wheel', (binc(m.message[6], m.message[7]) / 24 * 1.6))
- elif (m.mid == 0x2A0):
- # ID: 1010100000
- # http://www.e90post.com/forums/showpost.php?p=14513521&postcount=114
- # ID:2A0 [8] DATA:88 88 80 0 16 40 86 0
- # I've been able to open the trunk.
- # Hope this is helpful for somebody.
- #
- # ID: 2a0 -0- Data: [8] 88 88 88 1 2c 40 6 0
- # This should be normal status?
- # Inspect more
- pass
- elif (m.mid == 0x2F0):
- pass # ID: 2f0 -0- Data: [2] f7 fc
- elif (m.mid == 0x23A):
- # Ispect more
- print 'ID: 0x23A', m.message
- elif (m.mid == 0x2F4):
- pass # ID: 2f4 -0- Data: [4] cf ff ff ff
- elif (m.mid == 0x2F6):
- pass # ID: 2f6 -0- Data: [2] 0 f5
- elif (m.mid == 0x2FA):
- pass # ID: 2fa -0- Data: [5] fd 8 ff ff ff
- elif (m.mid == 0x311):
- pass # ID: 311 -0- Data: [2] 0 f0
- elif (m.mid == 0x335):
- pass # Sensor data?! Inspect more
- elif (m.mid == 0x367):
- pass # Sensor data?! Inspect more
- elif (m.mid == 0x35C):
- pass # ID: 35c -0- Data: [4] ff f0 ff a0
- elif (m.mid == 0x3B3):
- pass # ID: 3b3 -0- Data: [6] 11 c8 0 0 0 f0
- elif (m.mid == 0x3BD):
- pass # ID: 3bd -0- Data: [2] fd ff
- elif (m.mid == 0x3BE):
- pass # ID: 3be -0- Data: [2] fe ff
- elif (m.mid == 0x360):
- pass # ID: 360 -0- Data: [7] ff ff ff ff ff ff ff
- elif (m.mid == 0x364):
- pass # ID: 364 -0- Data: [7] ff ff ff ff ff ff ff
- elif (m.mid == 0x35E):
- pass # ID: 35e -0- Data: [8] ff ff ff ff ff ff ff ff
- elif (m.mid == 0xD7):
- pass # Seat belt & airbag alarm stuff?
- elif (m.mid == 0xC0):
- pass # ABS Module "all ok" ?
- elif (m.mid == 0xC4):
- pass # Same as 0xC8 (not sent as often thou)
- elif (m.mid == 0x2BA):
- pass # Counter (Toggle / Heartbeat)
- elif (m.mid == 0x19E):
- # Get brake pressure?
- pass # ABS Stuff http://www.loopybunny.co.uk/CarPC/can/19E.html
- elif (m.mid == 0x7C3):
- pass # Keyfob (security, comfort and CBS data). # To inspect more...
- elif (m.mid == 0x1A6):
- pass # Speed used by instrument cluster (offsets)
- else:
- idstr = str(hex(m.mid))
- if idstr in self.uids:
- self.uids[idstr] += 1
- else:
- self.uids[idstr] = 1
- return self.result
- def unknown_ids(self):
- return self.uids
- def calc_kw(self):
- # (RPM * Torque) / (60 / 2Pi) / 1000
- self.kw = ((self.rpm * self.torque) * 9.54) / 1000
- def binc(i, n):
- return (n * 256) + i
- def biny(i, l):
- return format(i, 'b').zfill(l)
- def hl_bits(i):
- bb = hex(i)[2:]
- return int(bb[0], 16), int(bb[1], 16)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement