question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Subscribing to two notifiable characteristics simultaneously

See original GitHub issue

Great package, was able to get some ideas going super quickly. Though I ran into a problem; Is it possible to subscribe to two notifiable characteristics at the same time?

I am working on a POC where we want to measure multiple data points simultaneously. Two of the three devices use a notifiable characteristic. I notice that subscribing to a notification stream and reading from a readable characteristic is possible. But when I try to listen to both streams things just kinda freeze.

I am going to spend more time on researching if this is me, not being able to write react. Though, if there are any limitations with regard to simultaneous subscriptions on multiple devices, it would be nice to find out.

This is my code

import React, {useEffect, useState} from 'react';
import {
  SafeAreaView,
  StyleSheet,
  ScrollView,
  View,
  Text,
  StatusBar,
  PermissionsAndroid,
} from 'react-native';
import _ from 'lodash';
import {Colors} from 'react-native/Libraries/NewAppScreen';
import {BleManager} from 'react-native-ble-plx';
import {
  DefaultTheme,
  Button,
  List,
  Provider as PaperProvider,
  Chip,
} from 'react-native-paper';

import Grid, {Row, Col} from './Grid/Grid';

const getCharacteristicContainingUuids = (
  serviceUuid,
  characteristicUuid,
  characteristics,
) => {
  // console.log(
  //   serviceUuid,
  //   characteristics[serviceUuid],
  //   _.find(characteristics[serviceUuid], {uuid: characteristicUuid}),
  //   characteristics,
  // );

  return _.find(characteristics[serviceUuid], {uuid: characteristicUuid});
};

const CharacteristicData = ({
  name,
  serviceUuid,
  characteristicUuid,
  bleState,
}) => {
  const [value, setValue] = useState(null);
  const [subscription, setSubscription] = useState(null);

  const characteristic = getCharacteristicContainingUuids(
    serviceUuid,
    characteristicUuid,
    bleState.characteristics,
  );

  // console.log(characteristic);

  useEffect(() => {
    return () => {
      if (subscription) {
        subscription.remove();
        setSubscription(null);
      }
    };
  }, [subscription]);

  const onPress = (characteristic) => async () => {
    if (characteristic) {
      if (!characteristic.isNotifiable) {
        if (characteristic.isReadable) {
          const result = await characteristic.read();

          if (result) {
            setValue(result.value);
          }
        }
      } else {
        if (!characteristic.isNotifying) {
          const sub = characteristic.monitor((error, result) => {
            // console.log('hoi', name, error, result);
            if (result) {
              setValue(result.value);
            }
          });
          setSubscription(sub);
        } else {
          if (subscription) {
            subscription.remove();
          }
        }
      }
    }
  };

  return (
    <List.Item
      description={name}
      title={value}
      style={characteristic && characteristic.isNotifying && styles.highlight}
      onPress={onPress(characteristic)}
    />
  );
};

const theme = {
  ...DefaultTheme,
  roundness: 2,
  colors: {
    ...DefaultTheme.colors,
    primary: '#3498db',
    accent: '#f1c40f',
  },
};

// console.log(BleManager);

const bleManager = new BleManager();

const requestBLEPermission = async () => {
  try {
    const granted = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
      {
        title: 'Cool BLE Permission',
        message: 'Cool BLE App needs access to your bluetooth adapter ',
        buttonNeutral: 'Ask Me Later',
        buttonNegative: 'Cancel',
        buttonPositive: 'OK',
      },
    );
    if (granted === PermissionsAndroid.RESULTS.GRANTED) {
      console.log('You can use BLE');
      return true;
    } else {
      console.log('BLE permission denied');
      return false;
    }
  } catch (err) {
    // console.warn(err);
    return false;
  }
};

