2014-01-05 12:04:42 jason0539 阅读数 37710
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    4003 人正在学习 去看看 朱有鹏

之前两篇都是在说与手机的连接,连接方法,和主动配对连接,都是手机与手机的操作,做起来还是没问题的,但是最终的目的是与单片机的蓝牙模块的通信。

 

下面是到目前为止尝试的与单片机的通信方法,没有成功,但是从思路上来说没有问题,最大的问题是与单片机配对的时候,单片机的蓝牙模块的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<String> devices;
    private List<BluetoothDevice> 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<BluetoothDevice>();
        devices = new ArrayList<String>();
        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<String> adapter = new ArrayAdapter<String>(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(转载请说明出处)

2019-12-18 09:53:55 qq_43566257 阅读数 7
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    4003 人正在学习 去看看 朱有鹏

我的项目适用于android和单片机蓝牙通信,以完成功能。直接上代码,两个 activity,清单文件加布局文件需要修改。本人学识短浅,只负责代码搬运,实测有效可完成通讯
##Git地址: https://github.com/BSHead/BLTAndroid/tree/master

2018-08-28 18:51:06 u010898329 阅读数 10566
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    4003 人正在学习 去看看 朱有鹏

系列博文:

(1)安卓手机与蓝牙模块联合调试(一)——蓝牙模块的串口通讯 

(2)安卓手机与蓝牙模块联合调试(二)—— 单片机蓝牙控制LED灯亮灭(上)

(3)安卓手机与蓝牙模块联合调试(三)—— 单片机蓝牙控制LED灯亮灭(下)

本教程的项目地址:1989Jiangtao/BluetoothSCM: 安卓手机通过蓝牙与单片机通信-发送指令/接收数据​​​​​​​


忙里偷闲,承接上一篇文章继续

安卓手机与蓝牙模块联合调试(一)——蓝牙模块的串口通讯 - CSDN博客

本篇将实现两个实例,手头正好有8位的单片机,索性就用来练手了。将会提供两个例子,一个是基于STM8的库函数实例,一个是基于STC89C52的实例。

1.首先了解下单片机串口通讯线的接法。这个比较重要,建议参考文章。

(1)HC-05初探 - 骑着蜗牛逛世界 - 博客园      https://www.cnblogs.com/LittleHann/p/5621937.html

(2)HC-05 蓝牙模块的调试与使用 - CSDN博客      https://blog.csdn.net/txf1984/article/details/46553715

 

2. 准备工作和单片机原理图。

       首先看下我用到的硬件支持,一块STM8S103F2P6最小系统板,一个ST-Link下载器,一个HC-05的模块(暂时先调试HC-5的模块,CC2541单片机端的代码是一样的)

最小系统的原理图如下,大家按照上面两个博文中的接线方法接好单片机,特别注意蓝牙模块的TX/RX与单片机的TX/RX要交叉相接

3. 单片机的代码,使用STM8库函数编写。

首先看下目录结构,本项目使用的是IAR编译器

下面贴出来main函数,注释在代码中已经描述的很清楚了,大家可以自行参考。

/*************
** Author   : 江涛
** Date     :  2018/08/28
** Describe : 蓝牙控制单片机开关灯
**************/
/* Includes -----------*/
#include "stm8s.h"
#include <string.h>

/********************************************
**  TestLed        PB5  测试LED
**********************************************/

/**串口1接收数据标志位**/
unsigned char Usart1_bufStart_or_bufSotp = 0 ;
/**串口1数据,这里10个字节够放命令了,大家可以根据实际需要调整数组长度**/
char Usart1BufData[10];
/**串口数据数目自增量,用来统计数目是否达到数组最大长度**/
char Usart1BufConst;

/****以下是定义的命令*****/
char LED_ON[10] = "ON\n";  
char LED_OFF[10] = "OFF\n";

void Delay(uint16_t nCount)
{
  /* Decrement nCount value */
  while (nCount != 0)
  {   
    nCount--;
  }
}

/*************
* 初始化串口UART1
**************/
void initUart1()
{
  // 串口设置
  UART1_DeInit();
  // 9600波特率,8位数据位,一位停止位,
  UART1_Init((u32)9600, UART1_WORDLENGTH_8D, UART1_STOPBITS_1, UART1_PARITY_NO, UART1_SYNCMODE_CLOCK_DISABLE, UART1_MODE_TXRX_ENABLE);//UART1_MODE_TX_ENABLE);
  // 使能串口接收中断
  UART1_ITConfig(UART1_IT_RXNE_OR, ENABLE);
}

/*********
* 端口配置
**************/
void initGPIO(){
  // LED灯的GPIO口初始化
  GPIO_Init(GPIOB, GPIO_PIN_5, GPIO_MODE_OUT_PP_LOW_FAST);
  GPIO_WriteHigh(GPIOB , GPIO_PIN_5); // 关测试灯
  
}

/*****************
*  程序入口
*******************/
void main(void)
{

  // 初始化系统时钟,
  CLK_HSICmd(ENABLE);
  CLK_SYSCLKConfig(CLK_PRESCALER_HSIDIV1);
  //内部时钟16M,8分频
  CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);    
  
  initGPIO();
   
  initUart1();
  
  enableInterrupts(); // 使能中断
       
  while (1)
  {    
    if(Usart1_bufStart_or_bufSotp)
    {
       Usart1_bufStart_or_bufSotp = 0 ; 
              
       if(strcmp(Usart1BufData,LED_ON)==0)
       {
          GPIO_WriteLow(GPIOB , GPIO_PIN_5);         
       }
       else if(strcmp(Usart1BufData,LED_OFF)==0)
       {
          GPIO_WriteHigh(GPIOB , GPIO_PIN_5);
       }
              
       Usart1BufConst = 0 ;
       memset(Usart1BufData,0,10);//清0数组   
    }    
  }
}


#ifdef USE_FULL_ASSERT

/**
  * @brief  Reports the name of the source file and the source line number
  *   where the assert_param error has occurred.
  * @param file: pointer to the source file name
  * @param line: assert_param error line source number
  * @retval : None
  */
void assert_failed(u8* file, u32 line)
{ 
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */

  /* Infinite loop */
  while (1)
  {
  }
}
#endif

 项目中还有个it.c文件,内容如下:

/* Includes ------------------------------------------------------------------*/
#include "stm8s.h"

/* Private typedef -----------------------------------------------------------*/

extern unsigned char Usart1_bufStart_or_bufSotp;
extern char Usart1BufData[10];
extern char Usart1BufConst;

/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/* Public functions ----------------------------------------------------------*/


#if defined (STM8S208) || defined(STM8S207) || defined(STM8S007) || defined(STM8S103) || \
    defined(STM8S003) ||  defined (STM8AF62Ax) || defined (STM8AF52Ax) || defined (STM8S903)
/**
  * @brief UART1 TX Interrupt routine.
  * @param  None
  * @retval None
  */
 INTERRUPT_HANDLER(UART1_TX_IRQHandler, 17)
 {
    /* In order to detect unexpected events during development,
       it is recommended to set a breakpoint on the following instruction.
    */
 }


/**
  * @brief UART1 RX Interrupt routine.
  * @param  None
  * @retval None
  */
 INTERRUPT_HANDLER(UART1_RX_IRQHandler, 18)
 {
    /* In order to detect unexpected events during development,
       it is recommended to set a breakpoint on the following instruction.
    */  
     
     Usart1BufData[Usart1BufConst]=UART1_ReceiveData8();
     UART1_SendData8(UART1_ReceiveData8()); 
     // 收到了结束符号
     if(Usart1BufData[Usart1BufConst]==0x0A)
     {
       Usart1_bufStart_or_bufSotp=1;    
     }
     else
       Usart1BufConst++;
   
      UART1_ClearITPendingBit(UART1_IT_RXNE); // 清除标志位
      
 }
#endif /*STM8S208 or STM8S207 or STM8S103 or STM8S903 or STM8AF62Ax or STM8AF52Ax */

/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/

主要代码就是这些了,注释中已经写的很清楚了。

4.接好线看下运行的效果 。

过后放上一个视频连接,实在因为gif的效果不明显。 

 

好了,STM8蓝牙控制LED亮灭的功能基本实现了,下一遍将实现蓝牙和STC89单片机的联调。

当这个系列结束之后我会将代码一并上传到Git。博文更新的话,可能因为工作原因会断断续续,希望大家见谅。如果觉得有帮助或者有设么其他好的建议可以在博文下留言,赞赏码没有别的意思,这是个知识付费的时代,可能付费了才会更珍惜学习的不易,当然也是作者创作的动力所在,哈哈。


 

 

 

 

2018-09-18 00:10:43 u010898329 阅读数 6592
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    4003 人正在学习 去看看 朱有鹏

(1)安卓手机与蓝牙模块联合调试(一)—— 蓝牙模块的串口通讯 

(2)安卓手机与蓝牙模块联合调试(二)—— 单片机蓝牙控制LED灯亮灭(上)

(3)安卓手机与蓝牙模块联合调试(三)—— 单片机蓝牙控制LED灯亮灭(下)

(4)安卓手机与蓝牙模块联合调试(四)—— 单片机数据上传至蓝牙(STC89C52 + DS18b20)

本教程的项目地址:1989Jiangtao/BluetoothSCM: 安卓手机通过蓝牙与单片机通信-发送指令/接收数据​​​​​​​


Github参考:  dingjikerbo/BluetoothKit: Android BLE蓝牙通信库 

                      zagum/Android-SwitchIcon: Google launcher-style implementation of switch (enable/disable) icon

                      huangyanbin/CalendarView: 日历 仪表盘 圆盘,提供全新RecyclerView日历,功能更加强大。

                      Android BLE4.0 常用的一些Service和Characteristic的UUID - CSDN博客
 

1.AS添加依赖的注意事项。

由于许多前辈的开源精神和无私奉献,才使得现如今你的IT事业日新月异,也让我们后来人免去了很多重复造轮子的繁琐工作,因此在这里感谢无私的开源奉献者。

下面说下依赖项目引入时候的注意事项,其实如果自己去git上看也能够自己搞懂。switch-icon的依赖请务必在项目project/build.gradle中添加如下两行,原因是因为该项目放在了jitpack上,使用maven方式。

 

