don / cordova-plugin-ble-central

Bluetooth Low Energy (BLE) Central plugin for Apache Cordova (aka PhoneGap)
Apache License 2.0
947 stars 608 forks source link

Ios error stopStateNotifications #909

Closed AngeNew closed 2 years ago

AngeNew commented 2 years ago

Hi, I use in my app startStateNotifications in android works fine, in Ios works but after call startStateNotifications calls stopStateNotifications without use in my code, Xcode return me this: 2022-03-24 09:43:27.008398+0100 App[657:59780] Status of CoreBluetooth central manager changed 4 State BLE powered off (CBCentralManagerStatePoweredOff) 2022-03-24 09:43:27.008498+0100 App[657:59780] Report Bluetooth state "off" on callback BLE751184174 To Native Cordova -> BLE stopStateNotifications INVALID ["options": []] To Native Cordova -> BLE stopStateNotifications INVALID ["options": []]

this happens only after use first startStateNotifications in app, after first time works fine. How can I trouble??

peitschie commented 2 years ago

@AngeNew is this using the ionic wrapper? If so, are you able to see if you experience the same problem using the plugin directly without going via the wrapper?

AngeNew commented 2 years ago

I use this function, in two different scenarios in ios, and I see in one of this, all works fine, and in the other scenario doesn't work, while in android works fine in all scenarios. No I am not using ionic wrapper

peitschie commented 2 years ago

Interesting. Are you able to provide more details about the two scenarios? I can't think of an obvious reason why the state notifications would stop like this. Is there any chance you can provide a reproduction source repo I could test with?

AngeNew commented 2 years ago

it is as if the error came from the fact that at the first start it sees the bluetooth off, and therefore it cannot stop the status, because if you see when it gives me the 2 invalid calls, the bluetooth status is off, but the bluetooth was on , while I was doing the test, but at the first start he sees it turned off, another thing, I search in google CBCentralManagerStatePoweredOff, and in various forum tell is deprecated in Ios 10, is it true??

this is my code:

import { Component, NgZone, OnInit } from '@angular/core';
import { AlertController, NavController, Platform } from '@ionic/angular';
import Echo from '../echo-plugin'
import { BLE } from '@awesome-cordova-plugins/ble/ngx';
import { TranslateService } from '@ngx-translate/core';
import { concatMap, tap } from 'rxjs/operators';
import { ActivatedRoute } from '@angular/router';
import { Storage } from '@capacitor/storage';

/**
 * Bluetooth
 * 
 * Bluetooth serves to connection to bluetooth sensors
 */
@Component({
  selector: 'app-bluetooth',
  templateUrl: './bluetooth.page.html',
  styleUrls: ['./bluetooth.page.scss'],
})
export class BluetoothPage implements OnInit {
  //isAndroid save true if platform is android else false
  isAndroid: boolean;
  /** devices is array where save only all sensors Xsens DOT */
  devices: any[] = [];
  /** status1 contains state of first sensor */
  status1 = "";
  /** status2 contains state of second sensor */
  status2 = "";
  /** statusConnected contains  translate string of "connected" */
  statusConnected = "";
  timeOutAlert: any;
  /** stateSuccess contains state of bluetooth, state can be  mainly 'on' or 'off'" */
  stateSuccess: string;
  sub: any;
  nameImage: string = "";
  /** id1 save value of callback getIdSensor1 in storage */
  id1: string;
  /** id2 save value of callback getIdSensor2 in storage */
  id2: string;
  /** isConnect1 is OK, if sensor1 is connected */
  isConnect1: string = "";
  /** isConnect2 is OK, if sensor2 is connected */
  isConnect2: string = "";
  translateConnectedSubscription: any;
  startStateNotificationsSubscription: any;
  connectSubscription: any;
  /** devicesDisconnected save devices disconnected from failure function of connectSensor*/
  devicesDisconnected: any[] = [];
  constructor(private ble: BLE, private translate: TranslateService, private route: ActivatedRoute, private nav: NavController, private platform: Platform, private ngZone: NgZone, public alertController: AlertController) { }

  /* async tryPlugin() {
    const { value } = await Echo.startScan();
    console.log('Response from native return:', value);

  } */

  ngOnInit() {
    this.devices = [];
    this.isAndroid = this.platform.is("android");
    this.sub = this.route.params.subscribe(params => {
      this.nameImage = params.nameImage2;
    });

    this.translateConnectedSubscription = this.translate.get('tab5.Connected').subscribe((res: string) => {
      this.statusConnected = res;
    });

  }

  ngOnDestroy() {
    this.translateConnectedSubscription.unsubscribe();
  }

  /** automatize scan of bluetooth devices */
  ionViewDidEnter() {

    const getIdSensor1 = async () => {
      const { value } = await Storage.get({ key: 'id1' });
      this.id1 = value;

      /** block of connection success first sensor */
      this.ble.isConnected(this.id1).then(() => {
        this.isConnect1 = "OK";
        console.log("sono qui" + this.isConnect1);
        this.status1 = this.statusConnected;

      },
        /** block of connection failure first sensor */
        () => {
          this.scan();
        });

    };
    getIdSensor1().then(() => {
    });

    const getIdSensor2 = async () => {
      const { value } = await Storage.get({ key: 'id2' });
      this.id2 = value;

      /** block of connection success second sensor*/
      this.ble.isConnected(this.id2).then(() => {
        this.isConnect2 = "OK";
        console.log(this.isConnect2);
        this.status2 = this.statusConnected;
      },
        /**block of connection failure second sensor*/
        () => {
          this.scan();
        });
    }
    getIdSensor2().then(() => {
    });

  }

  //disable timeOut of scan
  ionViewDidLeave() {
    clearTimeout(this.timeOutAlert);
  }

  /** stop notifications of bluetooth status */
  stopStateNotifications() {
    console.log("excuted stop");
    this.ble.stopStateNotifications().then(
      () => {
        console.log("stop state notification")
      },
      () => { });

  }

  /** scanning new bluetooth devices */
  async scan() {
    /** check bluetooth is on or off */
    this.startStateNotificationsSubscription = this.ble.startStateNotifications().pipe(
      tap((state) => {
        this.devices = [];
        document.getElementById("spinnerChange").style.display = "initial";
        this.stateSuccess = state;

      }),
      concatMap((state) => {
        if (state == "on") {
          this.timeOutAlert = setTimeout(() => {

            /** check if scan find 0 or 1 sensors and show corresponding alert */
            if (this.devices.length <= 1) {
              this.showSensorsDisabled(this.devices.length);
            }

            /** app find all 2 sensors and set ids in storage */
            else if (this.devices.length == 2) {

              /** after 3 seconds, if previous view is exercises and sensors is connected goes to calibration else if previous view is more remains in bluetooth */
              if (this.nameImage.indexOf("exercises") > 0) {
                this.nameImage = this.nameImage.replace(" exercises", "");
                this.nav.navigateForward(["/calibration", this.nameImage], { animated: true });

              } else if (this.nameImage.indexOf("bluetooth") > 0) {
                return;
              }
            }

          }, 3000);

          return this.ble.scan([], 3)
        }
        else if (state == "off") {
          this.alertBluetoothDisabled();
        }
      }),
      tap(device => {
        this.onDeviceDiscovered(device);
      })
    ).subscribe(() => {

    }, () => {

    });

  }

  /** discover and save only sensors, connect 2 sensors and set statuses */
  onDeviceDiscovered(device) {
    this.ngZone.run(() => {
  if (device.name == "") {
        this.devices.push(device);
        if (this.devices.length == 2) {
          this.connectSensor(this.devices[0].id);
          this.connectSensor(this.devices[1].id);
          const setId1Sensor = async () => {
            await Storage.set({
              key: 'id1',
              value: this.devices[0].id
            })
          };
          setId1Sensor().then();

          const setId2Sensor = async () => {
            await Storage.set({
              key: 'id2',
              value: this.devices[1].id
            })
          };
          setId2Sensor().then();

          this.status1 = this.statusConnected;
          this.status2 = this.statusConnected;
          document.getElementById("refresh").style.pointerEvents = "none";

        }

      }

    })
  }

  /** alert shows disabled bluetooth */
  async alertBluetoothDisabled() {

    const alert = await this.alertController.create({
      cssClass: 'my-custom-class',
      header: '',
      subHeader: 'Bluetooth disabilitato. Attiva il Bluetooth per trovare i sensori.',
      message: '',
      buttons: ['OK']
    });

    await alert.present();
    this.stopStateNotifications();

    document.getElementById("spinnerChange").style.display = "none";
  }

