前言
我喜欢钓鱼,喜欢野钓。但是因为资源问题,我总是空军。于是我开始研究天气、河流、钓点。但是研究来研究去,总还是空军,我不服,但是我也无可奈何,我不知道到底是因为我钓鱼的技术菜,还是根本就没有鱼。
直到某一天,我在网上发现了一些种工具,一种通过声纳去绘制水底的情景,还有一种直接是一个水下摄像头,我就在想为什么没有一种玩具遥控潜水艇可以远程控制,并且携带水下摄像头,可以自由的查看水底的情况呢?
于是,就有了这个小项目
事先说明:
- 我是一个彩笔,大佬勿喷,当成笑话看完拉到
- 很多代码跑不通,但是思路我都尽量去写了一写
控制核心选用esp32 的devkitu1,通信方式暂时选用板子上的蓝牙,控制端用Android程序
一开始,我想的是用板子的低功率蓝牙BLE来进行控制,然后就从GitHub上扒了一个这个板子的程序下来。
uint8_t txValue = 0;
BLEServer *pServer = NULL; //BLEServer指针 pServer
BLECharacteristic *pTxCharacteristic = NULL; //BLECharacteristic指针 pTxCharacteristic
bool deviceConnected = false; //本次连接状态
bool oldDeviceConnected = false; //上次连接状态
// See the following for generating UUIDs: https://www.uuidgenerator.net/
#define SERVICE_UUID "12a59900-17cc-11ec-9621-0242ac130002" // UART service UUID
#define CHARACTERISTIC_UUID_RX "12a59e0a-17cc-11ec-9621-0242ac130002"
#define CHARACTERISTIC_UUID_TX "12a5a148-17cc-11ec-9621-0242ac130002"
class MyServerCallbacks : public BLEServerCallbacks
{
void onConnect(BLEServer *pServer)
{
deviceConnected = true;
};
void onDisconnect(BLEServer *pServer)
{
deviceConnected = false;
}
};
class MyCallbacks : public BLECharacteristicCallbacks
{
void onWrite(BLECharacteristic *pCharacteristic)
{
std::string rxValue = pCharacteristic->getValue(); //接收信息
String s = "";
if (rxValue.length() > 0)
{ //向串口输出收到的值
for (int i = 0; i < rxValue.length(); i++)
{
s += rxValue[i];
}
};
void setup()
{
Serial.begin(115200);
// 创建一个 BLE 设备
BLEDevice::init("UART_BLE");
// 创建一个 BLE 服务
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks()); //设置回调
BLEService *pService = pServer->createService(SERVICE_UUID);
// 创建一个 BLE 特征
pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);
pTxCharacteristic->addDescriptor(new BLE2902());
BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
pRxCharacteristic->setCallbacks(new MyCallbacks()); //设置回调
pService->start(); // 开始服务
pServer->getAdvertising()->start(); // 开始广播
Serial.println("wait for client to connect...");
}
void loop()
{
// deviceConnected 已连接
if (deviceConnected)
{
pTxCharacteristic->setValue(&txValue, 1); // 设置要发送的值为1
pTxCharacteristic->notify(); // 广播
txValue++; // 指针地址自加1
delay(2000); // 如果有太多包要发送,蓝牙会堵塞
}
// disconnecting 断开连接
if (!deviceConnected && oldDeviceConnected)
{
delay(500); // 留时间给蓝牙缓冲
pServer->startAdvertising(); // 重新广播
Serial.println(" 开始广播 ");
oldDeviceConnected = deviceConnected;
}
// connecting 正在连接
if (deviceConnected && !oldDeviceConnected)
{
// do stuff here on connecting
oldDeviceConnected = deviceConnected;
}
}
于是接下来,我就开始准备安卓程序了。安卓上的蓝牙开发其实说难也不难,主要流程就是先通过搜索,获取附近的蓝牙设备及其对应的信息。那么,要搜索蓝牙设备,第一步就需要想系统申请相关的许可,需要在AndroidManifest.xml文件中,加入以下代码以申请权限,没有深入研究,就把需要的权限都加了。(但是测试加了权限之后还是需要手动在系统应用权限里面给程序的定位权限给与一下)
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
接下来,通过btAdapter去开启搜索,蓝牙搜索的结果需要通过广播来获取(虽然不明白,但是大概意思就是系统蓝牙搜索到的内容会广播出来,不是直接通过某个函数返回值直接给你),那么就创建一个广播
btAdapter.startDiscovery()
// 注册广播接收器来接收蓝牙搜索的结果
IntentFilter intent = new IntentFilter();
intent.addAction(BluetoothDevice.ACTION_FOUND);
intent.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
intent.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(searchDevices, intent);
private final BroadcastReceiver searchDevices = new BroadcastReceiver() {
@Override
// 收到广播就会调用下面的函数
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// 去判断这个action来确认是搜索到新设备还是什么其他状况
switch (action){
// 发现新设备就添加到listview里面去
case BluetoothDevice.ACTION_FOUND:
BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Device device = new Device(btDevice.getName(), btDevice.getAddress());
if (btDevice.getName() == null){
device.setName("未知设备");
}
// 避免重复
if (!list.contains(device)){
list.add(device);
adapter.notifyDataSetChanged();
}
break;
case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
Toast.makeText(getBaseContext(), "正在扫描", Toast.LENGTH_SHORT).show();
// 扫描过程中,如果再次点击触发搜索操作会直接闪退,提示是空指针,暂时不清楚原理,所以直接让扫描过程
// 按键不可操作,并修改文字
btn1.setText("正在扫描");
btn1.setEnabled(false);
break;
case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
// 扫描完成后,放出按键的操作权,并将按键上的文字进行修改
if (btn1.getText().equals("正在扫描")){
btn1.setText("再次扫描");
btn1.setEnabled(true);
Toast.makeText(getBaseContext(), "扫描完成", Toast.LENGTH_SHORT).show();
}
break;
default:
Toast.makeText(getBaseContext(), "出错了!", Toast.LENGTH_SHORT).show();
}
}
};
现在,我已经通过蓝牙搜索,获取到了周围蓝牙设备的信息,接下来就是连接蓝牙设备.连接的过程其实也很简单,BLE蓝牙主要就是通过Gatt连接,然后通过修改其中的Characteristics的value来完成数据的传输
// 新建一个BluetoothGattCallback的子类,然后重写需要用的方法,
// 因为我暂定的只是手机通过app给esp32发信息,所以暂时就不考虑接收
package com.example.myapplication;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
public class GattCallback extends BluetoothGattCallback {
@Override
// 状态改变时会调用该方法
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
// 如果状态ok,那么就开始搜索service,如果失败就直接close
if(status == BluetoothGatt){
gatt.discoverServices();
}else{
gatt.close();
}
}
@Override
// 当发现service时会调用这个
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
// 通过我们之前再esp32上设置的uuid获取到service
BluetoothGattService service = gatt.getService(s_uuid);
// 同样的通过uuid获取到characteristic
BluetoothGattCharacteristic characteristic = service.getCharacteristic(r_uuid);
// 设置characteristic的值,然后通过gatt去write一下,信息就发送过去了
characteristic.setValue("发送的信息");
gatt.writeCharacteristic(characteristic);
}
}
好了, 能连接了,但是问题又来了,数据是发出去了,但是呢一个characteristic只能写一点点数据.而且,如果要清除呢还必须要使用gatt.close()方法,再次连接又可能会需要一点时间.本来我是想着多线程,这边还在写,发数据,背后就开始创建新的连接,毕竟可以连接多个,但是想想还是不得劲,于是决定换方案.
esp32上是有正儿八经的蓝牙连接的,也不差那点功耗,直接上吧:
#include <Arduino.h>
#include <BluetoothSerial.h>
BluetoothSerial SerialBT;
void setup()
{
Serial.begin(115200);
SerialBT.begin("my bluetooth"); // 如果没有参数传入则默认是蓝牙名称是: "ESP32"
SerialBT.setPin("1234"); // 蓝牙连接的配对码
Serial.printf("BT initial ok and ready to pair. \r\n");
}
void loop()
{
if (SerialBT.available())
{
Serial.wirte(BTSerial.read());
}
delay(1);
}
好家伙,这代码量可真是够少,看代码的时候我也是懵的,试了几次连不上,我甚至有些怀疑不是我错了,是这代码的原作者错了!(我之前没有写过蓝牙方面的代码,这段代码是参考别的大佬的)
这里的UUID都是同一的嗷,都是这,直接创建一个socket,然后socket.connect(),就直接脸上了,太神奇了!
try {
UUID tmp = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
try {
socket = device.createInsecureRfcommSocketToServiceRecord(tmp);
if (socket != null){
socket.connect();
btn1.setEnabled(false);
btn1.setText("已连接:" + device.getName());
}
} catch (IOException e) {
Toast.makeText(getBaseContext(), "连接失败", Toast.LENGTH_SHORT).show();
btn1.setEnabled(true);
btn1.setText("再次搜索");
}
// 连接上之后,发送数据也很简单.
// 首先通过socket获取OutPutStream
// 然后你就write就完了
try {
OutputStream outputStream = socket.getOutputStream();
outputStream.write("gas.".getBytes());
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
至此,我觉得我的软件部分已经打通,接下来就是需要去搞点建模什么的…
诶!我不会,我先去慢慢学.