2015-08-05 15:19:59 cjc_karen 阅读数 5051
  • 单片机控制第一个外设-LED灯-第1季第6部分

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

    3995 人正在学习 去看看 朱有鹏
·简介:由于业务关系,需要开发一个向单片机发送数据的应用,不过由于需求很简单,只  需要发送数据即可,所以该dome的功能只有发送数据功能,并没有对输入的数据   做进一步的处理

这里有蓝牙开发的基本资料,建议蓝牙基础欠缺的可以先悉知一下:http://pan.baidu.com/s/1ntrHntB

这里是我的dome工程目录





布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">



    <TextView
        android:id="@+id/textView1"
        android:text="@string/hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/textView2"
        android:layout_below="@id/textView1"
        android:text="@string/hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/textView3"
        android:layout_below="@id/textView2"
        android:text="@string/hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />


    <SeekBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/seekBar1"
        android:layout_below="@+id/textView2"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_marginTop="108dp" />
    <SeekBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/seekBar2"
        android:layout_centerVertical="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true" />

</RelativeLayout>

总的来说,通讯过程是:
1、得到本地蓝牙设备,
 void initOpenBluetooth() {
        //取得本地蓝牙设备
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

        if (mBluetoothAdapter == null) {
            Toast.makeText(this, "蓝牙不可用", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        //如果蓝牙没打开,提醒用户打开蓝牙
        if (!mBluetoothAdapter.isEnabled()) {
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(intent, REQUEST_ENABLE_BT);
        }else {
            if(mBluetoothAdapter.isEnabled()){
                initGetDevice();
            }
        }

    }

2、得到远程设备,
private void initGetDevice(){
        //获取远程BlueToothDevice
        device = mBluetoothAdapter.getRemoteDevice(address);
        textView3.setText(device.getAddress() + ":" + device.getName());

        if (mChatService == null) {
            mChatService = new BluetoothChatService(this);
            if (D) Log.v(TAG, "当前蓝牙状态:" + mChatService.getState());

            mChatService.connect(device);//启动了蓝牙连接线程,
            if (D) Log.v(TAG, "当前蓝牙状态:" + mChatService.getState());
        }
    }

3、找到已配对的设备,连接,连接需要唯一的通讯信道UUID
 /*********************    连接线程   ****************/
    private class ConnectThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;

        public ConnectThread(BluetoothDevice device) {
            mmDevice = device;
            BluetoothSocket tmp = null;

            // Get a BluetoothSocket for a connection with the
            // given BluetoothDevice
            try {
                tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
            } catch (IOException e) {
                Log.e(TAG, "create() failed", e);
            }
            mmSocket = tmp;
        }

        public void run() {
            Log.i(TAG, "BEGIN mConnectThread");
            setName("ConnectThread");

            // Always cancel discovery because it will slow down a connection
            mAdapter.cancelDiscovery();

            // Make a connection to the BluetoothSocket
            try {
                // This is a blocking call and will only return on a
                // successful connection or an exception
                mmSocket.connect();
            } catch (IOException e) {
                connectionFailed();
                // Close the socket
                try {
                    mmSocket.close();
                } catch (IOException e2) {
                    Log.e(TAG, "unable to close() socket during connection failure", e2);
                }
                /*// Start the service over to restart listening mode
                BluetoothChatService.this.start();*/
                return;
            }

            // Reset the ConnectThread because we're done
            synchronized (BluetoothChatService.this) {
                mConnectThread = null;
            }

            // Start the connected thread
            connected(mmSocket, mmDevice);
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of connect socket failed", e);
            }
        }
    }

3、连接后的连接管理,通讯
 /*********************   连接管理,通讯线程   **********************/
    private class ConnectedThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final InputStream mmInStream;
        private final OutputStream mmOutStream;

        public ConnectedThread(BluetoothSocket socket) {
            Log.d(TAG, "create ConnectedThread");
            mmSocket = socket;
            InputStream tmpIn = null;
            OutputStream tmpOut = null;

            // Get the BluetoothSocket input and output streams
            try {
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) {
                Log.e(TAG, "temp sockets not created", e);
            }

            mmInStream = tmpIn;
            mmOutStream = tmpOut;
        }

        public void run() {
            Log.i(TAG, "BEGIN mConnectedThread");
            byte[] buffer = new byte[1024];
            int bytes;

            // Keep listening to the InputStream while connected
            while (true) {
                try {
                    // Read from the InputStream
                    bytes = mmInStream.read(buffer);

                } catch (IOException e) {
                    Log.e(TAG, "disconnected", e);
                    connectionLost();
                    break;
                }
            }
        }

        /**
         * Write to the connected OutStream.
         * @param buffer  The bytes to write
         */
        public void write(byte[] buffer) {
            try {
                mmOutStream.write(buffer);

            } catch (IOException e) {
                Log.e(TAG, "Exception during write", e);
            }
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of connect socket failed", e);
            }
        }
    }

4、最后一步,修复一些小Bug,然后可以根据自己的业务需求来更改代码。
     本来还有一个等待连接线程的,但该次业务无此需求,故不列出来了。

5:这里是整个dome的代码:
MainActivity:
public class MainActivity extends ActionBarActivity implements SeekBar.OnSeekBarChangeListener {

    // Debugging
    private static final String TAG = "MainActivity";
    private static final boolean D = true;

    //定义打开蓝牙的请求码
    private static final int REQUEST_ENABLE_BT = 2;

    // 本地蓝牙设备
    private BluetoothAdapter mBluetoothAdapter = null;
    // Member object for the chat services
    private BluetoothChatService mChatService = null;

    BluetoothDevice device;
    private static String address = "30:14:11:12:38:01";

    SeekBar seekBar1,seekBar2;
    TextView textView1,textView2,textView3;


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

        if(D) Log.v(TAG, "+++ ON CREATE +++");
        System.out.println("+++ ON CREATE +++");

        seekBar1 = (SeekBar) findViewById(R.id.seekBar1);
        seekBar2 = (SeekBar) findViewById(R.id.seekBar2);
        textView1 = (TextView) findViewById(R.id.textView1);
        textView2 = (TextView) findViewById(R.id.textView2);
        textView3 = (TextView) findViewById(R.id.textView3);

        //两种方式监听
        seekBar1.setOnSeekBarChangeListener(this);
        seekBar2.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                textView2.setText("当前值:" + progress);

                //  发送数据
                String msg = String.valueOf(progress);

                if (mChatService.getState() == BluetoothChatService.STATE_CONNECTED) {

                    mChatService.write(getHexBytes(msg));

                }else {
                    System.out.println("没有连接好");
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });

        initOpenBluetooth();

    }

    /***************  再按一次退出程序  ****************/
    private long _doubleClickedTime = 0;
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            long clickedTime = System.currentTimeMillis();
            if (clickedTime - _doubleClickedTime <= 2000) {
                // 两次点击退出
                finish();
            } else {
                Toast.makeText(getApplicationContext(), "再按一次退出程序", Toast.LENGTH_SHORT).show();
                _doubleClickedTime = clickedTime;
            }
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }


    /***********  退出后,把蓝牙的连接注销   ***********/
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mChatService.stop();
        finish();
    }


    /*************  当“打开蓝牙的dialog结束后,要回调的函数”    ************/
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if(requestCode == REQUEST_ENABLE_BT){

            initGetDevice();

        }
    }


    /*******************  initOpenBluetooth    *********************/
    void initOpenBluetooth() {
        //取得本地蓝牙设备
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

        if (mBluetoothAdapter == null) {
            Toast.makeText(this, "蓝牙不可用", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        //如果蓝牙没打开,提醒用户打开蓝牙
        if (!mBluetoothAdapter.isEnabled()) {
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(intent, REQUEST_ENABLE_BT);
        }else {
            if(mBluetoothAdapter.isEnabled()){
                initGetDevice();
            }
        }

    }


    /********************  intDetDevice  **********************/
    private void initGetDevice(){
        //获取远程BlueToothDevice
        device = mBluetoothAdapter.getRemoteDevice(address);
        textView3.setText(device.getAddress() + ":" + device.getName());

        if (mChatService == null) {
            mChatService = new BluetoothChatService(this);
            if (D) Log.v(TAG, "当前蓝牙状态:" + mChatService.getState());

            mChatService.connect(device);//启动了蓝牙连接线程,
            if (D) Log.v(TAG, "当前蓝牙状态:" + mChatService.getState());
        }
    }


    /******************** 字符串转换为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;
    }


    /********************************  拖动发送数据   ********************************/
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

        textView1.setText("当前值:" + progress);
        //  发送数据
        String msg = String.valueOf(progress);

        if (mChatService.getState() == BluetoothChatService.STATE_CONNECTED) {

            mChatService.write(getHexBytes(msg));

        }else {
            System.out.println("没有连接好");
        }
    }
    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {

    }
    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {

    }

/******************************   标题栏  ************************************/
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

工具类:BluetoothChatService:
public class BluetoothChatService {
    // Debugging
    private static final String TAG = "BluetoothChatService";
    private static final boolean D = true;

    // Name for the SDP record when creating server socket
    private static final String NAME = "BluetoothChat";

    // Unique UUID for this application

    private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

    // Member fields
    private final BluetoothAdapter mAdapter;

    private AcceptThread mAcceptThread;
    private ConnectThread mConnectThread;
    private ConnectedThread mConnectedThread;
    private int mState;

    // Constants that indicate the current connection state
    public static final int STATE_NONE = 0;       // we're doing nothing
    public static final int STATE_LISTEN = 1;     // now listening for incoming connections
    public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
    public static final int STATE_CONNECTED = 3;  // now connected to a remote device


    public BluetoothChatService(Context context) {
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mState = STATE_NONE;
    }


    private synchronized void setState(int state) {
        if (D) Log.d(TAG, "setState() " + mState + " -> " + state);
        mState = state;


    }

    public synchronized int getState() {
        return mState;
    }

    /***************  本来还有个监听线程入口 和 监听线程。此应用没用到  ***********/

    /**********************   启动连接线程  ************************/
    public synchronized void connect(BluetoothDevice device) {
        if (D) Log.d(TAG, "connect to: " + device);

        // Cancel any thread attempting to make a connection
      /*  if (mState == STATE_CONNECTING) {
            if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
        }
*/
        if (mState == STATE_NONE) {
            if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
        }

        // Cancel any thread currently running a connection
        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}

        // Start the thread to connect with the given device
        mConnectThread = new ConnectThread(device);
        mConnectThread.start();
        setState(STATE_CONNECTING);
    }


    /*******************  启动连接管理,通讯线程的入口 **************************/
    public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {
        if (D) Log.d(TAG, "connected");

        // Cancel the thread that completed the connection
        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}

        // Cancel any thread currently running a connection
        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}

        // Cancel the accept thread because we only want to connect to one device
        if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}

        // Start the thread to manage the connection and perform transmissions
        mConnectedThread = new ConnectedThread(socket);
        mConnectedThread.start();
        setState(STATE_CONNECTED);
    }

    /******************   注销掉所有  *****************/
    public synchronized void stop() {
        if (D) Log.d(TAG, "stop");
        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
        if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}
        setState(STATE_NONE);
    }

    /***************  写出给通许线程,由线程来发送数据  ***********/
    public void write(byte[] out) {
        // Create temporary object
        ConnectedThread r;
        // Synchronize a copy of the ConnectedThread
        synchronized (this) {
            if (mState != STATE_CONNECTED) return;
            r = mConnectedThread;
        }
        // Perform the write unsynchronized
        r.write(out);
    }


    private void connectionFailed() {
        setState(STATE_LISTEN);
    }

    private void connectionLost() {
        setState(STATE_LISTEN);
    }


   /*********************  等待监听线程,此应用没用到  ****************/
    private class AcceptThread extends Thread {
        // The local server socket
        private final BluetoothServerSocket mmServerSocket;

        public AcceptThread() {
            BluetoothServerSocket tmp = null;

            // Create a new listening server socket
            try {
                tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
            } catch (IOException e) {
                Log.e(TAG, "listen() failed", e);
            }
            mmServerSocket = tmp;
        }

        public void run() {
            if (D) Log.d(TAG, "BEGIN mAcceptThread" + this);
            setName("AcceptThread");
            BluetoothSocket socket = null;

            // Listen to the server socket if we're not connected
            while (mState != STATE_CONNECTED) {
                try {
                    // This is a blocking call and will only return on a
                    // successful connection or an exception
                    socket = mmServerSocket.accept();
                } catch (IOException e) {
                    Log.e(TAG, "accept() failed", e);
                    break;
                }

                // If a connection was accepted
                if (socket != null) {
                    synchronized (BluetoothChatService.this) {
                        switch (mState) {
                            case STATE_LISTEN:
                            case STATE_CONNECTING:
                                // Situation normal. Start the connected thread.
                                connected(socket, socket.getRemoteDevice());
                                break;
                            case STATE_NONE:
                            case STATE_CONNECTED:
                                // Either not ready or already connected. Terminate new socket.
                                try {
                                    socket.close();
                                } catch (IOException e) {
                                    Log.e(TAG, "Could not close unwanted socket", e);
                                }
                                break;
                        }
                    }
                }
            }
            if (D) Log.i(TAG, "END mAcceptThread");
        }

        public void cancel() {
            if (D) Log.d(TAG, "cancel " + this);
            try {
                mmServerSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of server failed", e);
            }
        }
    }


   /*********************    连接线程   ****************/
    private class ConnectThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;

        public ConnectThread(BluetoothDevice device) {
            mmDevice = device;
            BluetoothSocket tmp = null;

            // Get a BluetoothSocket for a connection with the
            // given BluetoothDevice
            try {
                tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
            } catch (IOException e) {
                Log.e(TAG, "create() failed", e);
            }
            mmSocket = tmp;
        }

        public void run() {
            Log.i(TAG, "BEGIN mConnectThread");
            setName("ConnectThread");

            // Always cancel discovery because it will slow down a connection
            mAdapter.cancelDiscovery();

            // Make a connection to the BluetoothSocket
            try {
                // This is a blocking call and will only return on a
                // successful connection or an exception
                mmSocket.connect();
            } catch (IOException e) {
                connectionFailed();
                // Close the socket
                try {
                    mmSocket.close();
                } catch (IOException e2) {
                    Log.e(TAG, "unable to close() socket during connection failure", e2);
                }
                /*// Start the service over to restart listening mode
                BluetoothChatService.this.start();*/
                return;
            }

            // Reset the ConnectThread because we're done
            synchronized (BluetoothChatService.this) {
                mConnectThread = null;
            }

            // Start the connected thread
            connected(mmSocket, mmDevice);
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of connect socket failed", e);
            }
        }
    }

  /*********************   连接管理,通讯线程   **********************/
    private class ConnectedThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final InputStream mmInStream;
        private final OutputStream mmOutStream;

        public ConnectedThread(BluetoothSocket socket) {
            Log.d(TAG, "create ConnectedThread");
            mmSocket = socket;
            InputStream tmpIn = null;
            OutputStream tmpOut = null;

            // Get the BluetoothSocket input and output streams
            try {
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) {
                Log.e(TAG, "temp sockets not created", e);
            }

            mmInStream = tmpIn;
            mmOutStream = tmpOut;
        }

        public void run() {
            Log.i(TAG, "BEGIN mConnectedThread");
            byte[] buffer = new byte[1024];
            int bytes;

            // Keep listening to the InputStream while connected
            while (true) {
                try {
                    // Read from the InputStream
                    bytes = mmInStream.read(buffer);

                } catch (IOException e) {
                    Log.e(TAG, "disconnected", e);
                    connectionLost();
                    break;
                }
            }
        }

        /**
         * Write to the connected OutStream.
         * @param buffer  The bytes to write
         */
        public void write(byte[] buffer) {
            try {
                mmOutStream.write(buffer);

            } catch (IOException e) {
                Log.e(TAG, "Exception during write", e);
            }
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of connect socket failed", e);
            }
        }
    }
}


