窃听CAN总线自动进行语音控制



现代汽车不仅是交通工具,而且是具有多媒体功能的先进小工具以及用于单元和传感器的电子控制系统。许多汽车制造商通过电话提供运动辅助,停车辅助,汽车监视和控制功能。由于在所有系统都连接到的汽车中使用了CAN总线,因此有可能做到这一点:引擎,制动系统,方向盘,多媒体,气候等。

我的车是2011斯柯达明锐。它不提供电话控制功能,因此我决定修复此缺陷,同时添加语音控制功能。作为CAN总线和电话之间的网关,我将Raspberry Pi与CAN BUS防护罩和TP-Link WiFi路由器一起使用。自动聚合的通讯协议已关闭,大众汽车回复了我的所有来信以及协议文件。因此,找出设备如何在汽车中通信并学习如何管理它们的唯一方法是对大众总线的CAN协议进行反向工程。

我分阶段行动:

  1. 为Raspberry Pi开发CAN屏蔽
  2. 安装用于CAN总线的软件
  3. 连接到汽车的CAN总线
  4. 嗅探器的开发和CAN总线协议的研究
  5. 手机应用开发
  6. 使用Homekit和Siri进行语音控制

在视频语音控制窗口的末尾。

为Raspberry Pi开发CAN屏蔽


在这里,屏蔽方案是由lnxpps.de/rpie采取的,还对结论进行了描述,使用了2个微电路MCP2515和MCP2551与CAN进行通信。2线CAN-High和CAN-Low连接到屏蔽层。在SprintLayout 6中,我展开了电路板,任何人都可以方便地使用CANBoardRPi.lay(标题照片中为面包板上的屏蔽板原型)。





安装用于CAN总线的软件


在2岁的Raspbian上,我需要修补bcm2708.c以添加CAN支持(也许现在不需要)。要使用CAN总线,您需要从github.com/linux-can/can-utils安装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

我们使用ifconfig命令



验证CAN接口是否已经上升您可以通过发送和接收命令来验证一切正常。

在一个终端中,我们听:

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

在另一个终端,我们发送:

root@raspberrypi ~ # cansend can0 123#deadbeef

lnxpps.de/rpie中 描述了更详细的安装过程

连接到汽车的CAN总线


对VW CAN总线上开放文档进行了一些研究之后我发现我使用了2条总线。

功率单元的CAN总线以 500 kbit / s的速度传输数据,连接了服务于该单元的所有控制单元。

例如,以下设备可以连接到电源设备的CAN总线:

  • 发动机控制单元
  • ABS控制单元
  • 航向稳定控制单元,
  • 变速箱控制单元
  • 安全气囊控制单元
  • 组合仪表。

Comfort系统和信息命令系统的CAN总线,允许在为这些系统提供服务的控制单元之间以100 kbit / s的速度传输数据。

例如,
以下设备可以连接到Comfort系统的CAN总线和信息<command system

  • Climatronic控制单元或空调系统,
  • 车门中的控制单元
  • 舒适系统控制单元
  • 带收音机和导航系统显示器的控制单元。

可以访问第一个

总线,您可以控制交通(在我的机械手版本中,您至少可以控制巡航控制),可以访问第二个总线,您可以控制无线电,气候,中央门锁,电动车窗,前大灯等。两条总线均通过位于网关的网关连接在方向盘下方的区域中,诊断型OBD2连接器也连接到网关,很遗憾,无法通过OBD2连接器听到两条总线的通讯,您只能发送命令并请求状态。我决定只使用Comfort公交车,而驾驶员车门中的连接器竟然是连接公交车最方便的地方。



现在,我可以聆听Comfort CAN上发生的一切并发送命令。

嗅探器的开发和CAN总线协议的研究




访问了CAN总线后,我需要解密谁通过谁和什么。图中显示了CAN数据包格式。



can-utils集合中的所有实用程序本身都能够解析CAN数据包并仅提供有用的信息,即:

  • 编号
  • 资料长度
  • 资料

数据以未加密的形式传输,这有助于协议的研究。在Raspberry Pi上,我编写了一个小型服务器,该服务器将数据从candump重定向到TCP / IP,以解析计算机上的数据流并显示精美的数据。

对于macOS,我编写了一个简单的应用程序,为每个设备地址在平板电脑上添加了一个单元格,在该单元格中我已经可以看到正在更改的数据。



我按下电源窗口按钮,找到一个其中数据发生变化的单元格,然后确定哪些命令对应于按下,按下,按住,按住。

您可以通过从终端发送该命令来验证该命令是否有效,例如,将左玻璃抬高的命令:

cansend can0 181#0200

通过VAG汽车中的CAN总线传输设备的团队(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进行语音控制


在我的一篇文章中,我描述了在Raspberry Pi上安装Homebridge以便对Z-Way家庭自动化系统进行语音控制的过程安装Homebridge之后,您将能够使用Siri进行语音控制。我确信对于Android,有许多应用程序允许语音发送HTTP请求来控制Z-Way。

视频包含语音控制窗口

Source: https://habr.com/ru/post/zh-CN399043/


All Articles