├── .gear ├── rules └── modbus-utils.spec ├── .gitmodules ├── modbus-utils.pro ├── modbus_client ├── modbus_client.pro └── modbus_client.c ├── modbus_server ├── modbus_server.pro └── modbus_server.c ├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── signals.c ├── threaded-test-server.c └── common └── mbu-common.h /.gear/rules: -------------------------------------------------------------------------------- 1 | spec: .gear/modbus-utils.spec 2 | tar: . -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libmodbus"] 2 | path = libmodbus 3 | url = https://github.com/stephane/libmodbus.git 4 | -------------------------------------------------------------------------------- /modbus-utils.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += modbus_client \ 4 | modbus_server \ 5 | modbus_threaded_server 6 | -------------------------------------------------------------------------------- /modbus_client/modbus_client.pro: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # Automatically generated by qmake (2.01a) Wed Jul 10 03:22:43 2013 3 | ###################################################################### 4 | 5 | TEMPLATE = app 6 | TARGET = mbClient 7 | DESTDIR = ../ 8 | DEPENDPATH += . 9 | INCLUDEPATH += . 10 | 11 | # Input 12 | SOURCES += modbus_client.c 13 | 14 | INCLUDEPATH += ../libmodbus/src \ 15 | ../common 16 | 17 | LIBS += -L../libmodbus/src/.libs 18 | LIBS += -lmodbus 19 | -------------------------------------------------------------------------------- /modbus_server/modbus_server.pro: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # Automatically generated by qmake (2.01a) Wed Jul 10 03:22:43 2013 3 | ###################################################################### 4 | 5 | TEMPLATE = app 6 | TARGET = mbServer 7 | DESTDIR = ../ 8 | DEPENDPATH += . 9 | INCLUDEPATH += . 10 | 11 | # Input 12 | SOURCES += modbus_server.c 13 | 14 | INCLUDEPATH += ../libmodbus/src \ 15 | ../common 16 | 17 | LIBS += -L../libmodbus/src/.libs 18 | LIBS += -lmodbus 19 | -------------------------------------------------------------------------------- /.gear/modbus-utils.spec: -------------------------------------------------------------------------------- 1 | %define unpackaged_files_terminate_build 1 2 | 3 | Name: modbus-utils 4 | Version: 1.0.0 5 | Release: alt1 6 | 7 | Summary: CLI utilities to work with Modbus devices 8 | License: %mit 9 | Group: Other 10 | Url: https://github.com/Krzysztow/modbus-utils 11 | Source0: %name-%version.tar 12 | 13 | BuildRequires(pre): rpm-build-licenses 14 | BuildRequires(pre): rpm-macros-cmake 15 | BuildRequires: cmake 16 | BuildRequires: libmodbus-devel 17 | 18 | %description 19 | Client and server CLI utilities to work with Modbus devices 20 | 21 | %prep 22 | %setup -q 23 | 24 | %build 25 | %cmake 26 | %install 27 | %cmakeinstall_std 28 | %files 29 | %_bindir/modbus_client 30 | %_bindir/modbus_server 31 | 32 | %changelog 33 | * Tue Aug 10 2021 Aleksey Saprunov 1.0.0-alt1 34 | - Initial release 35 | 36 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(modbus-utils VERSION 1.0.0 LANGUAGES C) 4 | 5 | find_package(PkgConfig REQUIRED) 6 | pkg_check_modules(MODBUS REQUIRED IMPORTED_TARGET libmodbus) 7 | 8 | add_executable(modbus_client "${CMAKE_CURRENT_SOURCE_DIR}/modbus_client/modbus_client.c") 9 | target_link_libraries(modbus_client PkgConfig::MODBUS) 10 | target_include_directories(modbus_client PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/common" "${MODBUS_INCLUDE_DIRS}") 11 | 12 | add_executable(modbus_server "${CMAKE_CURRENT_SOURCE_DIR}/modbus_server/modbus_server.c") 13 | target_link_libraries(modbus_server PkgConfig::MODBUS) 14 | target_include_directories(modbus_server PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/common" "${MODBUS_INCLUDE_DIRS}") 15 | 16 | install(TARGETS modbus_server modbus_client DESTINATION ${CMAKE_INSTALL_BINDIR} 17 | RUNTIME DESTINATION bin 18 | PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) 19 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © `2013` `Krzysztow` 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | modbus-utils 2 | ============ 3 | 4 | Modbus client and server command line tools based on libmodbus. 5 | 6 | NOTE: 7 | Both apps are linked with libmodbus library. After repository is pulled do the following: 8 | 9 | compilation 10 | =========== 11 | 12 | ## option 1 (cmake) 13 | 14 | ```sh 15 | $ git clone https://github.com/Krzysztow/modbus-utils 16 | $ cd modbus-utils 17 | $ git submodule update --init 18 | $ mkdir build 19 | $ cd build 20 | $ cmake .. 21 | $ make 22 | ``` 23 | 24 | ## option 2 25 | 26 | #assumes you are in a root of the repository 27 | 28 | #go to libmodbus dir and compile it 29 | 30 | cd ./libmodbus 31 | ./configure 32 | ./make 33 | 34 | #as a result *.so libraries are in export ./src/.libs/ directory 35 | 36 | #get back to the root 37 | 38 | cd .. 39 | gcc ./modbus_client/modbus_client.c -I./common -I./libmodbus/src/ -L./libmodbus/src/.libs/ -lmodbus -o mbClient 40 | gcc ./modbus_server/modbus_server.c -I./common -I./libmodbus/src/ -L./libmodbus/src/.libs/ -lmodbus -o mbServer 41 | 42 | running 43 | ======= 44 | 45 | If modbus libraries are not in a default location (either it's needed to move libraries to app location or set 46 | appropriate environment variable): 47 | - on linux it would be: 48 | 1) LD_LIBRARY_PATH=./libmodbus/src/.libs/ ./mbClient OR 49 | 2) export LD_LIBRARY_PATH=./libmodbus/src/.libs/ and then run ./mbClient 50 | 51 | usage 52 | ===== 53 | 54 | Run apps with no arguments, descriptive help information will be provided. 55 | -------------------------------------------------------------------------------- /signals.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | 4 | * Copyright (c) 2013 Krzysztow 5 | 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | #include 25 | #include 26 | 27 | int toExitCtr = 0; 28 | 29 | void sig_func(int sig) 30 | { 31 | toExitCtr++; 32 | printf("Caught signal SIGINT (%d)\n", toExitCtr); 33 | } 34 | 35 | int main() 36 | { 37 | signal(SIGINT, sig_func); 38 | 39 | for (;;) { 40 | printf("loop \n"); 41 | sleep(1); 42 | 43 | if (3 <= toExitCtr) { 44 | printf("Exitting...\n"); 45 | break; 46 | } 47 | } 48 | 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /threaded-test-server.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2008-2010 Stéphane Raimbault 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #ifndef _MSC_VER 20 | #include 21 | #endif 22 | #include 23 | #include 24 | #include 25 | 26 | #include "modbus.h" 27 | #include "modbus-private.h" 28 | #include "modbus-tcp-private.h" 29 | 30 | #include 31 | 32 | modbus_t *modbus_clone_tcp(modbus_t* ctx) 33 | { 34 | modbus_t* new_ctx = (modbus_t *) malloc(sizeof(modbus_t)); 35 | memcpy(new_ctx, ctx, sizeof(modbus_t)); 36 | new_ctx->backend_data = (modbus_tcp_t *) malloc(sizeof(modbus_tcp_t)); 37 | memcpy(new_ctx->backend_data, ctx->backend_data, sizeof(modbus_tcp_t)); 38 | 39 | //modbus_tcp_t *mb_tcp = (modbus_tcp_t*)ctx->backend_data; 40 | printf("Socket copied %d, should be %d\n", new_ctx->s, ctx->s); 41 | ctx->s = 0; 42 | 43 | return new_ctx; 44 | } 45 | 46 | 47 | typedef struct { 48 | modbus_t *ctxt; 49 | modbus_mapping_t *mm; 50 | int id; 51 | } SA; 52 | 53 | void *serveClient(void *threadarg) 54 | { 55 | SA *a; 56 | a = (SA *) threadarg; 57 | modbus_t *ctx = a->ctxt; 58 | modbus_mapping_t *mb_mapping = a->mm; 59 | int id = a->id; 60 | printf("(%d) Serving... %p\n", id, ctx); 61 | for (;;) { 62 | uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH]; 63 | int rc; 64 | printf("(%d) receiving...\n", id); 65 | rc = modbus_receive(ctx, query); 66 | if (rc > 0) { 67 | /* rc is the query size */ 68 | modbus_reply(ctx, query, rc, mb_mapping); 69 | } else if (rc == -1) { 70 | printf("(%d) Disconnected %s\n", id, modbus_strerror(errno)); 71 | /* Connection closed by the client or error */ 72 | break; 73 | } 74 | } 75 | 76 | //clean-up 77 | free(ctx); 78 | free(a); 79 | } 80 | 81 | int main(void) 82 | { 83 | int s = -1; 84 | modbus_t *ctx; 85 | modbus_mapping_t *mb_mapping; 86 | int rc = -1; 87 | pthread_t tId; 88 | 89 | pthread_attr_t attr; 90 | /* Initialize and set thread detached attribute */ 91 | pthread_attr_init(&attr); 92 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 93 | 94 | ctx = modbus_new_tcp("127.0.0.1", 1502); 95 | modbus_set_debug(ctx, TRUE); 96 | 97 | mb_mapping = modbus_mapping_new(500, 500, 500, 500); 98 | if (mb_mapping == NULL) { 99 | fprintf(stderr, "Failed to allocate the mapping: %s\n", 100 | modbus_strerror(errno)); 101 | modbus_free(ctx); 102 | return -1; 103 | } 104 | 105 | s = modbus_tcp_listen(ctx, 1); 106 | 107 | int id = 0; 108 | for (;;) { 109 | id++; 110 | printf("Waiting for another connection...\n\n"); 111 | modbus_tcp_accept(ctx, &s); 112 | 113 | modbus_t *new_ctx = modbus_clone_tcp(ctx); 114 | 115 | SA *sa = (SA*)malloc(sizeof(SA)); 116 | sa->ctxt = new_ctx; 117 | sa->mm = mb_mapping; 118 | sa->id = id; 119 | 120 | rc = pthread_create(&tId, &attr, serveClient, (void *)sa); 121 | if (rc) { 122 | printf("ERROR; return code from pthread_create() is %d\n", rc); 123 | break; 124 | } 125 | } 126 | 127 | printf("Quit the loop: %s\n", modbus_strerror(errno)); 128 | 129 | if (s != -1) { 130 | close(s); 131 | } 132 | modbus_mapping_free(mb_mapping); 133 | modbus_close(ctx); 134 | modbus_free(ctx); 135 | 136 | return 0; 137 | } 138 | -------------------------------------------------------------------------------- /common/mbu-common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | 4 | * Copyright (c) 2013 Krzysztow 5 | 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef MBU_COMMON_H 26 | #define MBU_COMMON_H 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | typedef enum { 33 | None, 34 | Tcp, 35 | Rtu 36 | } ConnType; 37 | 38 | int getInt(const char str[], int *ok) { 39 | int value; 40 | int ret = sscanf(str, "0x%x", &value); 41 | if (0 >= ret) {//couldn't convert from hex, try dec 42 | ret = sscanf(str, "%d", &value); 43 | } 44 | 45 | if (0 != ok) { 46 | *ok = (0 < ret); 47 | } 48 | 49 | return value; 50 | } 51 | 52 | typedef struct { 53 | ConnType type; 54 | 55 | void (*del)(void *backend); 56 | 57 | //common client/server functions 58 | int (*setParam)(void *backend, char c, char *value); 59 | modbus_t *(*createCtxt)(void *backend); 60 | 61 | //server functions 62 | int (*listenForConnection)(void *backend, modbus_t *ctx); 63 | void (*closeConnection)(void *backend); 64 | } BackendParams; 65 | 66 | typedef struct { 67 | BackendParams base; 68 | char devName[32]; 69 | int baud; 70 | int dataBits; 71 | int stopBits; 72 | char parity; 73 | } RtuBackend; 74 | 75 | int setRtuParam(void *backend, char c, char *value) { 76 | RtuBackend *rtuParams = (RtuBackend*)backend; 77 | int ok = 1; 78 | 79 | switch (c) { 80 | case 'b': { 81 | rtuParams->baud = getInt(value, &ok); 82 | if (0 != ok) { 83 | printf("Baudrate is invalid %s", value); 84 | ok = 0; 85 | } 86 | } 87 | break; 88 | case 'd': { 89 | int db = getInt(value, &ok); 90 | if (0 == ok || (7 != db && 8 != db)) { 91 | printf("Data bits incorrect (%s)", value); 92 | ok = 0; 93 | } 94 | else 95 | rtuParams->dataBits = db; 96 | } 97 | break; 98 | case 's': { 99 | int sb = getInt(value, &ok); 100 | if (0 == ok || (1 != sb && 2 != sb)) { 101 | printf("Stop bits incorrect (%s)", value); 102 | ok = 0; 103 | } 104 | else 105 | rtuParams->stopBits = sb; 106 | } 107 | break; 108 | case 'p': { 109 | if (0 == strcmp(value, "none")) { 110 | rtuParams->parity = 'N'; 111 | } 112 | else if (0 == strcmp(value, "even")) { 113 | rtuParams->parity = 'E'; 114 | } 115 | else if (0 == strcmp(value, "odd")) { 116 | rtuParams->parity = 'O'; 117 | } 118 | else { 119 | printf("Unrecognized parity (%s)", value); 120 | ok = 0; 121 | } 122 | } 123 | break; 124 | default: 125 | printf("Unknown rtu param (%c: %s)\n\n", c, value); 126 | ok = 0; 127 | } 128 | 129 | return ok; 130 | } 131 | 132 | modbus_t *createRtuCtxt(void *backend) { 133 | RtuBackend *rtu = (RtuBackend*)backend; 134 | modbus_t *ctx = modbus_new_rtu(rtu->devName, rtu->baud, rtu->parity, rtu->dataBits, rtu->stopBits); 135 | 136 | return ctx; 137 | } 138 | 139 | void delRtu(void *backend) { 140 | RtuBackend *rtu = (RtuBackend*)backend; 141 | free(rtu); 142 | } 143 | 144 | int listenForRtuConnection(void *backend, modbus_t *ctx) { 145 | (void)backend; 146 | (void)ctx; 147 | 148 | printf("Connecting...\r\n"); 149 | return (0 == modbus_connect(ctx)); 150 | } 151 | void closeRtuConnection(void *backend) {(void)backend;} 152 | 153 | BackendParams *createRtuBackend() { 154 | RtuBackend *rtu = (RtuBackend*)malloc(sizeof(RtuBackend)); 155 | rtu->base.type = Rtu; 156 | rtu->base.setParam = &setRtuParam; 157 | rtu->base.createCtxt = &createRtuCtxt; 158 | rtu->base.listenForConnection = &listenForRtuConnection; 159 | rtu->base.closeConnection = &closeRtuConnection; 160 | rtu->base.del = &delRtu; 161 | 162 | strcpy(rtu->devName, ""); 163 | rtu->baud = 9600; 164 | rtu->dataBits = 8; 165 | rtu->stopBits = 1; 166 | rtu->parity = 'E'; 167 | 168 | return (BackendParams*)rtu; 169 | } 170 | 171 | typedef struct { 172 | BackendParams base; 173 | char ip[32]; 174 | int port; 175 | 176 | int clientSocket; 177 | } TcpBackend; 178 | 179 | int setTcpParam(void* backend, char c, char *value) { 180 | TcpBackend *tcp = (TcpBackend*)backend; 181 | 182 | int ok = 1; 183 | 184 | switch (c) { 185 | 186 | case 'p': { 187 | tcp->port = getInt(optarg, &ok); 188 | if (0 == ok) { 189 | printf("Port parameter %s is not integer!\n\n", optarg); 190 | } 191 | } 192 | break; 193 | 194 | default: 195 | printf("Unknown tcp param (%c: %s)\n\n", c, value); 196 | ok = 0; 197 | } 198 | 199 | return ok; 200 | } 201 | 202 | modbus_t *createTcpCtxt(void *backend) { 203 | TcpBackend *tcp = (TcpBackend*)backend; 204 | modbus_t *ctx = modbus_new_tcp(tcp->ip, tcp->port); 205 | 206 | return ctx; 207 | } 208 | 209 | void delTcp(void *backend) { 210 | TcpBackend *tcp = (TcpBackend*)backend; 211 | free(tcp); 212 | } 213 | 214 | int listenForTcpConnection(void *backend, modbus_t *ctx) { 215 | TcpBackend *tcp = (TcpBackend*)backend; 216 | tcp->clientSocket = modbus_tcp_listen(ctx, 1); 217 | if (-1 == tcp->clientSocket) { 218 | printf("Listen returned %d (%s)\n", tcp->clientSocket, modbus_strerror(errno)); 219 | return 0; 220 | } 221 | modbus_tcp_accept(ctx, &(tcp->clientSocket)); 222 | return 1; 223 | } 224 | 225 | void closeTcpConnection(void *backend) { 226 | TcpBackend *tcp = (TcpBackend*)backend; 227 | if (tcp->clientSocket != -1) { 228 | close(tcp->clientSocket); 229 | tcp->clientSocket = -1; 230 | } 231 | } 232 | 233 | BackendParams *createTcpBackend() { 234 | TcpBackend *tcp = (TcpBackend*)malloc(sizeof(TcpBackend)); 235 | tcp->clientSocket = -1; 236 | tcp->base.setParam = &setTcpParam; 237 | tcp->base.createCtxt = &createTcpCtxt; 238 | tcp->base.del = &delTcp; 239 | tcp->base.listenForConnection = &listenForTcpConnection; 240 | tcp->base.closeConnection = &closeTcpConnection; 241 | 242 | tcp->base.type = Tcp; 243 | strcpy(tcp->ip, "0.0.0.0"); 244 | tcp->port = 502; 245 | 246 | return (BackendParams*)tcp; 247 | } 248 | 249 | #endif //MBU_COMMON_H 250 | -------------------------------------------------------------------------------- /modbus_server/modbus_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | 4 | * Copyright (c) 2013 Krzysztow 5 | 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | /* 26 | * The file is strongly based upon libmodbus/tests/random-test-server.c of libmodbus library 27 | */ 28 | 29 | #include 30 | #ifndef _MSC_VER 31 | #include 32 | #endif 33 | #include 34 | #include 35 | 36 | #include 37 | #include 38 | #include 39 | 40 | #include "mbu-common.h" 41 | 42 | #if defined(_WIN32) 43 | #include 44 | #else 45 | #include 46 | #include 47 | #include 48 | #include 49 | #endif 50 | 51 | #define NB_CONNECTION 10 52 | 53 | static modbus_t *ctx = NULL; 54 | static modbus_mapping_t *mb_mapping; 55 | 56 | static int server_socket = -1; 57 | 58 | static void close_sigint(int dummy) 59 | { 60 | if (server_socket != -1) { 61 | close(server_socket); 62 | } 63 | modbus_free(ctx); 64 | modbus_mapping_free(mb_mapping); 65 | 66 | exit(dummy); 67 | } 68 | 69 | const char DebugOpt[] = "debug"; 70 | const char TcpOptVal[] = "tcp"; 71 | const char RtuOptVal[] = "rtu"; 72 | const char DiscreteInputsNo[] = "di"; 73 | const char CoilsNo[] = "co"; 74 | const char InputRegistersNo[] = "ir"; 75 | const char HoldingRegistersNo[] = "hr"; 76 | 77 | void printUsage(const char progName[]) { 78 | printf("%s [--%s] -m{tcp|rtu}\n\t" \ 79 | "[-a] --%s=100 --%s=100 --%s=100 --%s=100\n\t" \ 80 | "[{rtu-params|tcp-params}]\n", progName, DebugOpt, DiscreteInputsNo, CoilsNo, InputRegistersNo, HoldingRegistersNo); 81 | printf("rtu-params:\n" \ 82 | "\tb=9600\n" \ 83 | "\td{7|8}=8\n" \ 84 | "\ts{1|2}=1\n" \ 85 | "\tp{none|even|odd}=even\n"); 86 | printf("tcp-params:\n" \ 87 | "\tp=502\n"); 88 | } 89 | 90 | int main(int argc, char **argv) 91 | { 92 | int c; 93 | int ok; 94 | int rc; 95 | 96 | BackendParams *backend = 0; 97 | int slaveAddr = 1; 98 | int debug = 0; 99 | int diNo = 100; 100 | int coilsNo = 100; 101 | int irNo = 100; 102 | int hrNo = 100; 103 | 104 | while (1) { 105 | int option_index = 0; 106 | static struct option long_options[] = { 107 | {DebugOpt, no_argument, 0, 0}, 108 | {DiscreteInputsNo, required_argument, 0, 0}, 109 | {CoilsNo, required_argument, 0, 0}, 110 | {InputRegistersNo, required_argument, 0, 0}, 111 | {HoldingRegistersNo, required_argument, 0, 0}, 112 | {0, 0, 0, 0} 113 | }; 114 | 115 | c = getopt_long(argc, argv, "a:b:d:m:s:p:", 116 | long_options, &option_index); 117 | if (c == -1) { 118 | break; 119 | } 120 | 121 | switch (c) { 122 | case 0: 123 | if (0 == strcmp(long_options[option_index].name, DebugOpt)) { 124 | debug = 1; 125 | } 126 | else if (0 == strcmp(long_options[option_index].name, DiscreteInputsNo)) { 127 | diNo = getInt(optarg, &ok); 128 | if (0 == ok || diNo < 0) { 129 | printf("Cannot set discrete inputs no from %s", optarg); 130 | printUsage(argv[0]); 131 | exit(EXIT_FAILURE); 132 | } 133 | } 134 | else if (0 == strcmp(long_options[option_index].name, CoilsNo)) { 135 | coilsNo = getInt(optarg, &ok); 136 | if (0 == ok || coilsNo < 0) { 137 | printf("Cannot set discrete coils no from %s", optarg); 138 | printUsage(argv[0]); 139 | exit(EXIT_FAILURE); 140 | } 141 | } 142 | else if (0 == strcmp(long_options[option_index].name, InputRegistersNo)) { 143 | irNo = getInt(optarg, &ok); 144 | if (0 == ok || irNo < 0) { 145 | printf("Cannot set input registers no from %s", optarg); 146 | printUsage(argv[0]); 147 | exit(EXIT_FAILURE); 148 | } 149 | } 150 | else if (0 == strcmp(long_options[option_index].name, HoldingRegistersNo)) { 151 | hrNo = getInt(optarg, &ok); 152 | if (0 == ok || hrNo < 0) { 153 | printf("Cannot set holding registers no from %s\n", optarg); 154 | printUsage(argv[0]); 155 | exit(EXIT_FAILURE); 156 | } 157 | } 158 | 159 | break; 160 | 161 | case 'a': { 162 | slaveAddr = getInt(optarg, &ok); 163 | if (0 == ok) { 164 | printf("Slave address (%s) is not integer!\n\n", optarg); 165 | printUsage(argv[0]); 166 | exit(EXIT_FAILURE); 167 | } 168 | } 169 | break; 170 | 171 | case 'm': 172 | if (0 == strcmp(optarg, TcpOptVal)) { 173 | backend = createTcpBackend(); 174 | } 175 | else if (0 == strcmp(optarg, RtuOptVal)) 176 | backend = createRtuBackend(); 177 | else { 178 | printf("Unrecognized connection type %s\n\n", optarg); 179 | printUsage(argv[0]); 180 | exit(EXIT_FAILURE); 181 | } 182 | break; 183 | 184 | //tcp/rtu params 185 | case 'p': 186 | case 'b': 187 | case 'd': 188 | case 's': 189 | if (0 == backend) { 190 | printf("Connection type (-m switch) has to be set before its params are provided!\n"); 191 | printUsage(argv[0]); 192 | exit(EXIT_FAILURE); 193 | } 194 | else { 195 | if (0 == backend->setParam(backend, c, optarg)) { 196 | printUsage(argv[0]); 197 | exit(EXIT_FAILURE); 198 | } 199 | } 200 | break; 201 | case '?': 202 | break; 203 | 204 | default: 205 | printf("?? getopt returned character code 0%o ??\n", c); 206 | } 207 | } 208 | 209 | if (0 == backend) { 210 | printf("No connection type was specified!\n"); 211 | printUsage(argv[0]); 212 | exit(EXIT_FAILURE); 213 | } 214 | 215 | if (1 == argc - optind) { 216 | if (Rtu == backend->type) { 217 | RtuBackend *rtuP = (RtuBackend*)backend; 218 | strcpy(rtuP->devName, argv[optind]); 219 | } 220 | else if (Tcp == backend->type) { 221 | TcpBackend *tcpP = (TcpBackend*)backend; 222 | strcpy(tcpP->ip, argv[optind]); 223 | } 224 | } 225 | else { 226 | printf("Expecting only serialport|ip as free parameter!\n"); 227 | printUsage(argv[0]); 228 | exit(EXIT_FAILURE); 229 | } 230 | 231 | //prepare mapping 232 | mb_mapping = modbus_mapping_new(coilsNo, diNo, hrNo, irNo); 233 | if (mb_mapping == NULL) { 234 | fprintf(stderr, "Failed to allocate the mapping: %s\n", 235 | modbus_strerror(errno)); 236 | exit(EXIT_FAILURE); 237 | } 238 | if (debug) 239 | printf("Ranges: \n \tCoils: 0-0x%04x\n\tDigital inputs: 0-0x%04x\n\tHolding registers: 0-0x%04x\n\tInput registers: 0-0x%04x\n", 240 | coilsNo, diNo, hrNo, irNo); 241 | 242 | if (0 == backend) { 243 | printf("No backend has been specified!\n"); 244 | printUsage(argv[0]); 245 | exit(EXIT_FAILURE); 246 | } 247 | 248 | ctx = backend->createCtxt(backend); 249 | modbus_set_debug(ctx, debug); 250 | modbus_set_slave(ctx, slaveAddr); 251 | 252 | if (Rtu == backend->type) { 253 | 254 | for(;;) { 255 | 256 | if (0 == backend->listenForConnection(backend, ctx)) { 257 | break; 258 | } 259 | 260 | for (;;) { 261 | uint8_t query[MODBUS_RTU_MAX_ADU_LENGTH]; 262 | 263 | rc = modbus_receive(ctx, query); 264 | if (rc > 0) { 265 | /* rc is the query size */ 266 | modbus_reply(ctx, query, rc, mb_mapping); 267 | } else if (rc == -1) { 268 | /* Connection closed by the client or error */ 269 | break; 270 | } 271 | } 272 | printf("Client disconnected: %s\n", modbus_strerror(errno)); 273 | 274 | backend->closeConnection(backend); 275 | } 276 | 277 | } 278 | else if (Tcp == backend->type) { 279 | uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH]; 280 | int master_socket; 281 | fd_set refset; 282 | fd_set rdset; 283 | /* Maximum file descriptor number */ 284 | int fdmax; 285 | 286 | server_socket = modbus_tcp_listen(ctx, NB_CONNECTION); 287 | if (server_socket == -1) { 288 | fprintf(stderr, "Unable to listen TCP connection\n"); 289 | modbus_free(ctx); 290 | return -1; 291 | } 292 | 293 | signal(SIGINT, close_sigint); 294 | 295 | /* Clear the reference set of socket */ 296 | FD_ZERO(&refset); 297 | /* Add the server socket */ 298 | FD_SET(server_socket, &refset); 299 | 300 | /* Keep track of the max file descriptor */ 301 | fdmax = server_socket; 302 | 303 | for (;;) { 304 | rdset = refset; 305 | if (select(fdmax+1, &rdset, NULL, NULL, NULL) == -1) { 306 | perror("Server select() failure."); 307 | close_sigint(1); 308 | } 309 | 310 | /* Run through the existing connections looking for data to be 311 | * read */ 312 | for (master_socket = 0; master_socket <= fdmax; master_socket++) { 313 | 314 | if (!FD_ISSET(master_socket, &rdset)) { 315 | continue; 316 | } 317 | 318 | if (master_socket == server_socket) { 319 | /* A client is asking a new connection */ 320 | socklen_t addrlen; 321 | struct sockaddr_in clientaddr; 322 | int newfd; 323 | 324 | /* Handle new connections */ 325 | addrlen = sizeof(clientaddr); 326 | memset(&clientaddr, 0, sizeof(clientaddr)); 327 | newfd = accept(server_socket, (struct sockaddr *)&clientaddr, &addrlen); 328 | if (newfd == -1) { 329 | perror("Server accept() error"); 330 | } else { 331 | FD_SET(newfd, &refset); 332 | 333 | if (newfd > fdmax) { 334 | /* Keep track of the maximum */ 335 | fdmax = newfd; 336 | } 337 | printf("New connection from %s:%d on socket %d\n", 338 | inet_ntoa(clientaddr.sin_addr), clientaddr.sin_port, newfd); 339 | } 340 | } else { 341 | modbus_set_socket(ctx, master_socket); 342 | rc = modbus_receive(ctx, query); 343 | if (rc > 0) { 344 | modbus_reply(ctx, query, rc, mb_mapping); 345 | } else if (rc == -1) { 346 | /* This example server in ended on connection closing or 347 | * any errors. */ 348 | printf("Connection closed on socket %d\n", master_socket); 349 | close(master_socket); 350 | 351 | /* Remove from reference set */ 352 | FD_CLR(master_socket, &refset); 353 | 354 | if (master_socket == fdmax) { 355 | fdmax--; 356 | } 357 | } 358 | } 359 | } 360 | } 361 | } 362 | 363 | modbus_mapping_free(mb_mapping); 364 | modbus_close(ctx); 365 | modbus_free(ctx); 366 | backend->del(backend); 367 | 368 | return 0; 369 | } 370 | -------------------------------------------------------------------------------- /modbus_client/modbus_client.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | 4 | * Copyright (c) 2013 Krzysztow 5 | 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include /* for printf */ 26 | #include /* for exit */ 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include "errno.h" 33 | 34 | #include "mbu-common.h" 35 | 36 | const char DebugOpt[] = "debug"; 37 | const char TcpOptVal[] = "tcp"; 38 | const char RtuOptVal[] = "rtu"; 39 | 40 | typedef enum { 41 | FuncNone = -1, 42 | 43 | ReadCoils = 0x01, 44 | ReadDiscreteInput = 0x02, 45 | ReadHoldingRegisters= 0x03, 46 | ReadInputRegisters = 0x04, 47 | WriteSingleCoil = 0x05, 48 | WriteSingleRegister = 0x06, 49 | WriteMultipleCoils = 0x0f, 50 | WriteMultipleRegisters = 0x10 51 | } FuncType; 52 | 53 | void printUsage(const char progName[]) { 54 | printf("%s [--%s] [-m {rtu|tcp}] [-a] [-c=1]\n\t" \ 55 | "[-r=100] [-t] [-o=1000] [{rtu-params|tcp-params}] serialport|host []\n", progName, DebugOpt); 56 | printf("NOTE: if first reference address starts at 0, set -0\n"); 57 | printf("f-type:\n" \ 58 | "\t(0x01) Read Coils, (0x02) Read Discrete Inputs, (0x05) Write Single Coil\n" \ 59 | "\t(0x03) Read Holding Registers, (0x04) Read Input Registers, (0x06) WriteSingle Register\n" \ 60 | "\t(0x0F) WriteMultipleCoils, (0x10) Write Multiple register\n"); 61 | printf("rtu-params:\n" \ 62 | "\tb=9600\n" \ 63 | "\td{7|8}=8\n" \ 64 | "\ts{1|2}=1\n" \ 65 | "\tp{none|even|odd}=even\n"); 66 | printf("tcp-params:\n" \ 67 | "\tp=502\n"); 68 | printf("Examples (run with default mbServer at port 1502): \n" \ 69 | "\tWrite data: \t%s --debug -mtcp -t0x10 -r0 -p1502 127.0.0.1 0x01 0x02 0x03\n" \ 70 | "\tRead that data:\t%s --debug -mtcp -t0x03 -r0 -p1502 127.0.0.1 -c3\n", progName, progName); 71 | } 72 | 73 | int main(int argc, char **argv) 74 | { 75 | int c; 76 | int ok; 77 | 78 | int debug = 0; 79 | BackendParams *backend = 0; 80 | int slaveAddr = 1; 81 | int startAddr = 100; 82 | int startReferenceAt0 = 0; 83 | int readWriteNo = 1; 84 | int fType = FuncNone; 85 | int timeout_ms = 1000; 86 | int hasDevice = 0; 87 | 88 | int isWriteFunction = 0; 89 | enum WriteDataType { 90 | DataInt, 91 | Data8Array, 92 | Data16Array 93 | } wDataType = DataInt; 94 | union Data { 95 | int dataInt; 96 | uint8_t *data8; 97 | uint16_t *data16; 98 | } data; 99 | 100 | while (1) { 101 | int option_index = 0; 102 | static struct option long_options[] = { 103 | {DebugOpt, no_argument, 0, 0}, 104 | {0, 0, 0, 0} 105 | }; 106 | 107 | c = getopt_long(argc, argv, "a:b:d:c:m:r:s:t:p:o:0", 108 | long_options, &option_index); 109 | if (c == -1) { 110 | break; 111 | } 112 | 113 | switch (c) { 114 | case 0: 115 | if (0 == strcmp(long_options[option_index].name, DebugOpt)) { 116 | debug = 1; 117 | } 118 | break; 119 | 120 | case 'a': { 121 | slaveAddr = getInt(optarg, &ok); 122 | if (0 == ok) { 123 | printf("Slave address (%s) is not integer!\n\n", optarg); 124 | printUsage(argv[0]); 125 | exit(EXIT_FAILURE); 126 | } 127 | } 128 | break; 129 | 130 | case 'c': { 131 | readWriteNo = getInt(optarg, &ok); 132 | if (0 == ok) { 133 | printf("# elements to read/write (%s) is not integer!\n\n", optarg); 134 | printUsage(argv[0]); 135 | exit(EXIT_FAILURE); 136 | } 137 | } 138 | break; 139 | 140 | case 'm': 141 | if (0 == strcmp(optarg, TcpOptVal)) { 142 | backend = createTcpBackend((TcpBackend*)malloc(sizeof(TcpBackend))); 143 | } 144 | else if (0 == strcmp(optarg, RtuOptVal)) 145 | backend = createRtuBackend((RtuBackend*)malloc(sizeof(RtuBackend))); 146 | else { 147 | printf("Unrecognized connection type %s\n\n", optarg); 148 | printUsage(argv[0]); 149 | exit(EXIT_FAILURE); 150 | } 151 | break; 152 | 153 | case 'r': { 154 | startAddr = getInt(optarg, &ok); 155 | if (0 == ok) { 156 | printf("Start address (%s) is not integer!\n\n", optarg); 157 | printUsage(argv[0]); 158 | exit(EXIT_FAILURE); 159 | } 160 | } 161 | break; 162 | 163 | case 't': { 164 | fType = getInt(optarg, &ok); 165 | if (0 == ok) { 166 | printf("Function type (%s) is not integer!\n\n", optarg); 167 | printUsage(argv[0]); 168 | exit(EXIT_FAILURE); 169 | } 170 | } 171 | break; 172 | 173 | case 'o': { 174 | timeout_ms = getInt(optarg, &ok); 175 | if (0 == ok) { 176 | printf("Timeout (%s) is not integer!\n\n", optarg); 177 | printUsage(argv[0]); 178 | exit(EXIT_FAILURE); 179 | } 180 | printf("Timeout set to %d\r\n", timeout_ms); 181 | } 182 | break; 183 | 184 | case '0': 185 | startReferenceAt0 = 1; 186 | break; 187 | //tcp/rtu params 188 | case 'p': 189 | case 'b': 190 | case 'd': 191 | case 's': 192 | if (0 == backend) { 193 | printf("Connection type (-m switch) has to be set before its params are provided!\n"); 194 | printUsage(argv[0]); 195 | exit(EXIT_FAILURE); 196 | } 197 | else { 198 | if (0 == backend->setParam(backend, c, optarg)) { 199 | printUsage(argv[0]); 200 | exit(EXIT_FAILURE); 201 | } 202 | } 203 | break; 204 | case '?': 205 | break; 206 | 207 | default: 208 | printf("?? getopt returned character code 0%o ??\n", c); 209 | } 210 | } 211 | 212 | if (0 == backend) { 213 | printf("No connection type was specified!\n"); 214 | printUsage(argv[0]); 215 | exit(EXIT_FAILURE); 216 | } 217 | 218 | if (1 == startReferenceAt0) { 219 | startAddr--; 220 | } 221 | 222 | //choose write data type 223 | switch (fType) { 224 | case(ReadCoils): 225 | wDataType = Data8Array; 226 | break; 227 | case(ReadDiscreteInput): 228 | wDataType = DataInt; 229 | break; 230 | case(ReadHoldingRegisters): 231 | case(ReadInputRegisters): 232 | wDataType = Data16Array; 233 | break; 234 | case(WriteSingleCoil): 235 | case(WriteSingleRegister): 236 | wDataType = DataInt; 237 | isWriteFunction = 1; 238 | break; 239 | case(WriteMultipleCoils): 240 | wDataType = Data8Array; 241 | isWriteFunction = 1; 242 | break; 243 | case(WriteMultipleRegisters): 244 | wDataType = Data16Array; 245 | isWriteFunction = 1; 246 | break; 247 | default: 248 | printf("No correct function type chosen"); 249 | printUsage(argv[0]); 250 | exit(EXIT_FAILURE); 251 | } 252 | 253 | if (isWriteFunction) { 254 | int dataNo = argc - optind - 1; 255 | /*if (-1 != readWriteNo && dataNo != readWriteNo) { 256 | printf("Write count specified, not equal to data values count!"); 257 | printUsage(argv[0]); 258 | exit(EXIT_FAILURE); 259 | } 260 | else*/ readWriteNo = dataNo; 261 | } 262 | 263 | //allocate buffer for data 264 | switch (wDataType) { 265 | case (DataInt): 266 | //no need to alloc anything 267 | break; 268 | case (Data8Array): 269 | data.data8 = malloc(readWriteNo * sizeof(uint8_t)); 270 | break; 271 | case (Data16Array): 272 | data.data16 = malloc(readWriteNo * sizeof(uint16_t)); 273 | break; 274 | default: 275 | printf("Data alloc error!\n"); 276 | exit(EXIT_FAILURE); 277 | } 278 | 279 | int wDataIdx = 0; 280 | if (1 == debug && 1 == isWriteFunction) 281 | printf("Data to write: "); 282 | if (optind < argc) { 283 | while (optind < argc) { 284 | if (0 == hasDevice) { 285 | if (0 != backend) { 286 | if (Rtu == backend->type) { 287 | RtuBackend *rtuP = (RtuBackend*)backend; 288 | strcpy(rtuP->devName, argv[optind]); 289 | hasDevice = 1; 290 | } 291 | else if (Tcp == backend->type) { 292 | TcpBackend *tcpP = (TcpBackend*)backend; 293 | strcpy(tcpP->ip, argv[optind]); 294 | hasDevice = 1; 295 | } 296 | } 297 | } 298 | else {//setting write data buffer 299 | switch (wDataType) { 300 | case (DataInt): 301 | data.dataInt = getInt(argv[optind], 0); 302 | if (debug) 303 | printf("0x%x", data.dataInt); 304 | break; 305 | case (Data8Array): { 306 | data.data8[wDataIdx] = getInt(argv[optind], 0); 307 | if (debug) 308 | printf("0x%02x ", data.data8[wDataIdx]); 309 | } 310 | break; 311 | case (Data16Array): { 312 | data.data16[wDataIdx] = getInt(argv[optind], 0); 313 | if (debug) 314 | printf("0x%04x ", data.data16[wDataIdx]); 315 | } 316 | break; 317 | } 318 | wDataIdx++; 319 | } 320 | optind++; 321 | } 322 | } 323 | if (1 == debug && 1 == isWriteFunction) 324 | printf("\n"); 325 | 326 | //create modbus context, and preapare it 327 | modbus_t *ctx = backend->createCtxt(backend); 328 | modbus_set_debug(ctx, debug); 329 | modbus_set_slave(ctx, slaveAddr); 330 | 331 | //issue the request 332 | int ret = -1; 333 | if (modbus_connect(ctx) == -1) { 334 | fprintf(stderr, "Connection failed: %s\n", 335 | modbus_strerror(errno)); 336 | modbus_free(ctx); 337 | return -1; 338 | } else { 339 | switch (fType) { 340 | case(ReadCoils): 341 | ret = modbus_read_bits(ctx, startAddr, readWriteNo, data.data8); 342 | break; 343 | case(ReadDiscreteInput): 344 | printf("ReadDiscreteInput: not implemented yet!\n"); 345 | wDataType = DataInt; 346 | break; 347 | case(ReadHoldingRegisters): 348 | ret = modbus_read_registers(ctx, startAddr, readWriteNo, data.data16); 349 | break; 350 | case(ReadInputRegisters): 351 | ret = modbus_read_input_registers(ctx, startAddr, readWriteNo, data.data16); 352 | break; 353 | case(WriteSingleCoil): 354 | ret = modbus_write_bit(ctx, startAddr, data.dataInt); 355 | break; 356 | case(WriteSingleRegister): 357 | ret = modbus_write_register(ctx, startAddr, data.dataInt); 358 | break; 359 | case(WriteMultipleCoils): 360 | ret = modbus_write_bits(ctx, startAddr, readWriteNo, data.data8); 361 | break; 362 | case(WriteMultipleRegisters): 363 | ret = modbus_write_registers(ctx, startAddr, readWriteNo, data.data16); 364 | break; 365 | default: 366 | printf("No correct function type chosen"); 367 | printUsage(argv[0]); 368 | exit(EXIT_FAILURE); 369 | } 370 | } 371 | 372 | if (ret == readWriteNo) {//success 373 | if (isWriteFunction) 374 | printf("SUCCESS: written %d elements!\n", readWriteNo); 375 | else { 376 | printf("SUCCESS: read %d of elements:\n\tData: ", readWriteNo); 377 | int i = 0; 378 | if (DataInt == wDataType) { 379 | printf("0x%04x\n", data.dataInt); 380 | } 381 | else { 382 | const char Format8[] = "0x%02x "; 383 | const char Format16[] = "0x%04x "; 384 | const char *format = ((Data8Array == wDataType) ? Format8 : Format16); 385 | for (; i < readWriteNo; ++i) { 386 | printf(format, (Data8Array == wDataType) ? data.data8[i] : data.data16[i]); 387 | } 388 | printf("\n"); 389 | } 390 | } 391 | } 392 | else { 393 | printf("ERROR occured!\n"); 394 | modbus_strerror(errno); 395 | } 396 | 397 | //cleanup 398 | modbus_close(ctx); 399 | modbus_free(ctx); 400 | backend->del(backend); 401 | 402 | switch (wDataType) { 403 | case (DataInt): 404 | //nothing to be done 405 | break; 406 | case (Data8Array): 407 | free(data.data8); 408 | break; 409 | case (Data16Array): 410 | free(data.data16); 411 | break; 412 | } 413 | 414 | exit(EXIT_SUCCESS); 415 | } 416 | --------------------------------------------------------------------------------