2.看下演示的效果。

 

 

 

3.上代码,本篇就提交部分主要代码,后续会更新到GitHub。

先看下依赖引入,使用了第三方的蓝牙扫描连接和读写库,按钮切换库,自定义的表盘,build.gradle文件如下

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.cjt.bluetoothscm"
        minSdkVersion 15
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        /*由于使用了Vector Asset,所以必须添加支持*/
        vectorDrawables.useSupportLibrary = true
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.android.support:design:27.1.1'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    /*第三方蓝牙操作库*/
    implementation 'com.inuker.bluetooth:library:1.4.0'
    /*第三方SwitchIcon,图标切换库*/
    implementation 'com.github.zagum:Android-SwitchIcon:1.3.7'
}

由于我在项目中大量使用了Vector Asset图标,所以依赖中要添加对VectorDrawable的支持。还有一个表盘的自定义View,我没有引入依赖,直接拷贝了别人的java代码和资源文件到项目中使用的,稍后也会贴出来部分。

布局全部使用的约束布局,感觉比RelativeLayout还好用,如果有不熟悉的同学,建议可以找相关的教程学习下,刚开始可能不习惯,后面用熟练了会感觉很爽,以前我都是手写布局xml文件,现在有了这个东西,反而喜欢直接拖拽,简单快捷。下面的是activity_main.xml布局文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/main_title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:gravity="center"
        android:textSize="18sp"
        android:text="@string/scan_hint"
        android:textColor="@color/colorAccent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <View
        android:id="@+id/top_div"
        android:layout_width="wrap_content"
        android:layout_height="1dp"
        android:layout_marginEnd="5dp"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:layout_marginStart="5dp"
        android:layout_marginTop="10dp"
        android:background="@color/arc1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/main_title" />

    <com.cjt.bluetoothscm.DashboardView
        android:id="@+id/temp_view"
        android:layout_width="280dp"
        android:layout_height="280dp"
        android:layout_marginTop="15dp"
        app:angleTextSize="16sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/top_div" />

    <View
        android:id="@+id/top_div2"
        android:layout_width="wrap_content"
        android:layout_height="1dp"
        android:layout_marginEnd="5dp"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:layout_marginStart="5dp"
        android:layout_marginTop="20dp"
        android:background="@color/arc1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/temp_view" />

    <com.github.zagum.switchicon.SwitchIconView
        android:id="@+id/sw_lamp_01"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_marginLeft="20dp"
        android:layout_marginStart="20dp"
        android:layout_marginTop="20dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/top_div2"
        app:si_animation_duration="200"
        app:si_disabled_alpha=".5"
        app:si_disabled_color="@color/colorOff"
        app:si_enabled="false"
        app:si_no_dash="true"
        app:si_tint_color="@color/colorOn"
        app:srcCompat="@drawable/ic_lamp" />

    <com.github.zagum.switchicon.SwitchIconView
        android:id="@+id/sw_lamp_02"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_marginLeft="30dp"
        android:layout_marginStart="30dp"
        android:layout_marginTop="20dp"
        app:layout_constraintStart_toEndOf="@+id/sw_lamp_01"
        app:layout_constraintTop_toBottomOf="@+id/top_div2"
        app:si_animation_duration="200"
        app:si_disabled_alpha=".5"
        app:si_disabled_color="@color/colorOff"
        app:si_enabled="false"
        app:si_no_dash="true"
        app:si_tint_color="@color/colorOn"
        app:srcCompat="@drawable/ic_lamp" />

    <com.github.zagum.switchicon.SwitchIconView
        android:id="@+id/sw_power"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_marginLeft="30dp"
        android:layout_marginStart="30dp"
        android:layout_marginTop="20dp"
        app:layout_constraintStart_toEndOf="@+id/sw_lamp_02"
        app:layout_constraintTop_toBottomOf="@+id/top_div2"
        app:si_animation_duration="200"
        app:si_disabled_alpha=".5"
        app:si_disabled_color="@color/colorOff"
        app:si_enabled="false"
        app:si_no_dash="true"
        app:si_tint_color="@color/colorOn"
        app:srcCompat="@drawable/ic_switch" />

    <com.github.zagum.switchicon.SwitchIconView
        android:id="@+id/sw_fan"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_marginLeft="30dp"
        android:layout_marginStart="30dp"
        android:layout_marginTop="20dp"
        app:layout_constraintStart_toEndOf="@+id/sw_power"
        app:layout_constraintTop_toBottomOf="@+id/top_div2"
        app:si_animation_duration="200"
        app:si_disabled_alpha=".5"
        app:si_disabled_color="@color/colorOff"
        app:si_enabled="false"
        app:si_no_dash="true"
        app:si_tint_color="@color/colorOn"
        app:srcCompat="@drawable/ic_fan" />

    <TextView
        android:id="@+id/lamp_01_name"
        android:layout_width="60dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:gravity="center"
        android:text="@string/lamp_01"
        app:layout_constraintEnd_toEndOf="@+id/sw_lamp_01"
        app:layout_constraintStart_toStartOf="@+id/sw_lamp_01"
        app:layout_constraintTop_toBottomOf="@+id/sw_lamp_01"
        tools:ignore="HardcodedText" />

    <TextView
        android:id="@+id/lamp_02_name"
        android:layout_width="60dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:gravity="center"
        android:text="@string/lamp_02"
        app:layout_constraintEnd_toEndOf="@+id/sw_lamp_02"
        app:layout_constraintStart_toStartOf="@+id/sw_lamp_02"
        app:layout_constraintTop_toBottomOf="@+id/sw_lamp_02" />

    <TextView
        android:id="@+id/power_name"
        android:layout_width="60dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:gravity="center"
        android:text="@string/power_sw"
        app:layout_constraintEnd_toEndOf="@+id/sw_power"
        app:layout_constraintStart_toStartOf="@+id/sw_power"
        app:layout_constraintTop_toBottomOf="@+id/sw_power" />

    <TextView
        android:id="@+id/fan_name"
        android:layout_width="60dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:gravity="center"
        android:text="@string/fan_sw"
        app:layout_constraintEnd_toEndOf="@+id/sw_fan"
        app:layout_constraintStart_toStartOf="@+id/sw_fan"
        app:layout_constraintTop_toBottomOf="@+id/sw_fan" />

