感谢:(1)PC端蓝牙开发 https://www.cnblogs.com/zeussbook/p/12827479.html
https://blog.csdn.net/svizzera/article/details/77434917
(2)解决数据读取乱序 https://blog.csdn.net/clliu_hust/article/details/80874272
蓝牙客户端Socket的与Sokcet流程是一样的,只不过参数不同而已。如下:
1、创建客户端蓝牙Sokcet
2、创建连接
3、读写数据
4、关闭
需要导入第三方的蓝牙bluecove.jar包。其中32位系统和64位系统所导的包不同,需要区分。否则会报错。BlueCove还需要Apache的commons-io包,这个顺便下就可以的。
PS:如果启动报错:bluecove native library version mismatch,可尝试把commons-io包去掉。
链接:https://pan.baidu.com/s/1tFixZRIRaN4HdlslDlKhuQ
提取码:rr12
3.1设备查找类
package com.jcsim;
/**
* Created with IntelliJ IDEA.
*
* @Author: Jcsim
* @Date: 2020/11/25 15:15
* @Description:设备查找类
*/
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.Vector;
import javax.bluetooth.DataElement;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;
public class RemoteDeviceDiscovery {
public final static Set<RemoteDevice> devicesDiscovered = new HashSet<RemoteDevice>();
public final static Vector<String> serviceFound = new Vector<String>();
final static Object serviceSearchCompletedEvent = new Object();
final static Object inquiryCompletedEvent = new Object();
/**
* 发现监听
*/
private static DiscoveryListener listener = new DiscoveryListener() {
public void inquiryCompleted(int discType) {
System.out.println("#" + "搜索完成");
synchronized (inquiryCompletedEvent) {
inquiryCompletedEvent.notifyAll();
}
}
/**
* 发现设备
* @param remoteDevice
* @param deviceClass
*/
@Override
public void deviceDiscovered(RemoteDevice remoteDevice, DeviceClass deviceClass) {
devicesDiscovered.add(remoteDevice);
try {
System.out.println("#发现设备" + remoteDevice.getFriendlyName(false)+" 设备地址:"+remoteDevice.getBluetoothAddress());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 发现服务
* @param transID id
* @param servRecord 服务记录
*/
@Override
public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
for (int i = 0; i < servRecord.length; i++) {
String url = servRecord[i].getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
if (url == null) {
continue;
}
serviceFound.add(url);
DataElement serviceName = servRecord[i].getAttributeValue(0x0100);
if (serviceName != null) {
System.out.println("service " + serviceName.getValue() + " found " + url);
} else {
System.out.println("service found " + url);
}
}
System.out.println("#" + "servicesDiscovered");
}
/**
* 服务搜索已完成
* @param arg0
* @param arg1
*/
@Override
public void serviceSearchCompleted(int arg0, int arg1) {
System.out.println("#" + "serviceSearchCompleted");
synchronized(serviceSearchCompletedEvent){
serviceSearchCompletedEvent.notifyAll();
}
}
};
/**
* 查找设备
* @throws IOException
* @throws InterruptedException
*/
private static void findDevices() throws IOException, InterruptedException {
devicesDiscovered.clear();
synchronized (inquiryCompletedEvent) {
LocalDevice ld = LocalDevice.getLocalDevice();
System.out.println("#本机蓝牙名称:" + ld.getFriendlyName());
boolean started = LocalDevice.getLocalDevice().getDiscoveryAgent().startInquiry(DiscoveryAgent.GIAC,listener);
if (started) {
System.out.println("#" + "等待搜索完成...");
inquiryCompletedEvent.wait();
LocalDevice.getLocalDevice().getDiscoveryAgent().cancelInquiry(listener);
System.out.println("#发现设备数量:" + devicesDiscovered.size());
}
}
}
/**
* 获取设备
* @return
* @throws IOException
* @throws InterruptedException
*/
public static Set<RemoteDevice> getDevices() throws IOException, InterruptedException {
findDevices();
return devicesDiscovered;
}
/**
* 查找服务
* @param btDevice
* @param serviceUUID
* @return
* @throws IOException
* @throws InterruptedException
*/
public static String searchService(RemoteDevice btDevice, String serviceUUID) throws IOException, InterruptedException {
UUID[] searchUuidSet = new UUID[] { new UUID(serviceUUID, false) };
int[] attrIDs = new int[] {
0x0100 // Service name
};
synchronized(serviceSearchCompletedEvent) {
System.out.println("search services on " + btDevice.getBluetoothAddress() + " " + btDevice.getFriendlyName(false));
LocalDevice.getLocalDevice().getDiscoveryAgent().searchServices(attrIDs, searchUuidSet, btDevice, listener);
serviceSearchCompletedEvent.wait();
}
if (serviceFound.size() > 0) {
return serviceFound.elementAt(0);
} else {
return "";
}
}
}
3.2蓝牙客户端类
package com.jcsim;
/**
* Created with IntelliJ IDEA.
*
* @Author: Jcsim
* @Date: 2020/11/25 15:14
* @Description:蓝牙客户端类
*/
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.bluetooth.BluetoothConnectionException;
import javax.bluetooth.RemoteDevice;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
public class BluetoothClient {
private StreamConnection streamConnection;//流连接
private OnDiscoverListener onDiscoverListener = null;//发现监听
private OnClientListener onClientListener = null;//客户端监听
/**
* 客户端监听
*/
public interface OnClientListener {
void onConnected(DataInputStream inputStream, OutputStream outputStream);
void onConnectionFailed();
void onDisconnected();
void onClose();
}
/**
* 发现监听
*/
public interface OnDiscoverListener {
void onDiscover(RemoteDevice remoteDevice);
}
/**
* 无参构造函数
*/
public BluetoothClient() {
}
/**
* 查找所有
* @throws IOException
* @throws InterruptedException
*/
public void find() throws IOException, InterruptedException {
//附近所有的蓝牙设备,必须先执行 runDiscovery
Set<RemoteDevice> devicesDiscovered = RemoteDeviceDiscovery.getDevices();
Iterator<RemoteDevice> itr = devicesDiscovered.iterator();
//连接
while (itr.hasNext()) {
RemoteDevice remoteDevice = itr.next();
onDiscoverListener.onDiscover(remoteDevice);
}
}
/**
* 启动连接
* @param remoteDevice
* @throws IOException
* @throws InterruptedException
*/
public void startClient(RemoteDevice remoteDevice) throws IOException, InterruptedException {
// String url = RemoteDeviceDiscovery.searchService(remoteDevice, serviceUUID);
// System.out.println("url=="+url);
// 1 为通道;authenticate=true;encrypt=true表示需要验证pin码
// btspp://<蓝牙设备地址>:<通道号>
String url = "btspp://"+remoteDevice.getBluetoothAddress()+":1;authenticate=true;encrypt=true";
try{
streamConnection = (StreamConnection) Connector.open(url);
if (this.onClientListener != null) {
this.onClientListener.onConnected(streamConnection.openDataInputStream(), streamConnection.openOutputStream());
}else{
System.out.println("请打开蓝牙");
}
} catch (BluetoothConnectionException e){
e.printStackTrace();
System.out.println("蓝牙连接错误,请查看蓝牙是否打开。");
}catch (Exception e){
e.printStackTrace();
}
}
public OnDiscoverListener getOnDiscoverListener() {
return onDiscoverListener;
}
public void setOnDiscoverListener(OnDiscoverListener onDiscoverListener) {
this.onDiscoverListener = onDiscoverListener;
}
public OnClientListener getClientListener() {
return onClientListener;
}
public void setClientListener(OnClientListener onClientListener) {
this.onClientListener = onClientListener;
}
}
3.3 蓝牙客户端业务类
package com.jcsim;
/**
* Created with IntelliJ IDEA.
*
* @Author: Jcsim
* @Date: 2020/11/25 15:17
* @Description:蓝牙客户端业务类
*/
import javax.bluetooth.RemoteDevice;
import javax.microedition.io.ConnectionNotFoundException;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Vector;
public class BluetoothClientService {
public static void main(String[] argv) {
final String serverUUID = "1000110100001000800000805F9B34FB"; //需要与服务端相同
BluetoothClient client = new BluetoothClient();
Vector<RemoteDevice> remoteDevices = new Vector<>();
Boolean isConnect = false;
client.setOnDiscoverListener(new BluetoothClient.OnDiscoverListener() {
@Override
public void onDiscover(RemoteDevice remoteDevice) {
remoteDevices.add(remoteDevice);
}
});
client.setClientListener(new BluetoothClient.OnClientListener() {
@Override
public void onConnected(DataInputStream inputStream, OutputStream outputStream) {
System.out.printf("Connected");
// 开启线程读写蓝牙上接收和发送的数据。
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("客户端开始监听...");
// System.out.println("接收连接");
// System.out.println("开始读数据...");
SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");
while (true) {
byte[] buffer = new byte[1024];
int bytes=0;
int ch;
// inputStream.read(buffer)
while ((ch = inputStream.read()) != '\n') {
// 读数据。
// String s = new String(buffer);
// System.out.println("===========start=============");
// System.out.println(s.trim());
// System.out.println("------------end------------");
// Thread.sleep(1000);
if(ch!=-1){
buffer[bytes] = (byte) ch;
bytes++;
}
}
buffer[bytes] = (byte)'\n';
bytes++;
String s = new String(buffer);
System.out.println("===========start=============");
System.out.println(df.format(new Date())+"->"+s.trim());
System.out.println("------------end------------");
// inputStream.close();
// onClose();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
@Override
public void onConnectionFailed() {
System.out.printf("Connection failed");
}
@Override
public void onDisconnected() {
}
@Override
public void onClose() {
}
});
try {
client.find();
if (remoteDevices.size() > 0 ) {
for(int i=0;i<remoteDevices.size();i++){
System.out.println("第"+i+"个地址为:"+remoteDevices.get(i).getBluetoothAddress());
if( "所要连接的蓝牙的地址".equals(remoteDevices.get(i).getBluetoothAddress())){
isConnect = true;
client.startClient(remoteDevices.get(i));
break;
}
}
if (!isConnect){
System.out.println("请打开传感器蓝牙设备。");
}
// System.out.println("remoteDevices.firstElement="+remoteDevices.firstElement());
}else {
System.out.println("附件没有蓝牙设备");
}
} catch (ConnectionNotFoundException e){
System.out.println("当前蓝牙不在线");
e.printStackTrace();
} catch (InterruptedException | IOException e) {
e.printStackTrace();
}
}
}
之前读取数据的时候,一直乱码或乱序(一段字符被分成几段)。
乱码解决方法:蓝牙发送的波特率问题,一般为9600。
乱序:使用InputStream读取时不能和串口一样设置一个结束符(串口的结束符一般是换行符'\r\n'),于是线程读取就很随机性了,你不知道它在什么时候就读完一刀下去,你的字符串就不完整了。
旧方法(乱序):
while (true) {
byte[] buffer = new byte[1024];
//
while (inputStream.read(buffer) != -1) {
// 读数据。
String s = new String(buffer);
System.out.println("===========start=============");
System.out.println(s.trim());
System.out.println("------------end------------");
Thread.sleep(1000);
}
// inputStream.close();
// onClose();
}
乱序解决方法:使用InputStream.read()逐个读取字符,判读是不是结束符,如果没到结束符就一直读取写入
SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");
while (true) {
byte[] buffer = new byte[1024];
int bytes = 0; //字符串长度
int ch; // 读取字符的变量
while ((ch = inputStream.read()) != '\n') {
// 读数据。
if(ch!=-1){
buffer[bytes] = (byte) ch; // 将读取到的字符写入
bytes++;
}
}
buffer[bytes] = (byte)'\n'; //最后加上一个换行
bytes++;
String s = new String(buffer);
System.out.println("===========start=============");
System.out.println(df.format(new Date())+"->"+s.trim());
System.out.println("------------end------------");
}