Usage guide
The library exposes three practical entry paths. Pick one based on how much transport control your application needs.
Entry paths
| Entry path | Header | Use it when |
|---|---|---|
| High-level helpers | mcprotocol/serial/high_level.hpp |
You want protocol presets and string-address request builders. |
| Host sync facade | mcprotocol/serial/host_sync.hpp |
You are writing a blocking Linux or Windows bring-up tool. |
| Low-level async client | mcprotocol/serial/client.hpp |
You are integrating your own UART, DMA, interrupt, or scheduler layer. |
Entry path 1: high-level helpers
make_c4_binary_protocol(PlcProfile::...) creates a ProtocolConfig preset. Request builders such as make_batch_read_words_request("D100", count, request) convert plain device strings into typed request structs.
#include <cstdint>
#include <cstdio>
#include "mcprotocol_serial.hpp"
int main() {
using mcprotocol::serial::BatchReadWordsRequest;
using mcprotocol::serial::PlcProfile;
using mcprotocol::serial::ProtocolConfig;
using mcprotocol::serial::Status;
using mcprotocol::serial::highlevel::make_batch_read_words_request;
using mcprotocol::serial::highlevel::make_c4_binary_protocol;
ProtocolConfig protocol = make_c4_binary_protocol(PlcProfile::MelsecQL);
protocol.route.station_no = 0;
BatchReadWordsRequest request {};
Status status = make_batch_read_words_request("D100", 2, request);
if (!status.ok()) {
std::fprintf(stderr, "request build failed: %s\n", status.message);
return 1;
}
std::printf("head=%u points=%u\n", request.head_device.number, request.points);
return 0;
}
Entry path 2: synchronous host facade
PosixSyncClient opens a host serial port, configures the protocol client, transmits one request, waits for completion, and returns a Status.
Read words
#include <array>
#include <cstdint>
#include <cstdio>
#include "mcprotocol_serial.hpp"
int main() {
using mcprotocol::serial::PlcProfile;
using mcprotocol::serial::PosixSerialConfig;
using mcprotocol::serial::PosixSyncClient;
using mcprotocol::serial::Status;
using mcprotocol::serial::highlevel::make_c4_binary_protocol;
PosixSerialConfig serial {};
serial.device_path = "/dev/ttyUSB0";
serial.baud_rate = 19200;
serial.data_bits = 8;
serial.stop_bits = 2;
serial.parity = 'E';
serial.rts_cts = false;
auto protocol = make_c4_binary_protocol(PlcProfile::MelsecQL);
PosixSyncClient plc;
Status status = plc.open(serial, protocol);
if (!status.ok()) {
return 1;
}
std::array<std::uint16_t, 2> words {};
status = plc.read_words("D100", words);
if (!status.ok()) {
return 1;
}
std::printf("D100=0x%04X D101=0x%04X\n", words[0], words[1]);
return 0;
}
Write words
#include <array>
#include <cstdint>
#include "mcprotocol_serial.hpp"
int main() {
using mcprotocol::serial::PlcProfile;
using mcprotocol::serial::PosixSerialConfig;
using mcprotocol::serial::PosixSyncClient;
using mcprotocol::serial::highlevel::make_c4_binary_protocol;
PosixSerialConfig serial {};
serial.device_path = "/dev/ttyUSB0";
serial.baud_rate = 19200;
serial.data_bits = 8;
serial.stop_bits = 2;
serial.parity = 'E';
PosixSyncClient plc;
auto protocol = make_c4_binary_protocol(PlcProfile::MelsecQL);
if (!plc.open(serial, protocol).ok()) {
return 1;
}
const std::array<std::uint16_t, 2> words {0x1234, 0x5678};
return plc.write_words("D100", words).ok() ? 0 : 1;
}
Random read and random write
#include <array>
#include <cstdint>
#include "mcprotocol_serial.hpp"
int main() {
using mcprotocol::serial::PlcProfile;
using mcprotocol::serial::PosixSerialConfig;
using mcprotocol::serial::PosixSyncClient;
using mcprotocol::serial::highlevel::RandomWriteWordSpec;
using mcprotocol::serial::highlevel::make_c4_binary_protocol;
PosixSerialConfig serial {};
serial.device_path = "/dev/ttyUSB0";
serial.baud_rate = 19200;
serial.data_bits = 8;
serial.stop_bits = 2;
serial.parity = 'E';
PosixSyncClient plc;
auto protocol = make_c4_binary_protocol(PlcProfile::MelsecQL);
if (!plc.open(serial, protocol).ok()) {
return 1;
}
std::uint32_t d100 = 0;
if (!plc.random_read("D100", d100).ok()) {
return 1;
}
const std::array<RandomWriteWordSpec, 1> writes {{{.device = "D101", .value = d100, .double_word = false}}};
return plc.random_write_words(writes).ok() ? 0 : 1;
}
Remote control and CPU model
#include <cstdio>
#include "mcprotocol_serial.hpp"
int main() {
using mcprotocol::serial::CpuModelInfo;
using mcprotocol::serial::PlcProfile;
using mcprotocol::serial::PosixSerialConfig;
using mcprotocol::serial::PosixSyncClient;
using mcprotocol::serial::RemoteOperationMode;
using mcprotocol::serial::RemoteRunClearMode;
using mcprotocol::serial::highlevel::make_c4_binary_protocol;
PosixSerialConfig serial {};
serial.device_path = "/dev/ttyUSB0";
serial.baud_rate = 19200;
serial.data_bits = 8;
serial.stop_bits = 2;
serial.parity = 'E';
PosixSyncClient plc;
auto protocol = make_c4_binary_protocol(PlcProfile::MelsecQL);
if (!plc.open(serial, protocol).ok()) {
return 1;
}
CpuModelInfo info {};
if (!plc.read_cpu_model(info).ok()) {
return 1;
}
std::printf("model=%s code=0x%04X\n", info.model_name.data(), info.model_code);
if (!plc.remote_stop().ok()) {
return 1;
}
return plc.remote_run(RemoteOperationMode::DoNotExecuteForcibly, RemoteRunClearMode::DoNotClear).ok() ? 0 : 1;
}
Entry path 3: low-level async client
MelsecSerialClient owns the protocol state machine but not the UART. Your code configures the client, starts an async request, sends pending_tx_frame(), calls notify_tx_complete(), feeds response bytes with on_rx_bytes(), and calls poll() for timeout handling.
This is the path used in the PlatformIO examples.
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include "mcprotocol_serial.hpp"
#include "mcprotocol/serial/span_compat.hpp"
namespace {
struct Completion {
bool done = false;
mcprotocol::serial::Status status {};
};
void on_complete(void* user, mcprotocol::serial::Status status) {
auto* completion = static_cast<Completion*>(user);
completion->done = true;
completion->status = status;
}
} // namespace
int main() {
using mcprotocol::serial::BatchReadWordsRequest;
using mcprotocol::serial::DeviceAddress;
using mcprotocol::serial::DeviceCode;
using mcprotocol::serial::FrameCodec;
using mcprotocol::serial::MelsecSerialClient;
using mcprotocol::serial::PlcProfile;
using mcprotocol::serial::Status;
using mcprotocol::serial::highlevel::make_c4_ascii_format4_protocol;
MelsecSerialClient client;
const auto protocol = make_c4_ascii_format4_protocol(PlcProfile::MelsecQL);
Status status = client.configure(protocol);
if (!status.ok()) {
return 1;
}
std::array<std::uint16_t, 2> words {};
Completion completion {};
status = client.async_batch_read_words(
0,
BatchReadWordsRequest {
.head_device = DeviceAddress {.code = DeviceCode::D, .number = 100},
.points = static_cast<std::uint16_t>(words.size()),
},
std::span<std::uint16_t>(words.data(), words.size()),
on_complete,
&completion);
if (!status.ok()) {
return 1;
}
const std::span<const std::byte> frame = client.pending_tx_frame();
// Send `frame` through your UART here.
(void)frame;
status = client.notify_tx_complete(1);
if (!status.ok()) {
return 1;
}
const std::array<std::uint8_t, 8> response_data {'1', '2', '3', '4', '5', '6', '7', '8'};
std::array<std::uint8_t, mcprotocol::serial::kMaxResponseFrameBytes> response_frame {};
std::size_t response_frame_size = 0;
status = FrameCodec::encode_success_response(
protocol,
std::span<const std::uint8_t>(response_data.data(), response_data.size()),
response_frame,
response_frame_size);
if (!status.ok()) {
return 1;
}
std::array<std::byte, mcprotocol::serial::kMaxResponseFrameBytes> rx_frame {};
std::memcpy(rx_frame.data(), response_frame.data(), response_frame_size);
client.on_rx_bytes(2, std::span<const std::byte>(rx_frame.data(), response_frame_size));
client.poll(2);
if (!completion.done || !completion.status.ok()) {
return 1;
}
std::printf("D100=0x%04X D101=0x%04X\n", words[0], words[1]);
return 0;
}
See examples/mcu_async_batch_read.cpp for a complete simulated async example.
Address format
| Address form | Example | Status |
|---|---|---|
| Plain decimal word device | D100 |
Supported. |
| Plain decimal bit device | M100 |
Supported. |
| Plain hexadecimal bit device | X10 |
Supported. |
| Plain hexadecimal word device | W100 |
Supported. |
| Typed suffix | D100:D, D100:F |
Not supported by the current parser. |
| Bit-in-word suffix | D100.0, D100.F |
Not supported by the current parser. |
| Link-direct string | J1\D100 |
Parsed by link-direct helpers, not by parse_device_address(). |
Serial config reference
| Field | Type | Example | Notes |
|---|---|---|---|
device_path |
std::string_view |
/dev/ttyUSB0, COM3 |
Host serial device path. |
baud_rate |
std::uint32_t |
19200 |
Must match the PLC serial module. |
data_bits |
std::uint8_t |
8 |
Host quickstart uses 8 data bits. |
stop_bits |
std::uint8_t |
2 |
Host quickstart uses 2 stop bits. |
parity |
char |
'E' |
Use 'N', 'E', or 'O' as supported by the host backend. |
rts_cts |
bool |
false |
Hardware flow-control flag for host serial ports. |