android开发之手机与单片机蓝牙模块通信

2019-04-15 17:36发布

之前两篇都是在说与手机的连接,连接方法,和主动配对连接,都是手机与手机的操作,做起来还是没问题的,但是最终的目的是与单片机的蓝牙模块的通信。   下面是到目前为止尝试的与单片机的通信方法,没有成功,但是从思路上来说没有问题,最大的问题是与单片机配对的时候,单片机的蓝牙模块的PIN配对码是写死的,固定为1234, 而手机这边连接配对都是自动生成的PIN配对码,这种方式在手机与手机配对的时候是极为方便的,但是在这里与单片机连接却成了最大的问题,因为手机自动生成而且每次都不一样,所以没法与单片机蓝牙模块的1234相同也就没法陪对了。下面只是介绍的到目前为止我们的大题思路,具体代码很多,而且涉及到项目也就没有贴。 如果关于上面的问题哪位同学有思路或者做过类似的项目还请指点。   首先,如何开启蓝牙设备和设置可见时间: private void search() { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); if (!adapter.isEnabled()) { adapter.enable(); } Intent enable = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); enable.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 3600); //3600为蓝牙设备可见时间 startActivity(enable); Intent searchIntent = new Intent(this, ComminuteActivity.class); startActivity(searchIntent); }
正式开始与蓝牙模块进行通信 public class ComminuteActivity extends Activity { private BluetoothReceiver receiver; private BluetoothAdapter bluetoothAdapter; private List devices; private List deviceList; private Bluetooth client; private final String lockName = "YESYOU"; private String message = "000001"; private ListView listView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search_layout); listView = (ListView) this.findViewById(R.id.list); deviceList = new ArrayList(); devices = new ArrayList(); bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); bluetoothAdapter.startDiscovery(); IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); receiver = new BluetoothReceiver(); registerReceiver(receiver, filter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { setContentView(R.layout.connect_layout); BluetoothDevice device = deviceList.get(position); client = new Bluetooth(device, handler); try { client.connect(message); } catch (Exception e) { Log.e("TAG", e.toString()); } } }); } @Override protected void onDestroy() { unregisterReceiver(receiver); super.onDestroy(); } private final Handler handler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case Bluetooth.CONNECT_FAILED: Toast.makeText(ComminuteActivity.this, "连接失败", Toast.LENGTH_LONG).show(); try { client.connect(message); } catch (Exception e) { Log.e("TAG", e.toString()); } break; case Bluetooth.CONNECT_SUCCESS: Toast.makeText(ComminuteActivity.this, "连接成功", Toast.LENGTH_LONG).show(); break; case Bluetooth.READ_FAILED: Toast.makeText(ComminuteActivity.this, "读取失败", Toast.LENGTH_LONG).show(); break; case Bluetooth.WRITE_FAILED: Toast.makeText(ComminuteActivity.this, "写入失败", Toast.LENGTH_LONG).show(); break; case Bluetooth.DATA: Toast.makeText(ComminuteActivity.this, msg.arg1 + "", Toast.LENGTH_LONG).show(); break; } } }; private class BluetoothReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (isLock(device)) { devices.add(device.getName()); } deviceList.add(device); } showDevices(); } } private boolean isLock(BluetoothDevice device) { boolean isLockName = (device.getName()).equals(lockName); boolean isSingleDevice = devices.indexOf(device.getName()) == -1; return isLockName && isSingleDevice; } private void showDevices() { ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, devices); listView.setAdapter(adapter); } } 这里需要提一下的是,startDiscovery()这个方法和它的返回值,它是一个异步方法,会对其他蓝牙设备进行搜索,持续时间为12秒。 搜索过程其实是在System Service中进行,我们可以通过cancelDiscovery()方法来停止这个搜索。在系统搜索蓝牙设备的过程中,系统可能会发送以下三个广播:ACTION_DISCOVERY_START(开始搜索), ACTION_DISCOVERY_FINISHED(搜索结束) 和ACTION_FOUND(找到设备)。 ACTION_FOUND这个才是我们想要的,这个Intent中包含两个extra fields:    EXTRA_DEVICE和EXTRA_CLASS, 包含的分别是BluetoothDevice和BluetoothClass EXTRA_DEVICE中的BluetoothDevice就是我们搜索到的设备对象,从中获得设备的名称和地址。
EXTRA_CLASS中的BluetoothClass是搜索到的设备的类型,比如搜索到的是手机还是耳机或者其他,之后我会写一篇关于它的介绍 在这个上面我现在在想,是否通过判断搜索到的设备类型来识别单片机蓝牙模块与手机蓝牙的不同,采取不一样的配对方式,从而不自动生成配对码。不知是否可行,一会尝试。    搜索到该设备后,我们就要对该设备进行连接和通信。 public void connect(final String message) { Thread thread = new Thread(new Runnable() { public void run() { BluetoothSocket tmp = null; Method method; try { method = device.getClass().getMethod("createRfcommSocket", new Class[]{int.class}); tmp = (BluetoothSocket) method.invoke(device, 1); } catch (Exception e) { setState(CONNECT_FAILED); Log.e("TAG", e.toString()); } socket = tmp; try { socket.connect(); isConnect = true; } catch (Exception e) { setState(CONNECT_FAILED); Log.e("TAG", e.toString()); } if (isConnect) { try { OutputStream outStream = socket.getOutputStream(); outStream.write(getHexBytes(message)); } catch (IOException e) { setState(WRITE_FAILED); Log.e("TAG", e.toString()); } try { InputStream inputStream = socket.getInputStream(); int data; while (true) { try { data = inputStream.read(); Message msg = handler.obtainMessage(); msg.what = DATA; msg.arg1 = data; handler.sendMessage(msg); } catch (IOException e) { setState(READ_FAILED); Log.e("TAG", e.toString()); break; } } } catch (IOException e) { setState(WRITE_FAILED); Log.e("TAG", e.toString()); } } if (socket != null) { try { socket.close(); } catch (IOException e) { Log.e("TAG", e.toString()); } } } }  这里包括写入和读取,用法和基本的Socket是一样的,但是写入的时候,需要将字符串转化为16进制: private byte[] getHexBytes(String message) { int len = message.length() / 2; char[] chars = message.toCharArray(); String[] hexStr = new String[len]; byte[] bytes = new byte[len]; for (int i = 0, j = 0; j < len; i += 2, j++) { hexStr[j] = "" + chars[i] + chars[i + 1]; bytes[j] = (byte) Integer.parseInt(hexStr[j], 16); } return bytes; }
 
连接设备之前需要UUID,所谓的UUID,就是用来进行配对的,全称是Universally Unique Identifier,是一个128位的字符串ID,用于进行唯一标识。网上的例子,包括谷歌的例子提供的uuid,通用的"00001101-0000-1000-8000-00805F9B34FB"也试过了,在配对的时候都是自动生成了配对码,也无法正常与单片机的蓝牙模块连接,所以,我就利用反射的原理,让设备自己提供UUID尝试。到这里其实我有点怀疑自己对于UUID的理解是否正确了。             在谷歌提供的例子中,我们可以看到谷歌的程序员的程序水平很高,一些好的编码习惯我们可以学习一下,像是在try..catch中才定义的变量,我们应该在try...catch之前声明一个临时变量,然后再在try...catch后赋值给我们真正要使用的变量。这种做法的好处就是:如果我们直接就是使用真正的变量,当出现异常的时候,该变量的使用就会出现问题,而且很难进行排查,如果是临时变量,我么可以通过检查变量的值来确定是否是赋值时出错。     作者:jason0539 微博:http://weibo.com/2553717707 博客:http://blog.csdn.net/jason0539(转载请说明出处)