打印
[应用相关]

手机蓝牙控制开关

[复制链接]
5502|63
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
是你的乱码|  楼主 | 2023-6-29 14:15 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
先讲一下整体思路哈!手机肯定不能直接控制台灯的,需要一个中间物来协调,在这里我用的是51单片机(如果大家不知道也没关系,下面我还会说的)。接下来就是具体怎么控制的,其实原理挺简单的。1.手机通过蓝牙来与单片机通信,因而单片机需要外接一个蓝牙模块(我用的是hc-05 主从一体 蓝牙模块)。大家千万不要被外接给吓到了,外接模块一点都不难的,就只要去淘宝卖相应的模块然后用杜邦线(不知道的可以把它当做导线来理解)和51单片机连起来就好了。到这里手机已经可以和51单片机通信了,也就是说手机可以给单片机发送“开灯”和“关灯”的消息了。2.接下来就要解决当51单片机接收到”开灯“和”关灯“的消息后,该怎么控制台灯实际的开关?这个时候我们就需要一个继电器(也是单片机的外接模块),关于继电器我们可以看下面的图片。继电器一共有三个输出端口(常开端 公共端 常闭端)。事先申明,我们可以通过单片机控制继电器的公共端是和常开端

连通,还是和常闭端连通。现在我们只需要剪断台灯的一根电线,将电线的两头分别和继电器的常开端和公共端连接起来即可。通过单片机控制继电器的公共端和常开端连接时台灯打开,反之台灯关闭。大致原理就是这样,我们来梳理一下整个流程。首先手机通过蓝牙和单片机的蓝牙模块建立通信,当手机发送一个打开台灯信号时,单片机收到相应的信号并控制继电器的公共端指向常开端,台灯亮起。

next~就是具体实现了。一共分为两大部分,分别是Android端和单片机端,先从Android端开始说起。

使用特权

评论回复
沙发
是你的乱码|  楼主 | 2023-6-29 14:16 | 只看该作者
.Android端:

Android端其实就是一个简单的蓝牙通信,Android端只需要通过蓝牙向单片机的蓝牙模块发送开关对应的消息即可(我是用0xff表示打开台灯,0x00关闭台灯)。先来看看工程的总体结构以及软件界面。

其中BluetoothTool类是一个蓝牙工具类,里面有关于蓝牙的连接以及发送接收数据功能。IUpdateUI是一个接口,用来在 BlurtoothTool中更新主界面的设备列表以及log。MainAty就是主界面的Activity。activity_main.xml和layout_lv_devices_item.xml不用多说了吧,就是一些界面有关的。关于Android端的解析以贴代码为主,因为代码中我都有详细的解释,比较重要的我会在博客中用文字再次解释的。

1.MainAty:

获取蓝牙适配器,可以通过蓝牙适配器获取蓝牙设备的信息。

/**

获得默认的蓝牙适配器
*/
private BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
如果手机没打开蓝牙,则界面跳转到打开蓝牙界面。

