fbpx
Black Friday Sale! Get $150+ off Neosensory Buzz + Free US Shipping. Use code THANKS at checkout.

Neosensory SDK for Android example project

While Android™ has a slightly steeper learning curve than working in Arduino, once you have the basics down, the Neosensory SDK for Android is a breeze to work with. It opens up an infinitely deep world of possibilities without the need for additional hardware. By building on top of Martijn van Welie’s BLESSED Bluetooth Low Energy library, we’ve streamlined the process of connecting and transmitting to Neosensory Buzz wristbands from Android.

To learn more about the Neosensory SDK for Android and download everything you need for this walkthrough post, check out the Neosensory SDK for Android first.

About this walkthrough

The following walkthrough is geared toward people with some coding experience but little-to-no experience working with Android. The example app barely scratches the surface of what Android can accomplish, but it should get you up to speed quickly with building basic applications that can control Buzz. If you’ve never touched Android before, you might first want to go through this official getting started tutorial as a prerequisite to this walkthrough; also, Google maintains a great set of developer guides. Otherwise, if you have substantive Android experience, reviewing the code directly will probably be sufficient. 

The Android SDK takes the form of an example Android project and accompanying Android library. The example app in the project is minimalistic and comprises of several basic on-screen UI components (buttons and text fields) that:

  1. allow a user to scan and connect to the first Buzz wristband found in the environment
  2. launch a thread that transmits a looping vibratory pattern
  3. display the command line interface (CLI) messages sent by Buzz back to the Android device 
  4. disconnect and reconnect to Buzz

Adding the library to a project

The example app already has the module included, but for your own project, JitPack (a library distribution system for Android and Java virtual machine) makes this incredibly easy. Just use our repository’s page on JitPack to add the module dependency in your “project” and “app” build.gradle files. More specific instructions are on the JitPack page. Upon editing the .gradle files, Android studio will detect the change and run a Gradle sync to automatically pull the library and other required dependencies into your project.

Setting permissions in AndroidManifest.xml

Using Bluetooth in Android requires both Bluetooth and location access. See this article for more on why location permissions are needed. To add the permissions, we apply the following lines in AndroidManifest.xml right below the <manifest> tag. 

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION_LOCATION" />

You can view the AndroidManifest.xml file in the example app for more detail.

Creating UI Components

For this example app, we work with a basic Android Activity – the simplest kind of Android app with which a user can directly interact. User interface (UI) components like clickable buttons and text views are defined in the app’s activity_main.xml file. Android Studio has a built-in visual editor that can assist in adding components. Here is an example of a button component:

<Button
   android:id="@+id/pattern_button"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_marginTop="14dp"
   android:text="Run Vibration Pattern"
   app:layout_constraintEnd_toEndOf="parent"
   app:layout_constraintHorizontal_bias="0.5"
   app:layout_constraintStart_toStartOf="parent"
   app:layout_constraintTop_toBottomOf="@+id/connection_button" />

The “id” field is used for accessing and manipulating the button from the main activity file – where all of the app’s “action” occurs. The other fields establish where it sits on the screen, the text on the button, etc. All of these properties can be dynamically changed on the fly as the app runs as we’ll later see.

Initializations

Now we get to where the real action begins: the main activity. In this project, the entire application logic is contained within the class MainActivity.

First, we define some constants and variables that we will interact with throughout the activity:

 // set string for filtering output for this activity in Logcat
  private final String TAG = MainActivity.class.getSimpleName();

For debugging the application, we create a string called TAG. When we run our application in Android studio from either a physically attached or emulated device, we can view various logging messages from the device in Android Studio’s logcat window. We can supply this string when printing log messages to help filter out other irrelevant messages from the device in logcat. 

  // UI Components
  private TextView neoCliOutput;
  private TextView neoCliHeader;
  private Button neoConnectButton;
  private Button neoVibrateButton;

Here, we create variables for our UI elements. We have 2 TextViews: neoCliHeader is for displaying a static header message on the app’s screen, and neoCliOutput is for printing the output from the Buzz’s command line interface (CLI). We also have 2 Buttons: neoConnectButton is for performing connecting, disconnecting, and reconnecting, and neoVibrateButton is for toggling our looping vibration pattern on/off. Later, we will connect these variables to the UI components that we defined earlier in the app’s activity_main.xml file.

  // Constants
  private static final int ACCESS_LOCATION_REQUEST = 2;
  private static final int NUM_MOTORS = 4;

Here we define two constants that we’ll be using later: ACCESS_LOCATION_REQUEST is a user-defined request code that will be used in the process of obtaining location permissions that are required for our application to use Bluetooth.  NUM_MOTORS is the number of vibration elements on our device: 4 in the case of a Neosensory Buzz.

  // Access the library to leverage the Neosensory API
  private NeosensoryBlessed blessedNeo = null;

blessedNeo is an instance of our SDK’s library, which is used to help facilitate connecting to Buzz over Bluetooth and transmitting API calls.

  // Variable to track whether or not the wristband should be vibrating
  private static boolean vibrating = false;
  private static boolean disconnectRequested =
      false; // used for requesting a disconnect within our thread

Vibrating is a variable that we will set depending on whether or not we want to send our looping custom vibration pattern to Buzz.

disconnectRequested is a variable that we need to ensure our vibrating pattern terminates properly if we disconnect from Buzz.

  Runnable vibratingPattern;
  Thread vibratingPatternThread;

The last two variables, vibratingPattern and vibratingPatternThread will be used for launching a thread to transmit our vibrating pattern in a loop.

Off to the races

Now we’re ready to dig into MainActivity.java, where all the real action for our app occurs.

OnCreate()

Now that we’ve defined all of our constants and variables that will be shared throughout the app, it’s time to spring into action. The entry point for our app is a callback method called onCreate(). onCreate() is a special method that is given to us in Android – it’s not a name we came up with. Beyond onCreate(), you can read more about an Activity’s built-in callback methods here

@Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 // Get a lock on on the UI components (2 Textviews and 2 Buttons)
 setContentView(R.layout.activity_main);
 neoCliOutput = (TextView) findViewById(R.id.cli_response);
 neoCliHeader = (TextView) findViewById(R.id.cli_header);
 neoVibrateButton = (Button) findViewById(R.id.pattern_button);
 neoConnectButton = (Button) findViewById(R.id.connection_button);

 displayInitialUI();
 NeosensoryBlessed.requestBluetoothOn(this);
 if (checkLocationPermissions()) {
   displayInitConnectButton();
 } // Else, this function will have the system request permissions and handle displaying the
   // button in the callback onRequestPermissionsResult

 // Create the vibrating pattern thread (but don't start it yet)
 vibratingPattern = new VibratingPattern();
}

The @Override annotation means we will be changing the default behavior of onCreate(). 

super.onCreate(savedInstanceState);

Is a required call to make the activity work properly. A good explanation of what exactly it does can be found here. The short version is that onCreate() has a bunch of other necessary code in it – in conjunction with @Override, we ensure this code will be run.

 setContentView(R.layout.activity_main);
 neoCliOutput = (TextView) findViewById(R.id.cli_response);
 neoCliHeader = (TextView) findViewById(R.id.cli_header);
 neoVibrateButton = (Button) findViewById(R.id.pattern_button);
 neoConnectButton = (Button) findViewById(R.id.connection_button);

Here we first tell the Activity to use the layout we defined in our activity_main.xml file for display. We then connect our UI element variables to the UI components we defined in our activity_main.xml file. This enables us to programmatically control them from our Activity.

displayInitialUI();

This method uses the UI variables we set above to set the initial display that should be presented to the user when they launch the app. We’ll delve into this shortly. 

 NeosensoryBlessed.requestBluetoothOn(this);

Then we use our Android library to have the app request Android turn Bluetooth on.

 if (checkLocationPermissions()) {
   displayInitConnectButton();
 } // Else, this function will have the system request permissions and handle displaying the
   // button in the callback onRequestPermissionsResult

Here, we call the method checkLocationPermissions() to see if we have the needed location permissions to utilize Bluetooth. If we do, we call displayInitConnectButton() to update our UI to now show a button that lets us try to connect to a buzz. If we don’t have permission, under the hood, checkLocationPermissions() will attempt to obtain the needed permissions and then call displayInitConnectButton() if it is able to obtain the permissions.

 // Create the vibrating pattern thread (but don't start it yet)
 vibratingPattern = new VibratingPattern();

