/* A simple example DroneCAN node, this implements 2 features: - announces on the bus using NodeStatus at 1Hz - answers GetNodeInfo requests This example uses socketcan on Linux for CAN transport Example usage: ./simple_node vcan0 */ /* This example application is distributed under the terms of CC0 (public domain dedication). More info: https://creativecommons.org/publicdomain/zero/1.0/ */ #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include // include the headers for the generated DroneCAN messages from the // dronecan_dsdlc compiler #include /* libcanard library instance and a memory pool for it to use */ static CanardInstance canard; static uint8_t memory_pool[1024]; /* in this simple example we will use a fixed CAN node ID. This would need to be a parameter or use dynamic node allocation in a real application */ #define MY_NODE_ID 97 /* hold our node status as a static variable. It will be updated on any errors */ static struct uavcan_protocol_NodeStatus node_status; /* get a 64 bit monotonic timestamp in microseconds since start. This is platform specific */ static uint64_t micros64(void) { static uint64_t first_us; struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); uint64_t tus = (uint64_t)(ts.tv_sec * 1000000ULL + ts.tv_nsec / 1000ULL); if (first_us == 0) { first_us = tus; } return tus - first_us; } /* get a 16 byte unique ID for this node, this should be based on the CPU unique ID or other unique ID */ static void getUniqueID(uint8_t id[16]) { memset(id, 0, 16); FILE *f = fopen("/etc/machine-id", "r"); if (f) { fread(id, 1, 16, f); fclose(f); } } /* handle a GetNodeInfo request */ static void handle_GetNodeInfo(CanardInstance *ins, CanardRxTransfer *transfer) { printf("GetNodeInfo request from %d\n", transfer->source_node_id); uint8_t buffer[UAVCAN_PROTOCOL_GETNODEINFO_RESPONSE_MAX_SIZE]; struct uavcan_protocol_GetNodeInfoResponse pkt; memset(&pkt, 0, sizeof(pkt)); node_status.uptime_sec = micros64() / 1000000ULL; pkt.status = node_status; // fill in your major and minor firmware version pkt.software_version.major = 1; pkt.software_version.minor = 2; pkt.software_version.optional_field_flags = 0; pkt.software_version.vcs_commit = 0; // should put git hash in here // should fill in hardware version pkt.hardware_version.major = 2; pkt.hardware_version.minor = 3; getUniqueID(pkt.hardware_version.unique_id); strncpy((char*)pkt.name.data, "SimpleNode", sizeof(pkt.name.data)); pkt.name.len = strnlen((char*)pkt.name.data, sizeof(pkt.name.data)); uint16_t total_size = uavcan_protocol_GetNodeInfoResponse_encode(&pkt, buffer); canardRequestOrRespond(ins, transfer->source_node_id, UAVCAN_PROTOCOL_GETNODEINFO_SIGNATURE, UAVCAN_PROTOCOL_GETNODEINFO_ID, &transfer->transfer_id, transfer->priority, CanardResponse, &buffer[0], total_size); } /* This callback is invoked by the library when a new message or request or response is received. */ static void onTransferReceived(CanardInstance *ins, CanardRxTransfer *transfer) { // switch on data type ID to pass to the right handler function if (transfer->transfer_type == CanardTransferTypeRequest) { // check if we want to handle a specific service request switch (transfer->data_type_id) { case UAVCAN_PROTOCOL_GETNODEINFO_ID: { handle_GetNodeInfo(ins, transfer); break; } } } } /* This callback is invoked by the library when it detects beginning of a new transfer on the bus that can be received by the local node. If the callback returns true, the library will receive the transfer. If the callback returns false, the library will ignore the transfer. All transfers that are addressed to other nodes are always ignored. This function must fill in the out_data_type_signature to be the signature of the message. */ static bool shouldAcceptTransfer(const CanardInstance *ins, uint64_t *out_data_type_signature, uint16_t data_type_id, CanardTransferType transfer_type, uint8_t source_node_id) { if (transfer_type == CanardTransferTypeRequest) { // check if we want to handle a specific service request switch (data_type_id) { case UAVCAN_PROTOCOL_GETNODEINFO_ID: { *out_data_type_signature = UAVCAN_PROTOCOL_GETNODEINFO_REQUEST_SIGNATURE; return true; } } } // we don't want any other messages return false; } /* send the 1Hz NodeStatus message. This is what allows a node to show up in the DroneCAN GUI tool and in the flight controller logs */ static void send_NodeStatus(void) { uint8_t buffer[UAVCAN_PROTOCOL_NODESTATUS_MAX_SIZE]; node_status.uptime_sec = micros64() / 1000000ULL; node_status.health = UAVCAN_PROTOCOL_NODESTATUS_HEALTH_OK; node_status.mode = UAVCAN_PROTOCOL_NODESTATUS_MODE_OPERATIONAL; node_status.sub_mode = 0; // put whatever you like in here for display in GUI node_status.vendor_specific_status_code = 1234; uint32_t len = uavcan_protocol_NodeStatus_encode(&node_status, buffer); // we need a static variable for the transfer ID. This is // incremeneted on each transfer, allowing for detection of packet // loss static uint8_t transfer_id; canardBroadcast(&canard, UAVCAN_PROTOCOL_NODESTATUS_SIGNATURE, UAVCAN_PROTOCOL_NODESTATUS_ID, &transfer_id, CANARD_TRANSFER_PRIORITY_LOW, buffer, len); } /* This function is called at 1 Hz rate from the main loop. */ static void process1HzTasks(uint64_t timestamp_usec) { /* Purge transfers that are no longer transmitted. This can free up some memory */ canardCleanupStaleTransfers(&canard, timestamp_usec); /* Transmit the node status message */ send_NodeStatus(); } /* Transmits all frames from the TX queue, receives up to one frame. */ static void processTxRxOnce(SocketCANInstance *socketcan, int32_t timeout_msec) { // Transmitting for (const CanardCANFrame* txf = NULL; (txf = canardPeekTxQueue(&canard)) != NULL;) { const int16_t tx_res = socketcanTransmit(socketcan, txf, 0); if (tx_res < 0) { // Failure - drop the frame canardPopTxQueue(&canard); } else if (tx_res > 0) // Success - just drop the frame { canardPopTxQueue(&canard); } else // Timeout - just exit and try again later { break; } } // Receiving CanardCANFrame rx_frame; const uint64_t timestamp = micros64(); const int16_t rx_res = socketcanReceive(socketcan, &rx_frame, timeout_msec); if (rx_res < 0) { (void)fprintf(stderr, "Receive error %d, errno '%s'\n", rx_res, strerror(errno)); } else if (rx_res > 0) // Success - process the frame { canardHandleRxFrame(&canard, &rx_frame, timestamp); } } /* main program entry point */ int main(int argc, char** argv) { if (argc < 2) { (void)fprintf(stderr, "Usage:\n" "\t%s \n", argv[0]); return 1; } /* * Initializing the CAN backend driver; in this example we're using SocketCAN */ SocketCANInstance socketcan; const char* const can_iface_name = argv[1]; int16_t res = socketcanInit(&socketcan, can_iface_name); if (res < 0) { (void)fprintf(stderr, "Failed to open CAN iface '%s'\n", can_iface_name); return 1; } /* Initializing the Libcanard instance. */ canardInit(&canard, memory_pool, sizeof(memory_pool), onTransferReceived, shouldAcceptTransfer, NULL); canardSetLocalNodeID(&canard, MY_NODE_ID); /* Run the main loop. */ uint64_t next_1hz_service_at = micros64(); while (true) { processTxRxOnce(&socketcan, 10); const uint64_t ts = micros64(); if (ts >= next_1hz_service_at) { next_1hz_service_at += 1000000ULL; process1HzTasks(ts); } } return 0; }