</android.support.constraint.ConstraintLayout>

接着贴出来MainActivity.java文件

package com.cjt.bluetoothscm;

import android.app.Activity;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.github.zagum.switchicon.SwitchIconView;
import com.inuker.bluetooth.library.Constants;
import com.inuker.bluetooth.library.connect.options.BleConnectOptions;
import com.inuker.bluetooth.library.connect.response.BleConnectResponse;
import com.inuker.bluetooth.library.connect.response.BleNotifyResponse;
import com.inuker.bluetooth.library.connect.response.BleWriteResponse;
import com.inuker.bluetooth.library.model.BleGattProfile;

import java.util.UUID;

import static com.inuker.bluetooth.library.Constants.REQUEST_SUCCESS;

/*****************
 * 包名:com.cjt.bluetoothscm
 * 类名:MainActivity.java
 * 时间:2018/9/11  23:28
 * 作者:Cao Jiangtao
 * 首页:https://1989jiangtao.github.io/index.html
 ******************/
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final int REQUEST_CONNECT_DEVICE = 0x100;

    TextView mainTitle ;

    // 温度显示仪表盘
    DashboardView tempView ;
    private final static int invs[] = {35, 18, 35};
    private final static int[] colorRes = {R.color.arc1, R.color.arc2, R.color.arc3};

    // 灯组01 ,灯组02 , 电源开关, 风扇开关
    SwitchIconView lamp01 , lamp02 , powerSw , fanSw ;
    TextView lamp01Name , lamp02Name ,powerName , fanName;

    // 蓝牙通信的地址和两个UUID
    String MAC = "" ;
    UUID serviceUuid , characterUuid ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();

        // 运行的时候检查是否打开蓝牙,没有打开就开启蓝牙
        if(!MyApp.getBluetoothClient().isBluetoothOpened())
            MyApp.getBluetoothClient().openBluetooth();

    }

    private void initView() {

        mainTitle = findViewById(R.id.main_title);
        tempView = findViewById(R.id.temp_view);
        lamp01 = findViewById(R.id.sw_lamp_01);
        lamp02 = findViewById(R.id.sw_lamp_02);
        powerSw = findViewById(R.id.sw_power);
        fanSw = findViewById(R.id.sw_fan);
        lamp01Name = findViewById(R.id.lamp_01_name);
        lamp02Name = findViewById(R.id.lamp_02_name);
        powerName = findViewById(R.id.power_name);
        fanName = findViewById(R.id.fan_name);

        // 为按钮设置点击事件
        lamp01.setOnClickListener(this);
        lamp02.setOnClickListener(this);
        powerSw.setOnClickListener(this);
        fanSw.setOnClickListener(this);

        // 初始化温度表盘
        String[] str = getResources().getStringArray(R.array.mult_temp_dash);
        tempView.initDash(-20, invs, str, "℃", colorRes);

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main , menu); // 加载菜单页面
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if(item.getItemId() == R.id.action_scan){
            Intent intent = new Intent(MainActivity.this , ScanResultActivity.class);
            startActivityForResult(intent , REQUEST_CONNECT_DEVICE);
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    protected void onDestroy() {
        // 关闭蓝牙
        MyApp.getBluetoothClient().closeBluetooth();
        super.onDestroy();
    }

    @Override
    public void onClick(View v) {

        // 当蓝牙有连接,并且MAC地址存在,两个UUID都不为空的情况下,点击按钮才有效
        // 以下只要有一个条件不满足,就不让点击按钮发送数据
        if(!MyApp.getBluetoothClient().isBleSupported()
                || TextUtils.isEmpty(MAC)
                || TextUtils.isEmpty(serviceUuid.toString())
                || TextUtils.isEmpty(characterUuid.toString())){
            Toast.makeText(MainActivity.this , "请先检查蓝牙设备与手机是否连接正常",Toast.LENGTH_SHORT).show();
            return;
        }

        switch (v.getId()){
            case R.id.sw_lamp_01: // 灯组01
                lamp01.switchState();
                lamp01Name.setText(lamp01.isIconEnabled() ? "灯组1开" : "灯组1关");
                writeCmd(MAC , serviceUuid , characterUuid , "001-on\r\n");
                break;
            case R.id.sw_lamp_02: // 灯组02
                lamp02.switchState();
                lamp02Name.setText(lamp02.isIconEnabled() ? "灯组1开" : "灯组1关");
                break;
            case R.id.sw_power: // 电源
                powerSw.switchState();
                powerName.setText(powerSw.isIconEnabled() ? "电源开" : "电源关");
                writeCmd(MAC , serviceUuid , characterUuid , "power-on\r\n");
                break;
            case R.id.sw_fan: // 风扇
                fanSw.switchState();
                fanName.setText(fanSw.isIconEnabled() ? "风扇开" : "风扇关");
                writeCmd(MAC , serviceUuid , characterUuid , "fan-sw\r\n");
                break;
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Log.d("CJT" , "requestCode = "+ requestCode +" ,  resultCode = "+ resultCode + " , data ="+data);
        if(requestCode == REQUEST_CONNECT_DEVICE) {
            // 响应结果
            switch (resultCode) {

                case Activity.RESULT_CANCELED:
                    Toast.makeText(this , "取消了扫描!",Toast.LENGTH_SHORT).show();
                    break;
                case Activity.RESULT_OK:
                    // 选择连接的设备
                    final BluetoothDevice device = data.getParcelableExtra(RecycleAdapter.EXTRA_DEVICE);
                    // 得到选择后传过来的MAC地址
                    MAC = device.getAddress();
                    Log.d("CJT" , "address ===================== " +MAC);

                    // 设置BLE设备的连接参数
                    BleConnectOptions options = new BleConnectOptions.Builder()
                            .setConnectRetry(3)   // 连接如果失败重试3次
                            .setConnectTimeout(30000)   // 连接超时30s
                            .setServiceDiscoverRetry(3)  // 发现服务如果失败重试3次
                            .setServiceDiscoverTimeout(20000)  // 发现服务超时20s
                            .build();

                    // 开始连接操作
                    MyApp.getBluetoothClient().connect(MAC, options, new BleConnectResponse() {
                        @Override
                        public void onResponse(int code, BleGattProfile data) {
                            Log.d("CJT" , "getBluetoothClient().connect  --- code ----- " + code);

                            // 表示连接成功
                            if(code == REQUEST_SUCCESS){

                                mainTitle.setText("当前连接设备 :"+device.getName());
//                                for(BleGattService sls : data.getServices()){
//                                    Log.d("CJT" , "onActivityResult -------1111111111-------  : "+sls.getUUID());
//                                    for(BleGattCharacter gls : sls.getCharacters()){
//                                        Log.d("CJT" , "onActivityResult *******22222222222*****  : "+gls.getUuid());
//                                    }
//                                }
                                serviceUuid = data.getServices().get(3).getUUID();
                                Log.d("CJT" , "getBluetoothClient().connect  --- serviceUuid  : "+serviceUuid);
                                characterUuid = data.getService(serviceUuid).getCharacters().get(0).getUuid();
                                Log.d("CJT" , "getBluetoothClient().connect  --- characterUuid : "+characterUuid);

                                // 获取温度值
                                getTemperature(MAC , serviceUuid , characterUuid);

                                // 下发数据
                                writeCmd(MAC , serviceUuid , characterUuid , "finish\r\n");

                            }else{
                                mainTitle.setText("当前暂无蓝牙设备连接");
                                Toast.makeText(MainActivity.this , "蓝牙连接不成功!",Toast.LENGTH_SHORT).show();
                            }
                        }
                    });

                    break;
            }

        }
    }

    /***
     * 获取温度值并显示到界面上
     * @param address           设备地址
     * @param serviceUuid       服务UUID
     * @param characterUuid     特征UUID
     */
    private void getTemperature(String address , UUID serviceUuid , UUID characterUuid ){
        MyApp.getBluetoothClient().notify(address, serviceUuid, characterUuid, new BleNotifyResponse() {
            @Override
            public void onNotify(UUID service, UUID character, byte[] value) {
                    String hexStr = bytesToHexString(value);
                    int beginIndex = hexStr.indexOf("2b") + 2; // 加号开始截取,并且跳过加号
                    int endIndex = hexStr.indexOf("2e") + 2 ;  // 小数点开始截取
                    String validTemp = hexStr.substring(beginIndex , endIndex );
                    Log.d("CJT" , "valid temp = "+validTemp+", hex2Str = "+ new String(hexStringToBytes(validTemp)));

                    // 设置温度值
                    tempView.setAngleWithAnim(Double.valueOf(new String(hexStringToBytes(validTemp))));
            }

            @Override
            public void onResponse(int code) {

            }
        });
    }

    /***
     * 向设备下发指令
     * @param address           设备MAC地址
     * @param serviceUuid       服务UUID
     * @param characterUuid     特征UUID
     * @param cmd               待下发的命令
     */
    private void writeCmd(String address , UUID serviceUuid , UUID characterUuid , String cmd){
        MyApp.getBluetoothClient().write(address, serviceUuid, characterUuid, cmd.getBytes(), new BleWriteResponse() {
            @Override
            public void onResponse(int code) {
                if(code == Constants.REQUEST_SUCCESS){

                }
            }
        });
    }

    /**
     * Convert byte[] to hex string.这里我们可以将byte转换成int,然后利用Integer.toHexString(int)来转换成16进制字符串。
     * @param src byte[] data
     * @return hex string
     */
    public static String bytesToHexString(byte[] src){
        StringBuilder stringBuilder = new StringBuilder("");
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }

    /**
     * Convert hex string to byte[]
     * @param hexString the hex string
     * @return byte[]
     */
    public static byte[] hexStringToBytes(String hexString) {
        if (hexString == null || hexString.equals("")) {
            return null;
        }
        hexString = hexString.toUpperCase();
        int length = hexString.length() / 2;
        char[] hexChars = hexString.toCharArray();
        byte[] d = new byte[length];
        for (int i = 0; i < length; i++) {
            int pos = i * 2;
            d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
        }
        return d;
    }
    /**
     * Convert char to byte
     * @param c char
     * @return byte
     */
    private static byte charToByte(char c) {
        return (byte) "0123456789ABCDEF".indexOf(c);
    }

}

代码可能暂时比较乱,我就挑主要的给大家梳理下逻辑,注释中有的已经写的很清楚了。

(1)先在界面上Toolbar上添加扫描的按钮,点击扫描按钮会跳转到扫描界面。

贴出来main.xml的菜单文件

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="com.cjt.bluetoothscm.MainActivity">
    <item
        android:id="@+id/action_scan"
        android:orderInCategory="100"
        android:icon="@drawable/ic_scan"
        android:title="@string/action_settings"
        tools:ignore="AppCompatResource"
        app:showAsAction="always" />
</menu>

其中有个icon,引用的是drawable下的一个VectorDrawable资源文件,ic_scan.xml

<vector android:height="42dp" android:viewportHeight="1024"
    android:viewportWidth="1024" android:width="42dp" xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="#ffffff" android:pathData="M512,960c-249.6,0 -448,-198.4 -448,-448s198.4,-448 448,-448 448,198.4 448,448 -198.4,448 -448,448zM512,883.2c204.8,0 371.2,-166.4 371.2,-371.2S716.8,140.8 512,140.8 140.8,307.2 140.8,512s166.4,371.2 371.2,371.2z"/>
    <path android:fillColor="#3cbc74" android:pathData="M512,787.2c-153.6,0 -275.2,-121.6 -275.2,-275.2S358.4,236.8 512,236.8s275.2,121.6 275.2,275.2 -121.6,275.2 -275.2,275.2zM512,748.8c128,0 236.8,-108.8 236.8,-236.8S640,275.2 512,275.2 275.2,384 275.2,512 384,748.8 512,748.8z"/>
    <path android:fillColor="#ffffff" android:pathData="M512,512m-38.4,0a38.4,38.4 0,1 0,76.8 0,38.4 38.4,0 1,0 -76.8,0Z"/>
    <path android:fillColor="#ffffff" android:pathData="M524.8,531.2l345.6,-217.6 -19.2,-32 -352,211.2zM492.8,25.6L492.8,320c0,12.8 6.4,19.2 19.2,19.2s19.2,-6.4 19.2,-19.2L531.2,25.6c0,-12.8 -6.4,-19.2 -19.2,-19.2s-19.2,6.4 -19.2,19.2zM998.4,492.8L704,492.8c-12.8,0 -19.2,6.4 -19.2,19.2s6.4,19.2 19.2,19.2h294.4c12.8,0 19.2,-6.4 19.2,-19.2s-6.4,-19.2 -19.2,-19.2zM531.2,998.4L531.2,704c0,-12.8 -6.4,-19.2 -19.2,-19.2s-19.2,6.4 -19.2,19.2v294.4c0,12.8 6.4,19.2 19.2,19.2s19.2,-6.4 19.2,-19.2zM25.6,531.2L320,531.2c12.8,0 19.2,-6.4 19.2,-19.2s-6.4,-19.2 -19.2,-19.2L25.6,492.8c-12.8,0 -19.2,6.4 -19.2,19.2s6.4,19.2 19.2,19.2z"/>
</vector>

使用这个的好处就是,改变大小的时候不会失真,因为使用的矢量作图。 

(2)蓝牙连接的处理,这一块大家要恶补下关于蓝牙UUID的知识,由于我也研究的不深入,所以就不误导大家了,梳理下onActivityResult回调方法

(3)我把client写到了全局Application中,如下

package com.cjt.bluetoothscm;

import android.app.Application;

import com.inuker.bluetooth.library.BluetoothClient;

/*****************
 * 包名:com.cjt.bluetoothscm
 * 类名:MyApp.java
 * 时间:2018/9/17  23:46
 * 作者:Cao Jiangtao
 * 首页:https://1989jiangtao.github.io/index.html
 ******************/

public class MyApp extends Application {

    private static MyApp instance ;

    private static BluetoothClient bluetoothClient ;

    public static MyApp getInstance() {
        return instance;
    }

    public static BluetoothClient getBluetoothClient() {
        return bluetoothClient;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        instance = this ;

        // 新建全局的蓝牙客户端实例
        bluetoothClient = new BluetoothClient(this);

    }

}

 (4)看下扫描界面的界面布局文件和java代码

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ScanResultActivity">


    <android.support.v7.widget.RecyclerView
        android:id="@+id/result_recycle_list"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginBottom="8dp"
        android:layout_marginTop="8dp"
        android:paddingLeft="8dp"
        android:paddingRight="8dp"
        app:layout_constraintBottom_toTopOf="@+id/btn_exit"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_exit"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginStart="16dp"
        android:textSize="20sp"
        android:textStyle="bold"
        android:background="@color/colorRed"
        android:textColor="@color/colorWhite"
        android:text="@string/btn_exit"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</android.support.constraint.ConstraintLayout>
package com.cjt.bluetoothscm;

import android.bluetooth.BluetoothDevice;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;

import com.inuker.bluetooth.library.search.SearchRequest;
import com.inuker.bluetooth.library.search.SearchResult;
import com.inuker.bluetooth.library.search.response.SearchResponse;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
/*****************
 * 包名:com.cjt.bluetoothscm
 * 类名:ScanResultActivity.java
 * 时间:2018/9/17  23:49
 * 作者:Cao Jiangtao
 * 首页:https://1989jiangtao.github.io/index.html
 ******************/
public class ScanResultActivity extends AppCompatActivity implements View.OnClickListener {

    RecyclerView recyclerView ; // 列表展示扫描的结果

    ProgressBar progressBar ; // 页面上的进度条

    Button exitBtn ; // 退出按钮

    RecycleAdapter adapter ; // 列表适配器

    List<BluetoothDevice> bluetoothDeviceList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scan_result);

        initView(); // 界面初始化

        startScan(); // 开启扫描

    }

    @Override
    protected void onDestroy() {
        // 停止扫描
        MyApp.getBluetoothClient().stopSearch();
        super.onDestroy();
    }

    /**开启蓝牙扫描**/
    private void startScan() {
        // 新建一个扫描结果集
        SearchRequest request = new SearchRequest.Builder()
                .searchBluetoothLeDevice(3000, 3)   // 先扫BLE设备3次,每次3s
                .searchBluetoothClassicDevice(5000) // 再扫经典蓝牙5s
                .searchBluetoothLeDevice(2000)      // 再扫BLE设备2s
                .build();

        // 开始扫描
        MyApp.getBluetoothClient().search(request, new SearchResponse() {
            @Override
            public void onSearchStarted() {
                progressBar.setVisibility(View.VISIBLE);
                Log.d("CJT" , "onSearchStarted ***** ");
            }

            @Override
            public void onDeviceFounded(SearchResult device) {
                Log.d("CJT" , "onDeviceFounded ==   name :"+ device.device.getName()+", address : " + device.device.getAddress());
                if(!TextUtils.isEmpty(device.device.getName())){
                    bluetoothDeviceList.add(device.device);
                }
            }

            @Override
            public void onSearchStopped() {
                progressBar.setVisibility(View.GONE);
                // 实时更新列表适配器
                adapter.notifyData(removeDuplicate(bluetoothDeviceList));
                Log.d("CJT" , "onSearchStopped ######  设备数量 :"+removeDuplicate(bluetoothDeviceList).size());
            }

            @Override
            public void onSearchCanceled() {
                progressBar.setVisibility(View.GONE);
            }
        });

    }

    /***
     * 列表去重复
     * @param list 待去除重复数据的列表
     * @return      返回去重后的列表
     */
    public static List removeDuplicate(List list) {
        HashSet h = new HashSet(list);
        list.clear();
        list.addAll(h);
        return list;
    }

    /**初始化页面和控件**/
    private void initView(){
        // 初始化页面控件
        recyclerView = findViewById(R.id.result_recycle_list);
        progressBar = findViewById(R.id.progressBar);
        exitBtn = findViewById(R.id.btn_exit);

        exitBtn.setOnClickListener(this);

        // 设置recycleView显示方式
        recyclerView.setLayoutManager(new LinearLayoutManager(this , LinearLayoutManager.VERTICAL , false));

        adapter = new RecycleAdapter();
        // 设置适配器
        recyclerView.setAdapter(adapter);

    }

    @Override
    public void onClick(View v) {
        finish();
    }
}

代码中有一个去重复的方法,避免最后的列表中很多重复的设备,因为蓝牙在后台不停的扫描,会有很多重复的设备。

(5)看下列表适配器的写法,我用了RecycleView来列表展示已扫描到的设备,同时添加了点击事件,当选择和点击的时候就携带相关的设备信息跳转到MainActivity,进行读写操作。

package com.cjt.bluetoothscm;

import android.app.Activity;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.constraint.ConstraintLayout;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

/*****************
 * 包名:com.cjt.bluetoothscm
 * 类名:RecycleAdapter.java
 * 时间:2018/9/14  10:14
 * 作者:Cao Jiangtao
 * 首页:https://1989jiangtao.github.io/index.html
 ******************/
public class RecycleAdapter extends RecyclerView.Adapter<RecycleAdapter.MyViewHolder> {

    public static final String EXTRA_DEVICE = "extra_device";

    List<BluetoothDevice> deviceList = new ArrayList<>();

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        Log.d("CJT"," RecycleAdapter --- onCreateViewHolder --  ");
        return new RecycleAdapter.MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_device_layout,parent,false));
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.deviceName.setText(getItem(position).getName());
        holder.deviceAddress.setText(getItem(position).getAddress());
        // 为列表添加点击事件,传入的参数是当前项的蓝牙设备BluetoothDevice
        holder.deviceItem.setOnClickListener(new itemClick(getItem(position)));
    }

    @Override
    public int getItemCount() {
        return deviceList.size();
    }

    /***
     * 获取指定位置的元素
     * @param position
     * @return
     */
    private BluetoothDevice getItem(int position){
        return deviceList.get(position);
    }

    /***
     * 数据集合改变的方法
     * @param deviceList
     */
    public void notifyData(List<BluetoothDevice> deviceList){
        Log.d("CJT" , "notifyData == %%%%%%%%%%%%%% ");
        this.deviceList = deviceList ;
        this.notifyDataSetChanged();
    }


    class MyViewHolder extends RecyclerView.ViewHolder{

        ConstraintLayout deviceItem ; // item的单个布局
        TextView deviceName ; // 设备名称
        TextView deviceAddress; // 设备地址

        public MyViewHolder(View itemView) {
            super(itemView);
            deviceItem = itemView.findViewById(R.id.item_layout);
            deviceName = itemView.findViewById(R.id.item_device_name);
            deviceAddress = itemView.findViewById(R.id.item_device_address);
        }
    }

    /**点击事件内部类**/
    private class itemClick implements View.OnClickListener {

        private BluetoothDevice device ;

        public itemClick(BluetoothDevice item) {
            this.device = item ;
        }

        @Override
        public void onClick(View v) {
            // 设置返回数据
            Intent intent = new Intent();
            intent.putExtra(EXTRA_DEVICE, device);
            Log.d("CJT" , "device.getAddress() == "+device.getAddress());
            // 设置返回值并结束程序
            ((Activity)v.getContext()).setResult(Activity.RESULT_OK, intent);
            ((Activity)v.getContext()).finish();
        }
    }
}

 (6)还有一个表盘布局的文件,关于自定义View,有不明白的同学可以自行去学习下,我这里就不深入讲解了。

