Bluetooth Low Energy - Monkey.Robotics problem

Hello Everyone!

I am working now on an app that needs to support connection and data exchange with a BLE device.
I have seen on the Xamarin page (https://developer.xamarin.com/samples/mobile/BluetoothLEExplorer/) that using the original Android/iOS APIs is deprecated, and that the Monkey.Robotics libraries should be used instead.

I have started the development with this library but it seems that some funkctions of the original BLE API are not available yet in Monkey.Robotics (e.g. WriteDescriptor). Unfortunatelly I need this to successfully "talk" to my device (with Stollmann Terminal I/O Profile).

So my questions are:
1. Is there a roadmap for the Monkey.Robotics project?
2. Is it planned to support all the native Android/iOS APIs in Monkey.Robotics?
3. Will the native APIs be still supported in the future?

Thank you for your time.

Best Answer

Answers

  • MarcoTMarcoT USMember ✭✭

    I'm using Monkey robotics but I'm having some problems with notifications under Android (under iOS all seems to work).

    Have you managed to use notifications under android?

  • DamianDamian USMember

    Thank you for your answers!

    I have decided to use the standard APIs from now. Since I am not using Xamarin.Forms it won't hurt so much, and the advantage will be that I will have more control on that what happens in code.

    @DanielVartdal - Thank you very much for the examples.

    @Marco.8016 - Sorry, but unfortunately I had some problems with other things, so I did not come so far with Monkey.Robotics to use notifications...

  • DanielVartdalDanielVartdal USMember ✭✭
    edited April 2015

    @marco.8016 Yes I have. I made some adjustment and got both notifications and indications working.

    What seems to be the problem?

  • DanielVartdalDanielVartdal USMember ✭✭

    @Damian no problem :) Good luck with your development!

  • ab2014ab2014 USMember
    edited April 2015

    Hi,

    I am having some troubles with BLE on iOS which I hope you can help me with.

    As I see some has written the code for enabling notification on Android:

    BluetoothGattDescriptor descriptor = _nativeCharacteristic.Descriptors [0]; descriptor.SetValue (BluetoothGattDescriptor.EnableNotificationValue.ToArray ()); _gatt.WriteDescriptor (descriptor);

    How is it done in iOS? How can I enable notification? As far as I know the CBDescriptor doesn't have the "EnableNotificationValue".

  • SybexiaSybexia USMember, University

    @DanielVartdal - It appears you are really familiar with the Monkey.Robotics library. Do you have any issues when you disconnect and then reconnect to the same device (I am using iOS)? Each time I disconnect and reconnect, my application discovers the services multiple times. For example, the first time I connect, I get my single service once. The second time I get the service twice and so on. This same issue occurs in the sample BLE Explorer app as well. Did you run into this issue and if so how did you fix it?

  • DanielVartdalDanielVartdal USMember ✭✭
    edited April 2015

    @Sybexia unfortunately, I haven't started using the library for iOS. It's something I'll be working on the next couple of months.

    The DiscoveredService method is in the device.cs file on the framework:

        this._nativeDevice.DiscoveredService += (object sender, NSErrorEventArgs e) => {
                // why we have to do this check is beyond me. if a service has been discovered, the collection
                // shouldn't be null, but sometimes it is. le sigh, apple.
                if (this._nativeDevice.Services != null) {
                    foreach (CBService s in this._nativeDevice.Services) {
                        Console.WriteLine ("Device.Discovered Service: " + s.Description);
                        if(!ServiceExists(s)) {
                            this._services.Add (new Service(s, this._nativeDevice));
                        }
                    }
                    this.ServicesDiscovered(this, new EventArgs());
                }
            };
    
    
        protected bool ServiceExists(CBService service)
        {
            foreach (var s in this._services) {
                if (s.ID == Service.ServiceUuidToGuid(service.UUID))
                    return true;
            }
            return false;
        }
    

    So, what you should look into (as it seems to me) is the service.UUID and the s.ID to check if there are some mismatch in the ID's or if they somehow change on the services on the BLE device (they shouldn't though)

    • Does the list(returned from ServicesDiscovered event) of services contain multiple items of the same service? (Then I would look on the ID's and see if you can find anything that differs)

    • Or is it the ServicesDiscovered event which triggers multiple times?

  • DanielVartdalDanielVartdal USMember ✭✭
    edited April 2015

    @mrbassir Unfortunately, I haven't worked on the iOS implementation yet.

    Take a look at Jeff Rowbergs' answer on this post:

    https://bluegiga.zendesk.com/entries/90962587-How-to-enable-Indications-on-CRP-Client-Configuration-descriptor-from-iOS8-Objective-C-

    The CoreBluetooth APIs in iOS don't let you differentiate between notifications and indications; based on the peripheral's GATT structure, it will simply use whichever type of data push method is available (notify or indicate). If the GATT structure has both notify and indicate enabled--which is very uncommon and probably shouldn't be done--then iOS will use notify.

    The Bluetooth 4.0 core specification (page 1898, section 3.3.1.1 listing Characteristic Properties) notes that the "Indicate" bit is 0x20, and the "Write" bit is 0x08, so the properties byte value of 0x28 reported by iOS shows that it is correctly identifying the characteristic as supporting "write" and "indicate" operations.

    From what it seems, the iOS device should know if the descriptor is waiting for notifications or indications based on what is set on the BLE device.

  • DavidKaplanDavidKaplan USMember

    Hi!
    I created a Xamarin forms App on Android using the old Bluetooth 2.0 specification using a Bluetooth Chat example which works fine.
    Now I must change over to Bluetooth BLE so we can support IOS (to my dismay).
    Our App will now connect from the phone to our proprietary hardware by way of a simple HM-10 BLE chip
    I will need to transfer binary data (up to 576 bytes) in both directions so I understand I will need to implement multiple low level messages.
    The Monkey.Robotics project is very appealing in that it provides C# code that hides the platform differences.
    On the other hand it is marked beta and is a moving target if I have to change platform code to get it to work.
    It also looks to include a lot of code that I would not need making possibly my App even larger yet.
    I mainly just need generic Characteristic Write and read functionality.
    I played around with the Monkey BtLeExplorer, it found my device and could send a byte to my hardware somehow.

    I need feedback as what is the best way for me to follow:
    1) Try to use the Monkey BtLeExplorer code without major changes.
    2) Try to change the previous (https://developer.xamarin.com/samples/mobile/BluetoothLEExplorer/) example to fit Android and IOS needs.
    3) Try to convert the HM-10 Chinese vendor's standard java and IOS code to work under forms
    4) Another way.

    if anyone has already done this and has code examples or other feedback I would appreciate it.
    David

  • Jon.CJon.C USMember

    hi David, did you figure out what seems to be the better approach? I am also looking forward to use Xamarin with hm-10. thanks
    Jon

  • sam.2541sam.2541 USMember

    Has anybody been able to figure out how to fix the issue mentioned by Sybexia?

    @DanielVartdal - It appears you are really familiar with the Monkey.Robotics library. Do you have any issues when you disconnect and then reconnect to the same device (I am using iOS)? Each time I disconnect and reconnect, my application discovers the services multiple times. For example, the first time I connect, I get my single service once. The second time I get the service twice and so on. This same issue occurs in the sample BLE Explorer app as well. Did you run into this issue and if so how did you fix it?”

  • DanielVartdalDanielVartdal USMember ✭✭

    @sam.2541 said:
    Has anybody been able to figure out how to fix the issue mentioned by Sybexia?

    @DanielVartdal - It appears you are really familiar with the Monkey.Robotics library. Do you have any issues when you disconnect and then reconnect to the same device (I am using iOS)? Each time I disconnect and reconnect, my application discovers the services multiple times. For example, the first time I connect, I get my single service once. The second time I get the service twice and so on. This same issue occurs in the sample BLE Explorer app as well. Did you run into this issue and if so how did you fix it?”

    I looked at one of the iOS projects yesterday, I do believe that the .discoverServices() callback is raised for each service found (as I could see yesterday, I had 7 services, and eventhough all services where found on the first callback, 7 where raised).

    I'm not sure if this has to do with Monkey.Robotics or the Peripheral interface which Xamarin has adapted from the BLE stack in iOS. I don't have the code now, as that is from another customer, but I vaguely remember that the discoverservices() is just calling the BLE interface from Xamarin.

    I don't think that this issue is too battery or memory consuming, as the discoverservice callback is fairly quick, so I would (if you don't want to do any changes yourself on the source code) check the list if a service exist (using the GUID) before adding.

    But a follow up question; is the peripheral.services that is adding? Or are you handling the services on your own lists?

    That is. First connect; peripheral.services.count = 5
    Second connect; perihperal.services.count = 5 or 10?

    If it's adding on the peripheral, you might not have closed/disposed the peripheral object after disconnect? (released the object)

  • GopsNathGopsNath USMember

    Dear All

    I am trying to Explorer bluetooth in Xamarin Forms and quite new to it, my objective is to convert the Andriod version of working Bluetooth app into xamarin forms iOS. Could someone assist me to convert this Andriod code to xamarin forms using bluetooth. Thanks in Advance.

    My email id : [email protected]

    Here is the andriod code

    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.util.UUID;

    import android.bluetooth.BluetoothDevice;
    import android.bluetooth.BluetoothSocket;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.util.Log;

    public class ConnectThread extends Thread {
    // constants
    static final String TAG = "HealForce-ConnectThread";
    // Well known SPP UUID
    private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
    private static final int BT_STATUS_Init = 0;
    private static final int BT_STATUS_CONNECTED = 1;
    private static final int BT_STATUS_DIGTIAL_MODE = 2;
    private static final int BT_STATUS_WAVEFORM_MODE = 3;
    private static final int BT_STATUS_HEARTBEAT_MODE = 4;

    public static final int BT_WAVEFORM_MODE = 1;
    public static final int BT_DIGTIAL_MODE = 2;
    public static final int BT_HEARTBEAT_MODE = 3;
    
    // for UI
    private Handler mHandler;
    private boolean mStop = false;
    
    // for BlueTooth
    private BluetoothDevice mBTDevice = null;
    private BluetoothSocket mBTSocket = null;
    private OutputStream mOutStream = null;
    private InputStream mInputStream = null;
    private int mBTStatus = BT_STATUS_Init;
    private int mBTMode = BT_DIGTIAL_MODE;
    
    private Handler mMsgHandler = new Handler() { // handles the INcoming msgs
        @Override
        public void handleMessage(Message msg) {
            String aResponse = msg.getData().getString("ChangeMode");
            if (aResponse.equalsIgnoreCase("Digital")) {
                mBTMode = BT_DIGTIAL_MODE;
            } else {
                if (aResponse.equalsIgnoreCase("Waveform")) {
                    mBTMode = BT_WAVEFORM_MODE;
                } else
                    mBTMode = BT_HEARTBEAT_MODE;
    
            }
        }
    };
    
    public Handler getHandler() {
        // a Get method which return the handler which This Thread is connected
        // with.
        return mMsgHandler;
    }
    
    public void CloseBTSocketResource() {
        if (mBTSocket != null) {
            try {
                mBTSocket.close();
            } catch (IOException e2) {
                Log.e(TAG, "unable to close() socket during connection failure", e2);
            }
        }
        mBTSocket = null;
        if (mOutStream != null) {
            try {
                mOutStream.close();
            } catch (IOException e2) {
                Log.e(TAG, "unable to close() mOutStream during connection failure", e2);
            }
        }
        mOutStream = null;
        if (mInputStream != null) {
            try {
                mInputStream.close();
            } catch (IOException e2) {
                Log.e(TAG, "unable to close() mOutStream during connection failure", e2);
            }
        }
        mInputStream = null;
    }
    
    public ConnectThread(BluetoothDevice device, Handler handler, int mode) {
        // for BlueTooth
        mBTDevice = device;
        CloseBTSocketResource();
    
        // for UI
        mHandler = handler;
        mBTMode = mode;
    
    }
    
    public void Stop() {
        Log.d(TAG, "Call Thread onDestroy....");
        mStop = true;
    }
    
    public void run() {
        mBTStatus = BT_STATUS_Init;
        ConnectDevice();
        CloseBTSocketResource();
    }
    
    private void ConnectDevice() {
        try {
            // Get a BluetoothSocket for a connection with the device
            /*
             * if(Build.VERSION.SDK_INT >= 4.0){ String PinStr="6666";
             * mBTDevice.setPin(PinStr.getBytes()); mBTSocket =
             * mBTDevice.createRfcommSocketToServiceRecord(MY_UUID); }
             */
    
            mBTSocket = mBTDevice.createInsecureRfcommSocketToServiceRecord(MY_UUID);
    
            if (mBTSocket != null) {
                // connect to remote device
                Log.d(TAG, "Call Connect()");
    
                mBTSocket.connect();
    
            }
            // create Input and Output Stream
    
            mOutStream = mBTSocket.getOutputStream();
            mInputStream = mBTSocket.getInputStream();
    
        } catch (IOException e) {
            e.printStackTrace();
            Log.e(TAG, "Bluetooth socket connect fail.\nPlease check the device is turned on.");
            Bundle b = new Bundle();
            Message msgObj = mHandler.obtainMessage();
            b.putString("error", "Please check status of Healforce");
            msgObj.setData(b);
            msgObj.what = 1;// message
            mHandler.sendMessage(msgObj);
            return;
    
        }
    
        if (mBTSocket != null) {
    
            Log.d(TAG, "Connected");
            Log.d(TAG, mBTSocket.getRemoteDevice().getName());
            Log.d(TAG, "start Measure");
            StartMeasure();
            CloseBTSocketResource();
        }
    }
    
    private void StartTransfer() {
        switch (mBTMode) {
        case BT_DIGTIAL_MODE:
            /* Enable Digital data */
            Log.d(TAG, "Enable Digital data");
            DisableDeviceToData(BT_WAVEFORM_MODE);
            if (mStop)
                return;
            DisableDeviceToData(BT_HEARTBEAT_MODE);
            if (mStop)
                return;
            EnableDeviceToData(BT_DIGTIAL_MODE);
            if (mStop)
                return;
            mBTStatus = BT_STATUS_DIGTIAL_MODE;
            PollingData(BT_DIGTIAL_MODE);
            break;
        case BT_WAVEFORM_MODE:
    
            /* Enable WaveForm data */
            Log.d(TAG, "Enable WaveForm data");
            DisableDeviceToData(BT_DIGTIAL_MODE);
            if (mStop)
                return;
            DisableDeviceToData(BT_HEARTBEAT_MODE);
            if (mStop)
                return;
            EnableDeviceToData(BT_WAVEFORM_MODE);
            if (mStop)
                return;
            mBTStatus = BT_STATUS_WAVEFORM_MODE;
            PollingData(BT_WAVEFORM_MODE);
            break;
        case BT_HEARTBEAT_MODE:
        default:
            /* Enable WaveForm data */
            Log.d(TAG, "Enable WaveForm data");
            DisableDeviceToData(BT_DIGTIAL_MODE);
            if (mStop)
                return;
            DisableDeviceToData(BT_WAVEFORM_MODE);
            if (mStop)
                return;
            EnableDeviceToData(BT_HEARTBEAT_MODE);
            if (mStop)
                return;
            mBTStatus = BT_STATUS_HEARTBEAT_MODE;
            PollingData(BT_HEARTBEAT_MODE);
    
            break;
        }
    
    }
    
    private void StartMeasure() {
    
        // get Version
        if (!GetVersion())
            return;
        mBTStatus = BT_STATUS_CONNECTED;
        while (!mStop) {
            StartTransfer();
        }
    }
    
    private boolean GetVersion() {
        byte[] msgBuffer = new byte[6];
        boolean isSucess = false;
        byte DataPacket[];
        String versionStr = "";
        Bundle b = new Bundle();
        Message msgObj = mHandler.obtainMessage();
    
        // generate get version packet
        msgBuffer[0] = (byte) 0xAA;
        msgBuffer[1] = (byte) 0x55;
        msgBuffer[2] = (byte) 0x0F;
        msgBuffer[3] = (byte) 0x02;
        msgBuffer[4] = (byte) 0x83;
        msgBuffer[5] = (byte) (CRC8_Calculator.crc8(msgBuffer, 0, 5));
        try {
            mOutStream.write(msgBuffer, 0, 6);
            mOutStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
            mStop = true;
            return false;
        }
    
        DataPacket = ReadPacket(1000);// 1 seconds
        if (mStop)
            return false;
        if (DataPacket.length >= 8) {
            versionStr = MessageParser.VersionPacket(DataPacket, DataPacket.length);
            if (versionStr.equalsIgnoreCase("Error"))
                isSucess = false;
            else
                isSucess = true;
        }
    
        if (!isSucess) {
            b.putString("error", "Get version failed");
            msgObj.setData(b);
            msgObj.what = 1;// error
            mHandler.sendMessage(msgObj);
    
        } else {
    
            b.putString("message", versionStr);
            msgObj.setData(b);
            msgObj.what = 2;// version message
            mHandler.sendMessage(msgObj);
    
        }
        return isSucess;
    
    }
    
    /*
     * true is digital data false is Waveform data
     */
    private boolean EnableDeviceToData(int dataType) {
        byte[] msgBuffer = new byte[7];
        byte DataPacket[];
        boolean isSucess = false;
    
        Bundle b = new Bundle();
        Message msgObj = mHandler.obtainMessage();
    
        // generate get SetFrequency packet
        msgBuffer[0] = (byte) 0xAA;
        msgBuffer[1] = (byte) 0x55;
        msgBuffer[2] = (byte) 0x0F;
        msgBuffer[3] = (byte) 0x03;
        switch (dataType) {
        case BT_DIGTIAL_MODE:
            msgBuffer[4] = (byte) 0x84;
            msgBuffer[5] = (byte) 0x01;
            break;
        case BT_WAVEFORM_MODE:
            msgBuffer[4] = (byte) 0x85;
            msgBuffer[5] = (byte) 0x01;
            break;
        case BT_HEARTBEAT_MODE:
        default:
            msgBuffer[4] = (byte) 0x80;
            msgBuffer[5] = (byte) 0x02;// interval
            break;
    
        }
    
        msgBuffer[6] = (byte) (CRC8_Calculator.crc8(msgBuffer, 0, 6));
        try {
            mOutStream.write(msgBuffer, 0, 7);
            mOutStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
            mStop = true;
            return false;
        }
        while (!mStop) {
    
            DataPacket = ReadPacket(1000);// 1 seconds
            if (mStop)
                return isSucess;
            if (DataPacket.length == 7) {
                isSucess = true;
                break;
            }
        }
    
        if (!isSucess) {
    
            b.putString("error", "Enable command failed");
            msgObj.setData(b);
            msgObj.what = 1;// error
            mHandler.sendMessage(msgObj);
    
        }
        return isSucess;
    
    }
    
    /*
     * true is digital data false is Waveform data
     */
    private boolean DisableDeviceToData(int dataType) {
        byte[] msgBuffer = new byte[7];
        byte DataPacket[];
        boolean isSucess = false;
    
        Bundle b = new Bundle();
        Message msgObj = mHandler.obtainMessage();
    
        // generate get SetFrequency packet
        msgBuffer[0] = (byte) 0xAA;
        msgBuffer[1] = (byte) 0x55;
        msgBuffer[2] = (byte) 0x0F;
        msgBuffer[3] = (byte) 0x03;
        switch (dataType) {
        case BT_DIGTIAL_MODE:
            msgBuffer[4] = (byte) 0x84;
            msgBuffer[5] = (byte) 0x00;
            break;
        case BT_WAVEFORM_MODE:
            msgBuffer[4] = (byte) 0x85;
            msgBuffer[5] = (byte) 0x00;
            break;
        case BT_HEARTBEAT_MODE:
        default:
            msgBuffer[4] = (byte) 0x80;
            msgBuffer[5] = (byte) 0x00;// interval
            break;
    
        }
    
        msgBuffer[6] = (byte) (CRC8_Calculator.crc8(msgBuffer, 0, 6));
        try {
            mOutStream.write(msgBuffer, 0, 7);
            mOutStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
            mStop = true;
            return false;
        }
        while (!mStop) {
    
            DataPacket = ReadPacket(1000);// 1 seconds
            if (mStop)
                return isSucess;
            if (DataPacket.length == 7) {
                isSucess = true;
                break;
            }
        }
    
        if (!isSucess) {
    
            b.putString("error", "Get version failed");
            msgObj.setData(b);
            msgObj.what = 1;// error
            mHandler.sendMessage(msgObj);
    
        }
        /*
         * else {
         * 
         * b.putString("message","Start Polling data"); msgObj.setData(b);
         * msgObj.what=0;//message mHandler.sendMessage(msgObj);
         * 
         * }
         */
        return isSucess;
    
    }
    
    /*
     * true is digital data false is Waveform data
     */
    private void PollingData(int dataType) {
        String DataStr = null;
        int datalen;
        byte DataPacket[];
    
        switch (dataType) {
        case BT_DIGTIAL_MODE:
            datalen = 12;
            break;
        case BT_WAVEFORM_MODE:
            datalen = 11;
            break;
        case BT_HEARTBEAT_MODE:
        default:
            datalen = 10;
            break;
    
        }
    
        while (!mStop) {
            // Log.d(TAG,"Start polling data\n");
    
            // change mode
            if (mBTStatus == BT_STATUS_WAVEFORM_MODE && mBTMode != BT_WAVEFORM_MODE) {
                Log.d(TAG, "change mode");
                return;
            }
            // change mode
            if (mBTStatus == BT_STATUS_DIGTIAL_MODE && mBTMode != BT_DIGTIAL_MODE) {
                Log.d(TAG, "change WAVEFORM mode");
                return;
            }
            // change mode
            if (mBTStatus == BT_STATUS_HEARTBEAT_MODE && mBTMode != BT_HEARTBEAT_MODE) {
                Log.d(TAG, "change WAVEFORM mode");
                return;
            }
            DataPacket = ReadPacket(1000);// 1 seconds
            if (mStop)
                return;
            // Log.d(TAG,MessageParser.bytesToHex(DataPacket));
            if (DataPacket.length == datalen) {
                switch (dataType) {
                case BT_DIGTIAL_MODE:
                    DataStr = MessageParser.DigtialDataPacket(DataPacket, DataPacket.length);
                    break;
                case BT_WAVEFORM_MODE:
                    DataStr = MessageParser.WaveformDataPacket(DataPacket, DataPacket.length);
                    break;
                case BT_HEARTBEAT_MODE:
                default:
                    // Log.d(TAG,MessageParser.bytesToHex(DataPacket));
                    // DataStr=MessageParser.bytesToHex(DataPacket);
                    DataStr = "message: test";
                    break;
    
                }
    
                if (mStop)
                    return;
                if (!DataStr.equalsIgnoreCase("Error")) {
                    Log.d(TAG, "Post Message : " + DataStr);
                    Bundle b = new Bundle();
                    Message msgObj = mHandler.obtainMessage();
                    b.putString("message", DataStr);
                    msgObj.setData(b);
                    msgObj.what = 0;// message
                    mHandler.sendMessage(msgObj);
                }
            } // if(DataPacket.length == datalen)
    
        } // while(!mStop)
    }
    
    private byte[] ReadPacketHeaderfromStream(int offset, int intervals) {
        int needLen = 0;
        int bytesAvailable;
        int datalen = 0;
        int i;
        int readSize;
        boolean isHeader1 = false;// 0xAA
        boolean isHeader2 = false;// 0x55
        boolean isHeader3 = false;// 0x0F
    
        byte[] buffer = new byte[4];
        byte[] msgBuffer = null;
    
        switch (offset) {
        case 1:
            isHeader1 = true;
            break;
        case 2:
            isHeader1 = true;
            isHeader2 = true;
            break;
        case 3:
            isHeader1 = true;
            isHeader2 = true;
            isHeader3 = true;
            break;
        default:
            isHeader1 = false;// 0xAA
            isHeader2 = false;// 0x55
            isHeader3 = false;// 0x0F
            break;
        }
    
        try {
            while (!mStop) {
                // Get Header 0xAA 0x55
                bytesAvailable = mInputStream.available();
                needLen = 4 - offset;
    
                if (needLen <= 0)
                    break;
                if (bytesAvailable >= needLen) {
                    readSize = mInputStream.read(buffer, 0, needLen);
                    if (readSize < 0) {
                        msgBuffer = null;
                        return msgBuffer;
                    }
                    // Log.d(TAG,MessageParser.bytesToHex(buffer));
                    // Log.d(TAG,Integer.toString(readSize));
                    for (i = 0; i < needLen; i++) {
    
                        if (isHeader1) { // founded 0xAA
    
                            if (isHeader2) { // founded 0xAA 0x55
                                if (isHeader3) { // founded 0xAA 0x55 0x0F
                                    datalen = buffer[i];
                                    offset = 4;
                                    break;
                                } else {
                                    if (buffer[i] == (byte) 0x0F) {
                                        isHeader3 = true;
                                        offset = 3;
    
                                    } else {
                                        isHeader1 = false;
                                        isHeader2 = false;
                                        offset = 0;
                                    }
    
                                }
                            } else {
    
                                if (buffer[i] == (byte) 0x55) {
                                    isHeader2 = true;
                                    offset = 2;
    
                                } else {
                                    isHeader1 = false;
                                    offset = 0;
                                }
                            }
    
                        } else {
                            if (buffer[i] == (byte) 0xAA) {
    
                                isHeader1 = true;
                                offset = 1;
    
                            }
                        }
    
                    } // for(i=0;i<needLen;i++)
    
                } // if (bytesAvailable >= needLen)
                else {
    
                    try {
                        Thread.sleep(intervals);
                        // Do some stuff
                    } catch (Exception e) {
                        e.getLocalizedMessage();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            mStop = true;
            return null;
        }
        if (isHeader1 && isHeader2 && isHeader3 && datalen > 0) {
    
            msgBuffer = new byte[4];
            msgBuffer[0] = (byte) 0xAA;
            msgBuffer[1] = (byte) 0x55;
            msgBuffer[2] = (byte) 0x0F;
            msgBuffer[3] = (byte) datalen;
        }
        return msgBuffer;
    }
    
    private byte[] ReadPacketHeaderFromBuf(byte[] Packet, int offset, int Packetlen, int intervals) {
        byte[] msgBuffer = null;
        int i;
    
        for (i = offset; i < Packetlen; i++) {
            if (Packet[i] == 0xAA) {
    
                if (Packetlen - i >= 4) {
                    if (Packet[i + 1] == 0x55 && Packet[i + 2] == 0x0F) {
                        msgBuffer = new byte[Packetlen - i];
                        System.arraycopy(Packet, i, msgBuffer, 0, Packetlen - i);
                        return msgBuffer;
                    } else {
                        continue;
                    }
                } else {
                    switch (Packetlen - i) {
                    case 1:
                        return ReadPacketHeaderfromStream(1, intervals);
    
                    case 2:
                        if (Packet[i + 1] == 0x55)
                            return ReadPacketHeaderfromStream(2, intervals);
                        else
                            continue;
    
                    case 3:
                        if (Packet[i + 1] == 0x55 && Packet[i + 2] == 0x0F)
                            return ReadPacketHeaderfromStream(3, intervals);
                        else
                            continue;
    
                    default:
                        continue;
                    }
    
                }
            } // if(Packet[i] == 0xAA)
        } // for
    
        return msgBuffer;
    }
    
    private byte[] ReadPacket(int intervals) {
    
        int bytesAvailable;
        int offset;
        int datalen = 0;
        byte[] msgBuffer = null;
        byte[] newBuffer = null;
        byte[] headerBuffer;
        int needLen = 0;
        int readSize;
        int retryCount = 0;
    
        headerBuffer = ReadPacketHeaderfromStream(0, intervals);
        if (headerBuffer != null) {
            datalen = headerBuffer[3];
            msgBuffer = new byte[datalen + 4];
            msgBuffer[0] = (byte) 0xAA;
            msgBuffer[1] = (byte) 0x55;
            msgBuffer[2] = (byte) 0x0F;
            msgBuffer[3] = (byte) datalen;
            offset = 4;
            try {
                while (!mStop) {
                    // Get Content
                    datalen = msgBuffer[3];
                    needLen = datalen + 4 - offset;
                    // Log.d(TAG,"Need Len : "+Integer.toString(needLen));
                    if (needLen > 0)
                        bytesAvailable = mInputStream.available();
                    else
                        bytesAvailable = needLen;
    
                    if (bytesAvailable >= needLen) {
                        retryCount = 0;
                        if (needLen > 0) {
                            readSize = mInputStream.read(msgBuffer, offset, needLen);
                            if (readSize < 0) {
                                msgBuffer = null;
                                return msgBuffer;
                            }
                        }
                        if (msgBuffer[datalen + 3] == (byte) (CRC8_Calculator.crc8(msgBuffer, 0, datalen + 3)))
                            return msgBuffer;
                        else {
                            Log.d(TAG, "CRC8 error!!");
                            Log.d(TAG, MessageParser.bytesToHex(msgBuffer));
                            newBuffer = ReadPacketHeaderFromBuf(msgBuffer, 3, datalen + 1, intervals);
                            if (newBuffer == null)
                                return ReadPacket(intervals);
                            else {
                                offset = newBuffer.length;
                                msgBuffer = new byte[newBuffer[3] + 4];
                                System.arraycopy(newBuffer, 0, msgBuffer, 0, offset);
                                continue;
                            }
    
                        }
    
                    } else {
                        retryCount++;
                        if (retryCount > 60 && bytesAvailable > 0) {
                            offset = 4;
                            datalen = bytesAvailable;
                            needLen = datalen + 4 - offset;
                            readSize = mInputStream.read(msgBuffer, offset, needLen);
                            if (readSize < 0) {
                                msgBuffer = null;
                                return msgBuffer;
                            }
                            Log.d(TAG, "Data Len error!!");
                            Log.d(TAG, MessageParser.bytesToHex(msgBuffer));
                            newBuffer = ReadPacketHeaderFromBuf(msgBuffer, 3, datalen + 1, intervals);
                            if (newBuffer == null)
                                return ReadPacket(intervals);
                            else {
                                offset = newBuffer.length;
                                msgBuffer = new byte[newBuffer[3] + 4];
                                System.arraycopy(newBuffer, 0, msgBuffer, 0, offset);
                                continue;
                            }
                        }
    
                        try {
                            Thread.sleep(1000);
                            // Do some stuff
                        } catch (Exception e) {
                            e.getLocalizedMessage();
                            mStop = true;
                            return null;
                        }
                    }
    
                }
            } catch (IOException e) {
                Logger.debug(TAG, "Read Content error");
                e.printStackTrace();
            }
        }
        return msgBuffer;
    }
    

    }

    Please help to convert this code to Xamarin Forms in iOS, or give me the idea how to do it.

    Thanks

  • @sam.2541 said:
    Has anybody been able to figure out how to fix the issue mentioned by Sybexia?

    @DanielVartdal - It appears you are really familiar with the Monkey.Robotics library. Do you have any issues when you disconnect and then reconnect to the same device (I am using iOS)? Each time I disconnect and reconnect, my application discovers the services multiple times. For example, the first time I connect, I get my single service once. The second time I get the service twice and so on. This same issue occurs in the sample BLE Explorer app as well. Did you run into this issue and if so how did you fix it?”

    ********Im also facing the same issue when i disconnect and connect again then I see 10 services where as first time it shows only 5.

    Please let me know how to solve this issue. ********> @DanielVartdal said:

    @sam.2541 said:
    Has anybody been able to figure out how to fix the issue mentioned by Sybexia?

    @DanielVartdal - It appears you are really familiar with the Monkey.Robotics library. Do you have any issues when you disconnect and then reconnect to the same device (I am using iOS)? Each time I disconnect and reconnect, my application discovers the services multiple times. For example, the first time I connect, I get my single service once. The second time I get the service twice and so on. This same issue occurs in the sample BLE Explorer app as well. Did you run into this issue and if so how did you fix it?”

    I looked at one of the iOS projects yesterday, I do believe that the .discoverServices() callback is raised for each service found (as I could see yesterday, I had 7 services, and eventhough all services where found on the first callback, 7 where raised).

    I'm not sure if this has to do with Monkey.Robotics or the Peripheral interface which Xamarin has adapted from the BLE stack in iOS. I don't have the code now, as that is from another customer, but I vaguely remember that the discoverservices() is just calling the BLE interface from Xamarin.

    I don't think that this issue is too battery or memory consuming, as the discoverservice callback is fairly quick, so I would (if you don't want to do any changes yourself on the source code) check the list if a service exist (using the GUID) before adding.

    But a follow up question; is the peripheral.services that is adding? Or are you handling the services on your own lists?

    That is. First connect; peripheral.services.count = 5
    Second connect; perihperal.services.count = 5 or 10?

    If it's adding on the peripheral, you might not have closed/disposed the peripheral object after disconnect? (released the object)

    Im also facing the same issue the answer for above question is 10 when I connect next time it becomes 10 where first time its 5.

    Please let me know why is this happening. due to this issue my app is more in stable.

  • DanielVartdalDanielVartdal USMember ✭✭

    @mahendrareddy It might be that the Adapter class is not disposed/closed correctly between discoveries/scans.

    if you are only using the Monkey Robotics Library, it might be an issue that the device.ServicesDiscovered event is duplicated (and not closed) for every new scan. Or possible an duplicate event on the adapter.DevicesDicovered (Not certain on this at all).

    This is only an idea of what might be the problem. If so, you would need to do the changes in the library yourself.

    If not, you could make a list of discovered services, and ignore the duplicates. It's a work around, doesn't really do anytning with the problem, but might help you with the instability.

    -Daniel-

  • PhilippeBourquePhilippeBourque CAUniversity ✭✭

    I have the same problem here, but only with Android.

    Problem appear in Device.cs class, _gattCallbackthe ServicesDiscovered function :

                this._gattCallback.ServicesDiscovered += (s, e) => {
                    var services = this._gatt.Services;
                    this._services = new List<IService> ();
                    foreach (var item in services) {
                        this._services.Add (new Service (item, this._gatt, this._gattCallback));
                    }
                    this.ServicesDiscovered (this, e);
                };
    

    I don't understand why, but after a connect/disconnect/connect, the this._gatt is null...
    I try to figure it out right now.

  • PhilippeBourquePhilippeBourque CAUniversity ✭✭

    In Adapter.cs, we can see this comment, but the _gattCallback seems to be the same at the second connect :

        public void ConnectToDevice (IDevice device)
        {
            // returns the BluetoothGatt, which is the API for BLE stuff
            // TERRIBLE API design on the part of google here.
            ((BluetoothDevice)device.NativeDevice).ConnectGatt (Android.App.Application.Context, true, this._gattCallback);
        }
    
  • PhilippeBourquePhilippeBourque CAUniversity ✭✭

    I tweak the code to remove the duplicate call to ServicesDiscovered but it did not resolve the problem.

    The _gatt variable still null when the ServicesDiscovered callback fire up.

    I really don't get where this variable is deleted in memory...

  • JohnChiodiniJohnChiodini USMember

    You guys may find this helpful. I am using the BLE.Plugin library from here. It has extended the Monkey Robotics BLE implementation, and fixed many bugs.

    https://github.com/xabre/xamarin-bluetooth-le

  • Andrew_NemtsevAndrew_Nemtsev RUMember ✭✭

    @JohnChiodini said:
    You guys may find this helpful. I am using the BLE.Plugin library from here. It has extended the Monkey Robotics BLE implementation, and fixed many bugs.

    https://github.com/xabre/xamarin-bluetooth-le

    Is it working at 4.4 devices?
    I mean "Vanilla" nuget package:
    https://github.com/xabre/xamarin-bluetooth-le/issues/96

  • Lolo2ParisLolo2Paris FRMember

    Hello,
    I'm working on a Hearth Rate bluetooth device and I need to use it with both Android and Xamarin.
    There is a old dependency to monotouch and it doesn't work in my application.
    Is it possible to remove the reference ?
    Thanks

  • AlbertKAlbertK MYMember ✭✭✭✭

    @Lolo2Paris said:
    Hello,
    I'm working on a Hearth Rate bluetooth device and I need to use it with both Android and Xamarin.
    There is a old dependency to monotouch and it doesn't work in my application.
    Is it possible to remove the reference ?
    Thanks

    Hi,
    There is a more recent implementation that should work. Get the source code or get it from Nuget - Plugin.BLE

    https://github.com/xabre/xamarin-bluetooth-le

  • Lolo2ParisLolo2Paris FRMember

    Thanks !!!!!!!!! <3 <3 <3 <3

    I'll look at it !

Sign In or Register to comment.