react-native 中蓝牙连接、下发数据、监听数据

2021-11-30  本文已影响0人  物联白菜

简单封装,根据自己的项目需求自己改动,例如设备的服务uuid,特征值uuid以及协议指令需要双方约定生成等,根据协议来取,官网文档链接:https://dotintent.github.io/react-native-ble-plx/
新建bluetooth.js

/**
 *
 * 这是RN的蓝牙模块-----android**/

import {BleManager} from 'react-native-ble-plx';
import {Buffer} from 'buffer';

global.isConnected = false
global.isBleOpen = false

let isOnlyOne = false

module.exports = {

    Init() {
        this.manager = new BleManager();
        console.log('蓝牙已初始化')
        this.NoticeStateChange()
    },

    /**
     * 蓝牙状态监听*/
    NoticeStateChange() {
        this.manager.onStateChange((state) => {
            console.log('蓝牙状态===', state)
            if (state === 'PoweredOff') {
                global.isConnected = false
                global.isBleOpen = false
                alert('Bluetooth is turned off. Please turn on Bluetooth and proceed to the next step');
            }
            if (state === 'PoweredOn') {
                global.isBleOpen = true
            }
        }, true);
    },

    /**
     * 蓝牙搜索-----默认 5 秒, 如果传入设备名称,就返回所要搜索的设备,如果不传则默认 5秒后 返回搜索得到的列表*/
    SearchBle(deviceName, successCallback, errorCallback, seconds = 5000,) {
        this.timer && clearTimeout(this.timer)
        console.log('开始搜索=====>>>>>')
        let device_list = []
        this.manager.startDeviceScan(null, null, (error, device) => {
            if (error) {
                // Handle error (scanning will be stopped automatically)
                errorCallback(error)
                return
            }
            // console.log('扫描到的设备===', device)
            if (deviceName) {
                // if (device.name === deviceName && device.serviceUUIDs !== null) {
                if (device.localName === deviceName) {
                    // Stop scanning as it's not necessary if you are scanning for one device.
                    device_list.push(device)
                    successCallback(device_list)
                    this.manager.stopDeviceScan();
                    this.timer && clearTimeout(this.timer)
                }
            } else {
                device_list.push(device)
            }
        });
        if(global.isBleOpen){
            this.timer = setTimeout(() => {
                console.warn('扫描结束====停止扫描',)
                successCallback(device_list)
                this.manager.stopDeviceScan();
            }, seconds)
        }
    },


    ConnectBle(macId, successCallback, errorCallback) {
        if(global.isConnected){
            alert('Only one Bluetooth can be connected ')
            errorCallback('device is already connected')
        }else{
            this.manager.connectToDevice(macId,{autoConnect:true}).then((device) => {
                console.log('connect success===', device)
                global.isConnected = true
                // 查找设备的所有服务、特征和描述符。
                this.manager.discoverAllServicesAndCharacteristicsForDevice(device.id).then((data) => {
                    // 如果所有可用的服务和特征已经被发现,它会返回能用的Device对象。
                    console.log('all available services and characteristics device: ', device)
                    this.GetServiceId(device,successCallback,errorCallback)
                }, err => console.log('get all available services and characteristics device fail : ', err))
            }).catch((err) => {
                console.log('connect fail===', err)
                errorCallback(err)
            })
        }
    },

    //获取蓝牙设备的服务uuid,5.0    //服务uuid可能有多个
    GetServiceId(device,successCallback,errorCallback){
        this.manager.servicesForDevice(device.id).then((data) => {
            // 为设备发现的服务id对象数组
            console.log('services list: ', data,)
            this.mac_id = device.id
            let server_uuid = data[2].uuid
            this.GetCharacterIdNotify(server_uuid,successCallback,errorCallback)
        }, err => console.log('services list fail===', err))
    },

    // 根据服务uuid获取蓝牙特征值,开始监听写入和接收
    GetCharacterIdNotify(server_uuid,successCallback,errorCallback) {
        this.manager.characteristicsForDevice(this.mac_id, server_uuid).then((data) => {
            console.log('characteristics list: ', data)
            this.writeId = data[0].serviceUUID //写入id
            this.notifyId = data[0].uuid //接收id
            this.StartNoticeBle()
            this.onDisconnect()
            successCallback(this.mac_id, this.writeId, this.notifyId)
        }, err => {console.log('characteristics list fail:', err);errorCallback(err)})
    },

    // 开启蓝牙监听功能
    StartNoticeBle() {
        console.log('开始数据接收监听', this.mac_id, this.writeId, this.notifyId)
        this.manager.monitorCharacteristicForDevice(this.mac_id, this.writeId, this.notifyId, (error, characteristic) => {
            if (error) {
                console.log('ble response hex data fail:', error)
                global.isConnected = false
                this.DisconnectBle()
            } else {
                let resData = Buffer.from(characteristic.value, 'base64').toString('hex')
                console.log('ble response hex data:', resData);
                this.responseData = resData
            }
        }, 'monitor')
    },



    //  三、 设备返回的数据接收
    BleWrite(value, successCallback, errorCallback) {
        this.responseData = ''
        this.recivetimer && clearInterval(this.recivetimer)
        if(!global.isConnected){
            alert(' Bluetooth not connected ')
            return
        }
        let formatValue = Buffer.from(value, 'hex').toString('base64');
        this.manager.writeCharacteristicWithResponseForDevice(this.mac_id, this.writeId,
            this.notifyId, formatValue, 'write')
            .then(characteristic => {
                let resData = Buffer.from(characteristic.value, 'base64').toString('hex')
                console.log('write success', resData);
                this.recivetimer = setInterval(()=>{
                    if(this.responseData){
                        // console.log('while do ===',this.responseData);
                        successCallback(this.responseData)
                        this.recivetimer && clearInterval(this.recivetimer)
                    }
                },500)
            }, error => {
                console.log('write fail: ', error);
                errorCallback(error)
                // alert('write fail: ', error.reason);
            })
    },

    //只需向设备下发指令,无需接收
    BleWrite1(value, successCallback, errorCallback) {
        if(!global.isConnected){
            if(!isOnlyOne){
                isOnlyOne = true
                alert(' Bluetooth not connected ')
            }
            return
        }
        let formatValue = Buffer.from(value, 'hex').toString('base64');
        this.manager.writeCharacteristicWithoutResponseForDevice(this.mac_id, this.writeId,
            this.notifyId, formatValue, 'write')
            .then(characteristic => {
                let resData = Buffer.from(characteristic.value, 'base64').toString('hex')
                console.log('write success1', resData);
            }, error => {
                console.log('write fail: ', error);
                errorCallback(error)
                // alert('write fail: ', error.reason);
            })
    },

    // write(value,index){
    //     let formatValue;
    //     if(value === '0D0A') {  //直接发送小票打印机的结束标志
    //         formatValue = value;
    //     }else {  //发送内容,转换成base64编码
    //         // formatValue = new Buffer(value, "base64").toString('ascii');
    //         formatValue = Buffer.from(value, 'hex').toString('base64');
    //     }
    //     let transactionId = 'write';
    //     return new Promise( (resolve, reject) =>{
    //         this.manager.writeCharacteristicWithResponseForDevice(this.mac_id,this.writeId,
    //             this.notifyId,formatValue,transactionId)
    //             .then(characteristic=>{
    //                 console.log('write success',value);
    //                 resolve(characteristic);
    //             },error=>{
    //                 console.log('write fail: ',error);
    //                 // this.alert('write fail: ',error.reason);
    //                 reject(error);
    //             })
    //     });
    // },

    //手动停止搜索----在搜索里面,可以自己修改
    StopSearchBle(){
        this.manager.stopDeviceScan();
    },

    // 关闭蓝牙连接
    DisconnectBle() {
        if(!this.mac_id){
            return
        }
        this.manager.cancelDeviceConnection(this.mac_id)
            .then(res=>{
                console.warn('disconnect success',res);
                global.isConnected = false
            })
            .catch(err=>{
                console.warn('disconnect fail',err);
                alert(' Bluetooth disconnection failed :',err)
            })
    },

    //关闭蓝牙模块
    destroy(){
        global.isConnected = false
        this.manager.destroy();
    },

    //监听蓝牙断开
    onDisconnect(){
        this.manager.onDeviceDisconnected(this.mac_id,(error,device)=>{
            if(error){  //蓝牙遇到错误自动断开
                console.log('onDeviceDisconnected','device disconnect',error);
            }else{
                console.log('蓝牙连接状态',device)
            }
        })
    },

    /**==========工具函数=========**/
    // 指令生成
    order(instruction, data) { //传入指令和内容
        // let instruction = '02',data = '00'
        let length = (instruction + data).length / 2
        let hexLength = this.ten2Hex(length) //十六进制长度
        // console.log('lenth==',length,hexLength)

        let str = instruction + data //命令字与数据包字节
        let id = 0
        let sum = 0
        for (let i = 0; i < str.length / 2; i++) {
            id += 2
            let hexstr = str.slice(id - 2, id) //将数据拆分,将其转成10进制累加
            let intstr = this.hex2int(hexstr) //十进制
            sum += intstr
        }

        let checkstr = String(sum)
        let check = checkstr.slice(checkstr.length - 2, checkstr.length) //取得累加和后两位数后,转成16进制校验码
        let hexcheck = this.ten2Hex(check)


        // console.log('最终和为==',sum,check,hexcheck)
        let order = '550000' + '00' + hexLength + instruction + data + hexcheck +
            'AA' //     '00' 与 hexLength 共两个字节, hexLength 最大为ff,即长度暂时不要超过255,若是需要长度超过255的需要判断16进制的数据是否需要自动进位,本项目不需要再多做处理
        return order
    },


    //字符转换成16进制发送到服务器
    Char2Hex(str) {
        if (str === "") {
            return "";
        } else {
            var hexCharCode = '';
            for (var i = 0; i < str.length; i++) {
                hexCharCode += (str.charCodeAt(i)).toString(16);
            }
            return hexCharCode //  tuh:  747568
        }
    },

    //字符转换成16进制发送到服务器[转换放到新数组]
    Char2Hex2(str) {
        if (str === "") {
            return "";
        } else {
            var hexCharCode = [];
            for (var i = 0; i < str.length; i++) {
                hexCharCode.push('0x' + (str.charCodeAt(i)).toString(16));
            }
            hexCharCode.join(",");
            return hexCharCode //tuh:  ["0x74", "0x75", "0x68"]
        }
    },


    // ArrayBuffer转16进度字符串示例
    ab2hex(buffer) {
        const hexArr = Array.prototype.map.call(
            new Uint8Array(buffer),
            function (bit) {
                return ('00' + bit.toString(16)).slice(-2)
            }
        )
        return hexArr.join('')
    },

    // 16进制转buffer
    hexStringToArrayBuffer(str) {
        if (!str) {
            return new ArrayBuffer(0);
        }
        var buffer = new ArrayBuffer(str.length);
        let dataView = new DataView(buffer)
        let ind = 0;
        for (var i = 0, len = str.length; i < len; i += 2) {
            let code = parseInt(str.substr(i, 2), 16)
            dataView.setUint8(ind, code)
            ind++
        }
        return buffer;
    },

    // 10进制转16进制
    ten2Hex(number) {
        return Number(number) < 16 ? '0' + Number(number).toString(16) : Number(number).toString(16)
    },

    // 16进制转10进制整数
    hex2int(hex) {
        var len = hex.length,
            a = new Array(len),
            code;
        for (var i = 0; i < len; i++) {
            code = hex.charCodeAt(i);
            if (48 <= code && code < 58) {
                code -= 48;
            } else {
                code = (code & 0xdf) - 65 + 10;
            }
            a[i] = code;
        }

        return a.reduce(function (acc, c) {
            acc = 16 * acc + c;
            return acc;
        }, 0);
    },

    //16进制转10进制浮点数
    hex2Float(t) {

        t = t.replace(/\s+/g, "");
        if (t == "") {
            return "";
        }
        if (t == "00000000") {
            return "0";
        }
        if ((t.length > 8) || (isNaN(parseInt(t, 16)))) {
            return "Error";
        }
        if (t.length < 8) {
            t = this.FillString(t, "0", 8, true);
        }
        t = parseInt(t, 16).toString(2);
        t = this.FillString(t, "0", 32, true);
        var s = t.substring(0, 1);
        var e = t.substring(1, 9);
        var m = t.substring(9);
        e = parseInt(e, 2) - 127;
        m = "1" + m;
        if (e >= 0) {
            m = m.substr(0, e + 1) + "." + m.substring(e + 1)
        } else {
            m = "0." + this.FillString(m, "0", m.length - e - 1, true)
        }
        if (m.indexOf(".") == -1) {
            m = m + ".0";
        }
        var a = m.split(".");
        var mi = parseInt(a[0], 2);
        var mf = 0;
        for (var i = 0; i < a[1].length; i++) {
            mf += parseFloat(a[1].charAt(i)) * Math.pow(2, -(i + 1));
        }
        m = parseInt(mi) + parseFloat(mf);
        if (s == 1) {
            m = 0 - m;
        }
        return m;
    },

    //浮点数转16进制
    float2Hex(t) {
        if (t == "") {
            return "";
        }
        t = parseFloat(t);
        if (isNaN(t) == true) {
            return "Error";
        }
        if (t == 0) {
            return "00000000";
        }
        var s,
            e,
            m;
        if (t > 0) {
            s = 0;
        } else {
            s = 1;
            t = 0 - t;
        }
        m = t.toString(2);
        if (m >= 1) {
            if (m.indexOf(".") == -1) {
                m = m + ".0";
            }
            e = m.indexOf(".") - 1;
        } else {
            e = 1 - m.indexOf("1");
        }
        if (e >= 0) {
            m = m.replace(".", "");
        } else {
            m = m.substring(m.indexOf("1"));
        }
        if (m.length > 24) {
            m = m.substr(0, 24);
        } else {
            m = this.FillString(m, "0", 24, false)
        }
        m = m.substring(1);
        e = (e + 127).toString(2);
        e = this.FillString(e, "0", 8, true);
        var r = parseInt(s + e + m, 2).toString(16);
        r = this.FillString(r, "0", 8, true);
        return this.InsertString(r, " ", 2).toUpperCase();
    },

    //需要用到的函数
    InsertString(t, c, n) {
        var r = new Array();
        for (var i = 0; i * 2 < t.length; i++) {
            r.push(t.substr(i * 2, n));
        }
        return r.join(c);
    },
    //需要用到的函数
    FillString(t, c, n, b) {
        if ((t == "") || (c.length != 1) || (n <= t.length)) {
            return t;
        }
        var l = t.length;
        for (var i = 0; i < n - l; i++) {
            if (b == true) {
                t = c + t;
            } else {
                t += c;
            }
        }
        return t;
    },

}



使用模块

import React, {Component} from 'react';
import {View, Text, StyleSheet, TouchableOpacity, ScrollView, PermissionsAndroid, Platform} from 'react-native'
import MyBle from './bluetooth'
async function hasAndroidPermission() {
    const permission = PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION;
    const hasPermission = await PermissionsAndroid.check(permission);
    if (hasPermission) {
        return true;
    }

    const status = await PermissionsAndroid.request(permission);
    return status === 'granted';
}

class BluetoothManage extends Component {
    constructor(props) {
        super(props);
        this.state = {
            isShow: false,
            device_list: [],
        }

    }

    async componentDidMount() {
        if (Platform.OS === "android" && !(await hasAndroidPermission())) {
            return;
        }
        await MyBle.Init()
        
        // this.searchName = ''
        // this.searchName = 'unkown'
        this.searchName = 'DRX'
        await MyBle.SearchBle(this.searchName, (data) => {
            console.log('蓝牙搜索到的===', data)
            this.setState({
                device_list: data
            })
        }, (err) => {
            console.log('搜索失败===', err)
        }, 5000)
    }

    research() {
        MyBle.SearchBle(this.searchName, (data) => {
            console.log('蓝牙搜索到的===', data)
            this.setState({
                device_list: data
            })
        }, (err) => {
            console.log('搜索失败===', err)
        }, 5000)
    }

