MC Protocol Serial C++ 0.2.3
MC protocol serial library for MCU-oriented environments
Loading...
Searching...
No Matches
high_level.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <array>
4#include <cstddef>
5#include <cstdint>
6#include <cctype>
7
12
14
24
25namespace detail {
26
28 const char* prefix;
29 std::size_t prefix_length;
31 int base;
32};
33
34constexpr std::array<DeviceParseSpec, 39> kDeviceParseSpecs {{
35 {"STS", 3U, DeviceCode::STS, 10},
36 {"STC", 3U, DeviceCode::STC, 10},
37 {"STN", 3U, DeviceCode::STN, 10},
38 {"TS", 2U, DeviceCode::TS, 10},
39 {"TC", 2U, DeviceCode::TC, 10},
40 {"TN", 2U, DeviceCode::TN, 10},
41 {"CS", 2U, DeviceCode::CS, 10},
42 {"CC", 2U, DeviceCode::CC, 10},
43 {"CN", 2U, DeviceCode::CN, 10},
44 {"SB", 2U, DeviceCode::SB, 16},
45 {"SW", 2U, DeviceCode::SW, 16},
46 {"SM", 2U, DeviceCode::SM, 10},
47 {"SD", 2U, DeviceCode::SD, 10},
48 {"DX", 2U, DeviceCode::DX, 16},
49 {"DY", 2U, DeviceCode::DY, 16},
50 {"LTS", 3U, DeviceCode::LTS, 10},
51 {"LTC", 3U, DeviceCode::LTC, 10},
52 {"LTN", 3U, DeviceCode::LTN, 10},
53 {"LSTS", 4U, DeviceCode::LSTS, 10},
54 {"LSTC", 4U, DeviceCode::LSTC, 10},
55 {"LSTN", 4U, DeviceCode::LSTN, 10},
56 {"LCS", 3U, DeviceCode::LCS, 10},
57 {"LCC", 3U, DeviceCode::LCC, 10},
58 {"LCN", 3U, DeviceCode::LCN, 10},
59 {"LZ", 2U, DeviceCode::LZ, 10},
60 {"RD", 2U, DeviceCode::RD, 10},
61 {"ZR", 2U, DeviceCode::ZR, 10},
62 {"X", 1U, DeviceCode::X, 16},
63 {"Y", 1U, DeviceCode::Y, 16},
64 {"M", 1U, DeviceCode::M, 10},
65 {"L", 1U, DeviceCode::L, 10},
66 {"F", 1U, DeviceCode::F, 10},
67 {"V", 1U, DeviceCode::V, 10},
68 {"B", 1U, DeviceCode::B, 16},
69 {"D", 1U, DeviceCode::D, 10},
70 {"W", 1U, DeviceCode::W, 16},
71 {"S", 1U, DeviceCode::S, 10},
72 {"Z", 1U, DeviceCode::Z, 10},
73 {"R", 1U, DeviceCode::R, 10},
74}};
75
76[[nodiscard]] constexpr char ascii_upper(char value) noexcept {
77 return (value >= 'a' && value <= 'z') ? static_cast<char>(value - ('a' - 'A')) : value;
78}
79
80[[nodiscard]] inline bool parse_u32(
82 std::uint32_t& out_value,
83 int base) noexcept {
84 if (text.empty()) {
85 return false;
86 }
87
88 std::uint32_t value = 0U;
89 for (char ch : text) {
90 std::uint32_t digit = 0U;
91 if (ch >= '0' && ch <= '9') {
92 digit = static_cast<std::uint32_t>(ch - '0');
93 } else if (base == 16 && ch >= 'A' && ch <= 'F') {
94 digit = static_cast<std::uint32_t>(ch - 'A' + 10);
95 } else if (base == 16 && ch >= 'a' && ch <= 'f') {
96 digit = static_cast<std::uint32_t>(ch - 'a' + 10);
97 } else {
98 return false;
99 }
100
101 if (digit >= static_cast<std::uint32_t>(base)) {
102 return false;
103 }
104
105 value = static_cast<std::uint32_t>(value * static_cast<std::uint32_t>(base) + digit);
106 }
107
108 out_value = value;
109 return true;
110}
111
112[[nodiscard]] constexpr bool is_double_word_device(DeviceCode code) noexcept {
113 switch (code) {
114 case DeviceCode::LTN:
115 case DeviceCode::LSTN:
116 case DeviceCode::LCN:
117 case DeviceCode::LZ:
118 return true;
119 default:
120 return false;
121 }
122}
123
124} // namespace detail
125
128 PlcSeries series = PlcSeries::Q_L) noexcept {
129 ProtocolConfig config {};
130 config.frame_kind = FrameKind::C4;
131 config.code_mode = CodeMode::Binary;
132 config.ascii_format = AsciiFormat::Format3;
133 config.target_series = series;
134 config.sum_check_enabled = true;
135 config.route.kind = RouteKind::HostStation;
136 config.route.station_no = 0x00;
137 config.route.network_no = 0x00;
138 config.route.pc_no = 0xFF;
139 config.route.request_destination_module_io_no = 0x03FF;
140 config.route.request_destination_module_station_no = 0x00;
141 config.route.self_station_enabled = false;
142 config.route.self_station_no = 0x00;
143 config.timeout.response_timeout_ms = 5000;
144 config.timeout.inter_byte_timeout_ms = 250;
145 return config;
146}
147
150 PlcSeries series = PlcSeries::Q_L) noexcept {
151 ProtocolConfig config {};
152 config.frame_kind = FrameKind::C4;
153 config.code_mode = CodeMode::Ascii;
154 config.ascii_format = AsciiFormat::Format4;
155 config.target_series = series;
156 config.sum_check_enabled = false;
157 config.route.kind = RouteKind::MultidropStation;
158 config.route.station_no = 0x00;
159 config.route.network_no = 0x00;
160 config.route.pc_no = 0xFF;
161 config.route.request_destination_module_io_no = 0x03FF;
162 config.route.request_destination_module_station_no = 0x00;
163 config.route.self_station_enabled = false;
164 config.route.self_station_no = 0x00;
165 config.timeout.response_timeout_ms = 5000;
166 config.timeout.inter_byte_timeout_ms = 250;
167 return config;
168}
169
176 PlcSeries series = PlcSeries::Q_L) noexcept {
179 config.sum_check_enabled = true;
181 config.route.station_no = 0x00;
182 config.ascii_block_number = 0x00;
183 return config;
184}
185
193
199 std::uint32_t value = 0;
201 bool double_word = false;
202};
203
211
216[[nodiscard]] inline Status parse_device_address(
217 std::string_view text,
218 DeviceAddress& out_device) noexcept {
219 for (const auto& spec : detail::kDeviceParseSpecs) {
220 if (text.size() <= spec.prefix_length) {
221 continue;
222 }
223
224 bool prefix_match = true;
225 for (std::size_t index = 0; index < spec.prefix_length; ++index) {
226 const char lhs = detail::ascii_upper(text[index]);
227 if (lhs != spec.prefix[index]) {
228 prefix_match = false;
229 break;
230 }
231 }
232 if (!prefix_match) {
233 continue;
234 }
235
236 std::uint32_t number = 0;
237 if (!detail::parse_u32(text.substr(spec.prefix_length), number, spec.base)) {
238 return make_status(StatusCode::InvalidArgument, "Device address number is invalid");
239 }
240
241 out_device = DeviceAddress {
242 .code = spec.code,
243 .number = number,
244 };
245 return ok_status();
246 }
247
248 return make_status(StatusCode::InvalidArgument, "Device address prefix is not supported");
249}
250
253 std::string_view head_device,
254 std::uint16_t points,
255 BatchReadWordsRequest& out_request) noexcept {
256 DeviceAddress parsed {};
257 const Status status = parse_device_address(head_device, parsed);
258 if (!status.ok()) {
259 return status;
260 }
261 out_request = BatchReadWordsRequest {
262 .head_device = parsed,
263 .points = points,
264 };
265 return ok_status();
266}
267
270 std::string_view head_device,
271 std::uint16_t points,
272 BatchReadBitsRequest& out_request) noexcept {
273 DeviceAddress parsed {};
274 const Status status = parse_device_address(head_device, parsed);
275 if (!status.ok()) {
276 return status;
277 }
278 out_request = BatchReadBitsRequest {
279 .head_device = parsed,
280 .points = points,
281 };
282 return ok_status();
283}
284
287 std::string_view head_device,
289 BatchWriteWordsRequest& out_request) noexcept {
290 DeviceAddress parsed {};
291 const Status status = parse_device_address(head_device, parsed);
292 if (!status.ok()) {
293 return status;
294 }
295 out_request = BatchWriteWordsRequest {
296 .head_device = parsed,
297 .words = words,
298 };
299 return ok_status();
300}
301
304 std::string_view head_device,
306 BatchWriteBitsRequest& out_request) noexcept {
307 DeviceAddress parsed {};
308 const Status status = parse_device_address(head_device, parsed);
309 if (!status.ok()) {
310 return status;
311 }
312 out_request = BatchWriteBitsRequest {
313 .head_device = parsed,
314 .bits = bits,
315 };
316 return ok_status();
317}
318
320[[nodiscard]] inline Status make_random_read_item(
321 std::string_view device,
322 RandomReadItem& out_item,
323 bool double_word = false) noexcept {
324 DeviceAddress parsed {};
325 const Status status = parse_device_address(device, parsed);
326 if (!status.ok()) {
327 return status;
328 }
329 out_item = RandomReadItem {
330 .device = parsed,
331 .double_word = double_word || detail::is_double_word_device(parsed.code),
332 };
333 return ok_status();
334}
335
338 std::string_view device,
339 std::uint32_t value,
340 RandomWriteWordItem& out_item,
341 bool double_word = false) noexcept {
342 DeviceAddress parsed {};
343 const Status status = parse_device_address(device, parsed);
344 if (!status.ok()) {
345 return status;
346 }
347 out_item = RandomWriteWordItem {
348 .device = parsed,
349 .value = value,
350 .double_word = double_word || detail::is_double_word_device(parsed.code),
351 };
352 return ok_status();
353}
354
357 std::string_view device,
358 BitValue value,
359 RandomWriteBitItem& out_item) noexcept {
360 DeviceAddress parsed {};
361 const Status status = parse_device_address(device, parsed);
362 if (!status.ok()) {
363 return status;
364 }
365 out_item = RandomWriteBitItem {
366 .device = parsed,
367 .value = value,
368 };
369 return ok_status();
370}
371
376[[nodiscard]] inline Status make_random_read_request(
379 RandomReadRequest& out_request) noexcept {
380 if (out_items.size() < specs.size()) {
381 return make_status(StatusCode::BufferTooSmall, "Random read output item buffer is too small");
382 }
383
384 for (std::size_t index = 0; index < specs.size(); ++index) {
385 const Status status =
386 make_random_read_item(specs[index].device, out_items[index], specs[index].double_word);
387 if (!status.ok()) {
388 return status;
389 }
390 }
391
392 out_request = RandomReadRequest {
393 .items = std::span<const RandomReadItem>(out_items.data(), specs.size()),
394 };
395 return ok_status();
396}
397
405 MonitorRegistration& out_request) noexcept {
406 RandomReadRequest request {};
407 const Status status = make_random_read_request(specs, out_items, request);
408 if (!status.ok()) {
409 return status;
410 }
411
412 out_request = MonitorRegistration {
413 .items = request.items,
414 };
415 return ok_status();
416}
417
422 std::span<const RandomWriteWordItem>& out_item_view) noexcept {
423 if (out_items.size() < specs.size()) {
424 return make_status(StatusCode::BufferTooSmall, "Random write word output item buffer is too small");
425 }
426
427 for (std::size_t index = 0; index < specs.size(); ++index) {
428 const Status status = make_random_write_word_item(
429 specs[index].device,
430 specs[index].value,
431 out_items[index],
432 specs[index].double_word);
433 if (!status.ok()) {
434 return status;
435 }
436 }
437
438 out_item_view = std::span<const RandomWriteWordItem>(out_items.data(), specs.size());
439 return ok_status();
440}
441
446 std::span<const RandomWriteBitItem>& out_item_view) noexcept {
447 if (out_items.size() < specs.size()) {
448 return make_status(StatusCode::BufferTooSmall, "Random write bit output item buffer is too small");
449 }
450
451 for (std::size_t index = 0; index < specs.size(); ++index) {
452 const Status status =
453 make_random_write_bit_item(specs[index].device, specs[index].value, out_items[index]);
454 if (!status.ok()) {
455 return status;
456 }
457 }
458
459 out_item_view = std::span<const RandomWriteBitItem>(out_items.data(), specs.size());
460 return ok_status();
461}
462
463} // namespace mcprotocol::serial::highlevel
constexpr size_type size() const noexcept
constexpr bool is_double_word_device(DeviceCode code) noexcept
bool parse_u32(std::string_view text, std::uint32_t &out_value, int base) noexcept
constexpr char ascii_upper(char value) noexcept
constexpr std::array< DeviceParseSpec, 39 > kDeviceParseSpecs
Status make_random_write_word_items(std::span< const RandomWriteWordSpec > specs, std::span< RandomWriteWordItem > out_items, std::span< const RandomWriteWordItem > &out_item_view) noexcept
Builds sparse random word-write items from string-address specs.
Status make_random_write_word_item(std::string_view device, std::uint32_t value, RandomWriteWordItem &out_item, bool double_word=false) noexcept
Builds one sparse random word-write item from a string address.
constexpr ProtocolConfig make_c4_binary_protocol(PlcSeries series=PlcSeries::Q_L) noexcept
Returns a practical default for Q/L-era Format5 / Binary / C4.
Status make_random_read_request(std::span< const RandomReadSpec > specs, std::span< RandomReadItem > out_items, RandomReadRequest &out_request) noexcept
Builds a sparse random-read request from string-address specs.
Status make_batch_write_words_request(std::string_view head_device, std::span< const std::uint16_t > words, BatchWriteWordsRequest &out_request) noexcept
Builds a contiguous word-write request from a string address such as D100.
Status make_random_write_bit_items(std::span< const RandomWriteBitSpec > specs, std::span< RandomWriteBitItem > out_items, std::span< const RandomWriteBitItem > &out_item_view) noexcept
Builds sparse random bit-write items from string-address specs.
Status make_batch_write_bits_request(std::string_view head_device, std::span< const BitValue > bits, BatchWriteBitsRequest &out_request) noexcept
Builds a contiguous bit-write request from a string address such as M100.
Status make_random_read_item(std::string_view device, RandomReadItem &out_item, bool double_word=false) noexcept
Builds one sparse random-read item from a string address.
Status make_batch_read_words_request(std::string_view head_device, std::uint16_t points, BatchReadWordsRequest &out_request) noexcept
Builds a contiguous word-read request from a string address such as D100.
Status make_batch_read_bits_request(std::string_view head_device, std::uint16_t points, BatchReadBitsRequest &out_request) noexcept
Builds a contiguous bit-read request from a string address such as M100.
Status make_monitor_registration(std::span< const RandomReadSpec > specs, std::span< RandomReadItem > out_items, MonitorRegistration &out_request) noexcept
Builds a sparse monitor registration payload from string-address specs.
Status make_random_write_bit_item(std::string_view device, BitValue value, RandomWriteBitItem &out_item) noexcept
Builds one sparse random bit-write item from a string address.
constexpr ProtocolConfig make_c4_ascii_format4_protocol(PlcSeries series=PlcSeries::Q_L) noexcept
Returns a practical default for Format4 / ASCII / C4.
Status parse_device_address(std::string_view text, DeviceAddress &out_device) noexcept
Parses a plain MC device string such as D100, M100, X10, or B20.
constexpr ProtocolConfig make_c4_ascii_format2_protocol(PlcSeries series=PlcSeries::Q_L) noexcept
Returns a practical default for Format2 / ASCII / C4.
DeviceCode
Device-family identifier used by the request codecs.
Definition types.hpp:198
@ MultidropStation
Multidrop route where the destination station number is encoded in the frame.
@ HostStation
Host-station route with fixed station=0, network=0, and pc=FF.
@ C4
Chapter-8/10/11/13 oriented serial frame with the fullest feature coverage in this repository.
BitValue
Logical single-bit value used by bit read/write APIs.
Definition types.hpp:243
constexpr Status make_status(StatusCode code, const char *message, std::uint16_t plc_error_code=0) noexcept
Builds a status value with an optional PLC end code.
Definition status.hpp:42
@ Binary
Compact binary command data and response data.
@ Ascii
Text-encoded command data and response data.
@ Format4
CR/LF terminated layout often used by host-facing bring-up tools.
@ Format3
STX-only layout commonly used on serial MC links.
@ Format2
Format1 plus a 1-byte block number used for request/response pairing on 2C/3C/4C.
PlcSeries
PLC family selection used for subcommand and device-layout differences.
Definition types.hpp:181
constexpr Status ok_status() noexcept
Returns the default success status.
Definition status.hpp:37
Contiguous bit-read request (0401 bit path).
Definition types.hpp:377
DeviceAddress head_device
First bit device in the contiguous range.
Definition types.hpp:379
DeviceAddress head_device
First device in the contiguous range.
Definition types.hpp:371
Contiguous bit-write request (1401 bit path).
Definition types.hpp:393
DeviceAddress head_device
First bit device in the contiguous write range.
Definition types.hpp:395
Contiguous word-write request (1401).
Definition types.hpp:385
DeviceAddress head_device
First device in the contiguous write range.
Definition types.hpp:387
Device code plus numeric address.
Definition types.hpp:348
DeviceCode code
Device family such as D, M, X, LTN, or LZ.
Definition types.hpp:350
std::span< const RandomReadItem > items
Sparse list of word/dword items to register for a later 0802 read.
Definition types.hpp:540
Top-level protocol configuration shared by codecs and client requests.
Definition types.hpp:323
bool sum_check_enabled
Enables or disables the ASCII/binary sum-check where that frame family supports it.
Definition types.hpp:338
AsciiFormat ascii_format
Selected ASCII framing flavor when code_mode == CodeMode::Ascii.
Definition types.hpp:329
RouteConfig route
Route header fields used for every encoded request.
Definition types.hpp:340
FrameKind frame_kind
Selected serial frame family.
Definition types.hpp:325
DeviceAddress device
Target device address for this sparse item.
Definition types.hpp:455
Native random-read request made of sparse word/dword items.
Definition types.hpp:461
std::span< const RandomReadItem > items
Sparse word/dword items encoded in the native random-read request.
Definition types.hpp:463
One bit item inside native random write (1402 bit path).
Definition types.hpp:477
DeviceAddress device
Target bit device address for the sparse write.
Definition types.hpp:479
One word or double-word item inside native random write (1402 word path).
Definition types.hpp:467
DeviceAddress device
Target device address for the sparse write.
Definition types.hpp:469
RouteKind kind
Route interpretation used by the selected frame family.
Definition types.hpp:298
std::uint8_t station_no
Target station number on multidrop serial links.
Definition types.hpp:300
Result object returned by most public APIs.
Definition status.hpp:26
constexpr bool ok() const noexcept
Definition status.hpp:31
String-address spec used to build sparse random-read or monitor requests.
std::string_view device
Plain device string such as D100, LZ0, or LCN10.
bool double_word
true when the target should be encoded as a double-word sparse item.
String-address spec used to build sparse random bit-write items.
BitValue value
Bit value written to device.
std::string_view device
Plain bit-device string such as M100 or X10.
String-address spec used to build sparse random word-write items.
std::string_view device
Plain device string such as D100 or LZ0.
std::uint32_t value
Word or double-word value written to device.
bool double_word
true when the item should be encoded as a double-word sparse write.
Public request, response, configuration, and callback types for the serial MC protocol library.