├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── _clang-format ├── inc └── modbus.h ├── src └── modbus.c └── tests.c /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.swp 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | 3 | project(modbus-parser C) 4 | 5 | add_library(base INTERFACE) 6 | target_include_directories(base 7 | INTERFACE 8 | inc/ 9 | ) 10 | target_compile_options(base 11 | INTERFACE 12 | -std=gnu11 13 | -O3 14 | -g 15 | -fno-omit-frame-pointer 16 | -Wall 17 | #-Wextra 18 | ) 19 | 20 | add_library(modbus-parser 21 | src/modbus.c 22 | inc/modbus.h 23 | ) 24 | target_link_libraries(modbus-parser 25 | PUBLIC 26 | base 27 | ) 28 | 29 | add_executable(tests 30 | tests.c 31 | ) 32 | target_link_libraries(tests 33 | PRIVATE 34 | base 35 | modbus-parser 36 | ) 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Hamid Rostami 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Modbus protocol Sans-I/O implementation 2 | 3 | Modbus protocol parser written in C. Design principles is very similar 4 | to [http-parser](https://github.com/nodejs/http-parser) project. 5 | It parses both requests and responses, Plus can generate modbus master 6 | queries with simple API. It does not make any syscalls nor allocations, 7 | not even I/O operation. it does not buffer data, it can be interrupted 8 | at anytime depending on your architecture. 9 | 10 | Features: 11 | 12 | * No dependencies 13 | * Decodes chunked encoding. 14 | -------------------------------------------------------------------------------- /_clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Mozilla 2 | Language: Cpp 3 | AlwaysBreakBeforeMultilineStrings: true 4 | BinPackParameters: false 5 | -------------------------------------------------------------------------------- /inc/modbus.h: -------------------------------------------------------------------------------- 1 | #ifndef MODBUS_H_ 2 | #define MODBUS_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define MODBUS_PARSER_VERSION_MAJOR 0 9 | #define MODBUS_PARSER_VERSION_MINOR 1 10 | #define MODBUS_PARSER_VERSION_PATCH 0 11 | 12 | #define MODBUS_COIL_HIGH 0xFF00 13 | #define MODBUS_COIL_LOW 0x0000 14 | 15 | #define MODBUS_COILS_BYTE_LEN(qty) ((qty / 8) + ((qty % 8) > 0)) 16 | 17 | typedef struct modbus_parser modbus_parser; 18 | typedef struct modbus_parser_settings modbus_parser_settings; 19 | 20 | typedef int (*modbus_cb)(modbus_parser*); 21 | typedef int (*modbus_data_cb)(modbus_parser*, uint8_t* at); 22 | 23 | enum modbus_parser_type 24 | { 25 | MODBUS_QUERY, 26 | MODBUS_RESPONSE 27 | }; 28 | 29 | #define MODBUS_FUNC_MAP(XX) \ 30 | XX(1, READ_COILS, "Read Coils") \ 31 | XX(2, READ_DISCRETE_IN, "Read Discrete Inputs") \ 32 | XX(3, READ_HOLD_REG, "Read Holding Register") \ 33 | XX(4, READ_IN_REG, "Read Input Register") \ 34 | XX(5, WRITE_COIL, "Wire Single Coil") \ 35 | XX(6, WRITE_REG, "Write Single Register") \ 36 | XX(15, WRITE_COILS, "Write Multiple Coils") \ 37 | XX(16, WRITE_REGS, "Write Miltiple Registers") 38 | 39 | enum modbus_func 40 | { 41 | #define XX(num, name, string) MODBUS_FUNC_##name = num, 42 | MODBUS_FUNC_MAP(XX) 43 | #undef XX 44 | }; 45 | 46 | /* 47 | #define MODBUS_ERRNO_MAP(XX) \ 48 | XX(CB_slave_addr, "the on_slave_addr callback failed") \ 49 | XX(CB_data_len, "the on_data_len callback failed") \ 50 | XX(CB_data_start, "the on_data_start callback failed") \ 51 | XX(CB_data_end, "the on_data_end callback failed") \ 52 | XX(CB_crc_error, "the on_crc_error callback failed") \ 53 | 54 | #define XX(n, s) MBERR_##n, 55 | enum modbus_errno { 56 | MODBUS_ERRNO_MAP(XX) 57 | } 58 | #undef XX 59 | */ 60 | 61 | enum modbus_parser_state 62 | { 63 | s_slave_addr = 0, 64 | s_func, 65 | s_len, 66 | 67 | /* For Single reads */ 68 | s_single_addr_hi, 69 | s_single_addr_lo, 70 | 71 | /* For Multiplie reads */ 72 | s_start_addr_hi, 73 | s_start_addr_lo, 74 | 75 | /* Quantity */ 76 | s_qty_hi, 77 | s_qty_lo, 78 | 79 | /* For Single reads */ 80 | /* 81 | s_single_data_hi, 82 | s_single_data_lo, 83 | */ 84 | 85 | /* For Multiple reads */ 86 | s_data, 87 | 88 | /* NOTE: Don't edit order of these three elements, 89 | * It's important for updating CRC (inside parser state machine) 90 | */ 91 | s_crc_lo, 92 | s_crc_hi, 93 | s_complete 94 | }; 95 | 96 | struct modbus_parser 97 | { 98 | /* PRIVATE */ 99 | enum modbus_parser_type type; 100 | enum modbus_parser_state state; 101 | uint8_t data_cnt; 102 | uint8_t frame_start; 103 | uint16_t frame_crc; /* CRC inside frame */ 104 | uint16_t calc_crc; /* Calculated CRC */ 105 | 106 | /* READ-ONLY */ 107 | uint8_t slave_addr; 108 | enum modbus_func function; 109 | uint16_t addr; 110 | uint16_t qty; 111 | uint8_t data_len; 112 | const uint8_t* data; 113 | // bool crc_error; 114 | uint16_t errno; 115 | 116 | /* PUBLIC */ 117 | void* arg; 118 | }; 119 | 120 | struct modbus_parser_settings 121 | { 122 | modbus_cb on_slave_addr; 123 | modbus_cb on_function; 124 | modbus_cb on_addr; 125 | modbus_cb on_qty; 126 | modbus_cb on_data_len; 127 | modbus_cb on_data_start; 128 | modbus_cb on_data_end; 129 | modbus_cb on_crc_error; 130 | modbus_cb on_complete; 131 | }; 132 | 133 | struct modbus_query 134 | { 135 | uint8_t slave_addr; 136 | enum modbus_func function; 137 | 138 | /* Address of register or coil. For MULTIPLE commands act as 139 | * Starting-Address 140 | */ 141 | uint16_t addr; 142 | 143 | /* Quantity of registers or coils for MULTIPLE commands */ 144 | uint16_t qty; 145 | 146 | /* Pointer to data to send. 147 | * For MODBUS_FUNC_WRITE_COILS command, least significant bit 148 | * addressing the lowest coil. 149 | */ 150 | uint16_t* data; 151 | 152 | /* Length of data 153 | * NOTE: Don't get confused with byte-count. byte-count will calculate 154 | * with generator function. 155 | */ 156 | uint8_t data_len; 157 | }; 158 | 159 | void modbus_parser_init(modbus_parser* parser, enum modbus_parser_type t); 160 | 161 | /* Initialize http_parser_settings members to 0 162 | */ 163 | void modbus_parser_settings_init(modbus_parser_settings* settings); 164 | 165 | /* Executes the parser. Returns number of parsed bytes 166 | */ 167 | size_t modbus_parser_execute(modbus_parser* parser, 168 | const modbus_parser_settings* settings, 169 | const uint8_t* data, 170 | size_t len); 171 | 172 | /* Generate ready-to-send query and place it to buf array. 173 | * In success, return size of encoded message, otherwise return negative value 174 | */ 175 | int modbus_gen_query(struct modbus_query* q, uint8_t* buf, size_t sz); 176 | 177 | void modbus_query_init(struct modbus_query* q); 178 | 179 | const char* modbus_func_str(enum modbus_func f); 180 | 181 | /* Calculate CRC from array of bytes */ 182 | uint16_t modbus_calc_crc(const uint8_t* data, size_t sz); 183 | 184 | /* Update CRC with single byte, useful for calculating 185 | * CRC from streming bytes 186 | */ 187 | void modbus_crc_update(uint16_t* crc, uint8_t data); 188 | 189 | #endif 190 | -------------------------------------------------------------------------------- /src/modbus.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "modbus.h" 4 | 5 | static const uint16_t crc_table[] = { 6 | 0X0000, 0XC0C1, 0XC181, 0X0140, 0XC301, 0X03C0, 0X0280, 0XC241, 0XC601, 7 | 0X06C0, 0X0780, 0XC741, 0X0500, 0XC5C1, 0XC481, 0X0440, 0XCC01, 0X0CC0, 8 | 0X0D80, 0XCD41, 0X0F00, 0XCFC1, 0XCE81, 0X0E40, 0X0A00, 0XCAC1, 0XCB81, 9 | 0X0B40, 0XC901, 0X09C0, 0X0880, 0XC841, 0XD801, 0X18C0, 0X1980, 0XD941, 10 | 0X1B00, 0XDBC1, 0XDA81, 0X1A40, 0X1E00, 0XDEC1, 0XDF81, 0X1F40, 0XDD01, 11 | 0X1DC0, 0X1C80, 0XDC41, 0X1400, 0XD4C1, 0XD581, 0X1540, 0XD701, 0X17C0, 12 | 0X1680, 0XD641, 0XD201, 0X12C0, 0X1380, 0XD341, 0X1100, 0XD1C1, 0XD081, 13 | 0X1040, 0XF001, 0X30C0, 0X3180, 0XF141, 0X3300, 0XF3C1, 0XF281, 0X3240, 14 | 0X3600, 0XF6C1, 0XF781, 0X3740, 0XF501, 0X35C0, 0X3480, 0XF441, 0X3C00, 15 | 0XFCC1, 0XFD81, 0X3D40, 0XFF01, 0X3FC0, 0X3E80, 0XFE41, 0XFA01, 0X3AC0, 16 | 0X3B80, 0XFB41, 0X3900, 0XF9C1, 0XF881, 0X3840, 0X2800, 0XE8C1, 0XE981, 17 | 0X2940, 0XEB01, 0X2BC0, 0X2A80, 0XEA41, 0XEE01, 0X2EC0, 0X2F80, 0XEF41, 18 | 0X2D00, 0XEDC1, 0XEC81, 0X2C40, 0XE401, 0X24C0, 0X2580, 0XE541, 0X2700, 19 | 0XE7C1, 0XE681, 0X2640, 0X2200, 0XE2C1, 0XE381, 0X2340, 0XE101, 0X21C0, 20 | 0X2080, 0XE041, 0XA001, 0X60C0, 0X6180, 0XA141, 0X6300, 0XA3C1, 0XA281, 21 | 0X6240, 0X6600, 0XA6C1, 0XA781, 0X6740, 0XA501, 0X65C0, 0X6480, 0XA441, 22 | 0X6C00, 0XACC1, 0XAD81, 0X6D40, 0XAF01, 0X6FC0, 0X6E80, 0XAE41, 0XAA01, 23 | 0X6AC0, 0X6B80, 0XAB41, 0X6900, 0XA9C1, 0XA881, 0X6840, 0X7800, 0XB8C1, 24 | 0XB981, 0X7940, 0XBB01, 0X7BC0, 0X7A80, 0XBA41, 0XBE01, 0X7EC0, 0X7F80, 25 | 0XBF41, 0X7D00, 0XBDC1, 0XBC81, 0X7C40, 0XB401, 0X74C0, 0X7580, 0XB541, 26 | 0X7700, 0XB7C1, 0XB681, 0X7640, 0X7200, 0XB2C1, 0XB381, 0X7340, 0XB101, 27 | 0X71C0, 0X7080, 0XB041, 0X5000, 0X90C1, 0X9181, 0X5140, 0X9301, 0X53C0, 28 | 0X5280, 0X9241, 0X9601, 0X56C0, 0X5780, 0X9741, 0X5500, 0X95C1, 0X9481, 29 | 0X5440, 0X9C01, 0X5CC0, 0X5D80, 0X9D41, 0X5F00, 0X9FC1, 0X9E81, 0X5E40, 30 | 0X5A00, 0X9AC1, 0X9B81, 0X5B40, 0X9901, 0X59C0, 0X5880, 0X9841, 0X8801, 31 | 0X48C0, 0X4980, 0X8941, 0X4B00, 0X8BC1, 0X8A81, 0X4A40, 0X4E00, 0X8EC1, 32 | 0X8F81, 0X4F40, 0X8D01, 0X4DC0, 0X4C80, 0X8C41, 0X4400, 0X84C1, 0X8581, 33 | 0X4540, 0X8701, 0X47C0, 0X4680, 0X8641, 0X8201, 0X42C0, 0X4380, 0X8341, 34 | 0X4100, 0X81C1, 0X8081, 0X4040 35 | }; 36 | 37 | #define CALLBACK_NOTIFY(FOR) \ 38 | do { \ 39 | if (settings->on_##FOR) { \ 40 | if (settings->on_##FOR(parser) != 0) { \ 41 | parser->errno = 1; \ 42 | } \ 43 | } \ 44 | } while (0) 45 | 46 | void 47 | modbus_parser_init(modbus_parser* parser, enum modbus_parser_type t) 48 | { 49 | void* arg = parser->arg; /* preserve application data */ 50 | memset(parser, 0, sizeof(*parser)); 51 | parser->arg = arg; 52 | parser->type = t; 53 | parser->state = s_slave_addr; 54 | parser->calc_crc = 0xFFFF; 55 | } 56 | 57 | void 58 | modbus_parser_settings_init(modbus_parser_settings* settings) 59 | { 60 | memset(settings, 0, sizeof(*settings)); 61 | } 62 | 63 | void 64 | modbus_query_init(struct modbus_query* q) 65 | { 66 | memset(q, 0, sizeof(*q)); 67 | } 68 | 69 | const char* 70 | modbus_func_str(enum modbus_func f) 71 | { 72 | switch (f) { 73 | #define XX(num, name, string) \ 74 | case MODBUS_FUNC_##name: \ 75 | return string; 76 | MODBUS_FUNC_MAP(XX) 77 | #undef XX 78 | default: 79 | return ""; 80 | } 81 | } 82 | 83 | /* 84 | static enum modbus_parser_state 85 | state_after_function(modbus_func f) 86 | { 87 | switch (f) { 88 | case MODBUS_FUNC_READ_COILS: 89 | case MODBUS_FUNC_READ_DISCRETE_IN: 90 | case MODBUS_FUNC_READ_HOLD_REG: 91 | case MODBUS_FUNC_READ_IN_REG: 92 | return s_len; 93 | 94 | case MODBUS_FUNC_WRITE_COIL: 95 | case MODBUS_FUNC_WRITE_REG: 96 | return s_single_addr_hi; 97 | 98 | case MODBUS_FUNC_WRITE_COILS: 99 | case MODBUS_FUNC_WRITE_REGS: 100 | return s_start_addr_hi; 101 | } 102 | } 103 | */ 104 | 105 | uint16_t 106 | modbus_calc_crc(const uint8_t* data, size_t sz) 107 | { 108 | uint8_t tmp; 109 | uint16_t crc = 0xFFFF; 110 | 111 | while (sz--) { 112 | tmp = *data++ ^ crc; 113 | crc >>= 8; 114 | crc ^= crc_table[tmp]; 115 | } 116 | return crc; 117 | } 118 | 119 | void 120 | modbus_crc_update(uint16_t* crc, uint8_t data) 121 | { 122 | uint8_t tmp; 123 | 124 | tmp = data ^ *crc; 125 | *crc >>= 8; 126 | *crc ^= crc_table[tmp]; 127 | } 128 | 129 | static size_t 130 | parse_query(modbus_parser* parser, 131 | const modbus_parser_settings* settings, 132 | const uint8_t* data, 133 | size_t len) 134 | { 135 | return 0; 136 | } 137 | 138 | static size_t 139 | parse_response(modbus_parser* parser, 140 | const modbus_parser_settings* settings, 141 | const uint8_t* data, 142 | size_t len) 143 | { 144 | size_t nparsed = 0; 145 | 146 | for (int i = 0; i < len; i++) { 147 | if (parser->errno != 0) 148 | return nparsed; 149 | 150 | /* Update CRC value */ 151 | if (parser->state < s_crc_lo) 152 | modbus_crc_update(&parser->calc_crc, *data); 153 | 154 | switch (parser->state) { 155 | case s_slave_addr: 156 | parser->slave_addr = *data; 157 | parser->state = s_func; 158 | CALLBACK_NOTIFY(slave_addr); 159 | break; 160 | 161 | case s_func: 162 | parser->function = (enum modbus_func) * data; 163 | switch (parser->function) { 164 | case MODBUS_FUNC_READ_COILS: 165 | case MODBUS_FUNC_READ_DISCRETE_IN: 166 | case MODBUS_FUNC_READ_HOLD_REG: 167 | case MODBUS_FUNC_READ_IN_REG: 168 | parser->state = s_len; 169 | break; 170 | 171 | case MODBUS_FUNC_WRITE_COIL: 172 | case MODBUS_FUNC_WRITE_REG: 173 | parser->state = s_single_addr_hi; 174 | break; 175 | 176 | case MODBUS_FUNC_WRITE_COILS: 177 | case MODBUS_FUNC_WRITE_REGS: 178 | parser->state = s_start_addr_hi; 179 | break; 180 | } 181 | CALLBACK_NOTIFY(function); 182 | break; 183 | 184 | case s_len: 185 | parser->data_len = *data; 186 | parser->state = s_data; 187 | parser->data_cnt = 0; 188 | CALLBACK_NOTIFY(data_len); 189 | break; 190 | 191 | case s_single_addr_hi: 192 | parser->addr = (uint16_t)*data << 8; 193 | parser->state = s_single_addr_lo; 194 | break; 195 | 196 | case s_single_addr_lo: 197 | parser->addr += *data; 198 | parser->state = s_data; 199 | parser->data_cnt = 0; 200 | parser->data_len = 2; 201 | CALLBACK_NOTIFY(addr); 202 | break; 203 | 204 | case s_start_addr_hi: 205 | parser->addr = (uint16_t)*data << 8; 206 | parser->state = s_start_addr_lo; 207 | break; 208 | 209 | case s_start_addr_lo: 210 | parser->addr += *data; 211 | parser->state = s_qty_hi; 212 | CALLBACK_NOTIFY(addr); 213 | break; 214 | 215 | case s_qty_hi: 216 | parser->qty = (uint16_t)*data << 8; 217 | parser->state = s_qty_lo; 218 | break; 219 | 220 | case s_qty_lo: 221 | parser->qty += *data; 222 | parser->state = s_crc_lo; 223 | CALLBACK_NOTIFY(qty); 224 | break; 225 | 226 | case s_data: 227 | if (parser->data_cnt == 0) { 228 | /* start of data */ 229 | parser->data = data; 230 | CALLBACK_NOTIFY(data_start); 231 | } 232 | 233 | parser->data_cnt++; 234 | if (parser->data_cnt == parser->data_len) { 235 | /* end data */ 236 | CALLBACK_NOTIFY(data_end); 237 | parser->state = s_crc_lo; 238 | } 239 | break; 240 | 241 | case s_crc_lo: 242 | parser->frame_crc = *data; 243 | parser->state = s_crc_hi; 244 | break; 245 | 246 | case s_crc_hi: { 247 | parser->frame_crc += (uint16_t)*data << 8; 248 | parser->state = s_complete; 249 | if (parser->frame_crc != parser->calc_crc) { 250 | parser->errno = 1; /* TODO: assign right value */ 251 | CALLBACK_NOTIFY(crc_error); 252 | } 253 | CALLBACK_NOTIFY(complete); 254 | } break; 255 | 256 | case s_complete: 257 | return nparsed; 258 | 259 | default: 260 | return 0; 261 | } 262 | 263 | nparsed++; 264 | data++; 265 | } 266 | 267 | return nparsed; 268 | } 269 | 270 | size_t 271 | modbus_parser_execute(modbus_parser* parser, 272 | const modbus_parser_settings* settings, 273 | const uint8_t* data, 274 | size_t len) 275 | { 276 | switch (parser->type) { 277 | case MODBUS_QUERY: 278 | return parse_query(parser, settings, data, len); 279 | 280 | case MODBUS_RESPONSE: 281 | return parse_response(parser, settings, data, len); 282 | } 283 | 284 | return 0; 285 | } 286 | 287 | /* Concatenate memory to Modbus Query */ 288 | #define MBQ_CAT_MEM(data, len) \ 289 | do { \ 290 | nwrite += len; \ 291 | if (nwrite > sz) { \ 292 | return -1; \ 293 | } else { \ 294 | memcpy(buf, data, len); \ 295 | buf += len; \ 296 | } \ 297 | } while (0) 298 | 299 | /* Concatenate single byte to Modbus Query */ 300 | #define MBQ_CAT_BYTE(b) \ 301 | nwrite++; \ 302 | do { \ 303 | if (nwrite > sz) { \ 304 | return -1; \ 305 | } else { \ 306 | *buf = b; \ 307 | buf++; \ 308 | } \ 309 | } while (0); 310 | 311 | /* Concatenate uint16_t as big-endian to modbus query. 312 | * Modbus uses big-endian for address and data items, except CRC 313 | */ 314 | #define MBQ_CAT_WORD(i) \ 315 | do { \ 316 | nwrite += 2; \ 317 | if (nwrite > sz) { \ 318 | return -1; \ 319 | } else { \ 320 | *buf++ = i >> 8; \ 321 | *buf++ = i & 0x00FF; \ 322 | } \ 323 | } while (0) 324 | 325 | int 326 | modbus_gen_query(struct modbus_query* q, uint8_t* buf, size_t sz) 327 | { 328 | int nwrite = 0; 329 | uint16_t crc; 330 | uint8_t* buf_start = buf; 331 | 332 | MBQ_CAT_BYTE(q->slave_addr); 333 | MBQ_CAT_BYTE(q->function); 334 | 335 | switch (q->function) { 336 | case MODBUS_FUNC_READ_COILS: 337 | case MODBUS_FUNC_READ_DISCRETE_IN: 338 | case MODBUS_FUNC_READ_HOLD_REG: 339 | case MODBUS_FUNC_READ_IN_REG: 340 | MBQ_CAT_WORD(q->addr); 341 | MBQ_CAT_WORD(q->qty); 342 | break; 343 | 344 | case MODBUS_FUNC_WRITE_COIL: 345 | case MODBUS_FUNC_WRITE_REG: 346 | MBQ_CAT_WORD(q->addr); 347 | /* Check input data */ 348 | if (q->data == NULL || q->data_len != 1) 349 | return -1; 350 | 351 | // MBQ_CAT_MEM(q->data, q->data_len); 352 | MBQ_CAT_WORD(*q->data); 353 | break; 354 | 355 | case MODBUS_FUNC_WRITE_COILS: { 356 | int16_t nbyte = MODBUS_COILS_BYTE_LEN(q->qty); 357 | /* Check input data */ 358 | if (q->data == NULL || q->data_len == 0) 359 | return -1; 360 | 361 | MBQ_CAT_WORD(q->addr); 362 | MBQ_CAT_WORD(q->qty); 363 | MBQ_CAT_BYTE(nbyte); 364 | 365 | while (nbyte > 0) { 366 | nbyte -= 2; 367 | if (nbyte >= 0) { 368 | MBQ_CAT_WORD(*q->data); 369 | } else { 370 | /* Write last byte, MSB of last register */ 371 | MBQ_CAT_BYTE(*q->data); 372 | } 373 | q->data++; 374 | } 375 | break; 376 | } 377 | 378 | case MODBUS_FUNC_WRITE_REGS: 379 | /* Check input data */ 380 | if (q->data == NULL || q->data_len == 0) 381 | return -1; 382 | 383 | MBQ_CAT_WORD(q->addr); 384 | MBQ_CAT_WORD(q->data_len); 385 | MBQ_CAT_BYTE(q->data_len * 2); 386 | 387 | for (int i = 0; i < q->data_len; i++) { 388 | MBQ_CAT_WORD(*(q->data + i)); 389 | } 390 | break; 391 | } 392 | 393 | crc = modbus_calc_crc(buf_start, nwrite); 394 | /* CRC must be represent as little-endian */ 395 | MBQ_CAT_BYTE(crc & 0x00FF); 396 | MBQ_CAT_BYTE(crc >> 8); 397 | 398 | return nwrite; 399 | } 400 | -------------------------------------------------------------------------------- /tests.c: -------------------------------------------------------------------------------- 1 | #include "modbus.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #define TEST_START() printf("<< %s STARTED >>\n", __func__) 7 | #define TEST_SUCCESS() printf("** %s SUCCESS **\n\n", __func__) 8 | 9 | #define HEX_FMT "0x%04X (%d)" 10 | #define HEX(n) n, n 11 | 12 | #define UINT16(p) ((*(&p) << 8) + (*(&p + 1))) 13 | 14 | #define ADD_CRC(buf) \ 15 | do { \ 16 | uint16_t crc = modbus_calc_crc(buf, sizeof(buf) - 2); \ 17 | buf[sizeof(buf) - 1] = crc >> 8; \ 18 | buf[sizeof(buf) - 2] = crc & 0x00FF; \ 19 | } while (0) 20 | 21 | #define VAR_DUMP(buf, sz) \ 22 | do { \ 23 | int _n = 0; \ 24 | for (int i = 0; i < sz; i++) { \ 25 | _n += printf("%02X ", buf[i]); \ 26 | if (_n > 70) { \ 27 | _n = 0; \ 28 | printf("\n"); \ 29 | } \ 30 | } \ 31 | printf("\n"); \ 32 | } while (0) 33 | 34 | #define ASSERT_WORD(buf, w) \ 35 | do { \ 36 | assert(*buf == (w >> 8)); \ 37 | assert(*(buf + 1) == (w & 0x00FF)); \ 38 | } while (0) 39 | 40 | #define ASSERT_QUERY_CRC(buf, len) \ 41 | do { \ 42 | uint16_t crc; \ 43 | crc = modbus_calc_crc(buf, len - 2); \ 44 | assert(buf[len - 2] == (crc & 0x00FF)); \ 45 | assert(buf[len - 1] == (crc >> 8)); \ 46 | } while (0) 47 | 48 | int 49 | on_slave_addr(struct modbus_parser* p) 50 | { 51 | printf("Slave addr: " HEX_FMT "\n", HEX(p->slave_addr)); 52 | return 0; 53 | } 54 | 55 | int 56 | on_function(struct modbus_parser* p) 57 | { 58 | printf("Function: %d (%s)\n", p->function, modbus_func_str(p->function)); 59 | return 0; 60 | } 61 | 62 | int 63 | on_addr(struct modbus_parser* p) 64 | { 65 | printf("Address: " HEX_FMT "\n", HEX(p->addr)); 66 | return 0; 67 | } 68 | 69 | int 70 | on_qty(struct modbus_parser* p) 71 | { 72 | printf("Quantity: " HEX_FMT "\n", HEX(p->qty)); 73 | return 0; 74 | } 75 | 76 | int 77 | on_data_len(struct modbus_parser* p) 78 | { 79 | printf("Data len: %d\n", p->data_len); 80 | return 0; 81 | } 82 | 83 | int 84 | on_data_start(struct modbus_parser* p) 85 | { 86 | printf("Data start\n"); 87 | return 0; 88 | } 89 | 90 | int 91 | on_data_end(struct modbus_parser* p) 92 | { 93 | int n = 0; 94 | 95 | printf("Data:\n"); 96 | 97 | for (int i = 0; i < p->data_len; i++) { 98 | n += printf("%02X ", *(p->data + i)); 99 | if (n > 70) { 100 | n = 0; 101 | printf("\n"); 102 | } 103 | } 104 | printf("\n"); 105 | return 0; 106 | } 107 | 108 | int 109 | on_crc_error(struct modbus_parser* p) 110 | { 111 | printf("CRC Error, 0x%04X != 0x%04X\n", p->frame_crc, p->calc_crc); 112 | return 0; 113 | } 114 | 115 | int 116 | on_complete(struct modbus_parser* p) 117 | { 118 | printf("Complete\n"); 119 | return 0; 120 | } 121 | 122 | void 123 | test_read_coils(struct modbus_parser* parser, 124 | struct modbus_parser_settings* settings) 125 | { 126 | uint8_t res[] = { 0x11, MODBUS_FUNC_READ_COILS, 0x03, 0xCD, 0x6B, 0x05, 0x00, 127 | 0x00 }; 128 | size_t n; 129 | 130 | TEST_START(); 131 | 132 | ADD_CRC(res); 133 | modbus_parser_init(parser, MODBUS_RESPONSE); 134 | n = modbus_parser_execute(parser, settings, res, sizeof(res)); 135 | 136 | assert(n == sizeof(res)); 137 | assert(parser->slave_addr == res[0]); 138 | assert(parser->function == res[1]); 139 | assert(parser->data_len == res[2]); 140 | TEST_SUCCESS(); 141 | } 142 | 143 | void 144 | test_read_discrete_in(struct modbus_parser* parser, 145 | struct modbus_parser_settings* settings) 146 | { 147 | uint8_t res[] = { 148 | 0x11, MODBUS_FUNC_READ_DISCRETE_IN, 0x03, 0xAC, 0xDB, 0x35, 0x00, 0x00 149 | }; 150 | size_t n; 151 | 152 | TEST_START(); 153 | 154 | ADD_CRC(res); 155 | modbus_parser_init(parser, MODBUS_RESPONSE); 156 | 157 | n = modbus_parser_execute(parser, settings, res, sizeof(res)); 158 | 159 | assert(n == sizeof(res)); 160 | assert(parser->slave_addr == res[0]); 161 | assert(parser->function == res[1]); 162 | assert(parser->data_len == res[2]); 163 | 164 | TEST_SUCCESS(); 165 | } 166 | 167 | void 168 | test_read_hold_reg(struct modbus_parser* parser, 169 | struct modbus_parser_settings* settings) 170 | { 171 | uint8_t res[] = { 0x11, MODBUS_FUNC_READ_HOLD_REG, 172 | 0x06, 0x02, 173 | 0x2B, 0x00, 174 | 0x00, 0x00, 175 | 0x64, 0x00, 176 | 0x00 }; 177 | size_t n; 178 | 179 | TEST_START(); 180 | 181 | ADD_CRC(res); 182 | modbus_parser_init(parser, MODBUS_RESPONSE); 183 | 184 | /* Parse first 3 bytes */ 185 | n = modbus_parser_execute(parser, settings, res, 3); 186 | /* Parse res of bytes */ 187 | n += modbus_parser_execute(parser, settings, res + 3, sizeof(res) - 3); 188 | 189 | assert(n == sizeof(res)); 190 | assert(parser->slave_addr == res[0]); 191 | assert(parser->function == res[1]); 192 | assert(parser->data_len == res[2]); 193 | 194 | TEST_SUCCESS(); 195 | } 196 | 197 | void 198 | test_read_in_reg(struct modbus_parser* parser, 199 | struct modbus_parser_settings* settings) 200 | { 201 | uint8_t res[] = { 202 | 0x11, MODBUS_FUNC_READ_IN_REG, 0x02, 0x00, 0x0A, 0x00, 0x00 203 | }; 204 | size_t n; 205 | 206 | TEST_START(); 207 | 208 | ADD_CRC(res); 209 | modbus_parser_init(parser, MODBUS_RESPONSE); 210 | 211 | n = modbus_parser_execute(parser, settings, res, sizeof(res)); 212 | 213 | assert(n == sizeof(res)); 214 | assert(parser->slave_addr == res[0]); 215 | assert(parser->function == res[1]); 216 | assert(parser->data_len == res[2]); 217 | 218 | TEST_SUCCESS(); 219 | } 220 | 221 | void 222 | test_write_single_coil(struct modbus_parser* parser, 223 | struct modbus_parser_settings* settings) 224 | { 225 | uint8_t res[] = { 0x11, MODBUS_FUNC_WRITE_COIL, 0x00, 0xAC, 0xFF, 0x00, 0x00, 226 | 0x00 }; 227 | size_t n; 228 | 229 | TEST_START(); 230 | 231 | ADD_CRC(res); 232 | modbus_parser_init(parser, MODBUS_RESPONSE); 233 | 234 | n = modbus_parser_execute(parser, settings, res, sizeof(res)); 235 | 236 | assert(n == sizeof(res)); 237 | assert(parser->slave_addr == res[0]); 238 | assert(parser->function == res[1]); 239 | assert(parser->addr == UINT16(res[2])); 240 | 241 | TEST_SUCCESS(); 242 | } 243 | 244 | void 245 | test_write_single_reg(struct modbus_parser* parser, 246 | struct modbus_parser_settings* settings) 247 | { 248 | uint8_t res[] = { 0x11, MODBUS_FUNC_WRITE_REG, 0x00, 0x01, 0x00, 0x03, 0x00, 249 | 0x00 }; 250 | size_t n; 251 | 252 | TEST_START(); 253 | 254 | ADD_CRC(res); 255 | modbus_parser_init(parser, MODBUS_RESPONSE); 256 | 257 | n = modbus_parser_execute(parser, settings, res, sizeof(res)); 258 | 259 | assert(n == sizeof(res)); 260 | assert(parser->slave_addr == res[0]); 261 | assert(parser->function == res[1]); 262 | assert(parser->addr == UINT16(res[2])); 263 | 264 | TEST_SUCCESS(); 265 | } 266 | 267 | void 268 | test_write_multiple_coil(struct modbus_parser* parser, 269 | struct modbus_parser_settings* settings) 270 | { 271 | uint8_t res[] = { 0x11, MODBUS_FUNC_WRITE_COILS, 0x00, 0x13, 0x00, 0x0A, 0x00, 272 | 0x00 }; 273 | size_t n; 274 | 275 | TEST_START(); 276 | 277 | ADD_CRC(res); 278 | modbus_parser_init(parser, MODBUS_RESPONSE); 279 | 280 | n = modbus_parser_execute(parser, settings, res, sizeof(res)); 281 | 282 | assert(n == sizeof(res)); 283 | assert(parser->slave_addr == res[0]); 284 | assert(parser->function == res[1]); 285 | assert(parser->addr == UINT16(res[2])); 286 | assert(parser->qty == UINT16(res[4])); 287 | 288 | TEST_SUCCESS(); 289 | } 290 | 291 | void 292 | test_write_multiple_reg(struct modbus_parser* parser, 293 | struct modbus_parser_settings* settings) 294 | { 295 | uint8_t res[] = { 0x11, MODBUS_FUNC_WRITE_REGS, 0x00, 0x01, 0x01, 0x02, 0x00, 296 | 0x00 }; 297 | size_t n; 298 | 299 | TEST_START(); 300 | 301 | ADD_CRC(res); 302 | modbus_parser_init(parser, MODBUS_RESPONSE); 303 | 304 | n = modbus_parser_execute(parser, settings, res, sizeof(res)); 305 | 306 | assert(n == sizeof(res)); 307 | assert(parser->slave_addr == res[0]); 308 | assert(parser->function == res[1]); 309 | assert(parser->addr == UINT16(res[2])); 310 | assert(parser->qty == UINT16(res[4])); 311 | 312 | TEST_SUCCESS(); 313 | } 314 | 315 | void 316 | test_crc_error(struct modbus_parser* parser, 317 | struct modbus_parser_settings* settings) 318 | { 319 | /* Packet with bad CRC value */ 320 | uint8_t res[] = { 0x11, MODBUS_FUNC_WRITE_REGS, 0x00, 0x01, 0x01, 0x02, 0x12, 321 | 0x34 }; 322 | size_t n; 323 | 324 | TEST_START(); 325 | 326 | modbus_parser_init(parser, MODBUS_RESPONSE); 327 | 328 | n = modbus_parser_execute(parser, settings, res, sizeof(res)); 329 | 330 | assert(n == sizeof(res)); 331 | assert(parser->slave_addr == res[0]); 332 | assert(parser->function == res[1]); 333 | assert(parser->addr == UINT16(res[2])); 334 | assert(parser->qty == UINT16(res[4])); 335 | assert(parser->errno != 0); /* TODO: check error value */ 336 | 337 | TEST_SUCCESS(); 338 | } 339 | 340 | void 341 | test_bad_len(struct modbus_parser* parser, 342 | struct modbus_parser_settings* settings) 343 | { 344 | uint8_t res[50] = { 345 | 0x11, MODBUS_FUNC_WRITE_REGS, 0x00, 0x01, 0x01, 0x02, 0x00, 0x00 346 | }; 347 | size_t n; 348 | uint16_t crc = modbus_calc_crc(res, 6); 349 | 350 | /* Add CRC */ 351 | res[7] = crc >> 8; 352 | res[6] = crc & 0x00FF; 353 | 354 | TEST_START(); 355 | 356 | ADD_CRC(res); 357 | modbus_parser_init(parser, MODBUS_RESPONSE); 358 | 359 | n = modbus_parser_execute(parser, settings, res, sizeof(res)); 360 | 361 | assert(n == 8); 362 | assert(parser->slave_addr == res[0]); 363 | assert(parser->function == res[1]); 364 | assert(parser->addr == UINT16(res[2])); 365 | assert(parser->qty == UINT16(res[4])); 366 | 367 | TEST_SUCCESS(); 368 | } 369 | 370 | void 371 | test_gen_read_coils(void) 372 | { 373 | struct modbus_query q = {.slave_addr = 0x12, 374 | .function = MODBUS_FUNC_READ_COILS, 375 | .addr = 0x11, 376 | .qty = 5 }; 377 | uint8_t buf[10]; 378 | int n; 379 | // uint16_t crc; 380 | 381 | TEST_START(); 382 | 383 | /* Check buffer size error. length of output stream is 8 bytes, 384 | * but we gave 7 bytes. 385 | */ 386 | n = modbus_gen_query(&q, buf, 7); 387 | assert(n == -1); 388 | 389 | n = modbus_gen_query(&q, buf, sizeof(buf)); 390 | 391 | printf("Query:\n"); 392 | VAR_DUMP(buf, n); 393 | 394 | assert(n > 0); 395 | assert(buf[0] == q.slave_addr); 396 | assert(buf[1] == q.function); 397 | ASSERT_WORD(&buf[2], q.addr); 398 | ASSERT_WORD(&buf[4], q.qty); 399 | ASSERT_QUERY_CRC(buf, n); 400 | 401 | TEST_SUCCESS(); 402 | } 403 | 404 | void 405 | test_gen_discrete_input(void) 406 | { 407 | struct modbus_query q = {.slave_addr = 0x12, 408 | .function = MODBUS_FUNC_READ_DISCRETE_IN, 409 | .addr = 0x11, 410 | .qty = 5 }; 411 | uint8_t buf[10]; 412 | int n; 413 | 414 | TEST_START(); 415 | 416 | memset(buf, 0, sizeof(buf)); 417 | 418 | n = modbus_gen_query(&q, buf, sizeof(buf)); 419 | 420 | printf("Query:\n"); 421 | VAR_DUMP(buf, n); 422 | 423 | assert(n > 0); 424 | assert(buf[0] == q.slave_addr); 425 | assert(buf[1] == q.function); 426 | ASSERT_WORD(&buf[2], q.addr); 427 | ASSERT_WORD(&buf[4], q.qty); 428 | ASSERT_QUERY_CRC(buf, n); 429 | 430 | TEST_SUCCESS(); 431 | } 432 | 433 | void 434 | test_gen_read_hold_reg(void) 435 | { 436 | struct modbus_query q = {.slave_addr = 0x23, 437 | .function = MODBUS_FUNC_READ_HOLD_REG, 438 | .addr = 0x45, 439 | .qty = 0x1234 }; 440 | uint8_t buf[10]; 441 | int n; 442 | 443 | TEST_START(); 444 | 445 | memset(buf, 0, sizeof(buf)); 446 | 447 | n = modbus_gen_query(&q, buf, sizeof(buf)); 448 | 449 | printf("Query:\n"); 450 | VAR_DUMP(buf, n); 451 | 452 | assert(n > 0); 453 | assert(buf[0] == q.slave_addr); 454 | assert(buf[1] == q.function); 455 | ASSERT_WORD(&buf[2], q.addr); 456 | ASSERT_WORD(&buf[4], q.qty); 457 | ASSERT_QUERY_CRC(buf, n); 458 | 459 | TEST_SUCCESS(); 460 | } 461 | 462 | void 463 | test_gen_read_input_reg(void) 464 | { 465 | struct modbus_query q = {.slave_addr = 0x45, 466 | .function = MODBUS_FUNC_READ_IN_REG, 467 | .addr = 0x56, 468 | .qty = 0x78 }; 469 | uint8_t buf[10]; 470 | int n; 471 | 472 | TEST_START(); 473 | 474 | memset(buf, 0, sizeof(buf)); 475 | 476 | n = modbus_gen_query(&q, buf, sizeof(buf)); 477 | 478 | printf("Query:\n"); 479 | VAR_DUMP(buf, n); 480 | 481 | assert(n > 0); 482 | assert(buf[0] == q.slave_addr); 483 | assert(buf[1] == q.function); 484 | ASSERT_WORD(&buf[2], q.addr); 485 | ASSERT_WORD(&buf[4], q.qty); 486 | ASSERT_QUERY_CRC(buf, n); 487 | 488 | TEST_SUCCESS(); 489 | } 490 | 491 | void 492 | test_gen_write_single_coil(void) 493 | { 494 | uint16_t data = MODBUS_COIL_HIGH; 495 | 496 | struct modbus_query q = {.slave_addr = 0x45, 497 | .function = MODBUS_FUNC_WRITE_COIL, 498 | .addr = 0x78, 499 | .data = &data, 500 | .data_len = 1 }; 501 | uint8_t buf[10]; 502 | int n; 503 | 504 | TEST_START(); 505 | 506 | memset(buf, 0, sizeof(buf)); 507 | 508 | n = modbus_gen_query(&q, buf, sizeof(buf)); 509 | 510 | printf("Query:\n"); 511 | VAR_DUMP(buf, n); 512 | 513 | assert(n > 0); 514 | assert(buf[0] == q.slave_addr); 515 | assert(buf[1] == q.function); 516 | ASSERT_WORD(&buf[2], q.addr); 517 | ASSERT_QUERY_CRC(buf, n); 518 | 519 | TEST_SUCCESS(); 520 | } 521 | 522 | void 523 | test_gen_write_single_reg(void) 524 | { 525 | uint16_t data = 0xABCD; 526 | 527 | struct modbus_query q = {.slave_addr = 0x45, 528 | .function = MODBUS_FUNC_WRITE_COIL, 529 | .addr = 0x78, 530 | .data = &data, 531 | .data_len = 1 }; 532 | uint8_t buf[10]; 533 | int n; 534 | 535 | TEST_START(); 536 | 537 | memset(buf, 0, sizeof(buf)); 538 | 539 | n = modbus_gen_query(&q, buf, sizeof(buf)); 540 | 541 | printf("Query:\n"); 542 | VAR_DUMP(buf, n); 543 | 544 | assert(n > 0); 545 | assert(buf[0] == q.slave_addr); 546 | assert(buf[1] == q.function); 547 | ASSERT_WORD(&buf[2], q.addr); 548 | ASSERT_QUERY_CRC(buf, n); 549 | 550 | TEST_SUCCESS(); 551 | } 552 | 553 | void 554 | test_gen_write_multiple_coil(void) 555 | { 556 | uint16_t data = 0b0101010100001111; 557 | 558 | struct modbus_query q = {.slave_addr = 0x45, 559 | .function = MODBUS_FUNC_WRITE_COILS, 560 | .addr = 0x78, 561 | .qty = 16, 562 | .data = &data, 563 | .data_len = 1 }; 564 | uint8_t buf[15]; 565 | int n; 566 | 567 | TEST_START(); 568 | 569 | memset(buf, 0, sizeof(buf)); 570 | 571 | n = modbus_gen_query(&q, buf, sizeof(buf)); 572 | 573 | printf("Query:\n"); 574 | VAR_DUMP(buf, n); 575 | 576 | assert(n > 0); 577 | assert(buf[0] == q.slave_addr); 578 | assert(buf[1] == q.function); 579 | ASSERT_WORD(&buf[2], q.addr); 580 | ASSERT_WORD(&buf[4], q.qty); 581 | assert(buf[6] == 2); 582 | ASSERT_QUERY_CRC(buf, n); 583 | 584 | TEST_SUCCESS(); 585 | } 586 | 587 | void 588 | test_gen_write_multiple_coil_2(void) 589 | { 590 | uint16_t data[] = { 0b1111000011110000, 0b0000000001110000 }; 591 | 592 | struct modbus_query q = {.slave_addr = 0x45, 593 | .function = MODBUS_FUNC_WRITE_COILS, 594 | .addr = 0x78, 595 | .qty = 23, 596 | .data = data, 597 | .data_len = 2 }; 598 | uint8_t buf[15]; 599 | int n; 600 | 601 | TEST_START(); 602 | 603 | memset(buf, 0, sizeof(buf)); 604 | 605 | n = modbus_gen_query(&q, buf, sizeof(buf)); 606 | 607 | printf("Query:\n"); 608 | VAR_DUMP(buf, n); 609 | 610 | assert(n > 0); 611 | assert(buf[0] == q.slave_addr); 612 | assert(buf[1] == q.function); 613 | ASSERT_WORD(&buf[2], q.addr); 614 | ASSERT_WORD(&buf[4], q.qty); 615 | assert(buf[6] == 3); 616 | ASSERT_QUERY_CRC(buf, n); 617 | 618 | TEST_SUCCESS(); 619 | } 620 | 621 | void 622 | test_gen_write_multiple_reg(void) 623 | { 624 | uint16_t data[] = { 0xAB, 0xCD, 0xEF }; 625 | 626 | struct modbus_query q = {.slave_addr = 0x45, 627 | .function = MODBUS_FUNC_WRITE_REGS, 628 | .addr = 0x78, 629 | .data = data, 630 | .data_len = 3 }; 631 | uint8_t buf[20]; 632 | int n; 633 | 634 | TEST_START(); 635 | 636 | memset(buf, 0, sizeof(buf)); 637 | 638 | n = modbus_gen_query(&q, buf, sizeof(buf)); 639 | 640 | printf("Query:\n"); 641 | VAR_DUMP(buf, n); 642 | 643 | assert(n > 0); 644 | assert(buf[0] == q.slave_addr); 645 | assert(buf[1] == q.function); 646 | ASSERT_WORD(&buf[2], q.addr); 647 | ASSERT_QUERY_CRC(buf, n); 648 | 649 | TEST_SUCCESS(); 650 | } 651 | 652 | int 653 | main(void) 654 | { 655 | struct modbus_parser parser; 656 | struct modbus_parser_settings settings; 657 | 658 | /* Initialization */ 659 | modbus_parser_init(&parser, MODBUS_RESPONSE); 660 | modbus_parser_settings_init(&settings); 661 | 662 | /* Assign custom callbacks */ 663 | settings.on_slave_addr = on_slave_addr; 664 | settings.on_function = on_function; 665 | settings.on_addr = on_addr; 666 | settings.on_qty = on_qty; 667 | settings.on_data_len = on_data_len; 668 | settings.on_data_end = on_data_end; 669 | settings.on_crc_error = on_crc_error; 670 | settings.on_complete = on_complete; 671 | 672 | /* Test response parser */ 673 | test_read_coils(&parser, &settings); 674 | test_read_discrete_in(&parser, &settings); 675 | test_read_hold_reg(&parser, &settings); 676 | test_read_in_reg(&parser, &settings); 677 | test_write_single_coil(&parser, &settings); 678 | test_write_single_reg(&parser, &settings); 679 | test_write_multiple_coil(&parser, &settings); 680 | test_write_multiple_reg(&parser, &settings); 681 | test_crc_error(&parser, &settings); 682 | test_bad_len(&parser, &settings); 683 | 684 | /* Test generator */ 685 | test_gen_read_coils(); 686 | test_gen_discrete_input(); 687 | test_gen_read_hold_reg(); 688 | test_gen_read_input_reg(); 689 | test_gen_write_single_coil(); 690 | test_gen_write_single_reg(); 691 | test_gen_write_multiple_coil(); 692 | test_gen_write_multiple_coil_2(); 693 | test_gen_write_multiple_reg(); 694 | return 0; 695 | } 696 | --------------------------------------------------------------------------------