Last, we create a custom Runnable (a class that can be used for threads) to run our vibrating pattern, but we will not create a thread for it until later – once we are connected to a Buzz and have given one of our UI buttons the ability to start it.

displayReconnectUI()

Before diving into displayInitialUI(), which gets called in onCreate(), let’s first take a look at displayReconnectUI(), which gets called within displayInitialUI(). This method illustrates how we can manipulate UI components that are defined in our activity_main.xml file:

private void displayReconnectUI() {
 neoCliOutput.setVisibility(View.INVISIBLE);
 neoCliHeader.setVisibility(View.INVISIBLE);
 neoVibrateButton.setVisibility(View.INVISIBLE);
 neoVibrateButton.setClickable(false);
 neoVibrateButton.setText(
     "Start Vibration Pattern"); // Vibration stops on disconnect so reset the button text
 neoConnectButton.setText("Scan and Connect to Neosensory Buzz");
 neoConnectButton.setOnClickListener(
     new View.OnClickListener() {
       public void onClick(View v) {
         blessedNeo.attemptNeoReconnect();
         toastMessage("Attempting to reconnect. This may take a few seconds.");
       }
     });
}

First, we set our two TextViews neoCliOutput and neoCliHeader, and Button neoVibrateButton to be invisible (and the Button not clickable). We also set the neoVibrateButton and neoConnectButton text to their initial states. Last, we set the action of what happens when the user presses the neoConnectButton: we tell our library to attempt to reconnect to a Buzz and provide a little pop-up message indicating that this may take several seconds.

displayInitialUI()

This is the first method defined by us that is called within onCreate(). First, reuse code from displayReconnectUI() to initialize our UI components. We then customize our Button neoVibrateButton to create a toggle depending on whether or not our vibration thread is running. We use the variable, vibrating, which we created earlier to track this state. 

private void displayInitialUI() {
 displayReconnectUI();
 neoVibrateButton.setOnClickListener(
     new View.OnClickListener() {
       public void onClick(View v) {
         if (!vibrating) {
           blessedNeo.pauseDeviceAlgorithm();
           neoVibrateButton.setText("Stop Vibration Pattern");
           vibrating = true;
           // run the vibrating pattern loop
           vibratingPatternThread = new Thread(vibratingPattern);
           vibratingPatternThread.start();
         } else {
           neoVibrateButton.setText("Start Vibration Pattern");
           vibrating = false;
           blessedNeo.resumeDeviceAlgorithm();
         }
       }
     });
}

If we’re currently not causing Buzz to vibrate (which we expect), we set the neoVibrateButton action to:

  1. pause the algorithm running on our Neosensory Buzz with: blessedNeo.pauseDeviceAlgorithm();
  2. set the text of this button with  neoVibrateButton.setText(“Stop Vibration Pattern”); to indicate to the user that pressing this button again (while vibrating) will stop the vibration pattern.
  3. set our variable vibrating to true, which will be used for tracking our vibration state in other parts of our app
  4. create and launch a new thread called vibratingPatternThread of type vibratingPattern. We need to be careful when we create new instances using new, so that we don’t accidentally keep creating them without destroying them (unless this is being done on purpose). Doing so can lead to memory and other resource leaks that can cause all sorts of nasty effects like the app to crash or the operating system to become slow. In the case of vibratingPatternThread, you’ll see shortly that we make sure that the instance is destroyed every time we stop the vibration.

While not expected behavior, if we are currently vibrating, we make sure:

  1. after pressing this button, while the Buzz is not vibrating, that pressing it again will start the Buzz vibrating again: neoVibrateButton.setText(“Start Vibration Pattern”);
  2. to set our variable vibrating to true. Doing so, as we’ll see shortly, will cause our vibratingPatternThread to terminate and destroy itself.
  3. to command the Buzz to resume its normal algorithm onboard algorithm operation.

checkLocationPermissions()

After we’ve initialized our UI in onCreate(), it’s now time to make sure we have the necessary permissions for Android to let us work with Bluetooth. For better (for the user) or for worse (for the developer), we have to jump through some hoops to make sure the user of the app is aware we’re trying to use Bluetooth. As mentioned earlier in our section on AndroidManifest.xml, we need location permissions. There are two types: coarse and fine, and we’ll need one or the other depending on the user’s operating system version:

 private boolean checkLocationPermissions() {
   int targetSdkVersion = getApplicationInfo().targetSdkVersion;
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
       && targetSdkVersion >= Build.VERSION_CODES.Q) {
     if (getApplicationContext().checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
         != PackageManager.PERMISSION_GRANTED) {
       requestPermissions(
           new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, ACCESS_LOCATION_REQUEST);
       return false;
     } else {
       return true;
     }
   } else {
     if (getApplicationContext().checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
         != PackageManager.PERMISSION_GRANTED) {
       requestPermissions(
           new String[] {Manifest.permission.ACCESS_COARSE_LOCATION}, ACCESS_LOCATION_REQUEST);
       return false;
     } else {
       return true;
     }
   }
 }

The logic here is as follows:

If we are running Android version Q or higher (the first `if` statement), we need fine location access permissions (the second `if` statement). We formally access this permission by calling getApplicationContext().checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION). 

If the response is not equal to PackageManager.PERMISSION_GRANTED (i.e. we don’t have the needed permissions), then we need to request the permission with the following call: requestPermissions(

           new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, ACCESS_LOCATION_REQUEST); The first argument is a list of permissions to request as a String array that allows for requesting more than one permission. We just need the fine location permission in this case. The second argument is a request code that we defined in our initializations above. 

When we make this call, this kicks off a background process that asks the user to give their permission. As such, at the time we make this call, we don’t currently have permissions and we return false from this method. 

If we do have the permission already, we just return true from this method. The second half of this method is the same exact logic but for dealing with versions lower than Android Q and instead requesting coarse location access. 

Back in onCreate(), if we already have permissions, we can go ahead and let the user see our button to scan and connect to a Buzz. If not, then we need to wait for the requestPermissions() call to return its results to us. When the process finishes, the following callback Method, onRequestPermissionsResult() gets called by the system:

 @Override
 public void onRequestPermissionsResult(
     int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
   if ((requestCode == ACCESS_LOCATION_REQUEST)
       && (grantResults.length > 0)
       && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
     displayInitConnectButton();
   } else {
     toastMessage("Unable to obtain location permissions, which are required to use Bluetooth.");
     super.onRequestPermissionsResult(requestCode, permissions, grantResults);
   }
 }
}

Similar to onCreate(), we use @Override to indicate we are changing the default behaviour of this method. As it is possible to call requestPermissions() multiple times for different reasons, The variable we created earlier, ACCESS_LOCATION_REQUEST, is used to check that the callback is indeed the result from the request we had earlier sent.

If we’ve successfully obtained permission, here is where we call displayInitConnectButton() like we do in  onCreate() if we already had the needed permission. If we failed to get the needed permission (e.g. if the user decides to not grant permissions when the app asks), we display a pop-up message indicating this.

displayInitConnectButton() and initBluetoothHandler()

Now that we have all of the needed permissions out of the way, we are now able to let the user try to connect to a nearby Buzz, by calling initBluetoothHandler(). In the example app, we tie this to our Button, neoConnectButton, but we also could have just called this automatically as soon as we obtained permissions rather than in a button action. Here, we let the button become clickable, visible, and have it call initBluetoothHandler() when clicked. As we’ll see shortly, we only let neoConnectButton do this once in the app to avoid creating multiple instances of our Bluetooth class. Subsequently, neoConnectButton  is used for disconnecting and reconnecting, but never for attempting to establish an initial connection. 

private void displayInitConnectButton() {
 // Display the connect button and create the Bluetooth Handler if so
 neoConnectButton.setClickable(true);
 neoConnectButton.setVisibility(View.VISIBLE);
 neoConnectButton.setOnClickListener(
     new View.OnClickListener() {
       public void onClick(View v) {
         initBluetoothHandler();
       }
     });
}

initBluetoothHandler() is responsible for:

  1. creating the initial instance of our Bluetooth class, which attempts to scan and connect to a Buzz device when its created. NeosensoryBlessed.getInstance(getApplicationContext(), new String[] {“Buzz”}, false); creates an instance of our class that upon creation scans for and attempts to connect to the first Bluetooth Low Energy device in the environment with “Buzz” in its name. The second parameter, set to false, means that if the device becomes disconnected, we will not attempt to automatically reconnect.
  2. setting up the BroadcastReceiver, which is used for sending changes in connection state and command line interface messages from the instance of our Bluetooth class. The receiver has a callback method anytime we obtain/lose access to a Buzz’s connection/command line interface (CLI) or if we receive a CLI message from the Buzz: 
private void initBluetoothHandler() {
 // Create an instance of the Bluetooth handler. This uses the constructor that will search for
 // and connect to the first available device with "Buzz" in its name. To connect to a specific
 // device with a specific address, you can use the following pattern:  blessedNeo =
 // NeosensoryBlessed.getInstance(getApplicationContext(), <address> e.g."EB:CA:85:38:19:1D",
 // false);
 blessedNeo =
     NeosensoryBlessed.getInstance(getApplicationContext(), new String[] {"Buzz"}, false);
 // register receivers so that NeosensoryBlessed can pass relevant messages and state changes to MainActivity
 registerReceiver(BlessedReceiver, new IntentFilter("BlessedBroadcast"));
}

BlessedReceiver

For our app, we need to know when we’ve successfully connected to a Buzz and be able to act on that. This is where our BroadcastReceiver BlessedReceiver comes into play. BlessedReceiver uses the callback  onReceive() anytime a CLI becomes ready or unavailable (a state change occurs), a CLI output is received from Buzz, or if a Buzz becomes connected or disconnected.  In this app, we also whether or not the CLI is ready as a proxy for whether or not we’re connected or disconnected from a Buzz: 

// A Broadcast Receiver is responsible for conveying important messages/information from our
// NeosensoryBlessed instance. There are 3 types of messages we can receive:
//
// 1. "com.neosensory.neosensoryblessed.CliReadiness": conveys a change in state for whether or
// not a connected Buzz is ready to accept commands over its command line interface. Note: If the
// CLI is ready, then it is currently a prerequisite that a compliant device is connected.
//
// 2. "com.neosensory.neosensoryblessed.ConnectedState": conveys a change in state for whether or
// not we're connected to a device. True == connected, False == not connected. In this example, we
// don't actually need this, because we can use the CLI's readiness by proxy.
//
// 3. "com.neosensory.neosensoryblessed.CliMessage": conveys a message sent to Android from a
// connected Neosensory device's command line interface
private final BroadcastReceiver BlessedReceiver =
   new BroadcastReceiver() {
     @Override
     public void onReceive(Context context, Intent intent) {
       if (intent.hasExtra("com.neosensory.neosensoryblessed.CliReadiness")) {
         // Check the message from NeosensoryBlessed to see if a Neosensory Command Line
         // Interface
         // has become ready to accept commands
         // Prior to calling other API commands we need to accept the Neosensory API ToS
         if (intent.getBooleanExtra("com.neosensory.neosensoryblessed.CliReadiness", false)) {
           // request developer level access to the connected Neosensory device
           blessedNeo.sendDeveloperAPIAuth();
           // sendDeveloperAPIAuth() will then transmit a message back requiring an explicit
           // acceptance of Neosensory's Terms of Service located at
           // https://neosensory.com/legal/dev-terms-service/
           blessedNeo.acceptApiTerms();
           Log.i(TAG, String.format("state message: %s", blessedNeo.getNeoCliResponse()));
           // Assuming successful authorization, set up a button to run the vibrating pattern
           // thread above
           displayVibrateButton();
           displayDisconnectUI();
         } else {
           displayReconnectUI();
         }
       }

       if (intent.hasExtra("com.neosensory.neosensoryblessed.CliMessage")) {
         String notification_value =
             intent.getStringExtra("com.neosensory.neosensoryblessed.CliMessage");
         neoCliOutput.setText(notification_value);
       }

       if (intent.hasExtra("com.neosensory.neosensoryblessed.ConnectedState")) {
         if (intent.getBooleanExtra("com.neosensory.neosensoryblessed.ConnectedState", false)) {
           Log.i(TAG, "Connected to Buzz");
         } else {
           Log.i(TAG, "Disconnected from Buzz");
         }
       }
     }
   };

Here, when onReceive() is called, we call blessedNeo.getNeoCliReady() to see if a CLI has become available or unavailable. 

If a CLI becomes available (meaning we’ve successfully connected to a Buzz and can now send it commands), we then need to request developer access (blessedNeo.sendDeveloperAPIAuth();) to Buzz and accept the Neosensory developer terms of service (blessedNeo.sendDeveloperAPIAuth();). Once we’ve done this, you will be able to start sending commands to Buzz – see the docs for a full list of commands. As an example debug, we call  Log.i(TAG, String.format(“state message: %s”, blessedNeo.getNeoCliResponse())); to send the latest CLI message over Logcat. 

We then update our onscreen UI to enable a user to: 

(i) start sending custom vibration commands to Buzz, 

(ii) disconnect from Buzz, and 

(iii) see printed CLI output to the screen. 

At this point, we will skip over the implementation details of displayVibrateButton() and displayDisconnectUI() as the reader should now be familiar with how UI elements can programmatically be manipulated based on earlier examples.

If a CLI becomes unavailable, we take this to mean we’ve disconnected (either by pressing the disconnect button or losing connection for some other reason). If this occurs, we display our reconnect UI.

Similar to how we check for whether a CLI has become ready or not, we can check for if we’ve received a message from the CLI (here we just set the message to a string value in a TextView) or if the device has become connected or disconnected.

VibratingPattern

Now we get to where the fun happens. We create a class called VibratingPattern, which implements a Runnable – an interface that provides a run() method for use in a thread. A thread is a process that runs independent of the rest of the code in our Activity. In Android, any process that shouldn’t block key elements of our Activity should be put on a thread or AsyncTask. In our app, we don’t want our looping vibrating pattern to block the user from interacting with the UI components.

Launching the thread is called by pressing our neoVibrateButton whose launch and thread termination procedures are set in displayInitialUI(), which we reviewed earlier. 

To recap, in displayInitialUI(), if we’re currently set to not vibrating (vibrating == false), clicking the button will:

(i) call blessedNeo.pauseDeviceAlgorithm(), to pause the algorithm running on Buzz and enable it to receive custom vibration frames, 

(ii) set vibrating = true, and finally 

(iii) create and launch our VibratingPattern thread, which starts its run() method. If we are currently vibrating (vibrating == true), we set vibrating = false, which as we’ll see below, causes the thread to terminate. 

// Create a Runnable (thread) to send a repeating vibrating pattern. Should terminate if
// the variable `vibrating` is False
class VibratingPattern implements Runnable {
 private int minVibration = 40;
 private int currentVibration = minVibration;

 public void run() {
   // loop until the thread is interrupted
   int motorID = 0;
   while (!Thread.currentThread().isInterrupted() && vibrating) {
     try {
       Thread.sleep(150);
       int[] motorPattern = new int[4];
       motorPattern[motorID] = currentVibration;
       blessedNeo.vibrateMotors(motorPattern);
       motorID = (motorID + 1) % NUM_MOTORS;
       currentVibration = (currentVibration + 1) % NeosensoryBlessed.MAX_VIBRATION_AMP;
       if (currentVibration == 0) {
         currentVibration = minVibration;
       }
     } catch (InterruptedException e) {
       blessedNeo.stopMotors();
       blessedNeo.resumeDeviceAlgorithm();
       Log.i(TAG, "Interrupted thread");
       e.printStackTrace();
     }
   }
   if (disconnectRequested) {
     Log.i(TAG, "Disconnect requested while thread active");
     blessedNeo.stopMotors();
     blessedNeo.resumeDeviceAlgorithm();
     // When disconnecting: it is possible for the device to process the disconnection request
     // prior to processing the request to resume the onboard algorithm, which causes the last
     // sent motor command to "stick"
     try {
       Thread.sleep(200);
     } catch (InterruptedException e) {
       e.printStackTrace();
     }
     blessedNeo.disconnectNeoDevice();
     disconnectRequested = false;
   }
 }
}

First, we create two variables: minVibration and currentVibration. Buzz accepts 8 bits of motor vibration levels between 0 (completely off) and 255 (completely on).  For our custom pattern, we set the minVibration to 40, and we initialize currentVibration to be minVibration.

Next, we define our method, run(), which gets called when we create an instance of our class and launch it on a thread. We start by creating a while loop that will not terminate until either the system creates an interruption or if our variable vibrating gets set to false. Because we want to limit how quickly we send motor control frames to Buzz, we tell the thread to sleep for 150 milliseconds everytime we loop by calling Thread.sleep(150). If we try to send frames too quickly (more often than about 16 milliseconds), they will get dropped. Calling Thread.sleep() requires us to embed it within a try/catch structure in case the thread gets interrupted.

Neosensory Buzz has 4 motors. For our custom looping pattern, we will cycle through having only one motor active for each frame, and have the vibration strength grow with each frame as well. We send a frame of motor encodings as an integer array, where each element corresponds to a motor vibration level. We create and initialize our array by calling int[] motorPattern = new int[4];, which will default all of the values to 0 (motors off). Once our array contains the vibration levels we want for each motor, we simply transmit it to Buzz by calling blessedNeo.vibrateMotors(motorPattern). Buzz will vibrate indefinitely based on the last received frame.

Now that our pattern is running and looping, there are 3 ways it can terminate:

(i) We can explicitly terminate it from our app UI, by pressing a button that sets vibrating = false;

(ii) An interrupt could be called by the system

(iii) We disconnect from Buzz either through closing the app or pressing our disconnect button. When this happens, we want the vibrations to stop instead of being stuck in their most recent state.

For (i) this request is initiated by our neoVibrateButton, which sets vibrating = false and takes care of telling the Buzz to resume its normal operation. As soon as the while loop picks up that vibrating = false, we’ll exit the while loop and jump to the end of run(), and the thread will be terminated.

For (ii), if the thread is interrupted, we simply call blessedNeo.stopMotors() and  blessedNeo.resumeDeviceAlgorithm() to tell the Buzz to resume its normal operation.

For (iii), if we want to manually disconnect from Buzz, we actually need to make the disconnection request from within the thread if we want to ensure the motors don’t get stuck on. The reason is, if we try to call blessedNeo.stopMotors() right before blessedNeo.disconnectNeoDevice(), the disconnection request under the hood actually gets processed much more quickly before Buzz has a chance to turn its motors off. Therefore, we need to have a way to wait for blessedNeo.stopMotors() to finish processing. The easiest way to accomplish this is by using a sleep for a few hundred milliseconds (i.e. Thread.sleep(200)), which can only be called on a thread.

onDestroy()

Finally, we need to perform some cleanup when we quit our application. In the Android Activity lifecycle, this is done within onDestroy().

@Override
protected void onDestroy() {
 super.onDestroy();
 unregisterReceiver(BlessedReceiver);
 if (vibrating) {
   vibrating = false;
   disconnectRequested = true;
 }
 blessedNeo = null;
 vibratingPatternThread = null;
}

Similar to onCreate(), we need to call super.onDestroy() to ensure that other important pre-existing code gets run since we’re using @Override to add our own custom code. Importantly, we need to unregister the BroadcastReceiver. For good measure, though probably not required, we set vibrating = false, disconnectRequested = true, and null our instances to make sure that our thread terminates and the garbage gets collected.

Where to go from here

And that’s it! As you can see, working with Android requires a bit more legwork to get things done than working with other platforms (if you’d like to start with a quicker project, check out the  Neosensory Arduino SDK). But remember, once you’re up and running, you have access to a powerful computing device that’s packed with sensors and an Internet connection used by billions of people around the world. 

We’ll post more example applications in the coming weeks. If you run into any issues or have suggestions, feedback, or comments, feel free to reach us at developers@neosensory.com or join our Neosensory developer Slack. (Also, please consider contributing to our project on GitHub: the initial release of this SDK and walkthrough are put together by our CTO, who is an Android fan but not an Android expert.)

We want to see what you create. Drop us a line via email or Slack to share. We’ll feature select projects on our site in the near future.

Last, be sure to sign up on our developer list for important announcements regarding our updates to our SDKs and APIs.

By Scott Novich, Co-founder & CTO

Android is a trademark of Google LLC

Subscribe to our newsletter

Get the latest Buzz news, tips, and special offers.

Thanks for signing up. You must confirm your email address.
Please check your email and follow the instructions.

We respect your privacy. Your information is safe and will not be shared.

Don't miss out. Subscribe today.
×
×
×