希望这边文章可以帮到阅读者

顺便一提:参考的出处的时候太久远了,忘了出处,在这里给他(他)说声:不好意思。。。


2018-09-18 17:07:04 u010898329 阅读数 2656
  • 单片机控制第一个外设-LED灯-第1季第6部分

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

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

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

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

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

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

(5)安卓手机与蓝牙模块联合调试(五)-- 编写自己的蓝牙控制界面控制单片机(上篇,Android 代码实现)

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


接着上篇继续,本篇主要是完善单片机端的代码部分。废话不多说,开始飙车了。 

1.看下初步的演示效果

 

2.主要代码部分,main.c

 (1)单片机端的代码主要是在之前的代码基础上做了修改,多增加了几条指令。

/****************************************
**       蓝牙串口接收数据
** 
**   作者:江涛
**   时间:2018/08/31
**   描述:串口发送数据兼用OLED显示
****************************************/
#include "STC89C5xRC_RDP.h"
#include "string.h"     // 要使用字符串对比函数,需要引入该头文件
#include "OLED.h"       // OLED显示屏头文件
#include "DS18b20.h"

// 定义系统时钟和串口波特率
#define FOSC 11059200L      // 系统时钟
#define BAUD 9600           // 串口波特率

/******变量声明*********/ 
char RECEIVED_CMD[10] ;       	// 暂定为10字节的指令
char RECEIVED_INDEX ;         	// 数组指示索引,当接收到一个数据之后,索引会跟随增加
unsigned char flag = 0 ;      	// 数据接收的标志位
unsigned char power_flag = 0 ;  // 电源开关的标志位

