10 Tips about bluetooth le development

Currently, I was helping my client to develop an SDK to communicate with Bluetooth le device. I developer three SDKs, one is Xamarin, iOS native with Objective-c and Java SDK for Android. Here are some tips I found when I was developing these SDKs. I hope these will save the time for firmware and mobile developers.

1. Put Mac address into advertising manufacturer specific data.

As there is no way to get Mac address from iOS SDK (until iOS 11). In iOS, we can use CBPeripheral.identifier to tell the difference between devices, but it cannot cross the platforms. If you want to do some web API works and need to identify the device, the Mac address is the best choice. So the simple solution is put the Mac address into advertising.

2. Advertising manufacturer specific data cannot be longer than 26 bytes.

From the document, the advertising packer can be up to 31 bytes. But from our testings some Android devices, like Motorola Moto G4, it cannot handle that length. So 25 is the maximum length of advertising manufacturer specific data.

3. Put some random bytes into advertising data when you want to test background task in iOS.

iOS has strict restrictions on Bluetooth background task. Our use case is simple, like when a user approaching some device, the App can raise a notification. But it cannot test successfully every time. The best solution for this use case is making the device follow iBeacon design, but we don’t want to do that.
Another solution is when App found a device, then keep the connection of that, if the user leaves and back it will auto-connect the device, we don’t want to do this either, cause it will run lots of battery.

Back to the topic, if you want to do long-term iOS Bluetooth background task, you do better follow the “Performing Long-Term Actions in the Background” section in this document.
But why we need to put some random bytes into advertising data, because this API, scanForPeripheralsWithServices:options:, the CBCentralManagerScanOptionAllowDuplicatesKey option will set to NO in the background task. So it means if your advertising data are all the same each time, it won’t invoke centralManager:didDiscoverPeripheral:advertisementData:RSSI:  delegate. But even we do this, the background task still cannot work as we want, I have created a TSI to Apple, but the answer is this is by design to save the battery, and here is the condition can relaunch the App from the Bluetooth background task.

Another thing is, for advertising interval, you can pick one of them can work well both platforms.
● 152.5 ms
● 211.25 ms
● 318.75 ms
● 417.5 ms
● 546.25 ms
● 760 ms
● 852.5 ms
● 1022.5 ms
● 1285 ms
They come from Apple Bluetooth design guidelines.

4. Manufacturer data are different from Android and iOS.

I put analysis manufacturer data into PCL, but I found Android and iOS are different,

Android after Lollipop (API 21), it put manufacturer data into a key-value array, you should combine them to get the manufacturer data with iOS, here is the C# code


 byte[] advBytes = new byte[manufaturerData.ValueAt(0).ToArray<byte>().Length + 2];
 byte[] begin = BitConverter.GetBytes((ushort)manufaturerData.KeyAt(0)).Take(2).ToArray();
 Array.Copy(begin, advBytes, 2);
 Array.Copy(manufaturerData.ValueAt(0).ToArray<byte>(), 0, advBytes, 2, manufaturerData.ValueAt(0).ToArray<byte>().Length);

5. Call different API in Android under Lollipop.

Android support Bluetooth le since Android 4.3 (API 18), so before Lollipop(API 21), you should call StartLeScan instead of StartScan.
Another thing is
boolean startLeScan (UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback)
The serviceUuids filter is not working, so we need to do the filter by ourselves.

Here is the code to get the services Uuid from adv data in C#

6. Android system has the huge problem with connection.

At the beginning of the project, we have 1 service and 1 characteristic, it seems to work fine on both platforms. As we put more features in, we put more characteristics in the single service, the Android SDK cannot connect with devices successfully everytime. It’s not my code’s problem, I used some third-party tools like LightBlue, Ble Scanner, nRF Connect, they do the same thing.
The SDK can connect the devices after I call ConnectGatt API, but when I call DiscoverServices API, it disconnects the devices from low-level code.

I have tried some solutions for this, like

  • RequestConnectionPriority after connect
  • Delay 500ms to call DiscoverServices API

They make connection stable a little bit but still failed many times.

We are trying to reduce the characteristics to see what happens.

Why not use single characteristic? As we have many features in the devices. Multiple characteristics will be easy for testing with the third-party tool. You can name every characteristic. If using single characteristic, it will introduce more logic for both firmware side and mobile SDK side, and even make testing harder.

7. Auto disconnect from device

It’s a good idea to disconnect from App after few minutes, cause it can save the battery. The device can implement this logic, for the App need to handle the disconnect event. But in Android, if you set auto-connect as true in ConnectGatt API.
It will auto-connect the device, even the device drops the connection by itself. So the solution is you can call Disconnect() function in OnConnectionStateChange delegate like this

 gatt.Disconnect();
gatt.Dispose();
tracker.Gatt = null;

8. Notification of characteristics is different between iOS and Android.

In iOS we cannot tell notification and read, cause they are coming from the same delegate.
Enable characteristics notification is sample in iOS, just call SetNotifyValue API is OK.
peripheral.SetNotifyValue(enable, cbc);
But in Android, you need to write the descriptor. Here is the C# code

        bool EnableNotification(LXTracker tracker, BluetoothGattCharacteristic cbc, bool enable)
        {
            if (!cbc.Properties.HasFlag(GattProperty.Notify))
                return true;
            var descriptor = cbc.GetDescriptor(UUID.FromString("00002902-0000-1000-8000-00805f9b34fb"));

            if (enable && descriptor.GetValue() != null &&
                descriptor.GetValue()[0] == BluetoothGattDescriptor.EnableNotificationValue.ToArray()[0])
                return true;
            if (!enable && descriptor.GetValue() != null &&
                descriptor.GetValue()[0] == BluetoothGattDescriptor.DisableNotificationValue.ToArray()[0])
                return true;

            bool res = tracker.Gatt.SetCharacteristicNotification(cbc, enable);
            if (enable)
            {
                res &= descriptor.SetValue(BluetoothGattDescriptor.EnableNotificationValue.ToArray());
            }
            else
            {
                res &= descriptor.SetValue(BluetoothGattDescriptor.DisableNotificationValue.ToArray());
            }

            WriteDescriptEvent = new AutoResetEvent(false);
            res &= tracker.Gatt.WriteDescriptor(descriptor);
            WriteDescriptEvent.WaitOne();
            return res;
        }

9. Read and write operation on Android is not thread-safe.

In Android, you should put single read or write operation into a single thread. For example, you call ReadCharacteristic API, then you get the result from the callback, currently the callback and read API are in the same thread. If you call another read API in this callback, it won’t return success result. From the source code, there is a lock to make sure the front callback finished and then do another read. So you need to create another thread and do another read.

The lock applies to Read, write, read/write Descriptor APIs. Sometimes, even you create a new thread, it still not working, you need to delay few seconds to do another operation.

10. Make sure write with response must have the response.

There are two types write operations, one is write without response, another is write with response. In iOS even you tell the SDK wrong write type, it still can work, but in Android, it cannot. It will block all coming operations until timeout. So make sure both firmware and App are the same type for write operation.

 

conclusion

As a developer, I prefer iOS platform. The iOS SDK easy to use and stable, the document is good, even in iOS you cannot do what you want, like the background thing, but at least every issue has its answer.
But in Android, it’s a chaos. The document doesn’t update like this. It shows the API before Lollipop. And you cannot find the answer to a simple question, like why it cannot connect successfully every time. And the crazy thing is if a user reports you a bug, you need to buy the same device, but may not reproduce that.

After discovering, connecting, read/writing, the code can focus on operating bytes. If you want to do something with Bluetooth in Xamarin, I recommend Plugin.BluetoothLE which use Rx.net. Rx.net is very suitable for read and write operations, cause the result come just like a stream.

Advertisements

Forget ToolbarItem you don’t need it any more.

We help lots of custom to create their own Apps. In their designs, the navigation bar can be any style. Some custom doesn’t want the icon in navigation bar, some customer wants to put ToolbarItem in to left, some custom wants to change Page title’s font. In the old time, I was tired to change them by custom renderer, in iOS is OK, but in Android, it’s very hard to change layout of navigation bar.

