Android Core

Control Sphero using Temperature Sensor in Android

One of the most interesting topic around Android it is how we can connect our smart phones to other devices or smart devices to get information from them or to control them. In this post I want to introduce a new way to use our Android phone and explain how we can use it to control Sphero ball.

Introduction

In this Android project, I want to describe how we can integrate the temperature sensor inside the smart phone to control the Sphero ball color. In other words, I want to change the color ball according to the temperature measured by the smart phone even if the smart phones is in the stand by mode or the Activity is not in the foreground.

This is an interesting project because it can be used to describe some important concept:

and finally, but not less important, how to connect and use Sphero ball with its SDK. What we want to design an app like shown below:

android-sphero

Design the app

Now we know what we want to obtain, we can mix Android features and components to get it. We need a component that monitors the temperature sensor and another one that connect to the Sphero. As said before, we want to make this component working even if the app is not in the foreground or the smart phone is not active. So we need a Service, because this Android component can fulfill our requirements. We need, then,

  • An Activity that is the app UI
  • A Service that monitors the temperature sensor
  • A Service that connects to the ball and control its color

android_sphero_temperature

Looking at the pic below, we can notice that the UI Activity starts two services and listens for events coming from these services, in more details the Activity set up an alarm that is used to start the Temperature Sensor Service so that we won’t drain the battery. The alarm can be configured to start every fixed amount of time. Every time the Temperature Sensor starts it measure the environment temperature using the smart phone sensor and broad cast the value. The UI Activity listens these events and shows the value to the UI, at the same time the Ball Connection Service listens for the same event and as soon as it gets the event, this service calculates the color components (R,G,B) and set the ball color.

Create Temperature Sensor Service: code

Now we have an overview about the main components in our app, we can start coding it. The first element we want to code is the Temperature Sensor service that reads the current temperature. As we know we need a service:

public class SensorService  extends Service implements SensorEventListener {
...
}

we must implement SensorEventListener to listen to the sensor events, then in onStartCommand we register this class as listener:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
     sManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
     sensor = sManager.getDefaultSensor(Sensor.TYPE_AMBIENT_TEMPERATURE);
     sManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
     return Service.START_STICKY;
}

finally when we get notified about the new temperature value we handle it:

@Override
public void onSensorChanged(SensorEvent event) {
   // We get the temperature and notify it to the Activity
   float temp = event.values[0];
   Intent i = new Intent();
   i.setAction(TEMP_BALL_SENSOR);
   i.putExtra("value", temp);
   sendBroadcast(i);

   // stop listener
   if (sManager != null)
         sManager.unregisterListener(this);

   // stop service
   stopSelf();
}

At line 15,we stop the service because we don’t want to read all the times the values to not drain the battery.

Create Ball Connection Service: code

The other service we have to implement is to handle Sphero connection via bluetooth. You can refer to Sphero SDK to have more information. We want to handle the connection in the Android service:

public class BallConnectionService extends Service {
..
}

now in the onStartCommand we start connecting to the Sphero and at the same time we start listening for incoming temperature event (line 8).

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
   if (mySphero == null)
     doConnection();

   IntentFilter rec = new IntentFilter();
   rec.addAction(SensorService.TEMP_BALL_SENSOR);
   registerReceiver(receiver, rec);

   return Service.START_STICKY;
}

in doConnection we make the real connection:

private void doConnection() {

     sendStatus(CONNECTING);
     createNotification("Connecting...");

     RobotProvider.getDefaultProvider().addConnectionListener(new ConnectionListener() {
         @Override
          public void onConnected(Robot robot) {
             Log.d("Temp", "Connected");
             mySphero = (Sphero) robot;
             sendStatus(CONNECTED);
             createNotification("Connected");
          }

          @Override
          public void onConnectionFailed(Robot robot) {
              Log.d("Temp", "Conection failed");
              sendStatus(FAILED);
          }

          @Override
          public void onDisconnected(Robot robot) {
              Log.d("Temp", "Disconnected");
              mySphero = null;
              createNotification("Disconnected!");
          }
      });

      RobotProvider.getDefaultProvider().addDiscoveryListener(new DiscoveryListener() {
          @Override
          public void onBluetoothDisabled() {
               Log.d("Temp", "BT Disabled");
           }

           @Override
           public void discoveryComplete(List<Sphero> spheros) {
               Log.d("Temp", "Found ["+spheros.size()+"]");
           }

           @Override
           public void onFound(List<Sphero> spheros) {
              // Do connection
              Log.d("Temp", "Found ball");
              RobotProvider.getDefaultProvider().connect(spheros.get(0));
           }
        });

        boolean success = RobotProvider.getDefaultProvider().startDiscovery(this);
  }

The code seems complex but it is really simple if you look at it carefully. We start broadcasting the event that we are trying to connect to the Sphero (line 3), then, using Sphere API, we register a listener to know when the connection is established and broadcast a new event that the connection is active, at the end of this method we start discovering if new Sphero in around and ready to connect.

The last part of the service is used for listening to the temperature event and set the color ball:

private BroadcastReceiver receiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        float val = intent.getFloatExtra("value", 0);
        Log.d("Temp", "Received value ["+val+"]");
        if (mySphero != null) {
           // send color to sphero

           int red = (int) (255 * val / RANGE) * (val > 10 ? 1 : 0);
           int green = (int) ( (255 * (RANGE - Math.abs(val)) / RANGE) * (val < 10 ? 0.2 : 1) );
           int blue = (int) (255 * (10 - val) / 10) * (val < 10  ? 1 : 0);

           mySphero.setColor(red, green, blue);
        }
      }
 ;

 Create the Activity

The last step is creating the Activity that controls the UI and starts and stops the service. We provide two action bar buttons: one to start the services and another one to stop them. If we touch the start service, we start the AlarmManager to schedule when to run our service:

PendingIntent pi = createAlarm();
AlarmManager scheduler = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
scheduler.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 60 * 1000, pi);
Intent i1 = new Intent(this, BallConnectionService.class);
startService(i1);

In this simple code, we create a PendingIntent and get a reference to the AlarmManager, finally we schedule the alarm so that the service can be started after a fixed amount of time. (line 3). In createAlarm() method we setup the intent:

private PendingIntent createAlarm() {
    AlarmManager scheduler = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(this,SensorService.class );
    PendingIntent scheduledIntent = PendingIntent.getService(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    return scheduledIntent;
}

Finally, we have to create two receivers that listen for event coming from temperature sensor and ball connection services:

private BroadcastReceiver sensorReceiver = new BroadcastReceiver() {
   @Override
   public void onReceive(Context context, Intent intent) {
       float val = intent.getFloatExtra("value", 0);
       tempView.setText(String.format("%.1f",val));
    }
 };

At line 5, we show the current temperature, while for the ball service we have:

private BroadcastReceiver ballReceiver = new BroadcastReceiver() {
   @Override
   public void onReceive(Context context, Intent intent) {
       int status = intent.getIntExtra("status", -1000);

       Log.d("Temp", "Value Status ["+status+"]");
       if  (status == BallConnectionService.CONNECTING) {
           tempView.startAnimation(pulseAnim);
           Toast.makeText(MyActivity.this, "Connecting...", Toast.LENGTH_SHORT).show();
        }
        else if (status == BallConnectionService.CONNECTED) {
            tempView.clearAnimation();
            Intent i = new Intent(MyActivity.this, SensorService.class);
            startService(i);
            Toast.makeText(MyActivity.this, "Connected", Toast.LENGTH_LONG).show();
        }
        else if (status == BallConnectionService.FAILED) {
            Toast.makeText(MyActivity.this, "Connection failed. Try again pressing start button", Toast.LENGTH_LONG).show();
        }

      }
    };
  • Source code available soon @ github.

Francesco Azzola

He's a senior software engineer with more than 15 yrs old experience in JEE architecture. He's SCEA certified (Sun Certified Enterprise Architect), SCWCD, SCJP. He is an android enthusiast and he has worked for long time in the mobile development field.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button