/******命令常量*******/
code const char* LED_ON = "ON\r\n" ;					// 电源开指令
code const char* LED_OFF = "OFF\r\n" ;				// 电源关指令
code const char* LED_01_ON = "L1ON\r\n" ;			// 灯组01开指令
code const char* LED_01_OFF = "L1OFF\r\n" ;		// 灯组01关指令
code const char* LED_02_ON = "L2ON\r\n" ;			// 灯组02开指令
code const char* LED_02_OFF = "L2OFF\r\n" ;		// 灯组02关指令
code const char* FAN_ON = "FANON\r\n" ;				// 风扇开指令,使用RGB灯循环来模拟风扇工作
code const char* FAN_OFF = "FANOFF\r\n" ;			// 风扇关指令
code const char* FAILD = "power_off\r\n" ;		// 返回失败原因,电源关闭了

extern unsigned int tvalue;			//温度值
extern unsigned char tflag;			//温度正负标志
unsigned char disdata[7]; 			// 温度数据,使用8字节数组来存储

char color_table[8][3] = { // 颜色表
	{0,0,0},{0,0,1},{0,1,0},{0,1,1},{1,0,0},{1,0,1},{1,1,0},{1,1,1}
};

/*******函数声明*********/
void Init_UART(); // 初始化串口
void UART_SendData(char dat); // 串口发送数据
void UART_SendStr(char* str); // 串口发送字符串
void RGB_Display(int index);  // RGB灯显示

//void split(char str[],char delims[]); // 字符串截取函数

void ds1820disp(); 	// 温度显示

void test_Fan(char flag);		// 模拟测试风扇运行
void Delay1ms();						//@11.0592MHz

/***********端口定义*************/
sbit LED_R = P0^0 ;
sbit LED_G = P0^1 ;
sbit LED_B = P0^2 ;

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

    unsigned int temperature , old ; // 保存温度数值
	
	Init_UART();  // 串口初始化
	
	LCD_Init();  // OLED 初始化
	LCD_CLS();   // 清屏
	
	LCD_P8x16Str(0 , 0 , "TEMP:");  // 温度开始位置
	
	temperature = ReadTemperature(); 
	old = temperature ; 
	ds1820disp(); // 显示温度
	UART_SendStr(disdata); // 向串口发送数据
	LCD_P8x16Str(5*8 , 0 , disdata); // 显示温度
	
	while(1)
	{
			
	temperature=ReadTemperature();  // 读取一次新的温度
    if (temperature != old )	  
	  {	 
			old = temperature;
			ds1820disp(); // 显示温度
			UART_SendStr(disdata); // 向串口发送数据
			LCD_P8x16Str(5*8 , 0 , disdata); // 显示温度

	  }
		
		if(flag) // 接收数据完毕一次,就会进入中断一次
		{
			flag = 0 ; // 将标志位还原,使得串口又可以重新接收数据
					
			if(strcmp(RECEIVED_CMD , LED_ON) == 0)
			{
				P2 = 0xFF ; // P2口全亮
				power_flag = 1 ; // 标志电源打开
			}
			else if(strcmp(RECEIVED_CMD , LED_OFF) == 0)
			{
				P2 = 0x00 ; // P2口全灭
				power_flag = 0 ;// 标志电源关闭
			}
			else if(strcmp(RECEIVED_CMD , LED_01_ON) == 0)
			{
				if(power_flag) 		// 如果电源开关是关闭的,就不执行以下操作
					P2 = 0x55 ; 		// ‭01010101‬ 为1位置的灯是亮着的
				else
					UART_SendStr(FAILD); // 向串口发送失败原因
			}
			else if(strcmp(RECEIVED_CMD , LED_01_OFF) == 0)
			{
				P2 = P2^0x55 ; // P2口01010101相应位置的灯要全灭,所以使用异或操作
			}
			else if(strcmp(RECEIVED_CMD , LED_02_ON) == 0)
			{
				if(power_flag) 		// 如果电源开关是关闭的,就不执行以下操作
					P2 = 0xAA ; 		// ‭10101010‬ 为1位置的灯是亮着的
				else
					UART_SendStr(FAILD); // 向串口发送失败原因
			}
			else if(strcmp(RECEIVED_CMD , LED_02_OFF) == 0)
			{
				P2 = P2^0xAA ; // P2口10101010相应位置的灯要全灭,所以使用异或操作
			}
			else if(strcmp(RECEIVED_CMD , FAN_ON) == 0)
			{
				test_Fan(1);
			}
			else if(strcmp(RECEIVED_CMD , FAN_OFF) == 0)
			{
				test_Fan(0);
			}			

			
			// 用完之后要记得数组清零处理
      RECEIVED_INDEX = 0 ;        // 数组指引复位
      memset(RECEIVED_CMD,0,10);  // 清0数组
		}
	}
}

