Skip to content
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

Updating to libcanard v1.0 #48

Merged
merged 12 commits into from
Nov 17, 2020
Merged
112 changes: 4 additions & 108 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![General Formatting Checks](https://github.com/107-systems/107-Arduino-UAVCAN/workflows/General%20Formatting%20Checks/badge.svg)](https://github.com/107-systems/107-Arduino-UAVCAN/actions?workflow=General+Formatting+Checks)
[![Spell Check](https://github.com/107-systems/107-Arduino-UAVCAN/workflows/Spell%20Check/badge.svg)](https://github.com/107-systems/107-Arduino-UAVCAN/actions?workflow=Spell+Check)

Arduino library for providing a convenient C++ interface for accessing [UAVCAN](https://uavcan.org/) ([v1.0-alpha](https://uavcan.org/specification/UAVCAN_Specification_v1.0-alpha.pdf)) utilizing [libcanard](https://github.com/UAVCAN/libcanard).
Arduino library for providing a convenient C++ interface for accessing [UAVCAN](https://uavcan.org/) ([v1.0-beta](https://uavcan.org/specification/UAVCAN_Specification_v1.0-beta.pdf)) utilizing [libcanard](https://github.com/UAVCAN/libcanard).

<p align="center">
<a href="https://github.com/107-systems/107-Arduino-Viper"><img src="extras/logo/viper-logo.jpg" width="40%"></a>
Expand All @@ -17,30 +17,7 @@ This library works for
* [ArduinoCore-samd](https://github.com/arduino/ArduinoCore-samd): [`Arduino Zero`](https://store.arduino.cc/arduino-zero), [`MKR 1000`](https://store.arduino.cc/arduino-mkr1000-wifi), [`MKR WiFi 1010`](https://store.arduino.cc/arduino-mkr-wifi-1010), [`Nano 33 IoT`](https://store.arduino.cc/arduino-nano-33-iot), [`MKR GSM 1400`](https://store.arduino.cc/arduino-mkr-gsm-1400-1415), [`MKR NB 1500`](https://store.arduino.cc/arduino-mkr-nb-1500-1413), [`MKR WAN 1300/1310`](https://store.arduino.cc/mkr-wan-1310) :heavy_check_mark:
* [ArduinoCore-mbed](https://github.com/arduino/ArduinoCore-mbed): [`Portenta H7`](https://store.arduino.cc/portenta-h7), [`Nano 33 BLE`](https://store.arduino.cc/arduino-nano-33-ble) :heavy_check_mark:

## Example
### Subscribe
```C++
#include <ArduinoUAVCAN.h>
/* ... */
ArduinoUAVCAN uavcan(13, nullptr);
/* ... */
void setup() {
/* ... */
uavcan.subscribe<Heartbeat_1_0>(onHeatbeat_1_0_Received);
}
/* ... */
void onHeatbeat_1_0_Received(CanardTransfer const & transfer, ArduinoUAVCAN & /* uavcan */)
{
Heartbeat_1_0 const hb = Heartbeat_1_0::create(transfer);

char msg[64];
snprintf(msg, 64, "ID %02X, Uptime = %d, Health = %d, Mode = %d, VSSC = %d", transfer.remote_node_id, hb.data.uptime, hb.data.health, hb.data.mode, hb.data.vendor_specific_status_code);

Serial.println(msg);
}
```

### Publish
### Example
```C++
#include <ArduinoUAVCAN.h>
/* ... */
Expand Down Expand Up @@ -69,86 +46,5 @@ bool transmitCanFrame(CanardFrame const & frame) {
}
```

### Service Client
```C++
#include <ArduinoUAVCAN.h>
/* ... */
ArduinoUAVCAN uavcan(13, transmitCanFrame);
/* ... */
void setup() {
/* ... */
/* Request some coffee. */
char const cmd_param[] = "I want a double espresso with cream";
ExecuteCommand_1_0::Request req:
req.data.command = 0xCAFE;
req.data.parameter_length = std::min(cmd_param.length(), uavcan_node_ExecuteCommand_1_0_Request_parameter_array_capacity());
std::copy(cmd_param.c_str(), cmd_param.c_str() + req.data.parameter_length, req.data.parameter);

uavcan.request<ExecuteCommand_1_0::Request, ExecuteCommand_1_0::Response>(req, 27 /* remote node id */, onExecuteCommand_1_0_Response_Received);
}

void loop() {
/* Transmit all enqeued CAN frames */
while(uavcan.transmitCanFrame()) { }
}
/* ... */
void onExecuteCommand_1_0_Response_Received(CanardTransfer const & transfer, ArduinoUAVCAN & /* uavcan */) {
ExecuteCommand_1_0::Response const rsp = ExecuteCommand_1_0::Response::create(transfer);

if (rsp.data.status == ExecuteCommand_1_0::Response::Status::SUCCESS)
Serial.println("Coffee successful retrieved");
else
Serial.println("Error when retrieving coffee");
}
/* ... */
bool transmitCanFrame(CanardFrame const & frame) {
/* ... */
}
```


### Service Server
```C++
#include <ArduinoUAVCAN.h>
/* ... */
ArduinoUAVCAN uavcan(13, transmitCanFrame);
/* ... */
void setup() {
/* ... */
/* Subscribe to incoming service requests */
uavcan.subscribe<ExecuteCommand_1_0::Request>(onExecuteCommand_1_0_Request_Received);
}

void loop() {
/* Transmit all enqeued CAN frames */
while(uavcan.transmitCanFrame()) { }
}
/* ... */
void onExecuteCommand_1_0_Request_Received(CanardTransfer const & transfer, ArduinoUAVCAN & uavcan) {
ExecuteCommand_1_0::Request req = ExecuteCommand_1_0::Request::create(transfer);
ExecuteCommand_1_0::Response rsp;

if (req.command() == 0xCAFE) {
rsp = ExecuteCommand_1_0::Response::Status::SUCCESS;
uavcan.respond(rsp, transfer.remote_node_id, transfer.transfer_id);
} else {
rsp = ExecuteCommand_1_0::Response::Status::NOT_AUTHORIZED;
uavcan.respond(rsp, transfer.remote_node_id, transfer.transfer_id);
}
}
/* ... */
bool transmitCanFrame(CanardFrame const & frame) {
/* ... */
}
```

### How to generate C header files from DSDL via nunavut/nnvg
```bash
cd ~
https://github.com/UAVCAN/nunavut && cd nunavut
python3.8 -m pip install .

cd ~
git clone https://github.com/UAVCAN/public_regulated_data_types && cd public_regulated_data_types
nnvg --outdir include --templates c_jinja --pp-trim-trailing-whitespace -e .h uavcan
```
### Contribution
Please take a look at the [wiki](https://github.com/107-systems/107-Arduino-UAVCAN/wiki) for notes pertaining development of `107-Arduino-UAVCAN`.
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ void onReceiveBufferFull(CanardFrame const & frame)

void onHeatbeat_1_0_Received(CanardTransfer const & transfer, ArduinoUAVCAN & /* uavcan */)
{
Heartbeat_1_0 const hb = Heartbeat_1_0::create(transfer);
Heartbeat_1_0 const hb = Heartbeat_1_0::deserialize(transfer);

char msg[64];
snprintf(msg, 64,
Expand Down
8 changes: 5 additions & 3 deletions examples/UAVCAN-Service-Client/UAVCAN-Service-Client.ino
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@ void setup()
char const cmd_param[] = "I want a double espresso with cream";
ExecuteCommand_1_0::Request req;
req.data.command = 0xCAFE;
req.data.parameter_length = std::min(sizeof(cmd_param), uavcan_node_ExecuteCommand_1_0_Request_parameter_array_capacity());
std::copy(cmd_param, cmd_param + req.data.parameter_length, req.data.parameter);
req.data.parameter.count = std::min(strlen(cmd_param), (size_t)uavcan_node_ExecuteCommand_Request_1_0_parameter_ARRAY_CAPACITY_);
std::copy(cmd_param,
cmd_param + req.data.parameter.count,
req.data.parameter.elements);

uavcan.request<ExecuteCommand_1_0::Request, ExecuteCommand_1_0::Response>(req, 27 /* remote node id */, onExecuteCommand_1_0_Response_Received);
}
Expand Down Expand Up @@ -123,7 +125,7 @@ bool transmitCanFrame(CanardFrame const & frame)

void onExecuteCommand_1_0_Response_Received(CanardTransfer const & transfer, ArduinoUAVCAN & /* uavcan */)
{
ExecuteCommand_1_0::Response const rsp = ExecuteCommand_1_0::Response::create(transfer);
ExecuteCommand_1_0::Response const rsp = ExecuteCommand_1_0::Response::deserialize(transfer);

if (rsp.data.status == arduino::_107_::uavcan::to_integer(ExecuteCommand_1_0::Response::Status::SUCCESS))
Serial.println("Coffee successful retrieved");
Expand Down
2 changes: 1 addition & 1 deletion examples/UAVCAN-Service-Server/UAVCAN-Service-Server.ino
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ bool transmitCanFrame(CanardFrame const & frame)

void onExecuteCommand_1_0_Request_Received(CanardTransfer const & transfer, ArduinoUAVCAN & uavcan)
{
ExecuteCommand_1_0::Request req = ExecuteCommand_1_0::Request::create(transfer);
ExecuteCommand_1_0::Request req = ExecuteCommand_1_0::Request::deserialize(transfer);

if (req.data.command == 0xCAFE)
{
Expand Down
18 changes: 11 additions & 7 deletions extras/test/src/test_ExecuteCommand_ServiceClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ static CanardNodeID const REMOTE_NODE_ID = 27;
**************************************************************************************/

static util::CanFrameVect can_frame_vect;
static uavcan_node_ExecuteCommand_1_0_Response response;
static uavcan_node_ExecuteCommand_Response_1_0 response;

/**************************************************************************************
* PRIVATE FUNCTION DEFINITION
Expand All @@ -43,7 +43,7 @@ static bool transmitCanFrame(CanardFrame const & f)

static void onExecuteCommand_1_0_Response_Received(CanardTransfer const & transfer, ArduinoUAVCAN & /* uavcan */)
{
ExecuteCommand_1_0::Response const received_response = ExecuteCommand_1_0::Response::create(transfer);
ExecuteCommand_1_0::Response const received_response = ExecuteCommand_1_0::Response::deserialize(transfer);
response.status = received_response.data.status;
}

Expand All @@ -53,14 +53,16 @@ static void onExecuteCommand_1_0_Response_Received(CanardTransfer const & transf

TEST_CASE("A '435.ExecuteCommand.1.0' request is sent to a server", "[execute-command-client-01]")
{
uavcan_node_ExecuteCommand_1_0_Response_init(&response);
uavcan_node_ExecuteCommand_Response_1_0_initialize_(&response);
ArduinoUAVCAN uavcan(util::LOCAL_NODE_ID, transmitCanFrame);

std::string const cmd_1_param = "I want a double espresso with cream";
ExecuteCommand_1_0::Request req_1;
req_1.data.command = 0xCAFE;
req_1.data.parameter_length = std::min(cmd_1_param.length(), uavcan_node_ExecuteCommand_1_0_Request_parameter_array_capacity());
std::copy(cmd_1_param.c_str(), cmd_1_param.c_str() + req_1.data.parameter_length, req_1.data.parameter);
req_1.data.parameter.count = std::min(cmd_1_param.length(), (size_t)uavcan_node_ExecuteCommand_Request_1_0_parameter_ARRAY_CAPACITY_);
std::copy(cmd_1_param.c_str(),
cmd_1_param.c_str() + req_1.data.parameter.count,
req_1.data.parameter.elements);


REQUIRE(uavcan.request<ExecuteCommand_1_0::Request, ExecuteCommand_1_0::Response>(req_1, REMOTE_NODE_ID, onExecuteCommand_1_0_Response_Received) == true);
Expand Down Expand Up @@ -105,8 +107,10 @@ TEST_CASE("A '435.ExecuteCommand.1.0' request is sent to a server", "[execute-co
std::string const cmd_2_param = "I do not need coffee anymore";
ExecuteCommand_1_0::Request req_2;
req_2.data.command = 0xDEAD;
req_2.data.parameter_length = std::min(cmd_2_param.length(), uavcan_node_ExecuteCommand_1_0_Request_parameter_array_capacity());
std::copy(cmd_2_param.c_str(), cmd_2_param.c_str() + req_2.data.parameter_length, req_2.data.parameter);
req_2.data.parameter.count = std::min(cmd_2_param.length(), (size_t)uavcan_node_ExecuteCommand_Request_1_0_parameter_ARRAY_CAPACITY_);
std::copy(cmd_2_param.c_str(),
cmd_2_param.c_str() + req_2.data.parameter.count,
req_2.data.parameter.elements);

REQUIRE(uavcan.request<ExecuteCommand_1_0::Request, ExecuteCommand_1_0::Response>(req_2, REMOTE_NODE_ID, onExecuteCommand_1_0_Response_Received) == true);
/* Transmit all the CAN frames. */
Expand Down
18 changes: 10 additions & 8 deletions extras/test/src/test_ExecuteCommand_ServiceServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ static CanardNodeID const REMOTE_NODE_ID = 27;
**************************************************************************************/

static util::CanFrame response_can_frame;
static uavcan_node_ExecuteCommand_1_0_Request request;
static uavcan_node_ExecuteCommand_Request_1_0 request;

/**************************************************************************************
* PRIVATE FUNCTION DEFINITION
Expand All @@ -43,14 +43,16 @@ static bool transmitCanFrame(CanardFrame const & f)

static void onExecuteCommand_1_0_Request_Received(CanardTransfer const & transfer, ArduinoUAVCAN & uavcan)
{
ExecuteCommand_1_0::Request received_request = ExecuteCommand_1_0::Request::create(transfer);
ExecuteCommand_1_0::Request received_request = ExecuteCommand_1_0::Request::deserialize(transfer);

/* The next 2 lines are just for test purposes, you won't
* have them in your real application.
*/
request.command = received_request.data.command;
request.parameter_length = received_request.data.parameter_length;
std::copy(received_request.data.parameter, received_request.data.parameter + received_request.data.parameter_length, request.parameter);
request.parameter.count = received_request.data.parameter.count;
std::copy(received_request.data.parameter.elements,
received_request.data.parameter.elements + received_request.data.parameter.count,
request.parameter.elements);

/* Deal with the command ... */

Expand All @@ -67,7 +69,7 @@ static void onExecuteCommand_1_0_Request_Received(CanardTransfer const & transfe

TEST_CASE("A '435.ExecuteCommand.1.0' request is received from a client", "[execute-command-server-01]")
{
uavcan_node_ExecuteCommand_1_0_Request_init(&request);
uavcan_node_ExecuteCommand_Request_1_0_initialize_(&request);
ArduinoUAVCAN uavcan(util::LOCAL_NODE_ID, transmitCanFrame);

/* Subscribe to incoming server requests. */
Expand Down Expand Up @@ -98,13 +100,13 @@ TEST_CASE("A '435.ExecuteCommand.1.0' request is received from a client", "[exec
REQUIRE(request.command == 0xCAFE);
std::string const EXP_CMD_PARAM_STR = "I want a double espresso with cream";
std::vector<uint8_t> const EXP_CMD_PARAM_VECT(EXP_CMD_PARAM_STR.begin(), EXP_CMD_PARAM_STR.end());
REQUIRE(request.parameter_length == EXP_CMD_PARAM_VECT.size());
REQUIRE(std::equal(EXP_CMD_PARAM_VECT.begin(), EXP_CMD_PARAM_VECT.end(), request.parameter) == true);
REQUIRE(request.parameter.count == EXP_CMD_PARAM_VECT.size());
REQUIRE(std::equal(EXP_CMD_PARAM_VECT.begin(), EXP_CMD_PARAM_VECT.end(), request.parameter.elements) == true);

/* We should now have one CAN frame in the transmit pipeline */
REQUIRE(uavcan.transmitCanFrame() == true);

/* Check if the sent response is identical with what we expect. */
REQUIRE(response_can_frame.id == 0x126CCD8D);
REQUIRE(response_can_frame.data == std::vector<uint8_t>{0x02, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE0});
REQUIRE(response_can_frame.data == std::vector<uint8_t>{0x02, 0xE0});
}
20 changes: 10 additions & 10 deletions extras/test/src/test_Heartbeat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ static bool transmitCanFrame(CanardFrame const & f)

static void onHeatbeat_1_0_Received(CanardTransfer const & transfer, ArduinoUAVCAN & /* uavcan */)
{
Heartbeat_1_0 const received_hb = Heartbeat_1_0::create(transfer);
Heartbeat_1_0 const received_hb = Heartbeat_1_0::deserialize(transfer);

hb_node_id = transfer.remote_node_id;
hb_data.uptime = received_hb.data.uptime;
Expand All @@ -65,10 +65,10 @@ TEST_CASE("A '32085.Heartbeat.1.0.uavcan' message is sent", "[heartbeat-01]")
uavcan.publish(hb);
while(uavcan.transmitCanFrame()) { }
/*
* pyuavcan publish 32085.uavcan.node.Heartbeat.1.0 '{uptime: 9876, health: 0, mode: 3, vendor_specific_status_code: 5}' --tr='CAN(can.media.socketcan.SocketCANMedia("vcan0",8),13)'
* pyuavcan publish 7509.uavcan.node.Heartbeat.1.0 '{uptime: 9876, health: {value: 0}, mode: {value: 3}, vendor_specific_status_code: 5}' --tr='CAN(can.media.socketcan.SocketCANMedia("vcan0",8),13)'
*/
REQUIRE(can_frame.id == 0x107D550D);
REQUIRE(can_frame.data == std::vector<uint8_t>{0x94, 0x26, 0x00, 0x00, 0xAC, 0x00, 0x00, 0xE0});
REQUIRE(can_frame.data == std::vector<uint8_t>{0x94, 0x26, 0x00, 0x00, 0x00, 0x03, 0x05, 0xE0});

hb.data.uptime = 9881;
hb = Heartbeat_1_0::Health::ADVISORY;
Expand All @@ -77,34 +77,34 @@ TEST_CASE("A '32085.Heartbeat.1.0.uavcan' message is sent", "[heartbeat-01]")
uavcan.publish(hb);
while(uavcan.transmitCanFrame()) { }
/*
* pyuavcan publish 32085.uavcan.node.Heartbeat.1.0 '{uptime: 9881, health: 1, mode: 2, vendor_specific_status_code: 123}' --tr='CAN(can.media.socketcan.SocketCANMedia("vcan0",8),13)'
* pyuavcan publish 7509.uavcan.node.Heartbeat.1.0 '{uptime: 9881, health: {value: 1}, mode: {value: 2}, vendor_specific_status_code: 123}' --tr='CAN(can.media.socketcan.SocketCANMedia("vcan0",8),13)'
*/
REQUIRE(can_frame.id == 0x107D550D);
REQUIRE(can_frame.data == std::vector<uint8_t>{0x99, 0x26, 0x00, 0x00, 0x69, 0x0F, 0x00, 0xE1});
REQUIRE(can_frame.data == std::vector<uint8_t>{0x99, 0x26, 0x00, 0x00, 0x01, 0x02, 0x7B, 0xE1});
}

TEST_CASE("A '32085.Heartbeat.1.0.uavcan' message is received", "[heartbeat-02]")
{
uavcan_node_Heartbeat_1_0_init(&hb_data);
uavcan_node_Heartbeat_1_0_initialize_(&hb_data);
ArduinoUAVCAN uavcan(util::LOCAL_NODE_ID, nullptr);

REQUIRE(uavcan.subscribe<Heartbeat_1_0>(onHeatbeat_1_0_Received));

/* Create:
* pyuavcan publish 32085.uavcan.node.Heartbeat.1.0 '{uptime: 1337, health: 2, mode: 7, vendor_specific_status_code: 42}' --tr='CAN(can.media.socketcan.SocketCANMedia("vcan0",8),59)'
* pyuavcan publish 7509.uavcan.node.Heartbeat.1.0 '{uptime: 1337, health: {value: 2}, mode: {value: 2}, vendor_specific_status_code: 42}' --tr='CAN(can.media.socketcan.SocketCANMedia("vcan0",8),59)'
*
* Capture:
* sudo modprobe vcan
* sudo ip link add dev vcan0 type vcan
* sudo ip link set up vcan0
* candump -decaxta vcan0
*/
std::vector<uint8_t> const data{0x39, 0x05, 0x00, 0x00, 0x5E, 0x05, 0x00, 0xE1};
std::vector<uint8_t> const data{0x39, 0x05, 0x00, 0x00, 0x02, 0x02, 0x2A, 0xE1};
uavcan.onCanFrameReceived(util::toCanardFrame(0x107D553B, data));

REQUIRE(hb_node_id == 59);
REQUIRE(hb_data.uptime == 1337);
REQUIRE(hb_data.health == arduino::_107_::uavcan::to_integer(Heartbeat_1_0::Health::CAUTION));
REQUIRE(hb_data.mode == arduino::_107_::uavcan::to_integer(Heartbeat_1_0::Mode::OFFLINE));
REQUIRE(hb_data.health.value == arduino::_107_::uavcan::to_integer(Heartbeat_1_0::Health::CAUTION));
REQUIRE(hb_data.mode.value == arduino::_107_::uavcan::to_integer(Heartbeat_1_0::Mode::MAINTENANCE));
REQUIRE(hb_data.vendor_specific_status_code == 42);
}
6 changes: 3 additions & 3 deletions extras/test/src/test_ID.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ static bool transmitCanFrame(CanardFrame const & f)

static void onID_1_0_Received(CanardTransfer const & transfer, ArduinoUAVCAN & /* uavcan */)
{
ID_1_0<ID_PORT_ID> const received_id = ID_1_0<ID_PORT_ID>::create(transfer);
ID_1_0<ID_PORT_ID> const received_id = ID_1_0<ID_PORT_ID>::deserialize(transfer);

id_node_id = transfer.remote_node_id;
id.value = received_id.data.value;
Expand All @@ -65,13 +65,13 @@ TEST_CASE("A 'ID.1.0.uavcan' message is sent", "[id-01]")
/*
* pyuavcan publish 1337.uavcan.node.ID.1.0 '{value: 65}' --tr='CAN(can.media.socketcan.SocketCANMedia("vcan0",8),13)'
*/
REQUIRE(can_frame.id == 0x1005390D);
REQUIRE(can_frame.id == 0x1065390D);
REQUIRE(can_frame.data == std::vector<uint8_t>{0x41, 0x00, 0xE0});
}

TEST_CASE("A 'ID.1.0.uavcan' message is received", "[id-02]")
{
uavcan_node_ID_1_0_init(&id);
uavcan_node_ID_1_0_initialize_(&id);
ArduinoUAVCAN uavcan(util::LOCAL_NODE_ID, transmitCanFrame);

REQUIRE(uavcan.subscribe<ID_1_0<ID_PORT_ID>>(onID_1_0_Received));
Expand Down
Loading