Until one day, I came up a idea, why not use a full screen page and put all ToolbarItem into a view, then use Control Template to apply them to all pages? In that view we can do whatever we want.

I tried control template, but I found it hard to change different layouts by different pages, unless you create many templates.

How about we do like ListView, it has Header and Footer, put views into them. So I create a BasePage class, use Grid layout and it works, beyond this I also put pop-up view into BasePage. Cause this custom ask me to change the alert window designs.

After finish that, I’m very happy with using that, I can do binding, change layout, do custom navigation and pop-up different windows.

Then I think of should I put into FreshEssential?

We create it, when I maintain it I found lot feature requests are add API to change some control’s font, or change some default color. That is not good, I mean the API designs is not good, it should allow user to change everything they want, but if I allow user to change everything, it means I need to put more logic to keep it works fine and test every scenarios.

So in this time, I decide to share code instead of put it into Nuget. Two reasons, one the code is small and logic is simple, another the alert window has a lots of style we can change.

The code I put on Github,

Here are some demo images for that

Hope everybody can enjoy it.

Xamarin.Forms working within Android low resolution devices.

I was working on a project last time, the customer required the Xamarin.Forms App can be running on low resolution devices, like 480×800. I was following the design of iPhone, every thing worked fine with devices above iPhone 5, but not good on some Android devices with low resolution.

My Code is here. That is two entry and one button in a Frame, very simple.
<?xml version=1.0 encoding=UTF8?>
<local:ValidatorBasePage 
    xmlns:local=clrnamespace:MyAPP 
    BackgroundColor={x:Static local:AppColors.PageBackgroundGrey}
    Title=Enter Account &amp; Routing &#35;>
    <ContentPage.Content>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height=9* />
                <RowDefinition Height=1* />
            </Grid.RowDefinitions>
                <Grid Padding=10,30,10,30>
                    <Grid.RowDefinitions>
                        <RowDefinition Height=*/>
                        <RowDefinition Height=2*/>
                        <RowDefinition Height=*/>
                    </Grid.RowDefinitions>
                    <Frame>
                        <Image Source=ManualEnrollmentTopImage/>
                    </Frame>
                    <Frame Grid.Row=1>
                        <Grid Padding=10,10,10,15 RowSpacing=10>
                            <Grid.RowDefinitions>
                                <RowDefinition Height=1.5* />
                                <RowDefinition Height=1* />
                                <RowDefinition Height=1* />
                            </Grid.RowDefinitions>
                            <Label Text=“… />
                            <Entry Grid.Row=1/>
                            <Entry Grid.Row=2/>                         
                        </Grid>
                    </Frame>
                </Grid>
            <Button Grid.Row=1/>
        </Grid>
    </ContentPage.Content>
</local:ValidatorBasePage>

I want to built UI like this, even the keyboard appear, everything will be fine on iOS.

Simulator Screen Shot 7 Feb 2016, 10.21.36 AM

But in some Android devices, it like this. When the keyboard appear, it’s terrible.

 

That’s because of I have set this

        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height=9* />
                <RowDefinition Height=1* />
            </Grid.RowDefinitions>
                <Grid Padding=10,30,10,30>
                    <Grid.RowDefinitions>
                        <RowDefinition Height=*/>
                        <RowDefinition Height=2*/>
                        <RowDefinition Height=*/>
                    </Grid.RowDefinitions>

The grid’s height is based on the screen size, but the label and entry’s height is by default. It’s possible to get the devices’ size and do some change in UI, but it need lots of codes behind and hard to maintain.

Finally, I found a simply way to do that, just add a ScrollView
Like

            <ScrollView>
                <Grid Padding=10,30,10,30>
                    <Grid.RowDefinitions>
                        <RowDefinition Height=*/>
                        <RowDefinition Height=2*/>
                        <RowDefinition Height=*/>
                    </Grid.RowDefinitions>
                    <Frame>
                        <Image Source=ManualEnrollmentTopImage/>
                    </Frame>
                    <Frame Grid.Row=1>
                        <Grid Padding=10,10,10,15 
RowSpacing=10 >
                            …
                        </Grid>
                    </Frame>
                </Grid>
            </ScrollView>

After that, it looks better in small devices. But sometime, the keyboard will cover the last entry, to solve this, you can make the third row in Grid higher then before.

Finally It looks like this, much better.

Nexus S (KitKat) Screenshot 3

How to change the navigation page back icon?

In Xamarin Forms, there is no way to change the back icon in PCL. Actually, there is a property in NavigationPage class, called TitleIcon and also a function called SetTitleIcon. But they just add a Icon below the title like this.

resource

But that is not what we want, if we want like this, we need create PageRenderer for iOS and Android.

In iOS, we need to add a UIBarButtonItem into LeftBarButtonItem. But the interesting thing is, we cannot access the NavigationController.TopViewController.NavigationItem in OnElementChanged function, which we usually do the renderer things. After some research I find that we can do this in ViewWillAppear function.

In Android, I used the Android 6.0 SDK to build the App. The navigation bar in Xamarin.Android is the ActionBar, so we need few things to do here. First, we need to hide the App icon, then we need to replace the Up caret with custom icon, last we need to make title alignment in center. The default alignment is left, so we need to set customer view to ActionBar.

Here is the codes.

Also, I have added some logic for hiding the back icon in some pages. As sometimes we navigation to some pages, like after login page, we don’t want to customer to go back. It works well in iOS, but not Android. In Android, I can hide the back icon in screen, but if user press the physical button, this page still can go back. The better solution for this, is designing a good navigation stack, like after use login, we can clear the navigation stack or put this page as the root of navigation stack.

If you want to the whole dome solution, please check here

https://github.com/jessejiang0214/ChangeBackIconInXF

The confused Android soft keyboard

In Android soft keyboard, there is no dismiss button like iOS keyboard. When we want to use a big Editor control in Xamarin Forms pages, we should consider the keyboard will convert the page drama, like this.

It’s terrible that users could not find a way to click the “DONE” button(Actually, if user click the title bar, the keyboard will be hidden, but I believe lots of users do not know this.)

How to solve this problem?

Generally speaking, we can resize the page, when keyboard appears.
We can set “WindowSoftInputMode = SoftInput.AdjustResize” to MainActivity. But there is a problem, all the Xamarin pages are based on a single MainActivity. It means that if we change this, every pages will be resized when keyboard appears. It’s fine, but sometimes some pages like search login etc, which contains one or two Entries, these pages we don’t want them to be resized.

So How to solve this problem again?


I try to search solutions, which I just want to resize one pages in Xamarin Forms on Android, not all pages. I found a thread here
It cannot work, I have put them into PageRenderer. It did resize the page, but the new size is incorrectly.

I try to use TextView ImeOpt property. It can hide the keyboard, but if I set this, the Editor control cannot contain the multi-line text. It’s so funny.

Finally, I found the solution. One day, I just read the Android documents, and found

adjustUnspecified It is unspecified whether the activity’s main window resizes to make room for the soft keyboard, or whether the contents of the window pan to make the current focus visible on-screen. The system will automatically select one of these modes depending on whether the content of the window has any layout views that can scroll their contents. If there is such a view, the window will be resized, on the assumption that scrolling can make all of the window’s contents visible within a smaller area.

This is the default setting for the behavior of the main window.

This is the default settings for MainActivity. It said a layout can scroll their content.

OH,

How about put Editor into a ScrollView, like

mainGrid.Children.Add (new ScrollView { Content = textEditor}, 0, 1);

 

Yes, this solve my problem

Nexus 5 (Lollipop) Screenshot 3

How to hide keyboardAccessory bar in Xamarin.iOS

What is the keyboardAccessory bar?
As the image below, when user focus on an input in a webview, the keyboard will be launched with a keyboardAccessory bar. User can click “Done” to hide the keyboard.

Screen Shot 2015-09-25 at 5.01.19 pm

Sometime we don’t want it appears, we can also remove it in our codes.
In iOS, there is a library can do this
https://github.com/ionic-team/ionic-plugin-keyboard/blob/master/src/ios/IonicKeyboard.m