/******************
** 初始化串口
*******************/
void Init_UART()
{
	SCON = 0x50;                     //设置8位数据位
	TMOD = 0x20;                     //8位自动重载
	TH1 = TL1 = -(FOSC/12/32/BAUD);  //设置重载值
	TR1 = 1;                         //使能时钟
	ES = 1;                          //使能串口中断
	EA = 1;                          //开中断开关
}

/********************
** 串口中断处理
*********************/
void UART_Isr() interrupt 4 using 1
{
	// 串口接收中断处理
	if(RI) 
	{
		RI = 0 ;                              // 清除中断标志位
		RECEIVED_CMD[RECEIVED_INDEX] = SBUF ; // 保存串口接收的数据
		if(RECEIVED_CMD[RECEIVED_INDEX] == 0x0A ){ // 遇到了结束符号
			 flag = 1 ;           // 接收结束,到循环中处理接收的数据
		}else {
			 RECEIVED_INDEX ++ ;   // 继续接收数据
		}
	}

	// 串口发送中断处理
	if(TI)
	{
		TI = 0 ;  // 清发送中断标志位
	}
		
}

/**************************
** 通过串口发送一位数据
***************************/
void UART_SendData(char dat)
{
	ES = 0 ;      // 串口工作的时候禁止中断
	SBUF = dat ;  // 待发送的数据放到SBUF中
	while(!TI) ;  // 等待发送完毕
	TI = 0 ;      // 清TI中断
	ES = 1 ;      // 打开中断
}

/*****************************
**  通过串口发送字符串
******************************/
void UART_SendStr(char *str)
{
	do
	{
		UART_SendData(*str);
	}while(*str ++  != '\0' ); // 一直到字符串结束
}

/****************************
**  显示RGB灯的颜色
*****************************/
void RGB_Display(int index)
{
	LED_R = color_table[index%8][0];
	LED_G = color_table[index%8][1];
	LED_B = color_table[index%8][2];
}

void Delay1ms()		//@11.0592MHz
{
	unsigned char i, j;

	_nop_();
	i = 2;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}

/****************
** 模拟风扇运行
****************/
void test_Fan(char flag)
{
	unsigned int t , count = 500 ;

	if(!flag) return ; // 如果传入的是0,表示停止,不往下继续进行
	
	for(t=0 ; t<8 ; t++)
	{
		RGB_Display(t);		// 风扇
		for( ; count > 0 ; count --)
			Delay1ms();
	}	
}

///***********************************
//** 字符串截取函数
//***********************************/
//void split(char str[],char delims[])
//{
//	char *result = NULL; 
//	result = strtok( str, delims );  
//	while( result != NULL ) {  
//		 result = strtok( NULL, delims );  
//	}
//} 

