The Low-Power Wireless Personal Area Networks (LoWPAN) API enables developers to manage and configure IP-based, low-power, lossy networks, such as Thread networks.
On these networks, Android devices can communicate directly with other peer devices, even ultra-low power battery operated nodes that may not have WiFi or Bluetooth connectivity (such as door locks and window sensors). These IPv6 mesh networks are secure and resilient with no single point of failure.
The Android Things LoWPAN API enables applications to perform the following functions:
- Scan for nearby supported low-power wireless mesh networks
- Join a specific low-power wireless mesh network
- Form a new low-power wireless mesh network
In order for devices to participate on the same network they must all share the same provisioning parameters, consisting of the network identity and the network credential. If either of these parameters are different, devices will be unable to communicate with each other.
Hardware considerations
To develop LoWPAN applications, you will need a radio module running as an OpenThread Network Co-Processor (NCP). One recommended kit is the nRF52840-PDK. You will need a standard micro USB-B to USB-A cable to connect this kit to your Android Things device.
To get OpenThread running on the development kit, follow these instructions.
Adding the required permissions
Accessing the LowpanManager
API requires the ACCESS_LOWPAN_STATE
permission.
Creating and joining networks from a LowpanInterface
requires the
CHANGE_LOWPAN_STATE
permission. Add the required permissions to your app's
manifest file:
<uses-permission android:name="com.google.android.things.permission.ACCESS_LOWPAN_STATE" />
<uses-permission android:name="com.google.android.things.permission.CHANGE_LOWPAN_STATE" />
Managing the interface connection
In order for the device to be associated with a LoWPAN network, you need to get
an instance of class LowpanInterface
. Class LowpanManager
does this for you.
Your application can use this manager to look up LowpanInterface
objects on
your Android device.
Provisioning parameters, such as the network identity information
(LowpanIdentity
) and the associated credential (LowpanCredential
), are
stored persistently and are automatically restored at boot-up. To forget these
parameters, you must explicitly abandon the network by calling leave()
.
Kotlin
class HomeActivity : Activity() { private var lowpanInterface: LowpanInterface? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val lowpanManager = LowpanManager.getInstance() lowpanInterface = lowpanManager.getInterface() // Or get an interface by name if (lowpanInterface == null) { // Is the radio module connected and a user driver registered? } } override fun onDestroy() { super.onDestroy() // Detach from any network we might be attached to. lowpanInterface?.leave() } }
Java
public class HomeActivity extends Activity { LowpanInterface lowpanInterface; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LowpanManager lowpanManager = LowpanManager.getInstance(); lowpanInterface = lowpanManager.getInterface(); // Or get an interface by name if (lowpanInterface == null) { // Is the radio module connected and a user driver registered? } } @Override protected void onDestroy() { super.onDestroy(); /* Detach from any network we might be attached to. */ if (lowpanInterface != null) { lowpanInterface.leave(); } } }
Scanning for nearby networks
Create a LowpanScanner
with callbacks to handle received events.
Kotlin
private var lowpanScanner: LowpanScanner? = null @Throws(LowpanException::class) private fun scanForNetworks() { lowpanScanner = lowpanInterface?.createScanner()?.apply { setCallback(scanCallback) startNetScan() } } private fun stopNetworkScan() { lowpanScanner?.stopNetScan() } private val scanCallback = object : LowpanScanner.Callback() { override fun onNetScanBeacon(beacon: LowpanBeaconInfo) { val network = beacon.lowpanIdentity Log.d("LoWPAN", "Network Beacon: ${network.name}") } override fun onScanFinished() { // Release a semaphore } }
Java
LowpanScanner lowpanScanner; private void scanForNetworks() throws LowpanException { lowpanScanner = lowpanInterface.createScanner(); lowpanScanner.setCallback(scanCallback); lowpanScanner.startNetScan(); } private void stopNetworkScan() { lowpanScanner.stopNetScan(); } private LowpanScanner.Callback scanCallback = new LowpanScanner.Callback() { @Override public void onNetScanBeacon(LowpanBeaconInfo beacon) { LowpanIdentity network = beacon.getLowpanIdentity(); Log.d("LoWPAN", "Network Beacon: " + network.getName()); } @Override public void onScanFinished() { // Release a semaphore } };
Once the scan is finished, you should have a list of available networks.
Joining an existing network
Use the join()
method of LowpanInterface
when you know there is at least one
other device in range on the network you are trying to join and you want the
application to fail hard if it can't find it.
First, you need a LowpanProvisioningParams
object. Create one using a
LowpanIdentity
object and a LowpanCredential
object. Call the join()
method of class LowpanInterface
with the provisioning parameters
(LowpanProvisioningParams
) to create the network.
You can also obtain the LowpanIdentity
object from a scan of available
networks (see Scanning for nearby networks for the callback
definitions).
Kotlin
@Throws(LowpanException::class) private fun joinNetwork(lowpanInterface: LowpanInterface) { val identity = LowpanIdentity.Builder() .setName("YourNetwork") .setXpanid("DEBA7AB1E5EAF00D".toByteArray(Charset.defaultCharset())) .setPanid(0x1337) .setChannel(15) .build() val credential = LowpanCredential .createMasterKey("00112233445566778899AABBCCDDEEFF") val provision = LowpanProvisioningParams.Builder() .setLowpanIdentity(identity) .setLowpanCredential(credential) .build() lowpanInterface.join(provision) }
Java
private void joinNetwork(LowpanInterface lowpanInterface) throws LowpanException { final LowpanIdentity identity = new LowpanIdentity.Builder() .setName("YourNetwork") .setXpanid("DEBA7AB1E5EAF00D") .setPanid(0x1337) .setChannel(15) .build(); final LowpanCredential credential = LowpanCredential .createMasterKey("00112233445566778899AABBCCDDEEFF"); final LowpanProvisioningParams provision = new LowpanProvisioningParams.Builder() .setLowpanIdentity(identity) .setLowpanCredential(credential) .build(); lowpanInterface.join(provision); }
For the LowpanCredential
object, you will need to obtain the credential
out-of-band (for example, through wifi or the cloud) or supply your own. This is
known as out-of-band commissioning.
Creating a new network
Use form()
to create a new, empty network with only the device on it. Many of
the provisioning fields can be autogenerated with reasonable, non-conflicting
defaults. When you use form()
, there must not be an existing partition in
range with the same network parameters (LowpanIdentity
) or else the operation
will fail.
The LowpanProvisioningParams
object passed to form()
may be underspecified:
fields in LowpanInterface
that aren't specified will be filled in with
reasonable, non-conflicting defaults. If a LowpanCredential
isn't in the
provision, a new one will be securely generated.
Call the form()
method of class LowpanInterface
with the provisioning
parameters (LowpanProvisioningParams
) to create the network.
Kotlin
@Throws(LowpanException::class) private fun formNetwork(lowpanInterface: LowpanInterface) { /* 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("MyNetwork") .build() /* Not specifying a LowpanCredential here tells “form()” * that we want the interface to generate the primary (master) key * for us. */ val provision = LowpanProvisioningParams.Builder() .setLowpanIdentity(identity) .build() lowpanInterface.form(provision) }
Java
private void formNetwork(LowpanInterface lowpanInterface) throws LowpanException { /* 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. */ final LowpanIdentity identity = new LowpanIdentity.Builder() .setName("MyNetwork") .build(); /* Not specifying a LowpanCredential here tells “form()” * that we want the interface to generate the primary (master) key * for us. */ final LowpanProvisioningParams provision = new LowpanProvisioningParams.Builder() .setLowpanIdentity(identity) .build(); lowpanInterface.form(provision); }
Monitoring connectivity and state changes
Attach a LowpanInterface.Callback
to listen for state changes on a specific
interface. The onLowpanIdentityChanged()
method reports a successful attempt
to create or join a network. If an
error occurs during this process, it is reported through the
onProvisionException()
method.
You can use onStateChanged()
to react to low-level events from the hardware,
such as when an interface is enabled or a fault has occurred and the
LowpanInterface
needs to be reset.
Kotlin
class HomeActivity : Activity() { private var lowpanInterface: LowpanInterface? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... lowpanInterface?.registerCallback(callback) } override fun onDestroy() { super.onDestroy() lowpanInterface?.unregisterCallback(callback) ... } private val callback = object : LowpanInterface.Callback() { override fun onStateChanged(state: Int) { /* Handle interface state changes. */ } override fun onLowpanIdentityChanged(identity: LowpanIdentity?) { /* Form, join, or leave completed successfully. */ } override fun onProvisionException(exception: Exception?) { /* An error occurred during form or join. */ } } }
Java
public class HomeActivity extends Activity { LowpanInterface lowpanInterface; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... lowpanInterface.registerCallback(callback); } @Override protected void onDestroy() { super.onDestroy(); lowpanInterface.unregisterCallback(callback); ... } private LowpanInterface.Callback callback = new LowpanInterface.Callback() { @Override public void onStateChanged(int state) { /* Handle interface state changes. */ } @Override public void onLowpanIdentityChanged(LowpanIdentity identity) { /* Form, join, or leave completed successfully. */ } @Override public void onProvisionException(Exception exception) { /* An error occurred during form or join. */ } }; }
Command line reference
During development and testing, you can use the lowpanctl
command line tool to
perform LoWPAN operations on registered interfaces and verify status:
lowpanctl [options] command
Use an adb
shell to access this tool.
The table below lists the supported commands and explains their meaning and usage.
Global Options | Description |
---|---|
{-I | --interface} interface |
Target LoWPAN interface (e.g. wpan1 ). If this option is
omitted, commands will operate on the default interface.
|
Interface Commands | Description |
list |
List the available LoWPAN interfaces on the device. |
status |
Display information about the LoWPAN interface, such as the IP address and current network parameters. |
enable |
Enable the LoWPAN interface. This will re-attach to the current network
if the network unless the parameters were cleared with leave .
|
disable |
Disable the LoWPAN interface. This will detach from the current network and remember the network parameters. |
reset |
Send a reset command to the LoWPAN interface. |
Network Options | Description |
--name network_name |
Name of the LoWPAN network. |
{-p | --panid} panid |
PANID to associate with the network. |
{-c | --channel} channel |
Wireless channel to use for the network. |
{-x | --xpanid} xpanid |
XPANID to associate with the network. |
{-k | --master-key} master_key |
Primary (master ) key to use as the network credential.
|
Network Commands | Description |
scan |
Initiate a scan for nearby LoWPAN networks and list the results. |
form [network_options] |
Create a new LoWPAN network. |
join [network_options] |
Join an existing LoWPAN network. Will return an error if the network does not exist or the supplied credentials are invalid for that network. |
attach [network_options] |
Provision the LoWPAN interface with the given network info. |
leave |
Detach from the current LoWPAN network and forget the network parameters. |
show-credential |
Display the network credential for the currently attached network. |
The following example demonstrates using lowpanctl
to form a new network and
verify that the interface is attached:
$ adb shell lowpanctl list wpan1 $ adb shell lowpanctl -I wpan1 status wpan1 offline (detached) UP $ adb shell lowpanctl -I wpan1 form --name testnetwork Forming Name:testnetwork Formed. $ adb shell lowpanctl -I wpan1 status wpan1 attached (leader) UP CONNECTED COMMISSIONED Name:testnetwork, PANID:0x1337, Channel:25 fdcb:2c9e:2fb8:0:ac7a:efe9:1b0c:d40a/64 fe80::d097:3908:99c1:c647/64