//----------------------------------------------------------------------------- // Copyright 2015 Thiago Alves // // Based on the LDmicro software by Jonathan Westhues // This file is part of the OpenPLC Software Stack. // // OpenPLC is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // OpenPLC is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with OpenPLC. If not, see . //------ // // This file is responsible for parse and discovery of slave devices by parsing // the mbconfig.cfg file. This code also updates OpenPLC internal buffers with // the data queried from the slave devices. // Thiago Alves, Jul 2018 //----------------------------------------------------------------------------- #include #include #include #include #include #include #include #include #include #include #include #include "ladder.h" #define MB_TCP 1 #define MB_RTU 2 #define MAX_MB_IO 400 using namespace std; uint8_t bool_input_buf[MAX_MB_IO]; uint8_t bool_output_buf[MAX_MB_IO]; uint16_t int_input_buf[MAX_MB_IO]; uint16_t int_output_buf[MAX_MB_IO]; pthread_mutex_t ioLock; struct MB_address { uint16_t start_address; uint16_t num_regs; }; struct MB_device { modbus_t *mb_ctx; char dev_name[100]; uint8_t protocol; char dev_address[100]; uint16_t ip_port; int rtu_baud; char rtu_parity; int rtu_data_bit; int rtu_stop_bit; uint8_t dev_id; bool isConnected; struct MB_address discrete_inputs; struct MB_address coils; struct MB_address input_registers; struct MB_address holding_read_registers; struct MB_address holding_registers; }; struct MB_device *mb_devices; uint8_t num_devices; uint16_t polling_period = 100; uint16_t timeout = 1000; //----------------------------------------------------------------------------- // Finds the data between the separators on the line provided //----------------------------------------------------------------------------- void getData(char *line, char *buf, char separator1, char separator2) { int i=0, j=0; buf[j] = '\0'; while (line[i] != separator1 && line[i] != '\0') { i++; } i++; while (line[i] != separator2 && line[i] != '\0') { buf[j] = line[i]; i++; j++; buf[j] = '\0'; } } //----------------------------------------------------------------------------- // Get the number of the Modbus device //----------------------------------------------------------------------------- int getDeviceNumber(char *line) { char temp[5]; int i = 0, j = 6; while (line[j] != '.') { temp[i] = line[j]; i++; j++; temp[i] = '\0'; } return(atoi(temp)); } //----------------------------------------------------------------------------- // get the type of function or parameter for the Modbus device //----------------------------------------------------------------------------- void getFunction(char *line, char *parameter) { int i = 0, j = 0; while (line[j] != '.') { j++; } j++; while (line[j] != ' ' && line[j] != '=' && line[j] != '(') { parameter[i] = line[j]; i++; j++; parameter[i] = '\0'; } } void parseConfig() { string line; char line_str[1024]; ifstream cfgfile("mbconfig.cfg"); if (cfgfile.is_open()) { while (getline(cfgfile, line)) { strncpy(line_str, line.c_str(), 1024); if (line_str[0] != '#' && strlen(line_str) > 1) { if (!strncmp(line_str, "Num_Devices", 11)) { char temp_buffer[5]; getData(line_str, temp_buffer, '"', '"'); num_devices = atoi(temp_buffer); mb_devices = (struct MB_device *)malloc(num_devices*sizeof(struct MB_device)); } else if (!strncmp(line_str, "Polling_Period", 14)) { char temp_buffer[10]; getData(line_str, temp_buffer, '"', '"'); polling_period = atoi(temp_buffer); } else if (!strncmp(line_str, "Timeout", 7)) { char temp_buffer[10]; getData(line_str, temp_buffer, '"', '"'); timeout = atoi(temp_buffer); } else if (!strncmp(line_str, "device", 6)) { int deviceNumber = getDeviceNumber(line_str); char functionType[100]; getFunction(line_str, functionType); if (!strncmp(functionType, "name", 4)) { getData(line_str, mb_devices[deviceNumber].dev_name, '"', '"'); } else if (!strncmp(functionType, "protocol", 8)) { char temp_buffer[5]; getData(line_str, temp_buffer, '"', '"'); if (!strncmp(temp_buffer, "TCP", 3)) mb_devices[deviceNumber].protocol = MB_TCP; else if (!strncmp(temp_buffer, "RTU", 3)) mb_devices[deviceNumber].protocol = MB_RTU; } else if (!strncmp(functionType, "slave_id", 8)) { char temp_buffer[5]; getData(line_str, temp_buffer, '"', '"'); mb_devices[deviceNumber].dev_id = atoi(temp_buffer); } else if (!strncmp(functionType, "address", 7)) { getData(line_str, mb_devices[deviceNumber].dev_address, '"', '"'); } else if (!strncmp(functionType, "IP_Port", 7)) { char temp_buffer[6]; getData(line_str, temp_buffer, '"', '"'); mb_devices[deviceNumber].ip_port = atoi(temp_buffer); } else if (!strncmp(functionType, "RTU_Baud_Rate", 13)) { char temp_buffer[10]; getData(line_str, temp_buffer, '"', '"'); mb_devices[deviceNumber].rtu_baud = atoi(temp_buffer); } else if (!strncmp(functionType, "RTU_Parity", 10)) { char temp_buffer[3]; getData(line_str, temp_buffer, '"', '"'); mb_devices[deviceNumber].rtu_parity = temp_buffer[0]; } else if (!strncmp(functionType, "RTU_Data_Bits", 13)) { char temp_buffer[6]; getData(line_str, temp_buffer, '"', '"'); mb_devices[deviceNumber].rtu_data_bit = atoi(temp_buffer); } else if (!strncmp(functionType, "RTU_Stop_Bits", 13)) { char temp_buffer[20]; getData(line_str, temp_buffer, '"', '"'); mb_devices[deviceNumber].rtu_stop_bit = atoi(temp_buffer); } else if (!strncmp(functionType, "Discrete_Inputs_Start", 21)) { char temp_buffer[10]; getData(line_str, temp_buffer, '"', '"'); mb_devices[deviceNumber].discrete_inputs.start_address = atoi(temp_buffer); } else if (!strncmp(functionType, "Discrete_Inputs_Size", 20)) { char temp_buffer[10]; getData(line_str, temp_buffer, '"', '"'); mb_devices[deviceNumber].discrete_inputs.num_regs = atoi(temp_buffer); } else if (!strncmp(functionType, "Coils_Start", 11)) { char temp_buffer[10]; getData(line_str, temp_buffer, '"', '"'); mb_devices[deviceNumber].coils.start_address = atoi(temp_buffer); } else if (!strncmp(functionType, "Coils_Size", 10)) { char temp_buffer[10]; getData(line_str, temp_buffer, '"', '"'); mb_devices[deviceNumber].coils.num_regs = atoi(temp_buffer); } else if (!strncmp(functionType, "Input_Registers_Start", 21)) { char temp_buffer[10]; getData(line_str, temp_buffer, '"', '"'); mb_devices[deviceNumber].input_registers.start_address = atoi(temp_buffer); } else if (!strncmp(functionType, "Input_Registers_Size", 20)) { char temp_buffer[10]; getData(line_str, temp_buffer, '"', '"'); mb_devices[deviceNumber].input_registers.num_regs = atoi(temp_buffer); } else if (!strncmp(functionType, "Holding_Registers_Read_Start", 28)) { char temp_buffer[10]; getData(line_str, temp_buffer, '"', '"'); mb_devices[deviceNumber].holding_read_registers.start_address = atoi(temp_buffer); } else if (!strncmp(functionType, "Holding_Registers_Read_Size", 27)) { char temp_buffer[10]; getData(line_str, temp_buffer, '"', '"'); mb_devices[deviceNumber].holding_read_registers.num_regs = atoi(temp_buffer); } else if (!strncmp(functionType, "Holding_Registers_Start", 23)) { char temp_buffer[10]; getData(line_str, temp_buffer, '"', '"'); mb_devices[deviceNumber].holding_registers.start_address = atoi(temp_buffer); } else if (!strncmp(functionType, "Holding_Registers_Size", 22)) { char temp_buffer[10]; getData(line_str, temp_buffer, '"', '"'); mb_devices[deviceNumber].holding_registers.num_regs = atoi(temp_buffer); } } } } } else { unsigned char log_msg[1000]; sprintf(log_msg, "Skipping configuration of Slave Devices (mbconfig.cfg file not found)\n"); log(log_msg); } //Parser Debug ///* for (int i = 0; i < num_devices; i++) { printf("Device %d\n", i); printf("Name: %s\n", mb_devices[i].dev_name); printf("Protocol: %d\n", mb_devices[i].protocol); printf("Address: %s\n", mb_devices[i].dev_address); printf("IP Port: %d\n", mb_devices[i].ip_port); printf("Baud rate: %d\n", mb_devices[i].rtu_baud); printf("Parity: %c\n", mb_devices[i].rtu_parity); printf("Data Bits: %d\n", mb_devices[i].rtu_data_bit); printf("Stop Bits: %d\n", mb_devices[i].rtu_stop_bit); printf("DI Start: %d\n", mb_devices[i].discrete_inputs.start_address); printf("DI Size: %d\n", mb_devices[i].discrete_inputs.num_regs); printf("Coils Start: %d\n", mb_devices[i].coils.start_address); printf("Coils Size: %d\n", mb_devices[i].coils.num_regs); printf("IR Start: %d\n", mb_devices[i].input_registers.start_address); printf("IR Size: %d\n", mb_devices[i].input_registers.num_regs); printf("HR Start: %d\n", mb_devices[i].holding_registers.start_address); printf("HR Size: %d\n", mb_devices[i].holding_registers.num_regs); printf("\n\n"); } //*/ } //----------------------------------------------------------------------------- // Thread to poll each slave device //----------------------------------------------------------------------------- void *querySlaveDevices(void *arg) { while (run_openplc) { unsigned char log_msg[1000]; uint16_t bool_input_index = 0; uint16_t bool_output_index = 0; uint16_t int_input_index = 0; uint16_t int_output_index = 0; for (int i = 0; i < num_devices; i++) { //Verify if device is connected if (!mb_devices[i].isConnected) { sprintf(log_msg, "Device %s is disconnected. Attempting to reconnect...\n", mb_devices[i].dev_name); log(log_msg); if (modbus_connect(mb_devices[i].mb_ctx) == -1) { sprintf(log_msg, "Connection failed on MB device %s: %s\n", mb_devices[i].dev_name, modbus_strerror(errno)); log(log_msg); if (special_functions[2] != NULL) *special_functions[2]++; // Because this device is not connected, we skip those input registers bool_input_index += (mb_devices[i].discrete_inputs.num_regs); int_input_index += (mb_devices[i].input_registers.num_regs); int_input_index += (mb_devices[i].holding_read_registers.num_regs); bool_output_index += (mb_devices[i].coils.num_regs); int_output_index += (mb_devices[i].holding_registers.num_regs); } else { sprintf(log_msg, "Connected to MB device %s\n", mb_devices[i].dev_name); log(log_msg); mb_devices[i].isConnected = true; } } if (mb_devices[i].isConnected) { struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = (1000*1000*1000*28)/mb_devices[i].rtu_baud; //Read discrete inputs if (mb_devices[i].discrete_inputs.num_regs != 0) { uint8_t *tempBuff; tempBuff = (uint8_t *)malloc(mb_devices[i].discrete_inputs.num_regs); nanosleep(&ts, NULL); int return_val = modbus_read_input_bits(mb_devices[i].mb_ctx, mb_devices[i].discrete_inputs.start_address, mb_devices[i].discrete_inputs.num_regs, tempBuff); if (return_val == -1) { if (mb_devices[i].protocol != MB_RTU) { modbus_close(mb_devices[i].mb_ctx); mb_devices[i].isConnected = false; } sprintf(log_msg, "Modbus Read Discrete Input Registers failed on MB device %s: %s\n", mb_devices[i].dev_name, modbus_strerror(errno)); log(log_msg); bool_input_index += (mb_devices[i].discrete_inputs.num_regs); if (special_functions[2] != NULL) *special_functions[2]++; } else { pthread_mutex_lock(&ioLock); for (int j = 0; j < return_val; j++) { bool_input_buf[bool_input_index] = tempBuff[j]; bool_input_index++; } pthread_mutex_unlock(&ioLock); } free(tempBuff); } //Write coils if (mb_devices[i].coils.num_regs != 0) { uint8_t *tempBuff; tempBuff = (uint8_t *)malloc(mb_devices[i].coils.num_regs); pthread_mutex_lock(&ioLock); for (int j = 0; j < mb_devices[i].coils.num_regs; j++) { tempBuff[j] = bool_output_buf[bool_output_index]; bool_output_index++; } pthread_mutex_unlock(&ioLock); nanosleep(&ts, NULL); int return_val = modbus_write_bits(mb_devices[i].mb_ctx, mb_devices[i].coils.start_address, mb_devices[i].coils.num_regs, tempBuff); if (return_val == -1) { if (mb_devices[i].protocol != MB_RTU) { modbus_close(mb_devices[i].mb_ctx); mb_devices[i].isConnected = false; } sprintf(log_msg, "Modbus Write Coils failed on MB device %s: %s\n", mb_devices[i].dev_name, modbus_strerror(errno)); log(log_msg); if (special_functions[2] != NULL) *special_functions[2]++; } free(tempBuff); } //Read input registers if (mb_devices[i].input_registers.num_regs != 0) { uint16_t *tempBuff; tempBuff = (uint16_t *)malloc(2*mb_devices[i].input_registers.num_regs); nanosleep(&ts, NULL); int return_val = modbus_read_input_registers( mb_devices[i].mb_ctx, mb_devices[i].input_registers.start_address, mb_devices[i].input_registers.num_regs, tempBuff); if (return_val == -1) { if (mb_devices[i].protocol != MB_RTU) { modbus_close(mb_devices[i].mb_ctx); mb_devices[i].isConnected = false; } sprintf(log_msg, "Modbus Read Input Registers failed on MB device %s: %s\n", mb_devices[i].dev_name, modbus_strerror(errno)); log(log_msg); int_input_index += (mb_devices[i].input_registers.num_regs); if (special_functions[2] != NULL) *special_functions[2]++; } else { pthread_mutex_lock(&ioLock); for (int j = 0; j < return_val; j++) { int_input_buf[int_input_index] = tempBuff[j]; int_input_index++; } int_input_index++; pthread_mutex_unlock(&ioLock); } free(tempBuff); } //Read holding registers if (mb_devices[i].holding_read_registers.num_regs != 0) { uint16_t *tempBuff; tempBuff = (uint16_t *)malloc(2*mb_devices[i].holding_read_registers.num_regs); nanosleep(&ts, NULL); int return_val = modbus_read_registers(mb_devices[i].mb_ctx, mb_devices[i].holding_read_registers.start_address, mb_devices[i].holding_read_registers.num_regs, tempBuff); if (return_val == -1) { if (mb_devices[i].protocol != MB_RTU) { modbus_close(mb_devices[i].mb_ctx); mb_devices[i].isConnected = false; } sprintf(log_msg, "Modbus Read Holding Registers failed on MB device %s: %s\n", mb_devices[i].dev_name, modbus_strerror(errno)); log(log_msg); int_input_index += (mb_devices[i].holding_read_registers.num_regs); if (special_functions[2] != NULL) *special_functions[2]++; } else { pthread_mutex_lock(&ioLock); for (int j = 0; j < return_val; j++) { int_input_buf[int_input_index] = tempBuff[j]; int_input_index++; } pthread_mutex_unlock(&ioLock); } free(tempBuff); } //Write holding registers if (mb_devices[i].holding_registers.num_regs != 0) { uint16_t *tempBuff; tempBuff = (uint16_t *)malloc(2*mb_devices[i].holding_registers.num_regs); pthread_mutex_lock(&ioLock); for (int j = 0; j < mb_devices[i].holding_registers.num_regs; j++) { tempBuff[j] = int_output_buf[int_output_index]; int_output_index++; } pthread_mutex_unlock(&ioLock); nanosleep(&ts, NULL); int return_val = modbus_write_registers(mb_devices[i].mb_ctx, mb_devices[i].holding_registers.start_address, mb_devices[i].holding_registers.num_regs, tempBuff); if (return_val == -1) { if (mb_devices[i].protocol != MB_RTU) { modbus_close(mb_devices[i].mb_ctx); mb_devices[i].isConnected = false; } sprintf(log_msg, "Modbus Write Holding Registers failed on MB device %s: %s\n", mb_devices[i].dev_name, modbus_strerror(errno)); log(log_msg); if (special_functions[2] != NULL) *special_functions[2]++; } free(tempBuff); } } } sleepms(polling_period); } } //----------------------------------------------------------------------------- // This function is called by the main OpenPLC routine when it is initializing. // Modbus master initialization procedures are here. //----------------------------------------------------------------------------- void initializeMB() { parseConfig(); for (int i = 0; i < num_devices; i++) { if (mb_devices[i].protocol == MB_TCP) { mb_devices[i].mb_ctx = modbus_new_tcp(mb_devices[i].dev_address, mb_devices[i].ip_port); } else if (mb_devices[i].protocol == MB_RTU) { mb_devices[i].mb_ctx = modbus_new_rtu( mb_devices[i].dev_address, mb_devices[i].rtu_baud, mb_devices[i].rtu_parity, mb_devices[i].rtu_data_bit, mb_devices[i].rtu_stop_bit); } //slave id modbus_set_slave(mb_devices[i].mb_ctx, mb_devices[i].dev_id); //timeout uint32_t to_sec = timeout / 1000; uint32_t to_usec = (timeout % 1000) * 1000; modbus_set_response_timeout(mb_devices[i].mb_ctx, to_sec, to_usec); } //Initialize comm error counter if (special_functions[2] != NULL) *special_functions[2] = 0; if (num_devices > 0) { pthread_t thread; int ret = pthread_create(&thread, NULL, querySlaveDevices, NULL); if (ret==0) { pthread_detach(thread); } } } //----------------------------------------------------------------------------- // This function is called by the OpenPLC in a loop. Here the internal buffers // must be updated to reflect the actual Input state. //----------------------------------------------------------------------------- void updateBuffersIn_MB() { pthread_mutex_lock(&ioLock); for (int i = 0; i < MAX_MB_IO; i++) { if (bool_input[100+(i/8)][i%8] != NULL) *bool_input[100+(i/8)][i%8] = bool_input_buf[i]; if (int_input[100+i] != NULL) *int_input[100+i] = int_input_buf[i]; } pthread_mutex_unlock(&ioLock); } //----------------------------------------------------------------------------- // This function is called by the OpenPLC in a loop. Here the internal buffers // must be updated to reflect the actual Output state. //----------------------------------------------------------------------------- void updateBuffersOut_MB() { pthread_mutex_lock(&ioLock); for (int i = 0; i < MAX_MB_IO; i++) { if (bool_output[100+(i/8)][i%8] != NULL) bool_output_buf[i] = *bool_output[100+(i/8)][i%8]; if (int_output[100+i] != NULL) int_output_buf[i] = *int_output[100+i]; } pthread_mutex_unlock(&ioLock); }