/***************************
** 温度值显示
***************************/
void ds1820disp()
{ 	
	  unsigned char flagdat;
	
		if(tflag==0)
//			flagdat=0x20;//正温度不显示符号
		  flagdat=0x2b;//正温度显示符号
		else
		  flagdat=0x2d;//负温度显示负号:-
		
//		disdata[0] = flagdat; //符号位
	
		disdata[1]=tvalue/1000+0x30;//百位数
		disdata[2]=tvalue%1000/100+0x30;//十位数
		disdata[3]=tvalue%100/10+0x30;//个位数
		disdata[4]= 0x2E ;//小数点
		disdata[5]=tvalue%10/1+0x30;//小数位

		if(disdata[1]==0x30) // 如果百位为0
		{
			disdata[0]= 0x20; // 第一位不显示
			disdata[1]= flagdat; // 百位显示符号
			if(disdata[2]==0x30) //如果百位为0,十位为0
			{
				disdata[1]=0x20; // 百位不显示符号
				disdata[2]=flagdat; // 10位显示符号
			}
		}
}

代码中有部分逻辑处理还需要完善,其中有个电源开关和两组灯控制部分的逻辑,理论上电源关闭的时候,操作两个灯是无效的,为了演示用我将灯关闭的消息发送给了安卓端,让安卓端进行提示,但是单片机端这部分的代码逻辑还有要完善的地方。

(2)安卓端修改的部分

    @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 , lamp01.isIconEnabled() ? "L1ON\r\n" :"L1OFF\r\n");
                break;
            case R.id.sw_lamp_02: // 灯组02
                lamp02.switchState();
                lamp02Name.setText(lamp02.isIconEnabled() ? "灯组1开" : "灯组1关");
                writeCmd(MAC , serviceUuid , characterUuid , lamp02.isIconEnabled() ? "L2ON\r\n" :"L2OFF\r\n");
                break;
            case R.id.sw_power: // 电源
                powerSw.switchState();
                powerName.setText(powerSw.isIconEnabled() ? "电源开" : "电源关");
                writeCmd(MAC , serviceUuid , characterUuid , powerSw.isIconEnabled() ? "ON\r\n" :"OFF\r\n");
                break;
            case R.id.sw_fan: // 风扇
                fanSw.switchState();
                fanName.setText(fanSw.isIconEnabled() ? "风扇开" : "风扇关");
                writeCmd(MAC , serviceUuid , characterUuid , fanSw.isIconEnabled() ? "FANON\r\n" :"FANOFF\r\n");
                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) {
                    Log.d("CJT" , "getTemperature -- value2Str -- :" + new String(value));
                    if(new String(value).contains("power_off\r\n")){
                        Toast.makeText(MainActivity.this , "请打开电源开关",Toast.LENGTH_SHORT).show();
                        return;
                    }
                    String hexStr = bytesToHexString(value);
                    Log.d("CJT","getTemperature -- hexStr -- : "+hexStr);
                    if(!hexStr.contains("2e")) return ; // 不包含小数点
                    int beginIndex = hexStr.indexOf("2b") + 2; // 加号开始截取,并且跳过加号
                    int endIndex = hexStr.indexOf("2e") + 4 ;  // 小数点开始截取
                    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){

                }
            }
        });
    }

 注释代码中写得比较清楚了,这里当手机收到的指令中含有单片机发送过来的`"power-off"`指令的时候,表示电源开关关闭,需要提示先打开电源开关,这部分的逻辑需要大家去完善下,我这里就先留个BUG给大家。

3.小结

        最后,单片机通过蓝牙模块和手机通信其实就是通过蓝牙模块的串口通讯,如果熟悉单片机底层的串口通讯,那么底层蓝牙部分就很简单了,手机端的编程需要掌握java和安卓编程才能进行,如果没有太多基础,大家可以找些入门教程看下。

        最后希望该系列的博客能给大家一些启发和建议,行文至此,也了了我(从前的电子工程师,如今的安卓工程师)一个心愿。程序学习的路很漫长,希望大家都能找到自己喜欢的并坚持下去。

 

 

2019-12-06 13:11:35 qq_39827677 阅读数 26
  • 单片机控制第一个外设-LED灯-第1季第6部分

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

    3995 人正在学习 去看看 朱有鹏
使用安卓设备通过蓝牙模块控制单片机非常容易做到智能家居等场景的实现,接下来我以安卓开发和HC-05蓝牙模块介绍完整的通信过程以及相关代码实现。

获取相应权限

<uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <!-- 6.0以上需要加的额外权限 -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BROADCAST_STICKY" />
    <uses-feature
        android:name="android.hardware.bluetooth_le"
        android:required="true" />

注册蓝牙广播

IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        registerReceiver(receiver, filter);
        filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        registerReceiver(receiver, filter);

注册广播接收者

// 注册广播接收者
    private BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context arg0, Intent intent) {
            // 获取到广播的action
            String action = intent.getAction();
            // 判断广播是搜索到设备还是搜索完成
            if (action.equals(BluetoothDevice.ACTION_FOUND)) {
                // 找到设备后获取其设备
                BluetoothDevice device = intent
                        .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                // 判断这个设备是否是之前已经绑定过了,如果是则不需要添加,在程序初始化的时候已经添加了
                if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
                    // 设备没有绑定过,则将其保持到arrayList集合中
                    bluetoothDevices.add(device.getName() + ":"
                            + device.getAddress() + "\n");
                    // 更新字符串数组适配器,将内容显示在listView中
                    arrayAdapter.notifyDataSetChanged();
                }
            } else if (action
                    .equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
                setTitle("搜索完成");
            }
        }
    };

搜索蓝牙设备

mBluetoothAdapter.startDiscovery();

发送数据

private void sendCode(byte[] code)
    {
        try {
            // 判断客户端接口是否为空
            if (clientSocket == null) {
                // 获取到客户端接口
                clientSocket = selectDevice
                        .createRfcommSocketToServiceRecord(MY_UUID);
                // 向服务端发送连接
                clientSocket.connect();
                // 获取到输出流,向外写数据
                os = clientSocket.getOutputStream();
                if (commected) {
                    commected = false;
                    // 实例接收客户端传过来的数据线程
                    thread = new ConnectedThread(clientSocket);
                    // 线程开始
                    thread.start();
                }
            }
            // 判断是否拿到输出流
            if (os != null) {
                os.write(code);
            }
            Toast.makeText(BuletoothClientActivity.this, "发送信息成功,请查收", Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            e.printStackTrace();
            // 如果发生异常则告诉用户发送失败
            Toast.makeText(BuletoothClientActivity.this, "发送信息失败", Toast.LENGTH_SHORT).show();
        }

    }

接收数据线程

private class ConnectedThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final InputStream mmInStream;
        private final OutputStream mmOutStream;

        public ConnectedThread(BluetoothSocket socket) {
            Log.d("logcat", "create ConnectedThread");
            mmSocket = socket;
            InputStream tmpIn = null;
            OutputStream tmpOut = null;

            try {
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) {
                Log.d("logcat", "temp sockets not created" + e);
            }
            mmInStream = tmpIn;
            mmOutStream = tmpOut;
        }

        public void run() {
            if (Thread.interrupted()) {
                Log.d("logcat", "return");
                return;
            }
            Log.d("logcat", "BEGIN mConnectedThread");
            byte[] buffer = new byte[128];
            int bytes;

            while (true) {
                synchronized (this) {

                    try {
                        while (mmInStream.available() == 0) {
                        }
                        try {
                            Thread.sleep(100);  //当有数据流入时,线程休眠一段时间
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        bytes = mmInStream.read(buffer);
                        Log.d("logcat", "count   " + bytes);
                        Message msg = new Message();
                        msg.obj = new String(buffer, 0, bytes, "utf-8");
                        Log.d("logcat", "data   " + msg.obj);
                        handler.sendMessage(msg);
                    } catch (IOException e) {
                        Log.e("logcat", "disconnected", e);

                        break;
                    }
                }


            }
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e("logcat", "断开连接", e);
            }
        }
    }


}

测试通信

安卓端发送 test
服务端接受到数据,反馈ok客户端
客户端成功接收到消息

将APP安装到安卓手机当作客户端
安卓APP
HC-05蓝牙模块连线如下:
HC-05蓝牙模块连线
使用串口调试工具测试HC-05串口通信
串口调试工具
完整DEMO有需要可以评论区要Github地址

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

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

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

(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我会跟进更新地址,另外下一篇我会主要讲解和演示单片机接收数据的部分。
 

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

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

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

系列博文:

(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。博文更新的话,可能因为工作原因会断断续续,希望大家见谅。如果觉得有帮助或者有设么其他好的建议可以在博文下留言,赞赏码没有别的意思,这是个知识付费的时代,可能付费了才会更珍惜学习的不易,当然也是作者创作的动力所在,哈哈。


 

 

 

 

51单片机与hc06蓝牙模块

博文 来自: baiyuhancooL
没有更多推荐了,返回首页