├── .clang-format ├── .gitattributes ├── .github └── README.md ├── .gitignore ├── LICENSE.md ├── README.md ├── include ├── mb_error.h ├── mb_master.h ├── mb_rtu.h ├── mb_slave.h ├── mb_tcp.h ├── mb_transport.h └── mbus.h ├── options.h.in ├── sample ├── ports │ ├── linux │ │ ├── mb_bsp.c │ │ ├── mb_bsp.h │ │ ├── rtu_slave.c │ │ └── tcp_rtu_master.c │ ├── rt-kernel │ │ ├── mb_bsp.c │ │ ├── mb_bsp.h │ │ ├── mb_cmds.c │ │ ├── mb_stm32p407.c │ │ ├── rtu_master.c │ │ ├── rtu_slave.c │ │ └── tcp_rtu_master.c │ └── windows │ │ ├── mb_bsp.c │ │ ├── mb_bsp.h │ │ ├── rtu_slave.c │ │ └── tcp_rtu_master.c ├── slave.c ├── slave.h └── tcp_slave.c ├── src ├── README.md ├── mb_crc.c ├── mb_crc.h ├── mb_pdu.h ├── mb_rtu.c ├── mb_slave.c ├── mb_tcp.c ├── mb_transport.c ├── mbal_rtu.h ├── mbal_tcp.h ├── mbus.c └── ports │ ├── linux │ ├── mb-tp.c │ ├── mb-tp.h │ ├── mbal_rtu.c │ ├── mbal_sys.h │ └── mbal_tcp.c │ ├── rt-kernel │ ├── mb_master.c │ ├── mbal_rtu.c │ ├── mbal_sys.h │ └── mbal_tcp.c │ └── windows │ ├── mbal_rtu.c │ ├── mbal_sys.h │ └── mbal_tcp.c ├── test ├── mbus_test.cpp ├── mocks.cpp ├── mocks.h ├── test_mbus.cpp ├── test_slave.cpp └── test_util.h └── version.h.in /.clang-format: -------------------------------------------------------------------------------- 1 | AlignAfterOpenBracket: AlwaysBreak 2 | AlignConsecutiveAssignments: false 3 | AlignConsecutiveMacros: true 4 | AllowAllArgumentsOnNextLine: false 5 | AllowAllParametersOfDeclarationOnNextLine: false 6 | AllowShortCaseLabelsOnASingleLine: false 7 | AllowShortFunctionsOnASingleLine: None 8 | AllowShortIfStatementsOnASingleLine: Never 9 | BinPackArguments: false 10 | BinPackParameters: false 11 | BreakBeforeBraces: Custom 12 | BraceWrapping: 13 | AfterCaseLabel: true 14 | AfterClass: true 15 | AfterControlStatement: true 16 | AfterEnum: true 17 | AfterFunction: true 18 | AfterNamespace: true 19 | AfterStruct: true 20 | AfterUnion: true 21 | BeforeCatch: true 22 | BeforeElse: true 23 | IndentBraces: false 24 | SplitEmptyFunction: true 25 | SplitEmptyRecord: true 26 | SplitEmptyNamespace: false 27 | AfterExternBlock: false 28 | ColumnLimit: 80 29 | ContinuationIndentWidth: 3 30 | IndentCaseLabels: false 31 | IndentWidth: 3 32 | PenaltyBreakAssignment: 10 33 | PenaltyBreakBeforeFirstCallParameter: 30 34 | PenaltyBreakComment: 10 35 | PenaltyBreakString: 10 36 | PenaltyExcessCharacter: 100 37 | PenaltyReturnTypeOnItsOwnLine: 100000 38 | PointerAlignment: Middle 39 | SortIncludes: false 40 | SpaceBeforeParens: NonEmptyParentheses 41 | UseTab: Never 42 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.github/README.md: -------------------------------------------------------------------------------- 1 | # Evaluation version 2 | 3 | This repository contains an evaluation version the M-Bus Modbus stack. 4 | The stack is written to an OS abstraction layer and can also be used 5 | in a bare metal application. Using the abstraction layer, the stack 6 | can run on Linux, Windows or on an RTOS. 7 | 8 | It does not contain complete ports and cannot be built without adding additional sources. 9 | 10 | See [readme](../../README.md) for more information on the complete version of 11 | the stack and the [releases](https://github.com/rtlabs-com/m-bus/releases) 12 | for binary downloads for common targets. 13 | 14 | This version of M-Bus can be used for evaluation purposes 15 | only. Contact sales@rt-labs.com if you intend to use this stack in a 16 | product or if you need assistance during evaluation. The commercial 17 | version of this stack is supplied with full sources. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build*/ 2 | *~ 3 | *# 4 | CMakeFiles/ 5 | CMakeCache.txt 6 | install 7 | .venv 8 | 9 | .cproject 10 | .project 11 | .dir-locals.el 12 | .vs 13 | 14 | # Object files 15 | *.o 16 | *.ko 17 | *.obj 18 | *.elf 19 | 20 | # Precompiled Headers 21 | *.gch 22 | *.pch 23 | 24 | # Libraries 25 | *.lib 26 | *.a 27 | *.la 28 | *.lo 29 | 30 | # Shared objects (inc. Windows DLLs) 31 | *.dll 32 | *.so 33 | *.so.* 34 | *.dylib 35 | 36 | # Executables 37 | *.exe 38 | *.out 39 | *.app 40 | *.i*86 41 | *.x86_64 42 | *.hex 43 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | This software is dual-licensed. 4 | 5 | ## GPL version 3 6 | 7 | This software is distributed under GPLv3. You are allowed to use this 8 | software for an open-source project with a compatible license. 9 | 10 | [GNU GPL license v3](https://www.gnu.org/licenses/gpl-3.0.html) 11 | 12 | ## Commercial license 13 | 14 | This software is also available under a commercial license with 15 | options for support and maintenance. Please contact sales@rt-labs.com 16 | for further details. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Modbus stack 2 | ============= 3 | 4 | This repository contains the M-Bus Modbus stack. The stack is written to an OS 5 | abstraction layer and can also be used in a bare metal 6 | application. Using the abstraction layer, the stack can run on Linux, 7 | Windows or on an RTOS. 8 | 9 | Web resources 10 | ------------- 11 | 12 | * Source repository: [https://github.com/rtlabs-com/m-bus](https://github.com/rtlabs-com/m-bus) 13 | * Documentation: [https://rt-labs.com/docs/m-bus](https://rt-labs.com/docs/m-bus) 14 | * RT-Labs (stack integration, certification services and training): [https://rt-labs.com](https://rt-labs.com) 15 | 16 | Features 17 | -------- 18 | 19 | * Modbus master (client) 20 | * Modbus slave (server) 21 | * Modbus TCP 22 | * Modbus RTU 23 | * All four primary tables (coils, inputs, holding registers, input registers) 24 | * Vendor-defined function codes 25 | * Diagnostics 26 | * Implemented in C, supports C++ 27 | * Ports available for Linux, Windows (Modbus TCP only) and RT-Kernel 28 | * Porting layer allows extending the stack to new ports 29 | 30 | License 31 | ------- 32 | 33 | This software is dual-licensed, with GPL version 3 and a commercial license. See 34 | LICENSE.md for more details. 35 | 36 | Requirements 37 | ------------ 38 | 39 | cmake 40 | 41 | * cmake 3.28 or later 42 | 43 | For Linux: 44 | 45 | * gcc 4.6 or later 46 | 47 | For rt-kernel: 48 | 49 | * Workbench 2018.1 or later 50 | 51 | 52 | Contributions 53 | ------------- 54 | 55 | Contributions are welcome. If you want to contribute you will need to sign a 56 | Contributor License Agreement and send it to us either by e-mail or by physical 57 | mail. More information is available 58 | on [https://rt-labs.com/contribution](https://rt-labs.com/contribution). 59 | -------------------------------------------------------------------------------- /include/mb_error.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2012 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | /* 17 | * Modbus error codes 18 | */ 19 | 20 | #ifndef MB_ERROR_H 21 | #define MB_ERROR_H 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | /* Modbus exceptions. 28 | * 29 | * A slave callback can return these. A master read 30 | * or write operation may result in one of these exceptions. 31 | * 32 | * See "MODBUS Application Protocol Specification" chapter 7: 33 | * "MODBUS Exception Responses". 34 | */ 35 | #define EILLEGAL_FUNCTION -1 /**< Modbus exception ILLEGAL_FUNCTION */ 36 | #define EILLEGAL_DATA_ADDRESS -2 /**< Modbus exception ILLEGAL_DATA_ADDRESS */ 37 | #define EILLEGAL_DATA_VALUE -3 /**< Modbus exception ILLEGAL_DATA_VALUE */ 38 | #define ESLAVE_DEVICE_FAILURE -4 /**< Modbus exception SLAVE_DEVICE_FAILURE */ 39 | 40 | /* Modbus communication errors */ 41 | #define ECRC_FAIL -101 /**< CRC check failed */ 42 | #define EFRAME_NOK -102 /**< Received frame not valid */ 43 | #define ESLAVE_ID -103 /**< Unexpected slave ID */ 44 | #define ETIMEOUT -104 /**< Receive timed out */ 45 | #define EUNKNOWN_EXCEPTION -105 /**< Modbus exception code not recognised */ 46 | 47 | /** 48 | * Get error as string literal 49 | * 50 | * \param error Error code for communication error or exception 51 | * \return String with name of error or "Unknown error" if invalid 52 | */ 53 | static inline const char * mb_error_literal (int error) 54 | { 55 | switch (error) 56 | { 57 | case EILLEGAL_FUNCTION: 58 | return "EILLEGAL_FUNCTION"; 59 | case EILLEGAL_DATA_ADDRESS: 60 | return "EILLEGAL_DATA_ADDRESS"; 61 | case EILLEGAL_DATA_VALUE: 62 | return "EILLEGAL_DATA_VALUE"; 63 | case ESLAVE_DEVICE_FAILURE: 64 | return "ESLAVE_DEVICE_FAILURE"; 65 | case ECRC_FAIL: 66 | return "ECRC_FAIL"; 67 | case EFRAME_NOK: 68 | return "EFRAME_NOK"; 69 | case ESLAVE_ID: 70 | return "ESLAVE_ID"; 71 | case ETIMEOUT: 72 | return "ETIMEOUT"; 73 | case EUNKNOWN_EXCEPTION: 74 | return "EUNKNOWN_EXCEPTION"; 75 | default: 76 | return "Unknown error"; 77 | } 78 | } 79 | 80 | #ifdef __cplusplus 81 | } 82 | #endif 83 | 84 | #endif /* MB_ERROR_H */ 85 | -------------------------------------------------------------------------------- /include/mb_master.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * http://www.rt-labs.com 9 | * Copyright 2011 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #ifdef __rtk__ 17 | 18 | /** 19 | * \addtogroup mb_master Modbus master 20 | * \{ 21 | */ 22 | 23 | #ifndef MB_MASTER_H 24 | #define MB_MASTER_H 25 | 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | 30 | #include "mbus.h" 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | /* Modbus shell commands */ 38 | extern const shell_cmd_t cmd_mb_read; 39 | extern const shell_cmd_t cmd_mb_write; 40 | 41 | typedef mbus_cfg_t mb_master_cfg_t; 42 | 43 | /** 44 | * Initialise the modbus master stack 45 | */ 46 | drv_t * mb_master_init ( 47 | const char * name, 48 | const mb_master_cfg_t * cfg, 49 | mb_transport_t * transport); 50 | 51 | /** 52 | * Open modbus slave communication stream 53 | * 54 | * This function opens a communication stream to a modbus device. The 55 | * modbus device is specified through the filename, ie: 56 | * 57 | * \code 58 | * /modbus0/15 59 | * \endcode 60 | * 61 | * indicates the RTU slave with ID decimal 15 on modbus bus 0, whereas: 62 | * 63 | * \code 64 | * /modbus1/192.168.10.101 65 | * \endcode 66 | * 67 | * indicates the TCP slave with IP 192.168.10.101 on modbus bus 1. 68 | * 69 | * \param name slave identifier 70 | * 71 | * \return handle to be used in all further modbus operations 72 | */ 73 | static inline int mb_open (const char * name) 74 | { 75 | return open (name, O_RDWR, 0); 76 | } 77 | 78 | /** 79 | * Close modbus slave communication stream 80 | * 81 | * This function closes a communication stream to a modbus device. 82 | * 83 | * \param fd modbus handle 84 | */ 85 | static inline void mb_close (int fd) 86 | { 87 | close (fd); 88 | } 89 | 90 | /** 91 | * Return a handle to the transport data layer. 92 | * 93 | * \param fd modbus handle 94 | * 95 | * \return handle 96 | */ 97 | void * mb_master_transport_get (int fd); 98 | 99 | /** 100 | * Read modbus addresses 101 | * 102 | * This function reads a number of modbus address contents starting 103 | * from the given address. The function will read coils, inputs, 104 | * holding registers or input registers as specified by the given 105 | * starting address. 106 | * 107 | * The address is 1-based and can be specified by the MB_ADDRESS 108 | * macro, like so: 109 | * 110 | * \code 111 | * result = mb_read (fd, MB_ADDRESS (3, 10001), 10, buffer); 112 | * \endcode 113 | * 114 | * The example would read 10 input registers starting from modbus 115 | * address 3x10001. 116 | * 117 | * The caller must provide a buffer large enough to hold the requested 118 | * data. 119 | * 120 | * For coil and input status reads, the returned buffer contents will 121 | * be a bit string where the LSB is the bit that was read from the 122 | * starting address, and the MSB is the bit that was read from the 123 | * final address. 124 | * 125 | * For holding or input register reads, the returned buffer contents 126 | * will be in the correct byte format. Register contents are 127 | * byte-swapped from network byte order, if required. 128 | * 129 | * \param fd modbus handle 130 | * \param address 1-based starting address 131 | * \param quantity number of addresses to read 132 | * \param buffer output buffer 133 | * 134 | * \return 0 on success, error code otherwise 135 | */ 136 | int mb_read (int fd, mb_address_t address, uint16_t quantity, void * buffer); 137 | 138 | /** 139 | * Write modbus addresses 140 | * 141 | * This function writes a number of modbus addresses starting from the 142 | * given address. The function will write coils or holding registers 143 | * as specified by the given starting address. 144 | * 145 | * The address is 1-based and can be specified by the MB_ADDRESS macro, like so: 146 | * 147 | * \code 148 | * result = mb_write (fd, MB_ADDRESS (0, 23), 100, buffer); 149 | * \endcode 150 | * 151 | * The example would write 100 coils starting from modbus address 152 | * 0x23. 153 | * 154 | * For coil writes, the input buffer shall be a bit string where the 155 | * LSB is the bit that will be written to the starting address, and 156 | * the MSB is the bit that will be written to the final address. 157 | * 158 | * For holding register writes, the input buffer shall be a number of 159 | * 16-byte register values in the CPU byte ordering. Register values 160 | * are byte-swapped to network byte ordering before transmission, if 161 | * required. 162 | * 163 | * \param fd modbus handle 164 | * \param address 1-based starting address 165 | * \param quantity number of addresses to read 166 | * \param buffer input buffer 167 | * 168 | * \return 0 on success, error code otherwise 169 | */ 170 | int mb_write (int fd, mb_address_t address, uint16_t quantity, void * buffer); 171 | 172 | /** 173 | * Write a single modbus address 174 | * 175 | * This function writes a single modbus address. The function will 176 | * write a single coil or holding register as specified by the given 177 | * address. 178 | * 179 | * The is 1-based address and can be specified by the MB_ADDRESS macro, like so: 180 | * 181 | * \code 182 | * result = mb_write_single (fd, MB_ADDRESS (0, 23), 1); 183 | * \endcode 184 | * 185 | * The example would set the coil at modbus address 0x23. 186 | * 187 | * For coil writes, the coil shall be set if value is non-zero, or 188 | * cleared if the value is zero. 189 | * 190 | * For holding register writes, the value shall be in the CPU byte 191 | * ordering. The value is byte-swapped to network byte ordering before 192 | * transmission, if required. 193 | * 194 | * \param fd modbus handle 195 | * \param address 1-based address to be written to 196 | * \param value the value to be written 197 | * 198 | * \return 0 on success, error code otherwise 199 | */ 200 | int mb_write_single (int fd, mb_address_t address, uint16_t value); 201 | 202 | /** 203 | * Diagnostic loopback 204 | * 205 | * This function issues the diagnostic loopback function. The buffer 206 | * contents are sent the slave. The data read from the slave is placed 207 | * into the buffer (up to a maximum of \a size). 208 | * 209 | * \param fd modbus handle 210 | * \param size the number of bytes of data to send 211 | * \param buffer the value to be written 212 | * 213 | * \return number of bytes received on success, error code otherwise 214 | */ 215 | int mb_loopback (int fd, uint16_t size, void * buffer); 216 | 217 | /** 218 | * Send raw message 219 | * 220 | * This function sends the message in \a msg to the modbus slave. The 221 | * message contents are ignored. 222 | * 223 | * \param fd modbus handle 224 | * \param msg message to send 225 | * \param size size of message 226 | * 227 | * \return error code 228 | */ 229 | int mb_send_msg (int fd, const void * msg, uint8_t size); 230 | 231 | /** 232 | * Get raw message 233 | * 234 | * This function returns the next message received from the slave. The 235 | * message contents are ignored. 236 | * 237 | * The caller must provide a buffer large enough to hold the requested 238 | * data. 239 | * 240 | * \param fd modbus handle 241 | * \param msg message received 242 | * \param size max size of message 243 | * 244 | * \return number of bytes received on success, error code otherwise 245 | */ 246 | int mb_get_msg (int fd, void * msg, uint16_t size); 247 | 248 | #ifdef __cplusplus 249 | } 250 | #endif 251 | 252 | #endif /* MB_MASTER_H */ 253 | 254 | /** 255 | * \} 256 | */ 257 | 258 | #endif /* __rtk__ */ 259 | -------------------------------------------------------------------------------- /include/mb_rtu.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2012 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | /** 17 | * \file 18 | * Modbus RTU transport data layer 19 | */ 20 | 21 | #ifndef MB_RTU_H 22 | #define MB_RTU_H 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | #include "mb_transport.h" 29 | #include "mb_export.h" 30 | 31 | #include 32 | 33 | /** 34 | * Serial port parity setting 35 | */ 36 | typedef enum mb_rtu_parity 37 | { 38 | ODD, /**< Odd parity */ 39 | EVEN, /**< Even parity */ 40 | NONE, /**< No parity */ 41 | } mb_rtu_parity_t; 42 | 43 | /** 44 | * Serial port configuration 45 | */ 46 | typedef struct mb_rtu_serial_cfg 47 | { 48 | int baudrate; /**< Baud rate [bits/s] */ 49 | mb_rtu_parity_t parity; /**< Parity bit setting */ 50 | } mb_rtu_serial_cfg_t; 51 | 52 | /** 53 | * RTU layer configuration 54 | */ 55 | typedef struct mb_rtu_cfg 56 | { 57 | uint8_t id; /**< \private Unused field kept for compatibility */ 58 | 59 | /** 60 | * Name of serial port to use, e.g. "/sio0" 61 | */ 62 | const char * serial; 63 | 64 | /** 65 | * Serial port configuration 66 | */ 67 | const mb_rtu_serial_cfg_t * serial_cfg; 68 | 69 | /** 70 | * This callback function is called before and after 71 | * transmission. It should enable or disable the serial port for 72 | * transmission, as indicated by \a level. 73 | * 74 | * The callback can be disabled if not required, by setting it to 75 | * NULL. 76 | * 77 | * \param level 1 to enable transmission, 0 to disable 78 | */ 79 | void (*tx_enable) (int level); 80 | 81 | /** 82 | * This function should initialise the T1P5 and T3P5 one-shot timers, using 83 | * the given timeouts. 84 | * 85 | * Note that it may be possible to use a single timer if it has at 86 | * least two match values. 87 | * 88 | * \param t1p5 T1P5 timeout [us]. This is the maximum tolerated 89 | * time between transferred bytes. Exceeding this 90 | * limit may be interpreted as an end-of-frame 91 | * condition. Its value is calculated by the stack 92 | * based on the baud rate. 93 | * A typical value is 750 us. 94 | * \param t3p5 T3P5 timeout [us]. This is the minimum tolerated 95 | * time between transferred frames. Two frames 96 | * separated by a smaller delay may be interpreted 97 | * as a single frame. Its value is calculated by 98 | * the stack. A typical value is 1750 us. 99 | */ 100 | void (*tmr_init) (uint32_t t1p5, uint32_t t3p5); 101 | 102 | /** 103 | * This function should start or restart the T1P5 and T3P5 one-shot timers. 104 | * 105 | * \param t1p5_expired Function to be called when T1P5 expires. 106 | * If NULL, timer should not be started. 107 | * \param t3p5_expired Function to be called when T3P5 expires. 108 | * If NULL, timer should not be started. 109 | * \param arg Argument passed to t1p5_expired and t3p5_expired 110 | */ 111 | void (*tmr_start) ( 112 | void (*t1p5_expired) (void * arg), 113 | void (*t3p5_expired) (void * arg), 114 | void * arg); 115 | } mb_rtu_cfg_t; 116 | 117 | /** 118 | * Reconfigure the Modbus RTU serial parameters 119 | * 120 | * Calling this function reconfigures the serial port and calculates 121 | * the T1P5 and T3P5 timeouts. 122 | * 123 | * Example: 124 | * \code 125 | * static const mb_rtu_serial_cfg_t serial_cfg = 126 | * { 127 | * .baudrate = 115200, 128 | * .parity = NONE, 129 | * }; 130 | * mb_rtu_serial_cfg (rtu, &serial_cfg); 131 | * \endcode 132 | * 133 | * \param rtu RTU layer handle 134 | * \param serial_cfg Serial port configuration 135 | */ 136 | MB_EXPORT void mb_rtu_serial_cfg ( 137 | mb_transport_t * rtu, 138 | const mb_rtu_serial_cfg_t * serial_cfg); 139 | 140 | /** 141 | * Initialise and configure the Modbus RTU data layer 142 | * 143 | * This function allocates memory needed by the instance and initialises it. 144 | * A CC_ASSERT() is triggered upon memory allocation failure. 145 | * The serial port is also opened and configured. 146 | * User may use the returned handle when initialising a Modbus master or slave 147 | * instance. 148 | * 149 | * The following example creates a Modbus RTU transport layer instance with 150 | * stub callback functions: 151 | * \code 152 | * static void enable_transmission (int level) 153 | * { 154 | * // Turn transceiver or or off, if possible. 155 | * } 156 | * 157 | * static void init_rtu_timers (uint32_t t1p5_us, uint32_t t3p5_us) 158 | * { 159 | * // Set \a t1p5_us as the timeout for the T1P5 timer (if required). 160 | * // Set \a t3p5_us as the timeout for the T3P5 timer (if required). 161 | * } 162 | * 163 | * static void start_rtu_timers ( 164 | * void (*t1p5_expired) (void * arg), 165 | * void (*t3p5_expired) (void * arg), 166 | * void * arg) 167 | * { 168 | * // Set \a t1p5_expired as the timeout callback for the T1P5 one-shot timer. 169 | * // Set \a t3p5_expired as the timeout callback for the T3P5 one-shot timer. 170 | * // Start the T1P5 and T3P5 one-shot timers. 171 | * // This could also be managed by hardware 172 | * // directly if suitable hardware support exists. 173 | * } 174 | * 175 | * mb_transport_t * create_rtu_layer (void) 176 | * { 177 | * mb_transport_t * rtu; 178 | * static const mb_rtu_serial_cfg_t serial_cfg = 179 | * { 180 | * .baudrate = 115200, 181 | * .parity = NONE, 182 | * }; 183 | * static const mb_rtu_cfg_t rtu_cfg = 184 | * { 185 | * .serial = "/sio1", 186 | * .serial_cfg = &serial_cfg, 187 | * .tx_enable = enable_transmission, 188 | * .tmr_init = init_rtu_timers, 189 | * .tmr_start = start_rtu_timers, 190 | * }; 191 | * 192 | * rtu = mb_rtu_init (&rtu_cfg); 193 | * 194 | * return rtu; 195 | * } 196 | * \endcode 197 | * 198 | * \see mbus_create() and mb_slave_init(). 199 | * 200 | * \param cfg RTU layer configuration 201 | * 202 | * \return Handle to be used in further operations 203 | */ 204 | MB_EXPORT mb_transport_t * mb_rtu_init (const mb_rtu_cfg_t * cfg); 205 | 206 | #ifdef __cplusplus 207 | } 208 | #endif 209 | 210 | #endif /* MB_RTU_H */ 211 | -------------------------------------------------------------------------------- /include/mb_slave.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2012 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | /** 17 | * \file 18 | * Modbus slave 19 | */ 20 | 21 | #ifndef MB_SLAVE_H 22 | #define MB_SLAVE_H 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | #include "mb_transport.h" 29 | #include "mb_error.h" 30 | 31 | #include "mb_export.h" 32 | 33 | /** 34 | * I/O table interface 35 | * 36 | * This is the interface the slave task uses when reading and writing 37 | * to I/Os belonging to a table. The table could be coils, inputs, holding 38 | * registers and input registers. 39 | */ 40 | typedef struct mb_iotable 41 | { 42 | /** 43 | * Number of valid addresses in table. 44 | */ 45 | size_t size; 46 | 47 | /** 48 | * Getter callback 49 | * 50 | * This function is called in response to a read 51 | * operation. The function should perform the get operation by 52 | * storing the requested data in the \a data array. 53 | * 54 | * The mb_slave_bit_set() and mb_slave_reg_set() functions can be 55 | * used to set the contents of the data array. 56 | * 57 | * The address supplied to the input parameter is 0-based. 58 | * 59 | * \param address Starting address (0-based) 60 | * \param data Data array to store the requested data 61 | * \param quantity Number of addresses to get 62 | * 63 | * \return 0 on success, ESLAVE_DEVICE_FAILURE exception otherwise 64 | */ 65 | int (*get) (uint16_t address, uint8_t * data, size_t quantity); 66 | 67 | /** 68 | * Setter callback 69 | * 70 | * This function is called in response to a write 71 | * operation. The function should perform the set operation using 72 | * the data provided in the \a data array. 73 | * 74 | * The mb_slave_bit_get() and mb_slave_reg_get() functions can be 75 | * used to extract the contents of the data array. 76 | * 77 | * This callback is not used for input status and input register 78 | * tables, and should then be set to NULL. 79 | * 80 | * The address supplied to the input parameter is 0-based. 81 | * 82 | * \param address Starting address (0-based) 83 | * \param data Data array with the supplied data 84 | * \param quantity Number of addresses to set 85 | * 86 | * \return 0 on success, ESLAVE_DEVICE_FAILURE exception otherwise 87 | */ 88 | int (*set) (uint16_t address, uint8_t * data, size_t quantity); 89 | 90 | } mb_iotable_t; 91 | 92 | /** 93 | * Vendor-defined function 94 | */ 95 | typedef struct mb_vendor_func 96 | { 97 | /** 98 | * Vendor-defined function code 99 | */ 100 | uint8_t function; 101 | 102 | /** 103 | * Function callback 104 | * 105 | * This function is called in response to 106 | * receiving the function code specified in \a function. 107 | * 108 | * The \a data array holds the received request data. The number of 109 | * valid bytes in \a data is given by \a size. The first byte of the 110 | * data is the function code. 111 | * 112 | * The callback may return a response by storing it in the \a data 113 | * array. The callback should return the number of valid bytes in 114 | * the response, or 0 if no response is to be sent. The response 115 | * should normally include the function code unchanged. 116 | * 117 | * The callback can return a Modbus exception if an error 118 | * occurs. See mb_error.h for available exceptions. 119 | * 120 | * An extended exception can be sent by manually setting the MSB of 121 | * the function code (\a data[0]), and following it with the 122 | * exception data, returning the total number of valid bytes. 123 | * 124 | * \param data Request data, including the function code 125 | * \param size Size of request data (bytes) 126 | * 127 | * \return Size of response on success, Modbus exception otherwise 128 | */ 129 | int (*callback) (uint8_t * data, size_t size); 130 | 131 | } mb_vendor_func_t; 132 | 133 | /** 134 | * I/O map 135 | */ 136 | typedef struct mb_iomap 137 | { 138 | mb_iotable_t coils; /**< Coil definitions */ 139 | mb_iotable_t inputs; /**< Input status definitions */ 140 | mb_iotable_t holding_registers; /**< Holding register definitions */ 141 | mb_iotable_t input_registers; /**< Input register definitions */ 142 | size_t num_vendor_funcs; /**< Number of vendor-defined functions */ 143 | const mb_vendor_func_t * vendor_funcs; /**< Vendor-defined functions */ 144 | } mb_iomap_t; 145 | 146 | /** 147 | * Slave configuration 148 | */ 149 | typedef struct mb_slave_cfg 150 | { 151 | uint8_t id; /**< Slave ID */ 152 | uint32_t priority; /**< Priority of slave task */ 153 | size_t stack_size; /**< Stack size of slave task (in bytes) */ 154 | const mb_iomap_t * iomap; /**< Slave I/O map */ 155 | } mb_slave_cfg_t; 156 | 157 | /** 158 | * Modbus slave 159 | * 160 | * Instantiated by calling mb_slave_init(). 161 | * 162 | * The contents of this structure should be interpreted as an implementation 163 | * detail. Direct access by user application is prohibited. 164 | */ 165 | typedef struct mb_slave 166 | { 167 | uint8_t id; /**< \private */ 168 | int running; /**< \private */ 169 | mb_transport_t * transport; /**< \private */ 170 | const mb_iomap_t * iomap; /**< \private */ 171 | } mb_slave_t; 172 | 173 | /** 174 | * Create an instance of the Modbus slave stack 175 | * 176 | * This function allocates memory needed by the instance, initialises it 177 | * and starts a task for serving requests from the Modbus master. 178 | * A CC_ASSERT() is triggered upon memory allocation failure. 179 | * User may use the returned handle in further operations. 180 | * 181 | * The \a cfg parameter is used to configure the slave behaviour. It 182 | * contains the I/O map which defines how the slave responds to Modbus 183 | * requests. 184 | * 185 | * The following examples illustrates how to write a simple 186 | * slave: 187 | * 188 | * \code 189 | * #include 190 | * #include 191 | * 192 | * static bool coils[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 193 | * static uint16_t hold[4] = { 0x0000, 0x0000, 0x0000, 0x0000 }; 194 | * 195 | * static int coil_get (uint16_t address, uint8_t * data, size_t quantity) 196 | * { 197 | * uint16_t offset; 198 | * 199 | * for (offset = 0; offset < quantity; offset++) 200 | * { 201 | * uint32_t bit = address + offset; 202 | * 203 | * mb_slave_bit_set (data, offset, coils[bit]); 204 | * } 205 | * return 0; 206 | * } 207 | * 208 | * static int coil_set (uint16_t address, uint8_t * data, size_t quantity) 209 | * { 210 | * uint16_t offset; 211 | * 212 | * for (offset = 0; offset < quantity; offset++) 213 | * { 214 | * uint32_t bit = address + offset; 215 | * 216 | * coils[bit] = mb_slave_bit_get (data, offset); 217 | * } 218 | * return 0; 219 | * } 220 | * 221 | * static int input_get (uint16_t address, uint8_t * data, size_t quantity) 222 | * { 223 | * uint16_t offset; 224 | * 225 | * for (offset = 0; offset < quantity; offset++) 226 | * { 227 | * mb_slave_bit_set (data, offset, 0); 228 | * } 229 | * return 0; 230 | * } 231 | * 232 | * static int hold_get (uint16_t address, uint8_t * data, size_t quantity) 233 | * { 234 | * uint16_t offset; 235 | * 236 | * for (offset = 0; offset < quantity; offset++) 237 | * { 238 | * uint32_t reg = address + offset; 239 | * 240 | * mb_slave_reg_set (data, offset, hold[reg]); 241 | * } 242 | * return 0; 243 | * } 244 | * 245 | * static int hold_set (uint16_t address, uint8_t * data, size_t quantity) 246 | * { 247 | * uint16_t offset; 248 | * 249 | * for (offset = 0; offset < quantity; offset++) 250 | * { 251 | * uint32_t reg = address + offset; 252 | * 253 | * hold[reg] = mb_slave_reg_get (data, offset); 254 | * } 255 | * return 0; 256 | * } 257 | * 258 | * static int reg_get (uint16_t address, uint8_t * data, size_t quantity) 259 | * { 260 | * uint16_t offset; 261 | * 262 | * for (offset = 0; offset < quantity; offset++) 263 | * { 264 | * mb_slave_reg_set (data, offset, 0x1234); 265 | * } 266 | * return 0; 267 | * } 268 | * 269 | * static int ping (uint8_t * data, size_t rx_count) 270 | * { 271 | * char * message = "Hello World"; 272 | * memcpy (data, message, strlen (message)); 273 | * return strlen (message); 274 | * } 275 | * 276 | * static const mb_vendor_func_t vendor_funcs[] = 277 | * { 278 | * { 101, ping }, 279 | * }; 280 | * 281 | * static const mb_iomap_t mb_slave_iomap = 282 | * { 283 | * .coils = { 16, coil_get, coil_set }, // 16 coils 284 | * .inputs = { 2, input_get, NULL }, // 2 input status bits 285 | * .holding_registers = { 4, hold_get, hold_set }, // 4 holding registers 286 | * .input_registers = { 5, reg_get, NULL }, // 5 input registers 287 | * .num_vendor_funcs = NELEMENTS (vendor_funcs), // 1 vendor function 288 | * .vendor_funcs = vendor_funcs, 289 | * }; 290 | * 291 | * static const mb_rtu_cfg_t mb_rtu_cfg = 292 | * { 293 | * .serial = "/sio0", 294 | * .sio_cfg = &sio_cfg, 295 | * .tx_enable = tx_en, 296 | * .tmr_init = mb_tmr_init, 297 | * .tmr_start = mb_tmr_start, 298 | * }; 299 | * 300 | * static const mb_slave_cfg_t mb_slave_cfg = 301 | * { 302 | * .id = 2, // Slave ID: 2 303 | * .priority = 15, 304 | * .stack_size = 1024, 305 | * .iomap = &mb_slave_iomap 306 | * }; 307 | * 308 | * mb_slave_t * start_slave (void) 309 | * { 310 | * mb_slave_t * slave; 311 | * mb_transport_t * rtu; 312 | * 313 | * rtu = mb_rtu_init (&mb_rtu_cfg); 314 | * slave = mb_slave_init (&mb_slave_cfg, rtu); 315 | * return slave; 316 | * } 317 | * \endcode 318 | * 319 | * The example would create an RTU Modbus slave with ID 2. The slave has 16 320 | * coils, 2 input status bits, 4 holding registers and 5 input 321 | * registers. The input status bits return the constant value 0, 322 | * whereas the input registers return the constant value 0x1234. The 323 | * coils and holding registers can be written and read. There is one 324 | * vendor function (function code 101) that returns the string "Hello World" 325 | * when called. 326 | * 327 | * The callbacks should return 0 on success, or a Modbus exception code as 328 | * documented in mb_error.h, except for the vendor function callback 329 | * which returns the size of the response or a Modbus exception code. 330 | * 331 | * This function returns a handle to the slave which can be used for 332 | * further operations as documented below. 333 | * 334 | * \param cfg Slave configuration 335 | * \param transport Handle to transport data layer. 336 | * Must be initialised. See mb_rtu_init() and 337 | * mb_tcp_init(). 338 | * The stack assumes ownership of this object. 339 | * 340 | * \return Slave handle to be used in further operations 341 | */ 342 | MB_EXPORT mb_slave_t * mb_slave_init ( 343 | const mb_slave_cfg_t * cfg, 344 | mb_transport_t * transport); 345 | 346 | /** 347 | * Shut down a running slave 348 | * 349 | * This function orders the slave task to shut down. 350 | * The slave task will eventually exit after serving any pending requests. 351 | * 352 | * \param slave Slave handle 353 | */ 354 | MB_EXPORT void mb_slave_shutdown (mb_slave_t * slave); 355 | 356 | /** 357 | * Return a handle to the transport data layer 358 | * 359 | * \param slave Slave handle 360 | * 361 | * \return Handle to the transport data layer 362 | */ 363 | MB_EXPORT void * mb_slave_transport_get (mb_slave_t * slave); 364 | 365 | /** 366 | * Change the slave ID 367 | * 368 | * \param slave Slave handle 369 | * \param id New ID 370 | */ 371 | MB_EXPORT void mb_slave_id_set (mb_slave_t * slave, uint8_t id); 372 | 373 | /** 374 | * Get bit value from Modbus data 375 | * 376 | * This function gets the bit with index \a address in the 377 | * \a data array. The function is intended for use by the coils- and 378 | * inputs callbacks. 379 | * 380 | * In the following example, the value 1 is retrieved from the bit string 381 | * \a data at \a address 28: 382 | * 383 | * \code 384 | * uint8_t data[4] = {0x00, 0x00, 0x00, 0x10}; 385 | * int value = mb_slave_bit_get (data, 28); 386 | * \endcode 387 | * 388 | * \param data Bit-string (Modbus encoded) 389 | * \param address 0-based index of bit to get 390 | * 391 | * \return 0 if bit is clear, 1 if bit is set 392 | */ 393 | MB_EXPORT int mb_slave_bit_get (const void * data, uint32_t address); 394 | 395 | /** 396 | * Set bit value in Modbus data 397 | * 398 | * This function sets the bit with index \a address in the \a data array 399 | * to \a value. The function is intended for use by the coils- and inputs 400 | * callbacks. 401 | * 402 | * In the following example, bit 4 in byte 3 is set to 1: 403 | * 404 | * \code 405 | * uint8_t data[4] = {0x00, 0x00, 0x00, 0x00}; 406 | * mb_slave_bit_set (data, 28, 1); 407 | * \endcode 408 | * 409 | * \param data Bit-string (Modbus encoded) 410 | * \param address 0-based index of bit to set 411 | * \param value new value (0 to clear, non-zero to set) 412 | */ 413 | MB_EXPORT void mb_slave_bit_set (void * data, uint32_t address, int value); 414 | 415 | /** 416 | * Get register value from Modbus data 417 | * 418 | * This function gets the register with index \a address in the \a 419 | * data array. The data will be converted from network byte ordering 420 | * if required. The function is intended for use by the 421 | * holding_registers.set and input_registers.set callbacks. 422 | * 423 | * In the following example, the value 0x1234 is retrieved from the Modbus 424 | * \a data at \a address 1: 425 | * 426 | * \code 427 | * uint8_t data[4] = {0x00, 0x00, 0x12, 0x34}; 428 | * uint16_t value = mb_slave_reg_get (data, 1); 429 | * \endcode 430 | * 431 | * \param data Register array (Modbus encoded) 432 | * \param address 0-based index of register to get 433 | * 434 | * \return register value 435 | */ 436 | MB_EXPORT uint16_t mb_slave_reg_get (const void * data, uint32_t address); 437 | 438 | /** 439 | * Set register value in Modbus data 440 | * 441 | * This function sets the register with index \a address in the \a 442 | * data array. The data is converted to network byte ordering if 443 | * required. The function is intended for use by the 444 | * holding_registers.get and input_registers.get callbacks. 445 | * 446 | * In the following example, byte 2 in the buffer \a data is set to 0x12 447 | * while byte 3 is set to 0x34. 448 | * 449 | * \code 450 | * uint8_t data[4] = {0x00, 0x00, 0x00, 0x00}; 451 | * mb_slave_reg_set (data, 1, 0x1234); 452 | * \endcode 453 | * 454 | * \param data Register array (Modbus encoded) 455 | * \param address 0-based index of register to set 456 | * \param value New value 457 | */ 458 | MB_EXPORT void mb_slave_reg_set (void * data, uint32_t address, uint16_t value); 459 | 460 | /** 461 | * \private 462 | */ 463 | void mb_slave_handle_request (mb_slave_t * slave, pdu_txn_t * transaction); 464 | 465 | #ifdef __cplusplus 466 | } 467 | #endif 468 | 469 | #endif /* MB_SLAVE_H */ 470 | -------------------------------------------------------------------------------- /include/mb_tcp.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2015 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | /** 17 | * \file 18 | * Modbus TCP transport data layer 19 | */ 20 | 21 | #ifndef MB_TCP_H 22 | #define MB_TCP_H 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | #include "mb_transport.h" 29 | #include "mb_export.h" 30 | 31 | #include 32 | 33 | /** 34 | * Default TCP server port 35 | */ 36 | #define MODBUS_DEFAULT_PORT 502 37 | 38 | /** 39 | * TCP layer configuration 40 | */ 41 | typedef struct mb_tcp_cfg 42 | { 43 | uint16_t port; /**< TCP server port. 44 | * Modbus slave will listen on this local port. 45 | * Modbus master will connect to slaves at this remote port. 46 | */ 47 | } mb_tcp_cfg_t; 48 | 49 | /** 50 | * Initialise and configure the Modbus TCP data layer 51 | * 52 | * This function allocates memory needed by the instance and initialises it. 53 | * A CC_ASSERT() is triggered upon memory allocation failure. 54 | * User may use the returned handle when initialising a Modbus master or slave 55 | * instance. 56 | * 57 | * The following example creates a Modbus TCP layer instance with the default 58 | * TCP server port for Modbus: 59 | * \code 60 | * mb_transport_t * transport; 61 | * static const mb_tcp_cfg_t tcp_cfg = 62 | * { 63 | * .port = MODBUS_DEFAULT_PORT, 64 | * }; 65 | * transport = mb_tcp_init (&tcp_cfg); 66 | * \endcode 67 | * 68 | * \see mbus_create() and mb_slave_init(). 69 | * 70 | * \param cfg TCP layer configuration 71 | * 72 | * \return Handle to be used in further operations 73 | */ 74 | MB_EXPORT mb_transport_t * mb_tcp_init (const mb_tcp_cfg_t * cfg); 75 | 76 | #ifdef __cplusplus 77 | } 78 | #endif 79 | 80 | #endif /* MB_TCP_H */ 81 | 82 | -------------------------------------------------------------------------------- /include/mb_transport.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2015 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | /* 17 | * Modbus transport layer 18 | */ 19 | 20 | #ifndef MB_TRANSPORT_H 21 | #define MB_TRANSPORT_H 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | #include "mb_error.h" 28 | #include 29 | #include 30 | #include 31 | 32 | /** 33 | * PDU transaction 34 | */ 35 | typedef struct pdu_txn 36 | { 37 | int arg; /**< Transport peer identifier */ 38 | uint16_t id; /**< Transaction ID */ 39 | uint8_t unit; /**< Transaction Unit ID */ 40 | uint8_t flags; /**< Transaction flags */ 41 | void * data; /**< Data for transaction */ 42 | } pdu_txn_t; 43 | 44 | /** 45 | * Transport data layer instance 46 | */ 47 | typedef struct mb_transport mb_transport_t; 48 | 49 | struct mb_transport 50 | { 51 | int (*bringup) (mb_transport_t * transport, const char * name); 52 | int (*shutdown) (mb_transport_t * transport, int arg); 53 | bool (*is_down) (mb_transport_t * transport); 54 | void (*tx) ( 55 | mb_transport_t * transport, 56 | const pdu_txn_t * transaction, 57 | size_t size); 58 | int (*rx) ( 59 | mb_transport_t * transport, 60 | pdu_txn_t * transaction, 61 | uint32_t tmo); 62 | bool (*rx_is_bc) (mb_transport_t * transport); 63 | bool (*rx_avail) (mb_transport_t * transport); 64 | bool is_server; 65 | }; 66 | 67 | /** 68 | * Bring up transport layer 69 | * 70 | * \param transport Handle to transport layer 71 | * \param name Name of peer device 72 | * 73 | * \return File descriptor if successful, negative number otherwise 74 | */ 75 | int mb_transport_bringup (mb_transport_t * transport, const char * name); 76 | 77 | /** 78 | * Shut down transport layer 79 | * 80 | * \param transport Handle to transport layer 81 | * \param arg Argument, e.g. a slave file descriptor 82 | * 83 | * \return Always 0 84 | */ 85 | int mb_transport_shutdown (mb_transport_t * transport, int arg); 86 | 87 | /** 88 | * Return true if transport layer is currently down 89 | * 90 | * \param transport Handle to transport layer 91 | * 92 | * \return True transport layer is currently down, 93 | * false otherwise 94 | */ 95 | bool mb_transport_is_down (mb_transport_t * transport); 96 | 97 | /** 98 | * Transmit modbus protocol data unit (PDU). The PDU will be formatted 99 | * according to the application data unit protocol (ADU) of the 100 | * underlying transport protocol. 101 | * 102 | * \param transport Handle to transport layer 103 | * \param transaction PDU transaction 104 | * \param size Size of PDU 105 | */ 106 | void mb_pdu_tx ( 107 | mb_transport_t * transport, 108 | const pdu_txn_t * transaction, 109 | size_t size); 110 | 111 | /** 112 | * Receive modbus protocol data unit (PDU). The PDU is extracted from 113 | * the application data unit protocol (ADU) of the underlying 114 | * transport protocol. 115 | * 116 | * The PDU must be of size MAX_PDU_SIZE, in order to hold the maximum 117 | * message size. 118 | * 119 | * The function will return with ETIMEOUT if no message was received 120 | * in \a tmo ticks. A \a tmo of 0 means to wait forever. See mb_error.h 121 | * for other possible error codes. 122 | * 123 | * \param transport Handle to transport layer 124 | * \param transaction PDU Transaction 125 | * \param tmo Timeout in milliseconds 126 | * 127 | * \return Size of PDU on success, negative error code otherwise 128 | */ 129 | int mb_pdu_rx ( 130 | mb_transport_t * transport, 131 | pdu_txn_t * transaction, 132 | uint32_t tmo); 133 | 134 | /** 135 | * Return true if last received PDU was part of a broadcast message, 136 | * false otherwise 137 | * 138 | * \param transport Handle to transport layer 139 | * 140 | * \return True if last received PDU was part of a broadcast message, 141 | * false otherwise 142 | */ 143 | bool mb_pdu_rx_bc (mb_transport_t * transport); 144 | 145 | /** 146 | * Return true if data has been received, false otherwise 147 | * 148 | * \param transport Handle to transport layer 149 | * 150 | * \return True if data has been received, false otherwise 151 | */ 152 | bool mb_pdu_rx_avail (mb_transport_t * transport); 153 | 154 | #ifdef __cplusplus 155 | } 156 | #endif 157 | 158 | #endif /* MB_TRANSPORT_H */ 159 | 160 | -------------------------------------------------------------------------------- /include/mbus.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2011 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | /* 17 | * Modbus master 18 | */ 19 | 20 | #ifndef MBUS_H 21 | #define MBUS_H 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | #include "mb_transport.h" 28 | #include "mb_error.h" 29 | 30 | #include "mb_export.h" 31 | 32 | #include 33 | 34 | /** 35 | * Modbus master 36 | * 37 | * Instantiated at startup using mbus_create() or mbus_init(). 38 | * 39 | * The contents of this structure should be interpreted as an implementation 40 | * detail. Direct access by user application is prohibited. 41 | */ 42 | typedef struct mbus 43 | { 44 | uint32_t timeout; /**< \private */ 45 | mb_transport_t * transport; /**< \private */ 46 | pdu_txn_t transaction; /**< \private */ 47 | void * scratch; /**< \private */ 48 | } mbus_t; 49 | 50 | /** 51 | * Modbus table selection 52 | * 53 | * These are the primary tables in the Modbus data model. 54 | */ 55 | typedef enum mb_table 56 | { 57 | MB_TABLE_COILS = 0, /**< Coils. Single bit read/write */ 58 | MB_TABLE_INPUTS = 1, /**< Inputs. Single bit read-only */ 59 | MB_TABLE_INPUT_REGISTERS = 3, /**< Input registers. 16 bits read-only */ 60 | MB_TABLE_HOLDING_REGISTERS = 4, /**< Holding registers. 16 bits read/write */ 61 | } mb_table_t; 62 | 63 | /** 64 | * Modbus address 65 | * 66 | * May be constructed using MB_ADDRESS(). 67 | */ 68 | typedef uint32_t mb_address_t; 69 | 70 | /** 71 | * Return a Modbus address using the given \a table and \a 72 | * address. The allowed Modbus tables are: 73 | * 74 | * 75 | * 76 | * 77 | * 78 | * 79 | *
\b 0 Coils
\b 1 Inputs
\b 3 Input registers
\b 4 Holding registers
80 | * 81 | * The enumeration mb_table_t may also be used. 82 | * The following two calls are equivalent: 83 | * \code 84 | * mb_address_t address1 = MB_ADDRESS (3, 30001); 85 | * mb_address_t address2 = MB_ADDRESS (MB_TABLE_INPUT_REGISTERS, 30001); 86 | * \endcode 87 | * 88 | * \param table Table. Valid values: see above or \a mb_table_t 89 | * \param address Address. Valid range: 1 - 65536 90 | * \return Modbus address 91 | */ 92 | #define MB_ADDRESS(table, address) ((table) << 16 | ((address) & 0xFFFF)) 93 | 94 | /** 95 | * Master configuration 96 | */ 97 | typedef struct mbus_cfg 98 | { 99 | uint32_t timeout; /**< Receive-timeout in milliseconds. 100 | * A slave failing to respond in time will cause 101 | * master to abort its request and return an ETIMEOUT 102 | * error to user. 103 | */ 104 | } mbus_cfg_t; 105 | 106 | /** 107 | * Create an instance of the Modbus master stack 108 | * 109 | * This function allocates memory needed by the instance, followed by 110 | * a call to mbus_init(), which initialises it. 111 | * A CC_ASSERT() is triggered upon memory allocation failure. 112 | * User may use the returned handle in further operations. 113 | * 114 | * In the following example, an instance is created with TCP as the 115 | * transport data layer: 116 | * \code 117 | * mb_transport_t * transport; 118 | * mbus_t * mbus; 119 | * static const mb_tcp_cfg_t transport_cfg = 120 | * { 121 | * .port = 502, 122 | * }; 123 | * static const mbus_cfg_t master_cfg = 124 | * { 125 | * .timeout = 1000, 126 | * }; 127 | * 128 | * transport = mb_tcp_init (&transport_cfg); 129 | * mbus = mbus_create (&master_cfg, transport); 130 | * \endcode 131 | * 132 | * \param cfg Modbus configuration 133 | * \param transport Modbus transport data layer. 134 | * Must be initialised. See mb_rtu_init() and 135 | * mb_tcp_init(). 136 | * The stack assumes ownership of this object. 137 | * 138 | * \return Modbus handle to be used in further operations 139 | */ 140 | MB_EXPORT mbus_t * mbus_create ( 141 | const mbus_cfg_t * cfg, 142 | mb_transport_t * transport); 143 | 144 | /** 145 | * Initialise an instance of the Modbus master stack 146 | * 147 | * This is an alternative to mbus_create() when use of dynamic memory allocation 148 | * is not desired. 149 | * 150 | * \param mbus Modbus handle 151 | * \param cfg Modbus configuration 152 | * \param transport Modbus transport data layer. 153 | * Must be initialised. See mb_rtu_init() and 154 | * mb_tcp_init(). 155 | * The stack assumes ownership of this object. 156 | * \param scratch Scratch data array (MAX_PDU_SIZE bytes). 157 | * The stack assumes ownership of this object. 158 | */ 159 | MB_EXPORT void mbus_init ( 160 | mbus_t * mbus, 161 | const mbus_cfg_t * cfg, 162 | mb_transport_t * transport, 163 | uint8_t * scratch); 164 | 165 | /** 166 | * Return a handle to the transport data layer. 167 | * 168 | * \param mbus Modbus handle 169 | * 170 | * \return Handle to transport data layer 171 | */ 172 | MB_EXPORT void * mbus_transport_get (mbus_t * mbus); 173 | 174 | /** 175 | * Connect to a Modbus slave 176 | * 177 | * The following example connects to a slave over TCP/IP: 178 | * \code 179 | * int slave = mbus_connect (mbus, "192.168.10.134"); 180 | * \endcode 181 | * 182 | * \param mbus Modbus handle 183 | * \param name Slave identifier 184 | * 185 | * \return Slave handle on success, error code otherwise 186 | */ 187 | MB_EXPORT int mbus_connect (mbus_t * mbus, const char * name); 188 | 189 | /** 190 | * Disconnect from a Modbus slave 191 | * 192 | * \param mbus Modbus handle 193 | * \param slave Slave handle 194 | * 195 | * \return 0 always 196 | */ 197 | MB_EXPORT int mbus_disconnect (mbus_t * mbus, int slave); 198 | 199 | /** 200 | * Read Modbus addresses 201 | * 202 | * This function reads a number of Modbus address contents starting 203 | * from the given address. The function will read coils, inputs, 204 | * holding registers or input registers as specified by the given 205 | * starting address. 206 | * 207 | * Note that addresses are 1-based. 0 is thus an invalid address. 208 | * 209 | * For holding or input register reads, the returned buffer contents 210 | * will be in the correct byte format. Register contents are 211 | * byte-swapped from network byte order, if required. 212 | * The following example would read 10 input registers from address 30001 to 213 | * 30010: 214 | * \code 215 | * mb_address_t address = MB_ADDRESS (MB_TABLE_INPUT_REGISTERS, 30001); 216 | * uint16_t buffer[10]; 217 | * result = mbus_read (mbus, slave, address, 10, buffer); 218 | * \endcode 219 | * 220 | * For coil and input status reads, the returned buffer contents will 221 | * be a bit string where the LSB is the bit that was read from the 222 | * starting address, and the MSB is the bit that was read from the 223 | * final address. 224 | * The following example would read 10 inputs from address 10001 to 10010. 225 | * Note that 10 bits fit in two bytes: 226 | * \code 227 | * mb_address_t address = MB_ADDRESS (MB_TABLE_INPUTS, 10001); 228 | * uint8_t buffer[2]; 229 | * result = mbus_read (mbus, slave, address, 10, buffer); 230 | * \endcode 231 | * 232 | * \param mbus Modbus handle 233 | * \param slave Slave handle. See mbus_connect() 234 | * \param address 1-based starting address. May be constructed 235 | * by calling MB_ADDRESS() 236 | * \param quantity Number of addresses to read. Valid range is 1 - 2000 237 | * for coils/inputs and 1 - 125 for registers 238 | * \param buffer Output buffer. Must be large enough to hold the 239 | * requested data 240 | * 241 | * \return 0 on success, error code otherwise 242 | */ 243 | MB_EXPORT int mbus_read ( 244 | mbus_t * mbus, 245 | int slave, 246 | mb_address_t address, 247 | uint16_t quantity, 248 | void * buffer); 249 | 250 | /** 251 | * Write Modbus addresses 252 | * 253 | * This function writes a number of Modbus addresses starting from the 254 | * given address. The function will write coils or holding registers 255 | * as specified by the given starting address. 256 | * 257 | * Note that addresses are 1-based. 0 is thus an invalid address. 258 | * 259 | * For holding register writes, the \a buffer shall be a number of 260 | * 16-bit register values in the CPU byte ordering. Register values 261 | * are byte-swapped to network byte ordering before transmission, if 262 | * required. 263 | * The following example would write 3 holding registers from address 40001 264 | * to 40003: 265 | * \code 266 | * mb_address_t address = MB_ADDRESS (MB_TABLE_HOLDING_REGISTERS, 40001); 267 | * uint16_t buffer[3] = {0x1234, 0x5678, 0x90ab}; 268 | * result = mbus_write (mbus, slave, address, 3, buffer); 269 | * \endcode 270 | * 271 | * For coil writes, the \a buffer shall be a bit string where the 272 | * LSB is the bit that will be written to the starting address, and 273 | * the MSB is the bit that will be written to the final address. 274 | * The following example would write 100 coils from address 23 to 122. 275 | * Coils 23 to 30 are set to 1 while coils 31 to 122 are set to 0: 276 | * \code 277 | * mb_address_t address = MB_ADDRESS (MB_TABLE_COILS, 23); 278 | * uint8_t buffer[13] = {0xff,0,0,0,0,0,0,0,0,0,0,0,0}; 279 | * result = mbus_write (mbus, slave, address, 100, buffer); 280 | * \endcode 281 | * 282 | * \param mbus Modbus handle 283 | * \param slave Slave handle. See mbus_connect() 284 | * \param address 1-based starting address. May be constructed 285 | * by calling MB_ADDRESS(). Note that only coils and 286 | * holding registers are writable. 287 | * \param quantity Number of addresses to write. Valid range is 1 - 1968 288 | * for coils and 1 - 123 for holding registers 289 | * \param buffer Input buffer 290 | * 291 | * \return 0 on success, error code otherwise 292 | */ 293 | MB_EXPORT int mbus_write ( 294 | mbus_t * mbus, 295 | int slave, 296 | mb_address_t address, 297 | uint16_t quantity, 298 | const void * buffer); 299 | 300 | /** 301 | * Write a single Modbus address 302 | * 303 | * This function writes a single Modbus address. The function will 304 | * write a single coil or holding register as specified by the given 305 | * address. 306 | * 307 | * Note that addresses are 1-based. 0 is thus an invalid address. 308 | * 309 | * For holding register writes, the value shall be in the CPU byte 310 | * ordering. The value is byte-swapped to network byte ordering before 311 | * transmission, if required 312 | * The following example would set the holding register at address 40001 313 | * to the value 0x1234: 314 | * \code 315 | * mb_address_t address = MB_ADDRESS (MB_TABLE_HOLDING_REGISTERS, 40001); 316 | * result = mbus_write_single (mbus, slave, address, 0x1234); 317 | * \endcode 318 | * 319 | * For coil writes, the coil shall be set if value is non-zero, or 320 | * cleared if the value is zero. 321 | * The following example would set the coil at address 23: 322 | * \code 323 | * mb_address_t address = MB_ADDRESS (MB_TABLE_COILS, 23); 324 | * result = mbus_write_single (mbus, slave, address, 1); 325 | * \endcode 326 | * 327 | * \param mbus Modbus handle 328 | * \param slave Slave handle. See mbus_connect() 329 | * \param address 1-based address to be written to. May be constructed 330 | * by calling MB_ADDRESS(). Note that only coils and 331 | * holding registers are writable. 332 | * \param value The value to be written 333 | * 334 | * \return 0 on success, error code otherwise 335 | */ 336 | MB_EXPORT int mbus_write_single ( 337 | mbus_t * mbus, 338 | int slave, 339 | mb_address_t address, 340 | uint16_t value); 341 | 342 | /** 343 | * Diagnostic loopback 344 | * 345 | * This function issues the diagnostic loopback function. The \a buffer 346 | * contents are sent to the slave. The data read from the slave is placed 347 | * into the \a buffer (up to a maximum of \a size). 348 | * 349 | * The following example sends four bytes to \a slave and then receives up to 350 | * four bytes back: 351 | * \code 352 | * uint8_t buffer[4] = {0x11, 0x22, 0x33, 0x44}; 353 | * size = mbus_loopback (mbus, slave, sizeof (buffer), buffer); 354 | * \endcode 355 | * 356 | * \param mbus Modbus handle 357 | * \param slave Slave handle. See mbus_connect() 358 | * \param size The number of bytes of data to send 359 | * \param buffer Input/output buffer 360 | * 361 | * \return Number of bytes received on success, error code otherwise 362 | */ 363 | MB_EXPORT int mbus_loopback ( 364 | mbus_t * mbus, 365 | int slave, 366 | uint16_t size, 367 | void * buffer); 368 | 369 | /** 370 | * Send raw message 371 | * 372 | * This function sends the message in \a msg to the Modbus slave. The 373 | * message contents are not interpreted by the stack. 374 | * 375 | * The following example would send one byte to the \a slave: 376 | * \code 377 | * uint8_t msg[1] = {101}; 378 | * result = mbus_send_msg (mbus, slave, msg, sizeof (msg)); 379 | * \endcode 380 | * 381 | * \param mbus Modbus handle 382 | * \param slave Slave handle. See mbus_connect() 383 | * \param msg Message to send 384 | * \param size Size of message 385 | * 386 | * \return 0 on success, error code otherwise 387 | */ 388 | MB_EXPORT int mbus_send_msg ( 389 | mbus_t * mbus, 390 | int slave, 391 | const void * msg, 392 | uint8_t size); 393 | 394 | /** 395 | * Get raw message 396 | * 397 | * This function returns the next message received from the slave. The 398 | * message contents are not interpreted by the stack. 399 | * 400 | * The caller must provide a \a buffer large enough to hold the requested 401 | * data. 402 | * 403 | * The following example would receive up to 253 bytes from \a slave: 404 | * \code 405 | * int size; 406 | * uint8_t msg[253]; 407 | * size = mbus_get_msg (mbus, slave, msg, sizeof (msg)); 408 | * \endcode 409 | * 410 | * \param mbus Modbus handle 411 | * \param slave Slave handle. See mbus_connect() 412 | * \param msg Message received 413 | * \param size Max size of message. If message size is not known in 414 | * advance, use a buffer of size 253 bytes in order to 415 | * prevent buffer overflow. 416 | * 417 | * \return Number of bytes received on success, error code otherwise 418 | */ 419 | MB_EXPORT int mbus_get_msg (mbus_t * mbus, int slave, void * msg, uint16_t size); 420 | 421 | #ifdef __cplusplus 422 | } 423 | #endif 424 | 425 | #endif /* MBUS_H */ 426 | 427 | -------------------------------------------------------------------------------- /options.h.in: -------------------------------------------------------------------------------- 1 | #ifndef OPTIONS_H 2 | #define OPTIONS_H 3 | 4 | #define OPTIONS_H 5 | 6 | #cmakedefine LOG_ENABLE 7 | 8 | #ifndef LOG_LEVEL 9 | #define LOG_LEVEL (LOG_LEVEL_@LOG_LEVEL@) 10 | #endif 11 | 12 | #ifndef MB_RTU_LOG 13 | #define MB_RTU_LOG (LOG_STATE_@MB_RTU_LOG@) 14 | #endif 15 | 16 | #ifndef MB_TCP_LOG 17 | #define MB_TCP_LOG (LOG_STATE_@MB_TCP_LOG@) 18 | #endif 19 | 20 | #endif /* OPTIONS_H */ 21 | -------------------------------------------------------------------------------- /sample/ports/linux/mb_bsp.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2020 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mb_bsp.h" 17 | #include "osal.h" 18 | 19 | static os_timer_t * tmr1p5; 20 | static void (*t1p5_callback) (void * arg); 21 | 22 | static os_timer_t * tmr3p5; 23 | static void (*t3p5_callback) (void * arg); 24 | 25 | static void * tmr_arg; 26 | 27 | static void tmr1p5_expired (os_timer_t * tmr, void * arg) 28 | { 29 | if (t1p5_callback) 30 | t1p5_callback (tmr_arg); 31 | } 32 | 33 | static void tmr3p5_expired (os_timer_t * tmr, void * arg) 34 | { 35 | if (t3p5_callback) 36 | t3p5_callback (tmr_arg); 37 | } 38 | 39 | static void mb_tx_enable (int level) 40 | { 41 | /* This function controls the transceiver enabled state, if 42 | possible */ 43 | } 44 | 45 | static void mb_tmr_init (uint32_t t1p5, uint32_t t3p5) 46 | { 47 | os_timer_set (tmr1p5, t1p5); 48 | os_timer_set (tmr3p5, t3p5); 49 | } 50 | 51 | static void mb_tmr_start ( 52 | void (*t1p5_expired) (void * arg), 53 | void (*t3p5_expired) (void * arg), 54 | void * arg) 55 | { 56 | tmr_arg = arg; 57 | 58 | if (t1p5_expired) 59 | { 60 | t1p5_callback = t1p5_expired; 61 | os_timer_start (tmr1p5); 62 | } 63 | 64 | if (t3p5_expired) 65 | { 66 | t3p5_callback = t3p5_expired; 67 | os_timer_start (tmr3p5); 68 | } 69 | } 70 | 71 | mb_transport_t * mb_rtu_create ( 72 | const char * device, 73 | mb_rtu_serial_cfg_t * serial_cfg) 74 | { 75 | mb_transport_t * rtu; 76 | mb_rtu_cfg_t rtu_cfg; 77 | 78 | rtu_cfg.serial = device; 79 | rtu_cfg.serial_cfg = serial_cfg; 80 | rtu_cfg.tx_enable = mb_tx_enable; 81 | rtu_cfg.tmr_init = mb_tmr_init; 82 | rtu_cfg.tmr_start = mb_tmr_start; 83 | 84 | tmr1p5 = os_timer_create (0, tmr1p5_expired, NULL, true); 85 | tmr3p5 = os_timer_create (0, tmr3p5_expired, NULL, true); 86 | 87 | rtu = mb_rtu_init (&rtu_cfg); 88 | return rtu; 89 | } 90 | -------------------------------------------------------------------------------- /sample/ports/linux/mb_bsp.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2020 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #ifndef MB_BSP_H 17 | #define MB_BSP_H 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | #include "mb_transport.h" 24 | #include "mb_rtu.h" 25 | 26 | mb_transport_t * mb_rtu_create ( 27 | const char * device, 28 | mb_rtu_serial_cfg_t * serial_cfg); 29 | 30 | #ifdef __cplusplus 31 | } 32 | #endif 33 | 34 | #endif /* MB_BSP_H */ 35 | -------------------------------------------------------------------------------- /sample/ports/linux/rtu_slave.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2015 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "slave.h" 17 | #include "mb_bsp.h" 18 | #include "osal.h" 19 | 20 | int main (void) 21 | { 22 | mb_slave_t * slave; 23 | mb_transport_t * rtu; 24 | mb_rtu_serial_cfg_t serial_cfg; 25 | 26 | serial_cfg.baudrate = 115200; 27 | serial_cfg.parity = NONE; 28 | 29 | rtu = mb_rtu_create ("/dev/ttyAMA0", &serial_cfg); 30 | slave = mb_slave_init (&mb_slave_cfg, rtu); 31 | os_usleep (20 * 1000 * 1000); 32 | mb_slave_shutdown (slave); 33 | } 34 | -------------------------------------------------------------------------------- /sample/ports/linux/tcp_rtu_master.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2015 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mbus.h" 17 | #include "mb_tcp.h" 18 | #include "mb_rtu.h" 19 | #include "mb_bsp.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | static struct opt 29 | { 30 | int repeat; 31 | int delay; 32 | bool read; 33 | bool write; 34 | bool tcp; 35 | bool rtu; 36 | struct 37 | { 38 | char ip[20]; 39 | mb_tcp_cfg_t cfg; 40 | } tcp_details; 41 | struct 42 | { 43 | char device[80]; 44 | char unit[4]; 45 | mb_rtu_serial_cfg_t cfg; 46 | } rtu_details; 47 | int table; 48 | uint32_t address; 49 | uint32_t value; 50 | } opt; 51 | 52 | static void help (const char * name) 53 | { 54 | printf ( 55 | "Read/write Modbus registers\n" 56 | "\n" 57 | "This purpose of this program is to demonstrate use of the M-Bus\n" 58 | "Modbus master stack. It may also be useful as a debugging tool.\n" 59 | "\n" 60 | "USAGE:\n" 61 | "%s [OPTIONS] cmd transport connection table address [value]\n" 62 | "\n" 63 | "cmd {read, write} Type of transaction\n" 64 | "transport {tcp, rtu} Type of transport\n" 65 | "connection {IP:PORT, DEVICE:BAUDRATE:PARITY:UNIT}\n" 66 | " Connection details\n" 67 | "table {coil, input, reg, hold} Modbus address table\n" 68 | "address Modbus address\n" 69 | "\n" 70 | "TCP CONNECTION DETAILS\n" 71 | "IP IP-address, e.g. 192.168.0.1\n" 72 | "PORT Modbus port, e.g. 502\n" 73 | "\n" 74 | "RTU CONNECTION DETAILS\n" 75 | "DEVICE Serial device, e.g. /dev/ttyUSB0\n" 76 | "BAUDRATE Modbus baudrate\n" 77 | "PARITY Modbus parity {odd, even, none}\n" 78 | "\n" 79 | "OPTIONS:\n" 80 | " --repeat n Repeat command n times\n" 81 | " --delay ms Time to delay between Modbus transactions\n" 82 | "\n" 83 | "EXAMPLES:\n" 84 | "\n" 85 | "Repeatedly read coil 1 of Modbus TCP slave 192.168.10.134, port 8502.\n" 86 | "\n" 87 | "%s --repeat 100 read tcp 192.168.10.134:8502 coil 0x0001\n" 88 | "\n" 89 | "Write 42 to holding register 3 of Modbus RTU slave 2, accessed\n" 90 | "through /dev/ttyUSB0 with baudrate 115200, no parity.\n" 91 | "\n" 92 | "%s write rtu /dev/ttyUSB0:115200:none:2 hold 0x0003 42\n" 93 | "\n", 94 | name, 95 | name, 96 | name); 97 | exit (EXIT_SUCCESS); 98 | } 99 | 100 | static void fail (const char * name, const char * reason) 101 | { 102 | if (reason != NULL) 103 | { 104 | printf ("%s. ", reason); 105 | } 106 | printf ("Try %s --help\n", name); 107 | exit (EXIT_FAILURE); 108 | } 109 | 110 | static bool parse_tcp_details (char * s) 111 | { 112 | char * p; 113 | char * saveptr; 114 | 115 | /* Get ip address */ 116 | 117 | p = strtok_r (s, ":", &saveptr); 118 | if (p == NULL) 119 | return false; 120 | 121 | strncpy (opt.tcp_details.ip, p, sizeof (opt.tcp_details.ip)); 122 | opt.tcp_details.ip[sizeof (opt.tcp_details.ip) - 1] = 0; 123 | 124 | /* Get ip port */ 125 | 126 | p = strtok_r (NULL, ":", &saveptr); 127 | if (p == NULL) 128 | return false; 129 | 130 | opt.tcp_details.cfg.port = strtoul (p, NULL, 0); 131 | 132 | /* Check for extra unknown options */ 133 | 134 | p = strtok_r (NULL, ":", &saveptr); 135 | if (p != NULL) 136 | return false; 137 | 138 | return true; 139 | } 140 | 141 | static bool parse_rtu_details (char * s) 142 | { 143 | char * p; 144 | char * saveptr; 145 | 146 | /* Get device */ 147 | 148 | p = strtok_r (s, ":", &saveptr); 149 | if (p == NULL) 150 | return false; 151 | 152 | strncpy (opt.rtu_details.device, p, sizeof (opt.rtu_details.device)); 153 | opt.rtu_details.device[sizeof (opt.rtu_details.device) - 1] = 0; 154 | 155 | /* Get baudrate */ 156 | 157 | p = strtok_r (NULL, ":", &saveptr); 158 | if (p == NULL) 159 | return false; 160 | 161 | opt.rtu_details.cfg.baudrate = strtoul (p, NULL, 0); 162 | 163 | /* Get parity */ 164 | 165 | p = strtok_r (NULL, ":", &saveptr); 166 | if (p == NULL) 167 | return false; 168 | 169 | if (strcmp (p, "odd") == 0) 170 | opt.rtu_details.cfg.parity = ODD; 171 | else if (strcmp (p, "even") == 0) 172 | opt.rtu_details.cfg.parity = EVEN; 173 | else if (strcmp (p, "none") == 0) 174 | opt.rtu_details.cfg.parity = NONE; 175 | else 176 | return false; 177 | 178 | /* Get unit */ 179 | 180 | p = strtok_r (NULL, ":", &saveptr); 181 | if (p == NULL) 182 | return false; 183 | 184 | strncpy (opt.rtu_details.unit, p, sizeof (opt.rtu_details.unit)); 185 | opt.rtu_details.unit[sizeof (opt.rtu_details.unit) - 1] = 0; 186 | 187 | /* Check for extra unknown options */ 188 | 189 | p = strtok_r (NULL, ":", &saveptr); 190 | if (p != NULL) 191 | return false; 192 | 193 | return true; 194 | } 195 | 196 | static void parse_opt (int argc, char * argv[]) 197 | { 198 | static struct option options[] = { 199 | {"help", no_argument, 0, 0}, 200 | {"repeat", required_argument, 0, 0}, 201 | {"delay", required_argument, 0, 0}, 202 | {0, 0, 0, 0}}; 203 | 204 | /* Set defaults */ 205 | opt.repeat = 1; 206 | 207 | while (1) 208 | { 209 | int c; 210 | int option_index = 0; 211 | 212 | c = getopt_long (argc, argv, "h", options, &option_index); 213 | if (c == -1) 214 | break; 215 | 216 | switch (c) 217 | { 218 | case 0: 219 | switch (option_index) 220 | { 221 | case 0: 222 | help (argv[0]); 223 | break; 224 | case 1: 225 | opt.repeat = strtoul (optarg, NULL, 0); 226 | break; 227 | case 2: 228 | opt.delay = strtoul (optarg, NULL, 0); 229 | break; 230 | default: 231 | exit (EXIT_FAILURE); 232 | } 233 | break; 234 | 235 | case 'h': 236 | help (argv[0]); 237 | break; 238 | 239 | case '?': 240 | fail (argv[0], NULL); 241 | break; 242 | 243 | default: 244 | exit (EXIT_FAILURE); 245 | } 246 | } 247 | 248 | /* Get command */ 249 | 250 | if (optind == argc) 251 | fail (argv[0], "Nothing to do"); 252 | 253 | opt.read = strcmp (argv[optind], "read") == 0; 254 | opt.write = strcmp (argv[optind], "write") == 0; 255 | 256 | /* Get transport type */ 257 | 258 | if (++optind == argc) 259 | fail (argv[0], "No transport"); 260 | 261 | opt.tcp = strcmp (argv[optind], "tcp") == 0; 262 | opt.rtu = strcmp (argv[optind], "rtu") == 0; 263 | 264 | /* Get transport details */ 265 | 266 | if (++optind == argc) 267 | fail (argv[0], "No transport details"); 268 | 269 | if (opt.tcp) 270 | { 271 | if (!parse_tcp_details (argv[optind])) 272 | fail (argv[0], "Bad tcp details"); 273 | } 274 | else if (opt.rtu) 275 | { 276 | if (!parse_rtu_details (argv[optind])) 277 | fail (argv[0], "Bad rtu details"); 278 | } 279 | 280 | /* Get address table */ 281 | 282 | if (++optind == argc) 283 | fail (argv[0], "No address table"); 284 | 285 | if (strcmp (argv[optind], "coil") == 0) 286 | opt.table = 0; 287 | else if (strcmp (argv[optind], "input") == 0) 288 | opt.table = 1; 289 | else if (strcmp (argv[optind], "reg") == 0) 290 | opt.table = 3; 291 | else if (strcmp (argv[optind], "hold") == 0) 292 | opt.table = 4; 293 | else 294 | fail (argv[0], "Bad address table"); 295 | 296 | /* Get register address */ 297 | 298 | if (++optind == argc) 299 | fail (argv[0], "No address"); 300 | 301 | opt.address = strtoul (argv[optind], NULL, 0); 302 | if (opt.address > 0x10000) 303 | fail (argv[0], "Bad address"); 304 | 305 | if (opt.write) 306 | { 307 | /* Get value */ 308 | if (++optind == argc) 309 | fail (argv[0], "No value"); 310 | 311 | opt.value = strtoul (argv[optind], NULL, 0); 312 | if (opt.value > 0xFFFF) 313 | fail (argv[0], "Bad value"); 314 | } 315 | 316 | /* Check for extra unknown options */ 317 | 318 | if (++optind != argc) 319 | fail (argv[0], "Too many options"); 320 | } 321 | 322 | int main (int argc, char * argv[]) 323 | { 324 | static mbus_cfg_t mb_master_cfg = { 325 | .timeout = 1000, 326 | }; 327 | mbus_t * mbus; 328 | int slave; 329 | 330 | parse_opt (argc, argv); 331 | 332 | if (opt.tcp) 333 | { 334 | mb_transport_t * tcp; 335 | 336 | tcp = mb_tcp_init (&opt.tcp_details.cfg); 337 | mbus = mbus_create (&mb_master_cfg, tcp); 338 | 339 | slave = mbus_connect (mbus, opt.tcp_details.ip); 340 | } 341 | else if (opt.rtu) 342 | { 343 | mb_transport_t * rtu; 344 | 345 | rtu = mb_rtu_create (opt.rtu_details.device, &opt.rtu_details.cfg); 346 | mbus = mbus_create (&mb_master_cfg, rtu); 347 | 348 | slave = mbus_connect (mbus, opt.rtu_details.unit); 349 | } 350 | else 351 | { 352 | fail (argv[0], "Unknown transport"); 353 | } 354 | 355 | for (int i = 0; i < opt.repeat; i++) 356 | { 357 | uint16_t value = 0; 358 | int error = 0; 359 | mb_address_t address = MB_ADDRESS (opt.table, opt.address); 360 | 361 | if (opt.read) 362 | { 363 | error = mbus_read (mbus, slave, address, 1, &value); 364 | } 365 | else if (opt.write) 366 | { 367 | error = mbus_write_single (mbus, slave, address, opt.value); 368 | } 369 | else 370 | { 371 | fail (argv[0], "Unknown command"); 372 | } 373 | 374 | if (error) 375 | printf ("Modbus function failed (%s).\n", mb_error_literal (error)); 376 | else if (opt.read) 377 | printf ("0x%04x\n", value); 378 | 379 | usleep (opt.delay * 1000); 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /sample/ports/rt-kernel/mb_bsp.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2020 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mb_rtu.h" 17 | #include 18 | #include 19 | 20 | static void mb_tx_enable (int level) 21 | { 22 | /* This function controls the transceiver enabled state, if 23 | possible */ 24 | } 25 | 26 | static void mb_tmr_init (uint32_t t1p5_us, uint32_t t3p5_us) 27 | { 28 | /* This function enables the T1P5 and T3P5 timers (if required). */ 29 | } 30 | 31 | static void mb_tmr_start ( 32 | void (*t1p5_expired) (void * arg), 33 | void (*t3p5_expired) (void * arg), 34 | void * arg) 35 | { 36 | /* This function starts the T1P5 and T3P5 timers, and arranges for 37 | the t1p5_expired and t3p5_expired functions to be called when 38 | the timers expire. This could also be managed by hardware 39 | directly if suitable hardware support exists. */ 40 | } 41 | 42 | mb_transport_t * mb_rtu_create (void) 43 | { 44 | mb_transport_t * rtu; 45 | static const mb_rtu_serial_cfg_t serial_cfg = { 46 | .baudrate = 115200, 47 | .parity = NONE, 48 | }; 49 | static const mb_rtu_cfg_t mb_rtu_cfg = { 50 | .serial = "/sio1", 51 | .serial_cfg = &serial_cfg, 52 | .tx_enable = mb_tx_enable, 53 | .tmr_init = mb_tmr_init, 54 | .tmr_start = mb_tmr_start, 55 | }; 56 | 57 | rtu = mb_rtu_init (&mb_rtu_cfg); 58 | return rtu; 59 | } 60 | -------------------------------------------------------------------------------- /sample/ports/rt-kernel/mb_bsp.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2020 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #ifndef MB_BSP_H 17 | #define MB_BSP_H 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | mb_transport_t * mb_rtu_create (void); 24 | 25 | #ifdef __cplusplus 26 | } 27 | #endif 28 | 29 | #endif /* MB_BSP_H */ 30 | -------------------------------------------------------------------------------- /sample/ports/rt-kernel/mb_cmds.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2012 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include 17 | #include 18 | 19 | #include "mb_master.h" 20 | 21 | static int _cmd_mb_read (int argc, char * argv[]) 22 | { 23 | uint32_t reg; 24 | uint16_t value = 0; 25 | int fd; 26 | int result; 27 | 28 | if (argc != 3) 29 | { 30 | shell_usage (argv[0], "wrong number of arguments"); 31 | return -1; 32 | } 33 | 34 | reg = strtoul (argv[2], NULL, 0); 35 | if (reg > 0x4FFFF) 36 | { 37 | printf ("Bad register\n"); 38 | return -1; 39 | } 40 | 41 | fd = mb_open (argv[1]); 42 | if (fd < 0) 43 | { 44 | printf ("Failed to open %s\n", argv[1]); 45 | return -1; 46 | } 47 | 48 | result = mb_read (fd, reg, 1, &value); 49 | if (result != 0) 50 | { 51 | printf ("Failed to read value (result %s)\n", mb_error_literal (result)); 52 | mb_close (fd); 53 | return -1; 54 | } 55 | 56 | printf ("0x%04x\n", value); 57 | 58 | mb_close (fd); 59 | return 0; 60 | } 61 | 62 | static int _cmd_mb_write (int argc, char * argv[]) 63 | { 64 | uint32_t reg; 65 | uint16_t value; 66 | int fd; 67 | int result; 68 | 69 | if (argc != 4) 70 | { 71 | shell_usage (argv[0], "wrong number of arguments"); 72 | return -1; 73 | } 74 | 75 | reg = strtoul (argv[2], NULL, 0); 76 | if (reg > 0x4FFFF) 77 | { 78 | printf ("Bad register\n"); 79 | return -1; 80 | } 81 | 82 | value = strtoul (argv[3], NULL, 0) & 0xFFFF; 83 | 84 | fd = mb_open (argv[1]); 85 | if (fd < 0) 86 | { 87 | printf ("Failed to open %s\n", argv[1]); 88 | return -1; 89 | } 90 | 91 | result = mb_write (fd, reg, 1, &value); 92 | if (result != 0) 93 | { 94 | printf ("Failed to write value (result %s)\n", mb_error_literal (result)); 95 | mb_close (fd); 96 | return -1; 97 | } 98 | 99 | mb_close (fd); 100 | return 0; 101 | } 102 | 103 | const shell_cmd_t cmd_mb_read = { 104 | .cmd = _cmd_mb_read, 105 | .name = "mb_read", 106 | .help_short = "modbus read", 107 | .help_long = 108 | 109 | "mb_read name reg\n" 110 | "\n" 111 | "Read from modbus slave. The modbus slave is specified through " 112 | "the\n" 113 | "name, e.g:\n" 114 | "\n" 115 | "/modbus0/15 (RTU slave 15)\n" 116 | "/modbus1/192.168.10.101 (TCP slave 192.168.10.101)\n" 117 | "\n" 118 | "The register is specified using the format:\n" 119 | "\n" 120 | " 0xTRRRR\n" 121 | "\n" 122 | "where T is:\n" 123 | "\n" 124 | " 0 Coils\n" 125 | " 1 Inputs\n" 126 | " 3 Input registers\n" 127 | " 4 Holding registers\n" 128 | "\n" 129 | "and RRRR is the register address, starting at 1. Example:\n" 130 | "\n" 131 | "> mb_read /modbus0/192.168.10.101 0x40001\n" 132 | "(read holding register 1 in modbus TCP slave 192.168.10.101 " 133 | "on bus\n" 134 | "modbus0).\n"}; 135 | 136 | const shell_cmd_t cmd_mb_write = { 137 | .cmd = _cmd_mb_write, 138 | .name = "mb_write", 139 | .help_short = "modbus write", 140 | .help_long = 141 | 142 | "mb_write name reg value\n" 143 | "\n" 144 | "Write to modbus slave. The modbus slave is specified through " 145 | "the\n" 146 | "name, e.g:\n" 147 | "\n" 148 | "/modbus0/15 (RTU slave 15)\n" 149 | "/modbus1/192.168.10.101 (TCP slave 192.168.10.101)\n" 150 | "\n" 151 | "The register is specified using the format:\n" 152 | "\n" 153 | " 0xTRRRR\n" 154 | "\n" 155 | "where T is:\n" 156 | "\n" 157 | " 0 Coils\n" 158 | " 1 Inputs\n" 159 | " 3 Input registers\n" 160 | " 4 Holding registers\n" 161 | "\n" 162 | "and RRRR is the register address, starting at 1. Example:\n" 163 | "\n" 164 | "> mb_write /modbus0/192.168.10.101 0x40001 0x1234\n" 165 | "(write holding register 1 in modbus TCP slave 192.168.10.101 " 166 | "on bus\n" 167 | "modbus0).\n"}; 168 | -------------------------------------------------------------------------------- /sample/ports/rt-kernel/mb_stm32p407.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2020 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mb_rtu.h" 17 | #include "osal.h" 18 | #include 19 | #include 20 | 21 | #define TIMER_T1P5 2 /* STM32 timer to use as t1p5 */ 22 | #define TIMER_T3P5 5 /* STM32 timer to use as t3p5 */ 23 | 24 | static uint32_t t1p5_match = 0; 25 | static uint32_t t3p5_match = 0; 26 | 27 | static void (*t1p5_callback) (void * arg); 28 | static void (*t3p5_callback) (void * arg); 29 | 30 | static void t1p5_fn (void * arg) 31 | { 32 | timer_stop (TIMER_T1P5); 33 | t1p5_callback (arg); 34 | } 35 | 36 | static void t3p5_fn (void * arg) 37 | { 38 | timer_stop (TIMER_T3P5); 39 | t3p5_callback (arg); 40 | } 41 | 42 | static void mb_tx_enable (int level) 43 | { 44 | /* This function controls the transceiver enabled state, if 45 | possible */ 46 | #if 0 47 | gpio_set (GPIO_TX_EN, level); 48 | #endif 49 | } 50 | 51 | static uint32_t mb_tmr_compute (uint32_t us) 52 | { 53 | uint32_t f = 2 * CFG_PCLK1_FREQUENCY; /* Timer frequency */ 54 | uint32_t m = us * (f / (1000 * 1000)); /* Match value */ 55 | return m; 56 | } 57 | 58 | static void mb_tmr_init (uint32_t t1p5_us, uint32_t t3p5_us) 59 | { 60 | t1p5_match = mb_tmr_compute (t1p5_us); 61 | t3p5_match = mb_tmr_compute (t3p5_us); 62 | } 63 | 64 | static void mb_tmr_start ( 65 | void (*t1p5_expired) (void * arg), 66 | void (*t3p5_expired) (void * arg), 67 | void * arg) 68 | { 69 | timer_stop (TIMER_T1P5); 70 | timer_stop (TIMER_T3P5); 71 | 72 | if (t1p5_expired) 73 | { 74 | t1p5_callback = t1p5_expired; 75 | timer_init (TIMER_T1P5, t1p5_fn, arg); 76 | timer_start (TIMER_T1P5, 0, t1p5_match); 77 | } 78 | 79 | if (t3p5_expired) 80 | { 81 | t3p5_callback = t3p5_expired; 82 | timer_init (TIMER_T3P5, t3p5_fn, arg); 83 | timer_start (TIMER_T3P5, 0, t3p5_match); 84 | } 85 | } 86 | 87 | mb_transport_t * mb_rtu_create (void) 88 | { 89 | mb_transport_t * rtu; 90 | static const mb_rtu_serial_cfg_t serial_cfg = { 91 | .baudrate = 115200, 92 | .parity = NONE, 93 | }; 94 | static const mb_rtu_cfg_t mb_rtu_cfg = { 95 | .serial = "/sio1", 96 | .serial_cfg = &serial_cfg, 97 | .tx_enable = mb_tx_enable, 98 | .tmr_init = mb_tmr_init, 99 | .tmr_start = mb_tmr_start, 100 | }; 101 | 102 | RCC_APB1ENR |= APB1_TIM5 | APB1_TIM4; 103 | 104 | rtu = mb_rtu_init (&mb_rtu_cfg); 105 | return rtu; 106 | } 107 | -------------------------------------------------------------------------------- /sample/ports/rt-kernel/rtu_master.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2015 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mb_master.h" 17 | #include "mb_bsp.h" 18 | 19 | SHELL_CMD (cmd_mb_read); 20 | SHELL_CMD (cmd_mb_write); 21 | 22 | int main (void) 23 | { 24 | static mb_master_cfg_t mb_master_cfg = { 25 | .timeout = 1000, 26 | }; 27 | mb_transport_t * rtu; 28 | 29 | /* Configure RTU Modbus master */ 30 | rtu = mb_rtu_create(); 31 | mb_master_init ("/modbus0", &mb_master_cfg, rtu); 32 | } 33 | -------------------------------------------------------------------------------- /sample/ports/rt-kernel/rtu_slave.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2015 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "slave.h" 17 | #include "mb_bsp.h" 18 | #include "osal.h" 19 | 20 | int main (void) 21 | { 22 | mb_slave_t * slave; 23 | mb_transport_t * rtu; 24 | 25 | rtu = mb_rtu_create(); 26 | slave = mb_slave_init (&mb_slave_cfg, rtu); 27 | (void)slave; 28 | 29 | #if 0 30 | os_usleep (20 * 1000 * 1000); 31 | mb_slave_shutdown (slave); 32 | #endif 33 | } 34 | -------------------------------------------------------------------------------- /sample/ports/rt-kernel/tcp_rtu_master.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2015 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mb_master.h" 17 | #include "mb_tcp.h" 18 | #include "mb_bsp.h" 19 | 20 | SHELL_CMD (cmd_mb_read); 21 | SHELL_CMD (cmd_mb_write); 22 | 23 | int main (void) 24 | { 25 | static const mb_tcp_cfg_t mb_tcp_cfg = { 26 | .port = MODBUS_DEFAULT_PORT, 27 | }; 28 | static mb_master_cfg_t mb_master_cfg = { 29 | .timeout = 1000, 30 | }; 31 | mb_transport_t * tcp; 32 | mb_transport_t * rtu; 33 | 34 | /* Configure RTU Modbus master */ 35 | rtu = mb_rtu_create(); 36 | mb_master_init ("/modbus0", &mb_master_cfg, rtu); 37 | 38 | /* Configure TCP Modbus master */ 39 | tcp = mb_tcp_init (&mb_tcp_cfg); 40 | mb_master_init ("/modbus1", &mb_master_cfg, tcp); 41 | } 42 | -------------------------------------------------------------------------------- /sample/ports/windows/mb_bsp.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2020 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mb_bsp.h" 17 | 18 | mb_transport_t * mb_rtu_create ( 19 | const char * device, 20 | mb_rtu_serial_cfg_t * serial_cfg) 21 | { 22 | return NULL; 23 | } 24 | -------------------------------------------------------------------------------- /sample/ports/windows/mb_bsp.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2020 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #ifndef MB_BSP_H 17 | #define MB_BSP_H 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | #include "mb_transport.h" 24 | #include "mb_rtu.h" 25 | 26 | mb_transport_t * mb_rtu_create ( 27 | const char * device, 28 | mb_rtu_serial_cfg_t * serial_cfg); 29 | 30 | #ifdef __cplusplus 31 | } 32 | #endif 33 | 34 | #endif /* MB_BSP_H */ 35 | -------------------------------------------------------------------------------- /sample/ports/windows/rtu_slave.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2015 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "slave.h" 17 | #include "mb_bsp.h" 18 | #include "osal.h" 19 | 20 | int main (void) 21 | { 22 | mb_slave_t * slave; 23 | mb_transport_t * rtu; 24 | mb_rtu_serial_cfg_t serial_cfg; 25 | 26 | serial_cfg.baudrate = 115200; 27 | serial_cfg.parity = NONE; 28 | 29 | rtu = mb_rtu_create ("/dev/ttyAMA0", &serial_cfg); 30 | slave = mb_slave_init (&mb_slave_cfg, rtu); 31 | os_usleep (20 * 1000 * 1000); 32 | mb_slave_shutdown (slave); 33 | } 34 | -------------------------------------------------------------------------------- /sample/ports/windows/tcp_rtu_master.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2015 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mbus.h" 17 | #include "mb_tcp.h" 18 | #include "mb_rtu.h" 19 | #include "mb_bsp.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | static struct opt 29 | { 30 | int repeat; 31 | int delay; 32 | bool read; 33 | bool write; 34 | bool tcp; 35 | bool rtu; 36 | struct 37 | { 38 | char ip[20]; 39 | mb_tcp_cfg_t cfg; 40 | } tcp_details; 41 | struct 42 | { 43 | char device[80]; 44 | char unit[4]; 45 | mb_rtu_serial_cfg_t cfg; 46 | } rtu_details; 47 | int table; 48 | uint32_t address; 49 | uint32_t value; 50 | } opt; 51 | 52 | static void help (const char * name) 53 | { 54 | printf ( 55 | "Read/write Modbus registers\n" 56 | "\n" 57 | "This purpose of this program is to demonstrate use of the M-Bus\n" 58 | "Modbus master stack. It may also be useful as a debugging tool.\n" 59 | "\n" 60 | "USAGE:\n" 61 | "%s [OPTIONS] cmd transport connection table address [value]\n" 62 | "\n" 63 | "cmd {read, write} Type of transaction\n" 64 | "transport {tcp, rtu} Type of transport\n" 65 | "connection {IP:PORT, DEVICE:BAUDRATE:PARITY:UNIT}\n" 66 | " Connection details\n" 67 | "table {coil, input, reg, hold} Modbus address table\n" 68 | "address Modbus address\n" 69 | "\n" 70 | "TCP CONNECTION DETAILS\n" 71 | "IP IP-address, e.g. 192.168.0.1\n" 72 | "PORT Modbus port, e.g. 502\n" 73 | "\n" 74 | "RTU CONNECTION DETAILS\n" 75 | "DEVICE Serial device, e.g. /dev/ttyUSB0\n" 76 | "BAUDRATE Modbus baudrate\n" 77 | "PARITY Modbus parity {odd, even, none}\n" 78 | "\n" 79 | "OPTIONS:\n" 80 | " --repeat n Repeat command n times\n" 81 | " --delay ms Time to delay between Modbus transactions\n" 82 | "\n" 83 | "EXAMPLES:\n" 84 | "\n" 85 | "Repeatedly read coil 1 of Modbus TCP slave 192.168.10.134, port 8502.\n" 86 | "\n" 87 | "%s --repeat 100 read tcp 192.168.10.134:8502 coil 0x0001\n" 88 | "\n" 89 | "Write 42 to holding register 3 of Modbus RTU slave 2, accessed\n" 90 | "through /dev/ttyUSB0 with baudrate 115200, no parity.\n" 91 | "\n" 92 | "%s write rtu /dev/ttyUSB0:115200:none:2 hold 0x0003 42\n" 93 | "\n", 94 | name, 95 | name, 96 | name); 97 | exit (EXIT_SUCCESS); 98 | } 99 | 100 | static void fail (const char * name, const char * reason) 101 | { 102 | if (reason != NULL) 103 | { 104 | printf ("%s. ", reason); 105 | } 106 | printf ("Try %s --help\n", name); 107 | exit (EXIT_FAILURE); 108 | } 109 | 110 | static bool parse_tcp_details (char * s) 111 | { 112 | char * p; 113 | char * saveptr; 114 | 115 | /* Get ip address */ 116 | 117 | p = strtok_s (s, ":", &saveptr); 118 | if (p == NULL) 119 | return false; 120 | 121 | strncpy_s ( 122 | opt.tcp_details.ip, 123 | sizeof (opt.tcp_details.ip), 124 | p, 125 | sizeof (opt.tcp_details.ip)); 126 | opt.tcp_details.ip[sizeof (opt.tcp_details.ip) - 1] = 0; 127 | 128 | /* Get ip port */ 129 | 130 | p = strtok_s (NULL, ":", &saveptr); 131 | if (p == NULL) 132 | return false; 133 | 134 | opt.tcp_details.cfg.port = (uint16_t)strtoul (p, NULL, 0); 135 | 136 | /* Check for extra unknown options */ 137 | 138 | p = strtok_s (NULL, ":", &saveptr); 139 | if (p != NULL) 140 | return false; 141 | 142 | return true; 143 | } 144 | 145 | static bool parse_rtu_details (char * s) 146 | { 147 | char * p; 148 | char * saveptr; 149 | 150 | /* Get device */ 151 | 152 | p = strtok_s (s, ":", &saveptr); 153 | if (p == NULL) 154 | return false; 155 | 156 | strncpy_s ( 157 | opt.rtu_details.device, 158 | sizeof (opt.rtu_details.device), 159 | p, 160 | sizeof (opt.rtu_details.device)); 161 | opt.rtu_details.device[sizeof (opt.rtu_details.device) - 1] = 0; 162 | 163 | /* Get baudrate */ 164 | 165 | p = strtok_s (NULL, ":", &saveptr); 166 | if (p == NULL) 167 | return false; 168 | 169 | opt.rtu_details.cfg.baudrate = strtoul (p, NULL, 0); 170 | 171 | /* Get parity */ 172 | 173 | p = strtok_s (NULL, ":", &saveptr); 174 | if (p == NULL) 175 | return false; 176 | 177 | if (strcmp (p, "odd") == 0) 178 | opt.rtu_details.cfg.parity = ODD; 179 | else if (strcmp (p, "even") == 0) 180 | opt.rtu_details.cfg.parity = EVEN; 181 | else if (strcmp (p, "none") == 0) 182 | opt.rtu_details.cfg.parity = NONE; 183 | else 184 | return false; 185 | 186 | /* Get unit */ 187 | 188 | p = strtok_s (NULL, ":", &saveptr); 189 | if (p == NULL) 190 | return false; 191 | 192 | strncpy_s ( 193 | opt.rtu_details.unit, 194 | sizeof (opt.rtu_details.unit), 195 | p, 196 | sizeof (opt.rtu_details.unit)); 197 | opt.rtu_details.unit[sizeof (opt.rtu_details.unit) - 1] = 0; 198 | 199 | /* Check for extra unknown options */ 200 | 201 | p = strtok_s (NULL, ":", &saveptr); 202 | if (p != NULL) 203 | return false; 204 | 205 | return true; 206 | } 207 | 208 | static void parse_opt (int argc, char * argv[]) 209 | { 210 | int optind = 1; 211 | 212 | /* Set defaults */ 213 | opt.repeat = 1; 214 | 215 | while (optind < argc) 216 | { 217 | if (strcmp (argv[optind], "--help") == 0) 218 | { 219 | help (argv[0]); 220 | exit (EXIT_SUCCESS); 221 | } 222 | else if (strcmp (argv[optind], "--repeat") == 0) 223 | { 224 | opt.repeat = strtoul (argv[++optind], NULL, 0); 225 | } 226 | else if (strcmp (argv[optind], "--delay") == 0) 227 | { 228 | opt.delay = strtoul (argv[++optind], NULL, 0); 229 | } 230 | else if (argv[optind][0] == '-') 231 | { 232 | fail (argv[0], "unknown option"); 233 | } 234 | else 235 | { 236 | break; 237 | } 238 | 239 | ++optind; 240 | } 241 | 242 | /* Get command */ 243 | 244 | if (optind == argc) 245 | fail (argv[0], "Nothing to do"); 246 | 247 | opt.read = strcmp (argv[optind], "read") == 0; 248 | opt.write = strcmp (argv[optind], "write") == 0; 249 | 250 | /* Get transport type */ 251 | 252 | if (++optind == argc) 253 | fail (argv[0], "No transport"); 254 | 255 | opt.tcp = strcmp (argv[optind], "tcp") == 0; 256 | opt.rtu = strcmp (argv[optind], "rtu") == 0; 257 | 258 | /* Get transport details */ 259 | 260 | if (++optind == argc) 261 | fail (argv[0], "No transport details"); 262 | 263 | if (opt.tcp) 264 | { 265 | if (!parse_tcp_details (argv[optind])) 266 | fail (argv[0], "Bad tcp details"); 267 | } 268 | else if (opt.rtu) 269 | { 270 | if (!parse_rtu_details (argv[optind])) 271 | fail (argv[0], "Bad rtu details"); 272 | } 273 | 274 | /* Get address table */ 275 | 276 | if (++optind == argc) 277 | fail (argv[0], "No address table"); 278 | 279 | if (strcmp (argv[optind], "coil") == 0) 280 | opt.table = 0; 281 | else if (strcmp (argv[optind], "input") == 0) 282 | opt.table = 1; 283 | else if (strcmp (argv[optind], "reg") == 0) 284 | opt.table = 3; 285 | else if (strcmp (argv[optind], "hold") == 0) 286 | opt.table = 4; 287 | else 288 | fail (argv[0], "Bad address table"); 289 | 290 | /* Get register address */ 291 | 292 | if (++optind == argc) 293 | fail (argv[0], "No address"); 294 | 295 | opt.address = strtoul (argv[optind], NULL, 0); 296 | if (opt.address > 0x10000) 297 | fail (argv[0], "Bad address"); 298 | 299 | if (opt.write) 300 | { 301 | /* Get value */ 302 | if (++optind == argc) 303 | fail (argv[0], "No value"); 304 | 305 | opt.value = strtoul (argv[optind], NULL, 0); 306 | if (opt.value > 0xFFFF) 307 | fail (argv[0], "Bad value"); 308 | } 309 | 310 | /* Check for extra unknown options */ 311 | 312 | if (++optind != argc) 313 | fail (argv[0], "Too many options"); 314 | } 315 | 316 | int main (int argc, char * argv[]) 317 | { 318 | static mbus_cfg_t mb_master_cfg = { 319 | .timeout = 1000, 320 | }; 321 | mbus_t * mbus; 322 | int slave; 323 | 324 | parse_opt (argc, argv); 325 | 326 | if (opt.tcp) 327 | { 328 | mb_transport_t * tcp; 329 | 330 | tcp = mb_tcp_init (&opt.tcp_details.cfg); 331 | mbus = mbus_create (&mb_master_cfg, tcp); 332 | 333 | slave = mbus_connect (mbus, opt.tcp_details.ip); 334 | } 335 | else if (opt.rtu) 336 | { 337 | mb_transport_t * rtu; 338 | 339 | rtu = mb_rtu_create (opt.rtu_details.device, &opt.rtu_details.cfg); 340 | mbus = mbus_create (&mb_master_cfg, rtu); 341 | 342 | slave = mbus_connect (mbus, opt.rtu_details.unit); 343 | } 344 | else 345 | { 346 | fail (argv[0], "Unknown transport"); 347 | } 348 | 349 | if (slave == -1) 350 | { 351 | fail (argv[0], "Failed to open socket"); 352 | } 353 | 354 | for (int i = 0; i < opt.repeat; i++) 355 | { 356 | uint16_t value = 0; 357 | int error = 0; 358 | mb_address_t address = MB_ADDRESS (opt.table, opt.address); 359 | 360 | if (opt.read) 361 | { 362 | error = mbus_read (mbus, slave, address, 1, &value); 363 | } 364 | else if (opt.write) 365 | { 366 | error = mbus_write_single (mbus, slave, address, opt.value); 367 | } 368 | else 369 | { 370 | fail (argv[0], "Unknown command"); 371 | } 372 | 373 | if (error) 374 | printf ("Modbus function failed (%s).\n", mb_error_literal (error)); 375 | else if (opt.read) 376 | printf ("0x%04x\n", value); 377 | 378 | Sleep (opt.delay); 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /sample/slave.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2015 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "slave.h" 17 | #include "mb_tcp.h" 18 | #include "mb_rtu.h" 19 | #include "osal.h" 20 | 21 | #include 22 | #include 23 | 24 | static bool coils[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 25 | static uint16_t hold[4] = { 0x0000, 0x0000, 0x0000, 0x0000 }; 26 | 27 | static int coil_get (uint16_t address, uint8_t * data, size_t quantity) 28 | { 29 | uint16_t offset; 30 | 31 | for (offset = 0; offset < quantity; offset++) 32 | { 33 | uint32_t bit = address + offset; 34 | 35 | mb_slave_bit_set (data, offset, coils[bit]); 36 | } 37 | return 0; 38 | } 39 | 40 | static int coil_set (uint16_t address, uint8_t * data, size_t quantity) 41 | { 42 | uint16_t offset; 43 | 44 | for (offset = 0; offset < quantity; offset++) 45 | { 46 | uint32_t bit = address + offset; 47 | 48 | coils[bit] = mb_slave_bit_get (data, offset); 49 | } 50 | return 0; 51 | } 52 | 53 | static int input_get (uint16_t address, uint8_t * data, size_t quantity) 54 | { 55 | uint16_t offset; 56 | 57 | for (offset = 0; offset < quantity; offset++) 58 | { 59 | mb_slave_bit_set (data, offset, 1); 60 | } 61 | return 0; 62 | } 63 | 64 | static int hold_get (uint16_t address, uint8_t * data, size_t quantity) 65 | { 66 | uint16_t offset; 67 | 68 | for (offset = 0; offset < quantity; offset++) 69 | { 70 | uint32_t reg = address + offset; 71 | 72 | mb_slave_reg_set (data, offset, hold[reg]); 73 | } 74 | return 0; 75 | } 76 | 77 | static int hold_set (uint16_t address, uint8_t * data, size_t quantity) 78 | { 79 | uint16_t offset; 80 | 81 | for (offset = 0; offset < quantity; offset++) 82 | { 83 | uint32_t reg = address + offset; 84 | 85 | hold[reg] = mb_slave_reg_get (data, offset); 86 | } 87 | return 0; 88 | } 89 | 90 | static int reg_get (uint16_t address, uint8_t * data, size_t quantity) 91 | { 92 | uint16_t offset; 93 | 94 | for (offset = 0; offset < quantity; offset++) 95 | { 96 | mb_slave_reg_set (data, offset, 0x1100 | (offset & 0xFF)); 97 | } 98 | return 0; 99 | } 100 | 101 | static int ping (uint8_t * data, size_t rx_count) 102 | { 103 | char * message = "Hello World"; 104 | memcpy (data, message, strlen (message)); 105 | return (int)strlen (message); 106 | } 107 | 108 | static const mb_vendor_func_t vendor_funcs[] = { 109 | {101, ping}, 110 | }; 111 | 112 | const mb_iomap_t mb_slave_iomap = { 113 | .coils = {16, coil_get, coil_set}, // 16 coils 114 | .inputs = {2, input_get, NULL}, // 2 input status bits 115 | .holding_registers = {4, hold_get, hold_set}, // 4 holding registers 116 | .input_registers = {5, reg_get, NULL}, // 5 input registers 117 | .num_vendor_funcs = NELEMENTS (vendor_funcs), // 1 vendor function 118 | .vendor_funcs = vendor_funcs, 119 | }; 120 | 121 | const mb_slave_cfg_t mb_slave_cfg = { 122 | .id = 2, // Slave ID: 2 123 | .priority = 15, 124 | .stack_size = 2048, 125 | .iomap = &mb_slave_iomap}; 126 | -------------------------------------------------------------------------------- /sample/slave.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2020 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #ifndef SLAVE_H 17 | #define SLAVE_H 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | #include "mb_slave.h" 24 | 25 | extern const mb_iomap_t mb_slave_iomap; 26 | extern const mb_slave_cfg_t mb_slave_cfg; 27 | 28 | #ifdef __cplusplus 29 | } 30 | #endif 31 | 32 | #endif /* SLAVE_H */ 33 | -------------------------------------------------------------------------------- /sample/tcp_slave.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2015 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "slave.h" 17 | #include "mb_tcp.h" 18 | #include "osal.h" 19 | 20 | /* Start Modbus TCP slave thread and run for half an hour */ 21 | int main (void) 22 | { 23 | mb_slave_t * slave; 24 | mb_transport_t * tcp; 25 | static const mb_tcp_cfg_t mb_tcp_cfg = 26 | { 27 | .port = 8502, 28 | }; 29 | 30 | tcp = mb_tcp_init (&mb_tcp_cfg); 31 | slave = mb_slave_init (&mb_slave_cfg, tcp); 32 | 33 | os_usleep (30 * 60 * 1000 * 1000); 34 | mb_slave_shutdown (slave); 35 | } 36 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # Developer documentation 2 | 3 | This readme-file documents how to make changes to the M-Bus stack and is meant 4 | only for developers of the stack. For documentation about how to use the stack, 5 | see [the online manual](https://docs.rt-labs.com/m-bus/). 6 | 7 | 8 | ## How update the manual 9 | 10 | Please see docs/README.rst. 11 | 12 | 13 | ## How to perform static analysis on the stack 14 | 15 | 1. Install the clang-tidy tool on Linux: 16 | 17 | ```bash 18 | sudo apt install clang clang-format clang-tidy python3 19 | ``` 20 | 21 | 2. Run the clang-tidy static analysis tool: 22 | 23 | ```bash 24 | cmake -B build/analysis -DCMAKE_EXPORT_COMPILE_COMMANDS=ON 25 | run-clang-tidy -p build/analysis 26 | ``` 27 | 28 | There should be no warnings or errors. 29 | 30 | -------------------------------------------------------------------------------- /src/mb_crc.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2011 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mb_crc.h" 17 | 18 | /* Table of CRC values for high-order byte */ 19 | static uint8_t mb_TableCRCHi[] = { 20 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 21 | 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 22 | 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 23 | 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 24 | 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 25 | 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 26 | 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 27 | 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 28 | 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 29 | 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 30 | 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 31 | 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 32 | 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 33 | 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 34 | 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 35 | 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 36 | 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 37 | 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 38 | 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 39 | 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40}; 40 | 41 | /* Table of CRC values for low-order byte */ 42 | static uint8_t mb_TableCRCLo[] = { 43 | 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 44 | 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 45 | 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 46 | 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 47 | 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 48 | 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 49 | 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 50 | 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 51 | 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 52 | 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 53 | 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 54 | 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 55 | 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 56 | 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 57 | 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 58 | 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 59 | 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 60 | 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 61 | 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 62 | 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40}; 63 | 64 | /* 65 | * CRC calculation from MODBUS over serial line specification and 66 | * implementation guide V1.02, chapter 6.2.2. 67 | * 68 | * Note that the CRC is returned ready for transmission over serial 69 | * line, i.e. it is already in network byte order. 70 | */ 71 | crc_t mb_crc (const uint8_t * buffer, uint8_t len, crc_t preload) 72 | { 73 | uint8_t CRCHi = preload >> 8; 74 | uint8_t CRCLo = preload & 0xFF; 75 | uint16_t index; /* Will index into CRC lookup table */ 76 | 77 | while (len--) /* Pass through message buffer */ 78 | { 79 | index = CRCLo ^ *buffer++; /* Calculate the CRC */ 80 | CRCLo = CRCHi ^ mb_TableCRCHi[index]; 81 | CRCHi = mb_TableCRCLo[index]; 82 | } 83 | 84 | return ((CRCHi << 8) | CRCLo); 85 | } 86 | -------------------------------------------------------------------------------- /src/mb_crc.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2011 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #ifndef MB_CRC_H 17 | #define MB_CRC_H 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | #include 24 | 25 | typedef uint16_t crc_t; 26 | 27 | crc_t mb_crc (const uint8_t * buffer, uint8_t len, crc_t preload); 28 | 29 | #ifdef __cplusplus 30 | } 31 | #endif 32 | 33 | #endif /* MB_CRC_H */ 34 | -------------------------------------------------------------------------------- /src/mb_pdu.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2011 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #ifndef PDU_H 17 | #define PDU_H 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | #include "osal.h" 24 | 25 | #define MAX_PDU_SIZE 253 26 | 27 | /* PDU function codes */ 28 | #define PDU_READ_COILS 1 29 | #define PDU_READ_INPUTS 2 30 | #define PDU_READ_HOLDING_REGISTERS 3 31 | #define PDU_READ_INPUT_REGISTERS 4 32 | #define PDU_WRITE_COIL 5 33 | #define PDU_WRITE_HOLDING_REGISTER 6 34 | #define PDU_DIAGNOSTICS 8 35 | #define PDU_WRITE_COILS 15 36 | #define PDU_WRITE_HOLDING_REGISTERS 16 37 | #define PDU_READ_WRITE_HOLDING_REGISTERS 23 38 | 39 | /* Diagnostic sub-functions */ 40 | #define PDU_DIAG_LOOPBACK 0 41 | 42 | typedef struct pdu_exception 43 | { 44 | uint8_t function; 45 | uint8_t code; 46 | } pdu_exception_t; 47 | 48 | typedef struct pdu_request 49 | { 50 | uint8_t function; 51 | } pdu_request_t; 52 | 53 | typedef pdu_request_t pdu_response_t; 54 | 55 | CC_PACKED_BEGIN 56 | typedef struct pdu_read 57 | { 58 | uint8_t function; 59 | uint16_t address; 60 | uint16_t quantity; 61 | } CC_PACKED pdu_read_t; 62 | CC_PACKED_END 63 | 64 | CC_PACKED_BEGIN 65 | typedef struct pdu_read_response 66 | { 67 | uint8_t function; 68 | uint8_t count; 69 | uint8_t data[]; 70 | } CC_PACKED pdu_read_response_t; 71 | CC_PACKED_END 72 | 73 | CC_STATIC_ASSERT (sizeof (pdu_read_response_t) == 2); 74 | 75 | CC_PACKED_BEGIN 76 | typedef struct pdu_write_single 77 | { 78 | uint8_t function; 79 | uint16_t address; 80 | uint16_t value; 81 | } CC_PACKED pdu_write_single_t; 82 | CC_PACKED_END 83 | 84 | CC_STATIC_ASSERT (sizeof (pdu_write_single_t) == 5); 85 | 86 | typedef pdu_write_single_t pdu_write_single_response_t; 87 | 88 | CC_PACKED_BEGIN 89 | typedef struct pdu_write 90 | { 91 | uint8_t function; 92 | uint16_t address; 93 | uint16_t quantity; 94 | uint8_t count; 95 | uint8_t data[]; 96 | } CC_PACKED pdu_write_t; 97 | CC_PACKED_END 98 | 99 | CC_STATIC_ASSERT (sizeof (pdu_write_t) == 6); 100 | 101 | CC_PACKED_BEGIN 102 | typedef struct pdu_write_response 103 | { 104 | uint8_t function; 105 | uint16_t address; 106 | uint16_t quantity; 107 | } CC_PACKED pdu_write_response_t; 108 | CC_PACKED_END 109 | 110 | CC_STATIC_ASSERT (sizeof (pdu_write_response_t) == 5); 111 | 112 | CC_PACKED_BEGIN 113 | typedef struct pdu_read_write 114 | { 115 | uint8_t function; 116 | uint16_t read_address; 117 | uint16_t read_quantity; 118 | uint16_t write_address; 119 | uint16_t write_quantity; 120 | uint8_t count; 121 | uint8_t data[]; 122 | } CC_PACKED pdu_read_write_t; 123 | CC_PACKED_END 124 | 125 | CC_STATIC_ASSERT (sizeof (pdu_read_write_t) == 10); 126 | 127 | CC_PACKED_BEGIN 128 | typedef struct pdu_diag_t 129 | { 130 | uint8_t function; 131 | uint16_t sub_function; 132 | uint8_t data[]; 133 | } CC_PACKED pdu_diag_t; 134 | CC_PACKED_END 135 | 136 | CC_PACKED_BEGIN 137 | typedef struct pdu_vendor_t 138 | { 139 | uint8_t function; 140 | uint8_t data[]; 141 | } CC_PACKED pdu_vendor_t; 142 | CC_PACKED_END 143 | 144 | typedef union 145 | { 146 | pdu_exception_t exception; 147 | pdu_request_t request; 148 | pdu_response_t response; 149 | pdu_read_t read; 150 | pdu_read_response_t read_response; 151 | pdu_write_single_t write_single; 152 | pdu_write_single_response_t write_single_response; 153 | pdu_write_t write; 154 | pdu_write_response_t write_response; 155 | pdu_read_write_t read_write; 156 | pdu_diag_t diag; 157 | pdu_vendor_t vendor; 158 | } pdu_t; 159 | 160 | #ifdef __cplusplus 161 | } 162 | #endif 163 | 164 | #endif /* PDU_H */ 165 | 166 | /** 167 | * \} 168 | */ 169 | -------------------------------------------------------------------------------- /src/mb_rtu.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2012 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mb_rtu.h" 17 | #include "mb_pdu.h" 18 | #include "mb_crc.h" 19 | #include "mbal_rtu.h" 20 | #include "options.h" 21 | 22 | #include "osal.h" 23 | #include "osal_log.h" 24 | 25 | #include 26 | #include 27 | 28 | #if defined(__linux__) && defined (USE_TRACE) 29 | #include "mb-tp.h" 30 | #else 31 | #define tracepoint(...) 32 | #endif 33 | 34 | #define FLAG_TX_EMPTY BIT (0) 35 | #define FLAG_RX_AVAIL BIT (1) 36 | #define FLAG_T1P5 BIT (2) 37 | #define FLAG_T3P5 BIT (3) 38 | 39 | typedef struct mb_rtu 40 | { 41 | mb_transport_t transport; 42 | 43 | int fd; 44 | os_event_t * flags; 45 | void (*tx_enable) (int level); 46 | void (*tmr_init) (uint32_t t1p5, uint32_t t3p5); 47 | void (*tmr_start) ( 48 | void (*t1p5_expired) (void * arg), 49 | void (*t3p5_expired) (void * arg), 50 | void * arg); 51 | bool broadcast; 52 | uint32_t char_time_us; 53 | } mb_rtu_t; 54 | 55 | int mb_tx_hook (void * arg, void * data) 56 | { 57 | mb_rtu_t * rtu = (mb_rtu_t *)arg; 58 | os_event_set (rtu->flags, FLAG_TX_EMPTY); 59 | return 0; 60 | } 61 | 62 | static void mb_t1p5_expired (void * arg) 63 | { 64 | mb_rtu_t * rtu = (mb_rtu_t *)arg; 65 | tracepoint (mb, t1p5); 66 | os_event_set (rtu->flags, FLAG_T1P5); 67 | } 68 | 69 | static void mb_t3p5_expired (void * arg) 70 | { 71 | mb_rtu_t * rtu = (mb_rtu_t *)arg; 72 | tracepoint (mb, t3p5); 73 | os_event_set (rtu->flags, FLAG_T3P5); 74 | } 75 | 76 | int mb_rx_hook (void * arg, void * data) 77 | { 78 | mb_rtu_t * rtu = (mb_rtu_t *)arg; 79 | 80 | tracepoint (mb, rx_hook); 81 | rtu->tmr_start (mb_t1p5_expired, mb_t3p5_expired, rtu); 82 | os_event_clr (rtu->flags, FLAG_T1P5 | FLAG_T3P5); 83 | os_event_set (rtu->flags, FLAG_RX_AVAIL); 84 | return 0; 85 | } 86 | 87 | static void mb_rtu_dump ( 88 | const char * header, 89 | const uint8_t * buffer, 90 | size_t size) 91 | { 92 | #if LOG_DEBUG_ENABLED(MB_RTU_LOG) 93 | LOG_DEBUG (MB_RTU_LOG, "%s", header); 94 | while (size--) 95 | LOG_DEBUG (MB_RTU_LOG, " %02x\n", *buffer++); 96 | #endif 97 | } 98 | 99 | static int mb_rtu_bringup (mb_transport_t * transport, const char * name) 100 | { 101 | const char * p = name; 102 | unsigned int slave = 0; 103 | 104 | CC_ASSERT (name != NULL); 105 | 106 | /* Remainder of filename is modbus slave */ 107 | while (*p != '\0') 108 | { 109 | uint8_t digit = *p++ - '0'; 110 | 111 | if (digit > 9) 112 | goto error; 113 | 114 | slave = slave * 10 + digit; 115 | } 116 | 117 | /* Check that slave is valid */ 118 | if (slave > 247) 119 | goto error; 120 | 121 | return slave; 122 | 123 | error: 124 | errno = ENOENT; 125 | return -1; 126 | } 127 | 128 | static int mb_rtu_shutdown (mb_transport_t * transport, int arg) 129 | { 130 | return 0; 131 | } 132 | 133 | static bool mb_rtu_is_down (mb_transport_t * transport) 134 | { 135 | return false; 136 | } 137 | 138 | static void mb_rtu_write (mb_rtu_t * rtu, const void * buffer, size_t size) 139 | { 140 | size_t nwrite; 141 | const uint8_t * p = buffer; 142 | 143 | while (size > 0) 144 | { 145 | nwrite = os_rtu_write (rtu->fd, p, size); 146 | if (nwrite <= 0) 147 | { 148 | LOG_ERROR (MB_RTU_LOG, "tx failure\n"); 149 | break; 150 | } 151 | p += nwrite; 152 | size -= nwrite; 153 | } 154 | } 155 | 156 | static size_t mb_rtu_read (mb_rtu_t * rtu, void * buffer, size_t size) 157 | { 158 | size_t navail; 159 | size_t nread; 160 | 161 | os_event_clr (rtu->flags, FLAG_RX_AVAIL); 162 | navail = os_rtu_rx_avail (rtu->fd); 163 | if (navail > size) 164 | { 165 | /* There will still be data available */ 166 | os_event_set (rtu->flags, FLAG_RX_AVAIL); 167 | navail = size; 168 | } 169 | 170 | nread = os_rtu_read (rtu->fd, buffer, navail); 171 | if (nread <= 0) 172 | { 173 | LOG_ERROR (MB_RTU_LOG, "rx failure\n"); 174 | } 175 | tracepoint (mb, rx_read, nread); 176 | return nread; 177 | } 178 | 179 | static void mb_rtu_tx ( 180 | mb_transport_t * transport, 181 | const pdu_txn_t * transaction, 182 | size_t size) 183 | { 184 | mb_rtu_t * rtu = (mb_rtu_t *)transport; 185 | uint32_t flags; 186 | crc_t crc; 187 | uint8_t slave = transaction->unit; 188 | 189 | tracepoint (mb, tx_trace, 1); 190 | mb_rtu_dump ("Tx:\n", transaction->data, size); 191 | 192 | /* Compute CRC */ 193 | crc = mb_crc (&slave, 1, 0xFFFF); 194 | crc = mb_crc (transaction->data, (uint8_t)size, crc); 195 | 196 | /* Enable Tx */ 197 | if (rtu->tx_enable) 198 | rtu->tx_enable (1); 199 | 200 | /* Send slave address */ 201 | mb_rtu_write (rtu, &slave, 1); 202 | 203 | /* Send PDU */ 204 | os_event_clr (rtu->flags, FLAG_TX_EMPTY); 205 | mb_rtu_write (rtu, transaction->data, size); 206 | 207 | /* Send CRC */ 208 | mb_rtu_write (rtu, &crc, sizeof (crc_t)); 209 | tracepoint (mb, tx_trace, 2); 210 | 211 | /* Wait for emission of last character */ 212 | #if !defined(__linux__) 213 | os_event_wait (rtu->flags, FLAG_TX_EMPTY, &flags, OS_WAIT_FOREVER); 214 | #endif 215 | os_rtu_tx_drain (rtu->fd, 3 + size); 216 | tracepoint (mb, tx_trace, 3); 217 | 218 | /* Clear state for reception */ 219 | os_event_clr (rtu->flags, FLAG_T1P5 | FLAG_T3P5); 220 | 221 | /* Disable Tx */ 222 | if (rtu->tx_enable) 223 | rtu->tx_enable (0); 224 | 225 | /* Start and wait for T3P5 timer */ 226 | rtu->tmr_start (NULL, mb_t3p5_expired, rtu); 227 | os_event_wait (rtu->flags, FLAG_T3P5, &flags, OS_WAIT_FOREVER); 228 | tracepoint (mb, tx_trace, 4); 229 | } 230 | 231 | static bool mb_rtu_rx_avail (mb_transport_t * transport) 232 | { 233 | mb_rtu_t * rtu = (mb_rtu_t *)transport; 234 | ssize_t navail; 235 | 236 | navail = os_rtu_rx_avail (rtu->fd); 237 | if (navail < 0) 238 | { 239 | LOG_ERROR (MB_RTU_LOG, "rx_avail failure\n"); 240 | } 241 | return navail > 0; 242 | } 243 | 244 | static int mb_rtu_rx ( 245 | mb_transport_t * transport, 246 | pdu_txn_t * transaction, 247 | uint32_t tmo) 248 | { 249 | mb_rtu_t * rtu = (mb_rtu_t *)transport; 250 | bool frame_ok = true; 251 | size_t count = 0; 252 | uint32_t flags; 253 | crc_t crc; 254 | uint8_t slave_rx; 255 | uint8_t * p = transaction->data; 256 | int error; 257 | 258 | tracepoint (mb, rx_trace, 1); 259 | 260 | /* Wait for first character */ 261 | if (tmo) 262 | { 263 | int timedout; 264 | 265 | timedout = 266 | os_event_wait (rtu->flags, FLAG_T1P5 | FLAG_RX_AVAIL, &flags, tmo); 267 | if (timedout) 268 | { 269 | tracepoint (mb, rx_trace, 2); 270 | return ETIMEOUT; 271 | } 272 | } 273 | else 274 | { 275 | os_event_wait ( 276 | rtu->flags, 277 | FLAG_T1P5 | FLAG_RX_AVAIL, 278 | &flags, 279 | OS_WAIT_FOREVER); 280 | } 281 | 282 | /* Get slave ID */ 283 | mb_rtu_read (rtu, &slave_rx, 1); 284 | crc = mb_crc (&slave_rx, 1, 0xFFFF); 285 | 286 | /* Get remainder of message (until T1P5 expires) */ 287 | do 288 | { 289 | size_t nread; 290 | 291 | os_event_wait ( 292 | rtu->flags, 293 | FLAG_T1P5 | FLAG_RX_AVAIL, 294 | &flags, 295 | OS_WAIT_FOREVER); 296 | if (flags & FLAG_RX_AVAIL) 297 | { 298 | nread = mb_rtu_read (rtu, p, MAX_PDU_SIZE - count); 299 | p += nread; 300 | count += nread; 301 | } 302 | } while ((flags & FLAG_T1P5) == 0); 303 | 304 | /* Verify message */ 305 | crc = mb_crc (transaction->data, (uint8_t)count, crc); 306 | if (crc != 0) 307 | { 308 | error = ECRC_FAIL; 309 | frame_ok = false; 310 | } 311 | 312 | /* Match station ID with our ID or the broadcast ID */ 313 | if ((slave_rx != transaction->unit) && (slave_rx != 0)) 314 | { 315 | error = ESLAVE_ID; 316 | frame_ok = false; 317 | } 318 | 319 | /* Set broadcast flag if it was a broadcast station ID */ 320 | rtu->broadcast = (slave_rx == 0); 321 | 322 | /* Wait for end of frame (until T3P5 expires) */ 323 | do 324 | { 325 | os_event_wait ( 326 | rtu->flags, 327 | FLAG_T3P5 | FLAG_RX_AVAIL, 328 | &flags, 329 | OS_WAIT_FOREVER); 330 | if (flags & FLAG_RX_AVAIL) 331 | { 332 | error = EFRAME_NOK; 333 | frame_ok = false; 334 | 335 | /* Need to process extra characters. Stick them at the end of 336 | the current message but don't increment counter */ 337 | mb_rtu_read (rtu, p, MAX_PDU_SIZE - count); 338 | } 339 | } while ((flags & FLAG_T3P5) == 0); 340 | 341 | os_event_clr (rtu->flags, FLAG_T1P5 | FLAG_T3P5 | FLAG_RX_AVAIL); 342 | 343 | if (!frame_ok) 344 | { 345 | LOG_DEBUG (MB_RTU_LOG, "RxErr: %d\n", error); 346 | mb_rtu_dump ("RxErr:\n", transaction->data, count); 347 | tracepoint (mb, rx_trace, 3); 348 | return error; 349 | } 350 | 351 | count -= sizeof (crc_t); 352 | mb_rtu_dump ("Rx:\n", transaction->data, count); 353 | tracepoint (mb, rx_trace, 4); 354 | 355 | return (int)count; 356 | } 357 | 358 | static bool mb_rtu_rx_bc (mb_transport_t * transport) 359 | { 360 | mb_rtu_t * rtu = (mb_rtu_t *)transport; 361 | return rtu->broadcast; 362 | } 363 | 364 | static uint32_t mb_rtu_char_time (int baudrate) 365 | { 366 | uint32_t char_time; 367 | 368 | /* If baud rate is > 19200 then 1p5 should be 750 us and 3p5 1750 369 | * us. If less or equal to 19200 we calculate the values. 370 | */ 371 | if (baudrate > 19200) 372 | { 373 | /* This char_time makes 1p5 = 750 us and 3p5 = 1750 us */ 374 | char_time = 500; 375 | } 376 | else 377 | { 378 | /* Calculate the time for 1 character in us 379 | * t (us) = bits / (baudrate / 10^6) => 380 | * t (us) = (bits * 10^6) / baudrate 381 | * 382 | * For modbus RTU, 1 character is always 11 bits. 383 | */ 384 | char_time = (11 * 1000 * 1000) / baudrate; 385 | } 386 | 387 | return char_time; 388 | } 389 | 390 | void mb_rtu_serial_cfg ( 391 | mb_transport_t * transport, 392 | const mb_rtu_serial_cfg_t * cfg) 393 | { 394 | mb_rtu_t * rtu = (mb_rtu_t *)transport; 395 | uint32_t t1p5, t3p5; 396 | 397 | /* Configure serial port */ 398 | os_rtu_set_serial_cfg (rtu->fd, cfg); 399 | 400 | /* Calculate T1P5 and T3P5 timeouts */ 401 | rtu->char_time_us = mb_rtu_char_time (cfg->baudrate); 402 | 403 | t1p5 = 15 * rtu->char_time_us / 10; 404 | t3p5 = 35 * rtu->char_time_us / 10; 405 | 406 | /* Configure timers */ 407 | rtu->tmr_init (t1p5, t3p5); 408 | } 409 | 410 | mb_transport_t * mb_rtu_init (const mb_rtu_cfg_t * cfg) 411 | { 412 | mb_rtu_t * rtu; 413 | 414 | /* Allocate and initialise driver structure */ 415 | 416 | rtu = malloc (sizeof (mb_rtu_t)); 417 | CC_ASSERT (rtu != NULL); 418 | 419 | rtu->transport.bringup = mb_rtu_bringup; 420 | rtu->transport.shutdown = mb_rtu_shutdown; 421 | rtu->transport.is_down = mb_rtu_is_down; 422 | rtu->transport.tx = mb_rtu_tx; 423 | rtu->transport.rx = mb_rtu_rx; 424 | rtu->transport.rx_is_bc = mb_rtu_rx_bc; 425 | rtu->transport.rx_avail = mb_rtu_rx_avail; 426 | 427 | rtu->tx_enable = cfg->tx_enable; 428 | rtu->tmr_init = cfg->tmr_init; 429 | rtu->tmr_start = cfg->tmr_start; 430 | rtu->flags = os_event_create(); 431 | 432 | /* Open serial port */ 433 | rtu->fd = os_rtu_open (cfg->serial, rtu); 434 | 435 | /* Configure RTU layer */ 436 | mb_rtu_serial_cfg (&rtu->transport, cfg->serial_cfg); 437 | 438 | return (mb_transport_t *)rtu; 439 | } 440 | -------------------------------------------------------------------------------- /src/mb_tcp.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2015 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mb_tcp.h" 17 | #include "mb_transport.h" 18 | #include "mb_pdu.h" 19 | #include "osal.h" 20 | #include "mbal_tcp.h" 21 | #include "osal_log.h" 22 | #include "options.h" 23 | #include "inttypes.h" 24 | 25 | #include 26 | #include 27 | 28 | #define KEEP_ALIVE_IDLE 10 /* max idle time before keepalive sent [s] */ 29 | #define KEEP_ALIVE_INTVL 2 /* time between keepalives [s] */ 30 | #define KEEP_ALIVE_CNT 3 /* max number of unacked keepalives */ 31 | 32 | #define RCV_TIMEOUT 500 /* max time to wait for message in progress [ms] */ 33 | 34 | typedef struct mbap 35 | { 36 | uint16_t id; 37 | uint16_t protocol; 38 | uint16_t length; 39 | uint8_t unit; 40 | uint8_t data[MAX_PDU_SIZE]; 41 | } CC_PACKED mbap_t; 42 | 43 | #define MBAP_HEADER_SIZE offsetof (mbap_t, data) 44 | 45 | typedef struct mb_tcp 46 | { 47 | mb_transport_t transport; 48 | uint16_t port; 49 | bool is_down; 50 | mbap_t mbap; 51 | } mb_tcp_t; 52 | 53 | static int mb_tcp_bringup (mb_transport_t * transport, const char * name) 54 | { 55 | mb_tcp_t * mb_tcp = (mb_tcp_t *)transport; 56 | int peer; 57 | 58 | if (transport->is_server) 59 | { 60 | peer = os_tcp_accept_connection (mb_tcp->port); 61 | } 62 | else 63 | { 64 | peer = os_tcp_connect (name, mb_tcp->port); 65 | } 66 | 67 | if (peer > 0) 68 | { 69 | mb_tcp->is_down = false; 70 | LOG_INFO (MB_TCP_LOG, "Connection established\n"); 71 | } 72 | 73 | return peer; 74 | } 75 | 76 | static int mb_tcp_shutdown (mb_transport_t * transport, int arg) 77 | { 78 | mb_tcp_t * mb_tcp = (mb_tcp_t *)transport; 79 | int peer = arg; 80 | 81 | if (mb_tcp->is_down == false) 82 | { 83 | LOG_INFO (MB_TCP_LOG, "Connection closed\n"); 84 | os_tcp_close (peer); 85 | mb_tcp->is_down = true; 86 | } 87 | 88 | os_usleep (10 * 1000); 89 | return 0; 90 | } 91 | 92 | static bool mb_tcp_is_down (mb_transport_t * transport) 93 | { 94 | mb_tcp_t * mb_tcp = (mb_tcp_t *)transport; 95 | return mb_tcp->is_down; 96 | } 97 | 98 | static void mb_tcp_tx ( 99 | mb_transport_t * transport, 100 | const pdu_txn_t * transaction, 101 | size_t size) 102 | { 103 | mb_tcp_t * mb_tcp = (mb_tcp_t *)transport; 104 | int peer = transaction->arg; 105 | mbap_t * mbap = &mb_tcp->mbap; 106 | ssize_t result; 107 | 108 | mbap->id = CC_TO_BE16 (transaction->id); 109 | mbap->length = CC_TO_BE16 ((uint16_t)size + 1); /* Includes size of unit id */ 110 | mbap->protocol = 0; 111 | mbap->unit = transaction->unit; 112 | 113 | LOG_DEBUG (MB_TCP_LOG, "Sending header and %d bytes data\n", (unsigned)size); 114 | memcpy (mbap->data, transaction->data, size); 115 | result = os_tcp_send (peer, mbap, MBAP_HEADER_SIZE + size); 116 | 117 | if (result <= 0) 118 | { 119 | /* Peer closed their connection or some other error. Close 120 | connection. */ 121 | LOG_INFO (MB_TCP_LOG, "Connection closed\n"); 122 | os_tcp_close (peer); 123 | mb_tcp->is_down = true; 124 | return; 125 | } 126 | } 127 | 128 | static int mb_tcp_rx ( 129 | mb_transport_t * transport, 130 | pdu_txn_t * transaction, 131 | uint32_t tmo) 132 | { 133 | mb_tcp_t * mb_tcp = (mb_tcp_t *)transport; 134 | int peer = transaction->arg; 135 | mbap_t * mbap = &mb_tcp->mbap; 136 | size_t size = 0; 137 | ssize_t result; 138 | 139 | /* Wait for next message until timeout */ 140 | result = os_tcp_recv_wait (peer, tmo); 141 | if (result == -1) 142 | { 143 | LOG_INFO (MB_TCP_LOG, "Connection closed\n"); 144 | os_tcp_close (peer); 145 | mb_tcp->is_down = true; 146 | return EFRAME_NOK; 147 | } 148 | if (result == 0) 149 | { 150 | /* Timeout */ 151 | return ETIMEOUT; 152 | } 153 | 154 | /* Get message. The recv function will timeout if data is not 155 | available in a reasonable timeframe. */ 156 | 157 | LOG_DEBUG (MB_TCP_LOG, "Receiving header\n"); 158 | result = os_tcp_recv (peer, mbap, MBAP_HEADER_SIZE); 159 | if (result == MBAP_HEADER_SIZE) 160 | { 161 | /* The size of the PDU includes the unit id we already read */ 162 | size = CC_FROM_BE16 (mbap->length) - 1; 163 | 164 | /* Never overflow buffer */ 165 | if (size > MAX_PDU_SIZE) 166 | size = MAX_PDU_SIZE; 167 | 168 | LOG_DEBUG (MB_TCP_LOG, "Receiving %d bytes data\n", (unsigned)size); 169 | result = os_tcp_recv (peer, transaction->data, size); 170 | } 171 | 172 | if (result <= 0) 173 | { 174 | /* Peer closed their connection or some other error. Drop 175 | message, close connection. */ 176 | LOG_INFO (MB_TCP_LOG, "Connection closed\n"); 177 | os_tcp_close (peer); 178 | mb_tcp->is_down = true; 179 | return EFRAME_NOK; 180 | } 181 | 182 | /* Drop message if protocol field invalid */ 183 | if (mbap->protocol != 0) 184 | { 185 | return EFRAME_NOK; 186 | } 187 | 188 | transaction->id = CC_FROM_BE16 (mbap->id); 189 | transaction->unit = mbap->unit; 190 | 191 | return (int)size; 192 | } 193 | 194 | static bool mb_tcp_rx_is_bc (mb_transport_t * transport) 195 | { 196 | /* No broadcasts in Modbus/TCP */ 197 | return false; 198 | } 199 | 200 | static bool mb_tcp_rx_avail (mb_transport_t * transport) 201 | { 202 | /* This function is used to avoid sending a reply if another 203 | request has been received. In the TCP case the transaction ID is 204 | used to differentiate between replies so we can always send the 205 | reply. */ 206 | return false; 207 | } 208 | 209 | mb_transport_t * mb_tcp_init (const mb_tcp_cfg_t * cfg) 210 | { 211 | mb_tcp_t * mb_tcp; 212 | 213 | /* Allocate and initialise driver structure */ 214 | 215 | mb_tcp = malloc (sizeof (mb_tcp_t)); 216 | CC_ASSERT (mb_tcp != NULL); 217 | 218 | mb_tcp->transport.bringup = mb_tcp_bringup; 219 | mb_tcp->transport.shutdown = mb_tcp_shutdown; 220 | mb_tcp->transport.is_down = mb_tcp_is_down; 221 | mb_tcp->transport.tx = mb_tcp_tx; 222 | mb_tcp->transport.rx = mb_tcp_rx; 223 | mb_tcp->transport.rx_is_bc = mb_tcp_rx_is_bc; 224 | mb_tcp->transport.rx_avail = mb_tcp_rx_avail; 225 | 226 | mb_tcp->is_down = true; 227 | mb_tcp->port = cfg->port; 228 | 229 | return (mb_transport_t *)mb_tcp; 230 | } 231 | -------------------------------------------------------------------------------- /src/mb_transport.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2015 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mb_transport.h" 17 | 18 | int mb_transport_bringup (mb_transport_t * transport, const char * name) 19 | { 20 | return transport->bringup (transport, name); 21 | } 22 | 23 | int mb_transport_shutdown (mb_transport_t * transport, int arg) 24 | { 25 | return transport->shutdown (transport, arg); 26 | } 27 | 28 | bool mb_transport_is_down (mb_transport_t * transport) 29 | { 30 | return transport->is_down (transport); 31 | } 32 | 33 | void mb_pdu_tx ( 34 | mb_transport_t * transport, 35 | const pdu_txn_t * transaction, 36 | size_t size) 37 | { 38 | transport->tx (transport, transaction, size); 39 | } 40 | 41 | int mb_pdu_rx ( 42 | mb_transport_t * transport, 43 | pdu_txn_t * transaction, 44 | uint32_t tmo) 45 | { 46 | return transport->rx (transport, transaction, tmo); 47 | } 48 | 49 | bool mb_pdu_rx_bc (mb_transport_t * transport) 50 | { 51 | return transport->rx_is_bc (transport); 52 | } 53 | 54 | bool mb_pdu_rx_avail (mb_transport_t * transport) 55 | { 56 | return transport->rx_avail (transport); 57 | } 58 | -------------------------------------------------------------------------------- /src/mbal_rtu.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2012 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | /** 17 | * Modbus RTU platform abstraction layer 18 | */ 19 | 20 | #ifndef MBAL_RTU_H 21 | #define MBAL_RTU_H 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | #include "mbal_sys.h" 28 | #include "mb_rtu.h" 29 | 30 | /** 31 | * Inform M-Bus stack that a byte is available in Rx FIFO 32 | * 33 | * Implemented by the M-Bus stack. May be called by port-specific implementation 34 | * in mbal_rtu.c when at least one received byte is available. 35 | * 36 | * This function will also restart the t1p5 and t3p5 timers. 37 | * 38 | * \param arg Pointer to RTU instance, as returned from mb_rtu_init() 39 | * \param data Unused parameter. May be NULL 40 | * \return Always 0 41 | */ 42 | int mb_rx_hook (void * arg, void * data); 43 | 44 | /** 45 | * Inform M-Bus stack that no more bytes are available in Tx FIFO 46 | * 47 | * Implemented by the M-Bus stack. May be called by port-specific implementation 48 | * in mbal_rtu.c when Tx FIFO is empty. 49 | * 50 | * \param arg Pointer to RTU instance, as returned from mb_rtu_init() 51 | * \param data Unused parameter. May be NULL 52 | * \return Always 0 53 | */ 54 | int mb_tx_hook (void * arg, void * data); 55 | 56 | /** 57 | * Write up to \a size bytes to Tx FIFO 58 | * 59 | * \param fd File descriptor for serial port 60 | * \param buffer Buffer with data to be written 61 | * \param size Size of buffer in bytes 62 | * \return Number of bytes written if successful, 63 | * 0 or negative number on failure 64 | * 65 | */ 66 | ssize_t os_rtu_write (int fd, const void * buffer, size_t size); 67 | 68 | /** 69 | * Read up to \a size bytes from Rx FIFO into \a buffer 70 | * 71 | * \param fd File descriptor for serial port 72 | * \param buffer Buffer to store data 73 | * \param size Size of buffer in bytes. May be 0 74 | * \return Number of bytes read into buffer if successful, 75 | * 0 or negative number on failure 76 | */ 77 | ssize_t os_rtu_read (int fd, void * buffer, size_t size); 78 | 79 | /** 80 | * Drain Tx FIFO 81 | * 82 | * \param fd File descriptor for serial port 83 | * \param size Number of bytes that have been written to Tx FIFO 84 | */ 85 | void os_rtu_tx_drain (int fd, size_t size); 86 | 87 | /** 88 | * Get number of bytes in Rx FIFO 89 | * 90 | * \param fd File descriptor for serial port 91 | * \return Number of bytes available for reading (0 or more), 92 | * negative number on failure 93 | */ 94 | ssize_t os_rtu_rx_avail (int fd); 95 | 96 | /** 97 | * Configure serial port 98 | * 99 | * \param fd File descriptor for serial port 100 | * \param cfg Configuration (baudrate, parity) 101 | */ 102 | void os_rtu_set_serial_cfg (int fd, const mb_rtu_serial_cfg_t * cfg); 103 | 104 | /** 105 | * Open serial port 106 | * 107 | * \param name Name of serial port 108 | * \param arg Pointer to RTU instance, as returned from mb_rtu_init() 109 | * \return File descriptor for serial port on success, 110 | * negative number on failure 111 | */ 112 | int os_rtu_open (const char * name, void * arg); 113 | 114 | #ifdef __cplusplus 115 | } 116 | #endif 117 | 118 | #endif /* MBAL_RTU_H */ 119 | -------------------------------------------------------------------------------- /src/mbal_tcp.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2015 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | /** 17 | * Modbus TCP platform abstraction layer 18 | */ 19 | 20 | #ifndef MBAL_TCP_H 21 | #define MBAL_TCP_H 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | #include "mbal_sys.h" 28 | #include "mb_transport.h" 29 | 30 | /** 31 | * Connect to remote server 32 | * 33 | * Creates a socket and attempts to establish a TCP connection with remote 34 | * server. 35 | * 36 | * Only used by Modbus master 37 | * 38 | * \param name Remote server IP address, as string 39 | * \param port Remote server port 40 | * \return Socket descriptor for TCP connection if successful, 41 | * negative number otherwise 42 | */ 43 | int os_tcp_connect (const char * name, uint16_t port); 44 | 45 | /** 46 | * Wait for connection from remote client 47 | * 48 | * Listens for incoming TCP connections. Accepts the first one and returns 49 | * socket descriptor for that connection. 50 | * 51 | * Only used by Modbus slave 52 | * 53 | * \param port Local server port 54 | * \return Socket descriptor for TCP connection if successful, 55 | * negative number otherwise 56 | */ 57 | int os_tcp_accept_connection (uint16_t port); 58 | 59 | /** 60 | * Close TCP connection 61 | * 62 | * \param peer Socket descriptor for TCP connection 63 | */ 64 | void os_tcp_close (int peer); 65 | 66 | /** 67 | * Send data to connected TCP peer 68 | * 69 | * An attempt will be made to send all of the data in \a buffer. 70 | * 71 | * \param peer Socket descriptor for TCP connection 72 | * \param buffer Data to send 73 | * \param size Size of \a buffer in bytes 74 | * \returns Same as \a size if successful, 75 | * 0 or negative number otherwise 76 | */ 77 | int os_tcp_send (int peer, const void * buffer, size_t size); 78 | 79 | /** 80 | * Receive data from connected TCP peer 81 | * 82 | * Waits for all requested data to be received and then stores it in \a buffer. 83 | * 84 | * \param peer Socket descriptor for TCP connection 85 | * \param buffer Buffer to store received data 86 | * \param size Size of \a buffer in bytes 87 | * \returns Same as \a size if successful, 88 | * 0 or negative number otherwise 89 | */ 90 | int os_tcp_recv (int peer, void * buffer, size_t size); 91 | 92 | /** 93 | * Wait for received data from connected TCP peer 94 | * 95 | * Waits for any data to be available for reading without reading it. 96 | * 97 | * \param peer Socket descriptor for TCP connection 98 | * \param tmo Timeout in milliseconds 99 | * \returns Positive number if successful, 100 | * 0 or negative number otherwise 101 | */ 102 | int os_tcp_recv_wait (int peer, uint32_t tmo); 103 | 104 | #ifdef __cplusplus 105 | } 106 | #endif 107 | 108 | #endif /* MBAL_TCP_H */ 109 | -------------------------------------------------------------------------------- /src/mbus.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2011 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #ifdef UNIT_TEST 17 | #define mb_pdu_tx mock_mb_pdu_tx 18 | #define mb_pdu_rx mock_mb_pdu_rx 19 | #define mb_transport_bringup mock_mb_transport_bringup 20 | #endif 21 | 22 | #include "mbus.h" 23 | #include "mb_pdu.h" 24 | #include "mb_crc.h" 25 | #include "osal.h" 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | static int mb_is_exception (pdu_response_t * pdu) 33 | { 34 | return pdu->function & BIT (7); 35 | } 36 | 37 | static int mb_exception (pdu_exception_t * pdu) 38 | { 39 | switch (pdu->code) 40 | { 41 | case 1: 42 | return EILLEGAL_FUNCTION; 43 | case 2: 44 | return EILLEGAL_DATA_ADDRESS; 45 | case 3: 46 | return EILLEGAL_DATA_VALUE; 47 | case 4: 48 | return ESLAVE_DEVICE_FAILURE; 49 | default: 50 | return EUNKNOWN_EXCEPTION; 51 | } 52 | } 53 | 54 | int mbus_read ( 55 | mbus_t * mbus, 56 | int slave, 57 | mb_address_t address, 58 | uint16_t quantity, 59 | void * buffer) 60 | { 61 | pdu_txn_t * transaction = &mbus->transaction; 62 | pdu_read_t * request = mbus->scratch; 63 | void * response = mbus->scratch; 64 | int rx_count = 0; 65 | int result; 66 | int i; 67 | 68 | if (slave == 0) 69 | { 70 | /* Broadcast read is not possible */ 71 | return -1; 72 | } 73 | 74 | /* Build request */ 75 | switch (address >> 16) 76 | { 77 | case 0: 78 | if (quantity > 2000) 79 | { 80 | return -1; 81 | } 82 | request->function = PDU_READ_COILS; 83 | break; 84 | case 1: 85 | if (quantity > 2000) 86 | { 87 | return -1; 88 | } 89 | request->function = PDU_READ_INPUTS; 90 | break; 91 | case 3: 92 | if (quantity > 125) 93 | { 94 | return -1; 95 | } 96 | request->function = PDU_READ_INPUT_REGISTERS; 97 | break; 98 | case 4: 99 | if (quantity > 125) 100 | { 101 | return -1; 102 | } 103 | request->function = PDU_READ_HOLDING_REGISTERS; 104 | break; 105 | default: 106 | return -1; 107 | } 108 | request->address = CC_TO_BE16 ((address - 1) & 0xFFFF); 109 | request->quantity = CC_TO_BE16 (quantity); 110 | 111 | transaction->arg = slave; /* ? */ 112 | transaction->data = mbus->scratch; 113 | transaction->unit = slave; 114 | transaction->id++; 115 | 116 | /* Send request, receive response */ 117 | mb_pdu_tx (mbus->transport, transaction, sizeof (*request)); 118 | rx_count = mb_pdu_rx (mbus->transport, transaction, mbus->timeout); 119 | 120 | if (rx_count < 0) 121 | { 122 | result = rx_count; 123 | } 124 | else if (mb_is_exception (response)) 125 | { 126 | result = mb_exception (response); 127 | } 128 | else 129 | { 130 | pdu_read_response_t * read_response = response; 131 | switch (read_response->function) 132 | { 133 | case PDU_READ_COILS: /* Fall-through */ 134 | case PDU_READ_INPUTS: 135 | memcpy (buffer, read_response->data, read_response->count); 136 | result = 0; 137 | break; 138 | case PDU_READ_INPUT_REGISTERS: /* Fall-through */ 139 | case PDU_READ_HOLDING_REGISTERS: 140 | for (i = 0; i < read_response->count; i += 2) 141 | { 142 | ((uint8_t *)buffer)[i + 1] = read_response->data[i]; 143 | ((uint8_t *)buffer)[i] = read_response->data[i + 1]; 144 | } 145 | result = 0; 146 | break; 147 | default: 148 | result = -1; 149 | break; 150 | } 151 | } 152 | 153 | return result; 154 | } 155 | 156 | int mbus_write_single ( 157 | mbus_t * mbus, 158 | int slave, 159 | mb_address_t address, 160 | uint16_t value) 161 | { 162 | pdu_txn_t * transaction = &mbus->transaction; 163 | pdu_write_single_t * request = mbus->scratch; 164 | void * response = mbus->scratch; 165 | int rx_count = 0; 166 | int result; 167 | 168 | /* Build request */ 169 | switch (address >> 16) 170 | { 171 | case 0: 172 | request->function = PDU_WRITE_COIL; 173 | value = (value != 0) ? 0xFF00 : 0x0000; 174 | break; 175 | case 4: 176 | request->function = PDU_WRITE_HOLDING_REGISTER; 177 | break; 178 | default: 179 | return -1; 180 | } 181 | request->address = CC_TO_BE16 ((address - 1) & 0xFFFF); 182 | request->value = CC_TO_BE16 (value); 183 | 184 | transaction->arg = slave; /* ? */ 185 | transaction->data = mbus->scratch; 186 | transaction->unit = slave; 187 | transaction->id++; 188 | 189 | /* Send request, receive response */ 190 | mb_pdu_tx (mbus->transport, transaction, sizeof (*request)); 191 | 192 | if (slave != 0) 193 | { 194 | rx_count = mb_pdu_rx (mbus->transport, transaction, mbus->timeout); 195 | } 196 | 197 | if (rx_count < 0) 198 | { 199 | result = rx_count; 200 | } 201 | else if (mb_is_exception (response)) 202 | { 203 | result = mb_exception (response); 204 | } 205 | else 206 | { 207 | result = 0; 208 | } 209 | 210 | return result; 211 | } 212 | 213 | int mbus_write ( 214 | mbus_t * mbus, 215 | int slave, 216 | mb_address_t address, 217 | uint16_t quantity, 218 | const void * buffer) 219 | { 220 | pdu_txn_t * transaction = &mbus->transaction; 221 | pdu_write_t * request = mbus->scratch; 222 | void * response = mbus->scratch; 223 | int rx_count = 0; 224 | int result; 225 | uint8_t count = 0; 226 | int i; 227 | 228 | /* Build request */ 229 | switch (address >> 16) 230 | { 231 | case 0: 232 | if (quantity > 1968) 233 | { 234 | return -1; 235 | } 236 | request->function = PDU_WRITE_COILS; 237 | count = quantity / 8; 238 | if (quantity % 8 != 0) 239 | { 240 | count++; 241 | } 242 | memcpy (request->data, buffer, count); 243 | break; 244 | case 4: 245 | if (quantity > 123) 246 | { 247 | return -1; 248 | } 249 | request->function = PDU_WRITE_HOLDING_REGISTERS; 250 | count = 2 * quantity; 251 | for (i = 0; i < count; i += 2) 252 | { 253 | request->data[i] = ((uint8_t *)buffer)[i + 1]; 254 | request->data[i + 1] = ((uint8_t *)buffer)[i]; 255 | } 256 | break; 257 | default: 258 | return -1; 259 | } 260 | request->address = CC_TO_BE16 ((address - 1) & 0xFFFF); 261 | request->quantity = CC_TO_BE16 (quantity); 262 | request->count = count; 263 | 264 | transaction->arg = slave; /* ? */ 265 | transaction->data = mbus->scratch; 266 | transaction->unit = slave; 267 | transaction->id++; 268 | 269 | /* Send request, receive response */ 270 | mb_pdu_tx (mbus->transport, transaction, (sizeof (pdu_write_t) + count)); 271 | 272 | if (slave != 0) 273 | { 274 | rx_count = mb_pdu_rx (mbus->transport, transaction, mbus->timeout); 275 | } 276 | 277 | if (rx_count < 0) 278 | { 279 | result = rx_count; 280 | } 281 | else if (mb_is_exception (response)) 282 | { 283 | result = mb_exception (response); 284 | } 285 | else 286 | { 287 | result = 0; 288 | } 289 | 290 | return result; 291 | } 292 | 293 | int mbus_loopback (mbus_t * mbus, int slave, uint16_t size, void * buffer) 294 | { 295 | pdu_txn_t * transaction = &mbus->transaction; 296 | pdu_diag_t * request = mbus->scratch; 297 | void * response = mbus->scratch; 298 | int rx_count = 0; 299 | int result; 300 | 301 | if (size > 250) 302 | { 303 | return -1; 304 | } 305 | 306 | /* Build request */ 307 | request->function = PDU_DIAGNOSTICS; 308 | request->sub_function = PDU_DIAG_LOOPBACK; 309 | 310 | memcpy (request->data, buffer, size); 311 | 312 | transaction->arg = slave; /* ? */ 313 | transaction->data = mbus->scratch; 314 | transaction->unit = slave; 315 | transaction->id++; 316 | 317 | /* Send request, receive response */ 318 | mb_pdu_tx (mbus->transport, transaction, size + sizeof (*request)); 319 | if (slave != 0) 320 | { 321 | rx_count = mb_pdu_rx (mbus->transport, transaction, mbus->timeout); 322 | } 323 | 324 | if (rx_count < 0) 325 | { 326 | result = rx_count; 327 | } 328 | else if (mb_is_exception (response)) 329 | { 330 | result = mb_exception (response); 331 | } 332 | else 333 | { 334 | memcpy (buffer, response, (rx_count > size) ? size : rx_count); 335 | result = rx_count; 336 | } 337 | 338 | return result; 339 | } 340 | 341 | int mbus_send_msg (mbus_t * mbus, int slave, const void * msg, uint8_t size) 342 | { 343 | pdu_txn_t * transaction = &mbus->transaction; 344 | 345 | transaction->arg = slave; /* ? */ 346 | transaction->data = (void *)msg; 347 | transaction->unit = slave; 348 | transaction->id++; 349 | 350 | mb_pdu_tx (mbus->transport, transaction, size); 351 | 352 | return 0; 353 | } 354 | 355 | int mbus_get_msg (mbus_t * mbus, int slave, void * msg, uint16_t size) 356 | { 357 | pdu_txn_t * transaction = &mbus->transaction; 358 | int rx_count; 359 | 360 | transaction->arg = slave; /* ? */ 361 | transaction->data = msg; 362 | transaction->unit = slave; 363 | 364 | rx_count = mb_pdu_rx (mbus->transport, transaction, mbus->timeout); 365 | 366 | return rx_count; 367 | } 368 | 369 | void * mbus_transport_get (mbus_t * mbus) 370 | { 371 | return mbus->transport; 372 | } 373 | 374 | int mbus_connect (mbus_t * mbus, const char * name) 375 | { 376 | return mb_transport_bringup (mbus->transport, name); 377 | } 378 | 379 | int mbus_disconnect (mbus_t * mbus, int slave) 380 | { 381 | return mb_transport_shutdown (mbus->transport, slave); 382 | } 383 | 384 | void mbus_init ( 385 | mbus_t * mbus, 386 | const mbus_cfg_t * cfg, 387 | mb_transport_t * transport, 388 | uint8_t * scratch) 389 | { 390 | mbus->timeout = cfg->timeout; 391 | mbus->transaction.id = 0; 392 | mbus->scratch = scratch; 393 | 394 | memset (mbus->scratch, 0x55, MAX_PDU_SIZE); 395 | 396 | /* Set transport layer */ 397 | mbus->transport = transport; 398 | transport->is_server = false; 399 | } 400 | 401 | mbus_t * mbus_create (const mbus_cfg_t * cfg, mb_transport_t * transport) 402 | { 403 | mbus_t * mbus; 404 | uint8_t * scratch; 405 | 406 | /* Allocate and initialise driver structure */ 407 | mbus = malloc (sizeof (mbus_t)); 408 | CC_ASSERT (mbus != NULL); 409 | 410 | /* Allocate scratch buffer */ 411 | scratch = malloc (MAX_PDU_SIZE); 412 | CC_ASSERT (scratch != NULL); 413 | 414 | mbus_init (mbus, cfg, transport, scratch); 415 | return mbus; 416 | } 417 | -------------------------------------------------------------------------------- /src/ports/linux/mb-tp.c: -------------------------------------------------------------------------------- 1 | #define TRACEPOINT_CREATE_PROBES 2 | #define TRACEPOINT_DEFINE 3 | 4 | #include "mb-tp.h" 5 | -------------------------------------------------------------------------------- /src/ports/linux/mb-tp.h: -------------------------------------------------------------------------------- 1 | #undef TRACEPOINT_PROVIDER 2 | #define TRACEPOINT_PROVIDER mb 3 | 4 | #undef TRACEPOINT_INCLUDE 5 | #define TRACEPOINT_INCLUDE "./mb-tp.h" 6 | 7 | #if !defined(_MB_TP_H) || defined(TRACEPOINT_HEADER_MULTI_READ) 8 | #define _MB_TP_H 9 | 10 | #include 11 | 12 | TRACEPOINT_EVENT (mb, t1p5, TP_ARGS(), TP_FIELDS()) 13 | 14 | TRACEPOINT_EVENT (mb, t3p5, TP_ARGS(), TP_FIELDS()) 15 | 16 | TRACEPOINT_EVENT (mb, rx_hook, TP_ARGS(), TP_FIELDS()) 17 | 18 | TRACEPOINT_EVENT ( 19 | mb, 20 | rx_read, 21 | TP_ARGS (int, size), 22 | TP_FIELDS (ctf_integer (int, size, size))) 23 | 24 | TRACEPOINT_EVENT ( 25 | mb, 26 | rx_trace, 27 | TP_ARGS (int, id), 28 | TP_FIELDS (ctf_integer (int, id, id))) 29 | 30 | TRACEPOINT_EVENT ( 31 | mb, 32 | tx_trace, 33 | TP_ARGS (int, id), 34 | TP_FIELDS (ctf_integer (int, id, id))) 35 | 36 | #endif /* _MB_TP_H */ 37 | 38 | #include 39 | -------------------------------------------------------------------------------- /src/ports/linux/mbal_rtu.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2012 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mbal_rtu.h" 17 | #include "options.h" 18 | 19 | #include "osal.h" 20 | #include "osal_log.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | static void * mb_arg; 34 | static int mb_fd; 35 | 36 | ssize_t os_rtu_write (int fd, const void * buffer, size_t size) 37 | { 38 | ssize_t nwrite; 39 | nwrite = write (fd, buffer, size); 40 | return nwrite; 41 | } 42 | 43 | ssize_t os_rtu_read (int fd, void * buffer, size_t size) 44 | { 45 | ssize_t nread; 46 | nread = read (fd, buffer, size); 47 | return nread; 48 | } 49 | 50 | void os_rtu_tx_drain (int fd, size_t size) 51 | { 52 | float char_time_us; 53 | 54 | char_time_us = 11.0 * 1000 * 1000 / 115200; 55 | os_usleep ((uint32_t)char_time_us); 56 | } 57 | 58 | ssize_t os_rtu_rx_avail (int fd) 59 | { 60 | int navail = 0; 61 | if (ioctl (fd, FIONREAD, &navail) < 0) 62 | { 63 | return -1; 64 | } 65 | return navail; 66 | } 67 | 68 | void os_rtu_set_serial_cfg (int fd, const mb_rtu_serial_cfg_t * cfg) 69 | { 70 | struct termios tio; 71 | 72 | memset (&tio, 0, sizeof (tio)); 73 | 74 | /* Setup raw processing */ 75 | tio.c_cflag |= CS8 | CLOCAL | CREAD; 76 | 77 | switch (cfg->baudrate) 78 | { 79 | case 1200: 80 | tio.c_cflag |= B1200; 81 | break; 82 | case 1800: 83 | tio.c_cflag |= B1800; 84 | break; 85 | case 2400: 86 | tio.c_cflag |= B2400; 87 | break; 88 | case 4800: 89 | tio.c_cflag |= B4800; 90 | break; 91 | case 9600: 92 | tio.c_cflag |= B9600; 93 | break; 94 | case 19200: 95 | tio.c_cflag |= B19200; 96 | break; 97 | case 38400: 98 | tio.c_cflag |= B38400; 99 | break; 100 | case 57600: 101 | tio.c_cflag |= B57600; 102 | break; 103 | case 115200: 104 | tio.c_cflag |= B115200; 105 | break; 106 | case 230400: 107 | tio.c_cflag |= B230400; 108 | break; 109 | case 460800: 110 | tio.c_cflag |= B460800; 111 | break; 112 | default: 113 | tio.c_cflag |= B19200; 114 | break; 115 | } 116 | 117 | switch (cfg->parity) 118 | { 119 | case ODD: 120 | tio.c_cflag |= PARENB | PARODD; 121 | tio.c_iflag |= INPCK | ISTRIP; 122 | break; 123 | case EVEN: 124 | tio.c_cflag |= PARENB; 125 | tio.c_iflag |= INPCK | ISTRIP; 126 | break; 127 | case NONE: 128 | break; 129 | default: 130 | break; 131 | } 132 | 133 | tio.c_cc[VMIN] = 1; 134 | tio.c_cc[VTIME] = 0; 135 | 136 | tcflush (fd, TCIFLUSH); 137 | tcsetattr (fd, TCSANOW, &tio); 138 | } 139 | 140 | static void os_rtu_rx (void * arg) 141 | { 142 | struct epoll_event ev, events[1]; 143 | int epollfd; 144 | int nfds; 145 | int n; 146 | 147 | epollfd = epoll_create1 (0); 148 | if (epollfd == -1) 149 | { 150 | LOG_ERROR (MB_RTU_LOG, "epoll_create1 failed\n"); 151 | return; 152 | } 153 | 154 | /* Create edge-triggered event on input */ 155 | ev.events = EPOLLIN | EPOLLET; 156 | ev.data.fd = mb_fd; 157 | if (epoll_ctl (epollfd, EPOLL_CTL_ADD, mb_fd, &ev) == -1) 158 | { 159 | LOG_ERROR (MB_RTU_LOG, "epoll_ctl failed\n"); 160 | return; 161 | } 162 | 163 | for (;;) 164 | { 165 | nfds = epoll_wait (epollfd, events, 1, -1); 166 | if (nfds == -1) 167 | { 168 | if (errno == EINTR) 169 | continue; 170 | 171 | LOG_ERROR (MB_RTU_LOG, "epoll_wait failed\n"); 172 | return; 173 | } 174 | 175 | for (n = 0; n < nfds; n++) 176 | { 177 | if (events[n].data.fd == mb_fd) 178 | { 179 | mb_rx_hook (mb_arg, NULL); 180 | } 181 | } 182 | } 183 | } 184 | 185 | int os_rtu_open (const char * name, void * arg) 186 | { 187 | int fd; 188 | 189 | mb_arg = arg; 190 | 191 | fd = open (name, O_RDWR | O_NOCTTY | O_NONBLOCK, 0); 192 | assert (fd != -1); 193 | mb_fd = fd; 194 | 195 | os_thread_create ("mb_rtu_rx", 5, 1024, os_rtu_rx, arg); 196 | return fd; 197 | } 198 | -------------------------------------------------------------------------------- /src/ports/linux/mbal_sys.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2021 rt-labs AB, Sweden. 10 | * See LICENSE file in the project root for full license information. 11 | ********************************************************************/ 12 | 13 | #ifndef MBAL_SYS_H 14 | #define MBAL_SYS_H 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif 19 | 20 | #include 21 | 22 | #ifdef __cplusplus 23 | } 24 | #endif 25 | 26 | #endif /* MBAL_SYS_H */ 27 | -------------------------------------------------------------------------------- /src/ports/linux/mbal_tcp.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2015 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mbal_tcp.h" 17 | #include "mb_tcp.h" 18 | #include "osal.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | #define PERROR(s) perror ("modbus: "s) 31 | 32 | #define KEEP_ALIVE_IDLE 10 /* max idle time before keepalive sent [s] */ 33 | #define KEEP_ALIVE_INTVL 2 /* time between keepalives [s] */ 34 | #define KEEP_ALIVE_CNT 3 /* max number of unacked keepalives */ 35 | 36 | /* max time to wait for message in progress [ms] */ 37 | #define RCV_TIMEOUT 500 38 | 39 | int os_tcp_connect (const char * name, uint16_t port) 40 | { 41 | int result; 42 | int sock; 43 | int option; 44 | struct sockaddr_in addr; 45 | 46 | /* Create socket */ 47 | sock = socket (AF_INET, SOCK_STREAM, 0); 48 | if (sock == -1) 49 | { 50 | PERROR ("socket"); 51 | return -1; 52 | } 53 | 54 | /* Set socket options (nagle, reuseaddr) */ 55 | 56 | option = 1; 57 | result = setsockopt (sock, IPPROTO_TCP, TCP_NODELAY, &option, sizeof (int)); 58 | if (result == -1) 59 | { 60 | PERROR ("TCP_NODELAY"); 61 | goto error; 62 | } 63 | 64 | option = 1; 65 | result = setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &option, sizeof (int)); 66 | if (result == -1) 67 | { 68 | PERROR ("SO_REUSEADDR"); 69 | goto error; 70 | } 71 | 72 | addr.sin_family = AF_INET; 73 | addr.sin_addr.s_addr = inet_addr (name); 74 | addr.sin_port = htons (port); 75 | 76 | /* Connect to peer */ 77 | result = connect (sock, (struct sockaddr *)&addr, sizeof (addr)); 78 | if (result == -1) 79 | { 80 | goto error; 81 | } 82 | 83 | return sock; 84 | 85 | error: 86 | close (sock); 87 | return -1; 88 | } 89 | 90 | int os_tcp_accept_connection (uint16_t port) 91 | { 92 | int result; 93 | int sock; 94 | struct sockaddr_in addr; 95 | struct timeval tv; 96 | int option; 97 | int peer; 98 | 99 | /* Create listening socket */ 100 | sock = socket (AF_INET, SOCK_STREAM, 0); 101 | if (sock == -1) 102 | { 103 | PERROR ("socket"); 104 | return -1; 105 | } 106 | 107 | memset (&addr, 0, sizeof (addr)); 108 | 109 | addr.sin_family = AF_INET; 110 | addr.sin_addr.s_addr = htonl (INADDR_ANY); 111 | addr.sin_port = htons (port); 112 | 113 | option = 1; /* enable */ 114 | result = setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &option, sizeof (int)); 115 | if (result == -1) 116 | { 117 | PERROR ("SO_REUSEADDR"); 118 | goto error1; 119 | } 120 | 121 | tv.tv_sec = 0; 122 | tv.tv_usec = RCV_TIMEOUT * 1000; 123 | result = setsockopt (sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof (tv)); 124 | if (result == -1) 125 | { 126 | PERROR ("SO_RCVTIMEO"); 127 | goto error1; 128 | } 129 | 130 | result = bind (sock, (struct sockaddr *)&addr, sizeof (addr)); 131 | if (result == -1) 132 | { 133 | PERROR ("bind"); 134 | goto error1; 135 | } 136 | 137 | result = listen (sock, 1); 138 | if (result == -1) 139 | { 140 | PERROR ("listen"); 141 | goto error1; 142 | } 143 | 144 | /* Accept one connection */ 145 | peer = accept (sock, NULL, NULL); 146 | if (peer == -1) 147 | { 148 | /* Error or timeout */ 149 | goto error1; 150 | } 151 | 152 | /* Set peer socket options (nagle, reuseaddr, receive timeout, 153 | keepalive). */ 154 | 155 | option = 1; 156 | result = setsockopt (peer, IPPROTO_TCP, TCP_NODELAY, &option, sizeof (int)); 157 | if (result == -1) 158 | { 159 | PERROR ("TCP_NODELAY"); 160 | goto error2; 161 | } 162 | 163 | option = 1; /* enable */ 164 | result = setsockopt (peer, SOL_SOCKET, SO_REUSEADDR, &option, sizeof (int)); 165 | if (result == -1) 166 | { 167 | PERROR ("SO_REUSEADDR"); 168 | goto error2; 169 | } 170 | 171 | tv.tv_sec = 0; 172 | tv.tv_usec = RCV_TIMEOUT * 1000; 173 | result = setsockopt (peer, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof (tv)); 174 | if (result == -1) 175 | { 176 | PERROR ("SO_RCVTIMEO"); 177 | goto error2; 178 | } 179 | 180 | option = 1; 181 | result = setsockopt (peer, SOL_SOCKET, SO_KEEPALIVE, &option, sizeof (int)); 182 | if (result == -1) 183 | { 184 | PERROR ("SO_KEEPALIVE"); 185 | goto error2; 186 | } 187 | 188 | option = KEEP_ALIVE_IDLE; 189 | result = setsockopt (peer, IPPROTO_TCP, TCP_KEEPIDLE, &option, sizeof (int)); 190 | if (result == -1) 191 | { 192 | PERROR ("TCP_KEEPALIVE"); 193 | goto error2; 194 | } 195 | 196 | option = KEEP_ALIVE_INTVL; 197 | result = 198 | setsockopt (peer, IPPROTO_TCP, TCP_KEEPINTVL, &option, sizeof (int)); 199 | if (result == -1) 200 | { 201 | PERROR ("TCP_KEEPINTVL"); 202 | goto error2; 203 | } 204 | 205 | option = KEEP_ALIVE_CNT; 206 | result = setsockopt (peer, IPPROTO_TCP, TCP_KEEPCNT, &option, sizeof (int)); 207 | if (result == -1) 208 | { 209 | PERROR ("TCP_KEEPCNT"); 210 | goto error2; 211 | } 212 | 213 | /* Close listening socket. No more connections accepted. */ 214 | close (sock); 215 | return peer; 216 | 217 | error2: 218 | close (peer); 219 | error1: 220 | close (sock); 221 | return -1; 222 | } 223 | 224 | void os_tcp_close (int peer) 225 | { 226 | close (peer); 227 | } 228 | 229 | int os_tcp_send (int peer, const void * buffer, size_t size) 230 | { 231 | const uint8_t * p = buffer; 232 | size_t remain = size; 233 | int n; 234 | 235 | do 236 | { 237 | n = send (peer, p, remain, 0); 238 | remain -= n; 239 | p += n; 240 | } while (remain > 0 && n > 0); 241 | 242 | if (remain == 0) 243 | { 244 | /* Successfully sent all data */ 245 | return size; 246 | } 247 | 248 | /* Connection closed or error sending */ 249 | return n; 250 | } 251 | 252 | int os_tcp_recv (int peer, void * buffer, size_t size) 253 | { 254 | uint8_t * p = buffer; 255 | size_t remain = size; 256 | int n; 257 | 258 | do 259 | { 260 | n = recv (peer, p, remain, 0); 261 | remain -= n; 262 | p += n; 263 | } while (remain > 0 && n > 0); 264 | 265 | if (remain == 0) 266 | { 267 | /* Successfully received all data */ 268 | return size; 269 | } 270 | 271 | /* Connection closed or error receiving */ 272 | return n; 273 | } 274 | 275 | int os_tcp_recv_wait (int peer, uint32_t tmo) 276 | { 277 | fd_set fds; 278 | int result; 279 | struct timeval tv; 280 | 281 | FD_ZERO (&fds); 282 | FD_SET (peer, &fds); 283 | 284 | tv.tv_sec = 0; 285 | tv.tv_usec = tmo * 1000; 286 | result = select (peer + 1, &fds, NULL, NULL, &tv); 287 | return result; 288 | } 289 | -------------------------------------------------------------------------------- /src/ports/rt-kernel/mb_master.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2012 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mb_master.h" 17 | #include "mb_pdu.h" 18 | 19 | #include 20 | #include 21 | 22 | typedef struct mb_drv 23 | { 24 | drv_t drv; 25 | mbus_t mbus; 26 | } mb_drv_t; 27 | 28 | static int _mb_open (drv_t * drv, const char * name, int flags, int mode) 29 | { 30 | mb_drv_t * mb_drv = (mb_drv_t *)drv; 31 | return mb_transport_bringup (mb_drv->mbus.transport, name); 32 | } 33 | 34 | static int _mb_close (drv_t * drv, void * arg) 35 | { 36 | mb_drv_t * mb_drv = (mb_drv_t *)drv; 37 | return mb_transport_shutdown (mb_drv->mbus.transport, (int)arg); 38 | } 39 | 40 | int mb_read (int fd, mb_address_t address, uint16_t quantity, void * buffer) 41 | { 42 | drv_t * drv = fd_get_driver (fd); 43 | mb_drv_t * mb_drv = (mb_drv_t *)drv; 44 | int slave = (int)fd_get_arg (fd); 45 | int result; 46 | 47 | mtx_lock (drv->mtx); 48 | result = mbus_read (&mb_drv->mbus, slave, address, quantity, buffer); 49 | mtx_unlock (drv->mtx); 50 | 51 | return result; 52 | } 53 | 54 | int mb_write (int fd, mb_address_t address, uint16_t quantity, void * buffer) 55 | { 56 | drv_t * drv = fd_get_driver (fd); 57 | mb_drv_t * mb_drv = (mb_drv_t *)drv; 58 | int slave = (int)fd_get_arg (fd); 59 | int result; 60 | 61 | mtx_lock (drv->mtx); 62 | result = mbus_write (&mb_drv->mbus, slave, address, quantity, buffer); 63 | mtx_unlock (drv->mtx); 64 | 65 | return result; 66 | } 67 | 68 | int mb_write_single (int fd, mb_address_t address, uint16_t value) 69 | { 70 | drv_t * drv = fd_get_driver (fd); 71 | mb_drv_t * mb_drv = (mb_drv_t *)drv; 72 | int slave = (int)fd_get_arg (fd); 73 | int result; 74 | 75 | mtx_lock (drv->mtx); 76 | result = mbus_write_single (&mb_drv->mbus, slave, address, value); 77 | mtx_unlock (drv->mtx); 78 | 79 | return result; 80 | } 81 | 82 | int mb_loopback (int fd, uint16_t size, void * buffer) 83 | { 84 | drv_t * drv = fd_get_driver (fd); 85 | mb_drv_t * mb_drv = (mb_drv_t *)drv; 86 | int slave = (int)fd_get_arg (fd); 87 | int result; 88 | 89 | mtx_lock (drv->mtx); 90 | result = mbus_loopback (&mb_drv->mbus, slave, size, buffer); 91 | mtx_unlock (drv->mtx); 92 | 93 | return result; 94 | } 95 | 96 | int mb_send_msg (int fd, const void * msg, uint8_t size) 97 | { 98 | drv_t * drv = fd_get_driver (fd); 99 | mb_drv_t * mb_drv = (mb_drv_t *)drv; 100 | int slave = (int)fd_get_arg (fd); 101 | int result; 102 | 103 | mtx_lock (drv->mtx); 104 | result = mbus_send_msg (&mb_drv->mbus, slave, msg, size); 105 | mtx_unlock (drv->mtx); 106 | 107 | return result; 108 | } 109 | 110 | int mb_get_msg (int fd, void * msg, uint16_t size) 111 | { 112 | drv_t * drv = fd_get_driver (fd); 113 | mb_drv_t * mb_drv = (mb_drv_t *)drv; 114 | int slave = (int)fd_get_arg (fd); 115 | int result; 116 | 117 | mtx_lock (drv->mtx); 118 | result = mbus_get_msg (&mb_drv->mbus, slave, msg, size); 119 | mtx_unlock (drv->mtx); 120 | 121 | return result; 122 | } 123 | 124 | void * mb_master_transport_get (int fd) 125 | { 126 | drv_t * drv = fd_get_driver (fd); 127 | mb_drv_t * mb_drv = (mb_drv_t *)drv; 128 | return mb_drv->mbus.transport; 129 | } 130 | 131 | static const drv_ops_t mb_drv_ops = { 132 | .open = _mb_open, 133 | .read = NULL, 134 | .write = NULL, 135 | .close = _mb_close, 136 | .ioctl = NULL, 137 | .hotplug = NULL}; 138 | 139 | drv_t * mb_master_init ( 140 | const char * name, 141 | const mb_master_cfg_t * cfg, 142 | mb_transport_t * transport) 143 | { 144 | mb_drv_t * mb_drv; 145 | uint8_t * scratch; 146 | 147 | /* Allocate and initialise driver structure */ 148 | mb_drv = malloc (sizeof (mb_drv_t)); 149 | UASSERT (mb_drv != NULL, EMEM); 150 | 151 | /* Allocate scratch buffer */ 152 | scratch = malloc (MAX_PDU_SIZE); 153 | UASSERT (scratch != NULL, EMEM); 154 | 155 | mbus_init (&mb_drv->mbus, cfg, transport, scratch); 156 | 157 | mb_drv->drv.ops = &mb_drv_ops; 158 | 159 | /* Install device driver */ 160 | dev_install ((drv_t *)mb_drv, name); 161 | 162 | return (drv_t *)mb_drv; 163 | } 164 | -------------------------------------------------------------------------------- /src/ports/rt-kernel/mbal_rtu.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2012 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mbal_rtu.h" 17 | #include "options.h" 18 | 19 | #include "osal.h" 20 | #include "osal_log.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | ssize_t os_rtu_write (int fd, const void * buffer, size_t size) 30 | { 31 | ssize_t nwrite; 32 | nwrite = write (fd, buffer, size); 33 | return nwrite; 34 | } 35 | 36 | ssize_t os_rtu_read (int fd, void * buffer, size_t size) 37 | { 38 | ssize_t nread; 39 | nread = read (fd, buffer, size); 40 | return nread; 41 | } 42 | 43 | void os_rtu_tx_drain (int fd, size_t size) 44 | { 45 | int is_empty = 0; 46 | 47 | /* os_event_wait (rtu->flags, FLAG_TX_EMPTY, &flags, OS_WAIT_FOREVER); */ 48 | 49 | while (!is_empty) 50 | ioctl (fd, IOCTL_SIO_IS_TX_EMPTY, &is_empty); 51 | } 52 | 53 | ssize_t os_rtu_rx_avail (int fd) 54 | { 55 | ssize_t navail = 0; 56 | if (ioctl (fd, IOCTL_SIO_NREAD, &navail) < 0) 57 | { 58 | return -1; 59 | } 60 | return navail; 61 | } 62 | 63 | void os_rtu_set_serial_cfg (int fd, const mb_rtu_serial_cfg_t * cfg) 64 | { 65 | drv_t * drv = fd_get_driver (fd); 66 | sio_cfg_t sio_cfg; 67 | sio_parity_t parity; 68 | 69 | switch (cfg->parity) 70 | { 71 | case ODD: 72 | parity = Odd; 73 | break; 74 | case EVEN: 75 | parity = Even; 76 | break; 77 | case NONE: 78 | parity = None; 79 | break; 80 | default: 81 | parity = Odd; 82 | break; 83 | } 84 | 85 | sio_cfg.baudrate = cfg->baudrate; 86 | sio_cfg.databits = 8; 87 | sio_cfg.parity = parity; 88 | sio_cfg.stopbits = 1; 89 | 90 | sio_set_cfg (drv, &sio_cfg); 91 | } 92 | 93 | int os_rtu_open (const char * name, void * arg) 94 | { 95 | int fd; 96 | ioctl_hook_t hook; 97 | 98 | fd = open (name, O_RDWR, 0); 99 | UASSERT (fd != -1, EARG); 100 | 101 | /* Install serial hooks */ 102 | 103 | hook.func = mb_tx_hook; 104 | hook.arg = arg; 105 | ioctl (fd, IOCTL_SIO_TX_HOOK, &hook); 106 | 107 | hook.func = mb_rx_hook; 108 | hook.arg = arg; 109 | ioctl (fd, IOCTL_SIO_RX_HOOK, &hook); 110 | 111 | return fd; 112 | } 113 | -------------------------------------------------------------------------------- /src/ports/rt-kernel/mbal_sys.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2021 rt-labs AB, Sweden. 10 | * See LICENSE file in the project root for full license information. 11 | ********************************************************************/ 12 | 13 | #ifndef MBAL_SYS_H 14 | #define MBAL_SYS_H 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif 19 | 20 | #include 21 | 22 | #ifdef __cplusplus 23 | } 24 | #endif 25 | 26 | #endif /* MBAL_SYS_H */ 27 | -------------------------------------------------------------------------------- /src/ports/rt-kernel/mbal_tcp.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2015 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mbal_tcp.h" 17 | #include "mb_tcp.h" 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #if 0 24 | #define PERROR(s) perror ("modbus: "s) 25 | #else 26 | #define PERROR(s) 27 | #endif 28 | 29 | #define KEEP_ALIVE_IDLE 10 /* max idle time before keepalive sent [s] */ 30 | #define KEEP_ALIVE_INTVL 2 /* time between keepalives [s] */ 31 | #define KEEP_ALIVE_CNT 3 /* max number of unacked keepalives */ 32 | 33 | /* max time to wait for message in progress [ms] */ 34 | #define RCV_TIMEOUT 500 35 | 36 | int os_tcp_connect (const char * name, uint16_t port) 37 | { 38 | int result; 39 | int sock; 40 | int option; 41 | struct sockaddr_in addr; 42 | 43 | /* Create socket */ 44 | sock = socket (AF_INET, SOCK_STREAM, 0); 45 | if (sock == -1) 46 | { 47 | PERROR ("socket"); 48 | return -1; 49 | } 50 | 51 | /* Set socket options (nagle, reuseaddr) */ 52 | 53 | option = 1; 54 | result = setsockopt (sock, IPPROTO_TCP, TCP_NODELAY, &option, sizeof (int)); 55 | if (result == -1) 56 | { 57 | PERROR ("TCP_NODELAY"); 58 | goto error; 59 | } 60 | 61 | option = 1; 62 | result = setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &option, sizeof (int)); 63 | if (result == -1) 64 | { 65 | PERROR ("SO_REUSEADDR"); 66 | goto error; 67 | } 68 | 69 | addr.sin_family = AF_INET; 70 | addr.sin_addr.s_addr = inet_addr (name); 71 | addr.sin_port = htons (port); 72 | 73 | /* Connect to peer */ 74 | result = connect (sock, (struct sockaddr *)&addr, sizeof (addr)); 75 | if (result == -1) 76 | { 77 | goto error; 78 | } 79 | 80 | return sock; 81 | 82 | error: 83 | close (sock); 84 | return -1; 85 | } 86 | 87 | int os_tcp_accept_connection (uint16_t port) 88 | { 89 | int result; 90 | int sock; 91 | struct sockaddr_in addr; 92 | int option; 93 | int peer; 94 | 95 | /* Create listening socket */ 96 | sock = socket (AF_INET, SOCK_STREAM, 0); 97 | if (sock == -1) 98 | { 99 | PERROR ("socket"); 100 | return -1; 101 | } 102 | 103 | memset (&addr, 0, sizeof (addr)); 104 | 105 | addr.sin_family = AF_INET; 106 | addr.sin_addr.s_addr = htonl (INADDR_ANY); 107 | addr.sin_port = htons (port); 108 | 109 | option = 1; /* enable */ 110 | result = setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &option, sizeof (int)); 111 | if (result == -1) 112 | { 113 | PERROR ("SO_REUSEADDR"); 114 | goto error1; 115 | } 116 | 117 | option = RCV_TIMEOUT; 118 | result = setsockopt (sock, SOL_SOCKET, SO_RCVTIMEO, &option, sizeof (int)); 119 | if (result == -1) 120 | { 121 | PERROR ("SO_RCVTIMEO"); 122 | goto error1; 123 | } 124 | 125 | result = bind (sock, (struct sockaddr *)&addr, sizeof (addr)); 126 | if (result == -1) 127 | { 128 | PERROR ("bind"); 129 | goto error1; 130 | } 131 | 132 | result = listen (sock, 1); 133 | if (result == -1) 134 | { 135 | PERROR ("listen"); 136 | goto error1; 137 | } 138 | 139 | /* Accept one connection */ 140 | peer = accept (sock, NULL, NULL); 141 | if (peer == -1) 142 | { 143 | /* Error or timeout */ 144 | goto error1; 145 | } 146 | 147 | /* Set peer socket options (nagle, reuseaddr, receive timeout, 148 | keepalive). */ 149 | 150 | option = 1; 151 | result = setsockopt (peer, IPPROTO_TCP, TCP_NODELAY, &option, sizeof (int)); 152 | if (result == -1) 153 | { 154 | PERROR ("TCP_NODELAY"); 155 | goto error2; 156 | } 157 | 158 | option = 1; /* enable */ 159 | result = setsockopt (peer, SOL_SOCKET, SO_REUSEADDR, &option, sizeof (int)); 160 | if (result == -1) 161 | { 162 | PERROR ("SO_REUSEADDR"); 163 | goto error2; 164 | } 165 | 166 | option = RCV_TIMEOUT; 167 | result = setsockopt (peer, SOL_SOCKET, SO_RCVTIMEO, &option, sizeof (int)); 168 | if (result == -1) 169 | { 170 | PERROR ("SO_RCVTIMEO"); 171 | goto error2; 172 | } 173 | 174 | option = 1; 175 | result = setsockopt (peer, SOL_SOCKET, SO_KEEPALIVE, &option, sizeof (int)); 176 | if (result == -1) 177 | { 178 | PERROR ("SO_KEEPALIVE"); 179 | goto error2; 180 | } 181 | 182 | option = KEEP_ALIVE_IDLE; 183 | result = setsockopt (peer, IPPROTO_TCP, TCP_KEEPIDLE, &option, sizeof (int)); 184 | if (result == -1) 185 | { 186 | PERROR ("TCP_KEEPALIVE"); 187 | goto error2; 188 | } 189 | 190 | option = KEEP_ALIVE_INTVL; 191 | result = 192 | setsockopt (peer, IPPROTO_TCP, TCP_KEEPINTVL, &option, sizeof (int)); 193 | if (result == -1) 194 | { 195 | PERROR ("TCP_KEEPINTVL"); 196 | goto error2; 197 | } 198 | 199 | option = KEEP_ALIVE_CNT; 200 | result = setsockopt (peer, IPPROTO_TCP, TCP_KEEPCNT, &option, sizeof (int)); 201 | if (result == -1) 202 | { 203 | PERROR ("TCP_KEEPCNT"); 204 | goto error2; 205 | } 206 | 207 | /* Close listening socket. No more connections accepted. */ 208 | close (sock); 209 | return peer; 210 | 211 | error2: 212 | close (peer); 213 | error1: 214 | close (sock); 215 | return -1; 216 | } 217 | 218 | void os_tcp_close (int peer) 219 | { 220 | close (peer); 221 | } 222 | 223 | int os_tcp_send (int peer, const void * buffer, size_t size) 224 | { 225 | const uint8_t * p = buffer; 226 | size_t remain = size; 227 | int n; 228 | 229 | do 230 | { 231 | n = send (peer, p, remain, 0); 232 | remain -= n; 233 | p += n; 234 | } while (remain > 0 && n > 0); 235 | 236 | if (remain == 0) 237 | { 238 | /* Successfully sent all data */ 239 | return size; 240 | } 241 | 242 | /* Connection closed or error sending */ 243 | return n; 244 | } 245 | 246 | int os_tcp_recv (int peer, void * buffer, size_t size) 247 | { 248 | uint8_t * p = buffer; 249 | size_t remain = size; 250 | int n; 251 | 252 | do 253 | { 254 | n = recv (peer, p, remain, 0); 255 | remain -= n; 256 | p += n; 257 | } while (remain > 0 && n > 0); 258 | 259 | if (remain == 0) 260 | { 261 | /* Successfully received all data */ 262 | return size; 263 | } 264 | 265 | /* Connection closed or error receiving */ 266 | return n; 267 | } 268 | 269 | int os_tcp_recv_wait (int peer, uint32_t tmo) 270 | { 271 | fd_set fds; 272 | int result; 273 | struct timeval tv; 274 | 275 | FD_ZERO (&fds); 276 | FD_SET (peer, &fds); 277 | 278 | tv.tv_sec = 0; 279 | tv.tv_usec = tmo * 1000; 280 | result = select (peer + 1, &fds, NULL, NULL, &tv); 281 | return result; 282 | } 283 | -------------------------------------------------------------------------------- /src/ports/windows/mbal_rtu.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2012 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mbal_rtu.h" 17 | 18 | ssize_t os_rtu_write (int fd, const void * buffer, size_t size) 19 | { 20 | return -1; 21 | } 22 | 23 | ssize_t os_rtu_read (int fd, void * buffer, size_t size) 24 | { 25 | return -1; 26 | } 27 | 28 | void os_rtu_tx_drain (int fd, size_t size) 29 | { 30 | } 31 | 32 | ssize_t os_rtu_rx_avail (int fd) 33 | { 34 | return -1; 35 | } 36 | 37 | void os_rtu_set_serial_cfg (int fd, const mb_rtu_serial_cfg_t * cfg) 38 | { 39 | } 40 | 41 | int os_rtu_open (const char * name, void * arg) 42 | { 43 | return -1; 44 | } 45 | -------------------------------------------------------------------------------- /src/ports/windows/mbal_sys.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2021 rt-labs AB, Sweden. 10 | * See LICENSE file in the project root for full license information. 11 | ********************************************************************/ 12 | 13 | #ifndef MBAL_SYS_H 14 | #define MBAL_SYS_H 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif 19 | 20 | #include 21 | typedef SSIZE_T ssize_t; 22 | 23 | #ifdef __cplusplus 24 | } 25 | #endif 26 | 27 | #endif /* MBAL_SYS_H */ 28 | -------------------------------------------------------------------------------- /src/ports/windows/mbal_tcp.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2015 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | /* Do not include winsock.h */ 17 | #define WIN32_LEAN_AND_MEAN 18 | #define _WINSOCK_DEPRECATED_NO_WARNINGS 19 | 20 | #include "mbal_tcp.h" 21 | #include "mb_tcp.h" 22 | #include "osal.h" 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #define KEEP_ALIVE_IDLE 10 /* max idle time before keepalive sent [s] */ 29 | #define KEEP_ALIVE_INTVL 2 /* time between keepalives [s] */ 30 | #define KEEP_ALIVE_CNT 3 /* max number of unacked keepalives */ 31 | 32 | /* max time to wait for message in progress [ms] */ 33 | #define RCV_TIMEOUT 500 34 | 35 | static void os_winsock_init (void) 36 | { 37 | static int winsock_init = 0; 38 | 39 | if (!winsock_init) 40 | { 41 | /* Initialise winsock */ 42 | WSADATA wsaData; 43 | WSAStartup (MAKEWORD (2, 2), &wsaData); 44 | winsock_init = 1; 45 | } 46 | } 47 | 48 | int os_tcp_connect (const char * name, uint16_t port) 49 | { 50 | int result; 51 | SOCKET sock; 52 | int option; 53 | struct sockaddr_in addr; 54 | 55 | os_winsock_init(); 56 | 57 | /* Create socket */ 58 | sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); 59 | if (sock == INVALID_SOCKET) 60 | { 61 | return -1; 62 | } 63 | 64 | /* Set socket options (nagle, reuseaddr) */ 65 | 66 | option = 1; 67 | result = 68 | setsockopt (sock, IPPROTO_TCP, TCP_NODELAY, (char *)&option, sizeof (int)); 69 | if (result == SOCKET_ERROR) 70 | { 71 | goto error; 72 | } 73 | 74 | option = 1; 75 | result = 76 | setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, (char *)&option, sizeof (int)); 77 | if (result == SOCKET_ERROR) 78 | { 79 | goto error; 80 | } 81 | 82 | addr.sin_family = AF_INET; 83 | addr.sin_addr.s_addr = inet_addr (name); 84 | addr.sin_port = htons (port); 85 | 86 | /* Connect to peer */ 87 | result = connect (sock, (struct sockaddr *)&addr, sizeof (addr)); 88 | if (result == SOCKET_ERROR) 89 | { 90 | goto error; 91 | } 92 | 93 | return (int)sock; 94 | 95 | error: 96 | closesocket (sock); 97 | return -1; 98 | } 99 | 100 | int os_tcp_accept_connection (uint16_t port) 101 | { 102 | int result; 103 | SOCKET sock; 104 | struct sockaddr_in addr; 105 | DWORD tv; 106 | int option; 107 | SOCKET peer; 108 | 109 | os_winsock_init(); 110 | 111 | /* Create listening socket */ 112 | sock = socket (AF_INET, SOCK_STREAM, 0); 113 | if (sock == INVALID_SOCKET) 114 | { 115 | return -1; 116 | } 117 | 118 | memset (&addr, 0, sizeof (addr)); 119 | 120 | addr.sin_family = AF_INET; 121 | addr.sin_addr.s_addr = htonl (INADDR_ANY); 122 | addr.sin_port = htons (port); 123 | 124 | option = 1; /* enable */ 125 | result = 126 | setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, (char *)&option, sizeof (int)); 127 | if (result == SOCKET_ERROR) 128 | { 129 | goto error1; 130 | } 131 | 132 | tv = RCV_TIMEOUT; 133 | result = 134 | setsockopt (sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof (tv)); 135 | if (result == SOCKET_ERROR) 136 | { 137 | goto error1; 138 | } 139 | 140 | result = bind (sock, (struct sockaddr *)&addr, sizeof (addr)); 141 | if (result == SOCKET_ERROR) 142 | { 143 | goto error1; 144 | } 145 | 146 | result = listen (sock, 1); 147 | if (result == SOCKET_ERROR) 148 | { 149 | goto error1; 150 | } 151 | 152 | /* Accept one connection */ 153 | peer = accept (sock, NULL, NULL); 154 | if (peer == INVALID_SOCKET) 155 | { 156 | /* Error or timeout */ 157 | goto error1; 158 | } 159 | 160 | /* Set peer socket options (nagle, reuseaddr, receive timeout, 161 | keepalive). */ 162 | 163 | option = 1; 164 | result = 165 | setsockopt (peer, IPPROTO_TCP, TCP_NODELAY, (char *)&option, sizeof (int)); 166 | if (result == SOCKET_ERROR) 167 | { 168 | goto error2; 169 | } 170 | 171 | option = 1; /* enable */ 172 | result = 173 | setsockopt (peer, SOL_SOCKET, SO_REUSEADDR, (char *)&option, sizeof (int)); 174 | if (result == SOCKET_ERROR) 175 | { 176 | goto error2; 177 | } 178 | 179 | tv = RCV_TIMEOUT; 180 | result = 181 | setsockopt (peer, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof (tv)); 182 | if (result == SOCKET_ERROR) 183 | { 184 | goto error2; 185 | } 186 | 187 | option = 1; 188 | result = 189 | setsockopt (peer, SOL_SOCKET, SO_KEEPALIVE, (char *)&option, sizeof (int)); 190 | if (result == SOCKET_ERROR) 191 | { 192 | goto error2; 193 | } 194 | 195 | /* keepalive is not available on windows */ 196 | #if 0 197 | option = KEEP_ALIVE_IDLE; 198 | result = setsockopt (peer, IPPROTO_TCP, TCP_KEEPIDLE, (char *)&option, sizeof (int)); 199 | if (result == SOCKET_ERROR) 200 | { 201 | goto error2; 202 | } 203 | 204 | option = KEEP_ALIVE_INTVL; 205 | result = 206 | setsockopt (peer, IPPROTO_TCP, TCP_KEEPINTVL, (char *)&option, sizeof (int)); 207 | if (result == SOCKET_ERROR) 208 | { 209 | goto error2; 210 | } 211 | 212 | option = KEEP_ALIVE_CNT; 213 | result = setsockopt (peer, IPPROTO_TCP, TCP_KEEPCNT, (char *)&option, sizeof (int)); 214 | if (result == SOCKET_ERROR) 215 | { 216 | goto error2; 217 | } 218 | #endif 219 | 220 | /* Close listening socket. No more connections accepted. */ 221 | closesocket (sock); 222 | return (int)peer; 223 | 224 | error2: 225 | closesocket (peer); 226 | error1: 227 | closesocket (sock); 228 | return -1; 229 | } 230 | 231 | void os_tcp_close (int peer) 232 | { 233 | SOCKET s = (SOCKET)peer; 234 | closesocket (s); 235 | } 236 | 237 | int os_tcp_send (int peer, const void * buffer, size_t size) 238 | { 239 | SOCKET s = (SOCKET)peer; 240 | const char * p = buffer; 241 | int remain = (int)size; 242 | int n; 243 | 244 | do 245 | { 246 | n = send (s, p, remain, 0); 247 | remain -= n; 248 | p += n; 249 | } while (remain > 0 && n > 0); 250 | 251 | if (remain == 0) 252 | { 253 | /* Successfully sent all data */ 254 | return (int)size; 255 | } 256 | 257 | /* Connection closed or error sending */ 258 | return n; 259 | } 260 | 261 | int os_tcp_recv (int peer, void * buffer, size_t size) 262 | { 263 | SOCKET s = (SOCKET)peer; 264 | char * p = buffer; 265 | int remain = (int)size; 266 | int n; 267 | 268 | do 269 | { 270 | n = recv (s, p, remain, 0); 271 | remain -= n; 272 | p += n; 273 | } while (remain > 0 && n > 0); 274 | 275 | if (remain == 0) 276 | { 277 | /* Successfully received all data */ 278 | return (int)size; 279 | } 280 | 281 | /* Connection closed or error receiving */ 282 | return n; 283 | } 284 | 285 | int os_tcp_recv_wait (int peer, uint32_t tmo) 286 | { 287 | SOCKET s = (SOCKET)peer; 288 | fd_set fds; 289 | int result; 290 | struct timeval tv; 291 | 292 | FD_ZERO (&fds); 293 | FD_SET (s, &fds); 294 | 295 | tv.tv_sec = 0; 296 | tv.tv_usec = tmo * 1000; 297 | result = select (0, &fds, NULL, NULL, &tv); 298 | return result; 299 | } 300 | -------------------------------------------------------------------------------- /test/mbus_test.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2019 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include 17 | 18 | int main (int argc, char * argv[]) 19 | { 20 | if (argc > 0) 21 | ::testing::InitGoogleTest (&argc, argv); 22 | else 23 | ::testing::InitGoogleTest(); 24 | 25 | int result = RUN_ALL_TESTS(); 26 | exit (result); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /test/mocks.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2019 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mocks.h" 17 | 18 | #include 19 | #include 20 | 21 | unsigned int mock_mb_pdu_tx_calls; 22 | pdu_txn_t mock_mb_pdu_tx_transaction; 23 | uint8_t mock_mb_pdu_tx_data[MAX_PDU_SIZE]; 24 | size_t mock_mb_pdu_tx_size; 25 | 26 | void mock_mb_pdu_tx ( 27 | mb_transport_t * transport, 28 | const pdu_txn_t * transaction, 29 | size_t size) 30 | { 31 | mock_mb_pdu_tx_calls++; 32 | mock_mb_pdu_tx_size = size; 33 | memset (mock_mb_pdu_tx_data, 0, sizeof (mock_mb_pdu_tx_data)); 34 | memcpy (mock_mb_pdu_tx_data, transaction->data, size); 35 | } 36 | 37 | unsigned int mock_mb_pdu_rx_calls; 38 | const uint8_t * mock_mb_pdu_rx_data; 39 | size_t mock_mb_pdu_rx_size; 40 | int mock_mb_pdu_rx_result; 41 | 42 | int mock_mb_pdu_rx ( 43 | mb_transport_t * transport, 44 | pdu_txn_t * transaction, 45 | uint32_t tmp) 46 | { 47 | mock_mb_pdu_rx_calls++; 48 | memcpy (transaction->data, mock_mb_pdu_rx_data, mock_mb_pdu_rx_size); 49 | return mock_mb_pdu_rx_result; 50 | } 51 | 52 | bool mock_mb_pdu_rx_bc (mb_transport_t * transport) 53 | { 54 | return false; 55 | } 56 | 57 | bool mock_mb_pdu_rx_avail (mb_transport_t * transport) 58 | { 59 | return false; 60 | } 61 | 62 | int mock_mb_transport_bringup (mb_transport_t * transport, const char * name) 63 | { 64 | return 1; 65 | } 66 | -------------------------------------------------------------------------------- /test/mocks.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2019 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #ifndef MOCKS_H 17 | #define MOCKS_H 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | #include 24 | #include 25 | 26 | #include "mb_transport.h" 27 | #include "mb_pdu.h" 28 | 29 | extern unsigned int mock_mb_pdu_tx_calls; 30 | extern pdu_txn_t mock_mb_pdu_tx_transaction; 31 | extern uint8_t mock_mb_pdu_tx_data[MAX_PDU_SIZE]; 32 | extern size_t mock_mb_pdu_tx_size; 33 | 34 | void mock_mb_pdu_tx ( 35 | mb_transport_t * transport, 36 | const pdu_txn_t * transaction, 37 | size_t size); 38 | 39 | extern unsigned int mock_mb_pdu_rx_calls; 40 | extern const uint8_t * mock_mb_pdu_rx_data; 41 | extern size_t mock_mb_pdu_rx_size; 42 | extern int mock_mb_pdu_rx_result; 43 | 44 | int mock_mb_pdu_rx ( 45 | mb_transport_t * transport, 46 | pdu_txn_t * transaction, 47 | uint32_t tmo); 48 | 49 | bool mock_mb_pdu_rx_bc (mb_transport_t * transport); 50 | bool mock_mb_pdu_rx_avail (mb_transport_t * transport); 51 | int mock_mb_transport_bringup (mb_transport_t * transport, const char * name); 52 | 53 | #ifdef __cplusplus 54 | } 55 | #endif 56 | 57 | #endif /* MOCKS_H */ 58 | -------------------------------------------------------------------------------- /test/test_slave.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2020 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #include "mb_slave.h" 17 | 18 | #include "options.h" 19 | #include "osal.h" 20 | #include 21 | 22 | #include "mocks.h" 23 | #include "test_util.h" 24 | 25 | using namespace std; 26 | using namespace testing; 27 | 28 | extern "C" void __assert_func ( 29 | const char * file, 30 | int line, 31 | const char * func, 32 | const char * expr) 33 | { 34 | printf ("FAILED ASSERTION \"%s\" in %s @ %s:%d\n\n", expr, func, file, line); 35 | CC_ASSERT (0); 36 | } 37 | 38 | // Test fixture 39 | 40 | uint8_t coils[2]; 41 | uint16_t hold[4]; 42 | 43 | extern "C" int coil_get (uint16_t address, uint8_t * data, size_t quantity) 44 | { 45 | uint16_t offset; 46 | 47 | for (offset = 0; offset < quantity; offset++) 48 | { 49 | uint32_t bit = address + offset; 50 | int value; 51 | 52 | value = mb_slave_bit_get (coils, bit); 53 | mb_slave_bit_set (data, offset, value); 54 | } 55 | return 0; 56 | } 57 | 58 | extern "C" int coil_set (uint16_t address, uint8_t * data, size_t quantity) 59 | { 60 | uint16_t offset; 61 | 62 | for (offset = 0; offset < quantity; offset++) 63 | { 64 | uint32_t bit = address + offset; 65 | int value; 66 | 67 | value = mb_slave_bit_get (data, offset); 68 | mb_slave_bit_set (coils, bit, value); 69 | } 70 | return 0; 71 | } 72 | 73 | extern "C" int input_get (uint16_t address, uint8_t * data, size_t quantity) 74 | { 75 | uint16_t offset; 76 | 77 | for (offset = 0; offset < quantity; offset++) 78 | { 79 | mb_slave_bit_set (data, offset, 1); 80 | } 81 | return 0; 82 | } 83 | 84 | extern "C" int hold_get (uint16_t address, uint8_t * data, size_t quantity) 85 | { 86 | uint16_t offset; 87 | 88 | for (offset = 0; offset < quantity; offset++) 89 | { 90 | uint32_t reg = address + offset; 91 | 92 | mb_slave_reg_set (data, offset, hold[reg]); 93 | } 94 | return 0; 95 | } 96 | 97 | extern "C" int hold_set (uint16_t address, uint8_t * data, size_t quantity) 98 | { 99 | uint16_t offset; 100 | 101 | for (offset = 0; offset < quantity; offset++) 102 | { 103 | uint32_t reg = address + offset; 104 | 105 | hold[reg] = mb_slave_reg_get (data, offset); 106 | } 107 | return 0; 108 | } 109 | 110 | extern "C" int reg_get (uint16_t address, uint8_t * data, size_t quantity) 111 | { 112 | uint16_t offset; 113 | 114 | for (offset = 0; offset < quantity; offset++) 115 | { 116 | mb_slave_reg_set (data, offset, 0x1100 | (offset & 0xFF)); 117 | } 118 | return 0; 119 | } 120 | 121 | extern "C" int ping (uint8_t * data, size_t rx_count) 122 | { 123 | for (size_t i = 0; i < rx_count; i++) 124 | { 125 | data[i] |= 0x80; 126 | } 127 | return (int)rx_count; 128 | } 129 | 130 | extern "C" const mb_vendor_func_t vendor_funcs[] = { 131 | {101, ping}, 132 | }; 133 | 134 | extern "C" const mb_iomap_t slave_iomap = { 135 | .coils = {16, coil_get, coil_set}, // 16 coils 136 | .inputs = {2, input_get, NULL}, // 2 input status bits 137 | .holding_registers = {4, hold_get, hold_set}, // 4 holding registers 138 | .input_registers = {5, reg_get, NULL}, // 5 input registers 139 | .num_vendor_funcs = NELEMENTS (vendor_funcs), // 1 vendor function 140 | .vendor_funcs = vendor_funcs, 141 | }; 142 | 143 | extern "C" const mb_slave_cfg_t slave_cfg = { 144 | .id = 2, // Slave ID: 2 145 | .priority = 15, 146 | .stack_size = 2048, 147 | .iomap = &slave_iomap, 148 | }; 149 | 150 | class MbSlaveTest : public TestBase 151 | { 152 | protected: 153 | virtual void SetUp() 154 | { 155 | TestBase::SetUp(); 156 | 157 | slave.iomap = &slave_iomap; 158 | slave.transport = NULL; 159 | slave.id = 2; 160 | slave.running = 1; 161 | 162 | transaction.data = buffer; 163 | 164 | coils[0] = 0x00; 165 | coils[1] = 0x00; 166 | 167 | hold[0] = 0x1234; 168 | hold[1] = 0x5678; 169 | hold[2] = 0x55AA; 170 | hold[3] = 0xAA55; 171 | } 172 | 173 | mb_slave_t slave; 174 | pdu_txn_t transaction; 175 | uint8_t buffer[MAX_PDU_SIZE]; 176 | }; 177 | 178 | // Parameterized test fixtures 179 | 180 | class MbSlaveTestRead 181 | : public MbSlaveTest, 182 | public WithParamInterface, vector>> 183 | { 184 | }; 185 | 186 | class MbSlaveTestWrite 187 | : public MbSlaveTest, 188 | public WithParamInterface< 189 | tuple, vector, vector, vector>> 190 | { 191 | }; 192 | 193 | // Tests 194 | 195 | TEST_P (MbSlaveTestRead, MbSlaveTestReadResponse) 196 | { 197 | vector request = get<0> (GetParam()); 198 | vector expected_tx = get<1> (GetParam()); 199 | 200 | mock_mb_pdu_rx_data = &request[0]; 201 | mock_mb_pdu_rx_size = request.size(); 202 | mock_mb_pdu_rx_result = (int)request.size(); 203 | 204 | mb_slave_handle_request (&slave, &transaction); 205 | 206 | vector tx_data ( 207 | mock_mb_pdu_tx_data, 208 | mock_mb_pdu_tx_data + mock_mb_pdu_tx_size); 209 | 210 | EXPECT_PRED_FORMAT2 (VectorsMatch, tx_data, expected_tx); 211 | } 212 | 213 | // clang-format off 214 | INSTANTIATE_TEST_SUITE_P ( 215 | ReadTests, 216 | MbSlaveTestRead, 217 | Values ( 218 | // Read coils 219 | make_tuple ( 220 | vector{0x01, 0x00, 0x0A, 0x00, 0x06}, 221 | vector{0x01, 0x01, 0x00} 222 | ), 223 | // Read inputs 224 | make_tuple ( 225 | vector{0x02, 0x00, 0x00, 0x00, 0x02}, 226 | vector{0x02, 0x01, 0x03} 227 | ), 228 | // Read input registers 229 | make_tuple ( 230 | vector{0x04, 0x00, 0x00, 0x00, 0x05}, 231 | vector{0x04, 0x0a, 0x11, 0x00, 0x11, 0x01, 0x11, 0x02, 0x11, 0x03, 0x11, 0x04} 232 | ), 233 | // Read holding registers 234 | make_tuple ( 235 | vector{0x03, 0x00, 0x00, 0x00, 0x04}, 236 | vector{0x03, 0x08, 0x12, 0x34, 0x56, 0x78, 0x55, 0xaa, 0xaa, 0x55} 237 | ), 238 | // Read coils address error 239 | make_tuple ( 240 | vector{0x01, 0x00, 0x0A, 0x00, 0x07}, 241 | vector{0x81, 0x02} 242 | ), 243 | // Read inputs address error 244 | make_tuple ( 245 | vector{0x02, 0x00, 0x00, 0x00, 0x03}, 246 | vector{0x82, 0x02} 247 | ), 248 | // Read input registers address error 249 | make_tuple ( 250 | vector{0x04, 0x00, 0x00, 0x00, 0x06}, 251 | vector{0x84, 0x02} 252 | ), 253 | // Read holding registers address error 254 | make_tuple ( 255 | vector{0x03, 0x00, 0x00, 0x00, 0x05}, 256 | vector{0x83, 0x02} 257 | ), 258 | // Read coils quantity error 259 | make_tuple ( 260 | vector{0x01, 0x00, 0x0A, 0x07, 0xD1}, 261 | vector{0x81, 0x03} 262 | ), 263 | // Read inputs quantity error 264 | make_tuple ( 265 | vector{0x02, 0x00, 0x00, 0x07, 0xD1}, 266 | vector{0x82, 0x03} 267 | ), 268 | // Read input registers quantity error 269 | make_tuple ( 270 | vector{0x04, 0x00, 0x00, 0x00, 0x7E}, 271 | vector{0x84, 0x03} 272 | ), 273 | // Read holding registers quantity error 274 | make_tuple ( 275 | vector{0x03, 0x00, 0x00, 0x00, 0x7E}, 276 | vector{0x83, 0x03} 277 | ) 278 | ) 279 | ); 280 | // clang-format on 281 | 282 | TEST_P (MbSlaveTestWrite, MbSlaveTestWriteResponseAndState) 283 | { 284 | vector request = get<0> (GetParam()); 285 | vector expected_tx = get<1> (GetParam()); 286 | vector expected_coils = get<2> (GetParam()); 287 | vector expected_hold = get<3> (GetParam()); 288 | 289 | mock_mb_pdu_rx_data = &request[0]; 290 | mock_mb_pdu_rx_size = request.size(); 291 | mock_mb_pdu_rx_result = (int)request.size(); 292 | 293 | mb_slave_handle_request (&slave, &transaction); 294 | 295 | vector tx_data ( 296 | mock_mb_pdu_tx_data, 297 | mock_mb_pdu_tx_data + mock_mb_pdu_tx_size); 298 | 299 | EXPECT_PRED_FORMAT2 (VectorsMatch, tx_data, expected_tx); 300 | 301 | if (expected_coils.size() > 0) 302 | { 303 | vector vect_coils (coils, coils + NELEMENTS (coils)); 304 | EXPECT_PRED_FORMAT2 (VectorsMatch, vect_coils, expected_coils); 305 | } 306 | 307 | if (expected_hold.size() > 0) 308 | { 309 | vector vect_hold (hold, hold + NELEMENTS (hold)); 310 | EXPECT_PRED_FORMAT2 (VectorsMatch, vect_hold, expected_hold); 311 | } 312 | } 313 | 314 | // clang-format off 315 | INSTANTIATE_TEST_SUITE_P ( 316 | WriteTests, 317 | MbSlaveTestWrite, 318 | Values ( 319 | // Write coils bit 1 320 | make_tuple ( 321 | vector{0x05, 0x00, 0x01, 0xFF, 0x00}, // Request 322 | vector{0x05, 0x00, 0x01, 0xFF, 0x00}, // Expected Tx 323 | vector{0x02, 0x00}, // Expected coils 324 | vector{} // Expected hold 325 | ), 326 | // Write coils bits 2-12 327 | make_tuple ( 328 | vector{0x0F, 0x00, 0x02, 0x00, 0x0A, 0x02, 0xFF, 0x03}, 329 | vector{0x0F, 0x00, 0x02, 0x00, 0x0A}, 330 | vector{0xFC, 0x0F}, 331 | vector{} 332 | ), 333 | // Write holding register 1 334 | make_tuple ( 335 | vector{0x06, 0x00, 0x01, 0x11, 0x22}, 336 | vector{0x06, 0x00, 0x01, 0x11, 0x22}, 337 | vector{}, 338 | vector{0x1234, 0x1122, 0x55AA, 0xAA55} 339 | ), 340 | // Write 2 holding registers 341 | make_tuple ( 342 | vector{0x10, 0x00, 0x01, 0x00, 0x02, 0x04, 0x11, 0x22, 0x33, 0x44}, 343 | vector{0x10, 0x00, 0x01, 0x00, 0x02}, 344 | vector{}, 345 | vector{0x1234, 0x1122, 0x3344, 0xAA55} 346 | ), 347 | // ReadWrite 2 holding registers 348 | make_tuple ( 349 | vector{0x17, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x02, 0x11, 0x22}, 350 | vector{0x17, 0x04, 0x11, 0x22, 0x55, 0xAA}, 351 | vector{}, 352 | vector{0x1234, 0x1122, 0x55AA, 0xAA55} 353 | ) 354 | ) 355 | ); 356 | // clang-format on 357 | 358 | TEST (MbSlaveExamples, MbSlaveBitSet) 359 | { 360 | uint8_t data[4] = {0x00, 0x00, 0x00, 0x00}; 361 | 362 | mb_slave_bit_set (data, 28, 1); 363 | 364 | EXPECT_EQ (data[3], 0x10); 365 | } 366 | 367 | TEST (MbSlaveExamples, MbSlaveRegSet) 368 | { 369 | uint8_t data[4] = {0x00, 0x00, 0x00, 0x00}; 370 | 371 | mb_slave_reg_set (data, 1, 0x1234); 372 | 373 | EXPECT_EQ (data[2], 0x12); 374 | EXPECT_EQ (data[3], 0x34); 375 | } 376 | 377 | TEST (MbSlaveExamples, MbSlaveBitGet) 378 | { 379 | uint8_t data[4] = {0x00, 0x00, 0x00, 0x10}; 380 | 381 | int value = mb_slave_bit_get (data, 28); 382 | 383 | EXPECT_EQ (value, 1); 384 | } 385 | 386 | TEST (MbSlaveExamples, MbSlaveRegGet) 387 | { 388 | uint8_t data[4] = {0x00, 0x00, 0x12, 0x34}; 389 | 390 | uint16_t value = mb_slave_reg_get (data, 1); 391 | 392 | EXPECT_EQ (value, 0x1234); 393 | } 394 | -------------------------------------------------------------------------------- /test/test_util.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * _ _ _ 3 | * _ __ | |_ _ | | __ _ | |__ ___ 4 | * | '__|| __|(_)| | / _` || '_ \ / __| 5 | * | | | |_ _ | || (_| || |_) |\__ \ 6 | * |_| \__|(_)|_| \__,_||_.__/ |___/ 7 | * 8 | * www.rt-labs.com 9 | * Copyright 2019 rt-labs AB, Sweden. 10 | * 11 | * This software is dual-licensed under GPLv3 and a commercial 12 | * license. See the file LICENSE.md distributed with this software for 13 | * full license information. 14 | ********************************************************************/ 15 | 16 | #ifndef TEST_UTIL_H 17 | #define TEST_UTIL_H 18 | 19 | #include 20 | #include "mocks.h" 21 | #include "options.h" 22 | #include "mb_pdu.h" 23 | 24 | class TestBase : public ::testing::Test 25 | { 26 | protected: 27 | virtual void SetUp() 28 | { 29 | /* Reset mock call counters */ 30 | mock_mb_pdu_tx_calls = 0; 31 | mock_mb_pdu_rx_calls = 0; 32 | } 33 | }; 34 | 35 | template std::string FormatHex (T value) 36 | { 37 | std::stringstream ss; 38 | uint8_t w = 2 * sizeof (T); 39 | ss << "0x" << std::setfill ('0') << std::setw (w) << std::hex 40 | << std::uppercase; 41 | ss << static_cast (value); 42 | return ss.str(); 43 | } 44 | 45 | template 46 | ::testing::AssertionResult ArraysMatch ( 47 | const T (&expected)[size], 48 | const T (&actual)[size]) 49 | { 50 | for (size_t i (0); i < size; ++i) 51 | { 52 | if (expected[i] != actual[i]) 53 | { 54 | return ::testing::AssertionFailure() 55 | << "actual[" << i << "] (" << FormatHex (actual[i]) 56 | << ") != expected[" << i << "] (" << FormatHex (expected[i]) 57 | << ")"; 58 | } 59 | } 60 | 61 | return ::testing::AssertionSuccess(); 62 | } 63 | 64 | template std::string FormatVector (std::vector vector) 65 | { 66 | std::stringstream ss; 67 | 68 | if (vector.size() == 0) 69 | { 70 | ss << "{ empty }"; 71 | return ss.str(); 72 | } 73 | 74 | ss << "{ "; 75 | for (size_t i = 0; i < vector.size(); i++) 76 | { 77 | ss << FormatHex (vector[i]); 78 | if (i != vector.size() - 1) 79 | ss << ", "; 80 | } 81 | ss << " }"; 82 | 83 | return ss.str(); 84 | } 85 | 86 | template 87 | testing::AssertionResult VectorsMatch ( 88 | const char * expected_expr, 89 | const char * actual_expr, 90 | std::vector expected, 91 | std::vector actual) 92 | { 93 | if (actual == expected) 94 | return testing::AssertionSuccess(); 95 | 96 | std::stringstream ss, msg; 97 | std::string expected_str, actual_str; 98 | 99 | msg << "Expected equality of these values:\n"; 100 | 101 | msg << " " << actual_expr << "\n"; 102 | msg << " Which is: " << FormatVector (actual) << "\n"; 103 | 104 | msg << " " << expected_expr << "\n"; 105 | msg << " Which is: " << FormatVector (expected) << "\n"; 106 | 107 | return testing::AssertionFailure() << msg.str(); 108 | } 109 | #endif /* TEST_UTIL_H */ 110 | -------------------------------------------------------------------------------- /version.h.in: -------------------------------------------------------------------------------- 1 | #ifndef VERSION_H 2 | #define VERSION_H 3 | 4 | #define MBUS_VERSION_MAJOR @MBUS_VERSION_MAJOR@ 5 | #define MBUS_VERSION_MINOR @MBUS_VERSION_MINOR@ 6 | #define MBUS_VERSION_PATCH @MBUS_VERSION_PATCH@ 7 | 8 | #endif /* VERSION_H */ 9 | --------------------------------------------------------------------------------