Digital Inputs and Outputs
Digital inputs and outputs are used for communication between a robot and external devices or sensors by digital
signals. Unlike analog signals, digital signals are binary: they can only be in one of two states, PinLevel::High or
PinLevel::Low. Digital inputs can only be read, while digital outputs can be read and written to.
Depending on the specific robot, a digital input or output may represent a physical pin on the robot cabinet or an abstract bit on a fieldbus.
Note
DigitalInputs and DigitalOutputs are not implemented by FanucRobotArm or FanucRobotArmView.
Reading and Writing Digital Inputs and Outputs
A digital output is represented by a DigitalOutput handle and a digital input by a DigitalInput handle. They are
retrieved with digital_output() and digital_input(), which take the id of the pin as a PinId.
The first pin has id 1; max_output_id() and max_input_id() return the highest valid id.
A handle is live: every read goes to the robot control. A digital output is set high with set_high() and low with
set_low(); write(level) does either. Each write returns once the robot control has confirmed the new level, at
most after the robot’s command timeout. The current level of an output or input is
read with level(), or checked with is_high() and is_low().
For a quick interaction, an output can be read, set, and cleared inline:
let output_id = PinId(1);
robot.digital_output(output_id)?.set_high()?;
let level = robot.digital_output(output_id)?.level()?;
info!("Output {output_id} is {level}");
robot.digital_output(output_id)?.set_low()?;
Digital inputs can only be read but are used in the same fashion.
If one input or output is used multiple times, keep the handle in a variable instead of retrieving it repeatedly. A handle borrows the robot, so the robot cannot be disconnected or dropped while a handle is still in use:
let output = robot.digital_output(PinId(1))?;
output.set_high()?;
info!("Output {} is {}", output.id(), output.level()?);
output.set_low()?;
Waiting for an Input
Handles can wait for a level. wait_until() polls the robot control until the pin reads the given level and returns
RobotArmError::Timeout if it does not happen in time:
let valve = robot.digital_output(PinId(2))?;
let workpiece_present = robot.digital_input(PinId(3))?;
valve.write(PinLevel::High)?;
// Polls at the library interval; returns `Timeout` if the sensor does not
// report the level in time.
match workpiece_present.wait_until(PinLevel::High, Duration::from_millis(500)) {
Ok(()) => info!("Workpiece detected."),
Err(RobotArmError::Timeout { .. }) => info!("No workpiece within 500 ms."),
Err(e) => return Err(e),
}
valve.write(PinLevel::Low)?;
Writing Helpers Against the Pin Traits
The capabilities of a pin are traits: DigitalPin for anything readable (id(), level(), is_high(), is_low(),
wait_until()) and WritableDigitalPin for anything writable (set_high(), set_low(), write()). Helper code
written against them accepts inputs and outputs alike:
fn log_pin(pin: &impl DigitalPin) -> Result<()> {
info!("Pin {} is {}", pin.id(), pin.level()?);
Ok(())
}
log_pin(&robot.digital_input(PinId(1))?)?;
log_pin(&robot.digital_output(PinId(1))?)?;
Using Input and Output Groups
While single inputs and outputs are often sufficient for simple logic, most peripherals require setting more than
one output or reading more than one input together. DigitalInputGroup and DigitalOutputGroup bundle multiple
pins in one object. They are retrieved with digital_input_group() and digital_output_group(),
which take a slice of pin ids.
The order of the ids is the order of the group. It defines the order of the values returned by read_values() and the
bit order of read_int(), where the first pin is bit 0, as well as their write_* counterparts. An id may not
appear twice.
The current values of the whole group can be read in different representations with read_map(), read_values(), and
read_int(). Likewise, all outputs of a group can be set from the same representations with write_map(),
write_values(), and write_int(). write_map() takes each id at most once.
The following example shows how conveyors connected to the digital outputs 1, 2, and 4 could be switched on and off:
let conveyors = robot.digital_output_group(&[PinId(1), PinId(2), PinId(4)])?;
conveyors.write_map([
(PinId(1), PinLevel::High),
(PinId(2), PinLevel::Low),
(PinId(4), PinLevel::High),
])?;
info!("Conveyors state: {:?}", conveyors.read_values()?);
// Write all pins at once as an integer, first pin in bit 0. Zero sets
// every output of the group low.
conveyors.write_int(0)?;
Method |
Representation |
|---|---|
|
Pairs of pin id and level; |
|
|
|
A |
Please note that the control of a group is not exclusive: single inputs and outputs can still be used as usual, and pins can be shared between multiple groups. The values of a group are read in one snapshot, but written one pin after the other, each confirmed by the control before the next; if a write fails, the pins written before it keep their new state.
Full Example
Digital I/O example
//! Digital I/O examples.
//!
//! Demonstrates reading and writing digital inputs and outputs, both
//! individually and as groups.
mod common;
use std::time::Duration;
use tracing::info;
use voraus_robot_arm::prelude::*;
/// Show how digital outputs can be used inline.
fn run_inline(robot: &impl DigitalOutputs) -> Result<()> {
info!("--- Inline digital output ---");
let output_id = PinId(1);
robot.digital_output(output_id)?.set_high()?;
let level = robot.digital_output(output_id)?.level()?;
info!("Output {output_id} is {level}");
robot.digital_output(output_id)?.set_low()?;
Ok(())
}
/// Show how digital outputs can be used via a reusable handle.
fn run_use_object(robot: &impl DigitalOutputs) -> Result<()> {
info!("--- Digital output handle ---");
let output = robot.digital_output(PinId(1))?;
output.set_high()?;
info!("Output {} is {}", output.id(), output.level()?);
output.set_low()?;
Ok(())
}
/// Show how digital outputs can be grouped.
fn run_group_example(robot: &impl DigitalOutputs) -> Result<()> {
info!("--- Digital output group ---");
// The group's pin order is the caller's order; it defines the value and
// bit order of `read_values`, `read_int` and their `write_*` counterparts.
let conveyors = robot.digital_output_group(&[PinId(1), PinId(2), PinId(4)])?;
conveyors.write_map([
(PinId(1), PinLevel::High),
(PinId(2), PinLevel::Low),
(PinId(4), PinLevel::High),
])?;
info!("Conveyors state: {:?}", conveyors.read_values()?);
// Write all pins at once as an integer, first pin in bit 0. Zero sets
// every output of the group low.
conveyors.write_int(0)?;
Ok(())
}
/// Write a level directly, and wait for an input to reach a level.
fn run_pin_helpers(robot: &(impl DigitalOutputs + DigitalInputs)) -> Result<()> {
info!("--- Pin helpers ---");
let valve = robot.digital_output(PinId(2))?;
let workpiece_present = robot.digital_input(PinId(3))?;
valve.write(PinLevel::High)?;
// Polls at the library interval; returns `Timeout` if the sensor does not
// report the level in time.
match workpiece_present.wait_until(PinLevel::High, Duration::from_millis(500)) {
Ok(()) => info!("Workpiece detected."),
Err(RobotArmError::Timeout { .. }) => info!("No workpiece within 500 ms."),
Err(e) => return Err(e),
}
valve.write(PinLevel::Low)?;
Ok(())
}
/// Helper code can be written once against the pin traits, for inputs and
/// outputs alike.
fn log_pin(pin: &impl DigitalPin) -> Result<()> {
info!("Pin {} is {}", pin.id(), pin.level()?);
Ok(())
}
fn main() -> voraus_robot_arm::error::Result<()> {
common::init_logging();
let robot = common::create_connected_robot()?;
run_inline(&robot)?;
run_use_object(&robot)?;
run_group_example(&robot)?;
run_pin_helpers(&robot)?;
log_pin(&robot.digital_input(PinId(1))?)?;
log_pin(&robot.digital_output(PinId(1))?)?;
info!("Done.");
Ok(())
}
Trait Definitions
DigitalInputs
pub trait DigitalInputs {
/// Get a handle to a single digital input pin.
///
/// Creating the handle reads the inputs from the control once to check
/// `id` against the robot's pin count. The handle implements
/// [`DigitalPin`](crate::io_handle::DigitalPin) to read the pin with
/// [`level`](crate::io_handle::DigitalPin::level),
/// [`is_high`](crate::io_handle::DigitalPin::is_high) or
/// [`is_low`](crate::io_handle::DigitalPin::is_low), or to wait for a
/// level with [`wait_until`](crate::io_handle::DigitalPin::wait_until).
///
/// # Example
///
/// ```rust
/// # use std::time::Duration;
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl DigitalInputs) -> Result<()> {
/// let workpiece_present = robot.digital_input(PinId(3))?;
/// if workpiece_present.is_low()? {
/// workpiece_present.wait_until(PinLevel::High, Duration::from_secs(2))?;
/// }
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `id` - Pin ID (1-indexed).
///
/// # Returns
///
/// A [`DigitalInput`] handle for the specified pin.
///
/// # Errors
///
/// Returns
/// [`RobotArmError::InvalidArgument`](crate::error::RobotArmError::InvalidArgument)
/// with [`ArgumentError::PinIdZero`](crate::error::ArgumentError::PinIdZero) or
/// [`ArgumentError::PinIdTooLarge`](crate::error::ArgumentError::PinIdTooLarge)
/// if `id` is out of range, or
/// [`RobotArmError::Backend`](crate::error::RobotArmError::Backend) if the robot
/// is not connected or the read from the robot control fails.
fn digital_input(&self, id: PinId) -> Result<DigitalInput<'_>>;
/// Get a handle to a group of digital input pins.
///
/// Creating the handle reads the inputs from the control once to check
/// `ids` against the robot's pin count. The order of `ids` is the order
/// of the group. It defines the value order of
/// [`read_values`](DigitalInputGroup::read_values) and the bit order of
/// [`read_int`](DigitalInputGroup::read_int): the first pin is bit 0.
///
/// A read takes one snapshot of all inputs. Groups do not own their
/// pins: two groups can contain the same pin.
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl DigitalInputs) -> Result<()> {
/// let sensors = robot.digital_input_group(&[PinId(1), PinId(2), PinId(4)])?;
/// // Bit 0 is pin 1, bit 1 is pin 2, bit 2 is pin 4.
/// println!("Sensors: {:#05b}", sensors.read_int()?);
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `ids` - Pin IDs (1-indexed, in the order the group should present them; no duplicates).
///
/// # Returns
///
/// A [`DigitalInputGroup`] handle for the specified pins.
///
/// # Errors
///
/// Returns
/// [`RobotArmError::InvalidArgument`](crate::error::RobotArmError::InvalidArgument)
/// with [`ArgumentError::EmptyPinGroup`](crate::error::ArgumentError::EmptyPinGroup)
/// if `ids` is empty,
/// [`ArgumentError::PinIdZero`](crate::error::ArgumentError::PinIdZero) or
/// [`ArgumentError::PinIdTooLarge`](crate::error::ArgumentError::PinIdTooLarge)
/// if an ID is out of range, or
/// [`ArgumentError::DuplicatePinId`](crate::error::ArgumentError::DuplicatePinId)
/// if an ID is repeated; returns
/// [`RobotArmError::Backend`](crate::error::RobotArmError::Backend) if the robot
/// is not connected or the read from the robot control fails.
fn digital_input_group(&self, ids: &[PinId]) -> Result<DigitalInputGroup<'_>>;
/// Get the highest valid input pin ID.
///
/// Pin IDs are 1-indexed, so this is also the number of digital inputs.
///
/// # Returns
///
/// The highest valid input pin ID.
///
/// # Errors
///
/// Returns [`RobotArmError::Backend`](crate::error::RobotArmError::Backend) if the robot
/// is not connected or the read from the robot control fails.
fn max_input_id(&self) -> Result<PinId>;
}
DigitalOutputs
pub trait DigitalOutputs {
/// Get a handle to a single digital output pin.
///
/// Creating the handle reads the outputs from the control once to check
/// `id` against the robot's pin count. The handle implements
/// [`DigitalPin`](crate::io_handle::DigitalPin) to read the pin and
/// [`WritableDigitalPin`](crate::io_handle::WritableDigitalPin) to write
/// it with [`write`](crate::io_handle::WritableDigitalPin::write),
/// [`set_high`](crate::io_handle::WritableDigitalPin::set_high) or
/// [`set_low`](crate::io_handle::WritableDigitalPin::set_low). Each write
/// returns when the control has confirmed the new state.
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl DigitalOutputs) -> Result<()> {
/// let gripper = robot.digital_output(PinId(1))?;
/// gripper.set_high()?;
/// assert!(gripper.is_high()?);
/// gripper.set_low()?;
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `id` - Pin ID (1-indexed).
///
/// # Returns
///
/// A [`DigitalOutput`] handle for the specified pin.
///
/// # Errors
///
/// Returns
/// [`RobotArmError::InvalidArgument`](crate::error::RobotArmError::InvalidArgument)
/// with [`ArgumentError::PinIdZero`](crate::error::ArgumentError::PinIdZero) or
/// [`ArgumentError::PinIdTooLarge`](crate::error::ArgumentError::PinIdTooLarge)
/// if `id` is out of range, or
/// [`RobotArmError::Backend`](crate::error::RobotArmError::Backend) if the robot
/// is not connected or the read from the robot control fails.
fn digital_output(&self, id: PinId) -> Result<DigitalOutput<'_>>;
/// Get a handle to a group of digital output pins.
///
/// Creating the handle reads the outputs from the control once to check
/// `ids` against the robot's pin count. The order of `ids` is the order
/// of the group. It defines the value order of
/// [`read_values`](DigitalOutputGroup::read_values) and
/// [`write_values`](DigitalOutputGroup::write_values), and the bit order
/// of [`read_int`](DigitalOutputGroup::read_int) and
/// [`write_int`](DigitalOutputGroup::write_int): the first pin is bit 0.
///
/// A read takes one snapshot of all outputs. A write sets the pins one
/// after the other and then waits until the control reports all of them,
/// bounded by the command timeout. If a write fails, the pins written
/// before it keep their new state. Groups do not own their pins: two
/// groups can contain the same pin.
///
/// # Example
///
/// ```rust
/// # use voraus_robot_arm::prelude::*;
/// # fn demo(robot: &impl DigitalOutputs) -> Result<()> {
/// let conveyors = robot.digital_output_group(&[PinId(1), PinId(2), PinId(4)])?;
/// // Set pins 1 and 4 high, pin 2 low.
/// conveyors.write_int(0b101)?;
/// assert_eq!(
/// conveyors.read_values()?,
/// vec![PinLevel::High, PinLevel::Low, PinLevel::High]
/// );
/// # Ok(())
/// # }
/// ```
///
/// # Arguments
///
/// * `ids` - Pin IDs (1-indexed, in the order the group should present them; no duplicates).
///
/// # Returns
///
/// A [`DigitalOutputGroup`] handle for the specified pins.
///
/// # Errors
///
/// Returns
/// [`RobotArmError::InvalidArgument`](crate::error::RobotArmError::InvalidArgument)
/// with [`ArgumentError::EmptyPinGroup`](crate::error::ArgumentError::EmptyPinGroup)
/// if `ids` is empty,
/// [`ArgumentError::PinIdZero`](crate::error::ArgumentError::PinIdZero) or
/// [`ArgumentError::PinIdTooLarge`](crate::error::ArgumentError::PinIdTooLarge)
/// if an ID is out of range, or
/// [`ArgumentError::DuplicatePinId`](crate::error::ArgumentError::DuplicatePinId)
/// if an ID is repeated; returns
/// [`RobotArmError::Backend`](crate::error::RobotArmError::Backend) if the robot
/// is not connected or the read from the robot control fails.
fn digital_output_group(&self, ids: &[PinId]) -> Result<DigitalOutputGroup<'_>>;
/// Get the highest valid output pin ID.
///
/// Pin IDs are 1-indexed, so this is also the number of digital outputs.
///
/// # Returns
///
/// The highest valid output pin ID.
///
/// # Errors
///
/// Returns [`RobotArmError::Backend`](crate::error::RobotArmError::Backend) if the robot
/// is not connected or the read from the robot control fails.
fn max_output_id(&self) -> Result<PinId>;
}
DigitalPin and WritableDigitalPin
pub trait DigitalPin {
/// Get the 1-based pin id.
///
/// # Returns
///
/// The pin id as passed to the robot when the handle was created.
fn id(&self) -> PinId;
/// Read the current level of the pin from the robot control.
///
/// # Returns
///
/// [`PinLevel::High`] or [`PinLevel::Low`].
///
/// # Errors
///
/// Returns [`RobotArmError::Backend`](crate::error::RobotArmError::Backend)
/// if the read from the robot control fails.
fn level(&self) -> Result<PinLevel>;
/// Whether the pin is currently high.
///
/// # Returns
///
/// `true` if the pin is high, `false` if it is low.
///
/// # Errors
///
/// As for [`level`](Self::level).
fn is_high(&self) -> Result<bool> {
Ok(self.level()? == PinLevel::High)
}
/// Whether the pin is currently low.
///
/// # Returns
///
/// `true` if the pin is low, `false` if it is high.
///
/// # Errors
///
/// As for [`level`](Self::level).
fn is_low(&self) -> Result<bool> {
Ok(self.level()? == PinLevel::Low)
}
/// Block until the pin reads `level`, polling the robot control.
///
/// Returns at once if the pin already reads `level`.
///
/// # Arguments
///
/// * `level` - The pin level to wait for.
/// * `timeout` - How long to keep polling before giving up.
///
/// # Returns
///
/// `Ok(())` once the pin reads `level`.
///
/// # Errors
///
/// Returns [`RobotArmError::Timeout`](crate::error::RobotArmError::Timeout)
/// if the pin does not reach `level` within `timeout`, or
/// [`RobotArmError::Backend`](crate::error::RobotArmError::Backend) if a
/// read from the robot control fails.
fn wait_until(&self, level: PinLevel, timeout: Duration) -> Result<()> {
if poll_until(timeout, SYNCHRONIZE_POLLING_INTERVAL, || {
Ok(self.level()? == level)
})? {
Ok(())
} else {
Err(RobotArmError::Timeout {
operation: TimedOutOperation::DigitalPinValue,
timeout,
})
}
}
}
pub trait WritableDigitalPin: DigitalPin {
/// Set the pin to `level` and wait until the control confirms it.
///
/// Blocks until the control reports the new level, bounded by the
/// command timeout the handle took from the robot when it was created.
///
/// # Arguments
///
/// * `level` - The level to write.
///
/// # Returns
///
/// `Ok(())` once the control has confirmed the new state.
///
/// # Errors
///
/// Returns [`RobotArmError::NotConfirmed`](crate::error::RobotArmError::NotConfirmed)
/// if the control does not confirm the new value within the command
/// timeout, or
/// [`RobotArmError::Backend`](crate::error::RobotArmError::Backend) if the
/// write or a read of the robot control fails.
fn write(&self, level: PinLevel) -> Result<()> {
self.send(level)?.confirm()
}
/// Set the pin to `level` and return before the control confirms it.
///
/// Call [`confirm`](CommandHandle::confirm) on the returned handle to
/// wait until the control reports the new state.
///
/// # Arguments
///
/// * `level` - The level to write.
///
/// # Returns
///
/// The handle that confirms the write. [`CommandHandle::confirm`]
/// returns [`RobotArmError::NotConfirmed`](crate::error::RobotArmError::NotConfirmed)
/// if the control does not confirm the new value within the command
/// timeout.
///
/// # Errors
///
/// Returns [`RobotArmError::Backend`](crate::error::RobotArmError::Backend)
/// if the write to the robot control fails.
fn send(&self, level: PinLevel) -> Result<CommandHandle<'_>>;
/// Set the pin high.
///
/// # Returns
///
/// `Ok(())` once the control has confirmed the pin is high.
///
/// # Errors
///
/// As for [`write`](Self::write).
fn set_high(&self) -> Result<()> {
self.write(PinLevel::High)
}
/// Set the pin low.
///
/// # Returns
///
/// `Ok(())` once the control has confirmed the pin is low.
///
/// # Errors
///
/// As for [`write`](Self::write).
fn set_low(&self) -> Result<()> {
self.write(PinLevel::Low)
}
/// Set the pin high and return before the control confirms it.
///
/// # Returns
///
/// The handle that confirms the write.
///
/// # Errors
///
/// As for [`send`](Self::send).
fn send_high(&self) -> Result<CommandHandle<'_>> {
self.send(PinLevel::High)
}
/// Set the pin low and return before the control confirms it.
///
/// # Returns
///
/// The handle that confirms the write.
///
/// # Errors
///
/// As for [`send`](Self::send).
fn send_low(&self) -> Result<CommandHandle<'_>> {
self.send(PinLevel::Low)
}
}