STMicroelectronics / BlueSTSDK_iOS

Bluetooth low energy Sensors Technology Software Development Kit (iOSversion)
BSD 3-Clause "New" or "Revised" License
35 stars 21 forks source link

Adding new Feature to SensorTile #3

Open skaterSteve opened 6 years ago

skaterSteve commented 6 years ago

Hello,

I am trying to add new features so that I can use in the BlueSTSDK iOS on the SensorTile. I am using ALLMEMS1_ST firmware.

I have defined and added a new Service and corresponding Characteristics. I took care to follow the patterns in the ALLMEMS1_ST uuid definitions. I didn't choose 0x10 as uuid[15] as in the example because I noticed COPY_MIC_ANGLE_UUID ( is 100000000-0000-11e1-ac36-0002a5d5c51b) in case that mattered.

I have defined as such:

The API is able to discover the service! I am trying to follow "How to add a new Feature" section in the Readme. Would you please clarify my confusions on step 1 and 2?

  1. Steps i and ii are hard for me to understand and implement correctly. An explicit example as done in Step 2 would be very helpful for how to define MyNewFeature.
  2. I don't see an "addFeatureForBoard" method in the BlueSTSDK or in the documentation. The closest I see is addFeatureForNode - I tried this method following the example in this step, but it isn't working. It's likely that MyNewFeature is declared incorrectly? Or how do I properly use addFeatureForNode? Perhaps after a node is selected?

Thank you!

GiovanniVisentiniST commented 6 years ago

Hi Stave,

When you create a custom feature you have to:

  1. extend the class BlueSTSDKFeature and implement the abstract methods
  2. register your feature inside the Manager/Node to tell to the sdk what it has to do when it found that UUID

Create the Feature

To crate a valid extension of the class BlueSTSDKFeature you have to:

  1. create a constructor that has a node as a single parameter. the class BlueSTSDKFeature has a constructor that accept a node and a name. so you have to write something like:
    -(instancetype) initWhitNode:(BlueSTSDKNode *)node{
    self = [super initWhitNode:node name:FEATURE_NAME];
    return self;
    }

    or in swift

public override init(whitNode node: BlueSTSDKNode) {
    super.init(whitNode: node, name: MyFeature.FEATURE_NAME)
}
  1. Implement the getFieldDesc method: this method is not used inside the SDK, is present because in our idea a Feature should be self describing, so if you have a generic Feature you can query it to know witch fields it is exporting. and implementation can be:
    -(NSArray<BlueSTSDKFeatureField*>*) getFieldsDesc{
    return @[[BlueSTSDKFeatureField createWithName:FEATURE_DATA_NAME
                                                        unit:FEATURE_UNIT
                                                        type:FEATURE_TYPE
                                                         min:@FEATURE_MIN
                                                         max:@FEATURE_MAX]];
    }

or in Swift:

    public override func getFieldsDesc() -> [BlueSTSDKFeatureField] {
        return [BlueSTSDKFeatureField(name: MyFeature.DATA_NAME,
                                         unit: MyFeature.DATA_MIN,
                                         type: .uInt8,
                                         min: NSNumber(value: MyFeature.DATA_MIN),
                                         max: NSNumber(value: MyFeature.DATA_MAX))]
    }
  1. Implement the extractData method: this method is called each time the SDK receives a notification (or a read) from your feature and its responsibility is to parse the bytes and extract the actual values that the node sent, and pack it into a BlueSTSDKSample object that will be notified to the user thought the BlueSTSDKFeatureDeletage. in theory a single characteristics can export multiple Feature, so this method has an offset parameter that tell you where the unparsed data starts. a simple implementation can be:

    -(BlueSTSDKExtractResult*) extractData:(uint64_t)timestamp data:(NSData*)rawData dataOffset:(uint32_t)offset{
    if(rawData.length-offset < 2){
        @throw [NSException
                exceptionWithName:BLUESTSDK_LOCALIZE(@"Invalid data",nil)
                reason:BLUESTSDK_LOCALIZE(@"The feature need almost 2 byte for extract the data",nil)
                userInfo:nil];
    }//if
    
    float angle = [rawData extractLeUInt16FromOffset:offset]/100.0f;
    
    NSArray *data = @[@(angle)];
    BlueSTSDKFeatureSample *sample = [BlueSTSDKFeatureSample sampleWithTimestamp:timestamp data:data ];
    return [BlueSTSDKExtractResult resutlWithSample:sample nReadData:2];
    }

    or

    public override func extractData(_ timestamp: UInt64, data: Data, dataOffset offset: UInt32) -> BlueSTSDKExtractResult {
        if ((data.count - Int(offset)) < 2) {
            NSException(name: NSExceptionName(rawValue: "Invalid Data"),
                        reason: "No Bytes",
                        userInfo: nil).raise()
            return BlueSTSDKExtractResult(whitSample: nil, nReadData: 0)
        }
        let RSSIValue = NSNumber(value: (data as NSData).extractLeUInt16(fromOffset: UInt(offset)/100.0))
        let sample = BlueSTSDKFeatureSample(timestamp: timestamp, data: [RSSIValue])
        return BlueSTSDKExtractResult(whitSample: sample, nReadData: 2)
    }

note: the first 2 bytes of the notification will be used to extract a timestamp.. this field should be an increasing number that can be use to know if a sample is more recent than another or when the sample was acquired, if you don't care you can extend BlueSTSDKDeviceTimestampFeature instead of BlueSTSDKFeature, in this case the timestamp value will be the current time and you can parse also the first 2 bytes of the notification (offset will start from 0 instead of from 2)

  1. Optionally you can create some static methods that help the user to get the needed value from a sample, without knowing how the sample is organised.

for example:

+ (int32_t)getHeartRate:(BlueSTSDKFeatureSample *)sample {
    if(sample.data.count>=HEART_RATE_INDEX)
        return [sample.data[HEART_RATE_INDEX] intValue];
    return -1;
}

Register the feature

now that you have a your custom Feature you have to register it. there are 2 way of doing that. If you are using an "ST" characteristics, so something like: XXXXX-0001-11e1-ac36-0002a5d5c51b you have to use the addFeatureForNode before start the discovery like:

let charMap = [
            0x00080000 : MyFeature1.self,
            0x00040000 : MyFeature2.self,
            0x00008000 : MyFeature3..self
        ]
        let nodeId = 0x02
        BlueSTSDKManager.sharedInstance().addFeature(forNode: nodeId, features: charMap)

note that each key associated to a feature must have only 1 bit to 1..

By the way this is not your case and I'm thinking to deprecate this way in the next update..

In you case you have to register directly the characteristics/feature pair to the node before connecting, using the method:

(NSDictionary<CBUUID *, NSArray<Class> * > *)getManagedCharacteristics {
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    CBUUID *uuid = [CBUUID UUIDWithString:@"0a000000-000D-11e1-ac36-0002a5d5c51b"];
    [dict add:uuid feature:[MyFeature1 class]];
 // add all your Features
    return dict;
}

....
    [node addExternalCharacteristics:[ self getManagedCharacteristics]];
...

or in Swift:

public func getManagedCharacteristics() -> [CBUUID:[AnyClass]]{
        var temp:[CBUUID:[AnyClass]]=[:]
        temp.updateValue([MyFeature1.self],
                         forKey: CBUUID(string: "0a000000-000D-11e1-ac36-0002a5d5c51b"))
 // add all your Features
        return temp;
    }
...

node.addExternalCharacteristics(getManagedCharacteristics());
...

I hope that this is more clear.

Best Regards Giovanni

skaterSteve commented 6 years ago

Hi Giovanni,

Thank you for responding so quickly and the the detailed description.

I am confused by Step 1 - my problem is importing initWhitNode to create the constructor properly. I have a single view application right now for simplicity.

When I place the function from Step 1, my error is:

No visible @interface for 'UIViewController' declares the selector 'initWhitNode:name:'

I am placing this at:

`#import "BlueSTSDKFeature.h" ...

@interface ViewController() <BlueSTSDKFeatureDelegate,......> @end

@implementation ViewController { BlueSTSDKManager mManager; NSArray mNodes; ..... } .....

-(void)viewDidLoad { .....} . .

Xcode doesn't seem to recognize that method in this context. I tried adding to declarations to headers and @interface and within @implementation... but that didn't make error go away.

I did notice that in viewDidLoad if I write: mNodes[0] initWhitNode: (.....)
This method appears! But not the one with name as a function.
I am not very familiar with how to do add a method to a class, so please excuse me if this is very obvious.

Best Regards, Steve

GiovanniVisentiniST commented 6 years ago

Hi Steve,

You don't have to create a Feature instance, the sdk will allocate it for you, when you connect to the node. You have to create a new class different from your view controller, better if in its .h/.m file. Take a look to a simple one like the Compass to create your one: header, source

Best Regards Giovanni

skaterSteve commented 6 years ago

Hi Giovanni,

I apologize for such a delayed response... Thank you for pointing me to the Compass as reference, this makes sense to me now!