*`Updated: 2023/07/22`*
## ROS2 Communication
### Communication Interfaces
- **Topic** (sensor informations)
- Publisher: IMU, GPS, Ultrasound, etc.
- Subscriber: control server or AI compute unit
- **Service** (Ying's ID server bridging, time sync and safety server communication)
- Server:
- **TimeSyncServer**: Set time reference server
- **SafetyServer**: Manage devices emergency
- Client:
- **TimeSyncNode**: Can be inherited by ros2 node. Inherited from rclcpp::Node, setting Timesync client and operates background.
- **SafetyNode**: Can be inherited by ros2 node. Inherited from rclcpp::Node, setting SafetyServer client and operates background.
### Topic Message Type (vehicle_interfaces)
- **Header.msg** (the header for all message type in vehicle_interfaces)
:::spoiler
- **Members**
- *priority*: declares message priority, from high to low: `Control`, `Sensor` and `Info`
- *devicetype*: declares message-sending device type
- *stamptype*: declares message timestamp type, e.g., no sync, non-utc synced and utc synced.
- *stamp*: message timestamp, including seconds and nano seconds
- *ref_publish_time_ms*: the setting of message publish intervals.
- **Definitions**
```vim
uint8 PRIORITY_CONTROL = 0
uint8 PRIORITY_SENSOR = 1
uint8 PRIORITY_INFO = 2
uint8 DEVTYPE_NONE = 0
uint8 DEVTYPE_IDTABLE = 1
uint8 DEVTYPE_JOYSTICK = 2
uint8 DEVTYPE_STEERINGWHEEL = 3
uint8 DEVTYPE_MOTOR = 4
uint8 DEVTYPE_BATTERY = 5
uint8 DEVTYPE_IMU = 6
uint8 DEVTYPE_GPS = 7
uint8 DEVTYPE_ENVIRONMENT = 8
uint8 DEVTYPE_RF = 9
uint8 DEVTYPE_ULTRASONIC = 10
uint8 DEVTYPE_IMAGE = 11
uint8 DEVTYPE_MOTOR_AXLE = 12
uint8 DEVTYPE_MOTOR_STEERING = 13
uint8 DEVTYPE_MOTOR_BRAKING = 14
uint8 STAMPTYPE_NO_SYNC = 0
uint8 STAMPTYPE_NONE_UTC_SYNC = 1
uint8 STAMPTYPE_UTC_SYNC = 2
float32 VERSION = 1.3
```
- **Variables**
```vim
uint8 priority 2
uint8 device_type 0
string device_id
uint64 frame_id 0
uint8 stamp_type 0
builtin_interfaces/Time stamp
float32 ref_publish_time_ms
```
- **Examples**
- C++
```cpp
#include "vehicle_interfaces/msg/wheel_state.hpp"
// The remaining code is under rclcpp::Node class
auto msg = vehicle_interfaces::msg::WheelState();
msg.header.priority = vehicle_interfaces::msg::Header::PRIORITY_CONTROL;
msg.header.device_type = vehicle_interfaces::msg::Header::DEVTYPE_JOYSTICK;
msg.header.device_id = this->nodeName_;
msg.header.stamp_type = vehicle_interfaces::msg::Header::STAMPTYPE_NONE_UTC_SYNC
msg.header.stamp = this->get_clock()->now();
```
- Python3
```python
from vehicle_interfaces.msg import WheelState
# The remaining code is under rclpy.Node class
msg = WheelState()
msg.header.priority = msg.header.PRIORITY_CONTROL
msg.header.device_type = msg.header.DEVTYPE_JOYSTICK
msg.header.device_id = self.nodeName_
msg.header.stamp_type = msg.header.STAMPTYPE_NONE_UTC_SYNC
msg.header.stamp = self.get_clock().now().to_msg()
```
:::
- **Distance.msg** (for distance measurement sensor, e.g., ultrasonic sensor)
:::spoiler
- **Definitions**
```vim
uint8 UNIT_MICRO = 0
uint8 UNIT_MILLI = 1
uint8 UNIT_CENTI = 2
uint8 UNIT_METER = 3
```
- **Variables**
```vim
vehicle_interfaces/Header header
uint8 unit_type 1
float32 min
float32 max
float32 distance
```
- **Examples**
- C++
```cpp
#include "vehicle_interfaces/msg/distance.hpp"
// The remaining code is under rclcpp::Node class
static u_int64_t frame_id = 0;
auto msg = vehicle_interfaces::msg::Distance();
msg.header.priority = vehicle_interfaces::msg::Header::PRIORITY_SENSOR;
msg.header.device_type = vehicle_interfaces::msg::Header::DEVTYPE_ULTRASONIC;
msg.header.device_id = this->nodeName_;
msg.header.frame_id = frame_id++;
msg.header.stamp_type = vehicle_interfaces::msg::Header::STAMPTYPE_NONE_UTC_SYNC
msg.header.stamp = this->get_clock()->now();
msg.unit_type = vehicle_interfaces::msg::Distance::UNIT_METER;
msg.min = 0.2;
msg.max = 8.0;
msg.distance = dist;
```
- Python3
```python
from vehicle_interfaces.msg import Distance
# The remaining code is under rclpy.Node class
msg = Distance()
msg.header.priority = msg.header.PRIORITY_SENSOR
msg.header.device_type = msg.header.DEVTYPE_ULTRASONIC
msg.header.device_id = self.nodeName_
msg.header.frame_id = self.frame_id_# Defined in __init__
self.frame_id_ += 1
msg.header.stamp_type = msg.header.STAMPTYPE_NONE_UTC_SYNC
msg.header.stamp = self.get_clock().now().to_msg()
msg.unit_type = msg.UNIT_METER
msg.min = 0.2
msg.max = 8.0
msg.distance = dist
```
:::
- **Environment.msg** (for environment sensor, e.g., temperature, humidity and atmosphere pressure sensors)
:::spoiler
- **Definitions**
```vim
uint8 UNIT_TEMP_CELSIUS = 0
uint8 UNIT_TEMP_FAHRENHEIT = 1
uint8 UNIT_PRESS_MBAR = 0
uint8 UNIT_PRESS_HPA = 4
```
- **Variables**
```vim
vehicle_interfaces/Header header
uint8 unit_type 0
float32 temperature
float32 relative_humidity
float32 pressure
```
- **Examples**
- C++
```cpp
#include "vehicle_interfaces/msg/environment.hpp"
// The remaining code is under rclcpp::Node class
static u_int64_t frame_id = 0;
auto msg = vehicle_interfaces::msg::Environment();
msg.header.priority = vehicle_interfaces::msg::Header::PRIORITY_SENSOR;
msg.header.device_type = vehicle_interfaces::msg::Header::DEVTYPE_ENVIRONMENT;
msg.header.device_id = this->nodeName_;
msg.header.frame_id = frame_id++;
msg.header.stamp_type = vehicle_interfaces::msg::Header::STAMPTYPE_NONE_UTC_SYNC;
msg.header.stamp = this->get_clock()->now();
msg.unit_type = vehicle_interfaces::msg::Environment::UNIT_TEMP_CELSIUS | vehicle_interfaces::msg::Environment::UNIT_PRESS_MBAR;
msg.temperature = temp;
msg.relative_humidity = rh;
msg.pressure = press;
```
- Python3
```python
from vehicle_interfaces.msg import Environment
# The remaining code is under rclpy.Node class
msg = Environment()
msg.header.priority = msg.header.PRIORITY_SENSOR
msg.header.device_type = msg.header.DEVTYPE_ENVIRONMENT
msg.header.device_id = self.nodeName_
msg.header.frame_id = self.frame_id_# Defined in __init__
self.frame_id_ += 1
msg.header.stamp_type = msg.header.STAMPTYPE_NONE_UTC_SYNC
msg.header.stamp = self.get_clock().now().to_msg()
msg.unit_type = msg.UNIT_TEMP_CELSIUS | msg.UNIT_PRESS_MBAR
msg.temperature = temp
msg.relative_humidity = rh
msg.pressure = press
```
:::
- **GPS.msg** (for GNSS localization modules)
:::spoiler
- **Definitions**
```vim
uint8 GPS_NONE = 0
uint8 GPS_STABLE = 1
uint8 GPS_RTK = 2
```
- **Variables**
```vim
vehicle_interfaces/Header header
uint8 gps_status 0
float32 latitude 0
float32 longitude 0
```
- **Examples**
- C++
```cpp
#include "vehicle_interfaces/msg/gps.hpp"
// The remaining code is under rclcpp::Node class
static u_int64_t frame_id = 0;
auto msg = vehicle_interfaces::msg::GPS();
msg.header.priority = vehicle_interfaces::msg::Header::PRIORITY_SENSOR;
msg.header.device_type = vehicle_interfaces::msg::Header::DEVTYPE_GPS;
msg.header.device_id = this->nodeName_;
msg.header.frame_id = frame_id++;
msg.header.stamp_type = vehicle_interfaces::msg::Header::STAMPTYPE_NONE_UTC_SYNC;
msg.header.stamp = this->get_clock()->now();
msg.gps_status = vehicle_interfaces::msg::GPS::GPS_STABLE;
msg.latitude = lat;
msg.longitude = lon;
```
- Python3
```python
from vehicle_interfaces.msg import GPS
# The remaining code is under rclpy.Node class
msg = GPS()
msg.header.priority = msg.header.PRIORITY_SENSOR
msg.header.device_type = msg.header.DEVTYPE_GPS
msg.header.device_id = self.nodeName_
msg.header.frame_id = self.frame_id_# Defined in __init__
self.frame_id_ += 1
msg.header.stamp_type = msg.header.STAMPTYPE_NONE_UTC_SYNC
msg.header.stamp = self.get_clock().now().to_msg()
msg.gps_status = GPS.GPS_STABLE
msg.latitude = lat
msg.longitude = lon
```
:::
- **IDTable.msg** (for the table retrieved from ID server)
:::spoiler
- **Variables**
```vim
vehicle_interfaces/Header header
string[] idtable
```
- **Examples**
- C++
```cpp
#include "vehicle_interfaces/msg/id_table.hpp"
// The remaining code is under rclcpp::Node class
static u_int64_t frame_id = 0;
auto msg = vehicle_interfaces::msg::IDTable();
msg.header.priority = vehicle_interfaces::msg::Header::PRIORITY_INFO;
msg.header.device_type = vehicle_interfaces::msg::Header::DEVTYPE_IDTABLE;
msg.header.device_id = this->nodeName_;
msg.header.frame_id = frame_id++;
msg.header.stamp_type = vehicle_interfaces::msg::Header::STAMPTYPE_NONE_UTC_SYNC;
msg.header.stamp = this->get_clock()->now();
msg.idtable = idtable;// std::vector<std::string>
```
- Python3
```python
from vehicle_interfaces.msg import IDTable
# The remaining code is under rclpy.Node class
msg = IDTable()
msg.header.priority = msg.header.PRIORITY_INFO
msg.header.device_type = msg.header.DEVTYPE_IDTABLE
msg.header.device_id = self.nodeName_
msg.header.frame_id = self.frame_id_# Defined in __init__
self.frame_id_ += 1
msg.header.stamp_type = msg.header.STAMPTYPE_NONE_UTC_SYNC
msg.header.stamp = self.get_clock().now().to_msg()
msg.idtable = table# list[]
```
:::
- **IMU.msg** (for IMU sensor, e.g., gyroscope, accelerometer and compass)
:::spoiler
- **Definitions**
```vim
uint8 UNIT_ROT_RAD = 0
uint8 UNIT_ROT_DPS = 1
uint8 UNIT_ACC_GS = 0
uint8 UNIT_ACC_MS2 = 4
```
- **Variables**
```vim
vehicle_interfaces/Header header
uint8 unit_type 0
float32[4] orientation
float32[3] angular_velocity
float32[3] linear_acceleration
```
- **Examples**
- C++
```cpp
#include "vehicle_interfaces/msg/imu.hpp"
// The remaining code is under rclcpp::Node class
static u_int64_t frame_id = 0;
auto msg = vehicle_interfaces::msg::IMU();
msg.header.priority = vehicle_interfaces::msg::Header::PRIORITY_SENSOR;
msg.header.device_type = vehicle_interfaces::msg::Header::DEVTYPE_IMU;
msg.header.device_id = this->nodeName_;
msg.header.frame_id = frame_id++;
msg.header.stamp_type = vehicle_interfaces::msg::Header::STAMPTYPE_NONE_UTC_SYNC;
msg.header.stamp = this->get_clock()->now();
msg.unit_type = vehicle_interfaces::msg::IMU::UNIT_ACC_GS | vehicle_interfaces::msg::IMU::UNIT_ROT_DPS;
msg.orientation = quaternion;
msg.linear_acceleration = acc;
msg.angular_velocity = gyro;
```
- Python3
```python
from vehicle_interfaces.msg import IMU
# The remaining code is under rclpy.Node class
msg = IMU()
msg.header.priority = msg.header.PRIORITY_SENSOR
msg.header.device_type = msg.header.DEVTYPE_IMU
msg.header.device_id = self.nodeName_
msg.header.frame_id = self.frame_id_# Defined in __init__
self.frame_id_ += 1
msg.header.stamp_type = msg.header.STAMPTYPE_NONE_UTC_SYNC
msg.header.stamp = self.get_clock().now().to_msg()
msg.unit_type = msg.UNIT_ACC_GS | msg.UNIT_ROT_RAD
msg.orientation[0] = quaternion[0]
msg.orientation[1] = quaternion[1]
msg.orientation[2] = quaternion[2]
msg.orientation[3] = quaternion[3]
msg.linear_acceleration[0] = acc['x']
msg.linear_acceleration[1] = acc['y']
msg.linear_acceleration[2] = acc['z']
msg.angular_velocity[0] = gyro['x']
msg.angular_velocity[1] = gyro['y']
msg.angular_velocity[2] = gyro['z']
```
:::
- **MotorAxle.msg** (for motor status)
:::spoiler
- **Definitions**
```vim
```
- **Variables**
```vim
vehicle_interfaces/Header header
int8 dir
float32 pwm
int8 parking
```
- **Examples**
- C++
```cpp
#include "vehicle_interfaces/msg/motor_axle.hpp"
// The remaining code is under rclcpp::Node class
static u_int64_t frame_id = 0;
auto msg = vehicle_interfaces::msg::MotorAxle();
msg.header.priority = vehicle_interfaces::msg::Header::PRIORITY_INFO;
msg.header.device_type = vehicle_interfaces::msg::Header::DEVTYPE_MOTOR_AXLE;
msg.header.device_id = this->nodeName_;
msg.header.frame_id = frame_id++;
msg.header.stamp_type = vehicle_interfaces::msg::Header::STAMPTYPE_NONE_UTC_SYNC;
msg.header.stamp = this->get_clock()->now();
msg.dir = direction;// Motor spin direction
msg.pwm = pwm;
msg.parking = parking;
```
- Python3
```python
from vehicle_interfaces.msg import MotorAxle
# The remaining code is under rclpy.Node class
msg = MotorAxle()
msg.header.priority = msg.header.PRIORITY_SENSOR
msg.header.device_type = msg.header.DEVTYPE_MOTOR_AXLE
msg.header.device_id = self.nodeName_
msg.header.frame_id = self.frame_id_# Defined in __init__
self.frame_id_ += 1
msg.header.stamp_type = msg.header.STAMPTYPE_NO_SYNC
msg.header.stamp = self.get_clock().now().to_msg()
RunClient.ros2DictLock.acquire()
tmp = RunClient.axleDict# Motor status dictionary in RunClient.py
RunClient.ros2DictLock.release()
msg.dir = tmp["dir"]
msg.pwm = float(tmp["pwm"])
msg.parking = tmp["parking"]
```
:::
- **MotorSteering.msg** (for steering motor status)
:::spoiler
- **Definitions**
```vim
uint8 UNIT_ANG = 0
uint8 UNIT_DIST = 1
```
- **Variables**
```vim
vehicle_interfaces/Header header
uint8 unit_type 0
float32 min
float32 max
float32 center
float32 value
```
- **Examples**
- C++
```cpp
#include "vehicle_interfaces/msg/motor_steering.hpp"
// The remaining code is under rclcpp::Node class
static u_int64_t frame_id = 0;
auto msg = vehicle_interfaces::msg::MotorSteering();
msg.header.priority = vehicle_interfaces::msg::Header::PRIORITY_INFO;
msg.header.device_type = vehicle_interfaces::msg::Header::DEVTYPE_MOTOR_STEERING;
msg.header.device_id = this->nodeName_;
msg.header.frame_id = frame_id++;
msg.header.stamp_type = vehicle_interfaces::msg::Header::STAMPTYPE_NONE_UTC_SYNC;
msg.header.stamp = this->get_clock()->now();
msg.unit_type = unit_type;// Motor spin direction
msg.min = min;
msg.max = max;
msg.center = center;
msg.value = value;
```
- Python3
```python
from vehicle_interfaces.msg import MotorSteering
# The remaining code is under rclpy.Node class
msg = MotorSteering()
msg.header.priority = msg.header.PRIORITY_SENSOR
msg.header.device_type = msg.header.DEVTYPE_MOTOR_STEERING
msg.header.device_id = self.nodeName_
msg.header.frame_id = self.frame_id_# Defined in __init__
self.frame_id_ += 1
msg.header.stamp_type = msg.header.STAMPTYPE_NO_SYNC
msg.header.stamp = self.get_clock().now().to_msg()
RunClient.ros2DictLock.acquire()
tmp = RunClient.steeringDict# Steering motor status dictionary in RunClient.py
RunClient.ros2DictLock.release()
msg.unit_type = msg.UNIT_DIST
msg.min = float(tmp["min"])
msg.max = float(tmp["max"])
msg.center = float(tmp["center"])
msg.value = float(tmp["value"])
```
:::
- **WheelState.msg** (for steering wheel and pedal control signal)
:::spoiler
- **Definitions**
```vim
uint8 GEAR_PARK = 0
uint8 GEAR_REVERSE = 1
uint8 GEAR_NEUTRAL = 2
uint8 GEAR_DRIVE = 3
```
- **Variables**
```vim
vehicle_interfaces/Header header
uint8 gear 0
int16 steering 0
int16 pedal_throttle 0
int16 pedal_brake 0
int16 pedal_clutch 0
uint8 button 0
uint8 func 0
```
- **Examples**
- C++
```cpp
#include "vehicle_interfaces/msg/wheel_state.hpp"
// The remaining code is under rclcpp::Node class
static u_int64_t frame_id = 0;
auto msg = vehicle_interfaces::msg::WheelState();
msg.header.priority = vehicle_interfaces::msg::Header::PRIORITY_CONTROL;
msg.header.device_type = vehicle_interfaces::msg::Header::DEVTYPE_STEERINGWHEEL;
msg.header.device_id = this->nodeName_;
msg.header.frame_id = frame_id++;
msg.header.stamp_type = vehicle_interfaces::msg::Header::STAMPTYPE_NONE_UTC_SYNC;
msg.header.stamp = this->get_clock()->now();
msg.gear = gear;
msg.steering = wheelState;
msg.pedal_throttle = throttle;
msg.pedal_brake = brake;
msg.pedal_clutch = clutch;
msg.button = btn;
msg.func = func;
```
- Python3
```python
from vehicle_interfaces.msg import WheelState
# The remaining code is under rclpy.Node class
msg = WheelState()
msg.header.priority = msg.header.PRIORITY_CONTROL
msg.header.device_type = msg.header.DEVTYPE_STEERINGWHEEL
msg.header.device_id = self.nodeName_
msg.header.frame_id = self.frame_id_# Defined in __init__
self.frame_id_ += 1
msg.header.stamp_type = msg.header.STAMPTYPE_NONE_UTC_SYNC
msg.header.stamp = self.get_clock().now().to_msg()
msg.gear = gear
msg.steering = wheelState
msg.pedal_throttle = throttle
msg.pedal_brake = brake
msg.pedal_clutch = clutch
msg.button = btn
msg.func = func
```
:::
### Service .srv Type (vehicle_interfaces)
- **IDServer.srv** (communicate with ID server)
:::spoiler
- **Request and response structure**
```vim
string request_code
string content1
string content2
---
bool response
string content
```
- **Examples**
```cpp
// This example demonstrates requesting ID table from ID server node
auto clientNode = rclcpp::Node::make_shared("service_client");
auto client =
clientNode->create_client<vehicle_interfaces::srv::IDServer>(SERVICE_SERVER);
auto request = std::make_shared<vehicle_interfaces::srv::IDServer::Request>();
auto response = std::make_shared<vehicle_interfaces::srv::IDServer::Response>();
request->request_code
request->request_code = "getTable";
auto result = client->async_send_request(request);
if (rclcpp::spin_until_future_complete(clientNode, result) == rclcpp::FutureReturnCode::SUCCESS)
{
RCLCPP_INFO(clientNode->get_logger(), "Response: %d\n\t%s", result.get()->response, result.get()->content.c_str());
}
else
{
RCLCPP_ERROR(clientNode->get_logger(), "Failed to call service");
}
// If request succeed, the ID table will be retrieved from ID table topic which subscribed by IDTable.msg message type
```
:::
- **SafetyReg.srv** (deivce registered to safety server)
:::spoiler
- **Request and response structure**
```vim
# Passing device_id to safety server is necessary for control server to identify the place where emergency occurred
string device_id
# emergency_percentage from 0 to 1 represents emergency level from low to high
float32 emergency_percentage
---
# response is true if value accepted, otherwise server ignore the request and response false
bool response
```
- **Examples**
```cpp
// This example demonstrates safety server registration in a class
// SafetyNode and safety service implementation in safety.h
#include "vehicle_interfaces/safety.h"
// Class members
std::shared_ptr<Params> params_;
std::shared_ptr<SafetyNode> safetyNode_;
rclcpp::executors::SingleThreadedExecutor* safetyExec_;
std::thread safetyNodeTh_;
// Using SingleThreadedExecutor create thread for SafetyNode
this->safetyNode_ = std::make_shared<SafetyNode>(params->mainNodeName, params->service_SafetyServer_serviceName);
this->safetyExec_ = new rclcpp::executors::SingleThreadedExecutor();
this->safetyExec_->add_node(this->safetyNode_);
this->safetyNodeTh_ = std::thread(SpinNodeExecutor, this->safetyExec_, params->mainNodeName + "_safety");
// Set emergency value to safety server
this->safetyNode_->setEmergency(this->params_->mainNodeName, em);
```
:::
- **SafetyReq.srv** (deivce request emergencies from safety server)
:::spoiler
- **Request and response structure**
```vim
# Get device_id emergency percentage, return all emergencies if device_id set to "all"
string device_id
---
# response is true if value accepted, otherwise server ignore the request and response false
bool response
# Get device_id vector
string[] device_ids
# Get emergency_percentage vector corresponds to device_id vector
float32[] emergency_percentages
```
- **Examples**
```cpp
// This example demonstrates safety server requesting in a class
// SafetyNode and safety service implementation in safety.h
#include "vehicle_interfaces/safety.h"
// TODO: add safety server requesting demonstration
```
:::
- **TimeSync.srv** (synchronize UTC time from time server)
:::spoiler
- **Request and response structure**
```vim
uint8 request_code
builtin_interfaces/Time request_time
---
uint8 response_code
builtin_interfaces/Time request_time
builtin_interfaces/Time response_time
```
- **Examples**
```cpp
// This example demonstrates requesting UTC timestamp from time server
// The remaining code is under rclcpp::Node class
auto request = std::make_shared<vehicle_interfaces::srv::TimeSync::Request>();
request->request_code = this->timeStampType_;
request->request_time = this->get_clock()->now();
auto result = this->client_->async_send_request(request);
// Wait for the result.
if (rclcpp::spin_until_future_complete(this->clientNode_, result, 10ms) == rclcpp::FutureReturnCode::SUCCESS)
{
rclcpp::Time nowTime = this->get_clock()->now();
auto response = result.get();
this->initTime_ = response->request_time;
this->refTime_ = (rclcpp::Time)response->response_time - (nowTime - this->initTime_) * 0.5;
this->timeStampType_ = response->response_code;
}
else
{
RCLCPP_ERROR(this->clientNode_->get_logger(), "Failed to call service");
}
```
:::
## Instant Communication via Ying's ID Server (Deprecated)
For all topic message communicate with others via Ying's ID server, the only content we send are the variables in message. For the definitions such as `UNIT`, both devices needs the same `.msg` file to decode `unit_type`.
Ying's ID server provided 1450 bytes of payload size, including `description_id`, `device_length` and `message_length` which occupies 14 bytes. Considering the device name length, ==the conservative message length will be less then 1350 bytes==.
The message can be considered as two part: `header_field` and `data_field`. The `header_field` has fixed size for describing the message type and timestamp. The `data_field` filled with message data which sizes is variant depend on different message type, such as IMU, Ultrasonic, etc..
### Header Field
The `header_field` data size and order is reference to `Header.msg`. The `header_field` will only filled with variables from `Header.msg`. ==The `header_field` size is fixed at 75 bytes==, including `priority`, `device_type`, `device_id`, `stamp_type` and `stamp`. Users can easily determine whether or not the message will be used.
#### Variables
```vim
# Variables from Header.msg
uint8 priority 2
uint8 device_type 0
string device_id
uint8 stamp_type 0
builtin_interfaces/Time stamp
# Header field variables
uint8 priority (1 byte)
uint8 device_type (1 byte)
string device_id (64 bytes)
uint8 stamp_type (1 byte)
double stamp_sec (8 bytes)
```
#### Order
```vim
# Order
{header_field} = <priority><device_type><device_id><stamp_type><stamp_sec>
# Size (total 75 bytes)
{header_field} = <1><1><64><1><8>
```
#### Examples (Big-Endian Unconfirmed)
```cpp
// This example shows the IMU sensor header format
// Definitions decoding refers to Header.msg
# include <cstring>
# include <string>
typedef unsigned char UCHAR;
// Variable settings from data server
uint8_t priority = 1;
uint8_t device_type = 5;
std::string device_id = "Topic_IMU";
uint8_t stamp_type = 0;
double stamp_sec = 1665981835.921912432;
// Encoding
UCHAR* stampBytes = reinterpret_cast<UCHAR*>(&stamp_sec);// 8 bytes
UCHAR header_field[75];
memset(header_field, 0, 75);
UCHAR* ptr = header_field;
*ptr++ = priority;
*ptr++ = device_type;
memcpy(ptr, device_id.c_str(), device_id.length()); ptr += 64;
*ptr++ = 0;
memcpy(ptr, stampBytes, 8); ptr += 8;
printf("buff: %s\nptr pos: %ld\n", header_field, ptr - header_field);
//Decoding
UCHAR* recvBuf = header_field;
UCHAR* ptr2 = recvBuf;
printf("priority: %d\n", *ptr2++);
printf("device_type: %d\n", *ptr2++);
std::string deviceStr(reinterpret_cast<char*>(ptr2)); ptr2 += 64;
printf("device_id: %s\n", deviceStr.c_str());
printf("stamp_type: %d\n", *ptr2++);
double *stampPtr = reinterpret_cast<double*>(ptr2); ptr2 += 8;
printf("stamp_sec: %.9f\n", *stampPtr);
```
### Data_Field
The `data_field` content is variant with difference messages type. The convenient way to recognized each type of message, is reference from relative `.msg `file. The `data_field` filling and reading order must follow the `.msg` file from up to down, and each variables must follow the size of data type.
For the basic data type, like `int` or `double`, the data must slice and store according to Big-Endian sequence.
## Convert ROS2 Message To JSON Format
The ROS2 data converts to JSON format using timestamp to group the topics messages. The number of topics and types will be variant in different timestamp moment, but the same type of topic exist same message structure, while the message order may not be the same.
### Data Structure
```json
{
"TIMESTAMP_0": {
"TOPIC_A": {
"NUMERICAL": 100,
"STRING": "Example",
"NUMERICAL_ARRAY": [1, 2, 3, 4],
"STRING_ARRAY": ["a", "b", "c"]
}
}
}
```
### Under The Topic
Our JSON format will only stores the message variables and header. The header variables will be extracted to the same hierarchy with message variables.
- ROS2 topic message example
```vim
# IMU topic variables
vehicle_interfaces/Header header
uint8 unit_type 0
float32[4] orientation
float32[3] angular_velocity
float32[3] linear_acceleration
```
- ROS2 topic message to JSON example
```json
// IMU topic to JSON
"topic_IMU":{
"priority": 1,
"device_type": 6,
"device_id": "imu_publisher_node",
"frame_id": 0,
"stamp_type": 0,
"stamp": 1666699999.999999,
"ref_publish_time_ms": 50.0,
"unit_type": 4,
"orientation": [0, 0, 0, 0],
"angular_velocity": [0, 0, 0],
"linear_acceleration": [0, 0, 0],
"msg_type": 5
}
```
- The `stamp` is converted to double format
- The `msg_type` is added by dataserver, **not supported yet**