const App = () => {
  const [adapterState, setAdapterState] = useState(false);
  const [bleDevices, setBleDevices] = useState([]);
  const [isScanning, setIsScanning] = useState(false);
  const [connectedDevices, setConnectedDevices] = useState({
    ecFlex: null,
    Temperature: null,
    GDX: null,
  });

  useEffect(() => {
    const subscription = bleManager.onStateChange((state) => {
      if (state === 'PoweredOn') {
        setAdapterState(true);
      } else {
        setAdapterState(false);
      }
    }, true);
    return () => subscription.remove();
  }, []);

  const toggleScanDevices = () => {
    setIsScanning(true);
    setBleDevices([]);

    bleManager.startDeviceScan(null, {}, (bleError, device) => {
      // note devices may be scanned multiple times
      if (device && _.findIndex(bleDevices, {id: device.id}) < 0) {
        bleDevices.push(device);
        setBleDevices(bleDevices);
      }
    });

    setTimeout(() => {
      setIsScanning(false);
      bleManager.stopDeviceScan();
    }, 5000);
  };

  const permission = requestBLEPermission();

  const findDeviceWhereNameContains = (name) => {
    let device = null;
    _.each(bleDevices, (item) => {
      if (String(item.name).includes(name)) {
        device = item;
      }
    });
    return device;
  };

  const getIconForSensor = (name) => {
    if (bleDevices.length === 0) {
      return 'access-point';
    }

    if (findDeviceWhereNameContains(name)) {
      return 'access-point-network';
    } else {
      return 'access-point-network-off';
    }
  };

  const connectDevice = async (name) => {
    let device = findDeviceWhereNameContains(name);

    if (device == null) {
      setConnectedDevices({...connectedDevices, [name]: null});
      return false;
    }

    let isConnected = await device.isConnected();

    if (!isConnected) {
      device = await bleManager.connectToDevice(device.id);
      isConnected = await device.isConnected();
    }
    // console.log(isConnected, device);
    device = await device.discoverAllServicesAndCharacteristics();

    device.onDisconnected((error, device) => {
      setConnectedDevices({...connectedDevices, [name]: null});
    });

    const services = await device.services();
    const characteristics = {};
    const descriptors = {};
    await _.forEach(services, async (service) => {
      characteristics[service.uuid] = await device.characteristicsForService(
        service.uuid,
      );
      // descriptors[service.uuid] = device.
    });

    setConnectedDevices({
      ...connectedDevices,
      [name]: {
        connected: isConnected,
        services,
        device,
        characteristics,
        // descriptors,
      },
    });
  };

  const toggleConnectDevice = (name) => async () => {
    if (!connectedDevices[name]) {
      await connectDevice(name);
    } else {
      const device = await connectedDevices[name].device.cancelConnection();
      if (!(await device.isConnected())) {
        setConnectedDevices({...connectedDevices, [name]: null});
      }
    }
  };

  console.log(bleDevices.length + ' devices found');
  console.log(connectedDevices);

  return (
    <PaperProvider theme={theme}>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView>
        <ScrollView
          contentInsetAdjustmentBehavior="automatic"
          style={styles.scrollView}>
          {global.HermesInternal == null ? null : (
            <View style={styles.engine}>
              <Text style={styles.footer}>Engine: Hermes</Text>
            </View>
          )}
          <View style={styles.body}>
            <View style={styles.sectionContainer}>
              <Text style={styles.sectionTitle}>Step One Permissions</Text>
              <Text style={styles.sectionDescription}>
                {permission
                  ? 'we have permission to use BLE'
                  : "we don't have permission to use BLE"}
              </Text>
            </View>
            <View style={styles.sectionContainer}>
              <Text style={styles.sectionTitle}>Step two Adapter</Text>
              <Text style={styles.sectionDescription}>
                The bluetooth adapter is {adapterState ? 'on' : 'off'}
              </Text>

              <Button
                icon={!adapterState ? 'bluetooth-connect' : 'bluetooth-off'}
                mode="contained"
                onPress={() => {
                  // todo android only
                  if (adapterState) {
                    bleManager.disable();
                  } else {
                    bleManager.enable();
                  }
                }}>
                Toggle Bluetooth adapter
              </Button>
            </View>
            {adapterState && (
              <View style={styles.sectionContainer}>
                <Text style={styles.sectionTitle}>Step three scan devices</Text>
                {!isScanning && (
                  <Button
                    icon={adapterState ? 'bluetooth-connect' : 'bluetooth-off'}
                    mode="contained"
                    onPress={toggleScanDevices}>
                    Scan for devices
                  </Button>
                )}
                {bleDevices.length > 0 && (
                  <Button mode="contained" onPress={() => setBleDevices([])}>
                    Clear
                  </Button>
                )}
                {bleDevices.length > 0 && (
                  <List.Section>
                    <List.Accordion title="Devices found">
                      {_.map(bleDevices, (device, index) => (
                        <List.Item
                          key={index}
                          title={
                            (device.name || 'device') + ` (${device.rssi})`
                          }
                          description={device.localName}
                        />
                      ))}
                    </List.Accordion>
                  </List.Section>
                )}
              </View>
            )}

            {adapterState && (
              <View style={styles.sectionContainer}>
                <Text style={styles.sectionTitle}>
                  Step four connect to WSS devices
                </Text>

                <Grid>
                  <Col>
                    <Chip
                      icon={getIconForSensor('GDX')}
                      selected={!!connectedDevices.GDX}
                      onPress={toggleConnectDevice('GDX')}>
                      Vernier
                    </Chip>
                  </Col>
                  <Col>
                    <Chip
                      icon={getIconForSensor('Temperature')}
                      selected={!!connectedDevices.Temperature}
                      onPress={toggleConnectDevice('Temperature')}>
                      Temperature
                    </Chip>
                  </Col>
                  <Col>
                    <Chip
                      icon={getIconForSensor('ecFlex')}
                      selected={!!connectedDevices.ecFlex}
                      onPress={toggleConnectDevice('ecFlex')}>
                      EcFlex
                    </Chip>
                  </Col>
                </Grid>
              </View>
            )}

            <View style={styles.sectionContainer}>
              <Text style={styles.sectionTitle}>
                Step five read data from WSS devices
              </Text>
              {!!connectedDevices.Temperature && (
                <List.Section>
                  <List.Subheader>Temperature board data</List.Subheader>
                  <CharacteristicData
                    name="temperature"
                    serviceUuid="b067f00d-744d-8db5-9b42-aae2d7041e3c"
                    characteristicUuid="b067beef-744d-8db5-9b42-aae2d7041e3c"
                    bleState={connectedDevices.Temperature}
                  />
                </List.Section>
              )}
              {!!connectedDevices.ecFlex && (
                <List.Section>
                  <List.Subheader>ecFlex data</List.Subheader>
                  <CharacteristicData
                    name="ecflex-raw"
                    serviceUuid="00002d8d-0000-1000-8000-00805f9b34fb"
                    characteristicUuid="00002da7-0000-1000-8000-00805f9b34fb"
                    bleState={connectedDevices.ecFlex}
                  />
                </List.Section>
              )}
              {!!connectedDevices.GDX && (
                <List.Section>
                  <List.Subheader>Vernier data</List.Subheader>
                  <CharacteristicData
                    name="vernier-raw"
                    serviceUuid="8e6f0f58-5819-11e6-8b77-86f30ca893d3"
                    characteristicUuid="8e6f094a-5819-11e6-8b77-86f30ca893d3"
                    bleState={connectedDevices.GDX}
                  />
                </List.Section>
              )}
            </View>
          </View>

          <View style={styles.sectionContainer}>
            <Text style={styles.sectionTitle}>
              Step six collect multiple data streams in one set
            </Text>
            <Text style={styles.sectionTitle}>TODO</Text>
          </View>
        </ScrollView>
      </SafeAreaView>
    </PaperProvider>
  );
};

const styles = StyleSheet.create({
  scrollView: {
    backgroundColor: Colors.lighter,
  },
  engine: {
    position: 'absolute',
    right: 0,
  },
  body: {
    backgroundColor: Colors.white,
  },
  sectionContainer: {
    marginTop: 32,
    paddingHorizontal: 24,
  },
  sectionTitle: {
    fontSize: 24,
    fontWeight: '600',
    color: Colors.black,
  },
  sectionDescription: {
    marginTop: 8,
    fontSize: 18,
    fontWeight: '400',
    color: Colors.dark,
  },
  highlight: {
    fontWeight: '700',
  },
  footer: {
    color: Colors.dark,
    fontSize: 12,
    fontWeight: '600',
    padding: 4,
    paddingRight: 12,
    textAlign: 'right',
  },
});

export default App;

I hope anyone can help me! Thank you

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:2
  • Comments:11

github_iconTop GitHub Comments

1reaction
Cleversou1983commented, Dec 31, 2021

I did solve it but not with this library instead I went with https://github.com/innoveit/react-native-ble-manager

Thanks, man! I’ll check this out!

1reaction
wvanooijen92commented, Dec 10, 2021

I did solve it but not with this library instead I went with https://github.com/innoveit/react-native-ble-manager

Read more comments on GitHub >

github_iconTop Results From Across the Web

Subscribing to two notifiable characteristics simultaneously #747
I am working on a POC where we want to measure multiple data points simultaneously. Two of the three devices use a notifiable...
Read more >
How to subscribe to multiple BluetoothLE Characteristics with ...
I am developing an Android app which should subscribe to multiple BLE characteristics. But whatever I do, I receive only the updated values ......
Read more >
Subscribe to characteristic notification or indication - MATLAB ...
Notification or indication depends on the Attributes property of the characteristic c . The property must contain "Notify" , "Indicate" , or both....
Read more >
DA14531 Advice on Custom Service / Characteristic
Hi, I'm trying to figure out the best way write / read an external EEPROM over BLE with the DA14531. One method I...
Read more >
Generic Attribute Profile (GATT) — BLE-Stack User's Guide for ...
For a hands-on review of GATT services and characteristics, see SimpleLink Academy. ... Multiple services can be grouped together to form a profile....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found