flipper-io / flipper

Flipper is a development platform that can be controlled from any programming language.
https://www.flipper.io/
Apache License 2.0
70 stars 15 forks source link

Added Java Fs, I2c, Nvm, Config, and Usart driver wrappers. #49

Closed nicholastmosher closed 8 years ago

nicholastmosher commented 8 years ago

A brief synopsis:

For the Fs wrapper, I exposed functions that allow users to separately enter the file path and file name of files to upload, OR to specify a Java File object (which is really just an object that contains the same information). Additionally, I made a subclass of Fs called FilePointer which is analogous to the typedef fsp in C, it's just a way of carrying an int so that it doesn't accidentally get used in some other context. FilePointers can only be constructed by Flipper wrappers in instances such as Fs's data() function or Nvm's alloc(), and they are accepted as arguments in place of fsp in functions such as Nvm's push and pull.

For the I2c and Nvm and Usart wrappers, I made a set of functions for put/get and push/pull which allows the user to use 1) byte array buffers with pre-loaded data to push/put or empty arrays for pulling/getting (though it doesn't actually care if the buffer is empty), or to use 2) a JNA Pointer object, which has friendly methods for reading/writing most primitive Java data array types (see https://jna.java.net/javadoc/com/sun/jna/Pointer.html).

For Config, I just wrapped the thing... there wasn't a lot of transformation to do, especially seeing that I'm not entirely sure what it does. If it needs to be altered or removed let me know.

georgemorgan commented 8 years ago

What is .getBinding()? Does mFlipper refer to the globally attached Flipper device, or is it something else?

TravisWhitaker commented 8 years ago

Overall this looks good to me. The only issue I can see is that there is no way to select among different devices attached to the same computer, and no way to detach a device. An API caller could instantiate multiple Flipper objects, but all API calls would be directed towards the most recently attached device. One way to remedy this would be to have all of the driver methods check that the device corresponding to the Flipper object to which they belong is the currently active device. There are probably more efficient but potentially less idiomatic ways to do that. It would be nice if the API never even had to call detach(); the garbage collector could call it as a finalizer whenever a Flipper object gets collected.

Here are some enhancements you might consider.

The NVM driver supports incremental reading via the read()/get() functions. To use these, the caller first initiates the interleaved read by calling read(fsp), then repeatedly calls get(). The only way to stop the read is to reset the flash hardware. The C API does not provide anything for managing the state of an interleaved read, but it would be nice if higher-level language bindings provided some machinery to deal with this. I haven't come up with something I'm satisfied with on the Haskell side of things, so I don't export read/get from those bindings. In Java there might be some clever subclass pattern to deal with this sort of thing. I think we should have a wider discussion on whether or not we even want to provide the read()/get() interface in the first place.

The push/pull functions are fairly low-level; by this I mean that API callers must explicitly allocate something of type byte[] or a pair of Pointer and int in order to use these functions. In practice callers will be dealing with a more structured representation of their application's data. In order to send their application data they will have to allocate a byte[] of the correct size, call some custom method to serialize an object into the array, and then call push().

Receiving application data involves two cases. If the objects being exchanged are of a fixed size, then the caller must allocate a byte[] of the correct size, call pull(), then call some method that parses the byte[] and provides the object. If the objects being exchanged are of a variable size (e.g. null-terminated strings) then the procedure is more involved. The caller has to get a chunk of data with pull() and then feed it to a parser incrementally, which decides whether or not enough input has been received to correctly construct the object of exchange.

It would be good to provide some interface that abstracts over these push/pull patterns. Suppose I write a C program that runs on the device and sends/receives records like this:

typedef struct _rec
{
        uint32_t id;
        char *name;
        uint32_t length;
        void *payload;
} rec

I want to write a Java program that interacts with this C program, so I write a class like this:

public class Rec
{
        public int id;
        public String name;
        public byte[] payload;
}

It would be nice if all the API caller had to do was make their Rec class implement a serialization interface. Then the caller could use the push/pull functions without any boilerplate. The implementation of the serialization interface could be written in terms of default implementations for basic types that this library provides, like int, String, byte[], etc. I'm unsure of what idioms are typically employed in Java for parser interfaces, so this may require some research.

@georgemorgan, look at the constructors for all of the driver classes. mFlipper is a reference to the enclosing Flipper object; I'd assume that Nick structured it that way so that all of the driver structs don't have to be subclasses of Flipper. mFlipper is not guaranteed to reference the globally active device, which is part of the first issue I mentioned. getBindings is a method of Flipper, it has documentation. Also, I noticed when I was reviewing this that the I2C API is inconsistent; it's put/get functions should be called push/pull.

My recommendation is to merge this and open issues for the lack of select/detach and the aforementioned enhancements.

georgemorgan commented 8 years ago

I reviewed the code, and found the documentation work you're doing, Nick. It looks great. This is exactly what we need. We can then export it to JavaDocs and link to that with the overview documentation.

As far as the code is concerned, Travis makes very valid points. In our Messenger conversation, we covered some of the basic ideas following the implementation of the "serializer". Travis' Haskell counterpart is called "bufferable".

It's going to take a lot of work to figure out all the different ways each language provides facilities to achieve what we're after. Fortunately, Java and Python should be the easiest in terms of community and documentation surrounding the issue. We may even find ourselves needing to write C to facilitate the libflipper<->language bridge.

As we go, we should document all standards we exercise with regards to language bindings. It would be a good idea to start finalizing a documentation platform.