  /** alert shows sensors disconnected */
  async alertSensorsDisconnected() {
    this.devices = [];
    const alert = await this.alertController.create({
      cssClass: 'my-custom-class',
      header: '',
      subHeader: 'Controlla che il bluetooth e i sensori siano accessi dopo ripeti la procedura di connessione ai sensori',
      message: '',
      buttons: ['OK']
    });

    await alert.present();
    this.stopStateNotifications();

  }

  /** alert shows one or two sensors are disabled */
  async showSensorsDisabled(length) {
    clearTimeout(this.timeOutAlert);
    this.devices = [];
    this.stopStateNotifications();
    if (length == 0) {
      const alert1 = await this.alertController.create({
        cssClass: 'my-custom-class',
        subHeader: "Sensori non trovati, accendi i due sensori",
        buttons: [{
          text: "Annulla",
          handler: () => {
            document.getElementById("spinnerChange").style.display = "none";

          }
        }, {
          text: "Riprova",
          handler: () => {
            this.scan();
          }
        }]
      });
      await alert1.present();
    } else if (length == 1) {
      const alert2 = await this.alertController.create({
        cssClass: 'my-custom-class',
        subHeader: "Uno dei 2 sensori è spento, accendi il sensore",
        buttons: [{
          text: "Annulla",
          handler: () => {
            document.getElementById("spinnerChange").style.display = "none";

          }
        }, {
          text: "Riprova",
          handler: () => {
            this.scan();
          }
        }]
      });
      await alert2.present();
    }
  }

  /** connect sensor by id */
  connectSensor(id) {
    this.connectSubscription = this.ble.connect(id).subscribe(
      (dataConnect) => {
        console.log(dataConnect);

      },
      (dataDisconnect) => {
        document.getElementById("refresh").style.pointerEvents = "initial";

        this.devicesDisconnected.push(dataDisconnect.id);
        if (this.devicesDisconnected.length == 2) {
          this.isConnect1 = "";
          this.isConnect2 = "";
          this.devicesDisconnected = [];
          document.getElementById("sensor1").style.display = "none";
          document.getElementById("sensor2").style.display = "none";

        } else if (this.devicesDisconnected.length == 1) {
          console.log(dataDisconnect);
          this.isConnect1 = "";
          this.isConnect2 = "";
          this.alertSensorsDisconnected();
          document.getElementById("sensor1").style.display = "none";
          document.getElementById("sensor2").style.display = "none";
          this.ble.disconnect(this.id1).then();
          this.ble.disconnect(this.id2).then();

        }

      });
  }

}
AngeNew commented 2 years ago

Another thing that I want to question is this, what is better approach practically to test my bluetooth component?? and what is coverage test percentage in your plugin?? because, I use plugin in app that respects CE certification

peitschie commented 2 years ago

Another thing that I want to question is this, what is better approach practically to test my bluetooth component?? and what is coverage test percentage in your plugin?? because, I use plugin in app that respects CE certification

The short answer is there is very minimal coverage within this plugin. CE certification generally requires the hardware and BLE firmware to be considered also, which is all well outside the bounds of what this plugin does. Unfortunately, there's no standard approach I can suggest here as it all depends on the regulations applicable for your industry.

peitschie commented 2 years ago

@AngeNew based on the above code snippet, there seems to be quite a bit of complexity in how you're wiring up the Ionic wrapper with your own code.

The crux of the issue however looks to me like it's caused by calling stopStateNotifications when showing the BLE disabled dialog. If you disable state notifications, how does your app detect when the user re-enables the BLE again?

image

I'd suggest try removing all the stopStateNotification calls except for the one done when leaving the view, and see if that improves the behaviour.

It's very expected that the BLE adapter may report as initially off on iOS, due to how the BLE adapter initialises on first access.

AngeNew commented 2 years ago

thanks, I understand for test question and I try to delete all stopStateNotification calls and reorganize states in my app, thanks also for this

peitschie commented 2 years ago

My pleasure! I'll close this for now, but feel free to re-open if you need more help with this @AngeNew 🙂