androidandroid-thingsopenthread

Creating LowPAN network with nrf52840-Dongle with Android Things


I bought a Nordic nrf58240 USB Dongle a few weeks ago to try to play around with the lowpan API but have had little to no success with integrating it with the LowPAN API on android things.

At first I tried plugging it in out of the box to see if it worked using the lowpan example and I could not get it to connect and would get a Provision Exception in the onProvisionException callback when trying to create a network

So I thought let me buy the nrf58240-DK boards and see if that works, I flashed the prebuilt ncp image as described here and the DK board worked just fine on android things.

So I did a little more reading and read that I might have to build an image manually with the BOOTLOADER flag set so I did that.

Here are the commands I used with all the flags

make -f examples/Makefile-nrf52840 clean
make -f examples/Makefile-nrf52840 USB=1 BOOTLOADER=1 BORDER_AGENT=1 BORDER_ROUTER=1 COMMISSIONER=1 UDF_FORWARD=1 LINK_RAW=1
arm-none-eabi-objcopy output/nrf52840/bin/ot-cli-ftd -O ihex cli.hex

I took that .hex file and flashed it to the dongle with the nrf connect programmer tool for windows. Plugged it back into my raspberry pi only to still get the same Provision exception when trying to create a network

Here is the exception I get

W/System.err: com.google.android.things.lowpan.LowpanException: android.net.lowpan.InterfaceDisabledException: android.os.ServiceSpecificException: InvalidWhenDisabled (code 3)
W/System.err:     at com.google.android.things.lowpan.LowpanInterface$AsyncCallGlue.doInBackground(LowpanInterface.java:516)
W/System.err:     at com.google.android.things.lowpan.LowpanInterface$AsyncCallGlue.doInBackground(LowpanInterface.java:508)
W/System.err:     at android.os.AsyncTask$2.call(AsyncTask.java:333)
W/System.err:     at java.util.concurrent.FutureTask.run(FutureTask.java:266)
W/System.err:     at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
W/System.err:     at java.lang.Thread.run(Thread.java:764)
W/System.err: Caused by: android.net.lowpan.InterfaceDisabledException: android.os.ServiceSpecificException: InvalidWhenDisabled (code 3)
W/System.err:     at android.net.lowpan.LowpanInterface.form(LowpanInterface.java:236)
W/System.err:     at com.google.android.things.lowpan.LowpanInterface$1.run(LowpanInterface.java:587)
W/System.err:     at com.google.android.things.lowpan.LowpanInterface$AsyncCallGlue.doInBackground(LowpanInterface.java:513)
W/System.err:   ... 7 more
W/System.err: Caused by: android.os.ServiceSpecificException: InvalidWhenDisabled (code 3)
W/System.err:     at android.os.Parcel.readException(Parcel.java:2027)
W/System.err:     at android.os.Parcel.readException(Parcel.java:1959)
W/System.err:     at android.net.lowpan.ILowpanInterface$Stub$Proxy.form(ILowpanInterface.java:810)
W/System.err:     at android.net.lowpan.LowpanInterface.form(LowpanInterface.java:230)
W/System.err:   ... 9 more

Here is my code

class MainActivity2: Activity(){

    private var lowpanInterface: LowpanInterface? = null
    private var lowpanScanner: LowpanScanner? = null
    private var lowpanDriver: UartLowpanDriver? = null
    private var lowpanManager:LowpanManager? = null
    private val TAG = "MainActivity2"

    private var connectivityManager: ConnectivityManager? = null
    private var network: Network? = null

    private var backgroundHandlerThread: HandlerThread? = null
    private var handler: Handler? = null
    private var uiThreadHandler: Handler? = null
    private var socket: Socket? = null

    private val UART_PORT = "USB1-1.2:1.1"
    private val UART_BAUD = 115200

    private val SERVER_ADDRESS = "192.168.1.208"
    private val SERVER_PORT = 23456

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        lowpanManager = LowpanManager.getInstance()
        lowpanManager?.registerCallback(managerCallback)

        try {
            lowpanDriver = UartLowpanDriver(UART_PORT, UART_BAUD)
            lowpanDriver?.register()
            Log.d(TAG,"Lowpan Connected")
        } catch (e: Exception) {
            e.printStackTrace()
        }

