Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Template cast (uses PR #49) #52

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
May 30, 2020
Merged

Template cast (uses PR #49) #52

merged 21 commits into from
May 30, 2020

Conversation

Jeroen88
Copy link

In PR #49 the value returned by reading a remote characteristic or by getting a notification for it is kept in the class instance of the NimBLERemoteCharacteristic. This value can be accessed as a std::string type using the getValue() function.

This PR adds templates to read the value in the type used by the peripheral. If e.g. the type of the value in the remote characteristic is a uint16_t you can do:

uint16_t temperature = pCharacteristic->getValue<uint16_t>();

This is even possible if the data returned is more complex. A LYWSD03MMC temperature / humidity sensor e.g. returns both a uint16_t temperature and a uint8_t humidity. Since the temperature is times 100, it needs to be divided by 100.0 to get the right temperature and the decimals. Now you can do this:

struct LYWSD03MMCData {
  uint16_t temperature;
  uint8_t humidity;
};

time_t timestamp;

LYWSD03MMCData data = pCharacteristic->getValue<LYWSD03MMCData>(&timestamp);
Serial.printf("Device with address %s seen at epoch %u", std::string(pClient->getPeerAddress()).c_str(), timestamp);
Serial.printf("Temperature is %.2f : humidity = %u\n", data.temperature / 100.0, data.humidity);

The same functionality is implemented for getting the manufacturer data or the service data of an advertised device, e.g. for another BLE sensor:

uint8_t firmwareVersion = dev->getManufacturerData<uint8_t>();

The advantages of the template way are:

  • you do not need to implement different functions, like readUInt8(), readUInt16() and readUInt32() as in NimBLERemoteCharacteristic
  • you can also retrieve the value in any other native type you like. If e.g. the peripheral sends a float, float value = pCharacteristic->getValue<float>() is perfectly valid, assuming that the endianness and the float data type used by the peripheral are the same as for the ESP32
  • you can retrieve the value in structs, if the peripheral sends such data
  • only code for the types used is generated

Normally the size of the data returned by the peripheral is checked against the size of the requested data type. This check maybe skipped by passing the optional parameter 'true'. If the returned value is smaller than the size of the requested data type a value of 0 ('zero') or returned (or more accurate: the default constructor for the type requested).

Maybe a bit beyond the scope of an explanation for a PR, but even variant like types and bit fields can be used. Two examples:

Example 1: The MJ-HT-V1 temperature / humidity sensor sends in the service data either a temperature, or a humidity or a battery level, or the combination of temperature and humidity. What service data is sent can be discriminated by checking one of the bytes, byte[11], that holds 0x04, 0x06, 0x0a or 0x0d respectively. The actual data we are interested in starts at byte[14]. The length of the data varies, because battery level only is 1 byte, temperature or humidity is 2 byes and both temperature and humidity is 4 bytes. Now you can do this, using standard cpp to parse the data. Mind the 'true' passed into getServiceData(true), so the length of the data is not checked. This is because the sizeof a union is the largest size of each member, but the data sent maybe shorter.

struct ServiceData {
  uint8_t ignore[11];
  uint8_t variantType;
  uint16_t unknown;
  union {
    uint16_t temperature;
    uint16_t humidity;
    uint8_t battery;
    struct {
      uint16_t temperature;
      uint16_t humidity;
    } both;
  } variants;
};

ServiceData serviceData = dev->getServiceData<ServiceData>(true);

if(serviceData.variantType == 0x0d) {
  Serial.printf("MJ-HT-V1 temperature is %.01f, humidity %.01f\n", serviceData.variants.both.temperature / 10.0, serviceData.variants.both.humidity / 10.0);
} else if(serviceData.variantType == 0x0a) {
  Serial.printf("MJ-HT-V1 battery is %u\n", serviceData.variants.battery);
} else if(serviceData.variantType == 0x04) {
  Serial.printf("MJ-HT-V1 temperature is %.01f\n", serviceData.variants.temperature / 10.0);
} else if(serviceData.variantType == 0x06) {
  Serial.printf("MJ-HT-V1 humidity is %.01f\n", serviceData.variants.humidity / 10.0); 
} else {
  Serial.printf("MJ-HT-V1 unknown type %u\n", serviceData.variantType);
}
Serial.println();

Example 2: I have a BLE sensor that sends data through the manufacturer data of an advertised device. Some data is just one bit, some data is 3 bytes, some data is 5 bits long. Now you can do this:

struct ManufacturerDataDetails {
  uint8_t   firmwareVersion;
  uint32_t  deviceID: 24;

  uint8_t   :0; // Align to type ID
  uint8_t   deviceType: 6;
  uint8_t   sendStatusReport: 1;
  uint8_t   biDirectionalDevice: 1;

  uint8_t   :0; // Align to event data
  uint8_t   tamper: 1;
  uint8_t   alarm: 1;
  uint8_t   lowVoltage: 1;
  uint8_t   heartBeat: 1;

  uint8_t   :0; // Align to control data
  uint8_t   frameID: 5;
};

ManufacturerDataDetails manufacturerDataDetails = dev->getManufacturerData<ManufacturerDataDetails>();
Serial.printf("Firmware version 0x%02x\n", manufacturerDataDetails.firmwareVersion);
Serial.printf("Device ID: 0x%06x\n", manufacturerDataDetails.deviceID);
Serial.printf("Device type 0x%02x\n", manufacturerDataDetails.deviceType);
Serial.printf("Status report sent? %s\n", manufacturerDataDetails.sendStatusReport ? "true" : "false");
Serial.printf("Bi directional device? %s\n", manufacturerDataDetails.biDirectionalDevice ? "true" : "false");
if(manufacturerDataDetails.tamper) Serial.println("Tampered");
if(manufacturerDataDetails.alarm) Serial.println("Alarm");
if(manufacturerDataDetails.lowVoltage) Serial.println("Low battery");
if(manufacturerDataDetails.heartBeat) Serial.println("Heartbeat");
Serial.printf("Frame ID is %u\n", manufacturerDataDetails.frameID); 

As said, this is more (standard) cpp functionality, not really an accomplishment of the templates added, but I am quite happy with it, and I want to share this possibility.

Jeroen88 added 10 commits May 14, 2020 10:27
… to get the latest remote characteristic and (optionally) it's timestamp. Getting the value and the timestamp is made unary using the semaphore to assure value and timestamp belong together. A new NimBLERemoteCharacteristic::registerForNotify() is introduced, passing only the remote characteristic and it's type (notify / indicate), since the value can now be retrieved from the remote characteristic itself. NiBLEClient::handleGapEvent() is adapted to set m_value and the new m_timestamp in the remote characteristic. Also the call to the new callback is added
…eded for returning the service data to be copied correctly. Added a timestamp for the moment a device was scanned
@Jeroen88
Copy link
Author

@h2zero what's your opinion about this one? I like it very much myself, because it introduces some kind of variant type for the remote data making it easy for the programmer to handle remote data with cpp data types and structs.

@h2zero
Copy link
Owner

h2zero commented May 20, 2020

I like this, nice improvement to simplify application code. Will look into it more when the other PR's are sorted.

@Jeroen88
Copy link
Author

Added the timestamp option to readValue() too. Added the template cast to readValue() too.

Set readUInt8(), readUInt16() and readUInt32() deprecated, in a separate commit, in case you do not want this.

@Jeroen88
Copy link
Author

@h2zero Updating to latest master

@h2zero h2zero merged commit c2ae3cd into h2zero:master May 30, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants