Commit b87208447c3db94c22698c2c993f0573b30085f5

Authored by Peter M. Groen
1 parent 9a14850e

Added modbus_tcp

Showing 1 changed file with 663 additions and 0 deletions
include/modbus.h 0 → 100644
  1 +/*
  2 + * Copyright (c)2022 Peter M. Groen
  3 + *
  4 + * This source code is licensed under the MIT license found in the
  5 + * LICENSE file in hte root directory of this source tree
  6 + */
  7 +
  8 +#pragma once
  9 +
  10 +#include <cstring>
  11 +#include <stdint.h>
  12 +#include <string>
  13 +
  14 +#ifdef ENABLE_MODBUS_LOGGING
  15 +#include <cstdio>
  16 +#define LOG(fmt, ...) printf("[ modbuspp ]" fmt, ##__VA_ARGS__)
  17 +#else
  18 +#define LOG(...) (void)0
  19 +#endif
  20 +
  21 +#include <unistd.h>
  22 +#include <sys/socket.h>
  23 +#include <netinet/in.h>
  24 +#include <arpa/inet.h>
  25 +using X_SOCKET = int;
  26 +
  27 +#define X_ISVALIDSOCKET(s) ((s) >= 0)
  28 +#define X_CLOSE_SOCKET(s) close(s)
  29 +#define X_ISCONNECTSUCCEED(s) ((s) >= 0)
  30 +
  31 +using SOCKADDR = struct sockaddr;
  32 +using SOCKADDR_IN = struct sockaddr_in;
  33 +
  34 +#define MAX_MSG_LENGTH 260
  35 +
  36 +///Function Code
  37 +#define READ_COILS 0x01
  38 +#define READ_INPUT_BITS 0x02
  39 +#define READ_REGS 0x03
  40 +#define READ_INPUT_REGS 0x04
  41 +#define WRITE_COIL 0x05
  42 +#define WRITE_REG 0x06
  43 +#define WRITE_COILS 0x0F
  44 +#define WRITE_REGS 0x10
  45 +
  46 +///Exception Codes
  47 +
  48 +#define EX_ILLEGAL_FUNCTION 0x01 // Function Code not Supported
  49 +#define EX_ILLEGAL_ADDRESS 0x02 // Output Address not exists
  50 +#define EX_ILLEGAL_VALUE 0x03 // Output Value not in Range
  51 +#define EX_SERVER_FAILURE 0x04 // Slave Deive Fails to process request
  52 +#define EX_ACKNOWLEDGE 0x05 // Service Need Long Time to Execute
  53 +#define EX_SERVER_BUSY 0x06 // Server Was Unable to Accept MB Request PDU
  54 +#define EX_NEGATIVE_ACK 0x07
  55 +#define EX_MEM_PARITY_PROB 0x08
  56 +#define EX_GATEWAY_PROBLEMP 0x0A // Gateway Path not Available
  57 +#define EX_GATEWAY_PROBLEMF 0x0B // Target Device Failed to Response
  58 +#define EX_BAD_DATA 0XFF // Bad Data lenght or Address
  59 +
  60 +#define BAD_CON -1
  61 +
  62 +/**
  63 + * Modbus Operator Class
  64 + * Providing networking support and mobus operation support.
  65 + */
  66 +class modbus
  67 +{
  68 +
  69 +public:
  70 + bool err{};
  71 + int err_no{};
  72 + std::string error_msg;
  73 +
  74 + modbus(std::string host, uint16_t port);
  75 + ~modbus();
  76 +
  77 + bool modbus_connect();
  78 + void modbus_close() const;
  79 +
  80 + bool is_connected() const { return _connected; }
  81 +
  82 + void modbus_set_slave_id(int id);
  83 +
  84 + int modbus_read_coils(uint16_t address, uint16_t amount, bool *buffer);
  85 + int modbus_read_input_bits(uint16_t address, uint16_t amount, bool *buffer);
  86 + int modbus_read_holding_registers(uint16_t address, uint16_t amount, uint16_t *buffer);
  87 + int modbus_read_input_registers(uint16_t address, uint16_t amount, uint16_t *buffer);
  88 +
  89 + int modbus_write_coil(uint16_t address, const bool &to_write);
  90 + int modbus_write_register(uint16_t address, const uint16_t &value);
  91 + int modbus_write_coils(uint16_t address, uint16_t amount, const bool *value);
  92 + int modbus_write_registers(uint16_t address, uint16_t amount, const uint16_t *value);
  93 +
  94 +private:
  95 + bool _connected{};
  96 + uint16_t PORT{};
  97 + uint32_t _msg_id{};
  98 + int _slaveid{};
  99 + std::string HOST;
  100 +
  101 + X_SOCKET _socket{};
  102 + SOCKADDR_IN _server{};
  103 +
  104 + void modbus_build_request(uint8_t *to_send, uint16_t address, int func) const;
  105 +
  106 + int modbus_read(uint16_t address, uint16_t amount, int func);
  107 + int modbus_write(uint16_t address, uint16_t amount, int func, const uint16_t *value);
  108 +
  109 + ssize_t modbus_send(uint8_t *to_send, size_t length);
  110 + ssize_t modbus_receive(uint8_t *buffer) const;
  111 +
  112 + void modbuserror_handle(const uint8_t *msg, int func);
  113 +
  114 + void set_bad_con();
  115 + void set_bad_input();
  116 +};
  117 +
  118 +/**
  119 + * Main Constructor of Modbus Connector Object
  120 + * @param host IP Address of Host
  121 + * @param port Port for the TCP Connection
  122 + * @return A Modbus Connector Object
  123 + */
  124 +inline modbus::modbus(std::string host, uint16_t port = 502)
  125 +{
  126 + HOST = host;
  127 + PORT = port;
  128 + _slaveid = 1;
  129 + _msg_id = 1;
  130 + _connected = false;
  131 + err = false;
  132 + err_no = 0;
  133 + error_msg = "";
  134 +}
  135 +
  136 +/**
  137 + * Destructor of Modbus Connector Object
  138 + */
  139 +inline modbus::~modbus(void) = default;
  140 +
  141 +/**
  142 + * Modbus Slave ID Setter
  143 + * @param id ID of the Modbus Server Slave
  144 + */
  145 +inline void modbus::modbus_set_slave_id(int id)
  146 +{
  147 + _slaveid = id;
  148 +}
  149 +
  150 +/**
  151 + * Build up a Modbus/TCP Connection
  152 + * @return If A Connection Is Successfully Built
  153 + */
  154 +inline bool modbus::modbus_connect()
  155 +{
  156 + if (HOST.empty() || PORT == 0)
  157 + {
  158 + LOG("Missing Host and Port");
  159 + return false;
  160 + }
  161 + else
  162 + {
  163 + LOG("Found Proper Host %s and Port %d", HOST.c_str(), PORT);
  164 + }
  165 +
  166 + _socket = socket(AF_INET, SOCK_STREAM, 0);
  167 + if (!X_ISVALIDSOCKET(_socket))
  168 + {
  169 + LOG("Error Opening Socket");
  170 + return false;
  171 + }
  172 + else
  173 + {
  174 + LOG("Socket Opened Successfully");
  175 + }
  176 +
  177 + struct timeval timeout
  178 + {
  179 + };
  180 + timeout.tv_sec = 20; // after 20 seconds connect() will timeout
  181 + timeout.tv_usec = 0;
  182 +
  183 + setsockopt(_socket, SOL_SOCKET, SO_SNDTIMEO, (const char *)&timeout, sizeof(timeout));
  184 + setsockopt(_socket, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof(timeout));
  185 + _server.sin_family = AF_INET;
  186 + _server.sin_addr.s_addr = inet_addr(HOST.c_str());
  187 + _server.sin_port = htons(PORT);
  188 +
  189 + if (!X_ISCONNECTSUCCEED(connect(_socket, (SOCKADDR *)&_server, sizeof(_server))))
  190 + {
  191 + LOG("Connection Error");
  192 + return false;
  193 + }
  194 +
  195 + LOG("Connected");
  196 + _connected = true;
  197 + return true;
  198 +}
  199 +
  200 +/**
  201 + * Close the Modbus/TCP Connection
  202 + */
  203 +inline void modbus::modbus_close() const
  204 +{
  205 + X_CLOSE_SOCKET(_socket);
  206 + LOG("Socket Closed");
  207 +}
  208 +
  209 +/**
  210 + * Modbus Request Builder
  211 + * @param to_send Message Buffer to Be Sent
  212 + * @param address Reference Address
  213 + * @param func Modbus Functional Code
  214 + */
  215 +inline void modbus::modbus_build_request(uint8_t *to_send, uint16_t address, int func) const
  216 +{
  217 + to_send[0] = (uint8_t)(_msg_id >> 8u);
  218 + to_send[1] = (uint8_t)(_msg_id & 0x00FFu);
  219 + to_send[2] = 0;
  220 + to_send[3] = 0;
  221 + to_send[4] = 0;
  222 + to_send[6] = (uint8_t)_slaveid;
  223 + to_send[7] = (uint8_t)func;
  224 + to_send[8] = (uint8_t)(address >> 8u);
  225 + to_send[9] = (uint8_t)(address & 0x00FFu);
  226 +}
  227 +
  228 +/**
  229 + * Write Request Builder and Sender
  230 + * @param address Reference Address
  231 + * @param amount Amount of data to be Written
  232 + * @param func Modbus Functional Code
  233 + * @param value Data to Be Written
  234 + */
  235 +inline int modbus::modbus_write(uint16_t address, uint16_t amount, int func, const uint16_t *value)
  236 +{
  237 + int status = 0;
  238 + uint8_t *to_send;
  239 + if (func == WRITE_COIL || func == WRITE_REG)
  240 + {
  241 + to_send = new uint8_t[12];
  242 + modbus_build_request(to_send, address, func);
  243 + to_send[5] = 6;
  244 + to_send[10] = (uint8_t)(value[0] >> 8u);
  245 + to_send[11] = (uint8_t)(value[0] & 0x00FFu);
  246 + status = modbus_send(to_send, 12);
  247 + }
  248 + else if (func == WRITE_REGS)
  249 + {
  250 + to_send = new uint8_t[13 + 2 * amount];
  251 + modbus_build_request(to_send, address, func);
  252 + to_send[5] = (uint8_t)(7 + 2 * amount);
  253 + to_send[10] = (uint8_t)(amount >> 8u);
  254 + to_send[11] = (uint8_t)(amount & 0x00FFu);
  255 + to_send[12] = (uint8_t)(2 * amount);
  256 + for (int i = 0; i < amount; i++)
  257 + {
  258 + to_send[13 + 2 * i] = (uint8_t)(value[i] >> 8u);
  259 + to_send[14 + 2 * i] = (uint8_t)(value[i] & 0x00FFu);
  260 + }
  261 + status = modbus_send(to_send, 13 + 2 * amount);
  262 + }
  263 + else if (func == WRITE_COILS)
  264 + {
  265 + to_send = new uint8_t[14 + (amount - 1) / 8];
  266 + modbus_build_request(to_send, address, func);
  267 + to_send[5] = (uint8_t)(7 + (amount + 7) / 8);
  268 + to_send[10] = (uint8_t)(amount >> 8u);
  269 + to_send[11] = (uint8_t)(amount & 0x00FFu);
  270 + to_send[12] = (uint8_t)((amount + 7) / 8);
  271 + for (int i = 0; i < (amount + 7) / 8; i++)
  272 + to_send[13 + i] = 0; // init needed before summing!
  273 + for (int i = 0; i < amount; i++)
  274 + {
  275 + to_send[13 + i / 8] += (uint8_t)(value[i] << (i % 8u));
  276 + }
  277 + status = modbus_send(to_send, 14 + (amount - 1) / 8);
  278 + }
  279 + delete[] to_send;
  280 + return status;
  281 +}
  282 +
  283 +/**
  284 + * Read Request Builder and Sender
  285 + * @param address Reference Address
  286 + * @param amount Amount of Data to Read
  287 + * @param func Modbus Functional Code
  288 + */
  289 +inline int modbus::modbus_read(uint16_t address, uint16_t amount, int func)
  290 +{
  291 + uint8_t to_send[12];
  292 + modbus_build_request(to_send, address, func);
  293 + to_send[5] = 6;
  294 + to_send[10] = (uint8_t)(amount >> 8u);
  295 + to_send[11] = (uint8_t)(amount & 0x00FFu);
  296 + return modbus_send(to_send, 12);
  297 +}
  298 +
  299 +/**
  300 + * Read Holding Registers
  301 + * MODBUS FUNCTION 0x03
  302 + * @param address Reference Address
  303 + * @param amount Amount of Registers to Read
  304 + * @param buffer Buffer to Store Data Read from Registers
  305 + */
  306 +inline int modbus::modbus_read_holding_registers(uint16_t address, uint16_t amount, uint16_t *buffer)
  307 +{
  308 + if (_connected)
  309 + {
  310 + modbus_read(address, amount, READ_REGS);
  311 + uint8_t to_rec[MAX_MSG_LENGTH];
  312 + ssize_t k = modbus_receive(to_rec);
  313 + if (k == -1)
  314 + {
  315 + set_bad_con();
  316 + return BAD_CON;
  317 + }
  318 + modbuserror_handle(to_rec, READ_REGS);
  319 + if (err)
  320 + return err_no;
  321 + for (auto i = 0; i < amount; i++)
  322 + {
  323 + buffer[i] = ((uint16_t)to_rec[9u + 2u * i]) << 8u;
  324 + buffer[i] += (uint16_t)to_rec[10u + 2u * i];
  325 + }
  326 + return 0;
  327 + }
  328 + else
  329 + {
  330 + set_bad_con();
  331 + return BAD_CON;
  332 + }
  333 +}
  334 +
  335 +/**
  336 + * Read Input Registers
  337 + * MODBUS FUNCTION 0x04
  338 + * @param address Reference Address
  339 + * @param amount Amount of Registers to Read
  340 + * @param buffer Buffer to Store Data Read from Registers
  341 + */
  342 +inline int modbus::modbus_read_input_registers(uint16_t address, uint16_t amount, uint16_t *buffer)
  343 +{
  344 + if (_connected)
  345 + {
  346 + modbus_read(address, amount, READ_INPUT_REGS);
  347 + uint8_t to_rec[MAX_MSG_LENGTH];
  348 + ssize_t k = modbus_receive(to_rec);
  349 + if (k == -1)
  350 + {
  351 + set_bad_con();
  352 + return BAD_CON;
  353 + }
  354 + modbuserror_handle(to_rec, READ_INPUT_REGS);
  355 + if (err)
  356 + return err_no;
  357 + for (auto i = 0; i < amount; i++)
  358 + {
  359 + buffer[i] = ((uint16_t)to_rec[9u + 2u * i]) << 8u;
  360 + buffer[i] += (uint16_t)to_rec[10u + 2u * i];
  361 + }
  362 + return 0;
  363 + }
  364 + else
  365 + {
  366 + set_bad_con();
  367 + return BAD_CON;
  368 + }
  369 +}
  370 +
  371 +/**
  372 + * Read Coils
  373 + * MODBUS FUNCTION 0x01
  374 + * @param address Reference Address
  375 + * @param amount Amount of Coils to Read
  376 + * @param buffer Buffer to Store Data Read from Coils
  377 + */
  378 +inline int modbus::modbus_read_coils(uint16_t address, uint16_t amount, bool *buffer)
  379 +{
  380 + if (_connected)
  381 + {
  382 + if (amount > 2040)
  383 + {
  384 + set_bad_input();
  385 + return EX_BAD_DATA;
  386 + }
  387 + modbus_read(address, amount, READ_COILS);
  388 + uint8_t to_rec[MAX_MSG_LENGTH];
  389 + ssize_t k = modbus_receive(to_rec);
  390 + if (k == -1)
  391 + {
  392 + set_bad_con();
  393 + return BAD_CON;
  394 + }
  395 + modbuserror_handle(to_rec, READ_COILS);
  396 + if (err)
  397 + return err_no;
  398 + for (auto i = 0; i < amount; i++)
  399 + {
  400 + buffer[i] = (bool)((to_rec[9u + i / 8u] >> (i % 8u)) & 1u);
  401 + }
  402 + return 0;
  403 + }
  404 + else
  405 + {
  406 + set_bad_con();
  407 + return BAD_CON;
  408 + }
  409 +}
  410 +
  411 +/**
  412 + * Read Input Bits(Discrete Data)
  413 + * MODBUS FUNCITON 0x02
  414 + * @param address Reference Address
  415 + * @param amount Amount of Bits to Read
  416 + * @param buffer Buffer to store Data Read from Input Bits
  417 + */
  418 +inline int modbus::modbus_read_input_bits(uint16_t address, uint16_t amount, bool *buffer)
  419 +{
  420 + if (_connected)
  421 + {
  422 + if (amount > 2040)
  423 + {
  424 + set_bad_input();
  425 + return EX_BAD_DATA;
  426 + }
  427 + modbus_read(address, amount, READ_INPUT_BITS);
  428 + uint8_t to_rec[MAX_MSG_LENGTH];
  429 + ssize_t k = modbus_receive(to_rec);
  430 + if (k == -1)
  431 + {
  432 + set_bad_con();
  433 + return BAD_CON;
  434 + }
  435 + if (err)
  436 + return err_no;
  437 + for (auto i = 0; i < amount; i++)
  438 + {
  439 + buffer[i] = (bool)((to_rec[9u + i / 8u] >> (i % 8u)) & 1u);
  440 + }
  441 + modbuserror_handle(to_rec, READ_INPUT_BITS);
  442 + return 0;
  443 + }
  444 + else
  445 + {
  446 + return BAD_CON;
  447 + }
  448 +}
  449 +
  450 +/**
  451 + * Write Single Coils
  452 + * MODBUS FUNCTION 0x05
  453 + * @param address Reference Address
  454 + * @param to_write Value to be Written to Coil
  455 + */
  456 +inline int modbus::modbus_write_coil(uint16_t address, const bool &to_write)
  457 +{
  458 + if (_connected)
  459 + {
  460 + int value = to_write * 0xFF00;
  461 + modbus_write(address, 1, WRITE_COIL, (uint16_t *)&value);
  462 + uint8_t to_rec[MAX_MSG_LENGTH];
  463 + ssize_t k = modbus_receive(to_rec);
  464 + if (k == -1)
  465 + {
  466 + set_bad_con();
  467 + return BAD_CON;
  468 + }
  469 + modbuserror_handle(to_rec, WRITE_COIL);
  470 + if (err)
  471 + return err_no;
  472 + return 0;
  473 + }
  474 + else
  475 + {
  476 + set_bad_con();
  477 + return BAD_CON;
  478 + }
  479 +}
  480 +
  481 +/**
  482 + * Write Single Register
  483 + * FUCTION 0x06
  484 + * @param address Reference Address
  485 + * @param value Value to Be Written to Register
  486 + */
  487 +inline int modbus::modbus_write_register(uint16_t address, const uint16_t &value)
  488 +{
  489 + if (_connected)
  490 + {
  491 + modbus_write(address, 1, WRITE_REG, &value);
  492 + uint8_t to_rec[MAX_MSG_LENGTH];
  493 + ssize_t k = modbus_receive(to_rec);
  494 + if (k == -1)
  495 + {
  496 + set_bad_con();
  497 + return BAD_CON;
  498 + }
  499 + modbuserror_handle(to_rec, WRITE_COIL);
  500 + if (err)
  501 + return err_no;
  502 + return 0;
  503 + }
  504 + else
  505 + {
  506 + set_bad_con();
  507 + return BAD_CON;
  508 + }
  509 +}
  510 +
  511 +/**
  512 + * Write Multiple Coils
  513 + * MODBUS FUNCTION 0x0F
  514 + * @param address Reference Address
  515 + * @param amount Amount of Coils to Write
  516 + * @param value Values to Be Written to Coils
  517 + */
  518 +inline int modbus::modbus_write_coils(uint16_t address, uint16_t amount, const bool *value)
  519 +{
  520 + if (_connected)
  521 + {
  522 + uint16_t *temp = new uint16_t[amount];
  523 + for (int i = 0; i < amount; i++)
  524 + {
  525 + temp[i] = (uint16_t)value[i];
  526 + }
  527 + modbus_write(address, amount, WRITE_COILS, temp);
  528 + delete[] temp;
  529 + uint8_t to_rec[MAX_MSG_LENGTH];
  530 + ssize_t k = modbus_receive(to_rec);
  531 + if (k == -1)
  532 + {
  533 + set_bad_con();
  534 + return BAD_CON;
  535 + }
  536 + modbuserror_handle(to_rec, WRITE_COILS);
  537 + if (err)
  538 + return err_no;
  539 + return 0;
  540 + }
  541 + else
  542 + {
  543 + set_bad_con();
  544 + return BAD_CON;
  545 + }
  546 +}
  547 +
  548 +/**
  549 + * Write Multiple Registers
  550 + * MODBUS FUNCION 0x10
  551 + * @param address Reference Address
  552 + * @param amount Amount of Value to Write
  553 + * @param value Values to Be Written to the Registers
  554 + */
  555 +inline int modbus::modbus_write_registers(uint16_t address, uint16_t amount, const uint16_t *value)
  556 +{
  557 + if (_connected)
  558 + {
  559 + modbus_write(address, amount, WRITE_REGS, value);
  560 + uint8_t to_rec[MAX_MSG_LENGTH];
  561 + ssize_t k = modbus_receive(to_rec);
  562 + if (k == -1)
  563 + {
  564 + set_bad_con();
  565 + return BAD_CON;
  566 + }
  567 + modbuserror_handle(to_rec, WRITE_REGS);
  568 + if (err)
  569 + return err_no;
  570 + return 0;
  571 + }
  572 + else
  573 + {
  574 + set_bad_con();
  575 + return BAD_CON;
  576 + }
  577 +}
  578 +
  579 +/**
  580 + * Data Sender
  581 + * @param to_send Request to Be Sent to Server
  582 + * @param length Length of the Request
  583 + * @return Size of the request
  584 + */
  585 +inline ssize_t modbus::modbus_send(uint8_t *to_send, size_t length)
  586 +{
  587 + _msg_id++;
  588 + return send(_socket, (const char *)to_send, (size_t)length, 0);
  589 +}
  590 +
  591 +/**
  592 + * Data Receiver
  593 + * @param buffer Buffer to Store the Data Retrieved
  594 + * @return Size of Incoming Data
  595 + */
  596 +inline ssize_t modbus::modbus_receive(uint8_t *buffer) const
  597 +{
  598 + return recv(_socket, (char *)buffer, MAX_MSG_LENGTH, 0);
  599 +}
  600 +
  601 +inline void modbus::set_bad_con()
  602 +{
  603 + err = true;
  604 + error_msg = "BAD CONNECTION";
  605 +}
  606 +
  607 +inline void modbus::set_bad_input()
  608 +{
  609 + err = true;
  610 + error_msg = "BAD FUNCTION INPUT";
  611 +}
  612 +
  613 +/**
  614 + * Error Code Handler
  615 + * @param msg Message Received from the Server
  616 + * @param func Modbus Functional Code
  617 + */
  618 +inline void modbus::modbuserror_handle(const uint8_t *msg, int func)
  619 +{
  620 + err = false;
  621 + error_msg = "NO ERR";
  622 + if (msg[7] == func + 0x80)
  623 + {
  624 + err = true;
  625 + switch (msg[8])
  626 + {
  627 + case EX_ILLEGAL_FUNCTION:
  628 + error_msg = "1 Illegal Function";
  629 + break;
  630 + case EX_ILLEGAL_ADDRESS:
  631 + error_msg = "2 Illegal Address";
  632 + break;
  633 + case EX_ILLEGAL_VALUE:
  634 + error_msg = "3 Illegal Value";
  635 + break;
  636 + case EX_SERVER_FAILURE:
  637 + error_msg = "4 Server Failure";
  638 + break;
  639 + case EX_ACKNOWLEDGE:
  640 + error_msg = "5 Acknowledge";
  641 + break;
  642 + case EX_SERVER_BUSY:
  643 + error_msg = "6 Server Busy";
  644 + break;
  645 + case EX_NEGATIVE_ACK:
  646 + error_msg = "7 Negative Acknowledge";
  647 + break;
  648 + case EX_MEM_PARITY_PROB:
  649 + error_msg = "8 Memory Parity Problem";
  650 + break;
  651 + case EX_GATEWAY_PROBLEMP:
  652 + error_msg = "10 Gateway Path Unavailable";
  653 + break;
  654 + case EX_GATEWAY_PROBLEMF:
  655 + error_msg = "11 Gateway Target Device Failed to Respond";
  656 + break;
  657 + default:
  658 + error_msg = "UNK";
  659 + break;
  660 + }
  661 + }
  662 +}
  663 +
... ...