        uiThreadHandler = Handler(Looper.getMainLooper())
    }

    override fun onDestroy() {
        super.onDestroy()
        stopNetworkScan()
        // Detach from any network we might be attached to.
        lowpanInterface?.unregisterCallback(callback)
        lowpanInterface?.leave()
        lowpanInterface = null
        lowpanDriver?.unregister()
        lowpanDriver?.close()
        lowpanDriver = null
    }

    private fun scanForNetworks() {

        val params = lowpanInterface?.getLowpanProvisioningParams(false)

        if(params?.lowpanIdentity?.name == "MeshNetwork"){
            return
        }

        lowpanScanner = lowpanInterface?.createScanner()?.apply {
            setCallback(scanner)
            startNetScan()
        }
    }

    private fun stopNetworkScan() {
        lowpanScanner?.stopNetScan()
    }

    private fun createNetwork(){
/* We are only specifying the network name here. By
     * doing this we allow the interface to pick reasonable
     * defaults for other required fields. If we specified
     * our own values for those fields, they would be used
     * instead.
     */
        val identity = LowpanIdentity.Builder()
                .setName("MeshNetwork")
                .build()

        /* Not specifying a LowpanCredential here tells “form()”
         * that we want the interface to generate the master key
         * for us.
         */
        val provision = LowpanProvisioningParams.Builder().setLowpanIdentity(identity).build()

        try{
            lowpanInterface?.form(provision)
            Log.d(TAG,"Network created")
        }catch (e:LowpanException){
            e.printStackTrace()
        }catch (e:LowpanRuntimeException){
            e.printStackTrace()
        }

    }

    private fun resetNetwork() {
        connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val networkRequest = NetworkRequest.Builder()
                .addTransportType(NetworkCapabilities.TRANSPORT_LOWPAN)
                .build()
        backgroundHandlerThread = HandlerThread("BackgroundThread")
        backgroundHandlerThread!!.start()
        handler = Handler(backgroundHandlerThread!!.looper)
        connectivityManager!!.registerNetworkCallback(networkRequest,networkCallback, uiThreadHandler)
    }

    private val scanner = object : LowpanScanner.Callback(){

        override fun onNetScanBeacon(beacon: LowpanBeaconInfo) {
            val network = beacon.lowpanIdentity
            if(network.name == "MeshNetwork"){
                joinNetwork(beacon)
            }

        }

        override fun onScanFinished() {
            // Release a semaphore
            Log.d(TAG,"Scan Finished")
        }
    }

    private val managerCallback = object:LowpanManager.Callback(){
        override fun onInterfaceAdded(lowpan_interface: LowpanInterface?) {
            super.onInterfaceAdded(lowpan_interface)

            lowpanInterface = lowpan_interface

            lowpanManager?.let {
            Log.d(TAG,"resetting network")
            resetNetwork()
            Log.d(TAG,"register callback")
            lowpanInterface?.registerCallback(callback)
            createNetwork()
            }
        }

        override fun onInterfaceRemoved(lowpan_interface: LowpanInterface?) {
            super.onInterfaceRemoved(lowpan_interface)
        }
    }

    private val callback = object : LowpanInterface.Callback() {

        override fun onStateChanged(state: Int) {
            super.onStateChanged(state)
            when (state){
                LowpanInterface.STATE_ATTACHED -> Log.d(TAG,"Attached")
                LowpanInterface.STATE_ATTACHING -> Log.d(TAG,"Attaching")
                LowpanInterface.STATE_DISABLED -> Log.d(TAG,"Disabled")
                LowpanInterface.STATE_FAULT -> Log.d(TAG,"Fault")
                LowpanInterface.STATE_OFFLINE -> Log.d(TAG,"Offline")
            }
            /* Handle interface state changes. */
        }

        override fun onLowpanIdentityChanged(identity: LowpanIdentity?) {
            super.onLowpanIdentityChanged(identity)
            Log.d(TAG,"Identity changed")
            Log.d(TAG,"Name: ${identity?.name}")
            Log.d(TAG,"Channel: ${identity?.channel}")
            Log.d(TAG,"Is Valid: ${identity?.isNameValid}")
            Log.d(TAG,"Panid: ${identity?.panid}")
            Log.d(TAG,"Raw Name: ${identity?.rawName}")
            Log.d(TAG,"Xpanid: ${identity?.xpanid}")

            /* Form, join, or leave completed successfully. */
        }

        override fun onProvisionException(exception: Exception?) {
            super.onProvisionException(exception)
            exception?.printStackTrace()
            /* An error occurred during form or join. */
        }
    }

    private val networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
        }

        override fun onLost(network: Network) {
        }
    }
}

Do I need to flash the Dongle differently? Perhaps the Dongle is not supported?

Not sure what the issue is at this point really


Solution

  • Found the issue, I was actually using the wrong type of image ot-cli-ftd vs what I needed was ot-ncp-ftd

    So the command

    arm-none-eabi-objcopy output/nrf52840/bin/ot-cli-ftd -O ihex cli.hex

    is change to

    arm-none-eabi-objcopy output/nrf52840/bin/ot-ncp-ftd -O ihex ncp.hex

    As stated in the documentation for Android Things

    To develop LoWPAN applications, you will need a radio module running as an OpenThread Network Co-Processor (NCP)