秋月如诗 發表於 2019-5-24 10:01:00

【水滴石穿】react-native-ble-demo

<p>项目的话,是想打开蓝牙,然后连接设备<br>
<img src="https://img2018.cnblogs.com/blog/1037363/201905/1037363-20190524093724677-59110343.png" alt="" loading="lazy"><br>
点击已经连接的设备,我们会看到一些设备<br>
<img src="https://img2018.cnblogs.com/blog/1037363/201905/1037363-20190524093802939-275298595.png" alt="" loading="lazy"><br>
不过我这边在开启蓝牙的时候报错了<br>
<img src="https://img2018.cnblogs.com/blog/1037363/201905/1037363-20190524093846987-578508595.png" alt="" loading="lazy"><br>
先放作者的项目地址:<br>
https://github.com/hezhii/react-native-ble-demo<br>
然后我们来分析代码<br>
根入口文件是</p>
<pre><code class="language-js">//react-native-ble-demo/ble_central/index.js
import { AppRegistry } from 'react-native'

import App from './src/App'
import { name as appName } from './app.json'

console.disableYellowBox = true

AppRegistry.registerComponent(appName, () =&gt; App)
</code></pre>
<pre><code class="language-js">//react-native-ble-demo/ble_central/src/App.js
import React from 'react'
import { Provider } from 'react-redux'
import { Provider as AntProvider } from '@ant-design/react-native'
//store 普通的store封装
import store from './store/configureStore'
//跳转
import Navigator from './navigator'

export default class App extends React.PureComponent {
render() {
    return (
      &lt;Provider store={store}&gt;
      &lt;AntProvider&gt;
          &lt;Navigator /&gt;
      &lt;/AntProvider&gt;
      &lt;/Provider&gt;
    )
}
}
</code></pre>
<pre><code class="language-js">//react-native-ble-demo/ble_central/src/navigator.js
import React from 'react'
import { createAppContainer, createStackNavigator } from 'react-navigation'
//
import Device from './pages/Device'
import Search from './pages/Search'
import Service from './pages/Service'
import Characteristic from './pages/Characteristic'
import Operation from './pages/Operation'
//这个有意思,这种返回
const AppNavigator = createStackNavigator({
Search,
Device,
Service,
Characteristic,
Operation
}, {
    defaultNavigationOptions: {
      headerTitleStyle: {
      fontSize: 18,
      },
      headerBackTitle: '返回',
    },
})

export default createAppContainer(AppNavigator)
</code></pre>
<p><img src="https://img2018.cnblogs.com/blog/1037363/201905/1037363-20190524095050410-1036786994.png" alt="" loading="lazy"></p>
<pre><code class="language-js">//react-native-ble-demo/ble_central/src/pages/Device.js
import React from 'react'
import { StyleSheet, View, Text, FlatList } from 'react-native'
import { SafeAreaView } from 'react-navigation'
import { connect } from 'react-redux'

import DeviceList from '../components/DeviceList'

@connect(({ connectedDevice }) =&gt; ({
devices: connectedDevice.list
}))
export default class Device extends React.PureComponent {
static navigationOptions = {
    title: '已连接的设备'
}

onPressDevice = (item) =&gt; {
    const { navigation } = this.props
    navigation.push('Service', { device: item })
}

render() {
    const { devices } = this.props;
    return (
      &lt;SafeAreaView style={styles.container}&gt;
      &lt;DeviceList data={devices} onPress={this.onPressDevice} /&gt;
      &lt;/SafeAreaView&gt;
    )
}
}

const styles = StyleSheet.create({
container: {
    flex: 1
}
})
</code></pre>
<p><img src="https://img2018.cnblogs.com/blog/1037363/201905/1037363-20190524095129519-939399972.png" alt="" loading="lazy"></p>
<pre><code class="language-js">//react-native-ble-demo/ble_central/src/pages/Search.js
import React from 'react'
import {
StyleSheet,
View,
ActivityIndicator,
Text,
TouchableOpacity,
Alert,
Platform
} from 'react-native'
import { BleManager } from 'react-native-ble-plx'
import { SafeAreaView } from 'react-navigation'
import { connect } from 'react-redux'
import HeaderButtons, { Item } from 'react-navigation-header-buttons'
import { Toast, Portal } from '@ant-design/react-native'