But in Xamarin.iOS, we can just use these codes. It is simple and easily to use.

    public class HideFormAccessoryBar
    {

        [DllImport("/usr/lib/libobjc.dylib")]
        extern static IntPtr class_getInstanceMethod(IntPtr classHandle, IntPtr Selector);

        [DllImport("/usr/lib/libobjc.dylib")]
        extern static IntPtr method_getImplementation(IntPtr method);

        [DllImport("/usr/lib/libobjc.dylib")]
        extern static IntPtr imp_implementationWithBlock(ref BlockLiteral block);

        [DllImport("/usr/lib/libobjc.dylib")]
        extern static void method_setImplementation(IntPtr method, IntPtr imp);

        static IntPtr UIOriginalImp;
        static IntPtr WKOriginalImp;
        static bool _hideFormAccessoryBar;
        public static void SetHideFormAccessoryBar(bool hide)
        {
            if (hide == _hideFormAccessoryBar)
                return;
            var uiMethod = class_getInstanceMethod(Class.GetHandle("UIWebBrowserView"), new Selector("inputAccessoryView").Handle);
            var wkMethod = class_getInstanceMethod(Class.GetHandle("WKContentView"), new Selector("inputAccessoryView").Handle);

            if(hide)
            {
                UIOriginalImp = method_getImplementation(uiMethod);
                WKOriginalImp = method_getImplementation(wkMethod);

                var block_value = new BlockLiteral();
                CaptureDelegate d = MyCapture;
                block_value.SetupBlock(d, null);
                var nilimp = imp_implementationWithBlock(ref block_value);
                method_setImplementation(uiMethod, nilimp);
                method_setImplementation(wkMethod, nilimp);

            }else
            {
                method_setImplementation(uiMethod, UIOriginalImp);
                method_setImplementation(wkMethod, WKOriginalImp);
            }
            _hideFormAccessoryBar = hide;
        }

        delegate IntPtr CaptureDelegate();

        [MonoPInvokeCallback(typeof(CaptureDelegate))]
        static IntPtr MyCapture()
        {
            return IntPtr.Zero;
        }
    }

Using MvvmCross to link Xamarin Native page and Xamarin Forms page

MvvmCross is a powerful framework, which can be used on Xamarin.IOS, Xamarin.Android and WP. We can use it to binding data, navigation, etc. But it cannot help us to share the UI codes. For example, if we want to build login page, we can use MvvmCross to build the data layer. We need to build a login page for Android, and another page for IOS.

Xamarin Forms can share the UI codes, it means we can build only one login page in Xamarin Forms, and use it both in Android and IOS. Generally speaking, if our project pages are all data, images, something with basic logic, we can use Xamarin Forms pages to build the whole solution.  But if this project use like camera, video/audio player, maps, then Xamarin Forms cannot help us to build these pages.

In our last project, the solution used camera, so that for some basic pages it have to use native pages. As MvvmCross cannot navigate between native page and Xamarin Forms page by default.

But we changed it, as MvvmCross has a outstanding architecture, we write a extension to achieve this. Here is demo

https://github.com/jessejiang0214/MvvmCrossNavigationDemo

For this demo, some steps are import.

  1. Included Xamarin.Forms packages into IOS and Android Project
  2. Init Xamarin.Forms, for IOS, it is in Setup.cs. But in Android, it is in SplashScreen page, if your project doesn’t have that page, you should make sure Xamarin.Forms.Init() before show Xamarin Forms page.
  3. Copy MvxTouchViewPresenter MvxPresenterHelpers MvxFormsDroidPagePresenter pages into your project. For Android project, you need create MvxFormsApplicationActivity, because Xamarin Forms need a host activity in Android.

We didn’t do the same thing in Windows Phone, because Xamarin Forms only support Windows Phone Silverlight in currently version (1.4.4). I don’t like Windows Phone Silverlight, so I just wait for Xamarin Forms support Windows RT, and then use it.

Hope you can enjoy our demo, and build amazing apps based on Xamarin Forms and MvvmCross.