uniapp藍牙多設備連接

博客園斷更快一年了終於想起來我還有個博客,也主要是最近工作上面也沒有什麼想記錄的,不過最近倒是搞了個有意思的功能項目

 

請注意:此文章禁止轉載,抄襲,這是對我個人知識產權的不尊重 

 

項目簡介:原先單片機應用移植做一個無後端參與手機APP

功能介紹:單片機爲子機的命令發送裝置,開機使用單片機連接上子機的藍牙設備,然後發送命令給子機執行

需求介紹:需要將原先單片機上的功能全部移植到新APP內,並且要求連接多個藍牙設備,最好做到無上限設備連接(其實因爲各個手機的性能差異,基本上不可能無限連接藍牙設備,但是要求最少連接四個)

 

先簡單介紹一下連接藍牙設備的注意事項,整體連接藍牙的流程爲

1. 開啓掃描器,掃描藍牙,期間會獲取到設備的 deviceId ,這個ID需要用於手機和藍牙設備進行連接

2. 使用 deviceId 和藍牙設備進行初始連接,可以獲取到很多服務項,這些服務項都存在一個 serviceId ,後續需要再使用這個 serviceId 去獲取特徵項

3. 使用 serviceId 獲取服務內的特徵項,特徵項需要區分是否支持讀寫監聽等功能,可看下圖

圖中不同的uuid支持不同的功能,比如write:true就是支持往藍牙設備寫入數據的,notify:true是支持監聽藍牙設備發送出來的數據,read:true是支持讀取數據

此時需要注意如果需要監聽和發送信息功能就需要使用兩個不同的uuid去實現,

藍牙notify功能只需要運行一次即可在藍牙連接期間進行接收設備發出來的數據,發送需要每次均運行一遍

使用這三個ID就可以連接上藍牙進行對應的操作

 

完整代碼放在附件文件內,記得更換UUID去連接對應的設備

先弄一個需要用戶手動開啓掃描的按鈕,然後獲取到掃描的列表,丟進去數組將其展示到頁面(在開啓之前需要先檢測手機的藍牙是否開啓,不然是無法正常連接的)

添加完一些藍牙設備之後開始調用uniapp的官方api,即可實現連接

 

注意事項:

1. 連接上時最先要調用notify監聽功能,並且可以全局僅調用一次,以防止丟失信息

2. 當你已知服務項的uuid和特徵項的uuid時,可以省去獲取服務項和獲取特徵項的功能,直接使用已知的uuid進行連接,可以省下很多多設備連接的時間(具體的請查看完整代碼內 nowLinkLis 方法)

3. 目前我所測試的設備中,最好的情況是連接上了7個藍牙設備,若你在測試中發現只能6,7個設備連上是正常現象的,目前僅測試了安卓和小程序設備,iOS的話由於公司未給我提供設備就沒測試

4. 在調用 uni.getBLEDeviceServices 方法中一定要設置一個定時器延遲一點再連接,具體原因不詳但是是我踩過坑的

5. 使用固定服務uuid,固定特徵uuid進行連接啓用notify監聽時也同樣需要延遲一點再進行連接,不然會報連接失敗的錯誤

 

完整代碼,uniapp中.vue文件,

我已使用固定的uuid,如果需要直接貼上去先獲取服務和特徵的uuid,更換後再進行測試

<template>
  <view class="content">
    <button type="default" v-show="!shows" @click="initBle">
      初始化藍牙模塊
    </button>
    <scroll-view scroll-y="true" show-scrollbar="true">
      <radio-group>
        <view
          v-for="(item, index) in bleDevs"
          :key="index"
          v-show="item.name.length > 0 && !shows"
          style="padding: 10rpx 20rpx; border-bottom: 1rpx solid #ececec"
          v-if="Math.max(100 + item.RSSI, 0) >= 30"
        >
          <view style="font-size: 32rpx; color: #333">
            <checkbox-group
              @change="checkboxChange"
              :data-name="item.name"
              :data-deviceId="item.deviceId"
            >
              <label>
                <checkbox :value="item.deviceId">
                  {{ item.name }}
                </checkbox>
              </label>
            </checkbox-group>
          </view>
          <view style="font-size: 20rpx; padding: 10rpx 0">
            deviceId: {{ item.deviceId }} 信號強度: {{ item.RSSI }}dBm ({{
              Math.max(100 + item.RSSI, 0)
            }}%)
          </view>
        </view>
        <view class="dis">
          <view @tap="connectBle" v-if="!shows" class="pl"> 連接 </view>
          <view @tap="close" v-if="shows" class="pl"> 斷開 </view>
        </view>
      </radio-group>
    </scroll-view>

    <view class="barItems" v-if="shows">
      <view class="barItem" v-for="(item, index) in testItems" :key="index">
        <view class="name">{{ item.name }}</view>
        <!-- <sliderBar
          class="bar"
          :min="item.min"
          :max="item.max"
          @change="changeBar($event, item)"
        ></sliderBar> -->
        <view class="bar">
          <view class="reduce" @click="changNums(1, item)">-</view>
          <input type="tel" v-model="item.typeNums" @input="changeBar(item)" />
          <view class="add" @click="changNums(2, item)">+</view>
        </view>
      </view>
    </view>

    <view class="timers" v-if="shows">
      <view class="time">
        {{ titleTime }}
      </view>
      <view class="btns">
        <view @click="begin">啓動</view>
        <view @click="pause">暫停</view>
        <view @click="stop">停止</view>
      </view>
    </view>

    <view v-if="shows">
      <view class="input3">
        <input type="text" v-model="input1" />
        <input type="text" v-model="input2" />
      </view>
      <button type="default" class="send" @click="send(1)">發送</button>
    </view>

    <view class="appItems">
      <viwe
        :class="[item.status ? 'item bakBlue' : 'item']"
        v-for="(item, index) in totalList"
        :key="index"
      >
        <view class="txt">{{ item.text }}</view>
        <view class="name p_hide">{{ item.name }}</view>
      </viwe>
    </view>

    <view class="items" v-if="shows">
      <view class="item" v-for="(item, index) in getData" :key="index">
        {{ item.name }}:{{ item.txt }}
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      config: {
        color: "#333",
        backgroundColor: [1, "#fff"],
        title: "多設備藍牙連接",
        back: false,
      },
      title: "Hello",
      bleDevs: [],
      status: -2, //-2未連接  -1已連接  0連接成功
      deviceId: "",
      serviceId: "",
      characteristicId: "",

      sendData: "",
      getData: [],

      deviceIds: [],
      totalList: [], // 全部已連接的設備

      timeIndex: 0, // 默認是列表的第一個

      timeout: null,
      shows: false,

      testItems: [
        {
          index: 1,
          typeNums: 1,
          min: 0,
          max: 150,
          name: "設定頻率",
          value: "F",
        },
        {
          index: 2,
          typeNums: 250,
          min: 50,
          max: 250,
          name: "設定脈寬",
          value: "W",
        },
        { index: 3, typeNums: 3, min: 0, max: 3, name: "設定類型", value: "C" },
        {
          index: 4,
          typeNums: 0,
          min: 0,
          max: 120,
          name: "設定電流",
          value: "I",
        },
        {
          index: 5,
          typeNums: 0,
          min: 1,
          max: 100,
          name: "設定方案",
          value: "M",
        },
      ],

      titleTime: "00:00:00",
      timer: "",

      hour: 0,
      minutes: 0,
      seconds: 0,

      input1: "B",
      input2: "",
    };
  },
  destroyed() {
    clearInterval(this.timer);
  },
  onLoad() {},
  mounted() {
    this.onBLEConnectionStateChange();
  },
  methods: {
    // 開始計時
    begin() {
      if (this.start) {
        return;
      }
      this.sendData = "BS1\r";
      this.start = true;
      this.timer = setInterval(this.startTimer, 1000);
      this.send();
    },
    startTimer() {
      this.seconds += 1;
      if (this.seconds >= 60) {
        this.seconds = 0;
        this.minute = this.minute + 1;
      }

      if (this.minute >= 60) {
        this.minute = 0;
        this.hour = this.hour + 1;
      }
      this.titleTime =
        (this.hour < 10 ? "0" + this.hour : this.hour) +
        ":" +
        (this.minutes < 10 ? "0" + this.minutes : this.minutes) +
        ":" +
        (this.seconds < 10 ? "0" + this.seconds : this.seconds);
    },
    // 暫停倒計時
    pause() {
      if (this.timer) {
        clearInterval(this.timer);
        this.start = false;
        this.sendData = "BS2\r";
        this.send();
        // this.timer = null
      }
    },
    stop() {
      if (this.timer) {
        clearInterval(this.timer);
        // this.timer = null
        this.sendData = "BS3\r";
        this.send();
        this.titleTime = "00:00:00";
        this.timer = "";
        this.hour = 0;
        this.minutes = 0;
        this.seconds = 0;
        this.start = false;
      }
    },
    changNums(index, item) {
      // 1爲減少,2爲增加
      if (index == 1) {
        if (item.typeNums <= item.min) {
          uni.showToast({
            title: "已經不能再減少了",
            icon: "none",
          });
          return;
        }
        item.typeNums--;
      } else if (index == 2) {
        if (item.typeNums >= item.max) {
          uni.showToast({
            title: "已經不能再增加了",
            icon: "none",
          });
          return;
        }
        item.typeNums++;
      }
      this.changeBar(item);
    },
    changeBar(item) {
      // 處理防抖
      if (this.timeout) {
        clearTimeout(this.timeout);
      }
      this.timeout = setTimeout(() => {
        if (item.typeNums < item.min) {
          uni.showToast({
            title: "低於最小值,已變更爲最小值發送",
            icon: "none",
          });
          item.typeNums = item.min;
        } else if (item.typeNums > item.max) {
          uni.showToast({
            title: "超過最大值,已變更爲最大值發送",
            icon: "none",
          });
          item.typeNums = item.max;
        }
        this.sendData = "B" + item.value + item.typeNums + "\r";
        for (let i = 0; i < this.deviceIds.length; i++) {
          this.getBLEDeviceServices(1, this.deviceIds[i]);
        }
      }, 500);
    },
    checkboxChange(e) {
      if (e.target.value[0] && e.target.dataset.name) {
        let item = {
          deviceId: e.target.value[0],
          name: e.target.dataset.name,
        };
        this.deviceIds.push(item);
      } else {
        for (let index = 0; index < this.deviceIds.length; index++) {
          let item = this.deviceIds[index];
          if (item.deviceId == e.target.dataset.deviceid) {
            this.deviceIds.splice(index, 1);
          }
        }
      }
    },
    hextoString(hex) {
      var arr = hex.split("");
      var out = "";
      for (var i = 0; i < arr.length / 2; i++) {
        var tmp = "0x" + arr[i * 2] + arr[i * 2 + 1];
        var charValue = String.fromCharCode(tmp);
        out += charValue;
      }
      return out;
    },
    send(index) {
      let that = this;
      if (index == 1) {
        that.sendData = that.input1 + that.input2 + "\r";
      }
      if (!that.sendData) {
        return uni.showToast({
          title: "發送數據不可爲空",
          icon: "none",
        });
      }
      uni.showLoading({
        title: "發送中,請稍等",
        mask: true,
      });
      for (let i = 0; i < that.deviceIds.length; i++) {
        that.getBLEDeviceServices(1, that.deviceIds[i]);
      }
    },
    // 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("");
    },

    onBLEConnectionStateChange() {
      uni.onBLEConnectionStateChange((res) => {
        // 該方法回調中可以用於處理連接意外斷開等異常情況
        if (res.connected == false) {
          uni.hideLoading();
          for (let i = 0; i < this.deviceIds.length; i++) {
            if (res.deviceId == this.deviceIds[i].deviceId) {
              uni.showToast({
                title: this.deviceIds[i].name + " 藍牙設備斷開連接",
                icon: "none",
              });
            }
          }
        }
      });
    },
    //初始化藍牙
    initBle() {
      // console.log("初始化藍牙>>>");
      this.bleDevs = [];
      this.deviceIds = [];
      uni.openBluetoothAdapter({
        success: (res) => {
          //已打開
          uni.getBluetoothAdapterState({
            //藍牙的匹配狀態
            success: (res1) => {
              // console.log(res1, "“本機設備的藍牙已打開”");
              // 開始搜索藍牙設備
              this.startBluetoothDeviceDiscovery();
            },
            fail(error) {
              uni.showToast({ icon: "none", title: "查看手機藍牙是否打開" });
            },
          });
        },
        fail: (err) => {
          //未打開
          uni.showToast({ icon: "none", title: "查看手機藍牙是否打開" });
        },
      });
    },
    // 開始搜索藍牙設備
    startBluetoothDeviceDiscovery() {
      uni.startBluetoothDevicesDiscovery({
        success: (res) => {
          // console.log("啓動成功", res);
          // 發現外圍設備
          this.onBluetoothDeviceFound();
        },
        fail: (err) => {
          // console.log(err, "錯誤信息");
        },
      });
    },
    // 發現外圍設備
    onBluetoothDeviceFound() {
      // console.log("執行到這--發現外圍設備")
      uni.onBluetoothDeviceFound((res) => {
        // 吧搜索到的設備存儲起來,方便我們在頁面上展示
        if (this.bleDevs.indexOf(res.devices[0]) == -1) {
          this.bleDevs.push(res.devices[0]);
        }
        // console.log("藍牙列表", res);
      });
    },

    // 多選然後連接
    connectBle() {
      if (this.deviceIds.length == 0) {
        uni.showToast({ title: "請選擇連接的設備", icon: "none" });
        return;
      }
      this.getData = [];
      // for (let i = 0; i < this.deviceIds.length; i++) {
      //   this.createBLEConnection(this.deviceIds[i]);
      //   // this.nowLinkLis(this.deviceIds[i]);
      // }
      this.deviceIds.forEach((item) => {
        // this.createBLEConnection(item);
        this.nowLinkLis(item);
      });
    },

    //選擇設備連接吧deviceId傳進來
    createBLEConnection(item) {
      uni.showLoading({
        title: "連接中,請稍等",
        mask: true,
      });
      let that = this;
      //連接藍牙
      uni.createBLEConnection({
        deviceId: item.deviceId,
        success(res) {
          that.shows = true;
          that.stopBluetoothDevicesDiscovery();
          that.getBLEDeviceServices(2, item);
        },
        fail(res) {
          console.log("藍牙連接失敗", res);
          uni.showToast({
            title: items.name + "藍牙連接失敗",
            icon: "none",
          });
        },
      });
    },
    // 停止搜尋藍牙設備
    stopBluetoothDevicesDiscovery() {
      uni.stopBluetoothDevicesDiscovery({
        success: (e) => {
          this.loading = false;
          // console.log("停止搜索藍牙設備:" + e.errMsg);
        },
        fail: (e) => {
          console.log("停止搜索藍牙設備失敗,錯誤碼:" + e.errCode);
        },
      });
    },

    //獲取藍牙的所有服務
    getBLEDeviceServices(index, items) {
      setTimeout(() => {
        uni.getBLEDeviceServices({
          // 這裏的 deviceId 需要已經通過 createBLEConnection 與對應設備建立鏈接
          deviceId: items.deviceId,
          success: (res) => {
            // console.log("成功",res)
            // console.log("device services:", res);
            //這裏會獲取到好多個services  uuid  我們只存儲我們需要用到的就行,這個uuid一般硬件廠家會給我們提供
            console.log("services", res.services);
            res.services.forEach((item) => {
              if (
                item.uuid.indexOf("0000FFE0-0000-1000-8000-00805F9B34FB") != -1
              ) {
                items["serviceId"] = item.uuid;
                //進入特徵
                this.getBLEDeviceCharacteristics(index, items);
              }
            });
          },
        });
      }, 1000);
    },
    //獲取藍牙特徵
    getBLEDeviceCharacteristics(index, items) {
      // console.log("進入特徵");
      setTimeout(() => {
        uni.getBLEDeviceCharacteristics({
          // 這裏的 deviceId 需要已經通過 createBLEConnection 與對應設備建立鏈接
          deviceId: items.deviceId,
          // 這裏的 serviceId 需要在 getBLEDeviceServices 接口中獲取
          serviceId: items.serviceId,
          success: (res) => {
            console.log("characteristics", res);
            res.characteristics.forEach((item) => {
              if (
                // 2 支持監聽   1 支持寫入
                item.uuid.indexOf(
                  index == 1
                    ? "0000FFE1-0000-1000-8000-00805F9B34FB"
                    : "0000FFE2-0000-1000-8000-00805F9B34FB"
                ) != -1
              ) {
                items["characteristicId"] = item.uuid;
                if (index == 2) {
                  this.notifyBLECharacteristicValueChange(items);
                }
              }
            });
            if (index == 1) {
              this.writeString(this
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章