import Button from '../components/Button'
import DeviceList from '../components/DeviceList'
import { ADD_DEVICE } from '../reducer/connectedDevice'

@connect(({ connectedDevice }) =&gt; ({
connectedDevices: connectedDevice.list
}))
export default class Search extends React.PureComponent {
static navigationOptions = ({ navigation }) =&gt; ({
    title: '搜索设备',
    headerRight: (
      &lt;HeaderButtons&gt;
      &lt;Item
          title="已连接的设备"
          buttonStyle={{ color: '#1890ff', fontSize: 16 }}
          onPress={() =&gt; navigation.push('Device')}
      /&gt;
      &lt;/HeaderButtons&gt;
    ),
})

constructor(props) {
    super(props)

    this.state = {
      bleState: null,
      scanning: false,
      devices: []
    }
    this._initBleManager()
}

componentWillUnmount() {
    this.bleManager.destroy()
}

_initBleManager() {
    const manager = this.bleManager = new BleManager()

    manager.onStateChange(this.onStateChange)
    this._checkState()
}

_checkState = () =&gt; {
    this.bleManager.state()
      .then(state =&gt; {
      console.log('检查蓝牙状态:', state)
      this.setState({
          bleState: state
      })
      if (state === 'PoweredOff') {
          this._showAlert()
      }
      })
}

_showAlert() {
    Alert.alert(
      '蓝牙未开启',
      '需要您开启蓝牙才能使用后续功能',
      [
      { text: '取消' },
      { text: '开启蓝牙', onPress: this._onOpenBluetooth }
      ]
    )
}

_onOpenBluetooth = () =&gt; {
    if (Platform.OS === 'ios') {
      Linking.openURL('App-Prefs:root=Bluetooth')
    } else {
      this.bleManager.enable()
    }
}

// 蓝牙状态发生变化
onStateChange = (state) =&gt; {
    console.log('蓝牙状态发生变化,新的状态为:', state)
    this.setState({
      bleState: state
    })
    if (state === 'PoweredOff') {
      this._showAlert()
    }
}

// 搜索到设备
onScannedDevice = (err, device) =&gt; {
    const { devices } = this.state
    if (devices.findIndex(item =&gt; item.id === device.id) &lt; 0) {
      this.setState({
      devices: [...devices, device]
      })
    }
}

// 搜索设备
scanDevices = () =&gt; {
    const { bleState } = this.state
    if (bleState === 'PoweredOn') {
      console.log('开始搜索设备')
      this.setState({ scanning: true, devices: [] })
      this.bleManager.startDeviceScan(null, { allowDuplicates: false }, this.onScannedDevice)
    } else {
      this._showAlert()
    }
}

// 停止搜索设备
stopScan = () =&gt; {
    if (this.state.scanning) {
      console.log('停止搜索设备')
      this.setState({ scanning: false })
      this.bleManager.stopDeviceScan()
    }
}

clearDevices = () =&gt; {
    this.setState({
      devices: []
    })
}

connectDevice = async (device) =&gt; {
    const { connectedDevices, dispatch, navigation } = this.props
    const index = connectedDevices.findIndex(item =&gt; item.id === device.id)
    if (index &gt;= 0) {
      Alert.alert('已经连接该设备了')
      return
    }
    this.stopScan() // 连接时停止扫描
    const key = Toast.loading('正在连接设备...')
    await device.connect()
    console.log('成功连接设备:', device.id)
    await device.discoverAllServicesAndCharacteristics()
    console.log('获取设备的服务和特征')
    Portal.remove(key)
    dispatch({
      type: ADD_DEVICE,
      payload: device
    })
    Alert.alert('成功连接设备', null, [
      { text: '算了' },
      { text: '去看看', onPress: () =&gt; navigation.push('Device') }
    ])
}
//渲染设备列表
renderDevice = ({ item }) =&gt; {
    return (
      &lt;TouchableOpacity onPress={() =&gt; this.connectDevice(item)}&gt;
      &lt;View style={styles.listItem}&gt;
          &lt;Text style={styles.itemId}&gt;{item.id}&lt;/Text&gt;
          &lt;Text style={styles.itemName}&gt;{item.localName || item.name}&lt;/Text&gt;
      &lt;/View&gt;
      &lt;/TouchableOpacity&gt;
    )
}

render() {
    const { scanning, devices } = this.state
    return (
      &lt;SafeAreaView style={styles.container}&gt;
      &lt;View style={styles.header}&gt;
          &lt;Button
            style={{ marginRight: 16 }}
            onPress={this.scanDevices}
            disabled={scanning}
          &gt;{scanning ? '正在搜索' : '开始搜索'}&lt;/Button&gt;
          &lt;Button
            onPress={this.stopScan}
            disabled={!scanning}
          &gt;停止搜索&lt;/Button&gt;
      &lt;/View&gt;
      &lt;View style={styles.listHeader}&gt;
          &lt;View style={styles.row}&gt;
            &lt;Text style={styles.headerTitle}&gt;可用设备&lt;/Text&gt;
            {scanning &amp;&amp; &lt;ActivityIndicator /&gt;}
          &lt;/View&gt;
          &lt;TouchableOpacity onPress={this.clearDevices}&gt;
            &lt;Text&gt;清空&lt;/Text&gt;
          &lt;/TouchableOpacity&gt;
      &lt;/View&gt;
      &lt;DeviceList
          onPress={this.connectDevice}
          data={devices}
      /&gt;
      &lt;/SafeAreaView&gt;
    )
}
}

