mavlink / MAVSDK-Java

MAVSDK client for Java.
71 stars 41 forks source link

Can Android connect over serial? #23

Open JonasVautherin opened 5 years ago

JonasVautherin commented 5 years ago

Asked on Slack, copying here for the record:

Hello!

Just wanted to know whats the best way to create a custom android groundstation app with mavsdk? There doesn't seem to be any framework for connecting to a serial connection on android to a telemetry module. I tried modifying the qgroundcontrol app but it isn't java based so it doesn't work for my purposes. I also tried using dronekit but their code is so old, much of it is unsupported by android studio, same story with trying to fork Android Tower.

Thanks in advance!

JonasVautherin commented 5 years ago

MAVSDK-Java will always look like "pure Java" to you, because we hide the C++ stuff :smile:. Once the C++ will be packaged into an Android library, you will just have to do something like:

startMavlink(<connectionUrl>);
Action action = new Action();
action.arm().andThen(action.takeoff()).subscribe();

And the drone will takeoff. With connectionUrl being something like udp://:14540.

Before that (i.e. right now), you will have to run mavsdk_server on your computer (you'll find binaries in the "assets" for different platforms on the release page), and the code will look like:

Action action = new Action(<ipOfYourComputer>);
action.arm().andThen(action.takeoff()).subscribe();

You see here that the first line (that would start mavsdk_server on your phone) is missing, because you run mavsdk_server on your computer instead. And also, you enter an IP in new Action(...), because you need to tell the SDK where to contact mavsdk_server (default is 'localhost').

This means that you can totally develop your app with the current SDK, but your app has to connect to your computer (with <ipOfYourComputer> as described above), and your computer has to connect to the drone (or simulator). When we release mavsdk_server for Android, it will be a matter of adding the startMavlink() line and your app will run completely on your phone :+1:.

Also, note that the Android wrappers are not super mature yet, but they are already quite advanced. So feel free to report bugs and to contribute fixes! Please get started with the java-client example!

LorenzMeier commented 5 years ago

@JonasVautherin The question was specifically around how you get the serial port of Android into an application. So what would help this user is if MAVSDK included the logic to open and read the UART: https://developer.android.com/things/sdk/pio/uart

From previous experience the simplest approach might be to read it in Java, push the payload down to C++ (mavsdk core) and then have the same flow as in C++.

This library kind of does this: https://github.com/chzhong/serial-android

JonasVautherin commented 5 years ago

Right, I missed that, and I was mainly hoping to actually move the discussion here.

I'm not sure we even need to add a new library. The app would definitely need the permission:

<uses-permission android:name="com.google.android.things.permission.USE_PERIPHERAL_IO" />

And then I wouldn't be surprised if mavsdk_server could "just" use it, given it has the right connection_url. Anyway I've been using that in the past, both from Tower or with proprietary software, and I'm sure there is a way.

It is probably not so difficult to try, but for that one would need to run the example first, as described above.

maxwelllwang commented 5 years ago

Would this help? https://github.com/DroidPlanner/usb-serial-for-android/tree/develop/UsbSerialLibrary if i "loaded" the mavsdk_server onboard the android app by just putting the mavsdk C++ library onboard? I could use these series of drivers. Sorry if I am completely misunderstanding the functions I'm still relatively new to this

JonasVautherin commented 5 years ago

The serial connection would be done by mavsdk_server. Currently, that is done in serial_connection there. On Linux we would do something like:

./mavsdk_server serial:///dev/ttyUSB0

And my question would be: could we get that kind of address from Android (in Java), and would that be enough to access it (together with the permission mentioned above)?

I will try to have a look at that ASAP, but I would love your input on it!

dunifi91 commented 4 years ago

Hi

First of all I would like to say it is really cool that there is a Java frontend implementation to MAVSDK 👍. You said:

When we release mavsdk_server for Android, it will be a matter of adding the startMavlink() line and your app will run completely on your phone

Is someone working on it right now and will it come out soon or can it still take a long time? Because I have no idea how to program a serial connection on Android.

JonasVautherin commented 4 years ago

Hello @dunifi91!

I am currently looking into that :slightly_smiling_face:. If everything goes well, it may work in a few days! Otherwise, at least I'll be able to tell you what I tried that doesn't work :laughing:.

dunifi91 commented 4 years ago

Sounds good. I am looking forward to your results 🙂

mirkix commented 4 years ago

Hi @JonasVautherin Jonas, I am interested in using the MAVSDK-Java for an Android App as well. Is there further progress with the mavsdk_server running on Android, or is there any timeline when it will be available? Thanks in advanced!

JonasVautherin commented 4 years ago

Hej! Sorry I've been pretty busy these days. I have a proof of concept running mavsdk_server on Android, I need to wrap it up and push it. And then I'll need help to test the serial stuff. Sorry for the delay, I'll try hard to get that out next week!

mirkix commented 4 years ago

@JonasVautherin Thanks for the fast reply. We can do some testing of the mavsdk_server for Android if the server is available.

JonasVautherin commented 4 years ago

I pushed my changes, it is working (at least for me) with the android example app. Here is what I changed there, with link to the relevant code:

This is not the final version (I'd like to expose it through RxJava, and we should be able to stop mavsdk_server from the app as well), but that's already something.

Why does it concern this issue?

You can pass the connection url to mavsdk_server. Here I pass udp://:14540, but for trying a serial connection, that may be something like serial:///dev/bus/usb/001/002. And probably the app would need some more permissions.

I won't have time to try the serial connection in the next days, so please let me know here if you make tests! Also if the udp connection works.

Note:

The library only supports arm64-v8a (I published mavsdk-server with libmavsdk_server.so here). I hope it's enough for you to test for the moment!

mirkix commented 4 years ago

@JonasVautherin Thanks for pushing your changes. At the moment stuck at mavsdk-server on Linux because we want to use it with ArduPilot.

JonasVautherin commented 4 years ago

@dunifi91: Did you have an opportunity to test if the serial connection "just works" when the path is hardcoded, as per my suggestion above ("something like serial:///dev/bus/usb/001/002")?

Would be helpful :slightly_smiling_face:.

dunifi91 commented 4 years ago

@JonasVautherin Unfortunately i have no experience with serial connection on Android yet. But what i read it's not that straightforward as on a PC. So i think simply hardcoding the bus and adding the usb host permission <uses-feature android:name="android.hardware.usb.host" /> will not be enough. But maybe I'm wrong. I haven't tested it yet. A library like https://github.com/mik3y/usb-serial-for-android could be a solution.

maxwelllwang commented 4 years ago

Just wondering if anyone has had any success using mavsdk android to communicate over mavlink using a telemetry module connected to the usb port?

lbb1993 commented 4 years ago

   int mavsdkServerPort = mavsdkServer.run("serial:///dev/bus/usb/001/002");
    drone = new System(BACKEND_IP_ADDRESS, mavsdkServerPort)

I try to use serial on android,I got this permission error.I have granted the usb permission in my app,but I still got this.Does anybody know how to fix this?

2020-07-17 18:04:19.085 26853-26853/com.leopard.droneclient D/MAVSDK-Server: Running mavsdk_server with connection url: serial:///dev/bus/usb/001/002 2020-07-17 18:04:19.085 26853-26853/com.leopard.droneclient I/Mavsdk: MAVSDK version: 0.28.0 2020-07-17 18:04:19.085 26853-26853/com.leopard.droneclient D/Mavsdk: New: System ID: 0 Comp ID: 0 2020-07-17 18:04:19.088 26853-26853/com.leopard.droneclient I/Mavsdk: Server started 2020-07-17 18:04:19.088 26853-26853/com.leopard.droneclient I/Mavsdk: Server set to listen on 0.0.0.0:60096 2020-07-17 18:04:19.088 26853-26853/com.leopard.droneclient I/Mavsdk: Waiting to discover system on serial:///dev/bus/usb/001/002... 2020-07-17 18:04:19.088 26853-26853/com.leopard.droneclient E/Mavsdk: open failed: Permission denied 2020-07-17 18:04:19.088 26853-26853/com.leopard.droneclient E/Mavsdk: Connection failed: Connection error

JonasVautherin commented 4 years ago

Maybe it cannot be done this way :confused:. One way that I'm pretty sure would work would be to allow to pass a file descriptor to mavsdk_server. So that the Android code would get the file descriptor to access USB, and pass it directly to mavsdk_server. Though that requires a change in C++ in mavsdk_server.

Would you be willing to look into that?

lbb1993 commented 4 years ago

Maybe it cannot be done this way . One way that I'm pretty sure would work would be to allow to pass a file descriptor to mavsdk_server. So that the Android code would get the file descriptor to access USB, and pass it directly to mavsdk_server. Though that requires a change in C++ in mavsdk_server.

Would you be willing to look into that?

Hi,sir.In MAVSDK--src--core-serial_connection.cpp,I can't find anything about android.What should I do to make it support Android? And I used mavsdk-server for android-0.4.0 ,I could not successfully find my virtual Drone by udp://:14540,though they are in same network. But my Ubuntu server can find it.I don't know what is wrong with my program.

lbb1993 commented 4 years ago

I found QGroundControl can support Serial in android,I have try it .Dose QGroundControl use mavsdk-server?

JonasVautherin commented 4 years ago

In MAVSDK--src--core-serial_connection.cpp,I can't find anything about android.

No, it's just creating a serial link, it doesn't have to know about Android. Currently, it opens a file descriptor from the path you give (e.g. if you pass serial:///dev/ttyACM0, it will open a file descriptor for /dev/ttyACM0). Your tests suggest that we can't create a file descriptor on Android like this.

What I believe we should try is the following:

The first step is to add the possibility to pass a file descriptor to serial_connection.cpp (here). The rest will be done in MAVSDK-Java (actually in the mavsdk_server Android library, here).

Does that make sense?

Dose QGroundControl use mavsdk-server?

No, QGC uses its own MAVLink implementation. Though if you look at the sources, I'm pretty sure QGC does something similar (get the file descriptor and uses it to open the serial connection).

I could not successfully find my virtual Drone by udp://:14540

This has nothing to do with serial, so if you have issues using UDP, it should go in another issue :+1:

lbb1993 commented 4 years ago

Wow,thank you very much to reply me so detailedly.I have read your suggestions carefully.Because I am not familiar with Mavsdk-server,and I can read the full QGroundControl code.I think the best way to realize it maybe is to imitate QGC code. All in all,you saved my time.Thank you again~

JonasVautherin commented 4 years ago

I think the best way to realize it maybe is to imitate QGC code.

I personally would look at the Android documentation: just try to get a file descriptor to talk to a USB device. Note: this is done in Java, not in C++ :slightly_smiling_face:. But if you find where this is done in QGC (again, I think it has to be done in Java), then I would be happy to look at that code!

lbb1993 commented 4 years ago

I think the best way to realize it maybe is to imitate QGC code.

I personally would look at the Android documentation: just try to get a file descriptor to talk to a USB device. Note: this is done in Java, not in C++ . But if you find where this is done in QGC (again, I think it has to be done in Java), then I would be happy to look at that code!

I know how QGC open android serial,I am very happy to share this with you .QGC used the old version of https://github.com/mik3y/usb-serial-for-android to open the serial ,The code is in QGCActivity located in android.src.org.mavlink.qgroundcontrol in QGC QT project,QGC call open() in QGCActivity,then use QIODevice to send Mavlink data. Now I find dronekit-android has lots of code I can reuse,though it is very old code .

JonasVautherin commented 4 years ago

Thanks! So I think that you want to get a FileDescriptor, just like the public static int getDeviceHandle(int idA) function does. That's for the Android part.

Now in MAVSDK-C++, you want to extend serial_connection.h, which currently takes a path, to take an int fileDescriptor (e.g. in a new constructor). Then in serial_connection.cpp, you want to use the fileDescriptor passed in the constructor instead of initializing it from the path (i.e. instead of this, for Linux, I don't know for Windows). You see, _fd is short for "file descriptor", and it is an int. That's exactly the fileDescriptor you got from Java above.

You need a way to start mavsdk_server with a file descriptor, too. I would suggest ./mavsdk_server fd://<the_int>. So that when we parse the "connection_url" here, if it is fd://<int> (instead of e.g. serial:///dev/ttyACM0), then it creates a SerialConnection with your new constructor instead of doing this.

How does it sound? :blush:

lbb1993 commented 4 years ago

Thanks! So I think that you want to get a FileDescriptor, just like the public static int getDeviceHandle(int idA) function does. That's for the Android part.

Now in MAVSDK-C++, you want to extend serial_connection.h, which currently takes a path, to take an int fileDescriptor (e.g. in a new constructor). Then in serial_connection.cpp, you want to use the fileDescriptor passed in the constructor instead of initializing it from the path (i.e. instead of this, for Linux, I don't know for Windows). You see, _fd is short for "file descriptor", and it is an int. That's exactly the fileDescriptor you got from Java above.

You need a way to start mavsdk_server with a file descriptor, too. I would suggest ./mavsdk_server fd://<the_int>. So that when we parse the "connection_url" here, if it is fd://<int> (instead of e.g. serial:///dev/ttyACM0), then it creates a SerialConnection with your new constructor instead of doing this.

How does it sound?

Sorry for my late reply.I am not very familiar with c++,but I think your solution is possible.I have decided to use dronekit-android code to realize my app.In dronekit-android ,I can use usb serial to communicate with my drone.You can refer to https://github.com/dronekit/dronekit-android if you want to add some usb serial code.

divyanshupundir commented 3 years ago

Hello, I too have been trying to run MavsdkServer using a serial address, and I have come across a similar problem as @lbb1993 did:

serial:///dev/bus/usb/001/002... 2020-07-17 18:04:19.088 26853-26853/com.leopard.droneclient E/Mavsdk: open failed: Permission denied 2020-07-17 18:04:19.088 26853-26853/com.leopard.droneclient E/Mavsdk: Connection failed: Connection error

On checking the USB device permission: Log.d(TAG, "hasPermission: " + usbManager.hasPermission(usbDevice)); I can see that it returns true.

In Android (using Java) it is quite easy to find the FileDescriptor int:

UsbManager usbManager = (UsbManager) mAppContext.getSystemService(Context.USB_SERVICE); UsbDevice device = usbManager.getDeviceList().get(deviceName); UsbDeviceConnection connection = usbManager.openDevice(device); int fileDescriptor = connection.getFileDescriptor());

Is there any interface provided in the Java library that passes the fileDescriptor to the native code? Or has anyone been able to find a work-around to this?

Thank you in advance.

JonasVautherin commented 3 years ago

Awesome! You would need to pass that file descriptor to the C++ code, over JNI, and then pass it to mavsdk_server. Because mavsdk_server doesn't have an interface for receiving a file descriptor yet, you would need to add that.

I wrote more details in this comment: https://github.com/mavlink/MAVSDK-Java/issues/23#issuecomment-663623634.

It would be amazing if you could have a look at that!

divyanshupundir commented 3 years ago

Sure. I will try to figure it out. I guess I'll have to make the changes that you've suggested in the C++ code and rebuild it using Android NDK tools. Please let me know if this is the part of the C++ code that has been used to generate the libmavsdk_server.so file that is mentioned over here. Or is it this?

JonasVautherin commented 3 years ago

MAVSDK-C++ is built from core. mavsdk_server (and hence libmavsdk_server.so) is built on top of that, in backend. So backend uses core.

This said, before going into cross-compilation and all, I would just edit the core to accept a file descriptor in the serial_connection.cpp (and in the add_any_connection string), as mentioned above. And then build up from there.

divyanshupundir commented 3 years ago

Thanks! It'll take me some time to understand that part of the code and make the edits (I have very little experience with C++ :sweat_smile:). I'll get back to you after that.

divyanshupundir commented 3 years ago

I made the changes in core so that it takes the file descriptor from the command line args and generated the so file for Android arm64. This is how one can use it: serial_fd://file_descriptor_int[:baudrate]

I generate this in the Java code as follows: String systemAddress = "serial_fd://" + connection.getFileDescriptor() + ":" + USB_BAUD_RATE; >> serial_fd://48:57600

But now when I try to run it on my phone, I'm getting this error instead: 2020-10-05 18:38:20.542 6252-6252/app.package E/Mavsdk: tcgetattr failed: Not a typewriter 2020-10-05 18:38:20.542 6252-6252/app_package E/Mavsdk: Connection failed: Connection error

I have logged and cross-checked the fd that I'm passing and it seems to be passing correctly.

The problem is arising at this line. I can't quite figure it out.

JonasVautherin commented 3 years ago

Looking for tcgetattr failed: Not a typewriter, I find:

This code is now used to indicate that an invalid ioctl (input/output control) number was specified in an ioctl system call.

I guess you get the file descriptor from the Android code you wrote above, and print it both in Java and in mavsdk just before the failing call, right?

Did you get the Android permission to access that file descriptor (I guess that's the USB permission)? You still need to do that in Java, and I don't see it appear in your code sample above.

divyanshupundir commented 3 years ago

This is the function that I use to initialize the USB connection and get the system address:

private String initializeUsbDevice() {
    UsbManager usbManager = (UsbManager) mAppContext.getSystemService(Context.USB_SERVICE);
    HashMap<String, UsbDevice> deviceHm = usbManager.getDeviceList();
    if (deviceHm.isEmpty()) {
        return NO_ADDRESS;
    }

    UsbDevice usbDevice = deviceHm.values().iterator().next();
    Log.d(TAG, "initializeUsbDevice: hasPermission: " + usbManager.hasPermission(usbDevice));
    UsbDeviceConnection connection = usbManager.openDevice(usbDevice);
    String systemAddress = "serial_fd://" + connection.getFileDescriptor() + ":" + USB_BAUD_RATE;
    Log.d(TAG, "initializeUsbDevice: " + systemAddress);
    return systemAddress;
}

This is how I use it:

private void initializeServerAndDrone(String systemAddress) {
    if (NO_ADDRESS.equals(systemAddress)) {
        Toast.makeText(mAppContext, R.string.usb_device_not_found, Toast.LENGTH_SHORT).show();
    }

    mMavsdkServer = new MavsdkServer();
    int mavsdkServerPort = mMavsdkServer.run(systemAddress);
    mDrone = new System(MAVSDK_SERVER_IP, mavsdkServerPort);
}

And these are the logs on Android's side:

2020-10-05 18:38:20.486 6252-6252/package D/LOG_<package>.repositories.DroneRepository: initializeUsbDevice: hasPermission: true
2020-10-05 18:38:20.487 6252-6252/package D/LOG_<package>.repositories.DroneRepository: initializeUsbDevice: serial_fd://48:57600

And I added this log message in the setup_port() function of serial_connection.cpp:

    LogDebug() << "File Descriptor at setup_port(): " << _fd;
    if (_fd == -1) {
        _fd = open(_serial_node.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);
        if (_fd == -1) {
            LogErr() << "open failed: " << GET_ERROR();
            return ConnectionResult::ConnectionError;
        }
    }

Which gives the output: 2020-10-05 18:38:20.542 6252-6252/<package> D/Mavsdk: File Descriptor at setup_port(): 48

I don't explicitly ask for permission because the idea is that if one adds intent filters in the manifest and defines a device filter XML, the user gets a list of supporting apps when the USB module (telemetry in this case) is connected to the phone. If the activity starts like this, then the app directly has permission to use the device. It is given here in the Android docs.

I did a kind of soft verification, as when I simply start the app without using the method mentioned above, I get the following log: 2020-10-06 00:47:52.992 27049-27049/<package> D/LOG_<package>.repositories.DroneRepository: initializeUsbDevice: hasPermission: false And usbManager.openDevice(usbDevice) returns null.

JonasVautherin commented 3 years ago

Hmm, this all looks good to me, but I have never done that on Android. However, I believe that QGC does something like that, too (though I don't know the implementation details, but that's open source, if you feel like checking that).

Otherwise there are two things I can think of:

First, C++ opens the serial port like so:

_fd = open(_serial_node.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);

I guess Android directly comes with RDWR, right?

And second is: how do you cross-compile for Android? Using Dockcross? I'm just wondering if tcgetattr may require a more recent NDK than what Dockcross is providing :thinking:.

divyanshupundir commented 3 years ago

In Android, we can create a UsbDeviceConnection object using the openDevice() function of UsbManager. This object can be used to send data to the device in the form of byte arrays. That's through the Java code. Perhaps, Android SDK abstracts the communication protocol and actual file descriptor that we get from the C code that is running on the kernel.

Yes, I did use DockCross to compile the code. I used these commands. Though I did not create the tar file. I simply used the libmavsdk_server.so file that was generated in the Android project through NDK. And now I'm accessing its function like how you've done it over here.

Are you talking about the Android NDK version that I'm using in my project? It's the latest one 21.3.65 and the CMake version is 3.10.2

Thank you again for your input. I'll go through the QGC code and find out more about Android RDWR.

JonasVautherin commented 3 years ago

Are you talking about the Android NDK version that I'm using in my project? It's the latest one 21.3.65 and the CMake version is 3.10.2

No I was referring to the one used in dockcross: https://github.com/dockcross/dockcross/blob/master/android-arm64/Dockerfile#L24-L25. But it has been updated recently, and also your crash is at runtime, so that should not be related.

Thank you again for your input. I'll go through the QGC code and find out more about Android RDWR.

Feel free to ask on the #qgroundcontrol channel on Slack, and ping me there! I'm sure we can get QGC people to give us a hint :blush:

divyanshupundir commented 3 years ago

It seems that QGC has implemented its own Java side USB I/O. They've used usb-serial-for-android library for their UsbIoManager. I'm not 100% sure but here's the C++ code that is used to transfer data between the Java side and the native library/Qt framework that they've used to build the app.

I guess I'll try a few more things to get the serial connection to work without having to do something similar because it looks like a lot of work, and not something I can accommodate in my timeline :sweat_smile:.

Feel free to ask on the #qgroundcontrol channel on Slack, and ping me there! I'm sure we can get QGC people to give us a hint :blush:

Can't do that. I'm not a part of the px4 Slack workspace.

JonasVautherin commented 3 years ago

Can't do that. I'm not a part of the px4 Slack workspace.

If you want to join, that's free, just need to register here: http://slack.px4.io/ :blush:

because it looks like a lot of work, and not something I can accommodate in my timeline

Yes I agree, and I would love to find a solution using your code. I'll have a look to see if I can find some insights about why this error would occur :+1:

divyanshupundir commented 3 years ago

If you want to join, that's free, just need to register here: http://slack.px4.io/

Great! Thanks! Looks like I wasn't thorough in my Google search.

Feel free to ask on the #qgroundcontrol channel on Slack, and ping me there! I'm sure we can get QGC people to give us a hint

I'll try a few more things before doing this.

julianoes commented 3 years ago

Are you using a serial device like an FTDI adapter or SiK radio which requires an actual baudrate, etc.? Or is it just a USB device like ttyACM that doesn't actually need that? If it's the latter, you could try if it works without (some of the) configuration.

JonasVautherin commented 3 years ago

Looking at the QGC code, it seems like they get the file descriptor from C++. But we don't need that. I would look at trying to make it work without calling tcgetattr, and see if it goes somewhere. So essentially what @julianoes says above: "try if it works without the configuration" :+1:.

divyanshupundir commented 3 years ago

Are you using a serial device like an FTDI adapter or SiK radio which requires an actual baudrate, etc.?

It is SiK radio that runs on a CP21 driver, with a baudrate of 57600.

I would look at trying to make it work without calling tcgetattr

So from what I understood, tcgetattr is used to set up the serial port. So if we open the serial port from the Java side, Android must be doing that, as I'm able to receive the data in the app using the telemetry module. So I thought of bypassing the configurations and it's still not detecting the drone.

JonasVautherin commented 3 years ago

it's still not detecting the drone

So it does not complain anymore, and mavsdk_server is running (and did not crash). Correct?

divyanshupundir commented 3 years ago

mavsdk_server is running (and did not crash)

Exactly. It's not crashing. But when I logged recv_len over here:

LogDebug() << recv_len;

if (recv_len > static_cast<int>(sizeof(buffer)) || recv_len == 0) {
    continue;
}
_mavlink_receiver->set_new_datagram(buffer, recv_len);

I saw that recv_len is always 0. So it's never reaching _mavlink_receiver->set_new_datagram(buffer, recv_len); And I guess that this line: recv_len = static_cast<int>(read(_fd, buffer, sizeof(buffer))); isn't working as expected. Probably because of the fd.

divyanshupundir commented 3 years ago

I'm trying to find a way to use some kind of 'shared memory' between the two processes. The most simple method would probably be to use a file to read and write the data. So I wouldn't need to change the code much, instead of passing the path to the serial device, I will pass the file location.

But since two sources will asynchronously be reading and writing data to it, there can be a lot of data corruption. Also, I may still have the write permissions problem from the native side.

Do you think that would work?

JonasVautherin commented 3 years ago

I don't really get why you would want that at all :thinking:. I think there should be a way to share a file descriptor that would not imply complex workarounds like this.

I looked around, and:

So that gives me two questions:

  1. Could it be that the string you pass here somehow badly converts the int? I don't think so, because it's really just an ID, if I understand correctly (and not something more complex like, e.g. a pointer).
  2. Could it be that your FileDescriptor goes out of scope in Java, making the handle to it (the int fd) invalid by the time it is used in C++? Could you try keeping the FileDescriptor in scope, e.g. by making it a class variable?
divyanshupundir commented 3 years ago
  1. Could it be that your FileDescriptor goes out of scope in Java, making the handle to it (the int fd) invalid by the time it is used in C++? Could you try keeping the FileDescriptor in scope, e.g. by making it a class variable?

The android.hardware.usb API does not provide any public method to get the FileDescriptor object, though it uses it on the Java side of the code. When we call [usbDeviceConnection.getFileDescriptor()](https://developer.android.com/reference/android/hardware/usb/UsbDeviceConnection#getFileDescriptor()) it returns the int handle only.

When we call usbManager.open(usbDevice) it generates a ParcelFileDescriptor object for this usbDevice which it then uses to open the connection. If we go to the source for UsbDeviceConnection.java, we can see here that it calls native_open() to open the connection.

The native side of UsbDeviceConnection is available here. So android_hardware_UsbDeviceConnection_open() basically does the same thing that this person has done

  • this person passes the FileDescriptor as a jobject instead of a jint (in your case you pass it as a string).

Additionally, it creates an instance of usb_device struct with the int fd and device name. And when call usbDeviceConnection.getFileDescriptor(), on the native side it returns the same int fd that it had created earlier.

  1. Could it be that the string you pass here somehow badly converts the int? I don't think so, because it's really just an ID, if I understand correctly (and not something more complex like, e.g. a pointer).

Even I feel that it is simply an ID and nothing else. But I'll try to find out more about it.

Could this line be creating the problem?

// duplicate the file descriptor, since ParcelFileDescriptor will eventually close its copy
fd = fcntl(fd, F_DUPFD_CLOEXEC, 0);

I'm not sure how the CLOEXEC flag actually works.

divyanshupundir commented 3 years ago

I added these logs in SerialConnection::receive()

        int pollrc = poll(fds, 1, 1000);
        LogDebug() << "receive(): _fd: " << fds[0].fd;
        LogDebug() << "receive(): pollrc: " << pollrc;
        LogDebug() << "receive(): !(fds[0].revents & POLLIN):" << !(fds[0].revents & POLLIN);

        if (pollrc == 0 || !(fds[0].revents & POLLIN)) {
            continue;
        } else if (pollrc == -1) {
            LogErr() << "read poll failure: " << GET_ERROR();
        }

And these were the logs:

2020-10-10 17:20:13.901 25160-25202/<package> D/Mavsdk: receive(): _fd: 51
2020-10-10 17:20:13.901 25160-25202/<package> D/Mavsdk: receive(): pollrc: 0
2020-10-10 17:20:13.901 25160-25202/<package> D/Mavsdk: receive(): !(fds[0].revents & POLLIN):1
2020-10-10 17:20:14.903 25160-25202/<package> D/Mavsdk: receive(): _fd: 51
2020-10-10 17:20:14.904 25160-25202/<package> D/Mavsdk: receive(): pollrc: 0
2020-10-10 17:20:14.904 25160-25202/<package> D/Mavsdk: receive(): !(fds[0].revents & POLLIN):1
2020-10-10 17:20:15.905 25160-25202/<package> D/Mavsdk: receive(): _fd: 51
2020-10-10 17:20:15.906 25160-25202/<package> D/Mavsdk: receive(): pollrc: 0
2020-10-10 17:20:15.906 25160-25202/<package> D/Mavsdk: receive(): !(fds[0].revents & POLLIN):1

pollrc = 0 indicates that the call timed out and no file descriptors were ready. And since the timeout is 1000ms the logs repeat every second.

But immediately after physically disconnect the telemetry module:

2020-10-10 17:24:26.058 25160-25202/<package> D/Mavsdk: receive(): _fd: 51
2020-10-10 17:24:26.058 25160-25202/<package> D/Mavsdk: receive(): pollrc: 1
2020-10-10 17:24:26.058 25160-25202/<package> D/Mavsdk: receive(): !(fds[0].revents & POLLIN):1
2020-10-10 17:24:26.058 25160-25202/<package> D/Mavsdk: receive(): _fd: 51
2020-10-10 17:24:26.058 25160-25202/<package> D/Mavsdk: receive(): pollrc: 1
2020-10-10 17:24:26.058 25160-25202/<package> D/Mavsdk: receive(): !(fds[0].revents & POLLIN):1
2020-10-10 17:24:26.058 25160-25202/<package> D/Mavsdk: receive(): _fd: 51
2020-10-10 17:24:26.058 25160-25202/<package> D/Mavsdk: receive(): pollrc: 1
2020-10-10 17:24:26.059 25160-25202/<package> D/Mavsdk: receive(): !(fds[0].revents & POLLIN):1

:man_facepalming: I'm getting a feeling that connection on the Android side is blocking the port and preventing MAVSDK from using the fd. So I called usbDeviceConnection.close() after I called mavsdkServer.run(). Now the value of pollrc is 1 (so the port is not being blocked), and recv_len = static_cast<int>(read(_fd, buffer, sizeof(buffer))); is being executed, but the value of recv_len is 0. Meaning it did not read the data.

JonasVautherin commented 3 years ago

I'm getting a feeling that connection on the Android side is blocking the port and preventing MAVSDK from using the fd.

Like you should create the fd from the C++ side instead of the Java side? :confused: