Microsoft Band SDK use with Windows Runtime Compon

2019-05-31 09:01发布

问题:

Is it possible to use the Microsoft Band SDK from within a Windows Runtime Component? GetBandsAsync works, but it fails when connecting using:

IBandClient bandClient = await BandClientManager.Instance.ConnectAsync(pairedBands[0])

I am using Microsoft Band SDK 1.3.10518. I have added rfcomm capabilities to the app package manifest and I get a the following exception:

System.IO.FileNotFoundException: Could not load file or assembly 'System.Runtime.InteropServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.
File name: 'System.Runtime.InteropServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
   at Microsoft.Band.MarshalUtils.GetBytes[T](T& structure, Byte[] resultArray, Int32 offset)
   at Microsoft.Band.BandClient.CheckFirmwareSdkBit(FirmwareSdkCheckPlatform platform, Byte reserved)
   at Microsoft.Band.BandClientManager.<ConnectAsync>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at BandController.Band.<StartRemoteControlAsync>d__5.MoveNext()} System.Exception {System.IO.FileNotFoundException}

To reproduce the error, this is what I have done:

  1. In Visual Studio create a new Blank App (Windows Phone Silverlight)
  2. Add a button to the app (let's say "Test band") and add a handler for Tap event
  3. Add a Windows Runtime Component (Windows Phone) to the solution, call it BandTest, the namespace BandWrapper
  4. Add Microsoft Band SDK to the BandWrapper project
  5. Add rfcomm and proximity capabilities in the Package.appxmanifext of the Silverlight project (for the BandWrapper rfcomm is added by Microsoft Band SDK installer)
  6. Implement the test button handler as follows:

    private void Button_Tap(object sender, System.Windows.Input.GestureEventArgs e) { BandWrapper.BandTest wrapper = new BandWrapper.BandTest(); wrapper.TestBand(); }

  7. And in the runtime component copy from the SDK sample the following, in the public async void TestBand() method:

            try
            {
                // Get the list of Microsoft Bands paired to the phone/tablet/PC.
                IBandInfo[] pairedBands = await BandClientManager.Instance.GetBandsAsync();
                if (pairedBands.Length < 1)
                {
                    //this.viewModel.StatusMessage = "This sample app requires a Microsoft Band paired to your device. Also make sure that you have the latest firmware installed on your Band, as provided by the latest Microsoft Health app.";
                    return;
                }
    
                // Connect to Microsoft Band.
                using (IBandClient bandClient = await BandClientManager.Instance.ConnectAsync(pairedBands[0]))
                {
                    // We'll create a Tile that looks like this:
                    // +--------------------+
                    // | MY CARD            | 
                    // | |||||||||||||||||  | 
                    // | 123456789          |
                    // +--------------------+
    
                    // First, we'll prepare the layout for the Tile page described above.
                    TextBlock myCardTextBlock = new TextBlock()
                    {
                        Color = Colors.Blue.ToBandColor(),
                        ElementId = 1, // the Id of the TextBlock element; we'll use it later to set its text to "MY CARD"
                        Rect = new PageRect(0, 0, 200, 25)
                    };
    

    ...

If at the step 1. above, I create an Windows Phone app (not Silverlight) the execution of the test method goes up to

await bandClient.TileManager.AddTileAsync(myTile);

and there it fails with:

System.TypeLoadException: Could not find Windows Runtime type 'Windows.Foundation'.
   at System.StubHelpers.WinRTTypeNameConverter.GetTypeFromWinRTTypeName(String typeName, Boolean& isPrimitive)
   at System.StubHelpers.SystemTypeMarshaler.ConvertToManaged(TypeNameNative* pNativeType, Type& managedType)
   at Windows.UI.Xaml.Controls.Frame.Navigate(Type sourcePageType, Object parameter)
   at Microsoft.Band.StoreApplicationPlatformProvider`2.<>c__DisplayClassd`1.<GetConsentAsync>b__a()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable.ConfiguredTaskAwaiter.GetResult()
   at Microsoft.Band.StoreApplicationPlatformProvider`2.<GetConsentAsync>d__f`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at Microsoft.Band.BandClient.<>c__DisplayClass6f.<<AddTileAsync>b__6c>d__71.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at BandWrapper.BandTest.<TestBand>d__5.MoveNext()}   System.Exception {System.TypeLoadException}

回答1:

Yes, it is possible! You have to make sure, that when you test the connection in the Windows Runtime Component (from a Background Task), you have not previously connected the Band from the running main app. Even when you open a connection from within a Using-Statement and all resources should be released, there still is some port in use with the Band. In that case, connecting the Band from the Windows Runtime Component will fail.



回答2:

Earlier there were bugs in Band SDK, which totally blocked this scenario. With the current version of SDK I succeeded creating a Windows Runtime Component capable of creating a tile and deploying a layout. Unfortunately I was only successful calling it from C# and failed to find a way to provide JavaScript support.

It looks like with the current situation it is not possible. Please look at this post: https://social.msdn.microsoft.com/Forums/en-US/93d04495-171f-411d-bd2c-82f55888827b/windows-runtime-ui-component?forum=winappswithcsharp . The post says that the runtime components using XAML can't be called from JS.

Band SDK has 4 situations when it demands the call arriving from UI thread.

  1. At the very first time the application hosting the SDK is calling into the Bluetooth library, that one talks to OS to request permissions from the user. If that call is not on UI thread the Bluetooth library will throw an exception. Once user agrees then Bluetooth can be called from non-UI thread.

  2. There must be at least one call on UI thread to request consent to obtain user Heart Rate in order to subscribe to Heart Rate data. The subscription itself can be done on any thread. The UI to request the consent is part of SDK.

  3. AddTile always asks permissions from the user. The UI dialog is part of SDK. Calling AddTile from non UI thread will fail.

  4. When adding a tile the code must provide its icon. Current implementation of SDK offers ToBandIcon extension methods for XAML classes like WritableBitmap. Construction of the bitmap will fail with RPC_WRONG_THREAD if the call arrives on non UI thread.

In my mind the need of Windows Runtime Component was justified by the support for JavaScript, C# and C++ together. If you do not need JavaScript then maybe you will succeed with your project.

Here is the piece of code which worked for me. The code works when the component is being called by C# UI thread (from the phone application) and it also works when called by non-UI thread because that magic line of code resolving the dispatcher succeeds in the C# process:

var dispatcher = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher;

That line of code is supposed to resolve the dispatcher for the UI thread. So my experimental code obtains the dispatcher and schedules a call which will talk to the Band on UI thread. The "promise" is returned to the caller with the compatible IAsyncAction. JavaScript would handle it well if entire idea worked.

    private bool inCall;

    public IAsyncAction TestBandAsync()
    {
        var dispatcher = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher;

        var completionSource = new TaskCompletionSource<int>();

        // x is to suppress CS2014
        var x = dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
        {
            try
            {
                if (this.inCall)
                    throw new InvalidOperationException("Currently in call!");

                this.inCall = true;
                try
                {
                    WriteableBitmap textBitmap = new WriteableBitmap(1, 1);

                    // Get the list of Microsoft Bands paired to the phone.
                    IBandInfo[] pairedBands = await BandClientManager.Instance.GetBandsAsync();
                    if (pairedBands.Length < 1)
                        throw new InvalidOperationException("No bands found!");

                    var bitmap = await LoadBitmapAsync("ms-appx:///Assets/Smile.png");
                    var bandIcon = bitmap.ToBandIcon();

                    // Connect to Microsoft Band.
                    using (IBandClient bandClient = await BandClientManager.Instance.ConnectAsync(pairedBands[0]))
                    {
                        // Create a Tile with a TextButton on it.
                        Guid myTileId = new Guid("12408A60-13EB-46C2-9D24-F14BF6A033C6");

                        BandTile myTile = new BandTile(myTileId)
                        {
                            Name = "My Tile",
                            TileIcon = bandIcon,
                            SmallIcon = bandIcon
                        };

                        var designed = new BandTileLayout1();
                        myTile.PageLayouts.Add(designed.Layout);

                        // Remove the Tile from the Band, if present. An application won't need to do this everytime it runs. 
                        // But in case you modify this sample code and run it again, let's make sure to start fresh.
                        await bandClient.TileManager.RemoveTileAsync(myTileId);

                        await designed.LoadIconsAsync(myTile);

                        // Create the Tile on the Band.
                        await bandClient.TileManager.AddTileAsync(myTile);
                        await bandClient.TileManager.SetPagesAsync
                            (myTileId,
                            new PageData(new Guid("5F5FD06E-BD37-4B71-B36C-3ED9D721F200"),
                            0,
                            designed.Data.All));
                    }
                }
                finally
                {
                    this.inCall = false;
                }

                completionSource.SetResult(0);
            }
            catch (Exception ex)
            {
                completionSource.SetException(ex);
            }
        });

        return completionSource.Task.AsAsyncAction();
    }

Regards, Andrew