前言
作为一个在互联网时代成长起来的人,怎么能忍受自己的爱车不支持远程控制呢。
连我的小电驴都支持手机靠近自动解锁,蓝牙/网络远程控车。而我的汽车却不支持,实在是说不过去,所以萌发了自己改造一下让它支持靠近自动解锁,同时能够远程控车。
基本情况及整体实现思路
基本需求
最初我想实现的功能只有一个:无需携带车钥匙,靠近车辆自动解锁,熄火后离开车辆自动上锁。
当我萌发出这个想法时,第一时间想到的就是魔改车钥匙来实现我想要的功能。
前期我在 V2 发了一个帖子,原车不支持手机解锁,但是支持钥匙感应解锁,有没有可能改装成支持手机解锁的?试图寻求有经验的大佬的灵感,但是收效甚微。后来灵机一动,上某宝搜索了一下,最终找到了大量和我想法一致的改装套件。这证明我的想法是可行的,并且已经有前人实践过了。
至于我为什么不直接在某宝买成品而是要自己实现,无非三个理由:
- 淘宝成品略贵,我认为不值这个价钱
- 想要实现更多的功能,而套件只提供了固定的一两个功能,无法自由扩展
- 想自己折腾
改装思路
确定了方案可行,下一步就是实际测试。
钥匙支持的功能
经过查阅资料,我的车钥匙支持如下功能:
- 靠近车辆后车辆自动解锁(需要拉主驾门把手激活)
- 远离车辆后车辆自动上锁
- 长按解锁按键可以一键打开车窗(汽车未启动时)
- 长按上锁键则反之
- 连续短按两次多功能按键开启寻车
- 短按上锁按键后长按多功能按键,远程启动车辆
- 长按多功能按键可退出远程启动
改装方案概述
我的改装思路是,将车钥匙主板拆出,重新焊接一块开发板,并由这块开发板接管控制车钥匙的电源和按键。
然后将改装后的开发板放入车内,平时车钥匙电源设置为断开状态,在接收到手机的指令或检测到手机信号强度到一定阈值后给钥匙供电并配合接通特定按键实现感应解锁。而锁车则反之。
供电方案选择
前期测试了我的车支持的取电接口:USB充电口、点烟器、后视镜预留取电接口、OBD取电、保险盒取电。
经过测试,USB口、点烟器、预留接口均不支持熄火后继续供电,故放弃使用。
仅剩下 OBD 和 保险盒 取电,但是保险盒取电过于麻烦,故放弃。
而 OBD 支持熄火持续供电,且只需要使用转接头即可直接转接成 USB 口,使用起来很方便。
故最终选择 OBD 供电作为供电方案。
材料准备与技术选择
确定了改装思路,接下来就是选择改装材料。
线材,焊接工具等周边材料不过多赘述。
主要是需要选定一块合适的开发板。
基于我的需求,这块开发板应该至少支持蓝牙、至少有四个可用的 IO 接口(分别用于控制车钥匙电源、车钥匙上锁键、车钥匙解锁键、车钥匙多功能按键)、价格应该尽可能的低。
最初我打算选择 Arduino NANO 配合蓝牙模块,但是这样成本过高,已经快接近某宝成品模块的价格了,后来在B大佬的推荐下,我购买了 ESP32C3 开发板,并且搭配 MicroPython 进行开发。ESP32C3的优势在于价格极其便宜,一块仅需9.9还包邮。(在我写这篇文章的时候,它已经涨价了)、同时支持蓝牙(BLE)和WiFi、整体体积很小。
但是,经过一段时间的熟悉和测试,我发现 ESP32C3 仅支持 BLE 不支持经典蓝牙,而且 MicroPython 对于 BLE 的支持也不完善,不支持配对设备,这就导致我无法验证设备真伪,因为现在智能手机的 MAC 都是随机的,除非你和手机完成配对,否则无法得到手机的真实 MAC。
因此我开始将目光转向经典蓝牙,后来还是在B大佬的推荐下,我选择了 ESP32 开发板,优势依然是便宜,20元多一点包邮,并且支持经典蓝牙和BLE双模,同时也支持WIFI。
但是这次我没有选择 MicroPython 作为开发平台,而是选择了 Arduino。理由也很简单,MicroPython 不支持 ESP32 的经典蓝牙……
可行性研究测试
硬件焊接
拆开车钥匙主板后,结构非常简单,一眼就能看出各个元器件的作用。
首先将主板的正负极供电分别焊接引出一条线,接下来把需要的按键也分别引出线就OK了。
有一点需要注意的是,由于按键焊点特别的小,非常不容易焊接,好在这块主板上贴心的打了非常多的测试用的触点,所以我直接把线焊在了检测点上。
此时出现了一个问题,按键应该焊接那两条线?
我首先想到的是白嫖某宝店家的成熟方案:
只不过我搜遍了某宝,几乎都是同样的焊接方式,但是他们都是四脚的按键,而我的是六脚按键啊,这能一样吗?
看来白嫖不成只能自己琢磨了,经过我用放大镜仔细研究,发现其实这个按键只接了两脚,那么问题就不大了,不用关心它的实现原理,只要无脑把这两个脚引出即可。因为不管几脚的开关,一般来说都是保持常开,按下按键后接通。
那么,我要如何实现模拟按键的接通与断开呢?
还是在B大佬的推荐下,我购买了 NMOS 管,但是买的时候没注意看参数,买的 MOS 管是贴片 MOS 管,非常的小,压根没法焊接……还好我买 MOS 管时为了以防万一,顺手买了两个继电器。既然 MOS 管无法使用,那就使用继电器吧。
反正核心原理就是要在接收到 ESP32 的信号后接通从按键引出的两条线。
焊接并排线完成后如图:
(原谅我第一次焊接,全焊的糊成一团了)
硬件连接方案
经过直接测试,上述焊接和排线都没有问题。
但是,当我把 ESP32 接上时却出问题了: ESP32 带不动这个继电器! 一旦启动继电器 ESP32 就直接过载重启……
接下来怎么办?尝试倒腾比车钥匙触点还小的 MOS 管?还是外接一个电源模块?
或许,可以直接放弃接通按键两条引脚的方案,改为直接给连接芯片一端的引脚高电平以模拟按下按键?
说干就干,快速写了一个 demo 后开始测试,结果发现并不行。
后来还是在B大佬的提醒下,我才知道原来这个车钥匙按键是默认接通高电平,给低电平才触发按键功能。
具体如图示:
上述为按键的接线图,实际上车钥匙只用了两个引脚,其中一个引脚直接引向电源负极,另外一个在串联电阻后引向芯片的某个引脚。
ps: 上面图画反了,电阻是串联在引向芯片这条线的。
按照这个方案,重新连接后如图:
确定了按键实际上是给低电平触发后,连接起来就简单多了,因为车钥匙电源和 ESP32 已经共地,所以现在只需要把按键引向芯片这个引脚连接上 ESP32 ,默认给高电平,需要触发时给低电平即可,另外一条线不需要连接任何东西,也不需要再外接继电器或 MOS 管。
对了,上图中的 LED 是用来确定车钥匙是否上电的,这个车钥匙太质朴了,没有任何提示(蜂鸣或者LED),我无法得知是否正常上电,所以我额外接了一个 LED 方便测试。
控制软件
控制软件直接使用的 Arduino 官方的蓝牙串口示例代码改的:
//This example code is in the Public Domain (or CC0 licensed, at your option.)
//By Richard Li - 2020
//
//This example creates a bridge between Serial and Classical Bluetooth (SPP with authentication)
//and also demonstrate that SerialBT have the same functionalities of a normal Serial
#include "BluetoothSerial.h"
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif
#if !defined(CONFIG_BT_SPP_ENABLED)
#error Serial Bluetooth not available or not enabled. It is only available for the ESP32 chip.
#endif
#define LED_CONNECT 2
#define LED_POWER 15
#define PIN_POWER 13
#define PIN_LOCK 12
#define PIN_LOOP 14
boolean isConnectDevice = false;
BluetoothSerial SerialBT;
boolean confirmRequestPending = true;
void BTConfirmRequestCallback(uint32_t numVal)
{
confirmRequestPending = true;
Serial.println(numVal);
}
void BTAuthCompleteCallback(boolean success)
{
confirmRequestPending = false;
if (success)
{
Serial.println("Pairing success!!");
}
else
{
Serial.println("Pairing failed, rejected by user!!");
}
}
// Bt_Status callback function
void Bt_Status (esp_spp_cb_event_t event, esp_spp_cb_param_t *param) {
if (event == ESP_SPP_SRV_OPEN_EVT) {
Serial.println ("Client Connected");
digitalWrite(LED_CONNECT, HIGH);
isConnectDevice = true;
confirmRequestPending = false;
// Do stuff if connected
}
else if (event == ESP_SPP_CLOSE_EVT ) {
Serial.println ("Client Disconnected");
digitalWrite(LED_CONNECT, LOW);
isConnectDevice = false;
// Do stuff if not connected
}
}
void get_start() {
digitalWrite(LED_POWER, HIGH);
digitalWrite(PIN_POWER, HIGH);
}
void cut_power() {
digitalWrite(LED_POWER, LOW);
digitalWrite(PIN_POWER, LOW);
}
void lock_car() {
digitalWrite(PIN_LOCK, LOW);
delay(500);
digitalWrite(PIN_LOCK, HIGH);
delay(10000);
digitalWrite(LED_POWER, LOW);
digitalWrite(PIN_POWER, LOW);
}
void luncher_car() {
digitalWrite(PIN_LOCK, LOW);
delay(300);
digitalWrite(PIN_LOCK, HIGH);
delay(1000);
digitalWrite(PIN_LOOP, LOW);
delay(8000);
digitalWrite(PIN_LOOP, HIGH);
}
void shut_down_car() {
digitalWrite(PIN_LOOP, LOW);
delay(8000);
digitalWrite(PIN_LOOP, HIGH);
}
void find_my_car() {
digitalWrite(PIN_LOOP, LOW);
delay(200);
digitalWrite(PIN_LOOP, HIGH);
delay(300);
digitalWrite(PIN_LOOP, LOW);
delay(200);
digitalWrite(PIN_LOOP, HIGH);
}
void click_loop() {
digitalWrite(PIN_LOOP, LOW);
delay(200);
digitalWrite(PIN_LOOP, HIGH);
}
void click_lock() {
digitalWrite(PIN_LOCK, LOW);
delay(200);
digitalWrite(PIN_LOCK, HIGH);
}
void read_status() {
int power_value = digitalRead(PIN_POWER);
int loop_value = digitalRead(PIN_LOOP);
int lock_value = digitalRead(PIN_LOCK);
char s[200];
sprintf(s, "power %d, loop %d, lock %da\n", power_value, loop_value, lock_value);
SerialBT.print(s);
}
void setup()
{
Serial.begin(115200);
pinMode(LED_CONNECT, OUTPUT);
pinMode(LED_POWER, OUTPUT);
pinMode(PIN_POWER, OUTPUT);
pinMode(PIN_LOCK, OUTPUT);
pinMode(PIN_LOOP, OUTPUT);
digitalWrite(PIN_LOCK, HIGH);
digitalWrite(PIN_LOOP, HIGH);
SerialBT.enableSSP();
SerialBT.onConfirmRequest(BTConfirmRequestCallback);
SerialBT.onAuthComplete(BTAuthCompleteCallback);
// Define the Bt_Status callback
SerialBT.register_callback (Bt_Status);
SerialBT.begin("equationl's Auto"); //Bluetooth device name
Serial.println("The device started, now you can pair it with bluetooth!");
}
void loop()
{
if (confirmRequestPending)
{
if (Serial.available())
{
int dat = Serial.read();
if (dat == '1')
{
SerialBT.confirmReply(true);
}
else
{
SerialBT.confirmReply(false);
}
}
}
else
{
if (Serial.available())
{
SerialBT.write(Serial.read());
}
if (SerialBT.available())
{
int msg = SerialBT.read();
Serial.write(msg);
if (msg == '1') {
get_start();
}
if (msg == '2') {
cut_power();
}
if (msg == '3') {
lock_car();
}
if (msg == '4') {
luncher_car();
}
if (msg == '5') {
shut_down_car();
}
if (msg == '6') {
find_my_car();
}
if (msg == '7') {
click_loop();
}
if (msg == '8') {
click_lock();
}
if (msg == 'r') {
read_status();
}
}
delay(5);
}
}
代码很简单,我就不一一介绍了,大概说一下这段代码实现的功能:
- 接受其他设备的配对请求,并且配对需要校验避免任何人都能连接(现在就是简单的接收到串口发送的 ‘1’ 即视为校验通过,因为严格意义上来说,除了我自己,没有人能够连接 ESP32 的串口,所以这样就足够安全了,只是这样有个问题,无法方便的配对新设备)
- 接收已配对的设备连接后发送的指令,分别为:
指令 | 动作 | 说明 |
---|---|---|
1 | 给车钥匙上电 | 需要上电才能继续下面的其他操作 |
2 | 断开车钥匙供电 | 无 |
3 | 上锁 | 触发上锁按键后,延迟 10s 断开车钥匙供电 |
4 | 远程点火 | 模拟触发短按上锁按键后长按多功能键 |
5 | 远程熄火 | 模拟触发长按多功能按键 |
6 | 寻车 | 模拟双击多功能按键 |
7 | 触发多功能按键 | 无 |
8 | 触发上锁按键 | 无 |
r | 返回各个 IO 口状态 | 通过蓝牙串口返回当前 IO 口状态 |
最终效果
最终实现效果可以看我在 B站 的这个视频: 【改装车钥匙实现手机控制演示-哔哩哔哩】 (视频没拍好,可能需要戴上耳机才能听见发动机的声音)
视频里面只演示了通过手机控制远程点火这一个功能,但是实际上表中的功能经过测试都是可用的。
缺陷与后续
缺陷
因为这次只是为了验证手机控制的可行性,所以没有做太多功能,只做了手动触发按键测试是否可行。
另外,在测试中我发现了两点比较致命的问题:
- 钥匙在车内时无法使用感应解锁功能,必须触发解锁按钮才能解锁车辆
- 钥匙在车内时无法触发远程点火功能(上述演示视频拍摄时,我把测试模块放在了车外面,没放车内)
对于问题 1 ,可以通过触发解锁按键来替代,问题不大。
对于问题 2,为了安全着想,也为了方便供电,模块必须放置在车内,所以这个问题暂时无解,可能需要放弃;或者也可以想办法把车钥匙的天线延长到车外以欺骗车辆的钥匙位置感知功能。
后期计划
1.感应解锁
添加手机靠近车辆自动解锁,我的想法是可以利用蓝牙的 RSSI 感知手机与模块的距离,达到一定阈值后自动解锁。同时手机离开模块后自动上锁。
2.手动连接
当前已实现的手动连接模块触发相应功能也予以保留,不过应该在这个基础上增加可以设置一些参数,例如感应距离,按键触发时间,解锁时是否同步点火或打开窗户(点火后车辆会自动开启空调,可以在夏天自动提前降温,开窗同理)。
3.优化配对
应该优化一下配对新的手机,在保证安全的情况下尽可能简化配对新手机的流程、
4.真·远程控制
后期可以添加一个4G模块实现真正的远程控制,不过现在这个功能暂时想不到有实现它的必要,除了夏天在出门前先在家里远程点火给车辆降温外,似乎确实没有其他的优势。
原文发布于我的博客:likehide.com