don / cordova-plugin-ble-central

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

Able to send only one command #851

Closed JEricaM closed 2 years ago

JEricaM commented 3 years ago

Hi

I'm developing a ionic + stencil app. After sending a command, from the second onwards they no longer work, they are sent but it is as if they were not received. I have to disconnect and reconnect to the device to successfully send the command. Someone else has this problem?

This is my code:

  startScan() {
    this.setStatus('Scanning for Bluetooth LE Devices');
    let connectBtn = document.querySelector('#connectBtn') as any;
    connectBtn.innerText = "In corso...";

    this.device = [];  // clear list
    BLE.isEnabled().then(() => { 
      //console.log('isEnabled ok');
      BLE.startScan([]).subscribe(
        device => {
          console.log('dentro subscribe: ', device);
          if (device.name == 'EGTEL3') {
            console.log('egtl');
            this.onDeviceDiscovered(device);
            this.stopScan();
          };
        },
        error => {
          this.scanError(error);
          this.stopScan();
        } 
      );
    }
    ).catch(() => { 
        //console.log('isEnabled no');
          setTimeout(this.startScan,
          5000,
          function () { console.log("Scan complete"); },
          function () { console.log("stopScan failed"); }
        );
      }
    )
  }

onDeviceDiscovered(device) {
      console.log('Discovered ' + JSON.stringify(device, null, 2));
      this.connectToDevice(device);
  }

  connectToDevice(device){
    var toast = new utils();
    let tclass;
    let tmessage;

    console.log('----connectToDevice----');
    BLE.connect(device.id).subscribe( 
        peripheralData => {
          this.device = peripheralData;
          let connectBtn = document.querySelector('#connectBtn') as any;
          connectBtn.innerText = i18n("Connessione avvenuta");
          connectBtn.disabled = true;
          tmessage = i18n("Connessione avvenuta correttamente.");
          tclass = "toastOk";
          toast.presentToast(tmessage, "middle", 3000, tclass);
        },
        error => {
          console.log('connect error: ', error);
          let connectBtn = document.querySelector('#connectBtn') as any;
          connectBtn.innerText = i18n("Collegati al telecomando");
          tmessage = i18n("Connessione non avvenuta correttamente, riprovare.");
          tclass = "toastError";
          toast.presentToast(tmessage, "middle", 3000, tclass);
        }
    );
  }

  disconnectFromDevice(device){
    console.log('----disconnectFromDevice----');
    BLE.disconnect(device.id).then(() => {
      let connectBtn = document.querySelector('#connectBtn') as any;
      connectBtn.innerText = i18n("Collegati al telecomando");
      connectBtn.disabled = false;
      this.device = [];
    });
  }

  async sendCommandNew(id){
    console.log('-----sendCommand----');
    console.log('commandToSend: ', this.commandToSend);

    let data;
    data = this.stringToBytes(this.commandToSend);
    console.log('data: ', data);

    let timerId = setInterval(() => {
      BLE.write(id, "49535343-fe7d-4ae5-8fa9-9fafd205e455", "49535343-1e4d-4bd9-ba61-23c647249616", data).then((res) => {
        console.log('res: ', res);
      }).catch(() => {
        console.log('res no');
      }
      )
    }, 1000);
    setTimeout(() => { clearInterval(timerId); console.log('stopped'); }, 5000);
  }

on function sendCommandNew I have a timeout that I use to send more times the command inside 5000ms. I don't do anything after sendCommandNew its called so I don't know why only the first command works.

peitschie commented 3 years ago

@JEricaM do you see any res logged here? If not, perhaps the BLE device isn't giving you a response to the write? Does writeWithoutResponse give you any different behaviour here? https://github.com/don/cordova-plugin-ble-central#writewithoutresponse

JEricaM commented 3 years ago

Every time a send a command, my RES is null on IOS, and it's OK on Android.

I don't know why. Even if the command is sent successfull (I can see that my connected device receive it correctly ) on ios it's null. And this is the first problem that don't let me to understand better the situation. I saw that it seem an unresolved issue...

rvignero commented 3 years ago

Same here, Ok for BLE.read but can't send any command in IOS.. didn't try on android yet but the strange thing is that the code I have used to work before on my ionic 4 project.

I started a new ionic project with capacitor, and it seems that it's impossible to get any response from the .buffer as ArrayBuffer for BLE.write.

So I re started a ionic cordova project to fix the buffer trouble, worked with cordova but no res, error or nothing from the BLE.write ... It also stop all my event callback when trying to BLE.write

JEricaM commented 3 years ago

I'm able to send command with iOS, but the res it's always null. I didn't tried ble.read yet, this works properly?

digoocorrea commented 3 years ago

Hi there, I had a similar issue and solved it by using the .startNotification(peripheral.id, SERVICE_ID, CHARACTERISTIC_ID) function once the device is connected to subscribe any returns from the device and handle the received data. Also I had to change the .write() to .writeWithoutResponse() and all worked fine.

Hope this helps!

JEricaM commented 3 years ago

Thank you so so much. I will try this asap!

JEricaM commented 3 years ago

@digoocorrea I've added startNotification after the connection but every tot seconds the function it's called also if I don't do anything and if I console.log I see that prints ascii characters. I've also changed .write in . writeWithoutResponse This is my code

   connectToDevice(device){
    BLE.connect(device.id).subscribe( 
        peripheralData => {

          this.device = peripheralData;

            BLE.startNotification(this.device['id'], "49535343-fe7d-4ae5-8fa9-9fafd205e455", "49535343-1e4d-4bd9-ba61-23c647249616").subscribe(
            success => {
                let x = this.bytesToString(success);
                    console.log('ok: ', success); // before bytesToString
                    console.log('x: ', x); // after bytesToString
            }
                );

          localStorage.setItem('device', JSON.stringify(this.device));
        error => {
          console.log('connect error: ', error);
        }
      }
    );
  }
Schermata 2021-05-10 alle 11 00 52

I don't get this behaviour of the function. After I send a command using .writeWithoutResponse the startnotification callback returns always "\u0000", no matter what I send

digoocorrea commented 3 years ago

Hi @JEricaM

I believe that's right. That's what your ble device is returning.

The subscription function (startNotification) only notify every message your ble device is communicating through the characteristic. Everytime the characteristic value changes, the subscription will trigger showing you the new value. It does return each message as byte arrays.

Try the messages as console.log(success[0]) or console.log(String.fromCharCode(success[0])) and see what is the actual value of the buffer.

JEricaM commented 3 years ago

But how this solved the problem to return data after write? Because start notification work indipendently and it's called even if I don't write anything

digoocorrea commented 3 years ago

Well, my use case is communicating with a simple ble device which does not receive or send much information.

In my case it is a customers proprietary ble device which sends only update status data and responses for my commands. So I treat each response and interpret what that response was. For example, the ble device sends me a notification "S" to let the app know that there is statuses updates to be collected, and I write a command "S" which returns the status data array from the device and a "OKS" message response.

In my use case, if I send any command to the ble device it will return a "OK"+Command (Success) or "NK"+Command (Failure) response. It is very simple indeed. That's how I made it work, the device always returns some sort of code letting me know if that command was successful or not and the notification subscription only receives those responses so I can take some action. It is not synchronous.

Being a proprietary device, I can count on the ble device engineers to setting up that kind of communication. It is kind of a custom implementation on device's side.

I fear that, on this kind of use case, you will not be able to work as REST like responses, where you know which response is for which command/data written. At least as far as I know.

This way of communication that I suggested just allows you to write to a characteristic and then the subscription will monitor the changes, the ble device response will trigger the notification and show any response from the device (your write response included).

Sorry for not being so helpful, I'm not one of the plugin expert/developers. Actually I'm having some issues with the plugin autoconnect function myself, which is why I'm here. And once you have issued the problem on not receiving responses, I saw your question and decided on trying to help you, this was my best shot on helping you to get some sort of response from your ble device.

I also recommend you the ask on stackoverflow, maybe you can get lucky there, on my own experience here I unfortunately did not had any responses from the plugin team so far.

JEricaM commented 3 years ago

Hi First of all thank you for your time :) Any help it's appreciated. I asked "But how this solved the problem to return data after write? Because start notification work indipendently and it's called even if I don't write anything" only to understand better the context of your application (so I can figure it out if was similar to mine). Thank you for explain it to me.

In my case I need to connect to a peheripheral with a defined protocol (that I can't edit) and I need to send commands using ble.write. 4 of the commands that I need to send works perfectly because I don't need to wait for an answer from the pheripheral. Instead, I have 1 command that need to send me back informations and I don't know how to get them because I'm not able to retrieve any informations on .write response.

I think I will need to skip this command :S

peitschie commented 2 years ago

Hi @JEricaM

I'm just catching up on some of these issues.

It's worth noting that the behaviour you're describing may mean that the peripheral is replying via a notification or indication to your write.. which I agree certainly makes your life a little harder!

If this is the case, here's an approach I've used to talk with peripherals like this in my own code. Please let me know if it doesn't make any sense.

const delay = (millis) =>  new Promise(resolve => setTimeout(resolve, millis)); 

let notificationData = [];
ble.startNotification(device_id, service_uuid, characteristic_uuid).subscribe(success => {
  notificationData.push(...this.bytesToString(success))
});

await ble.write(device_id, service_uuid, characteristic_uuid, data);
await delay(500); // delay for 500ms
if (notificationData.length == 0) {
 await delay(500);
}
if (notificationData.length == 0) {
  console.error("no data received");
} else {
 console.log("data received was:", notificationData);
 notificationData = []; // reset shared variable for other calls
}
peitschie commented 2 years ago

I'll close this for now as it doesn't seem like there is a problem to be solved in the plugin. However, feel free to continue the discussion here!