- PVSM.RU - https://www.pvsm.ru -

Хакаем CAN шину авто для голосового управления

Хакаем CAN шину авто для голосового управления - 1
Современный автомобиль это не только средство передвижение, но и продвинутый гаджет с мультимедийными функциями и электронной системой управления агрегатами и кучей датчиков. Многие автопроизводители предлагают функции ассистентов движения, помощников при парковке, мониторинга и управления авто с телефона. Это возможно благодаря использованию в авто CAN шины к которой подключены все системы: двигатель, тормозная система, руль, мультимедиа, климат и др.
Мой автомобиль Skoda Octavia 2011 г. в. не предлагает возможностей управления с телефона, поэтому я решил исправить этот недостаток, а заодно и добавить функцию голосового управления. В качестве шлюза между CAN шиной и телефоном я использую Raspberry Pi с шилдом CAN BUS и WiFi роутер TP-Link. Протокол общения агрегатов авто закрытый, и на все мои письма предоставить документацию протокола Volkswagen отвечал отказом. Поэтому единственный способ узнать, как общаются устройства в авто и научиться ими управлять является реверс-инжиниринг протокола CAN шины VW.

Я действовал поэтапно:

  1. Разработка CAN шилда для Raspberry Pi
  2. Установка ПО для работы с CAN шиной
  3. Подключение к CAN шине авто
  4. Разработка сниффера и изучение протокола CAN шины
  5. Разработка приложения для телефона
  6. Голосовое управление с помощью Homekit и Siri

В конце видео голосового управления стеклоподъемником.

Разработка CAN шилда для Raspberry Pi

Схему шилда взял здесь lnxpps.de/rpie [1], там же и описание выводов, для общения с CAN используются 2 микросхемы MCP2515 и MCP2551. К шилду подключаются 2 провода CAN-High и CAN-Low. В SprintLayout 6 развел плату, может кому пригодится CANBoardRPi.lay [2] (на заглавном фото прототип шилда на макетке).
Хакаем CAN шину авто для голосового управления - 2
Хакаем CAN шину авто для голосового управления - 3

Установка ПО для работы с CAN шиной

На Raspbian 2-x годичной давность мне потребовалось пропатчить bcm2708.c, чтобы добавить поддержку CAN (возможно сейчас это не требуется). Для работы с CAN шиной нужно установить пакет утилит can-utils с github.com/linux-can/can-utils [3], после этого подгрузить модули и поднять can интерфейс:

# initialize
insmod spi-bcm2708
insmod can
insmod can-dev
insmod can-raw
insmod can-bcm
insmod mcp251x
# Maerklin Gleisbox (60112 and 60113) uses 250000
# loopback mode for testing
ip link set can0 type can bitrate 125000 loopback on
ifconfig can0 up

Проверяем, что интерфейс CAN поднялся командой ifconfig
Хакаем CAN шину авто для голосового управления - 4
Проверить, что все работает можно отправив команду и получив ее.
В одном терминале слушаем:

root@raspberrypi ~ # candump any,0:0,#FFFFFFFF

В другом терминале отправляем:

root@raspberrypi ~ # cansend can0 123#deadbeef

Более подробный процесс установки описан здесь lnxpps.de/rpie [1].

Подключение к CAN шине авто

Немного изучив открытую документацию на CAN шину VW [4] я выяснил, что у меня используется 2 шины.
Шина CAN силового агрегата, передающая данные со скоростью 500 кбит/с, связывает все
обслуживающие этот агрегат блоки управления.
Например, к шине CAN силового агрегата могут быть подключены следующие приборы:

  • блок управления двигателем,
  • блок управления АБС,
  • блок управления системой курсовой стабилизации,
  • блок управления коробкой передач,
  • блок управления подушками безопасности,
  • комбинация приборов.

Шина CAN системы «Комфорт» и информационнокомандной системы, позволяющая передавать данные
со скоростью 100 кбит/с между обслуживающими эти системы блоками управления.
Например, к шине CAN системы «Комфорт» и информационно<командной системы могут быть
подключены следующие приборы:

  • блок управления системой Climatronic или климатической установкой,
  • блоки управления в дверях автомобиля,
  • блок управления системой «Комфорт»,
  • блок управления с дисплеем для радио и навигационной системы.

Получив доступ к первой можно у управлять движением (в моем варианте на механике, как минимум можно управлять круиз контролем), получив доступ ко второй можно можно управлять магнитолой, климатом, центральным замком, стеклоподъемниками, фарами и др.
Обе шины связаны через шлюз, который находится в области под рулем, так же к шлюзу подключен диагностический OBD2 разъем, к сожаление через OBD2 разъем нельзя послушать трафик от обеих шин, можно только передать команду и запросить состояние. Я решил, что буду работать только с шиной «Комфорт» и самым удобным местом подключения к шине оказался разъем в водительской двери.
Хакаем CAN шину авто для голосового управления - 5
Теперь я могу слушать, все что происходит в CAN шине «Комфорт» и отправлять команды.

