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

от автора


Современный автомобиль это не только средство передвижение, но и продвинутый гаджет с мультимедийными функциями и электронной системой управления агрегатами и кучей датчиков. Многие автопроизводители предлагают функции ассистентов движения, помощников при парковке, мониторинга и управления авто с телефона. Это возможно благодаря использованию в авто 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, там же и описание выводов, для общения с CAN используются 2 микросхемы MCP2515 и MCP2551. К шилду подключаются 2 провода CAN-High и CAN-Low. В SprintLayout 6 развел плату, может кому пригодится CANBoardRPi.lay (на заглавном фото прототип шилда на макетке).

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

На Raspbian 2-x годичной давность мне потребовалось пропатчить bcm2708.c, чтобы добавить поддержку CAN (возможно сейчас это не требуется). Для работы с CAN шиной нужно установить пакет утилит can-utils с github.com/linux-can/can-utils, после этого подгрузить модули и поднять 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

Проверить, что все работает можно отправив команду и получив ее.
В одном терминале слушаем:

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

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

root@raspberrypi ~ # cansend can0 123#deadbeef 

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

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

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

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

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

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

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

Теперь я могу слушать, все что происходит в CAN шине «Комфорт» и отправлять команды.

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


После того как я получил доступ к прослушиванию CAN шины, мне нужно расшифровать кто кому и что передает. Формат пакета CAN показан на рисунке.

Все утилиты из набора can-utils сами умеют разбирать CAN пакеты и отдают только полезную информацию, а именно:

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

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

Нажимаю кнопку стеклоподъемника я нашел ячейку в которой меняются данные, затем я и определил какие команды соответствуют нажатию вниз, нажатию вверх, удержанию вверх, удержанию вниз.
Проверить, что команда работает, можно отправив из терминала, например команду поднять левое стекло вверх:

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.

Исходники приложения управления авто для 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 командой:

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

После этого добавляем наши CAN устройства в Z-Way систему автоматизации

И управляем стеклоподъемником как обычным выключателем

Мобильный приложения для Z-Way: ZWay Home Control и ZWay Control.

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

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

ссылка на оригинал статьи https://geektimes.ru/post/282338/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *