Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tpbot/movement service #249

Merged
merged 24 commits into from Jan 9, 2017
Merged

Conversation

danybony
Copy link
Contributor

@danybony danybony commented Jan 5, 2017

Problem

We need the Bot section of our Android app to communicate with the Arduino movement unit.

Solution

First of all we need to detect when a USB device is connected. This is done through a BroadcastReceiver declared in the manifest with the action android.hardware.usb.action.USB_DEVICE_ATTACHED.
It will be triggered only by devices with one of the vendor IDs defined in devices_filter.xml.
At the moment I only added three common vendor IDs generally associated with different versions of Arduino boards. If we will see that some other boards are not recognised, we can list the connected devices' vendor IDs from the BotActivity, and add these to the list later.

Once we detect a new matching device, the BroadcastReceiver starts an Android Service responsible of this communication.
The service (MovementService) does the following:

  • requests the permission to manage the USB-connected device
  • initialises the USB serial connection
  • waits for an activity/other service to bind and forwards all the received messages to the serial connection

In this PR I made the BotActivity bind with the service and added a command pad to that activity as well, so that we can control the bot also from the receiver part. This is for debug only and should be removed once we have the Human-Bot network communication done.

@danybony danybony added the TpBot label Jan 5, 2017
@@ -35,6 +50,68 @@ protected void onCreate(Bundle savedInstanceState) {

RecyclerView.Adapter adapter = new DirectionAdapter(LayoutInflater.from(this), Collections.<Direction>emptyList());
directions.setAdapter(adapter);

ControllerView controllerView = Views.findById(this, R.id.bot_debug_controls);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a controller view so that you can send commands straight to the Arduino?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes exactly, as I wrote in the description, this is only for debug purposes, while we don't have the network communication from the human controller


private MovementService movementService;
private boolean bound;
private Handler handler;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am used to have private final Handler handler = new Handler();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now these are not there anymore, moved into the CommandRepeater creation

}
};

private void startRepeatingCommand(String command) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am thinking about encapsulating this command handling in a separate class.
You would have a CommandHandler as a field maybe which has currentCommand inside.
I think it is weird to see String currentCommand inside the Activity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in ae51908, this reduced the duplication of that logic between the two activities

}
}

private Runnable repeatCommand = new Runnable() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in b66e064

private Runnable repeatCommand = new Runnable() {
@Override
public void run() {
sendCommand(BotActivity.this.currentCommand);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 2 lines can maybe extracted into a method named sendRepeatingCommand(String)
What do you think?

android:orientation="vertical"
tools:context="com.novoda.tpbot.human.HumanActivity">

<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

xmlns:android="http://schemas.android.com/apk/res/android" is this redundant?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, they were leftovers from a previous refactor. Removed in 1f1793c

Copy link
Contributor

@frapontillo frapontillo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor changes needed for my eyes' sake (I'm old).

IBinder iBinder) {
MovementService.Binder binder = (MovementService.Binder) iBinder;
movementService = binder.getService();
bound = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to boundToMovementService?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

@Override
public void onServiceDisconnected(ComponentName arg0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arg0? 🤢

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooops 👅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


public void startConnection() {

if (!isSerialStarted) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would invert this to return if isSerialStarted.

usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);

HashMap<String, UsbDevice> usbDevices = usbManager.getDeviceList();
if (!usbDevices.isEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too many nested layers here 👀

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also have a quick return above too. Always try to prefer the positive case when possible 😃


private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please break this down into smaller methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 33eb4f5

toaster.popToast("Port is not open");
}
} else {
Log.d("SERIAL", "Port is null");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extract TAG to constant?

Intent movementService = new Intent(context.getApplicationContext(), MovementService.class);
if (usbConnected(intent)) {
context.startService(movementService);
} else if(usbDisconnected(intent)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Space after if 😄

@@ -32,6 +34,22 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/bot" />

<receiver android:name="com.novoda.tpbot.bot.UsbReceiver">
<intent-filter>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it need to be in this order? Can we group the <intent-filter> together?


HashMap<String, UsbDevice> usbDevices = usbManager.getDeviceList();
if (!usbDevices.isEmpty()) {
boolean keep = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usage wasn't immediately clear when I read this, is there a better name?

}
};

private UsbSerialInterface.UsbReadCallback mCallback = new UsbSerialInterface.UsbReadCallback() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onDataReceievedListener?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. mCallback 😷

@@ -3,7 +3,7 @@
import com.novoda.notils.logger.simple.Log;
import com.novoda.tpbot.Direction;

interface ControllerListener {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is being used by both I would move it to the more generic package. I know that it will move again in the future but it shows our intent clearly when looking at the structure of the app.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 2739722

@@ -15,13 +15,11 @@
public class HumanActivity extends AppCompatActivity {

private static final String LAZERS = String.valueOf(Character.toChars(0x1F4A5));
private static final long COMMAND_REPEAT_DELAY = TimeUnit.MILLISECONDS.toMillis(100);
private static final long COMMAND_FADING_DELAY = TimeUnit.MILLISECONDS.toMillis(100);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is worth moving this internally to the debug view? Likely will be the same whether ever we use it at this point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good point

android:orientation="vertical"
tools:context="com.novoda.tpbot.human.HumanActivity">

<android.support.v7.widget.RecyclerView
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking of replacing this with the DebugView in my branch, it will make the process of building the debug version slightly faster.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do it once this is merged?
So that changes for a single PR are smaller

Copy link
Contributor

@Mecharyry Mecharyry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work on this 👍 Good to see the progress. We should try to extract out some of those if statements to make the usb connection class easier to read.

usbManager.requestPermission(device, pendingIntent);
break;
} else {
connection = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be set to null on each iteration of the loop? I assumed this would happen if we go through all of the usbDevices and we do not find a supporting device?

Copy link
Contributor

@tasomaniac tasomaniac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🎉

@danybony
Copy link
Contributor Author

danybony commented Jan 9, 2017

@frapontillo @Mecharyry all comments addressed (including @tasomaniac ones, but he already approved 👅 )

private static final String SERIAL_TAG = "SERIAL";

private static final String ACTION_USB_PERMISSION = "com.novoda.tpbot.USB_PERMISSION";
private static final List<Integer> SUPPORTED_VENDOR_IDS = Arrays.asList(9025, 10755, 4292); // TODO: read from devices_filter.xml
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this going to be done in a future PR?

Copy link
Contributor

@Mecharyry Mecharyry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job! 🎉

@frapontillo frapontillo merged commit 2a59746 into tpbot-feature-branch Jan 9, 2017
@frapontillo frapontillo deleted the tpbot/movement_service branch January 9, 2017 09:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants