| /**************************************************************************** |
| * apps/examples/opencyphal/canard_main.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <canard.h> |
| #include <canard_dsdl.h> |
| #include <o1heap.h> |
| |
| #include <sched.h> |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <time.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <errno.h> |
| |
| #include <net/if.h> |
| #include <sys/time.h> |
| #include <sys/ioctl.h> |
| #include <sys/socket.h> |
| |
| #include <poll.h> |
| |
| #include <nuttx/can.h> |
| #include <netpacket/can.h> |
| |
| #include "socketcan.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* Application constants */ |
| |
| #define APP_VERSION_MAJOR 1 |
| #define APP_VERSION_MINOR 0 |
| #define APP_NODE_NAME CONFIG_EXAMPLES_LIBOPENCYPHAL_APP_NODE_NAME |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* Arena for memory allocation, used by the library */ |
| |
| #define O1_HEAP_SIZE CONFIG_EXAMPLES_LIBOPENCYPHAL_NODE_MEM_POOL_SIZE |
| |
| /* Temporary development UAVCAN topic service ID to publish/subscribe from */ |
| #define PORT_ID 4421 |
| #define TOPIC_SIZE 512 |
| |
| O1HeapInstance *my_allocator; |
| static uint8_t uavcan_heap[O1_HEAP_SIZE] |
| __attribute__((aligned(O1HEAP_ALIGNMENT))); |
| |
| /* Node status variables */ |
| |
| static bool g_canard_daemon_started; |
| |
| static uint8_t my_message_transfer_id; |
| |
| struct pollfd fd; |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: memallocate |
| * |
| * Description: |
| * |
| ****************************************************************************/ |
| |
| static void *memallocate(CanardInstance *const ins, const size_t amount) |
| { |
| (void) ins; |
| return o1heapAllocate(my_allocator, amount); |
| } |
| |
| /**************************************************************************** |
| * Name: memfree |
| * |
| * Description: |
| * |
| ****************************************************************************/ |
| |
| static void memfree(CanardInstance *const ins, void *const pointer) |
| { |
| (void) ins; |
| o1heapFree(my_allocator, pointer); |
| } |
| |
| /**************************************************************************** |
| * Name: getmonotonictimestampusec |
| * |
| * Description: |
| * |
| ****************************************************************************/ |
| |
| uint64_t getmonotonictimestampusec(void) |
| { |
| struct timespec ts; |
| |
| memset(&ts, 0, sizeof(ts)); |
| |
| if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) |
| { |
| abort(); |
| } |
| |
| return ts.tv_sec * 1000000ULL + ts.tv_nsec / 1000ULL; |
| } |
| |
| /**************************************************************************** |
| * Name: process1hztasks |
| * |
| * Description: |
| * This function is called at 1 Hz rate from the main loop. |
| * |
| ****************************************************************************/ |
| |
| void process1hztasks(CanardInstance *ins, uint64_t timestamp_usec) |
| { |
| CanardMicrosecond transmission_deadline = |
| getmonotonictimestampusec() + 1000 * 10; |
| |
| const CanardTransfer transfer = |
| { |
| .timestamp_usec = transmission_deadline, /* Zero if transmission deadline is not limited. */ |
| .priority = CanardPriorityNominal, |
| .transfer_kind = CanardTransferKindMessage, |
| .port_id = 1234, /* This is the subject-ID. */ |
| .remote_node_id = CANARD_NODE_ID_UNSET, /* Messages cannot be unicast, so use UNSET. */ |
| .transfer_id = my_message_transfer_id, |
| .payload_size = 47, |
| .payload = "\x2D\x00" |
| "Sancho, it strikes me thou art in great fear.", |
| }; |
| |
| ++my_message_transfer_id; /* The transfer-ID shall be incremented after every transmission on this subject. */ |
| int32_t result = canardTxPush(ins, &transfer); |
| |
| if (result < 0) |
| { |
| /* An error has occurred: either an argument is invalid or we've |
| * ran out of memory. It is possible to statically prove that an |
| * out-of-memory will never occur for a given application if the |
| * heap is sized correctly; for background, refer to the Robson's |
| * Proof and the documentation for O1Heap. |
| */ |
| |
| fprintf(stderr, "Transmit error %ld\n", result); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: processTxRxOnce |
| * |
| * Description: |
| * Transmits all frames from the TX queue, receives up to one frame. |
| * |
| ****************************************************************************/ |
| |
| void processtxrxonce(CanardInstance *ins, canardsocketinstance *sock_ins, |
| int timeout_msec) |
| { |
| int32_t result; |
| |
| /* Transmitting, Look at the top of the TX queue. */ |
| |
| for (const CanardFrame *txf = NULL; (txf = canardTxPeek(ins)) != NULL; ) |
| { |
| if (txf->timestamp_usec > getmonotonictimestampusec()) /* Check if the frame has timed out. */ |
| { |
| if (socketcantransmit(sock_ins, txf) == 0) /* Send the frame. Redundant interfaces may be used here. */ |
| { |
| /* If the driver is busy, break and retry later. */ |
| |
| break; |
| } |
| } |
| |
| canardTxPop(ins); /* Remove the frame from the queue after it's transmitted. */ |
| ins->memory_free(ins, (CanardFrame *)txf); |
| } |
| |
| /* Poll receive */ |
| |
| if (poll(&fd, 1, timeout_msec) <= 0) |
| { |
| return; |
| } |
| |
| /* Receiving */ |
| |
| CanardFrame received_frame; |
| |
| socketcanreceive(sock_ins, &received_frame); |
| |
| CanardTransfer receive; |
| result = canardRxAccept(ins, |
| &received_frame, /* The CAN frame received from the bus. */ |
| 0, /* If the transport is not redundant, use 0. */ |
| &receive); |
| |
| if (result < 0) |
| { |
| /* An error has occurred: either an argument is invalid or we've ran |
| * out of memory. It is possible to statically prove that an |
| * out-of-memory will never occur for a given application |
| * if the heap is sized correctly; for background, refer to the |
| * Robson's Proof and the documentation for O1Heap. |
| * Reception of an invalid frame is NOT an error. |
| */ |
| |
| fprintf(stderr, "Receive error %ld\n", result); |
| } |
| else if (result == 1) |
| { |
| /* A transfer has been received, process it */ |
| |
| printf("Receive UAVCAN port id%d TODO process me\n", |
| receive.port_id); |
| |
| ins->memory_free(ins, (void *)receive.payload); |
| } |
| else |
| { |
| /* Nothing to do. |
| * The received frame is either invalid or it's a non-last frame |
| * of a multi-frame transfer. |
| * Reception of an invalid frame is NOT reported as an error |
| * because it is not an error. |
| */ |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: canard_daemon |
| * |
| * Description: |
| * |
| ****************************************************************************/ |
| |
| static int canard_daemon(int argc, char *argv[]) |
| { |
| int errval = 0; |
| int can_fd = 0; |
| int pub = 1; |
| |
| if (argc > 2) |
| { |
| for (int args = 2; args < argc; args++) |
| { |
| if (!strcmp(argv[args], "canfd")) |
| { |
| can_fd = 1; |
| } |
| |
| if (!strcmp(argv[args], "pub")) |
| { |
| pub = 1; |
| } |
| |
| if (!strcmp(argv[args], "sub")) |
| { |
| pub = 0; |
| } |
| } |
| } |
| |
| my_allocator = o1heapInit(&uavcan_heap, O1_HEAP_SIZE); |
| |
| if (my_allocator == NULL) |
| { |
| printf("o1heapInit failed with size %d\n", O1_HEAP_SIZE); |
| errval = 2; |
| goto errout_with_dev; |
| } |
| |
| CanardInstance ins = canardInit(&memallocate, &memfree); |
| |
| if (can_fd) |
| { |
| ins.mtu_bytes = CANARD_MTU_CAN_FD; |
| } |
| else |
| { |
| ins.mtu_bytes = CANARD_MTU_CAN_CLASSIC; |
| } |
| |
| ins.node_id = (pub ? CONFIG_EXAMPLES_LIBOPENCYPHAL_NODE_ID : |
| CONFIG_EXAMPLES_LIBOPENCYPHAL_NODE_ID + 1); |
| |
| /* Open the CAN device for reading */ |
| |
| canardsocketinstance sock_ins; |
| socketcanopen(&sock_ins, CONFIG_EXAMPLES_LIBOPENCYPHAL_DEV, can_fd); |
| |
| /* setup poll fd */ |
| |
| fd.fd = sock_ins.s; |
| fd.events = POLLIN; |
| |
| if (sock_ins.s < 0) |
| { |
| printf("canard_daemon: ERROR: open %s failed: %d\n", |
| CONFIG_EXAMPLES_LIBOPENCYPHAL_DEV, errno); |
| errval = 2; |
| goto errout_with_dev; |
| } |
| |
| printf("canard_daemon: canard initialized\n"); |
| printf("start node (ID: %d Name: %s MTU: %d PUB: %d TOPIC_SIZE: %d)\n", |
| ins.node_id, APP_NODE_NAME, ins.mtu_bytes, pub, TOPIC_SIZE); |
| |
| CanardRxSubscription heartbeat_subscription; |
| (void) canardRxSubscribe(&ins, /* Subscribe to messages uavcan.node.Heartbeat. */ |
| CanardTransferKindMessage, |
| 32085, /* The fixed Subject-ID of the Heartbeat message type (see DSDL definition). */ |
| 7, /* The maximum payload size (max DSDL object size) from the DSDL definition. */ |
| CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC, |
| &heartbeat_subscription); |
| |
| CanardRxSubscription my_subscription; |
| (void) canardRxSubscribe(&ins, |
| CanardTransferKindMessage, |
| PORT_ID, /* The Service-ID to subscribe to. */ |
| TOPIC_SIZE, /* The maximum payload size (max DSDL object size). */ |
| CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC, |
| &my_subscription); |
| |
| g_canard_daemon_started = true; |
| uint64_t next_1hz_service_at = getmonotonictimestampusec(); |
| |
| for (; ; ) |
| { |
| processtxrxonce(&ins, &sock_ins, 10); |
| |
| const uint64_t ts = getmonotonictimestampusec(); |
| |
| if (ts >= next_1hz_service_at) |
| { |
| next_1hz_service_at += 1000000; |
| process1hztasks(&ins, ts); |
| } |
| } |
| |
| errout_with_dev: |
| |
| g_canard_daemon_started = false; |
| printf("canard_daemon: Terminating!\n"); |
| fflush(stdout); |
| return errval; |
| } |
| |
| /**************************************************************************** |
| * Name: canard_main |
| * |
| * Description: |
| * |
| ****************************************************************************/ |
| |
| int opencyphal_main(int argc, FAR char *argv[]) |
| { |
| int ret; |
| |
| printf("canard_main: Starting canard_daemon\n"); |
| |
| if (g_canard_daemon_started) |
| { |
| printf("canard_main: receive and send task already running\n"); |
| return EXIT_SUCCESS; |
| } |
| |
| ret = task_create("canard_daemon", |
| CONFIG_EXAMPLES_LIBOPENCYPHAL_DAEMON_PRIORITY, |
| CONFIG_EXAMPLES_LIBOPENCYPHAL_DAEMON_STACK_SIZE, |
| canard_daemon, argv); |
| |
| if (ret < 0) |
| { |
| int errcode = errno; |
| printf("canard_main: ERROR: Failed to start canard_daemon: %d\n", |
| errcode); |
| return EXIT_FAILURE; |
| } |
| |
| printf("canard_main: canard_daemon started\n"); |
| return EXIT_SUCCESS; |
| } |