    disconnect() {
        MyBle.DisconnectBle()
    }

    connect(item, id) {
        MyBle.ConnectBle(item.id, (device) => {
            // console.log('连接成功',device)
        }, err => {
            console.log('连接失败===', err)
        })
    }

    send(item) {
        MyBle.BleWrite(item.order, (data) => {
            console.log('蓝牙页面数据返回===', data)
            switch (item.title) {
                case '开':
                    console.log('开了');
                    this.setState({openData: data});
                    break;
                case '关':
                    console.log('关了');
                    this.setState({openData: data});
                    break;
                case '红':
                    console.log('红');
                    this.setState({openData: data});
                    break;
                case '蓝':
                    console.log('蓝');
                    this.setState({openData: data});
                    break;
            }
        }, (err) => {
            console.log('写入失败===', err)
        })
    }


    render() {
        return (
            <View style={{flex: 1,}}>
                <ScrollView>
                    <Text style={{color: '#333'}}>
                        蓝牙manage页面
                    </Text>
                    <View style={styles.title}>
                        <Text style={{color: '#FFF'}}>
                            蓝牙列表
                        </Text>
                    </View>

                    <TouchableOpacity onPress={() => this.research()} style={[styles.title, {backgroundColor: '#fff'}]}>
                        <Text style={{color: '#333'}}>
                            重新搜索
                        </Text>
                    </TouchableOpacity>
                    {
                        this.state.device_list.map((item, id) => {
                            return (
                                <View key={id} style={styles.bleItem}>
                                    <View>
                                        <Text style={{color: '#333'}}>name:{item.name}</Text>
                                        <Text style={{color: '#333'}}>mac:{item.id}</Text>
                                        <Text style={{color: '#333'}}>isConnectable:{item.isConnectable}</Text>
                                    </View>
                                    <View style={{justifyContent: 'center', alignItems: 'center'}}>
                                        <TouchableOpacity onPress={() => this.connect(item)} style={styles.connectBtn}>
                                            <Text style={{color: '#fff'}}>连接</Text>
                                        </TouchableOpacity>
                                        <TouchableOpacity onPress={() => this.disconnect(item)}
                                                          style={styles.connectBtn}>
                                            <Text style={{color: '#fff'}}>断开连接</Text>
                                        </TouchableOpacity>
                                    </View>
                                </View>
                            )
                        })
                    }
                    {
                        [
                            {order: '5500000002050005AA', title: '开'},
                            {order: '5500000002050106AA', title: '关'},
                            {order: '55000000040100ff0000AA', title: '红'},
                            {order: '550000000401ff000000AA', title: '蓝'},
                        ].map((item, id) => {
                            return (
                                <TouchableOpacity key={id} onPress={() => this.send(item)} style={styles.btn}>
                                    <Text style={{color: '#fff'}}>{item.title}</Text>
                                </TouchableOpacity>
                            )
                        })
                    }
                </ScrollView>
            </View>
        );
    }

    componentWillUnmount() {
        MyBle.DisconnectBle()
        // MyBle.destroy()
    }
}

export default BluetoothManage;
const styles = StyleSheet.create({
    title: {backgroundColor: 'red', height: 50, width: '100%', alignItems: 'center', justifyContent: 'center'},
    bleItem: {
        borderBottomWidth: 1,
        width: '100%',
        marginVertical: 10,
        flexDirection: 'row',
        justifyContent: 'space-between',
        alignItems: 'center',
        paddingHorizontal: 15
    },
    connectBtn: {
        paddingHorizontal: 20,
        paddingVertical: 5,
        backgroundColor: 'green',
        borderRadius: 10,
        marginVertical: 5
    },
    btn: {backgroundColor: 'blue', paddingVertical: 20, alignItems: 'center', borderRadius: 999, elevation: 5},
})

上一篇 下一篇

猜你喜欢

热点阅读