dotintent / react-native-ble-plx

React Native BLE library
Apache License 2.0
3.08k stars 517 forks source link

writeWithResponse returns a response that is the sent message #1253

Open RoyceTheBiker opened 6 days ago

RoyceTheBiker commented 6 days ago

Prerequisites

Question

I have a peripheral device hosting BLE service with two characteristics, one for reading from the device, and one for writing to the device. The React-Native screen shows a list of Bluetooth devices and the user clicks on one to connect. My device is listening for a message GET config.

After connecting to the device the GET config message is sent to the device, it arrives and is perfectly readable on the device. At that point, the device would send back some config data but I have removed that because the only thing that shows in the response.value is GET config

I have removed the device code that would send a response, it is no longer sending a response, but the control app still gets a response that seems to be coming from itself with the same message it sent out.

I have tried using writeCharacteristicWithResponseForService and it does the same thing, replies with the outgoing message.

What am I missing? How can I stop the response from being the outgoing message?

Question related code

import { BleManager, Characteristic, Device, Service, State } from "react-native-ble-plx";
import Base64 from 'react-native-base64';

let bleManager: BleManager;
let btDevice: Device;
let btServices: Array<Service>;
let readChannel: Characteristic;
let writeChannel: Characteristic;

const ServiceUUID   = "aac12ad2-a77c-48e2-89ad-a3e7a32422fe"
const ReadFromESP32 = "915bb543-3299-403d-b924-b2c1887b4c82"
const WriteToESP32  = "f78f9b6c-c078-4ef1-af4f-b68c1df1af4e"

export const initBluetooth = (connectDevice: string): Promise<string> => {
    return new Promise( (resolve) => {
        if(!bleManager) {
            bleManager = new BleManager();
            if(bleManager) {
                bleManager.startDeviceScan(null, null, (error, device) => {
                    if (error) {
                        console.log('Device scan error %s', error.message);
                    } else {
                        if((connectDevice === device.localName) || (connectDevice === device.name)) {
                            // console.log('BT Device is set');
                            btDevice = device;
                            resolve('Connected');
                        }
                    }
                });
            }
        }
    });
}

export const connectToDevice = (deviceName: string): Promise<string> => {
    console.log('Connecting to BT device %s', deviceName);
    return new Promise( (resolve) => {
        console.log('Call connect');
        btDevice.connect().then( (device: Device) => {
            console.log('Discovering characteristics');
            return device.discoverAllServicesAndCharacteristics();
        }).then( (device: Device) => {
            console.log('charactoristics discovered');
            return device.services();
        }).then( (services: Service[]) => {

            btServices = new Array<Service>;
            services.forEach( (service) => {
                btServices.push(service);
                service.characteristics().then( (channels: Characteristic[]) => {
                    channels.forEach( (channel) => {
                        if(channel.uuid === ReadFromESP32) {
                            readChannel = channel;
                            // console.log('setupMonitoring');
                            // setupMonitoring(btDevice);
                            console.log('read channel has write with response %s', channel.isWritableWithResponse ? 'true' : 'false');
                        }
                        if(channel.uuid === WriteToESP32) {
                            console.log('write channel has write with response %s', channel.isWritableWithResponse ? 'true' : 'false');

                            writeChannel = channel;
                        }
                    });
                });
            });
            resolve('Connected');
        });
    });
}

// const incomingMessage = (error: BleError | null, charactoristic: Characteristic | null) => {
//     if(error) {
//         console.error(error.message);
//         return;
//     }
//     if(!charactoristic?.value) {
//         console.error('No data');
//         return;
//     }
//     console.log('Data %s', Base64.decode(charactoristic.value));
// }

// const setupMonitoring = async (device: Device) => {
//     if(device) {
//         console.log('setupMonitoring');
//         device.monitorCharacteristicForService(ServiceUUID, ReadFromESP32, incomingMessage);
//     }
// }

export const getData = (request: string): Promise<string> => {
    console.log('Request: %s', request);
    return new Promise( (resolve) => {
        bleManager.state().then( (bleState: State) => {
            console.log('BLE State is %s', bleState);
            if(bleState === 'PoweredOn') {
                console.log('Sending message to %s', writeChannel.uuid);
                console.log('request %s', request);

                let requestB64 = Base64.encode(request);
                console.log('requestB64 length %d', requestB64.length);
                console.log('requestB64 %s', requestB64);

                writeChannel.writeWithResponse(requestB64).then( (response: Characteristic) => {                    
                    console.log('got a response from %s', response.uuid);
                    console.log('got a response %s', Base64.decode(response.value));
                    // console.log('got a readChannel %s', readChannel.value ? Base64.decode(readChannel.value) : 'NULL');
                    // console.log('got a writeChannel %s', writeChannel.value ? Base64.decode(writeChannel.value) : 'NULL');                    
                    resolve(Base64.decode(response.value));
                }).catch( (err) => {
                    console.error('response %s', err.message);
                });
                // btDevice.writeCharacteristicWithResponseForService(ServiceUUID, WriteToESP32, requestB64).then( (response: Characteristic) => {
                //     console.log('got a response from %s', response.uuid);
                //     console.log('got a response %s', Base64.decode(response.value));
                // }).catch( (err) => {
                //     console.error(err.message);
                // });
            } else {
                resolve('Device is powered off');
            }
        });
    });
};

export const putData = (request: object): Promise<string> => {
    return new Promise( (resolve, reject) => {
        // btdevice send request, get a response
    });
};

Update 2024-11-26

I have now tested my peripheral device using the LightBlue app by Punch Through. With the device having the notification on write disabled, the app receives the sent message as the reply. I re-enabled the onWrite callback and now LightBlue receives the message that the device is set to reply with. I tried adding notify permission to the write characteristic and now LightBlue shows me a subscribe button that does not seem to do anything. I don't think that would help because I need the response to be in the promise returned from sending a message.

RoyceTheBiker commented 5 days ago

I may have found a workaround. The Wiki for Characteristic Writing has a helpful statement Latest value may not be stored inside returned object., so I could have this problem again in the future, but for now I have something working.

I found that writeWithoutResponse is able to get the correct reply by using monitor. I hope this does not become a problem for follow-up messages.

Can anyone tell me if I need to destroy this monitor to prevent it from catching other replies for to other messages?

writeChannel.writeWithoutResponse(requestB64).then( (response: Characteristic) => {
       response.monitor( (err: BleError, characteristic: Characteristic) => {
             if(err) {
                 console.error(err.message);
             } else {
                  console.log('characteristic value %s', Base64.decode(characteristic.value));
             }
       });                    
    }).catch( (err) => {
       console.error(err.message);
    });