tessel/tessel-rust

RFC: Rustic and Idiomatic Tessel API

Opened this issue · 4 comments

Since I've started getting into Rust development on Tessel I want to open this Issue to request feedback on an API rework I would like to help with.

For this I have some Goals in mind:

  • A Tessel API that can transparently be used from multiple threads.
  • Pins and Ports are represented by objects that can be owned in different scopes or objects
    e.g. Two pins of a port are owned by scopes of two separate thread. To share a pin it needs to be wrapped in a Mutex.
  • Pins and Ports should have lifetimes separate of each other. A Port just holds multiple Pins. Pins can be borrowed or taken.
  • Once all Pins for a Port are no longer owned (they've been dropped) the relevant hardware port is automatically disabled.
  • Not all pins apply to all communication protocols, use Rust's zero-cost abstraction to wrap pins with an object that exposes a communication interface
  • Provide idioms for consistent communication interfaces (Gpio, Analog, Interrupt, Pwm, I2c, Spi, Uart)
    • Idiom to convert Pin (or many Pins) to an interface
    • Idiom to convert an owning interface back into Pin(s) (just dropped borrowed interfaces)
    • Implement io::Read and io::Write for I2c, Spi, and Uart
    • A borrowed interface and owning interface are available for each communication protocol
  • Any IO related call (most of the Tessel interface) should return a Result

The API design is in this gist. It starts with examples on how using the design looks with comments on different ways to get a Pin for example and other thoughts. I included an example on how relay_mono might be reimplemented (More a thought experiment than how I think that specific module should be designed).

https://gist.github.com/mzgoddard/98d1b27979ef86cf2a7effe4bd201984

If there is a better way for me to format this issue, let me know.

cc @rwaldron @tcr

It is a nice approach from my limited knowledge. Bravo.

What is the point of: "Once all Pins for a Port are no longer owned (they've been dropped) the relevant hardware port is automatically disabled."? Is this the most expected behavior, power saving, or.. It seems some hardware might have other requirements for lifetime/session/start-stop cost and instead having a controlled Rustic approach like Drop trait for a port, possibly also a pin.

Is there a desire to have atomic ownership and release of several ports or individual pins within a port together? This can avoid potential split ownership deadlock corner cases. The effort should not slow down for this if it is not apparent how it should be done, but worth at least documenting the issues.

It would be more debuggable and a limited step forward to use .expect("why this is foobar") rather than .unrwrap()

Syntax sugar: ? is now available, though the documentation still shows try! macro (RFC was merged). https://m4rw3r.github.io/rust-questionmark-operator

Are the results going to be Result<Blah, String> or some more specific format?

What is the point of: "Once all Pins for a Port are no longer owned (they've been dropped) the relevant hardware port is automatically disabled."? Is this the most expected behavior, power saving, or.. It seems some hardware might have other requirements for lifetime/session/start-stop cost and instead having a controlled Rustic approach like Drop trait for a port, possibly also a pin.

The point is power saving. The JS api lets you explicitly turn port A and B on or off to save power. I figured it would be rustic to include that as an implied part of the rust API. If none of the 8 pins of a port are owned then no communication can go to or from a device connected on that port to the tessel. If a user needs a port just for power they can own a port or pin symbolically to keep the port on. The simplest case like that a user just creates and owns the Tessel object.

Another side to this is if a user only needs one port to begin with they can create the port directly and by not creating the other port it will not turn on. Only the port they own an handle to is turned on.

Is there a desire to have atomic ownership and release of several ports or individual pins within a port together? This can avoid potential split ownership deadlock corner cases. The effort should not slow down for this if it is not apparent how it should be done, but worth at least documenting the issues.

If I understand your question, each Pin can only be owned by one scope or object. Multiple ownership needs to be managed by the user with standard library objects like Arc and Mutex. Similar the Tessel and Port objects can only be owned by one scope or object. Tessel and Port are simple objects that can be decomposed into their individual Pins. Since Pins and Ports don't lock you can't deadlock with two threads needing two Pins and each locking one in a different order. Users could still encounter that problem with their own Mutex wrapping around Pins but we can't help them there and at least are not creating an opaque API that hides locking or some other mechanism so users can have multiple references to the same Pin.

It would be more debuggable and a limited step forward to use .expect("why this is foobar") rather than .unrwrap()
Syntax sugar: ? is now available, though the documentation still shows try! macro (RFC was merged). https://m4rw3r.github.io/rust-questionmark-operator

Those are really good points. I'll update the examples. (Was ? merged in 1.12 or earlier. A current debt item is to build more recent rust standard libraries.)

Are the results going to be Result<Blah, String> or some more specific format?

Result<X, tessel::Error>. I figured it'd provide the best avenue to create clear errors following the standard libraries idioms like std::io::Error. We can implement From<io::Error> and other lower errors to translate them into tessel::Error. The error will have an enum ErrorKind and formattable into a String.

Finally updated examples with expect and ?. (Not much use of the latter since most examples are fn main)

Renamed Gpio to Digital.

https://github.com/japaric/embedded-hal has traits for GPIO / serial / SPI / I2C / etc that look reasonable to adopt, and would give compatibility with drivers being written on other microcontrollers / embedded platforms.