Breaking News: 107-Arduino-Cyphal v3.0.0 w/ modern API and full Cyphal compliance has just been released!
This release represents a significant overhaul of the entire library. As a result, it is possible to create a fully Cyphal-compliant application by leveraging a powerful, compact, and concise API. These are the main characteristics:
- 
ROS/ROS2 style API with type erasure for creating Publisher's,Subscriptions's,ServiceServer's andServiceClient's.
- 
Run-time configurable parameters, such as - o1heap heap memory size
- transmit queue size
- receive queue size
 
- 
The library is free of Microcontroller/Arduino specific idioms, the library therefore compiles for any architecture (incl. your PC): - A CMakeLists.txt allows creation of a static library w/ your PC-run Cyphal application.
- An example application implements a Cyphal node running on your PC.
 
- 
nunavut pre-generated C++ headers. 
- 
Support for both CAN and CAN FD PHY layers. 
- 
Implementation of the Register API supporting both RO and RW registers, as well as array and scalar types. 
- 
Implementation of the NodeInfo API for providing generic node information for i.e. yakut. 
- 
Provision of a uavcan/node/port/List.1.0 publisher which periodically publishes information on publisher and subscribed topics, as well as service servers and clients. 
- 
Self-explaining examples for Publisher,Subscriptions,ServiceServerandServiceClient.
- 
A number of full Cyphal compliant reference implementations. 
- 
There are also external support libraries that provide Cyphal-specific key functionality: - 107-Arduino-UniqueId provides a 16-Byte unique id over various Arduino platforms. This is needed for the NodeInfo API.
- 107-Arduino-CriticalSection provides RAII-style critical sections for preventing race conditions when calling library APIs within both interrupts and normal execution context.
 
Let’s take a closer look at each of these features:
Declaration/Usage of a Cyphal-Node
The basic API entry point is a Node class instance, which must be provided with heap as well as a function pointer to a CAN transmit function:
Node::Heap<Node::DEFAULT_O1HEAP_SIZE> node_heap;
Node node_hdl(node_heap.data(), node_heap.size(), micros, [] (CanardFrame const & frame) { return mcp2515.transmit(frame); });
Processing happens inside spinSome which needs to be called regularly:
void loop()
{
  /* Process all pending OpenCyphal actions. */
  {
    CriticalSection crit_sec;
    node_hdl.spinSome();
  }
  /* ... */
Received CAN frames are passed to the application via onCanFrameReceived:
void onReceiveBufferFull(CanardFrame const & frame)
{
  node_hdl.onCanFrameReceived(frame);
}
Publisher
A Publisher can be created using the create_publisher API:
/* Create a publisher with a fixed port id: */
const auto heartbeat_pub = node_hdl.create_publisher<uavcan::node::Heartbeat_1_0>
  (1*1000*1000UL /* = transmit timeout / usec */);
/* Create a publisher with a custom port id: */
CanardPortID const ANGLE_ID = 1001U;
const auto angle_pub = node_hdl.create_publisher<uavcan::si::unit::angle::Scalar_1_0>
  (ANGLE_ID, 1*1000*1000UL /* = transmit timeout / usec */);
which can then be used to publish messages:
uavcan::node::Heartbeat_1_0 msg;
msg.uptime = now / 1000;
msg.health.value = uavcan::node::Health_1_0::NOMINAL;
msg.mode.value = uavcan::node::Mode_1_0::OPERATIONAL;
msg.vendor_specific_status_code = 0;
heartbeat_pub->publish(msg);
or
uavcan::si::unit::angle::Scalar_1_0 angle_scalar;
angle_scalar.radian = (b_angle_deg - b_angle_offset_deg) * M_PI / 180.0f;
angle_pub->publish(angle_scalar);
Subscription
A Subscription can be created using the create_subscription API:
/* Create a subscription with a fixed port id: */
const auto heartbeat_subscription = node_hdl.create_subscription<Heartbeat_1_0>(onHeartbeat_1_0_Received);
/* Create a subscription with a custom port id: */
static CanardPortID const BIT_PORT_ID   = 1620U;
const auto bit_subscription = node_hdl.create_subscription<Bit_1_0>(BIT_PORT_ID, onBit_1_0_Received);
Upon reception of such a transfer the callback provided during creation of the Subscription object is invoked:
void onBit_1_0_Received(Bit_1_0 const & msg)
{
  if(msg.value)
    digitalWrite(LED_BUILTIN, HIGH);
  else
    digitalWrite(LED_BUILTIN, LOW);
}
Service Server
A ServiceServer can be created using the create_service_server API:
const auto ServiceServer execute_command_srv = node_hdl.create_service_server<ExecuteCommand::Request_1_1, ExecuteCommand::Response_1_1>(
  2*1000*1000UL,
  onExecuteCommand_1_1_Request_Received);
Upon reception of a service clients request the callback provided during creation of the ServiceServer object is invoked:
ExecuteCommand::Response_1_1 onExecuteCommand_1_1_Request_Received(ExecuteCommand::Request_1_1 const & req)
{
  ExecuteCommand::Response_1_1 rsp;
  if (req.command == 0xCAFE)
    rsp.status = ExecuteCommand::Response_1_1::STATUS_SUCCESS;
  else
    rsp.status = ExecuteCommand::Response_1_1::STATUS_NOT_AUTHORIZED;
  return rsp;
}
Service Client
A ServiceClient can be created using the create_service_client API:
const auto srv_client = node_hdl.create_service_client<ExecuteCommand::Request_1_1, ExecuteCommand::Response_1_1>(
  2*1000*1000UL,
  onExecuteCommand_1_1_Response_Received);
Send an asynchronous request:
/* Request some coffee. */
std::string const cmd_param("I want a double espresso with cream!");
ExecuteCommand::Request_1_1 req;
req.command = 0xCAFE;
std::copy_n(cmd_param.begin(),
            std::min(cmd_param.length(), req.parameter.capacity()),
            req.parameter.begin());
if (!srv_client->request(27 /* remote node id */, req))
  Serial.println("Coffee request failed.");
Upon reception of a service servers response the callback provided during creation of the ServiceClient object is invoked:
void onExecuteCommand_1_1_Response_Received(ExecuteCommand::Response_1_1 const & rsp)
{
  if (rsp.status == ExecuteCommand::Response_1_1::STATUS_SUCCESS)
    Serial.println("Coffee successfully retrieved");
  else
    Serial.println("Error when retrieving coffee");
}
Register API
The Register API enables node configuration:
/* R/W register value. */
CanardNodeID node_id = node_hdl.getNodeId();
CanardPortID counter_port_id = DEFAULT_COUNTER_PORT_ID;
uint16_t counter_update_period_ms = DEFAULT_COUNTER_UPDATE_PERIOD_ms;
/* Creating a registry. */
const auto node_registry = node_hdl.create_registry();
/* Creating RO and RW registers. */
const auto reg_ro_node_description             = node_registry->route ("cyphal.node.description", {true}, []() { return "basic-cyphal-node"; });
const auto reg_ro_pub_counter_type             = node_registry->route ("cyphal.pub.counter.type", {true}, []() { return "uavcan.primitive.scalar.Integer8.1.0"; });
const auto reg_rw_node_id                      = node_registry->expose("cyphal.node.id", {}, node_id);
const auto reg_rw_pub_counter_id               = node_registry->expose("cyphal.pub.counter.id", {}, counter_port_id);
const auto reg_rw_pub_counter_update_period_ms = node_registry->expose("cyphal.pub.counter.update_period_ms", {}, counter_update_period_ms);
NodeInfo API
The NodeInfo API provides relevant node information:
static auto node_info = node_hdl.create_node_info
(
  /* uavcan.node.Version.1.0 protocol_version */
  1, 0,
  /* uavcan.node.Version.1.0 hardware_version */
  1, 0,
  /* uavcan.node.Version.1.0 software_version */
  0, 1,
  /* saturated uint64 software_vcs_revision_id */
#ifdef CYPHAL_NODE_INFO_GIT_VERSION
  CYPHAL_NODE_INFO_GIT_VERSION,
#else
  0,
#endif
  /* saturated uint8[16] unique_id */
  OpenCyphalUniqueId(),
  /* saturated uint8[<=50] name */
  "107-systems.l3xz-leg-ctrl"
);
Note: This release contains many breaking changes. If you are a corporation needing help to upgrade your 107-Arduino-Cyphal based application feel free to reach out to me (e-mail).