1
- # OpenVMP
1
+ # remote_serial
2
2
3
- [ ![ License] ( ./license .svg )] ( ./LICENSE.txt )
3
+ [ ![ License] ( ./apache20 .svg )] ( ./LICENSE.txt )
4
4
5
- This package is a part of [ the OpenVMP project] ( https://github.com/openvmp/openvmp ) .
6
- But it's designed to be universal and usable independently from the rest of OpenVMP or in a combination with select OpenVMP packages.
5
+ This is an ultimate implementation of serial line interface for ROS2.
7
6
8
- ## ROS2 serial driver
9
-
10
- This is an ultimate C++ implementation of serial port driver for ROS2.
11
-
12
- It can be used either as a library or a standalone process. In both cases it
13
- provides ROS2 interfaces for inter-process access, introspection and
7
+ It provides ROS2 interfaces for inter-process access, introspection and
14
8
debugging.
15
9
It performs wisely in case of serial line saturation in any of
16
10
the I/O directions, minimizing data loses and blocking behavior,
17
- ensuring maximum performance.
11
+ ensuring optimum performance.
12
+
13
+ This package implements a generic purpose serial port driver using
14
+ character device files.
15
+ However this package also implements most of the logic required for other
16
+ serial line drivers (see the "Examples" section below).
17
+
18
+ ## ROS2 interfaces
19
+
20
+ ### Parameters
21
+
22
+ All classes:
23
+
24
+ - ` serial_prefix ` : the prefix to ROS2 interfaces exposed by this driver
25
+ ("/serial/< ; node-name> ; " by default)
26
+
27
+ The factory class:
18
28
29
+ - ` serial_is_remote ` : instructs whether
30
+ to instantiate the driver locally or to connect to a remote instance
31
+ ("true" by default)
19
32
20
- ### Basic setup
33
+ Serial port driver:
34
+
35
+ - ` serial_dev_name ` : the character device path ("/dev/ttyS0" by default)
36
+ - serial port settings:
37
+ - ` serial_skip_init ` : skip the port initialization (ignore the below parameters)
38
+ - ` serial_baud_rate ` : baud rate ("115200" by default)
39
+ - ` serial_data ` : data bits ("8" by default)
40
+ - ` serial_parity ` : parity bit ("false" by default)
41
+ - ` serial_stop ` : stop bits ("1" by default)
42
+ - ` serial_flow_control ` : hardware flow control ("false" by default)
43
+ - ` serial_sw_flow_control ` : software flow control ("false" by default)
44
+ - ` serial_bs ` : the size of read buffer ("1024" by default)
45
+
46
+ ### Topics
47
+
48
+ #### Subscribers
49
+
50
+ - ` <namespace>/<serial_prefix>/inject/input ` : injects the given bytes into
51
+ the serial line as if it was written to the line by the driver
52
+ - ` <namespace>/<serial_prefix>/inject/output ` : injects the given bytes into
53
+ the serial line as if it was received by the driver from the line
54
+
55
+ #### Publishers
56
+
57
+ - ` <namespace>/<serial_prefix>/inspect/input ` : publishes data
58
+ received by the driver from the line
59
+ - ` <namespace>/<serial_prefix>/inspect/output ` : publishes data
60
+ sent by the driver to the line
61
+
62
+ ## Basic setup
21
63
22
64
Launch it as a separate node for each serial port:
23
65
24
66
```
25
- $ ros2 run ros2_serial ros2_serial_standalone
67
+ $ ros2 run remote_serial remote_serial_standalone
26
68
```
27
69
28
70
or
29
71
30
72
```
31
- $ ros2 run ros2_serial ros2_serial_standalone \
73
+ $ ros2 run remote_serial remote_serial_standalone \
32
74
--ros-args \
33
75
--remap serial:__node:=serial_com1 \
76
+ -p serial_is_remote:=false \
34
77
-p serial_prefix:=/serial/com1 \
35
78
-p serial_dev_name:=/dev/ttyS0 \
36
79
-p serial_baud_rate:=115200 \
@@ -42,15 +85,15 @@ $ ros2 run ros2_serial ros2_serial_standalone \
42
85
43
86
``` mermaid
44
87
flowchart TB
45
- cli["$ ros2 topic echo /serial/com1"] -. "DDS" .-> topic_serial[/ROS2 interfaces:\n/serial/com1/.../]
88
+ cli["<p style='text-align:left'> $ ros2 topic echo /serial/com1/inspect/input\n$ ros2 topic echo /serial/com1/inspect/output\n$ ros2 topic pub /serial/com1/inject/output \\n std_msgs/msg/UInt8MultiArray {'data':'ATH+CHUP\r\ n'}</p> "] -. "DDS" .-> topic_serial[/ROS2 interfaces:\n/serial/com1/.../]
46
89
app["Your process"] -- "DDS\n(with context switch)" --> topic_serial
47
- subgraph serial["Process: ros2_serial_standalone "]
90
+ subgraph serial["Process: remote_serial_standalone "]
48
91
topic_serial --> driver["Serial port driver"]
49
92
end
50
93
driver --> file{{"Character device: /dev/ttyS0"}}
51
94
```
52
95
53
- ### Advanced setup
96
+ ## Advanced setup
54
97
55
98
The more advanced setup is to initialize it as a separate node in the very executable which will be communicating with the port all the time
56
99
(e.g. a MODBUS RTU implementation).
@@ -61,9 +104,9 @@ See an example of such a setup in the Modbus RTU package:
61
104
62
105
``` mermaid
63
106
flowchart TB
64
- cli_serial["# Serial debugging\n$ ros2 topic echo /serial"] -. "DDS" ..-> topic_serial[/ROS2 interfaces:\n/serial/.../]
107
+ cli_serial["<p style='text-align:left'> # Serial debugging and troubleshooting \n$ ros2 topic echo /serial/inspect/input\n...</p> "] -. "DDS" ..-> topic_serial[/ROS2 interfaces:\n/serial/.../]
65
108
subgraph modbus_exe["Your process"]
66
- subgraph serial["Library: ros2_serial "]
109
+ subgraph serial["Library: remote_serial "]
67
110
topic_serial --> driver["Serial port driver"]
68
111
end
69
112
code["Your code"] -- "DDS\n(potentially without\ncontext switch)" --> topic_serial
@@ -73,16 +116,18 @@ flowchart TB
73
116
```
74
117
75
118
76
- ### Implementation details
119
+ ## Implementation details
120
+
121
+ The following diagram shows the high level view on the internals of this package:
77
122
78
123
``` mermaid
79
124
flowchart TB
80
125
owner["Native API"]
81
126
external["ROS2 Interface API"]
82
127
83
- subgraph node["ros2_serial ::Node"]
128
+ subgraph node["remote_serial ::Node"]
84
129
85
- subgraph interface_ros2["ros2_serial::InterfaceRemote "]
130
+ subgraph interface_ros2["remote_serial::Interface "]
86
131
subgraph topics["Topics: /serial"]
87
132
published_input[//inspect/input/]
88
133
published_output[//inspect/output/]
@@ -94,11 +139,11 @@ flowchart TB
94
139
end
95
140
end
96
141
97
- subgraph worker["ros2_serial ::Port"]
142
+ subgraph worker["remote_serial ::Port"]
98
143
output_queue["Output Queue"]
99
144
thread["Worker thread\nrunning select()"]
100
145
101
- subgraph interface_native["ros2_serial ::Implementation"]
146
+ subgraph interface_native["remote_serial ::Implementation"]
102
147
subgraph register_input_cb["register_input_cb()"]
103
148
input_cb["input_cb()"]
104
149
end
@@ -133,3 +178,85 @@ flowchart TB
133
178
thread -- "output" --> fd -- "output" --> file
134
179
135
180
```
181
+
182
+ ## Implementing serial line drivers
183
+
184
+ The serial line drivers implement the base class provided by this package.
185
+ For C++ drivers, that means that they use
186
+ ` remote_serial::Implementation ` as a parent class.
187
+ Its constructor requires an instance of ` rclcpp::Node ` to read the
188
+ parameters from.
189
+
190
+ <i >
191
+ Note:
192
+ There can be more than one instance of ` remote_serial::Implementation `
193
+ per node. However they will all be controlled by the same parameters.
194
+ Though the default value of the parameter ` serial_prefix ` can be passed
195
+ into each individual constructor which is sufficient for common use cases.
196
+ </i >
197
+ <br />
198
+ <br />
199
+
200
+ The following methods need to be implemented by each driver:
201
+
202
+ ``` c++
203
+ public:
204
+ // output writes bytes to the serial line
205
+ virtual void output (const std::string &) override;
206
+
207
+ // register_input_cb is used to set the callback function which
208
+ // is called every time data is received from the serial line
209
+ virtual void register_input_cb(void (* )(const std::string &msg,
210
+ void * user_data),
211
+ void * user_data) override;
212
+ ```
213
+
214
+ The users can chose one of three ways to interact with child classes of `remote_serial::Implementation`:
215
+
216
+ - Link with the driver directly to make native API calls:
217
+
218
+ ```c++
219
+ auto serial = \
220
+ std::make_shared<driver_package::DriverClass>(node, "/serial");
221
+ serial->output("ATH+CHUP\r\n");
222
+ ```
223
+
224
+ In this case the driver is running within the same process and it is
225
+ destroyed when the ` serial ` object is destroyed.
226
+
227
+ - Link with ` remote_serial ` to make ROS2 interface (DDS) calls
228
+ (locally or over a network):
229
+
230
+ ``` c++
231
+ auto serial = \
232
+ std::make_shared<remote_serial::RemoteInterface>(node, " /serial" );
233
+ serial->output ("ATH+CHUP\r\n");
234
+ ```
235
+
236
+ In this case the driver can be elsewhere within DDS' reach
237
+ (same process or another side of the globe).
238
+
239
+ - Let the runtime make the choice between the above two options:
240
+
241
+ ```c++
242
+ auto serial = \
243
+ remote_serial::Factory::New(node, "/serial");
244
+ // or
245
+ auto serial = \
246
+ driver_package::Factory::New(node, "/serial");
247
+ ```
248
+
249
+ In this case the boolean value of the parameter ` serial_is_remote `
250
+ determines whether the driver is instantiated locally or if a remote
251
+ interface is used to reach the driver instantiated elsewhere.
252
+ Please, note, the trivial class ` driver_package::Factory `
253
+ (similar to ` remote_serial::Factory ` ) has to be written
254
+ to support this use case.
255
+
256
+ ## Examples
257
+
258
+ ### remote_microcontroller
259
+
260
+ See the UART implementation in
261
+ [ remote_microcontroller] ( https://github.com/openvmp/microcontroller )
262
+ for an example of a driver that implements ` remote_serial ` .
0 commit comments