HAL, otherwise known as Hardware Abstraction Layer, is part of the RoFI platform that enables one to write code independent of its running platform, which can be real hardware or a simulator. It can also be used as an abstraction over different RoFI hardware - modules with motors, sensors and more - without changing the user firmware. Therefore, RoFI HAL aims to define a solid and compact interface to make both - the implementation and the usage simpler.

The interface

The current RoFI HAL interface consists of three main components: RoFI, Joints and Connector. Each component is designed as a Proxy, i.e., indirect access to the given component. Proxy doesn’t hold the component it represents and is only a lightweight abstraction that can be safely copied and shared. RoFI HAL provides a mechanism to react to events, such as errors, connections, or disconnections, as a callback function. The callback function is, as the name suggests, a function that gets “called back” when an event occurs and typically takes what event occurred and proxy to react to the given event.

RoFI

The RoFI component of the RoFI HAL interface represents the individual RoFI module. Each module has its unique identifier and shape. The shape of RoFI modules currently describes the count of joints and connectors on this module. Individual joints and connectors can be retrieved using its index. The unique identifier can be used to acquire the remote RoFI module from the current one. The RoFI proxy uses only one callback, which gets called after a delay given in milliseconds.

The following is example how rofi proxy can be used:

// Proxy for local RoFI
auto localRoFI = rofi::hal::RoFI::getLocalRoFI();
// Can pass as function argument without any issue
functionThatUsesRoFI( localRoFI );
auto descriptor = localRoFI.getDescriptor();
std::cout << "Local RoFI has id: " << localRoFI.getId()
    << " and has " << descriptor.jointCount
    << " joints and " << descriptor.connectorCount << "connectors\n";

Joint

The joint controls the movement of the module, and thus its interface presents functionality to get and set the joint’s position, velocity and torque. The joint also contains bounds for the position, speed and maximum torque. The joint proxy uses two callback functions - one as a reaction to reaching a specified position and the other for reacting to errors. The callback for reaching a specified position takes only a proxy for the joint. The callback for the error takes a proxy for the joint, what error happened and the error message. The error could be caused by a communication error or the motor itself.

The following is example how joint proxy can be used:

auto joint = localRoFI.getJoint( 0 );
joint.onError( []( auto /* proxy joint */, auto /* error type */, const std::string& msg ) {
    std::cout << msg << "\n";
} );
// `setPosition` calls the callback after position is reached
joint0.setPosition( -1.5 /* position */, 1.5 /* velocity */, []( rofi::hal::Joint ) {
    std::cout << "Position 1 reached";
} );

Connector

The connector of the RoFI module provides valuable information about its state. The connector state consists of the following:

  • position,
  • orientation,
  • whether the connector is connected to the mating side,
  • power line status,
  • a measured distance,
  • voltage and current of the internal and external power lines.

The connector position refers to whether the connector is retracted or extended. The connector orientation describes a mutual orientation of two connected connectors, which could be one of North, East, South, or West. (This is explained more thoroughly at https://ro.fi.muni.cz/platform/gridSystem/#connector-orientation ). The connector power lines inform whether internal and external lines are connected. The measured distance by lidar is between the connector and an object or surface in front of it.

The connector interface doesn’t only provide a passive state but can also control the connector. First, it allows extending or retracting the connector. Furthermore, it supports connecting to or disconnecting from the mating’s side power line. The connector interface also has callbacks for reacting to connector events and receiving packets. Connector events are connecting and disconnecting from another connector and changing the power voltage or current. The callback for receiving packets takes the connecter that received that packet, the content type, and the packet itself. Last but not least, this interface establishes functionality for transmitting packets.

The following is exmaple how connector proxy can be used:

auto connector = localRoFI.getConnector( 0 );
auto state = connector.getState();
std::cout << "Connector extended: " << state.position
    << "\nconnector connected to mating side: " << state.connected;

connector.onPacket( []( auto conn, auto, auto ) {
    // Let's say we received packet from mating side to connect.
    ...
    // `connect` extends the connector
    conn.connect();
} );