先讲一下整体思路哈!手机肯定不能直接控制台灯的,需要一个中间物来协调,在这里我用的是51单片机(如果大家不知道也没关系,下面我还会说的)。接下来就是具体怎么控制的,其实原理挺简单的。1.手机通过蓝牙来与单片机通信,因而单片机需要外接一个蓝牙模块(我用的是hc-05 主从一体 蓝牙模块)。大家千万不要被外接给吓到了,外接模块一点都不难的,就只要去淘宝卖相应的模块然后用杜邦线(不知道的可以把它当做导线来理解)和51单片机连起来就好了。到这里手机已经可以和51单片机通信了,也就是说手机可以给单片机发送“开灯”和“关灯”的消息了。2.接下来就要解决当51单片机接收到”开灯“和”关灯“的消息后,该怎么控制台灯实际的开关?这个时候我们就需要一个继电器(也是单片机的外接模块),关于继电器我们可以看下面的图片。继电器一共有三个输出端口(常开端 公共端 常闭端)。事先申明,我们可以通过单片机控制继电器的公共端是和常开端
连通,还是和常闭端连通。现在我们只需要剪断台灯的一根电线,将电线的两头分别和继电器的常开端和公共端连接起来即可。通过单片机控制继电器的公共端和常开端连接时台灯打开,反之台灯关闭。大致原理就是这样,我们来梳理一下整个流程。首先手机通过蓝牙和单片机的蓝牙模块建立通信,当手机发送一个打开台灯信号时,单片机收到相应的信号并控制继电器的公共端指向常开端,台灯亮起。
next~就是具体实现了。一共分为两大部分,分别是Android端和单片机端,先从Android端开始说起。
一.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);
}
}
通过蓝牙适配器获取之前匹配过的蓝牙设备信息(如单片机的蓝牙设备),所以第一次使用的时候,先用手机自带的蓝牙匹配成功一次哈!蓝牙设备中一个比较重要的信息就是设备地址-如98:D3:33:80:83:05就是一个蓝牙设备地址,唯一标示。
/** 获取以前匹配过的蓝牙设备*/
Set devices = null;
if (mBluetoothAdapter != null)
devices = mBluetoothAdapter.getBondedDevices();
else
Toast.makeText(MainAty.this, "该设备不支持蓝牙功能 ", Toast.LENGTH_SHORT).show();
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();
连接指定的蓝牙:通过调用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);
}
simpleAdapter.notifyDataSetChanged();
}
@Override
public void updateLog(String msg) {
mTextView.append("\r\n" + msg);
}
});
}
});
可以看到打开台灯按钮的点击事件,只是调用了BluetoothTool的发送功能向单片机蓝牙发送了一个ff消息(具体发送时将ff转化成16进制0xff,因而在单片机端我们会收到一个0xff的数据)。关闭事件同上。
@Override
public void onClick(View view) {
int id = view.getId();
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;
}
}
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;
以客户端身份连接的线程,我们来看看具体实现。
/**
- 客户端线程
*/
private ClientThread mClientThread = null;
通过蓝牙设备获取相应的socket,之后单片机的蓝牙和手机的蓝牙通信都是通过这个socket。其中在蓝牙中,每个服务和服务属性都唯一地由"全球唯一标识符" (UUID)来校验。而且这个UUID的值必须是00001101-0000-1000-8000-00805F9B34FB,这个是android的API上面说明的,用于普通蓝牙适配器和android手机蓝牙模块连接的。获取之后通过socket的connect进行连接,连接成功之后开启读取数据的线程。
/**
-
客户端线程
/
private class ClientThread extends Thread {
@Override
public void run() {
super.run();
try {
/* 客户端通过服务端的UUID与之连接*/
mClientSocket = mDevice.createRfcommSocketToServiceRecord(
UUID.fromString(“00001101-0000-1000-8000-00805F9B34FB”));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); }
}
}
可以看到线程一直在查看有没有数据,如果有的话就接受,并根据接收到的数据进行相应的显示。有一点要先说一下,就是如果手机成功发送了一个开灯命令给单片机,单片机收到之后成功控制继电器将台灯打开之后,单片机会回发一个消息0xff给手机。因此手机端只要收到0xff这个消息,就知道台灯打开成功了手机就可以显示台灯成功开启。
/**
-
读取数据线程
*/
private class ReadThread extends Thread {
@Override
public void run() {
super.run();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(); } }
}
}
向单片机发送数据,通过socket获取相应的输出流,然后将要发送的字符串转化成16进制发送。
/**
-
发送数据
-
@param str
*/
public void sendData(String str) {
if (mClientSocket == null) {
Log.d(TAG, “sendData: 没有连接。。。”);Message msg = Message.obtain(null,