Разработка сниффера и изучение протокола CAN шины

Хакаем CAN шину авто для голосового управления - 6
После того как я получил доступ к прослушиванию CAN шины, мне нужно расшифровать кто кому и что передает. Формат пакета CAN показан на рисунке.
Хакаем CAN шину авто для голосового управления - 7
Все утилиты из набора can-utils сами умеют разбирать CAN пакеты и отдают только полезную информацию, а именно:

  • Идентификатор
  • Длина данных
  • Данные

Данные передаются в не зашифрованном виде, это облегчило изучение протокола. На Raspberry Pi я написал маленький сервер который перенаправляет данные с candump в TCP/IP, чтобы на компьютере разобрать поток данных и красиво показать их.
Для macOS я написал простое приложение, которое для каждого адреса устройства добавляет ячейку в табличку и в этой ячейке я уже вижу какие данные меняются.
Хакаем CAN шину авто для голосового управления - 8
Нажимаю кнопку стеклоподъемника я нашел ячейку в которой меняются данные, затем я и определил какие команды соответствуют нажатию вниз, нажатию вверх, удержанию вверх, удержанию вниз.
Проверить, что команда работает, можно отправив из терминала, например команду поднять левое стекло вверх:

cansend can0 181#0200

Команды, которые передают устройства по CAN шине в автомобилях VAG (Skoda Octavia 2011), полученные методом реверс-инжиниринг:

// Front Left Glass Up
181#0200
// Front Left Glass Down
181#0800
// Front Right Glass Up
181#2000
// Front Right Glass Down
181#8000
// Back Left Glass Up
181#0002
// Back Left Glass Down
181#0008
// Back Right Glass Up
181#0020
// Back Right Glass Down
181#0080
// Central Lock Open
291#09AA020000
// Central Lock Close
291#0955040000
// Update Light status of central lock (Когда отправляешь команду открыть/закрыть замок то на кнопке управления замком светодиод не изменяет состояние, чтобы он показал реальное состояние центрального замка, нужно отправить команду обновления)
291#0900000000

Мне было лень изучить все остальные устройства, поэтому в этом списке, только то что мне было интересно.

Разработка приложения для телефона

Используя полученные команды я написал приложение для iPhone, которое открывает/закрывает стекла и управляет центральным замком.
На Raspberry Pi я запустил 2 маленьких сервера, первый отправляет данные с candump в TCP/IP, второй принимает команды от iPhone и передает их cansend.
Хакаем CAN шину авто для голосового управления - 9

Исходники приложения управления авто для iOS

//
//  FirstViewController.m
//  Car Control
//
//  Created by Vitaliy Yurkin on 17.05.15.
//  Copyright (c) 2015 Vitaliy Yurkin. All rights reserved.
//

#import "FirstViewController.h"
#import "DataConnection.h"
#import "CommandConnection.h"

@interface FirstViewController () <DataConnectionDelegate>
@property (nonatomic, strong) DataConnection *dataConnection;
@property (nonatomic, strong) CommandConnection *commandConnection;
@property (weak, nonatomic) IBOutlet UILabel *Door_1;
@property (weak, nonatomic) IBOutlet UILabel *Door_2;
@property (weak, nonatomic) IBOutlet UILabel *Door_3;
@property (weak, nonatomic) IBOutlet UILabel *Door_4;
@property (weak, nonatomic) IBOutlet UIButton *CentralLock;
- (IBAction)lockUnlock:(UIButton *)sender;
@end

@implementation FirstViewController

- (void)viewDidLoad {
    self.dataConnection = [DataConnection new];
    self.dataConnection.delegate = self;
    [self.dataConnection connectToCanBus];
    
    self.commandConnection = [CommandConnection new];
    [self.commandConnection connectToCanBus];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)doorStatusChanged:(char)value {
    /*
     1 - Front Left Door
     2 - Front Right Door
     4 - Back Left Door
     8 - Back Right Door
     
     3 - Front Left&Right Door = 1 + 3
     5 - Front& Back left Door = 1 + 4
     */
    
    // Front Left Door
    if (value & 1) {
        self.Door_1.backgroundColor = [UIColor yellowColor];
        self.Door_1.text = @"Открыто";
        NSLog(@"1");
    }
    else {
        self.Door_1.backgroundColor = [UIColor lightGrayColor];
        self.Door_1.text = @"Закрыто";
    }
    
    // Front Right Door
    if (value & 2) {
        self.Door_2.backgroundColor = [UIColor yellowColor];
        self.Door_2.text = @"Открыто";
        NSLog(@"2");
    }
    else {
        self.Door_2.backgroundColor = [UIColor lightGrayColor];
        self.Door_2.text = @"Закрыто";
    }
    
    // Back Left Door
    if (value & 4) {
        self.Door_3.backgroundColor = [UIColor yellowColor];
        self.Door_3.text = @"Открыто";
        NSLog(@"4");
    }
    else {
        self.Door_3.backgroundColor = [UIColor lightGrayColor];
        self.Door_3.text = @"Закрыто";
    }
    
    // Back Right Door
    if (value & 8) {
        self.Door_4.backgroundColor = [UIColor yellowColor];
        self.Door_4.text = @"Открыто";
        NSLog(@"8");
    }
    else {
        self.Door_4.backgroundColor = [UIColor lightGrayColor];
        self.Door_4.text = @"Закрыто";
    }
}

BOOL firstStatusChange = YES;
BOOL lastStatus;

-(void) centralLockStatusChanged:(BOOL)status {
    // At first status changes set lastStatus variable
    if (firstStatusChange) {
        firstStatusChange = NO;
        // Invert status, to pass the next test
        lastStatus = !status;
    }
    
    // Change Lock image only if status changed
    if (!(lastStatus == status)) {
        // Check status
        if (status) {
            [self.CentralLock setBackgroundImage:[UIImage imageNamed:@"lock_close"] forState:UIControlStateNormal];
        }
        else {
            [self.CentralLock setBackgroundImage:[UIImage imageNamed:@"lock_open"] forState:UIControlStateNormal];
        }
        lastStatus = status;
    }
}


// Front Left Glass
- (IBAction)frontLeftUp:(UIButton *)sender {
    [self.commandConnection sendMessage:@"cansend can0 181#0200"];
}
- (IBAction)frontLeftDown:(id)sender {
    [self.commandConnection sendMessage:@"cansend can0 181#0800"];
}

// Front Right Glass
- (IBAction)frontRightUp:(UIButton *)sender {
    [self.commandConnection sendMessage:@"cansend can0 181#2000"];
}
- (IBAction)frontRightDown:(id)sender {
    [self.commandConnection sendMessage:@"cansend can0 181#8000"];
}

// Back Left Glass
- (IBAction)backLeftUp:(UIButton *)sender {
    [self.commandConnection sendMessage:@"cansend can0 181#0002"];
}
- (IBAction)backLeftDown:(id)sender {
    [self.commandConnection sendMessage:@"cansend can0 181#0008"];
}

// Back Right Glass
- (IBAction)backRightUp:(UIButton *)sender {
    [self.commandConnection sendMessage:@"cansend can0 181#0020"];
}
- (IBAction)backtRightDown:(id)sender {
    [self.commandConnection sendMessage:@"cansend can0 181#0080"];
}

- (IBAction)lockUnlock:(UIButton *)sender {
    // If central lock closed
    if (lastStatus) {
        // Open
        [self.commandConnection sendMessage:@"cansend can0 291#09AA020000"];

        int64_t delayInSeconds = 1; // 1 sec
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
            [self.commandConnection sendMessage:@"cansend can0 291#0900000000"];
        });
        
    }
    else {
        // Close
        [self.commandConnection sendMessage:@"cansend can0 291#0955040000"];
        int64_t delayInSeconds = 1; // 1 sec
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
            [self.commandConnection sendMessage:@"cansend can0 291#0900000000"];
        });
    }
    
}
@end

Есть способ не писать свое приложение для телефона, а воспользоваться готовым из мира умных домов, всего лишь потребуется установиться на Raspberry Pi систему автоматизации Z-Way [5] командой:

wget -q -O - razberry.z-wave.me/install | sudo bash

После этого добавляем наши CAN устройства в Z-Way систему автоматизации
Хакаем CAN шину авто для голосового управления - 10
И управляем стеклоподъемником как обычным выключателем
Хакаем CAN шину авто для голосового управления - 11
Мобильный приложения для Z-Way: ZWay Home Control и ZWay Control.

Голосовое управление с помощью Homekit и Siri

В одной из своих статей я описывал процесс установки Homebridge на Raspberry Pi для голосового управления домашней системой автоматизации Z-Way [6]. После установки Homebridge вы получите возмоность голосового управления с помощью Siri. Уверен, что для Android есть множество приложений позволяющих голосом отправлять HTTP запросы для управления Z-Way.
Видео голосовогу управления стеклоподъемником прилагаю.

Автор: aivs

Источник [7]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/raspberry-pi/208855

Ссылки в тексте:

[1] lnxpps.de/rpie: http://lnxpps.de/rpie/

[2] CANBoardRPi.lay: https://www.dropbox.com/s/4zdlzutwkavsll3/CANBoardRPi.lay?dl=0

[3] github.com/linux-can/can-utils: https://github.com/linux-can/can-utils

[4] открытую документацию на CAN шину VW: http://vwts.ru/diag/can_2_obmen_rus.pdf

[5] Z-Way: http://razberry.z-wave.me/

[6] процесс установки Homebridge на Raspberry Pi для голосового управления домашней системой автоматизации Z-Way: https://geektimes.ru/post/276706/

[7] Источник: https://geektimes.ru/post/282338/