4.小结。

     主要的代码就是上面给大家展示的部分,由于使用的第三方的蓝牙库,这个蓝牙库呢还有一个比较有缺陷的地方就是可以扫描到经典蓝牙的设备,却没有相应的connect方法,更不能去读写了,所以后面我会再去完善连接经典蓝牙模块的部分,本次蓝牙模块我使用的是蓝牙BLE模块cc2541。

     好了,本篇就到此为止了,后面代码上传了GitHub我会跟进更新地址,另外下一篇我会主要讲解和演示单片机接收数据的部分。
 

2013-01-17 22:24:45 xiluoduyu 阅读数 5189
  • 单片机控制第一个外设-LED灯-第1季第6部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第6个课程,主要讲解LED的工作原理和开发板原理图、实践编程等,通过学习目的是让大家学会给单片机编程控制LED灯,并且为进一步学习其他外设打好基础。

    4003 人正在学习 去看看 朱有鹏

       本文来自CSDN博客,转载请标明出处http://blog.csdn.net/xiluoduyu/

       最近辛辛苦苦的终于把落下了七八个月的Android蓝牙开发搞完了,其中的痛苦不堪回首啊。感谢那些帮了大忙的老师和同学们!辛苦归辛苦,收获总是有的,因而在此总结一下关键所在,以勉励自己!

       网上说的好多的Android蓝牙开发多是基于同类设备之间的通讯,大多需要建立一个服务端和客户端,客户端连接服务器需要服务端的UUID号。而单片机上的蓝牙模块与手机蓝牙连接有点不同,它的UUID号需要用well-know的“"00001101-0000-1000-8000-00805F9B34FB”,AndroidAPI里面强调过。

       接下来的通讯就简单了,跟一般的蓝牙通讯差不多,网上很多文章都有详细介绍,所以偷偷懒啦。。。

       由于蓝牙串口端的开发不是偶负责的,就不好说啥了。不过目测同学的开发调试过程好像也蛮简单的,就是个串口通信过程。我们用的是HWW-S1800蓝牙模块,因为手机端有蓝牙,所以实际上我们只用到了从模块而已,价格也不贵,主从模块才不到三百块。这个模块功能比较简单,但对我们足够用了,而且使用起来很简单。只需要将蓝牙串口模块和单片机连接起来,再在Keil和STC-ISP两个软件上开发就行了,好像这两开发环境都是c语言的,也比较简单,接收一下蓝牙模块发过来的字符信息,判断和处理一下给个回应就行了。具体过程还得请教学过单片机开发的同学了。

       (没想到会有怎么多的人需要,呵呵,看来当初的选题也还好呢,文档放到我的资源里面了 ,需要的可以下载)

        

没有更多推荐了,返回首页