@Override
protected void onStart() {
super.onStart();

/** 判断蓝牙是否可用,不可用时请求打开*/
if (mBluetoothAdapter != null && !mBluetoothAdapter.isEnabled()) {
    Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivity(intent);
}

使用特权

评论回复
板凳
是你的乱码|  楼主 | 2023-6-29 14:16 | 只看该作者
通过蓝牙适配器获取之前匹配过的蓝牙设备信息(如单片机的蓝牙设备),所以第一次使用的时候,先用手机自带的蓝牙匹配成功一次哈!蓝牙设备中一个比较重要的信息就是设备地址-如98:D3:33:80:83:05就是一个蓝牙设备地址,唯一标示。

/** 获取以前匹配过的蓝牙设备*/
Set devices = null;
if (mBluetoothAdapter != null)
devices = mBluetoothAdapter.getBondedDevices();
else
Toast.makeText(MainAty.this, "该设备不支持蓝牙功能 ", Toast.LENGTH_SHORT).show();

使用特权

评论回复
地板
是你的乱码|  楼主 | 2023-6-29 14:16 | 只看该作者
    if (devices != null && devices.size() > 0) {
        data.clear();
        for (BluetoothDevice device : devices) {
            HashMap<String, Object> map = new HashMap<>();
            map.put("lv_left_icon", R.drawable.lv_left_icon);
            map.put("lv_address", device.getAddress());
            map.put("lv_right_icon", R.drawable.lv_right_white);
            data.add(map);
        }
    } else {
        HashMap<String, Object> map = new HashMap<>();
        map.put("lv_left_icon", R.drawable.lv_left_icon);
        map.put("lv_address", "没有已经匹配的设备");
        map.put("lv_right_icon", R.drawable.lv_right_white);
        data.add(map);
        mTextView.append("没有已经匹配的设备" + "\r\n");
    }

    simpleAdapter.notifyDataSetChanged();

使用特权

评论回复
5
是你的乱码|  楼主 | 2023-6-29 14:16 | 只看该作者
连接指定的蓝牙:通过调用BluetoothTool连接蓝牙,我们传入了设备的地址"(String) data.get(0).get(“lv_address”)"以及连接类型 BluetoothTool.ServiceOrClient.CLIENT(这里我们是以客户端的形式连接,也就是单片机的蓝牙当作客户端 )。之后设置了BluetoothTool的更新UI接口,并在MainAty中具体实现。
builder.setPositiveButton(“连接”, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
mBluetoothTool = new BluetoothTool((String) data.get(0).get(“lv_address”),
BluetoothTool.ServiceOrClient.CLIENT);
mBluetoothTool.SetOnIUpdateUI(new IUpdateUI() {
@Override
public void updateListViewDevices() {
for (int i = 0; i < data.size(); i++) {
if (i == index) {
data.get(i).put(“lv_right_icon”, R.drawable.checked);
continue;
}
data.get(i).put(“lv_right_icon”, R.drawable.lv_right_white);
}/**

使用特权

评论回复
6
是你的乱码|  楼主 | 2023-6-29 14:16 | 只看该作者
            simpleAdapter.notifyDataSetChanged();
        }

        @Override
        public void updateLog(String msg) {
            mTextView.append("\r\n" + msg);
        }
    });

}

使用特权

评论回复
7
是你的乱码|  楼主 | 2023-6-29 14:17 | 只看该作者
});

可以看到打开台灯按钮的点击事件,只是调用了BluetoothTool的发送功能向单片机蓝牙发送了一个ff消息(具体发送时将ff转化成16进制0xff,因而在单片机端我们会收到一个0xff的数据)。关闭事件同上。

@Override
public void onClick(View view) {
int id = view.getId();0

使用特权

评论回复
8
是你的乱码|  楼主 | 2023-6-29 14:17 | 只看该作者
switch (id) {
    case R.id.id_btn_open:
        if (mBluetoothTool != null) {
            mBluetoothTool.sendData("ff");
        } else
            Toast.makeText(MainAty.this, "蓝牙未连接...", Toast.LENGTH_SHORT).show();
        break;
    case R.id.id_btn_close:
        if (mBluetoothTool != null) {
            mBluetoothTool.sendData("00");
        } else
            Toast.makeText(MainAty.this, "蓝牙未连接...", Toast.LENGTH_SHORT).show();
        break;
    default:
        break;
}

使用特权

评论回复
9
是你的乱码|  楼主 | 2023-6-29 14:17 | 只看该作者
2.BluetoothTool

传入的蓝牙设备地址(一般是单片机端蓝牙的地址)
/**

蓝牙设备地址
*/
private String mBluetoothAddress = null;
通过传入的蓝牙地址获取相应的蓝牙设备。

/**

蓝牙设备
*/
private BluetoothDevice mDevice = null;
mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(mBluetoothAddress);

这里我们都是以客户端的形式连接的。(也就是单片机上的蓝牙是客户端)。

/**

枚举 表示是客户端还是服务端
*/
public static enum ServiceOrClient {
NONE, SERVICE, CLIENT
}
private ServiceOrClient mServiceOrClient = ServiceOrClient.NONE;
Handler 用来更新UI

private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UPDATE_LISTVIEW:
if (iUpdateUI != null)
iUpdateUI.updateListViewDevices();
break;
case MSG_UPDATE_LOG:
if(iUpdateUI!=null)
iUpdateUI.updateLog(msg.obj + “”);
break;
}
}
};

单片机的蓝牙与手机端的蓝牙通信的socket,说明一下蓝牙通信其实也是基于socket通信的。

/**

蓝牙客户端socket
*/
private BluetoothSocket mClientSocket = null;
以客户端身份连接的线程,我们来看看具体实现。

/**-

使用特权

评论回复
10
是你的乱码|  楼主 | 2023-6-29 14:17 | 只看该作者
客户端线程
*/
private ClientThread mClientThread = null;
通过蓝牙设备获取相应的socket,之后单片机的蓝牙和手机的蓝牙通信都是通过这个socket。其中在蓝牙中,每个服务和服务属性都唯一地由"全球唯一标识符" (UUID)来校验。而且这个UUID的值必须是00001101-0000-1000-8000-00805F9B34FB,这个是android的API上面说明的,用于普通蓝牙适配器和android手机蓝牙模块连接的。获取之后通过socket的connect进行连接,连接成功之后开启读取数据的线程。

/**

使用特权

评论回复
11
是你的乱码|  楼主 | 2023-6-29 14:18 | 只看该作者
客户端线程
/
private class ClientThread extends Thread {
@Override
public void run() {
super.run();
try {
/* 客户端通过服务端的UUID与之连接*/
mClientSocket = mDevice.createRfcommSocketToServiceRecord(
UUID.fromString(“00001101-0000-1000-8000-00805F9B34FB”));0

使用特权

评论回复
12
是你的乱码|  楼主 | 2023-6-29 14:18 | 只看该作者
     Message msg = Message.obtain(null, MSG_UPDATE_LOG);
     msg.obj = "正在连接。。。";
     mHandler.sendMessage(msg);

     /** 连接*/
     mClientSocket.connect();


     msg = Message.obtain(null, MSG_UPDATE_LOG);
     msg.obj = "连接成功";
     mHandler.sendMessage(msg);


     msg = Message.obtain(null, MSG_UPDATE_LISTVIEW);
     mHandler.sendMessage(msg);

     /** 接收数据*/
     mReadThread = new ReadThread();
     mReadThread.start();

} catch (IOException e) {
     e.printStackTrace();

     Message msg = Message.obtain(null, MSG_UPDATE_LOG);
     msg.obj = "连接失败";
     mHandler.sendMessage(msg);
}

使用特权

评论回复
13
是你的乱码|  楼主 | 2023-6-29 14:18 | 只看该作者
可以看到线程一直在查看有没有数据,如果有的话就接受,并根据接收到的数据进行相应的显示。有一点要先说一下,就是如果手机成功发送了一个开灯命令给单片机,单片机收到之后成功控制继电器将台灯打开之后,单片机会回发一个消息0xff给手机。因此手机端只要收到0xff这个消息,就知道台灯打开成功了手机就可以显示台灯成功开启。0

使用特权

评论回复
14
是你的乱码|  楼主 | 2023-6-29 14:18 | 只看该作者
读取数据线程
*/
private class ReadThread extends Thread {
@Override
public void run() {
super.run();

使用特权

评论回复
15
是你的乱码|  楼主 | 2023-6-29 14:18 | 只看该作者
 byte[] buffer = new byte[1024];
int bytes;
InputStream in = null;

try {
     in = mClientSocket.getInputStream();

     while (true) {
         if ((bytes = in.read(buffer)) > 0) {
             byte[] buf_data = new byte[bytes];
             for (int i = 0; i < bytes; i++) {
                 buf_data[i] = buffer[i];


                 int j = buffer[i];
                 j = buffer[i] & 0xff;
                 String str = Integer.toHexString(j);
                 if ("ff".equals(str)) {
                     Message msg = Message.obtain(null, MSG_UPDATE_LOG);
                     msg.obj = "台灯打开";
                     mHandler.sendMessage(msg);
                 } else if ("0".equals(str)) {//注意不能用00,因为0x00实际的值是0

                     Message msg = Message.obtain(null, MSG_UPDATE_LOG);
                     msg.obj = "台灯关闭";
                     mHandler.sendMessage(msg);
                 } else {
                    Message msg = Message.obtain(null, MSG_UPDATE_LOG);
                     msg.obj = "err...";
                     mHandler.sendMessage(msg);
                 }

             }
         }
     }

} catch (IOException e) {
     e.printStackTrace();

     Message msg = Message.obtain(null, MSG_UPDATE_LOG);
     msg.obj = "连接数据失败";
     mHandler.sendMessage(msg);
} finally {
     if (in != null)
         try {
             in.close();
         } catch (IOException e) {
             e.printStackTrace();
         }
}

使用特权

评论回复
16
是你的乱码|  楼主 | 2023-6-29 14:20 | 只看该作者
Message msg = Message.obtain(null, MSG_UPDATE_LOG);
msg.obj = "没有连接";
mHandler.sendMessage(msg);
return;

使用特权

评论回复
17
是你的乱码|  楼主 | 2023-6-29 14:21 | 只看该作者
}

OutputStream out = null;
try {
out = mClientSocket.getOutputStream();
out.write(getHexBytes(str));
} catch (IOException e) {
e.printStackTrace();
Message msg = Message.obtain(null, MSG_UPDATE_LOG);
msg.obj = “发送失败”;
mHandler.sendMessage(msg);

}
}

使用特权

评论回复
18
是你的乱码|  楼主 | 2023-6-29 14:21 | 只看该作者
连接蓝牙,就是启动客户端连接线程。

/**

连接蓝牙
*/
private void connect() {
if (mIsConnected == true) {
Log.d(TAG, “connect: 已经连接”);
Message msg = Message.obtain(null, MSG_UPDATE_LOG);
msg.obj = “已经连接”;
mHandler.sendMessage(msg);
return;
}

/** 客户端*/
if (mServiceOrClient == ServiceOrClient.CLIENT) {
if (!mBluetoothAddress.equals(“null”)) {

使用特权

评论回复
19
是你的乱码|  楼主 | 2023-6-29 14:21 | 只看该作者
     mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(mBluetoothAddress);

     mClientThread = new ClientThread();
     mClientThread.start();
     mIsConnected = true;
} else Log.d(TAG, "connect: address is null");

使用特权

评论回复
20
是你的乱码|  楼主 | 2023-6-29 14:21 | 只看该作者
Android端项目github地址:https://github.com/973927190/TableLamp

二.单片机端:

说具体实现之前,先要说一下用到的设备。

1.51单片机开发板 附上博主买的地址

https://s.click.taobao.com/t?e=m%3D2%26s%3Dttqsp5ERcDocQipKwQzePOeEDrYVVa64LKpWJ%2Bin0XLjf2vlNIV67n7RkSGOZLtKUQTSx8a5hQdMVPdbt3fgwWT400sgLOAr5yw7SOOxWj33gRJpVZU6CBpTelgOEeV6PeywkvrCtTjvvifwQ9Sl%2F32uxjQQGCZf9OSfRI2eAoOkOrGae4DS5oO2CiNcVz0KWFOauMHXHk3x3bTUdjTneMYOae24fhW0&scm=null&pvid=null&app_pvid=59590_11.9.33.73_542_1580443205037&ptl=floorId%3A17741&originalFloorId%3A17741&app_pvid%3A59590_11.9.33.73_542_1580443205037&union_lens=lensId%3APUB%401580443132%400b0b27cd_912a_16ff9c062d1_a31f%40021pTa5OJs6SH0FIuoJ20hvK
2.杜邦线(公对母,母对母,公对公) 附上博主买的地址

https://s.click.taobao.com/t?e=m%3D2%26s%3DePhW7ZvPQm8cQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67uEKCHZPv3GYxT40F5yyh61MVPdbt3fgwWT400sgLOAr5yw7SOOxWj33gRJpVZU6CBJv5lTqaGlYwI0svZcyUg2UIG1bHEE7hAlE7kG%2FgUXMSUm0FQ9tsQ8hhQs2DjqgEA%3D%3D&pvid=10_183.39.58.9_2309_1558880456607

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

30

主题

369

帖子

1

粉丝