Custom button press when connected vs disconnected

Hi Mbient Community,

How can you program a Metawear device to simply flash a color (eg green) when the button is pressed WHILE CONNECTED and then switch this button press behavior so that the button press triggers a different color flash (eg red) WHILE in disconnected state? I've tried several approaches that I will include in replies to this post (my current post is over the char limit)

Thanks,

Russell

Comments


  • I've several approaches, two of them are as follows:

    // HYPOTHESIS ONE: running code below while device is connected to iPhone will conditionally set color based on device.state
    // RESULT: always get greenColor when button pressed (when device is connected or disconnected)
    // CONCLUSION: invoking programCommandsToRunOnEventAsync evaluates block immediately, finds MBLConnectionStateConnected so
    // always flashes greenColor instead of redColor even when disconnected 

    [device.mechanicalSwitch.switchUpdateEvent programCommandsToRunOnEventAsync:^{
            
            switch (self.device.state) {
                case MBLConnectionStateConnected:
                {
                    [device.led flashLEDColorAsync:[UIColor greenColor] withIntensity:1.0 numberOfFlashes:4];
                    break;
                }
                default:
                    [device.led flashLEDColorAsync:[UIColor redColor] withIntensity:1.0 numberOfFlashes:4];
                    break;
            }

        }];

    }];


    // HYPOTHESIS TWO: When disconnectEvent notification occurs, mechanicalSwitch.switchUpdateEvent can be programmed
    // RESULT: color never changes
    // CONCLUSION: can’t nest programCommandsToRunOnEventAsync or startNotificationsWithHandlerAsync calls; 
    // btw also tried to nest startNotificationsWithHandlerAsync inside of this disconnectEvent startNotificationsWithHandlerAsync
    // but that also did not work. I even tried using [device.settings.disconnectEvent programCommandsToRunOnEventAsync: in the outer block
    // but that did not work.

    [device.settings.disconnectEvent startNotificationsWithHandlerAsync:^(MBLNumericData *obj, NSError *error) {
    [device.mechanicalSwitch.switchUpdateEvent programCommandsToRunOnEventAsync:^{
    [device.led flashLEDColorAsync:[UIColor redColor] withIntensity:1.0 numberOfFlashes:48];
    }];
    }];

    My next thought is to try to utilize setConfiguration:handler: upon disconnect, then program the device with disconnect state commands (e.g. flash redColor on button press), reset the device; upon reconnect, disable these bootstrap commands (maybe using eraseCommandsToRunOnEvent) and replace with connected state commands (e.g. flash greenColor on button press). Seems elaborate. Any help much appreciated!

    Russell
  • I would first program the switch to only turn on the LED.  Then, use the disconnect event to erase the pattern and set the color to red, and when you connect to the board again, erase the pattern and set the color to green.
  • Hi Eric,

    Thanks. I've been trying for several hours to implement your approach but can't get it working. Perhaps I'm not following your guidance properly. Do you have a code example for clarification? 

    Specifically

    1. How do you "first program the switch to only turn on the LED"? 

    Does that mean using… 

    device.mechanicalSwitch.switchUpdateEvent programCommandsToRunOnEventAsync 

    or 

    device.mechanicalSwitch.switchUpdateEvent startNotificationsWithHandlerAsync

    ?

    and then using setLEDOnAsync:withOptions:

    ?

    If so what are the parameters (e.g. YES and 1 to turn ON) and if you use programCommandsToRunOnEventAsync how do you switch on the button press states within this callback (since the MBLNumericData object doesn't seem visible in the callback)?

    2. How do you "Then use the disconnect to erase the pattern and set the color to red"?

    Does this mean using...

    device.settings.disconnectEven programCommandsToRunOnEventAsync

    or

    device.settings.disconnectEvent startNotificationsWithHandlerAsync

    ?

    The challenge with disconnectEvent is that the device is unreachable (disconnected) when this callback is invoked so I can’t seem to contact the device and dynamically reconfigure it. Also, since programCommandsToRunOnEventAsync runs the supplied block immediately, it seems like I can only invoke it when connected to the device.

    3. "when you connect to the board again, erase the pattern and set the color to green."

    From the connectWithHandler handler, do I invoke 

    device.mechanicalSwitch.switchUpdateEvent eraseCommandsToRunOnEventAsync

    ?

    And do you set the color with setLEDColorAsync

    ?

    Apologies for the barrage of detailed questions but I really want to understand how to do this. Thank you so much.

  • edited April 2016
    For 1) and 2), you want programCommandsToRunOnEventAsync

    For 3), use setLEDColorAsnyc with NO

  • Regarding hypothesis 1, you are correct.  Here is a expert from the documentation of programCommandsToRunOnEventAsync:

     @warning THE BLOCK IS ONLY EXECUTED ONCE DURNING THIS CALL AND

     NEVER AGAIN, DON'T ATTEMPT TO USE CALLBACKS INSIDE THIS BLOCK


    Regarding hypothesis 2, there is an explanation in the documentation for disconnectionEvent: 

     Event representing a BLE disconnection event.  Note this doesn't make sense to

     stream, but it's likely that programCommandsToRunOnEventAsync will have utility.

    Basically what is saying is that once you are disconnected you can no longer communicate with the MetaWear (thus no streaming).  If you would observe the BFTasks returned by startNotificationsWithHandler: you would see an error.


    I like to think about the problem backwards starting with the end:

    • We need 2 separate events, one that triggers a red flash, one that triggers a green flash.  We’ll call them connectedSwitchEvent and disconnectedSwitchEvent.  We can use programCommandsToRunOnEventAsync: for setting up the flash
    • We need to stop one or the other event from firing, so lets pass them through a conditionalDataSwitch
    • We need to change the MBLDataSwitch values on connect and disconnect.
    • Since we are dealing with offline behavior we need to use an MBLRestorable object to access our custom events across connect/disconnect events.


    For more specific details see the code snippets below:


  • #import <MetaWear/MetaWear.h>


    @interface ButtonConfig : NSObject <MBLRestorable>

    @property (nonatomic) MBLDataSwitch *disconnectedSwitchEvent;

    @property (nonatomic) MBLDataSwitch *connectedSwitchEvent;


    - (void)didConnect;

    @end


    @implementation ButtonConfig


    - (void)runOnDeviceBoot:(MBLMetaWear *)device

    {

        // Pass the switch event through a gate that only opens when we are disconnected

        self.disconnectedSwitchEvent = [device.mechanicalSwitch.switchUpdateEvent conditionalDataSwitch:NO];

        // When we get a disconnected switch event then flash red

        [[[[[self.disconnectedSwitchEvent programCommandsToRunOnEventAsync:^{

            [device.led flashLEDColorAsync:[UIColor redColor] withIntensity:1.0 numberOfFlashes:2];

        }] continueOnDispatchWithSuccessBlock:^id _Nullable(BFTask * _Nonnull task) {

            // Pass the switch event through a gate that only opens when we are connected

            self.connectedSwitchEvent = [device.mechanicalSwitch.switchUpdateEvent conditionalDataSwitch:YES];

            // When we get a connected switch event then flash green

            return [self.connectedSwitchEvent programCommandsToRunOnEventAsync:^{

                [device.led flashLEDColorAsync:[UIColor greenColor] withIntensity:1.0 numberOfFlashes:2];

            }];

        }] continueOnDispatchWithSuccessBlock:^id _Nullable(BFTask * _Nonnull task) {

            // When a disconnect event occurs, we open program the gate change

            return [device.settings.disconnectEvent programCommandsToRunOnEventAsync:^{

                [self.disconnectedSwitchEvent resetConditionalAsync:YES];

                [self.connectedSwitchEvent resetConditionalAsync:NO];

            }];

        }] success:^(id  _Nonnull result) {

            NSLog(@Success);

        }] failure:^(NSError * _Nonnull error) {

            NSLog(@Fail: %@", error);

        }];

    }


    // Manually reset the gates when we re-connect

    - (void)didConnect

    {

        [self.disconnectedSwitchEvent resetConditionalAsync:NO];

        [self.connectedSwitchEvent resetConditionalAsync:YES];

    }


    @end


  • // Here is an example unit test

    - (void)testButton

    {

        XCTestExpectation *waitingExpectation = [self expectationWithDescription:@pause for manual verification];


        [self.device setConfiguration:[[ButtonConfig alloc] init] handler:^(NSError * _Nullable error) {

            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

                [self.device disconnectWithHandler:^(NSError * _Nullable error) {

                    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

                        [self.device connectWithHandler:^(NSError * _Nullable error) {

                            ButtonConfig *config = self.device.configuration;

                            [config didConnect];

                            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

                                [self.device setConfiguration:nil handler:^(NSError * _Nullable error) {

                                    [waitingExpectation fulfill];

                                }];

                            });

                        }];

                    });

                }];

            });

        }];


        [self waitForExpectationsWithTimeout:180 handler:nil];

    }

  • Thanks Stephen I will check out your solution!
This discussion has been closed.