Piratage automatique du bus CAN. Tableau de bord virtuel



Dans le premier article, «Piratage d'un bus CAN pour une voiture pour la commande vocale», je me suis connecté directement au bus CAN Confort à la porte de ma voiture et j'ai examiné le trafic qui passait, cela m'a permis de déterminer les commandes de contrôle des fenêtres, du verrouillage central, etc.

Dans cet article, je vous dirai comment assembler mon propre tableau de bord virtuel ou numérique unique et obtenir des données de tous les capteurs des voitures VAG (Volkswagen, Audi, Seat, Skoda).

J'ai assemblé un nouveau renifleur CAN et un bouclier CAN pour le Raspberry Pi basé sur le module Niren MCP2515 TJA1050, les données obtenues avec eux ont été utilisées dans le développement d'un tableau de bord numérique utilisant un écran de 7 pouces pour le Raspberry Pi. En plus d'un simple affichage d'informations, le panneau numérique répond aux boutons de l'interrupteur de la colonne de direction et à d'autres événements dans la voiture.

En tant que cadre pour dessiner des instruments, Kivy pour Python était génial. Il fonctionne sans X et utilise GL pour afficher les graphiques.

  1. CAN sniffer d'Arduino Uno
  2. Écoute des demandes à l'aide du système de diagnostic VAG-COM (VCEMD)
  3. Développement du tableau de bord basé sur le Raspberry Pi et l'écran 7 ″
  4. Logiciel de tableau de bord en Python et Kivy (framework UI)
  5. Vidéo du tableau de bord numérique basé sur le Raspberry Pi

Sous la coupe, la mise en œuvre complète du projet sera intéressante!


La porte conducteur est ouverte

CAN sniffer d'Arduino Uno


Pour écouter ce que VCDS envoie au bus CAN, j'ai assemblé un renifleur sur une maquette d'Arduino et le module MCP2515 TJA1050 Niren.



Le schéma de connexion est le suivant:


Pour écouter le trafic, j'ai utilisé l'analyseur CanHackerV2 et le firmware Arduino-Canhacker pour Arduino, qui implémente une API compatible avec ce programme. Firmware dans le gith https://github.com/autowp/arduino-canhacker .

CanHackerV2 vous permet de regarder le trafic en transit, d'enregistrer et de lire des commandes à un intervalle spécifié, ce qui facilite grandement l'analyse des données.



Écoute des demandes à l'aide du système de diagnostic VAG-COM (VCEMD)


Description du VCEMD sur le site officiel ru.ross-tech.com :

Le scanner matériel et logiciel VCDS est conçu pour diagnostiquer les systèmes de contrôle électronique installés sur les véhicules VAG. Accès à tous les systèmes: moteur, ACP, ABS, climatisation, électronique de carrosserie, etc., lecture et effacement des codes d'erreur, affichage des paramètres actuels, activation, réglages de base, adaptation, codage, etc.



En connectant le renifleur aux lignes CAN_L et CAN_H dans le cordon de diagnostic, j'ai pu voir ce que le VCEMD demande et ce que la voiture répond.



La particularité du groupe automatique VAG est que le connecteur OBD2 est connecté au bus CAN via la passerelle et que la passerelle ne transmet pas tout le trafic qui traverse le réseau, c'est-à-dire Si vous vous connectez au connecteur OBD2 avec un renifleur, vous ne verrez rien. Pour recevoir des données dans le connecteur OBD2, vous devez envoyer des demandes spéciales à la passerelle. Ces demandes et réponses sont visibles lors de l'écoute du trafic provenant du VCEMD. Par exemple, voici comment vous pouvez obtenir un kilométrage.


Dans VCEMD, vous pouvez obtenir des informations de presque tous les capteurs de la voiture. Tout d'abord, j'étais intéressé par des informations qui ne sont généralement pas sur ma liste, ceci:

  • température d'huile
  • quelle porte est ouverte

J'ai également reçu la vitesse, les révolutions, la température du liquide de refroidissement, le kilométrage, le débit, l'espace du réservoir et d'autres demandes, je vais le poster pour référence.

// 
714 03 22 22 0D 55 55 55 55
77E 05 62 22 0D 55 65 AA AA -  
77E 05 62 22 0D 00 65 AA AA -  
77E 05 62 22 0D 54 65 AA AA -  
77E 05 62 22 0D 51 65 AA AA -  
77E 05 62 22 0D 50 65 AA AA -    
77E 05 62 22 0D 45 65 AA AA -   
77E 05 62 22 0D 15 65 AA AA -   
77E 05 62 22 0D 44 65 AA AA -     
77E 05 62 22 0D 40 65 AA AA - , ,   
01010101 = 0x55 ( )
0  - 
2  - 
4  -  
6  -  

// 
714 03 22 22 05 55 55 55 55
77E 05 62 22 05 21 AA AA AA - 
77E 05 62 22 05 20 AA AA AA -  

//  
714 03 22 22 0 55 55 55 55
77E 04 62 22 0C 55 AA AA AA - -7.5°
77E 04 62 22 0C 65 AA AA AA - 0.5 101°
77E 04 62 22 0C 66 AA AA AA - 1 = 102°
77E 04 62 22 0C 68 AA AA AA - 2 = 104°

//   
714 03 22 10 14 55 55 55 55
77E 04 62 10 14 84 AA AA AA - 16°

//  
714 03 22 22 94 55 55 55 55
77E 05 62 22 94 00 8E AA AA - 142

//  
714 03 22 22 06 55 55 55 55
77E 04 62 22 06 2C AA AA AA - 44
77E 04 62 22 06 16 AA AA AA - 22

//     
714 03 22 22 96 55 55 55 55
77E 05 62 22 96 01 9A AA AA - 41.0°

//  
714 03 22 F4 05 55 55 55 55
77E 04 62 F4 05 85 AA AA AA - 52.5°
77E 04 62 F4 05 87 AA AA AA - 54°
77E 04 62 F4 05 5 AA AA AA - 100.5°

//  
714 03 22 F4 0C 55 55 55 55
77E 05 62 F4 0C 0B C6 AA AA - 753.5 /; 0BC6 = 3014/4 = 753
77E 05 62 F4 0C 0B DC AA AA - 759 /; 0BDC = 3036
77E 05 62 F4 0C 0B E8 AA AA - 762 /; 0BE8 = 3048
77E 05 62 F4 0C 1C 32 AA AA - 1804.5 /

//  
714 03 22 20 2F 55 55 55 55
77E 04 62 20 2F 36 AA AA AA - -4°
77E 04 62 20 2F 67 AA AA AA - 45°
77E 04 62 20 2F 68 AA AA AA - 46°

//    
746 03 22 26 13 55 55 55 55
7B0 05 62 26 13 00 5B AA AA - 9.1, 91 == 0x5B
7B0 05 62 26 13 00 5C AA AA - 9.2°
7B0 05 62 26 13 00 5D AA AA - 9.3°

// 
714 03 22 22 16 55 55 55 55
77E 05 62 22 16 11 1E AA AA - 17:30

//   
714 03 22 22 1B 55 55 55 55
77E 05 62 22 1B 80 AA AA AA -  
77E 05 62 22 1B 81 AA AA AA -  
77E 05 62 22 1B 84 AA AA AA -  

//   2
714 03 22 22 99 55 55 55 55
77E 03 62 22 99 00 91 AA AA - 14.5/100

//  
714 03 22 22 98 55 55 55 55
77E 05 62 22 98 00 00 AA AA - 0.0/100

// 
714 03 22 22 03 55 55 55 55
77E 05 62 22 03 24 0 AA AA - 94080  0x240 * 10

Raspberry Pi 7″


Raspberry Pi. Android , , Raspberry Pi . 7″ , CAN TJA1050 Niren.



OBD2 ELM327 .



: CAN_L, CAN_H, +12, GND.



. , Raspberry Pi , , .



, . .



, . , 3D .



Python Kivy (UI framework)


. .




. , , - 80-.




- .




, Linux . , : , , nodejs, . Qt PySide2 , , .. . Kivy — Python, .

Kivy , , OpenGL. 10 .

import can
import os
import sys
from threading import Thread
import time

os.environ['KIVY_GL_BACKEND'] = 'gl'
os.environ['KIVY_WINDOW'] = 'egl_rpi'

from kivy.app import App
from kivy.properties import NumericProperty
from kivy.properties import BoundedNumericProperty
from kivy.properties import StringProperty
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.uix.scatter import Scatter
from kivy.animation import Animation

messageCommands = {
    'GET_DOORS_COMMAND': 0x220D,
    'GET_OIL_TEMPERATURE' : 0x202F,
    'GET_OUTDOOR_TEMPERATURE' : 0x220C,
    'GET_INDOOR_TEMPERATURE' : 0x2613,
    'GET_COOLANT_TEMPERATURE' : 0xF405,
    'GET_SPEED' : 0xF40D,
    'GET_RPM' : 0xF40C,
    'GET_KM_LEFT': 0x2294,
    'GET_FUEL_LEFT': 0x2206,
    'GET_TIME': 0x2216
}

bus = can.interface.Bus(channel='can0', bustype='socketcan')

python
# -*- coding: utf-8 -*-

import can
import os
import sys
from threading import Thread
import time

os.environ['KIVY_GL_BACKEND'] = 'gl'
os.environ['KIVY_WINDOW'] = 'egl_rpi'

from kivy.app import App
from kivy.properties import NumericProperty
from kivy.properties import BoundedNumericProperty
from kivy.properties import StringProperty
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.uix.scatter import Scatter
from kivy.animation import Animation

messageCommands = {
    'GET_DOORS_COMMAND': 0x220D,
    'GET_OIL_TEMPERATURE' : 0x202F,
    'GET_OUTDOOR_TEMPERATURE' : 0x220C,
    'GET_INDOOR_TEMPERATURE' : 0x2613,
    'GET_COOLANT_TEMPERATURE' : 0xF405,
    'GET_SPEED' : 0xF40D,
    'GET_RPM' : 0xF40C,
    'GET_KM_LEFT': 0x2294,
    'GET_FUEL_LEFT': 0x2206,
    'GET_TIME': 0x2216
}

bus = can.interface.Bus(channel='can0', bustype='socketcan')

class PropertyState:
    def __init__(self, last, current):
        self.last = last
        self.current = current

    def lastIsNotNow(self):
        return self.last is not self.current

class CanListener(can.Listener):
    def __init__(self, dashboard):
        self.dashboard = dashboard
        self.speedStates = PropertyState(None,None)
        self.rpmStates = PropertyState(None,None)
        self.kmLeftStates = PropertyState(None,None)
        self.coolantTemperatureStates = PropertyState(None,None)
        self.oilTempratureStates = PropertyState(None,None)
        self.timeStates = PropertyState(None,None)
        self.outDoorTemperatureStates = PropertyState(None,None)
        self.doorsStates = PropertyState(None,None)
        self.carMinimized = True

    def on_message_received(self, message):
	 messageCommand = message.data[3] | message.data[2] << 8

        if message.arbitration_id == 0x77E and messageCommand == messageCommands['GET_SPEED']:
            self.speedStates.current = message.data[4]
            if self.speedStates.lastIsNotNow():
                self.dashboard.speedometer.text = str(self.speedStates.current)
                self.speedStates.last = self.speedStates.current

        if message.arbitration_id == 0x77E and messageCommand == messageCommands['GET_RPM']:
            self.rpmStates.current = message.data[5] | message.data[4] << 8
            if self.rpmStates.lastIsNotNow():
                self.dashboard.rpm.value = self.rpmStates.current/4
                self.rpmStates.last = self.rpmStates.current
        if message.arbitration_id == 0x35B:
            self.rpmStates.current = message.data[2] | message.data[1] << 8
            if self.rpmStates.lastIsNotNow():
                self.dashboard.rpm.value = self.rpmStates.current/4
                self.rpmStates.last = self.rpmStates.current

        if message.arbitration_id == 0x77E and messageCommand == messageCommands['GET_KM_LEFT']:
            self.kmLeftStates.current = message.data[5] | message.data[4] << 8
            if self.kmLeftStates.lastIsNotNow():
                self.dashboard.kmLeftLabel.text = str(self.kmLeftStates.current)
                self.kmLeftStates.last = self.kmLeftStates.current

        if message.arbitration_id == 0x77E and messageCommand == messageCommands['GET_COOLANT_TEMPERATURE']:
            self.coolantTemperatureStates.current = message.data[4]
            if self.coolantTemperatureStates.lastIsNotNow():
                self.dashboard.coolantLabel.text = str(self.coolantTemperatureStates.current-81)
                self.coolantTemperatureStates.last = self.coolantTemperatureStates.current

        if message.arbitration_id == 0x77E and messageCommand == messageCommands['GET_OIL_TEMPERATURE']:
            self.oilTempratureStates.current = message.data[4]
            if self.oilTempratureStates.lastIsNotNow():
                self.dashboard.oilLabel.text = str(self.oilTempratureStates.current-58)
                self.oilTempratureStates.last = self.oilTempratureStates.current

        if message.arbitration_id == 0x77E and messageCommand == messageCommands['GET_TIME']:
            self.timeStates.current = message.data[5] | message.data[4] << 8
            if self.timeStates.lastIsNotNow():
                self.dashboard.clock.text = str(message.data[4]) + ":" + str(message.data[5])
                self.timeStates.last = self.timeStates.current

        if message.arbitration_id == 0x77E and messageCommand == messageCommands['GET_OUTDOOR_TEMPERATURE']:
            self.outDoorTemperatureStates.current = float(message.data[4])
            if self.outDoorTemperatureStates.lastIsNotNow():
                self.dashboard.outDoorTemperatureLabel.text = str((self.outDoorTemperatureStates.current - 100)/2)
                self.outDoorTemperatureStates.last = self.outDoorTemperatureStates.current

        if message.arbitration_id == 0x77E and messageCommand == messageCommands['GET_DOORS_COMMAND']:
            self.doorsStates.current = message.data[4]
            if self.doorsStates.lastIsNotNow():
                self.doorsStates.last = self.doorsStates.current
                self.dashboard.car.doorsStates=message.data[4]

                # all doors closed -> minimize car
                if self.doorsStates.current == 0x55:
                    self.dashboard.minimizeCar()
                    self.carMinimized = True
                else:
                    if self.carMinimized:
                        self.dashboard.maximizeCar()
                        self.carMinimized = False
          