const styles = StyleSheet.create({
container: {
    flex: 1
},
header: {
    flexDirection: 'row',
    padding: 15
},
listHeader: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    backgroundColor: '#f5f5f9',
    padding: 15
},
row: {
    flexDirection: 'row',
    alignItems: 'center',
},
headerTitle: {
    fontSize: 15,
    marginRight: 6,
    fontWeight: '500',
}
})
</code></pre>
<p>渲染设备列表</p>
<pre><code class="language-js">//react-native-ble-demo/ble_central/src/pages/Service.js
import React from 'react'
import { StyleSheet, Text, ScrollView, View } from 'react-native'
import { SafeAreaView } from 'react-navigation'
import { WingBlank, List } from '@ant-design/react-native'

const ListItem = List.Item
const Brief = ListItem.Brief;

export default class Service extends React.PureComponent {
static navigationOptions = {
    title: '服务'
}

constructor(props) {
    super(props)
    const { navigation } = props
    this.device = navigation.getParam('device')
    this.state = {
      services: []
    }
}

componentDidMount() {
    this.getServices()
}

getServices() {
    this.device.services()
      .then(services =&gt; {
      this.setState({
          services
      })
      })
}

onPressService = (service) =&gt; {
    const { navigation } = this.props
    navigation.push('Characteristic', { service })
}

render() {
    const { services } = this.state
    const device = this.device
    return (
      &lt;SafeAreaView style={styles.fill}&gt;
      &lt;WingBlank style={{ paddingVertical: 10 }}&gt;
          &lt;Text style={styles.label}&gt;设备 ID:&lt;/Text&gt;
          &lt;Text style={styles.value}&gt;{device.id}&lt;/Text&gt;
          &lt;Text style={styles.label}&gt;设备名称:&lt;/Text&gt;
          &lt;Text style={styles.value}&gt;{device.localName || device.name || '无'}&lt;/Text&gt;
      &lt;/WingBlank&gt;
      &lt;View style={styles.listHeader}&gt;
          &lt;Text style={styles.headerTitle}&gt;服务列表&lt;/Text&gt;
      &lt;/View&gt;
      &lt;ScrollView style={styles.fill}&gt;
          &lt;List&gt;
            {services.map((service, index) =&gt; (
            &lt;ListItem key={index} arrow="horizontal" onPress={() =&gt; this.onPressService(service)}&gt;
                {service.id}
                &lt;Brief&gt;{`UUID: ${service.uuid}`}&lt;/Brief&gt;
            &lt;/ListItem&gt;
            ))}
          &lt;/List&gt;
      &lt;/ScrollView&gt;
      &lt;/SafeAreaView &gt;
    )
}
}

const styles = StyleSheet.create({
fill: {
    flex: 1
},
label: {
    fontWeight: '500',
    fontSize: 16,
},
value: {
    marginTop: 8,
    marginBottom: 10,
    color: '#666'
},
listHeader: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f5f5f9',
    padding: 15
},
headerTitle: {
    fontSize: 15,
    fontWeight: '500'
}
})
</code></pre>
<pre><code class="language-js">//react-native-ble-demo/ble_central/src/pages/Operation.js
import React from 'react'
import { StyleSheet, Text, ScrollView, View, TextInput, Alert } from 'react-native'
import { SafeAreaView } from 'react-navigation'
import { WingBlank, Button } from '@ant-design/react-native'
import { Buffer } from 'buffer/'

function formatToDecimal(buffer) {
const hexStr = buffer.toString('hex')
return hexStr ? parseInt(hexStr, 16) : ''
}

function strToBinary(str) {
const result = [];
const list = str.split("");
for (let i = 0; i &lt; list.length; i++) {
    const str = list.charCodeAt().toString(2);
    result.push(str);
}
return result.join("");
}

export default class Operation extends React.PureComponent {
static navigationOptions = {
    title: '读写特征'
}

constructor(props) {
    super(props)
    const { navigation } = props
    this.characteristic = navigation.getParam('characteristic')
    this.state = {
      readValue: '',
      writeValue: ''
    }
}

read = () =&gt; {
    this.characteristic.read()
      .then(characteristic =&gt; {
      console.log('读取特征值:', characteristic.value)
      this.setState({
          readValue: characteristic.value
      })
      })
}

write = () =&gt; {
    const { writeValue } = this.state
    if (!writeValue) {
      Alert.alert('请输入要写入的特征值')
    }
    const str = Buffer.from(writeValue, 'hex').toString('base64')
    console.log('开始写入特征值:', str)
    this.characteristic.writeWithResponse(str)
      .then(() =&gt; {
      Alert.alert('成功写入特征值', '现在点击读取特征值看看吧...')
      })
      .catch(err =&gt; {
      console.log('写入特征值出错:', err)
      })
}

render() {
    const charac = this.characteristic
    const { readValue, writeValue } = this.state;
    const buffer = Buffer.from(readValue, 'base64');
    return (
      &lt;SafeAreaView style={styles.fill}&gt;
      &lt;ScrollView style={styles.fill}&gt;
          &lt;WingBlank style={{ paddingVertical: 10 }}&gt;
            &lt;Text style={styles.label}&gt;特征 ID:&lt;/Text&gt;
            &lt;Text style={styles.value}&gt;{charac.id}&lt;/Text&gt;
            &lt;Text style={styles.label}&gt;特征 UUID:&lt;/Text&gt;
            &lt;Text style={styles.value}&gt;{charac.uuid}&lt;/Text&gt;
            &lt;View style={styles.attributeWrapper}&gt;
            &lt;Text style={styles.name}&gt;
                可读:
             &lt;Text style={styles.des}&gt;{charac.isReadable ? '是' : '否'}&lt;/Text&gt;
            &lt;/Text&gt;
            &lt;Text style={styles.name}&gt;
                可写(有响应):
            &lt;Text style={styles.des} &gt;{charac.isWritableWithResponse ? '是' : '否'}&lt;/Text&gt;
            &lt;/Text&gt;
            &lt;Text style={styles.name}&gt;
                可写(无响应):
            &lt;Text style={styles.des}&gt;{charac.isWritableWithoutResponse ? '是' : '否'}&lt;/Text&gt;
            &lt;/Text&gt;
            &lt;Text style={styles.name}&gt;
                可通知:
            &lt;Text style={styles.des}&gt;{charac.isNotifiable ? '是' : '否'}&lt;/Text&gt;
            &lt;/Text&gt;
            &lt;/View&gt;
            &lt;Text style={styles.label}&gt;当前特征值&lt;/Text&gt;
            &lt;Text style={styles.charac}&gt;{`二进制: ${strToBinary(buffer.toString())}`}&lt;/Text&gt;
            &lt;Text style={styles.charac}&gt;{`十进制: ${formatToDecimal(buffer)}`}&lt;/Text&gt;
            &lt;Text style={styles.charac}&gt;{`十六进制: ${buffer.toString('hex')}`}&lt;/Text&gt;
            &lt;Text style={styles.charac}&gt;{`UTF8: ${buffer.toString()}`}&lt;/Text&gt;
            &lt;Button type="primary" style={{ marginTop: 8 }} onPress={this.read}&gt;读取特征值&lt;/Button&gt;
            &lt;TextInput
            style={styles.input}
            placeholder="请输入特征值(十六进制字符串)"
            value={writeValue}
            onChangeText={v =&gt; this.setState({ writeValue: v })}
            /&gt;
            &lt;Button type="primary" onPress={this.write}&gt;写入特征值&lt;/Button&gt;
          &lt;/WingBlank&gt;
      &lt;/ScrollView&gt;
      &lt;/SafeAreaView&gt;
    )
}
}

