locked
Bluetooth LE Samples : where are the apps + issue with TI CC2540 sample

    Question

  • Bluetooth LE Samples : where are the apps + issue with TI CC2540 sample

    Hi gurus!

       First question : I'm trying to play with Nordic and TI devices that were presented in http://channel9.msdn.com/Events/BUILD/BUILD2011/HW-254T, but cannot find the metro apps nor the WPD drivers : where can I find them? It would be of great help :)

       Currently I'm Learning UMDF->WPD->BTLE stack and am trying to play with TI keyfob (I got mine upgraded to firmware v1.1 today), but am facing trouble with a test metro app I made in javascript.

       First remark : I compiled the WPD driver from <DDK folder>src\bluetooth\WPD\GATT folder, but it seems a bit different from the code I see in the presentation HW-254T (the service event name and parameters are different).. Given that, I can still replace the umpass driver by this one on the interface which holds service ffe0 :

    I then call the following code, after having added

    DeviceCapability Name="0000ffe0-0000-1000-8000-00805f9b34fb" /> in package.appxmanifest, I call the following code :


    var serviceSelector = Windows.Devices.Portable.ServiceDevice.getDeviceSelectorFromServiceId("0000ffe0-0000-1000-8000-00805f9b34fb");

    Windows.Devices.Enumeration.DeviceInformation.findAllAsync(serviceSelector, null).

    then(function(devices)) {

      var deviceFactory = new ActiveXObject("PortableDeviceAutomation.Factory");

      var device = deviceFactory.getDeviceFromId(devices[0]).id;

      var services = device.Services;

      for (var i=0; i<services.Count; i++) {

        try {

          var aService = services[i];

        } catch(err) {

    }I fall into exception Handler with

    "Object doesn't support this action" message.. whereas services.Count is equal to 1.


    Can you help me please (sorry for the color.. it's late over this part of the world :)


    Julien

    Tuesday, October 4, 2011 9:48 PM

Answers

  • Hi Julien,

    Thanks for the post.  Can you make this change to your driver and let me know if this addresses the exception?

    Add the following block to WpdBaseDriver.cpp, in the WpdBaseDriver::DispatchWpdMessage() function:

            else if (CommandKey.fmtid == WPD_CATEGORY_SERVICE_COMMON        || 
                     CommandKey.fmtid == WPD_CATEGORY_SERVICE_METHODS       ||
                     CommandKey.fmtid == WPD_CATEGORY_SERVICE_CAPABILITIES)
            {
                hr = m_Service.DispatchWpdMessage(CommandKey, pParams, pResults);
            }
            // <--- Begin Insert Code
            else if (CommandKey.fmtid == WPD_CATEGORY_OBJECT_RESOURCES)
            {
                if (IsEqualPropertyKey(CommandKey, WPD_COMMAND_OBJECT_RESOURCES_GET_SUPPORTED))
                {
                    // Report empty list (no resources supported)
                    CComPtr<IPortableDeviceKeyCollection> pResources;
    
                    hr = CoCreateInstance(CLSID_PortableDeviceKeyCollection,
                                            NULL,
                                            CLSCTX_INPROC_SERVER,
                                            IID_IPortableDeviceKeyCollection,
                                            (VOID**) &pResources);
                    CHECK_HR(hr, "Failed to CoCreate CLSID_PortableDeviceKeyCollection");
                    if (hr == S_OK)
                    {
                        hr = pResults->SetIUnknownValue(WPD_PROPERTY_OBJECT_RESOURCES_RESOURCE_KEYS, pResources);
                        CHECK_HR(hr, "Failed to set WPD_PROPERTY_OBJECT_RESOURCES_RESOURCE_KEYS");
                    }
                }
            }
            // <--- End Insert Code
            else
            {
                hr = E_NOTIMPL;
                CHECK_HR(hr, "Unknown command %ws.%d received",CComBSTR(CommandKey.fmtid), CommandKey.pid);
            }
    

    lisa

     



    Thursday, October 6, 2011 2:05 AM

All replies

  • Hi Julien,

    Let me investigate and I'll follow up with you shortly.

    thanks

    Frank


    Thursday, October 6, 2011 12:16 AM
    Moderator
  • Hi Julien,

    Thanks for the post.  Can you make this change to your driver and let me know if this addresses the exception?

    Add the following block to WpdBaseDriver.cpp, in the WpdBaseDriver::DispatchWpdMessage() function:

            else if (CommandKey.fmtid == WPD_CATEGORY_SERVICE_COMMON        || 
                     CommandKey.fmtid == WPD_CATEGORY_SERVICE_METHODS       ||
                     CommandKey.fmtid == WPD_CATEGORY_SERVICE_CAPABILITIES)
            {
                hr = m_Service.DispatchWpdMessage(CommandKey, pParams, pResults);
            }
            // <--- Begin Insert Code
            else if (CommandKey.fmtid == WPD_CATEGORY_OBJECT_RESOURCES)
            {
                if (IsEqualPropertyKey(CommandKey, WPD_COMMAND_OBJECT_RESOURCES_GET_SUPPORTED))
                {
                    // Report empty list (no resources supported)
                    CComPtr<IPortableDeviceKeyCollection> pResources;
    
                    hr = CoCreateInstance(CLSID_PortableDeviceKeyCollection,
                                            NULL,
                                            CLSCTX_INPROC_SERVER,
                                            IID_IPortableDeviceKeyCollection,
                                            (VOID**) &pResources);
                    CHECK_HR(hr, "Failed to CoCreate CLSID_PortableDeviceKeyCollection");
                    if (hr == S_OK)
                    {
                        hr = pResults->SetIUnknownValue(WPD_PROPERTY_OBJECT_RESOURCES_RESOURCE_KEYS, pResources);
                        CHECK_HR(hr, "Failed to set WPD_PROPERTY_OBJECT_RESOURCES_RESOURCE_KEYS");
                    }
                }
            }
            // <--- End Insert Code
            else
            {
                hr = E_NOTIMPL;
                CHECK_HR(hr, "Unknown command %ws.%d received",CComBSTR(CommandKey.fmtid), CommandKey.pid);
            }
    

    lisa

     



    Thursday, October 6, 2011 2:05 AM
  • Thanks very much Lisa,


      your proposal does indeed fix the issue! Appreciate your help!!


    I still have 2 requests.. if possible for you to help me on those topics too :


    - do you know where I can find the samples demonstrated in the video. I guess I need 1 driver for the health thermometer and another one for the heart-rate monitor belt. I then need the drivers and the metro apps (if possible of course!). It would be also great if you could find the driver used in the demo by Alain Michaud so that I can see how we define a custom WPD event.


    - I tried accessing above driver from a C++ metro app (which I agree is much more work to do than from JS..  just for training purpose!), but I face another issue :

    Indeed I want to read "Key Press State" property on the service hosted on the keyfob driver, so I try to open the service.. but it (still) fails, even after applying your changes.

     

    Thanks in advance,

    Julien

    Friday, October 7, 2011 6:17 PM
  • Hi Julien,

    Thanks for the questions.

    Re: samples

    We haven't forgotten about your original question and this one about pointers to samples :).  Alain's team is working on compiling a list of samples, and are planning to respond to this thread when they have it ready to share.

    In the meantime, a custom event is propagated in the same way as the standard WPD events (e.g. WPD_OBJECT_UPDATED).  You can find a generic example of propagating a custom service event in another WDK driver sample, the WpdServicesSampleDriver in FakeContactsService.cpp.

    http://msdn.microsoft.com/en-us/library/windows/hardware/ff597714(v=VS.85).aspx

    Re: E_INVALIDARG error when calling IPortableDeviceService::Open 

    I'll look into this and get back to you by Monday.   In the meantime, you can try using Windows.Devices.Enumeration to get the pnpServiceID instead of going through the CLSID_PortableDeviceServiceManager route.  In the query string, you basically set System.Devices.InterfaceClassGuid to the "{GUID}" for the simple keys service, or use the Windows.Devices.Portable.ServiceDevice helpers to compose the query string.  

    http://code.msdn.microsoft.com/windowsapps/Device-Enumeration-Sample-a6e45169/sourcecode?fileId=44727&pathId=1462329729

    lisa


    Saturday, October 8, 2011 6:18 AM
  •  

    Hi Lisa,

     

      thanks so much for your time and kindness!

    • Allright for the samples :) I'll be patient. 
    • Regarding custom event, I already had a look at the sample you've quoted (in DDK\src\wpd I guess) and will try to mimic the code used by Alain during his presentation : my question was if there was a particular value for the names/guids in red below (but I guess not, since it is custom- anyway I'll try!) :

    I alsp don't see where the following (in red) elements are defined in BTLE headers:

    • For the C++ client implementation, I'll try your proposal on monday and will come back to you.

    Have a great WE,

    Julien




    • Edited by jracle Sunday, October 9, 2011 8:46 PM
    Saturday, October 8, 2011 8:56 AM
  • Hi Julien,

    While we wait for the samples, here are the steps in the driver to add a custom event in case you want to try them out.

    1. Define the event GUID, name, and parameters

     

    // key pressed event
    // This event contains an integer parameter containing the value of the key press
    // {E1E085FF-97A7-4A4C-839C-7609BF0D873C}
    DEFINE_DEVSVCGUID(EVENT_TICC2540SimpleKeysService_KeyPressed, 0xE1E085FF, 0x97A7, 0x4A4C, 0x83, 0x9C, 0x76, 0x09, 0xBF, 0x0D, 0x87, 0x3C);
    #define NAME_EVENT_TICC2540SimpleKeysService_KeyPressed L"KeyPressed"
    
    // key pressed value event parameter
    // {E1E085FF-97A7-4A4C-839C-7609BF0D873C}.2
    DEFINE_DEVSVCPROPKEY(EVENT_PARAMETER_TICC2540SimpleKeysService_KeyPressValue, 0xE1E085FF, 0x97A7, 0x4A4C, 0x83, 0x9C, 0x76, 0x09, 0xBF, 0x0D, 0x87, 0x3C, 2);
    
    
    

     

    2. Declare the event by adding it to the supported events array

     

    // This is new
    typedef struct tagEventAttributeInfo
    {
        const GUID*                       pEventGuid;
        PCWSTR                            wszName;
    } EventAttributeInfo;
    
    // This should already be there
    typedef struct tagEventParameterAttributeInfo
    {
        const GUID*                       pEventGuid;
        const PROPERTYKEY*                pParameter;
        VARTYPE                           Vartype;
    } EventParameterAttributeInfo;
    
    
    // The declarations
    // Supported events
    EventAttributeInfo g_SupportedServiceEvents[] =
    {
        {&EVENT_TICC2540SimpleKeysService_KeyPressed, NAME_EVENT_TICC2540SimpleKeysService_KeyPressed},
    };
    
    // Event parameters
    EventParameterAttributeInfo g_ServiceEventParameters[] =
    {
        {&EVENT_TICC2540SimpleKeysService_KeyPressed, &WPD_EVENT_PARAMETER_EVENT_ID,                              VT_CLSID},
        {&EVENT_TICC2540SimpleKeysService_KeyPressed, &EVENT_PARAMETER_TICC2540SimpleKeysService_KeyPressValue,   VT_UI4},
        {&EVENT_TICC2540SimpleKeysService_KeyPressed, &WPD_EVENT_PARAMETER_OBJECT_PARENT_PERSISTENT_UNIQUE_ID,    VT_LPWSTR},
        {&EVENT_TICC2540SimpleKeysService_KeyPressed, &WPD_OBJECT_CONTAINER_FUNCTIONAL_OBJECT_ID,                 VT_LPWSTR},
    };
    
    
    


    3.  Report the event name so that it can be accessed from JavaScript. So "KeyPressed" will be accessible as service.onKeyPressed = function (parameter1, parameter2, ...) { ... };
    In case it wasn't obvious, the name should not contain any punctuation or spaces since JavaScript cannot handle these as variable names.

     

     

    HRESULT TICC2540SimpleKeysService::GetEventAttributes(
                REFGUID                Event,
        __out   IPortableDeviceValues* pAttributes)
    {
    
        ...
    
        // Loop through the supported events for this service to find a match
        hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
        for (DWORD i=0; i<ARRAYSIZE(g_SupportedServiceEvents); i++)
        {
            if (Event == *g_SupportedServiceEvents[i])
            if (Event == *g_SupportedServiceEvents[i].pEventGuid)
            {
                // Set the event options.
                hr = pAttributes->SetIPortableDeviceValuesValue(WPD_EVENT_ATTRIBUTE_OPTIONS, pEventOptions);
                CHECK_HR(hr, "Failed to set WPD_EVENT_ATTRIBUTE_OPTIONS");
    
    	    // ----> Begin Insert Code
                // Set the event name.
                if (hr == S_OK)
                {
                    hr = pAttributes->SetStringValue(WPD_EVENT_ATTRIBUTE_NAME, g_SupportedServiceEvents[i].wszName);
                    CHECK_HR(hr, "Failed to set WPD_EVENT_ATTRIBUTE_NAME");
                }
                // ----> End Insert Code
    
    

     

    4. Update the remaining code that was expecting a GUID*, which now is changed to the EventAttributes struct. It should be fairly obvious once you see the compile errors, if not let me know.

    For example:

        for (DWORD i=0; i<ARRAYSIZE(g_SupportedServiceEvents); i++)
        {
            // Old
            // pv.puuid = (CLSID*)g_SupportedServiceEvents[i];  // Assignment, don't PropVariantClear this
    
            // New
            pv.puuid = (CLSID*)g_SupportedServiceEvents[i].pEventGuid;  // Assignment, don't PropVariantClear this
    
            hr = pEvents->Add(&pv);
            CHECK_HR(hr, "Failed to add event to the collection");
        }
    
    


    5. Finally, report the event whenever something interesting happens.  This requires minor modifications to the ValueChangeEvent function:

    VOID
    TICC2540SimpleKeysServiceContent::ValueChangeEvent(
        __in USHORT ChangedAttributeHandle,
        __in size_t CharacteristicValueDataSize,
        __in_bcount(CharacteristicValueDataSize)
                PBTH_LE_GATT_CHARACTERISTIC_VALUE CharacteristicValue
        )
    {
        ....
    
        if (SUCCEEDED(hr))
        {   
            // hr = pEventParams->SetGuidValue(WPD_EVENT_PARAMETER_EVENT_ID, WPD_EVENT_OBJECT_UPDATED);  // Old
            hr = pEventParams->SetGuidValue(WPD_EVENT_PARAMETER_EVENT_ID, EVENT_TICC2540SimpleKeysService_KeyPressed); // New
            CHECK_HR(hr, "Failed to add WPD_EVENT_PARAMETER_EVENT_ID");
        }
        
        if (SUCCEEDED(hr))
        {
            // hr = pEventParams->SetStringValue(WPD_OBJECT_PERSISTENT_UNIQUE_ID, PersistentUniqueID); // Old
            // CHECK_HR(hr, "Failed to add WPD_OBJECT_PERSISTENT_UNIQUE_ID");
            hr = pEventParams->SetUnsignedIntegerValue(EVENT_PARAMETER_TICC2540SimpleKeysService_KeyPressValue, m_Value); // New
            CHECK_HR(hr, "Failed to add EVENT_PARAMETER_TICC2540SimpleKeysService_KeyPressValue");
        }
    
        //
        // Adding this event parameter will allow WPD to scope this event to the container functional object    
        //
        if (SUCCEEDED(hr))
        {
            hr = pEventParams->SetStringValue(WPD_EVENT_PARAMETER_OBJECT_PARENT_PERSISTENT_UNIQUE_ID, ParentPersistentUniqueID);
            CHECK_HR(hr, "Failed to add WPD_EVENT_PARAMETER_OBJECT_PARENT_PERSISTENT_UNIQUE_ID");
        }
    
        //
        // Adding this event parameter will allow WPD to scope this event to the container functional object
        //
        if (SUCCEEDED(hr))
        {
            // hr = pEventParams->SetStringValue(WPD_OBJECT_CONTAINER_FUNCTIONAL_OBJECT_ID, ContainerFunctionalObjectID); // Old - typo
            hr = pEventParams->SetStringValue(WPD_OBJECT_CONTAINER_FUNCTIONAL_OBJECT_ID, SERVICE_OBJECT_ID);  // Correction
            CHECK_HR(hr, "Failed to add WPD_OBJECT_CONTAINER_FUNCTIONAL_OBJECT_ID");
        }
        
        ...
    }
    
    


    Hope this covered all the steps.  If something is missing, let me know.  I'll post separately on the PortableDeviceService C++ app code issue.

    lisa

     

     


    Monday, October 10, 2011 6:00 PM
  • As for the C++ issue, E_INVALIDARG is generally returned if the pnpServiceId used is not pointing to a valid device service instance path.
    Here's a quicker way to access the Bluetooth LE device service in C++.
    Adjust the AQS filter so that it finds instances of the device service you're using:
        // Query for all device service instances using Windows.Devices.Enumeration
        String^ aqsFilter = ServiceDevice::GetDeviceSelectorFromServiceId("0000ffe0-0000-1000-8000-00805f9b34fb");
        auto findAllAsyncOp = DeviceInformation::FindAllAsync(aqsFilter);
        findAllAsyncOp->Completed = ref new AsyncOperationCompletedHandler<DeviceInformationCollection^>(
            [=](IAsyncOperation<DeviceInformationCollection^>^ operation)
        ...
    
    

    Then, you can CoCreate IPortableDeviceService and open the service directly using deviceInfo. The PnP ID will point to the device service.
        ComPtr<IPortableDeviceService> deviceService;
        HRESULT hr;
    
        auto clientInfo = GetClientInfo();
        hr = CoCreateInstance(CLSID_PortableDeviceServiceFTM, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceService));
        if (FAILED(hr)) throw ref new Platform::COMException(hr);
    
        hr = deviceService->Open(deviceInfo->Id->Begin(), clientInfo.Get());
        if (FAILED(hr)) throw ref new Platform::COMException(hr);
    
    
    lisa

    Monday, October 10, 2011 6:29 PM
  • Hi Lisa,

     

      thanks so much for your efforts! I came to almost the same changes yesterday and got both issues fixed.

    Waiting eagerly the code samples, I'm implementing test-drivers for other services (like temperature measurement).

     

    Thanks again!

    Julien

    Tuesday, October 11, 2011 4:51 PM
  • Hi Lisa,

    I am attempting to do much the same as above so I am reviving this thread if I may. My problem is that I run into compiler issues when I attempt to do so. i am using the new (8175.5) Dev preview build and associated VS11 and here are the things I am seeing:

    1) DeviceInformationFindAllAsyncOperation does not seem to exist so the line:

    auto findAllAsyncOp = DeviceInformation::FindAllAsync(aqsFilter);

    ...returns only an IAsyncOperation interface. This in itself is not bad but it seems to create a couple of problems:

    2) When I try to create the Async operation handler:

    findAllAsyncOp->Completed = ref new AsyncOperationCompletedHandler<DeviceInformationCollection^>(
            [this](IAsyncOperation<DeviceInformationCollection^>^ operation) {...});

    ... I get a the compile error:

    1>MainPage.xaml.cpp(75): error C2064: term does not evaluate to a function taking 2 arguments

    1> class does not define an 'operator()' or a user defined conversion operator to a pointer-to-function or reference-to-function that takes appropriate number of arguments

    3) Finally I am noticing that the IAsyncInfo interface does not appear to support a "Start" method so I am at a loss as to figuring out how to actually begin the operation

    Sorry if I am missing something obvious here.

    Thanks!

    DJ

     

    Friday, January 20, 2012 5:01 PM
  • Hi DJ,

    This forum is only for discussion of the publically available Developer Preview. If you have a newer build under NDA then please work with the channel you got it from for support.

    --Rob

    Friday, January 20, 2012 6:49 PM
    Owner
  • I tried the same thing. But after installation of the driver, I got an error code 10, which is device can't be started. Any idea?

    Yves Liu


    • Edited by Yves Liu Wednesday, May 30, 2012 5:55 AM
    Wednesday, May 30, 2012 5:55 AM
  • Hi,

    is the sample code available now in 2014?

    Thursday, February 20, 2014 9:58 AM
  • Starting with Windows 8.1, you no longer need to install the driver and use the IPortableDevice C++/COM APIs. 

    You can use the new Bluetooth GATT WinRT APIs to communicate to the device.

    Documentation:

    http://msdn.microsoft.com/en-us/library/windows/apps/dn264587.aspx

    Sample:

    http://code.msdn.microsoft.com/windowsapps/Bluetooth-Generic-5a99ef95

    Thursday, February 20, 2014 7:11 PM