class Dashboard(FloatLayout):
    def __init__(self,**kwargs):
        super(Dashboard,self).__init__(**kwargs)

        # Background
        self.backgroundImage = Image(source='bg.png')
        self.add_widget(self.backgroundImage)

        # RPM
        self.rpm = Gauge(file_gauge = "gauge512.png", unit = 0.023, value=0, size_gauge=512, pos=(0,0))
        self.add_widget(self.rpm)
        self.rpm.value = -200

        # Speedometer
        self.speedometer = Label(text='0', font_size=80, font_name='hemi_head_bd_it.ttf', pos=(0,-15))
        self.add_widget(self.speedometer)

        # KM LEFT
        self.kmLeftLabel = Label(text='000', font_name='Avenir.ttc', halign="right", text_size=self.size, font_size=25, pos=(278,233))
        self.add_widget(self.kmLeftLabel)

        # COOLANT TEMPEARATURE
        self.coolantLabel = Label(text='00', font_name='hemi_head_bd_it.ttf', halign="right", text_size=self.size, font_size=27, pos=(295,-168))
        self.add_widget(self.coolantLabel)

        # OIL TEMPERATURE
        self.oilLabel = Label(text='00', font_name='hemi_head_bd_it.ttf', halign="right", text_size=self.size, font_size=27, pos=(-385,-168))
        self.add_widget(self.oilLabel)

        # CLOCK
        self.clock = Label(text='00:00', font_name='Avenir.ttc', font_size=27, pos=(-116,-202))
        self.add_widget(self.clock)

        # OUTDOOR TEMPERATURE
        self.outDoorTemperatureLabel = Label(text='00.0', font_name='Avenir.ttc', halign="right", text_size=self.size, font_size=27, pos=(76,-169))
        self.add_widget(self.outDoorTemperatureLabel)

        # CAR DOORS
        self.car = Car(pos=(257,84))
        self.add_widget(self.car)

    def minimizeCar(self, *args):
        print("min")
        anim = Animation(scale=0.5, opacity = 0, x = 400, y = 240, t='linear', duration=0.5)
        anim.start(self.car)

        animRpm = Animation(scale=1, opacity = 1, x = 80, y = -5, t='linear', duration=0.5)
        animRpm.start(self.rpm)

    def maximizeCar(self, *args):
        print("max")
        anim = Animation(scale=1, opacity = 1, x=257, y=84, t='linear', duration=0.5)
        anim.start(self.car)

        animRpm = Animation(scale=0.5, opacity = 0, x = 80, y = -5, t='linear', duration=0.5)
        animRpm.start(self.rpm)