const styles = StyleSheet.create({
fill: {
    flex: 1
},
label: {
    fontWeight: '500',
    fontSize: 16,
},
value: {
    marginTop: 8,
    marginBottom: 10,
    color: '#666'
},
attributeWrapper: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    alignItems: 'center',
    marginBottom: 16
},
des: {
    color: '#666'
},
name: {
    fontWeight: '500',
    fontSize: 16,
    marginRight: 16
},
input: {
    marginVertical: 32
},
charac: {
    fontSize: 15,
    color: '#666',
    marginVertical: 5
}
})
</code></pre>
<pre><code class="language-js">//react-native-ble-demo/ble_central/src/pages/Characteristic.js
import React from 'react'
import { StyleSheet, Text, ScrollView, View } from 'react-native'
import { SafeAreaView } from 'react-navigation'
import { WingBlank, List } from '@ant-design/react-native'

const ListItem = List.Item
const Brief = ListItem.Brief;

export default class Characteristic extends React.PureComponent {
static navigationOptions = {
    title: '特征'
}

constructor(props) {
    super(props)
    const { navigation } = props
    this.service = navigation.getParam('service')
    this.state = {
      characteristics: []
    }
}

componentDidMount() {
    this.getCharacteristics()
}

getCharacteristics() {
    this.service.characteristics()
      .then(characteristics =&gt; {
      this.setState({
          characteristics
      })
      })
}

onPressCharacteristic = (characteristic) =&gt; {
    const { navigation } = this.props
    navigation.push('Operation', { characteristic })
}

render() {
    const { characteristics } = this.state
    const service = this.service
    return (
      &lt;SafeAreaView style={styles.fill}&gt;
      &lt;WingBlank style={{ paddingVertical: 10 }}&gt;
          &lt;Text style={styles.label}&gt;服务 ID:&lt;/Text&gt;
          &lt;Text style={styles.value}&gt;{service.id}&lt;/Text&gt;
          &lt;Text style={styles.label}&gt;服务 UUID:&lt;/Text&gt;
          &lt;Text style={styles.value}&gt;{service.uuid}&lt;/Text&gt;
      &lt;/WingBlank&gt;
      &lt;View style={styles.listHeader}&gt;
          &lt;Text style={styles.headerTitle}&gt;特征列表&lt;/Text&gt;
      &lt;/View&gt;
      &lt;ScrollView style={styles.fill}&gt;
          &lt;List&gt;
            {characteristics.map((characteristic, index) =&gt; (
            &lt;ListItem key={index} arrow="horizontal" onPress={() =&gt; this.onPressCharacteristic(characteristic)}&gt;
                {characteristic.id}
                &lt;Brief&gt;{`UUID: ${characteristic.uuid}`}&lt;/Brief&gt;
            &lt;/ListItem&gt;
            ))}
          &lt;/List&gt;
      &lt;/ScrollView&gt;
      &lt;/SafeAreaView &gt;
    )
}
}

const styles = StyleSheet.create({
fill: {
    flex: 1
},
label: {
    fontWeight: '500',
    fontSize: 16,
},
value: {
    marginTop: 8,
    marginBottom: 10,
    color: '#666'
},
listHeader: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f5f5f9',
    padding: 15
},
headerTitle: {
    fontSize: 15,
    fontWeight: '500'
}
})
</code></pre>
<pre><code class="language-js">//react-native-ble-demo/ble_central/src/components/DeviceList/index.js
import React from 'react'
import { StyleSheet, FlatList, View, Text, TouchableOpacity } from 'react-native'

export default class DeviceList extends React.PureComponent {
renderItem = ({ item }) =&gt; {
    const { onPress } = this.props
    return (
      &lt;TouchableOpacity onPress={() =&gt; onPress(item)}&gt;
      &lt;View style={styles.item}&gt;
          &lt;Text style={styles.title}&gt;{item.id}&lt;/Text&gt;
          &lt;Text style={styles.desc}&gt;{item.localName || item.name}&lt;/Text&gt;
      &lt;/View&gt;
      &lt;/TouchableOpacity&gt;
    )
}

render() {
    const { data } = this.props
    return (
      &lt;FlatList
      style={styles.list}
      ListEmptyComponent={() =&gt; &lt;Text style={styles.placeholder}&gt;暂无数据&lt;/Text&gt;}
      data={data}
      ItemSeparatorComponent={() =&gt; &lt;View style={styles.border} /&gt;}
      keyExtractor={(item, index) =&gt; '' + index}
      renderItem={this.renderItem}
      /&gt;
    )
}
}

const styles = StyleSheet.create({
list: {
    paddingTop: 15
},
item: {
    paddingLeft: 16,
    paddingVertical: 8
},
title: {
    fontSize: 16
},
desc: {
    color: '#666'
},
border: {
    backgroundColor: '#d9d9d9',
    height: 0.5
},
placeholder: {
    fontSize: 16,
    paddingLeft: 15,
    color: '#666'
}
})
</code></pre>


</div>
<div id="MySignature" role="contentinfo">
    <div>作者:jser_dimple</div>
<div>出处:https://www.cnblogs.com/smart-girl/
</div>
<p>-------------------------------------------</p>
<p>个性签名:一个人在年轻的时候浪费自己的才华与天赋是一件非常可惜的事情</p>
<p>如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个<span>“推荐”</span>哦,博主在此感谢!</p>
<p></p>
<p>万水千山总是情,打赏5毛买辣条行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!</p>
<div style="display: flex">
<div><p>微信</p>
<div><img src="https://img2018.cnblogs.com/blog/1037363/201903/1037363-20190305113425881-790087636.jpg" style="width: 300px; height: 300px"></div>
</div>
<div><p>支付宝</p><div><img src="https://img2018.cnblogs.com/blog/1037363/201903/1037363-20190305114125930-314728927.jpg" style="width: 300px; height: 300px"></div>
</div>
<div></div></div><br><br>
来源:https://www.cnblogs.com/smart-girl/p/10916346.html
頁: [1]
查看完整版本: 【水滴石穿】react-native-ble-demo