|
| 1 | +# LibUDPard demo application |
| 2 | + |
| 3 | +This demo application is a usage demonstrator for [LibUDPard](https://github.com/OpenCyphal-Garage/libudpard) --- |
| 4 | +a compact Cyphal/UDP implementation for high-integrity systems written in C99. |
| 5 | +It implements a simple Cyphal node that showcases the following features: |
| 6 | + |
| 7 | +- Fixed port-ID and non-fixed port-ID publishers. |
| 8 | +- Fixed port-ID and non-fixed port-ID subscribers. |
| 9 | +- Fixed port-ID RPC server. |
| 10 | +- Plug-and-play node-ID allocation unless it is configured statically. |
| 11 | +- Fast Cyphal Register API and non-volatile storage for the persistent registers. |
| 12 | +- Support for redundant network interfaces. |
| 13 | + |
| 14 | +This document will walk you through the process of building, running, and evaluating the demo |
| 15 | +on a GNU/Linux-based OS. |
| 16 | +It can be easily ported to another platform, such as a baremetal MCU, |
| 17 | +by replacing the POSIX socket API and stdio with suitable alternatives; |
| 18 | +for details, please consult with `udp.h` and `storage.h`. |
| 19 | + |
| 20 | +## Preparation |
| 21 | + |
| 22 | +Install the [Yakut](https://github.com/OpenCyphal/yakut) CLI tool, |
| 23 | +Wireshark with the [Cyphal plugins](https://github.com/OpenCyphal/wireshark_plugins), |
| 24 | +and ensure you have CMake and a C11 compiler. |
| 25 | +Build the demo: |
| 26 | + |
| 27 | +```shell |
| 28 | +git clone --recursive https://github.com/OpenCyphal/demos |
| 29 | +cd demos/libudpard_demo |
| 30 | +mkdir build && cd build |
| 31 | +cmake .. && make |
| 32 | +``` |
| 33 | + |
| 34 | +## Running |
| 35 | + |
| 36 | +At the first launch the default parameters will be used. |
| 37 | +Upon their modification, the state will be saved on the filesystem in the current working directory |
| 38 | +-- you will see a new file appear per parameter (register). |
| 39 | + |
| 40 | +Per the default settings, the node will use only the local loopback interface. |
| 41 | +If it were an embedded system, it could be designed to run a DHCP client to configure the local interface(s) |
| 42 | +automatically and then use that configuration. |
| 43 | + |
| 44 | +Run the node: |
| 45 | + |
| 46 | +```shell |
| 47 | +./demo |
| 48 | +``` |
| 49 | + |
| 50 | +It will print a few informational messages and then go silent. |
| 51 | +With the default configuration being missing, the node will be attempting to perform a plug-and-play node-ID allocation |
| 52 | +by sending allocation requests forever until an allocation response is received. |
| 53 | +You can see this activity --- PnP requests being published --- using Wireshark; |
| 54 | +to exclude unnecessary traffic, use the following BPF expression: |
| 55 | + |
| 56 | +```bpf |
| 57 | +udp and dst net 239.0.0.0 mask 255.0.0.0 and dst port 9382 |
| 58 | +``` |
| 59 | + |
| 60 | +<img src="docs/wireshark-pnp.png" alt="Wireshark capture of a PnP request"> |
| 61 | + |
| 62 | +It will keep doing this forever until it got an allocation response from the node-ID allocator. |
| 63 | +Note that most high-integrity systems would always assign static node-ID instead of relying on this behavior |
| 64 | +to ensure deterministic behaviors at startup. |
| 65 | + |
| 66 | +To let our application complete the PnP allocation stage, we launch the PnP allocator implemented in Yakut monitor |
| 67 | +(this can be done in any working directory): |
| 68 | + |
| 69 | +```shell |
| 70 | +UAVCAN__UDP__IFACE="127.0.0.1" UAVCAN__NODE__ID=$(yakut accommodate) y mon -P allocation_table.db |
| 71 | +``` |
| 72 | + |
| 73 | +This will create a new file called `allocation_table.db` containing, well, the node-ID allocation table. |
| 74 | +Once the allocation is done (it takes a couple of seconds), the application will announce this by printing a message, |
| 75 | +and then the normal operation will be commenced. |
| 76 | +The Yakut monitor will display the following picture: |
| 77 | + |
| 78 | +<img src="docs/yakut-monitor-pnp.png" alt="Yakut monitor output after PnP allocation"> |
| 79 | + |
| 80 | +The newly allocated node-ID value will be stored in the `uavcan.node.id` register, |
| 81 | +but it will not be committed into the non-volatile storage until the node is commanded to restart. |
| 82 | +This is because storage I/O is not compatible with real-time execution, |
| 83 | +so the storage is only accessed during startup of the node (to read the values from the non-volatile memory) |
| 84 | +and immediately before shutdown (to commit values into the non-volatile memory). |
| 85 | + |
| 86 | +It is best to keep the Yakut monitor running and execute all subsequent commands in other shell sessions. |
| 87 | + |
| 88 | +Suppose we want the node to use another network interface aside from the local loopback `127.0.0.1`. |
| 89 | +This is done by entering additional local interface addresses in the `uavcan.udp.iface` register separated by space. |
| 90 | +You can do this with the help of Yakut as follows (here we are assuming that the allocated node-ID is 65532): |
| 91 | + |
| 92 | +```shell |
| 93 | +export UAVCAN__UDP__IFACE="127.0.0.1" # Pro tip: move these export statements into a shell file and source it. |
| 94 | +export UAVCAN__NODE__ID=$(yakut accommodate) |
| 95 | +y r 65532 uavcan.udp.iface "127.0.0.1 192.168.1.200" # Update the local addresses to match your setup. |
| 96 | +``` |
| 97 | + |
| 98 | +To let the new configuration take effect, the node has to be restarted. |
| 99 | +Before restart the register values will be committed into the non-volatile storage; |
| 100 | +configuration files will appear in the current working directory of the application. |
| 101 | + |
| 102 | +```shell |
| 103 | +y cmd 65532 restart |
| 104 | +``` |
| 105 | + |
| 106 | +The Wireshark capture will show that the node is now sending data via two interfaces concurrently |
| 107 | +(or however many you configured). |
| 108 | + |
| 109 | +Next we will evaluate the application-specific publisher and subscriber. |
| 110 | +The application can receive messages of type `uavcan.primitive.array.Real32.1` |
| 111 | +and re-publish them with the reversed order of the elements. |
| 112 | +The corresponding publisher and subscriber ports are both named `my_data`, |
| 113 | +and their port-ID registers are named `uavcan.pub.my_data.id` and `uavcan.sub.my_data.id`, |
| 114 | +per standard convention. |
| 115 | +As these ports are not configured yet, the node is unable to make use of them. |
| 116 | +Configure them manually as follows |
| 117 | +(you can also use a YAML file with the `y rb` command for convenience; for more info see Yakut documentation): |
| 118 | + |
| 119 | +```shell |
| 120 | +y r 65532 uavcan.pub.my_data.id 1001 # You can pick arbitrary values here. |
| 121 | +y r 65532 uavcan.sub.my_data.id 1002 |
| 122 | +``` |
| 123 | + |
| 124 | +Then restart the node, and the Yakut monitor will display the newly configured ports in the connectivity matrix: |
| 125 | + |
| 126 | +<img src="docs/yakut-monitor-data.png" alt="Yakut monitor showing the data topics in the connectivity matrix"> |
| 127 | + |
| 128 | +To evaluate this part of the application, |
| 129 | +subscribe to the topic it is supposed to publish on using the Yakut subscriber tool: |
| 130 | + |
| 131 | +```shell |
| 132 | +y sub +M 1001:uavcan.primitive.array.real32 |
| 133 | +``` |
| 134 | + |
| 135 | +Use another terminal to publish some data on the topic that the application is subscribed to |
| 136 | +(you can use a joystick to generate data dynamically; see the Yakut documentation for more info): |
| 137 | + |
| 138 | +```shell |
| 139 | +y pub 1002:uavcan.primitive.array.real32 '[50, 60, 70]' |
| 140 | +``` |
| 141 | + |
| 142 | +The subscriber we launched earlier will show the messages published by our node: |
| 143 | + |
| 144 | +```yaml |
| 145 | +1001: |
| 146 | + _meta_: {ts_system: 1693821518.340156, ts_monotonic: 1213296.516168, source_node_id: 65532, transfer_id: 183, priority: nominal, dtype: uavcan.primitive.array.Real32.1.0} |
| 147 | + value: [70.0, 60.0, 50.0] |
| 148 | +``` |
| 149 | +
|
| 150 | +The current status of the memory allocators can be checked by reading the corresponding diagnostic register |
| 151 | +as shown below. |
| 152 | +Diagnostic registers are a powerful tool for building advanced introspection interfaces and can be used to expose |
| 153 | +and even modify arbitrary internal states of the application over the network. |
| 154 | +
|
| 155 | +```yaml |
| 156 | +y r 65532 sys.info.mem |
| 157 | +``` |
| 158 | + |
| 159 | +Publishing and subscribing using different remote machines (instead of using the local loopback interface) |
| 160 | +is left as an exercise to the reader. |
| 161 | +Cyphal/UDP is a masterless peer protocol that does not require manual configuration of the networking infrastructure. |
| 162 | +As long as the local interface addresses are set correctly, the Cyphal distributed system will just work out of the box. |
| 163 | +Thus, you can simply run the same publisher and subscriber commands on different computers |
| 164 | +and see the demo node behave the same way. |
| 165 | + |
| 166 | +The only difference to keep in mind is that Yakut monitor may fail to display all network traffic unless the computer |
| 167 | +it is running on is connected to a SPAN port (mirrored port) of the network switch. |
| 168 | +This is because by default, IGMP-compliant switches will not forward multicast packets into ports for which |
| 169 | +no members for the corresponding multicast groups are registered. |
| 170 | + |
| 171 | +To reset the node configuration back to defaults, use this: |
| 172 | + |
| 173 | +```shell |
| 174 | +y cmd 65532 factory_reset |
| 175 | +``` |
| 176 | + |
| 177 | +As the application is not allowed to access the storage I/O during runtime, |
| 178 | +the factory reset will not take place until the node is restarted. |
| 179 | +Once restarted, the configuration files will disappear from the current working directory. |
| 180 | + |
| 181 | +## Porting |
| 182 | + |
| 183 | +Just read the code. Focus your attention on `udp.c` and `storage.c`. |
0 commit comments