class Car(Scatter):
    carImage = StringProperty("car362/car.png")

    driverDoorClosedImage = StringProperty("car362/driverClosedDoor.png")
    driverDoorOpenedImage = StringProperty("car362/driverOpenedDoor.png")

    passangerDoorClosedImage = StringProperty("car362/passangerClosedDoor.png")
    passangerDoorOpenedImage = StringProperty("car362/passangerOpenedDoor.png")

    leftDoorClosedImage = StringProperty("car362/leftClosedDoor.png")
    leftDoorOpenedImage = StringProperty("car362/leftOpenedDoor.png")

    rightDoorClosedImage = StringProperty("car362/rightClosedDoor.png")
    rightDoorOpenedImage = StringProperty("car362/rightOpenedDoor.png")

    doorsStates = NumericProperty(0)

    size = (286, 362)

    def __init__(self, **kwargs):
        super(Car, self).__init__(**kwargs)

        _car = Image(source=self.carImage, size=self.size)

        self.driverDoorOpened = Image(source=self.driverDoorOpenedImage, size=self.size)
        self.passangerDoorOpened = Image(source=self.passangerDoorOpenedImage, size=self.size)
        self.leftDoorOpened = Image(source=self.leftDoorOpenedImage, size=self.size)
        self.rightDoorOpened = Image(source=self.rightDoorOpenedImage, size=self.size)

        self.driverDoorClosed = Image(source=self.driverDoorClosedImage, size=self.size)
        self.passangerDoorClosed = Image(source=self.passangerDoorClosedImage, size=self.size)
        self.leftDoorClosed = Image(source=self.leftDoorClosedImage, size=self.size)
        self.rightDoorClosed = Image(source=self.rightDoorClosedImage, size=self.size)

        self.add_widget(_car)
        self.add_widget(self.driverDoorOpened)
        self.add_widget(self.passangerDoorOpened)
        self.add_widget(self.leftDoorOpened)
        self.add_widget(self.rightDoorOpened)

        self.bind(doorsStates=self._update)

    def _update(self, *args):
        driverDoorStates = self.doorsStates&1
        passangerDoorStates = self.doorsStates&4
        leftDoorStates = self.doorsStates&16
        rightDoorStates = self.doorsStates&64
        if driverDoorStates != 0:
            try:
                self.remove_widget(self.driverDoorOpened)
                self.add_widget(self.driverDoorClosed)
            except:
                pass
        else:
            try:
                self.remove_widget(self.driverDoorClosed)
                self.add_widget(self.driverDoorOpened)
            except:
                pass
        if passangerDoorStates != 0:
            try:
                self.remove_widget(self.passangerDoorOpened)
                self.add_widget(self.passangerDoorClosed)
            except:
                pass
        else:
            try:
                self.remove_widget(self.passangerDoorClosed)
                self.add_widget(self.passangerDoorOpened)
            except:
                pass
        if leftDoorStates != 0:
            try:
                self.remove_widget(self.leftDoorOpened)
                self.add_widget(self.leftDoorClosed)
            except:
                pass
        else:
            try:
                self.remove_widget(self.leftDoorClosed)
                self.add_widget(self.leftDoorOpened)
            except:
                pass
        if rightDoorStates != 0:
            try:
                self.remove_widget(self.rightDoorOpened)
                self.add_widget(self.rightDoorClosed)
            except:
                pass
        else:
            try:
                self.remove_widget(self.rightDoorClosed)
                self.add_widget(self.rightDoorOpened)
            except:
                pass

class Gauge(Scatter):
    unit = NumericProperty(1.125)
    zero = NumericProperty(116)
    value = NumericProperty(10) #BoundedNumericProperty(0, min=0, max=360, errorvalue=0)
    size_gauge = BoundedNumericProperty(512, min=128, max=512, errorvalue=128)
    size_text = NumericProperty(10)
    file_gauge = StringProperty("")

    def __init__(self, **kwargs):
        super(Gauge, self).__init__(**kwargs)

        self._gauge = Scatter(
            size=(self.size_gauge, self.size_gauge),
            do_rotation=False, 
            do_scale=False,
            do_translation=False
            )

        _img_gauge = Image(source=self.file_gauge, size=(self.size_gauge, self.size_gauge))

        self._needle = Scatter(
            size=(self.size_gauge, self.size_gauge),
            do_rotation=False,
            do_scale=False,
            do_translation=False
            )

        _img_needle = Image(source="arrow512.png", size=(self.size_gauge, self.size_gauge))


        self._gauge.add_widget(_img_gauge)
        self._needle.add_widget(_img_needle)

        self.add_widget(self._gauge)
        self.add_widget(self._needle)

        self.bind(pos=self._update)
        self.bind(size=self._update)
        self.bind(value=self._turn)

    def _update(self, *args):
        self._gauge.pos = self.pos
        self._needle.pos = (self.x, self.y)
        self._needle.center = self._gauge.center

    def _turn(self, *args):
        self._needle.center_x = self._gauge.center_x
        self._needle.center_y = self._gauge.center_y
        a = Animation(rotation=-self.value*self.unit + self.zero, t='in_out_quad',duration=0.05)
        a.start(self._needle)

class requestsLoop(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.daemon = True
        self.start()

    canCommands = [
        can.Message(arbitration_id=0x714, data=[0x03, 0x22, messageCommands['GET_DOORS_COMMAND'] >> 8, messageCommands['GET_DOORS_COMMAND'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False),
        can.Message(arbitration_id=0x714, data=[0x03, 0x22, messageCommands['GET_SPEED'] >> 8, messageCommands['GET_SPEED'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False),
        can.Message(arbitration_id=0x714, data=[0x03, 0x22, messageCommands['GET_KM_LEFT'] >> 8, messageCommands['GET_KM_LEFT'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False),
        can.Message(arbitration_id=0x714, data=[0x03, 0x22, messageCommands['GET_RPM'] >> 8, messageCommands['GET_RPM'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False),
        can.Message(arbitration_id=0x714, data=[0x03, 0x22, messageCommands['GET_OIL_TEMPERATURE'] >> 8, messageCommands['GET_OIL_TEMPERATURE'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False),
        can.Message(arbitration_id=0x714, data=[0x03, 0x22, messageCommands['GET_FUEL_LEFT'] >> 8, messageCommands['GET_FUEL_LEFT'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False),
        can.Message(arbitration_id=0x714, data=[0x03, 0x22, messageCommands['GET_OUTDOOR_TEMPERATURE'] >> 8, messageCommands['GET_OUTDOOR_TEMPERATURE'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False),
        can.Message(arbitration_id=0x746, data=[0x03, 0x22, messageCommands['GET_INDOOR_TEMPERATURE'] >> 8, messageCommands['GET_INDOOR_TEMPERATURE'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False),
        can.Message(arbitration_id=0x714, data=[0x03, 0x22, messageCommands['GET_COOLANT_TEMPERATURE'] >> 8, messageCommands['GET_COOLANT_TEMPERATURE'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False),
        can.Message(arbitration_id=0x714, data=[0x03, 0x22, messageCommands['GET_TIME'] >> 8, messageCommands['GET_TIME'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False)
    ]

    def run(self):
        while True:
            for command in self.canCommands:
                bus.send(command)
                time.sleep(0.005)

class BoxApp(App):
    def build(self):
        dashboard = Dashboard();
        listener = CanListener(dashboard)
        can.Notifier(bus, [listener])

        return dashboard
        
if __name__ == "__main__":
    # Send requests
    requestsLoop()

    _old_excepthook = sys.excepthook
    def myexcepthook(exctype, value, traceback):
        if exctype == KeyboardInterrupt:
            print "Handler code goes here"
        else:
            _old_excepthook(exctype, value, traceback)
    sys.excepthook = myexcepthook

    # Show dashboard
    BoxApp().run()


, 3 :

  1. (, , , )
  2. 5
  3. CAN ,

, . iOS, .

. !

https://github.com/aivs/blackCockpit

Raspberry Pi




24.06.2019




— , ELM327 Wi-Fi . OBD2 , CAN Wi-Fi.


VAG Virtual Cockpit AppStore. , iPhone/iPad, Android . .
, , !
VAG Virtual Cockpit


Source: https://habr.com/ru/post/fr442184/


All Articles