-
Notifications
You must be signed in to change notification settings - Fork 12
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
API design for 1.0 #29
Comments
Moving over two comments from the other thread... @Dr-Emann Thanks for taking the time to draft this. Is my understanding correct that you're proposing GpioPort as a trait, which different implementations depending on the underlying port mode, be it all GPIO, I2C, SPI, or UART? @kevinmehall So I'm in favor of keeping the initial API simple and synchronous, but I'm not sure how to do that once we introduce pin edge/level triggers. At the point where we're writing |
In a purely synchronous API, I think you could get away with a But, this also brings up a good argument for using futures -- the command protocol supports batching commands together, so that |
I've spent some time thinking, and came up with a few possible APIs Single Owner, passed parametersApi:// in crate tessel, so called as tessel::ports()
ports() -> Option<Vec<Port>> // Will only return Some() once. Optionally, on Port drop, allow to be called again
// If we know we'll only have two ports, we could return Option<(Port, Port)>
struct Port {
socket: UnixSocket, // Note: No mutex
}
Port::pin(&self, index: u8) -> GpioPin<Port> // Panics for invalid indexes
Port::enable_i2c(self, frequency: u32) -> I2CPort
// On drop, disable I2C
struct I2CPort {
port: Port, // Ownership
frequency: u32,
}
I2CPort::disable_i2c(self) -> Port
I2CPort::pin(&self, index: u8) -> GpioPin<I2CPort> // Panics for invalid indexes
I2CPort::i2c(&self) -> I2C
struct I2C;
I2C::send(&self, port: &mut I2CPort, data: &[u8]) // etc
struct GpioPin<P> {
index: u8,
_marker: PhantomData<P>,
}
GpioPin<P>::index(&self) -> u8
// By restricting with P, you can only use it with the type of port it was created with. (e.g. Port GpioPin's aren't useable with I2CPort)
GpioPin<P>::read(&self, port: &mut P) -> bool // etc Possible Usage:// in mod my_sensor
const FREQUENCY: u32 = ...;
struct Sensor {
port: I2CPort,
i2c: I2c,
other_pin: GpioPin<I2CPort>
}
impl Sensor {
// This sensor owns the port
fn new(port: Port) -> Sensor {
let port = port.enable_i2c(FREQUENCY);
let i2c = port.i2c();
let other_pin = port.pin(5);
Sensor {
port: port,
i2c: i2c,
other_pin: other_pin
}
}
fn sense(&mut self) -> u32 {
self.other_pin.read(&mut self.port);
unimplemented!();
}
} // in mod other_sensor
// This sensor has shared ownership of the port, and it doesn't care what type of port,
// as long as you can pass in a GPIO pin from that type of port
// it should be possible to not own the port, if the port is passed in for all calls (e.g. sense(&self, &mut P))
struct SingePinSensor<P> {
port: Arc<Mutex<P>>,
pin: GpioPin<P>,
}
impl<P> SinglePinSensor<P> {
fn new(port: Arc<Mutex<P>>, pin: GpioPin<P>) -> Self {
SinglePinSensor { port: port, pin: pin }
}
fn sense(&self) -> u32 {
let port = self.port.lock().unwrap();
self.pin.read(&mut port);
unimplemented!();
}
} Pros:
Cons:
I have a few other API ideas, but it's getting late, and this comment is getting long. |
Mutexing should definitely be removed for GPIO pins, it can be handled by keeping the port in scope. I like the practice of specializing a Tessel port as an I2CPort—it also scales to a hypothetical future hardware design that allows I2C + SPI (I2cSpiPort) or all comms at once (at that point... TesselPort). Async is an issue for leveraging the full GPIO bank. I can't think of a good API where I can wait for pin interrupts on several pins simultaneously unless I'm using an event loop (or a poor emulation of one). Essentially how much value do we gain by keeping the SAMD21 socket on the main thread rather than another thread + channels or futures? (We gain flexibility by not using sysfs for I/O, but lose flexibility by each pin not owning its own file descriptor...) (Got to work on USB maintenance for a bit, so no commits by me today.) |
Time for another possible API. Static References, with lockingApi:lazy_static! { TESSEL_INSTANCE:Tessel= { /* make tessel */ }; }
tessel() -> &'static Tessel // returns &TESSEL_INSTANCE
Tessel::ports(&'static self) -> &'static [port]
struct Port {
socket: Mutex<UnixSocket>, // Note: No Arc
mode: Mutex<PortMode>,
pins: Vec<AtomicUsize>, // Keeps track of how many references there are to each pin.
// If pin is in use by, e.g. I2C, set to (!0)
}
enum PortMode {
Normal,
I2C (Arc<I2C>),
Spi(Arc<...>),
Uart(Arc<...>),,
]
Port::pin(&'static self, index: u8) -> GpioPin // Panics for invalid indexes
// On creation (or clone), increment RefCount (or panic if == !0 (means pin in use))
struct GpioPin {
port: &'static Port,
index: u8,
ref_count: &'static AtomicUsize
}
GpioPin::read(&self) -> bool // etc
Port::i2c(&'static self, frequency: u32) -> Arc<I2C> // Panic if needed pins have a use count > 0
// Panic if port.mode is not I2c with the correct frequency already, or Normal
// if current mode is None, set needed pin use counts to (!0), then set current mode
Port::disable_i2c(&'static self) // Panic if not I2C or normal
// Panic if PortMode's arc is not the only I2C reference left
// It might be better to automatically disable I2C when all `I2C` structs are dropped
struct I2C {
port: &'static Port,
frequency: u32,
}
I2C::send(&self, data: &[u8]) // etc Possible Usage:// in mod my_sensor
const FREQUENCY: u32 = ...;
struct Sensor {
i2c: I2C,
other_pin: GpioPin,
}
impl Sensor {
fn new(port: &'static Port) -> Sensor {
let i2c = port.i2c(FREQUENCY);
let other_pin = port.pin(5);
Sensor {
i2c: i2c,
other_pin: other_pin
}
}
fn sense(&self) -> u32 {
self.other_pin.read();
unimplemented!();
}
} // in mod other_sensor
struct SingePinSensor {
pin: GpioPin,
}
impl SinglePinSensor {
fn new(pin: GpioPin) -> Self {
SinglePinSensor { pin: pin }
}
fn sense(&self) -> u32 {
let port = self.port.lock().unwrap();
self.pin.read();
unimplemented!();
}
} Pros:
Cons:
|
I spent some time thinking over this, and I want to propose the following:
Part 1. is easy and mostly implemented, the new change is to make it public. For 2., @Dr-Emann I think the above API proposals are pretty good. I wanted to propose another revision on yours which adds more compile-time guarantees: https://play.rust-lang.org/?gist=6d0968659dafde02cb139aa05f8f30af&version=stable&backtrace=0 We probably would still require a mandatory Mutex on the Port socket, but we can guarantee if a port is in I2C mode and we can't re-borrow a borrowed Pin (because we own the gpio bank object). The ergonomics aren't JavaScript-easy but they are pretty straightforward to use. (I don't like the "tuple hack" to get varargs but Iron uses it so there's precedent, and it's only for the Tessel API so it's not imposing on compatible code. We can add a |
Notes from #34:
|
Wanted to move discussion over from #23 about blockers for 1.0.
Some ideas to continue discussing:
The text was updated successfully, but these errors were encountered: