Edouard@1912: /* File generated by Beremiz (PlugGenerate_C method of Modbus plugin) */ Edouard@1912: Edouard@1912: /* Edouard@1912: * Copyright (c) 2016 Mario de Sousa (msousa@fe.up.pt) Edouard@1912: * Edouard@1912: * This file is part of the Modbus library for Beremiz and matiec. Edouard@1912: * Edouard@1912: * This Modbus library is free software: you can redistribute it and/or modify Edouard@1912: * it under the terms of the GNU Lesser General Public License as published by Edouard@2019: * the Free Software Foundation, either version 2 of the License, or Edouard@1912: * (at your option) any later version. Edouard@1912: * Edouard@1912: * This program is distributed in the hope that it will be useful, but Edouard@1912: * WITHOUT ANY WARRANTY; without even the implied warranty of Edouard@1912: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser Edouard@1912: * General Public License for more details. Edouard@1912: * Edouard@1912: * You should have received a copy of the GNU Lesser General Public License Edouard@1912: * along with this Modbus library. If not, see . Edouard@1912: * Edouard@1912: * This code is made available on the understanding that it will not be Edouard@1912: * used in safety-critical situations without a full and competent review. Edouard@1912: */ Edouard@1912: Edouard@1912: Edouard@1912: #include Edouard@1912: #include /* required for memcpy() */ Edouard@2683: #include msousa@2647: #include msousa@2647: #include msousa@2687: #include /* required for pause() */ Edouard@1912: #include "mb_slave_and_master.h" Edouard@1912: #include "MB_%(locstr)s.h" Edouard@1912: Edouard@1912: Edouard@1912: #define MAX_MODBUS_ERROR_CODE 11 Edouard@1912: static const char *modbus_error_messages[MAX_MODBUS_ERROR_CODE+1] = { Edouard@1912: /* 0 */ "", /* un-used -> no error! */ Edouard@1912: /* 1 */ "illegal/unsuported function", Edouard@1912: /* 2 */ "illegal data address", Edouard@1912: /* 3 */ "illegal data value", Edouard@1912: /* 4 */ "slave device failure", Edouard@1912: /* 5 */ "acknowledge -> slave intends to reply later", Edouard@1912: /* 6 */ "slave device busy", Edouard@1912: /* 7 */ "negative acknowledge", Edouard@1912: /* 8 */ "memory parity error", Edouard@1912: /* 9 */ "", /* undefined by Modbus */ Edouard@1912: /* 10*/ "gateway path unavalilable", Edouard@1912: /* 11*/ "gateway target device failed to respond" Edouard@1912: }; Edouard@1912: Edouard@1912: Edouard@1912: /* Execute a modbus client transaction/request */ Edouard@1912: static int __execute_mb_request(int request_id){ Edouard@1912: switch (client_requests[request_id].mb_function){ Edouard@1912: Edouard@1912: case 1: /* read coils */ Edouard@1912: return read_output_bits(client_requests[request_id].slave_id, Edouard@1912: client_requests[request_id].address, Edouard@1912: client_requests[request_id].count, Edouard@1912: client_requests[request_id].coms_buffer, Edouard@1912: (int) client_requests[request_id].count, Edouard@1912: client_nodes[client_requests[request_id].client_node_id].mb_nd, Edouard@1912: client_requests[request_id].retries, msousa@2713: &(client_requests[request_id].mb_error_code), Edouard@1912: &(client_requests[request_id].resp_timeout), Edouard@1912: &(client_requests[request_id].coms_buf_mutex)); Edouard@1912: Edouard@1912: case 2: /* read discrete inputs */ Edouard@1912: return read_input_bits( client_requests[request_id].slave_id, Edouard@1912: client_requests[request_id].address, Edouard@1912: client_requests[request_id].count, Edouard@1912: client_requests[request_id].coms_buffer, Edouard@1912: (int) client_requests[request_id].count, Edouard@1912: client_nodes[client_requests[request_id].client_node_id].mb_nd, Edouard@1912: client_requests[request_id].retries, msousa@2713: &(client_requests[request_id].mb_error_code), Edouard@1912: &(client_requests[request_id].resp_timeout), Edouard@1912: &(client_requests[request_id].coms_buf_mutex)); Edouard@1912: Edouard@1912: case 3: /* read holding registers */ Edouard@1912: return read_output_words(client_requests[request_id].slave_id, Edouard@1912: client_requests[request_id].address, Edouard@1912: client_requests[request_id].count, Edouard@1912: client_requests[request_id].coms_buffer, Edouard@1912: (int) client_requests[request_id].count, Edouard@1912: client_nodes[client_requests[request_id].client_node_id].mb_nd, Edouard@1912: client_requests[request_id].retries, msousa@2713: &(client_requests[request_id].mb_error_code), Edouard@1912: &(client_requests[request_id].resp_timeout), Edouard@1912: &(client_requests[request_id].coms_buf_mutex)); Edouard@1912: Edouard@1912: case 4: /* read input registers */ Edouard@1912: return read_input_words(client_requests[request_id].slave_id, Edouard@1912: client_requests[request_id].address, Edouard@1912: client_requests[request_id].count, Edouard@1912: client_requests[request_id].coms_buffer, Edouard@1912: (int) client_requests[request_id].count, Edouard@1912: client_nodes[client_requests[request_id].client_node_id].mb_nd, Edouard@1912: client_requests[request_id].retries, msousa@2713: &(client_requests[request_id].mb_error_code), Edouard@1912: &(client_requests[request_id].resp_timeout), Edouard@1912: &(client_requests[request_id].coms_buf_mutex)); Edouard@1912: Edouard@1912: case 5: /* write single coil */ Edouard@1912: return write_output_bit(client_requests[request_id].slave_id, Edouard@1912: client_requests[request_id].address, Edouard@1912: client_requests[request_id].coms_buffer[0], Edouard@1912: client_nodes[client_requests[request_id].client_node_id].mb_nd, Edouard@1912: client_requests[request_id].retries, msousa@2713: &(client_requests[request_id].mb_error_code), Edouard@1912: &(client_requests[request_id].resp_timeout), Edouard@1912: &(client_requests[request_id].coms_buf_mutex)); Edouard@1912: Edouard@1912: case 6: /* write single register */ Edouard@1912: return write_output_word(client_requests[request_id].slave_id, Edouard@1912: client_requests[request_id].address, Edouard@1912: client_requests[request_id].coms_buffer[0], Edouard@1912: client_nodes[client_requests[request_id].client_node_id].mb_nd, Edouard@1912: client_requests[request_id].retries, msousa@2713: &(client_requests[request_id].mb_error_code), Edouard@1912: &(client_requests[request_id].resp_timeout), Edouard@1912: &(client_requests[request_id].coms_buf_mutex)); Edouard@1912: Edouard@1912: case 7: break; /* function not yet supported */ Edouard@1912: case 8: break; /* function not yet supported */ Edouard@1912: case 9: break; /* function not yet supported */ Edouard@1912: case 10: break; /* function not yet supported */ Edouard@1912: case 11: break; /* function not yet supported */ Edouard@1912: case 12: break; /* function not yet supported */ Edouard@1912: case 13: break; /* function not yet supported */ Edouard@1912: case 14: break; /* function not yet supported */ Edouard@1912: Edouard@1912: case 15: /* write multiple coils */ Edouard@1912: return write_output_bits(client_requests[request_id].slave_id, Edouard@1912: client_requests[request_id].address, Edouard@1912: client_requests[request_id].count, Edouard@1912: client_requests[request_id].coms_buffer, Edouard@1912: client_nodes[client_requests[request_id].client_node_id].mb_nd, Edouard@1912: client_requests[request_id].retries, msousa@2713: &(client_requests[request_id].mb_error_code), Edouard@1912: &(client_requests[request_id].resp_timeout), Edouard@1912: &(client_requests[request_id].coms_buf_mutex)); Edouard@1912: Edouard@1912: case 16: /* write multiple registers */ Edouard@1912: return write_output_words(client_requests[request_id].slave_id, Edouard@1912: client_requests[request_id].address, Edouard@1912: client_requests[request_id].count, Edouard@1912: client_requests[request_id].coms_buffer, Edouard@1912: client_nodes[client_requests[request_id].client_node_id].mb_nd, Edouard@1912: client_requests[request_id].retries, msousa@2713: &(client_requests[request_id].mb_error_code), Edouard@1912: &(client_requests[request_id].resp_timeout), Edouard@1912: &(client_requests[request_id].coms_buf_mutex)); Edouard@1912: Edouard@1912: default: break; /* should never occur, if file generation is correct */ Edouard@1912: } Edouard@1912: Edouard@1912: fprintf(stderr, "Modbus plugin: Modbus function %%d not supported\n", request_id); /* should never occur, if file generation is correct */ Edouard@1912: return -1; Edouard@1912: } Edouard@1912: Edouard@1912: Edouard@1912: Edouard@1912: /* pack bits from unpacked_data to packed_data */ Edouard@1912: static inline int __pack_bits(u16 *unpacked_data, u16 start_addr, u16 bit_count, u8 *packed_data) { Edouard@1912: u8 bit; Edouard@1912: u16 byte, coils_processed; Edouard@1912: Edouard@1912: if ((0 == bit_count) || (65535-start_addr < bit_count-1)) Edouard@1912: return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */ Edouard@1912: Edouard@1912: for( byte = 0, coils_processed = 0; coils_processed < bit_count; byte++) { Edouard@1912: packed_data[byte] = 0; Edouard@1912: for( bit = 0x01; (bit & 0xFF) && (coils_processed < bit_count); bit <<= 1, coils_processed++ ) { Edouard@1912: if(unpacked_data[start_addr + coils_processed]) Edouard@1912: packed_data[byte] |= bit; /* set bit */ Edouard@1912: else packed_data[byte] &= ~bit; /* reset bit */ Edouard@1912: } Edouard@1912: } Edouard@1912: return 0; Edouard@1912: } Edouard@1912: Edouard@1912: Edouard@1912: /* unpack bits from packed_data to unpacked_data */ Edouard@1912: static inline int __unpack_bits(u16 *unpacked_data, u16 start_addr, u16 bit_count, u8 *packed_data) { Edouard@1912: u8 temp, bit; Edouard@1912: u16 byte, coils_processed; Edouard@1912: Edouard@1912: if ((0 == bit_count) || (65535-start_addr < bit_count-1)) Edouard@1912: return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */ Edouard@1912: Edouard@1912: for(byte = 0, coils_processed = 0; coils_processed < bit_count; byte++) { Edouard@1912: temp = packed_data[byte] ; Edouard@1912: for(bit = 0x01; (bit & 0xff) && (coils_processed < bit_count); bit <<= 1, coils_processed++) { Edouard@1912: unpacked_data[start_addr + coils_processed] = (temp & bit)?1:0; Edouard@1912: } Edouard@1912: } Edouard@1912: return 0; Edouard@1912: } Edouard@1912: Edouard@1912: msousa@2721: static int __read_inbits (void *mem_map, u16 start_addr, u16 bit_count, u8 *data_bytes) { msousa@2721: int res = __pack_bits(((server_mem_t *)mem_map)->ro_bits, start_addr, bit_count, data_bytes); msousa@2721: msousa@2722: if (res >= 0) { msousa@2722: /* update the flag and counter of Modbus requests we have processed. */ msousa@2721: ((server_mem_t *)mem_map)->flag_read_req_counter++; msousa@2722: ((server_mem_t *)mem_map)->flag_read_req_flag = 1; msousa@2722: } msousa@2721: msousa@2721: return res; msousa@2721: } msousa@2721: msousa@2721: static int __read_outbits (void *mem_map, u16 start_addr, u16 bit_count, u8 *data_bytes) { msousa@2721: int res = __pack_bits(((server_mem_t *)mem_map)->rw_bits, start_addr, bit_count, data_bytes); msousa@2721: msousa@2722: if (res >= 0) { msousa@2722: /* update the flag and counter of Modbus requests we have processed. */ msousa@2721: ((server_mem_t *)mem_map)->flag_read_req_counter++; msousa@2722: ((server_mem_t *)mem_map)->flag_read_req_flag = 1; msousa@2722: } msousa@2721: msousa@2721: return res; msousa@2721: } msousa@2721: msousa@2721: static int __write_outbits (void *mem_map, u16 start_addr, u16 bit_count, u8 *data_bytes) { msousa@2721: int res = __unpack_bits(((server_mem_t *)mem_map)->rw_bits, start_addr, bit_count, data_bytes); msousa@2721: msousa@2722: if (res >= 0) { msousa@2722: /* update the flag and counter of Modbus requests we have processed. */ msousa@2721: ((server_mem_t *)mem_map)->flag_write_req_counter++; msousa@2722: ((server_mem_t *)mem_map)->flag_write_req_flag = 1; msousa@2722: } msousa@2721: msousa@2721: return res; msousa@2721: } msousa@2721: Edouard@1912: Edouard@1912: Edouard@1912: Edouard@1912: static int __read_inwords (void *mem_map, u16 start_addr, u16 word_count, u16 *data_words) { Edouard@1913: Edouard@1912: if ((start_addr + word_count) > MEM_AREA_SIZE) Edouard@1912: return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */ Edouard@1913: msousa@2722: /* update the flag and counter of Modbus requests we have processed. */ msousa@2721: ((server_mem_t *)mem_map)->flag_read_req_counter++; msousa@2722: ((server_mem_t *)mem_map)->flag_read_req_flag = 1; msousa@2721: Edouard@1913: /* use memcpy() because loop with pointers (u16 *) caused alignment problems */ Edouard@1912: memcpy(/* dest */ (void *)data_words, Edouard@1912: /* src */ (void *)&(((server_mem_t *)mem_map)->ro_words[start_addr]), Edouard@1912: /* size */ word_count * 2); Edouard@1912: return 0; Edouard@1912: } Edouard@1912: Edouard@1912: Edouard@1912: Edouard@1912: static int __read_outwords (void *mem_map, u16 start_addr, u16 word_count, u16 *data_words) { Edouard@1913: Edouard@1912: if ((start_addr + word_count) > MEM_AREA_SIZE) Edouard@1912: return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */ Edouard@1913: msousa@2722: /* update the flag and counter of Modbus requests we have processed. */ msousa@2721: ((server_mem_t *)mem_map)->flag_read_req_counter++; msousa@2722: ((server_mem_t *)mem_map)->flag_read_req_flag = 1; msousa@2721: Edouard@1913: /* use memcpy() because loop with pointers (u16 *) caused alignment problems */ Edouard@1912: memcpy(/* dest */ (void *)data_words, Edouard@1912: /* src */ (void *)&(((server_mem_t *)mem_map)->rw_words[start_addr]), Edouard@1912: /* size */ word_count * 2); Edouard@1912: return 0; Edouard@1912: } Edouard@1912: Edouard@1912: Edouard@1912: Edouard@1912: Edouard@1912: static int __write_outwords(void *mem_map, u16 start_addr, u16 word_count, u16 *data_words) { Edouard@1913: Edouard@1912: if ((start_addr + word_count) > MEM_AREA_SIZE) Edouard@1912: return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */ Edouard@1912: msousa@2722: /* update the flag and counter of Modbus requests we have processed. */ msousa@2721: ((server_mem_t *)mem_map)->flag_write_req_counter++; msousa@2722: ((server_mem_t *)mem_map)->flag_write_req_flag = 1; msousa@2721: Edouard@1912: /* WARNING: The data returned in the data_words[] array is not guaranteed to be 16 bit aligned. Edouard@1912: * It is not therefore safe to cast it to an u16 data type. Edouard@1912: * The following code cannot be used. memcpy() is used instead. Edouard@1912: */ Edouard@1912: /* Edouard@1912: for (count = 0; count < word_count ; count++) Edouard@1912: ((server_mem_t *)mem_map)->rw_words[count + start_addr] = data_words[count]; Edouard@1912: */ Edouard@1912: memcpy(/* dest */ (void *)&(((server_mem_t *)mem_map)->rw_words[start_addr]), Edouard@1912: /* src */ (void *)data_words, Edouard@1912: /* size */ word_count * 2); Edouard@1912: return 0; Edouard@1912: } Edouard@1912: Edouard@1912: Edouard@1912: Edouard@1912: Edouard@1912: #include Edouard@1912: Edouard@1912: static void *__mb_server_thread(void *_server_node) { Edouard@1912: server_node_t *server_node = _server_node; Edouard@1912: mb_slave_callback_t callbacks = { Edouard@1912: &__read_inbits, Edouard@1912: &__read_outbits, Edouard@1912: &__write_outbits, Edouard@1912: &__read_inwords, Edouard@1912: &__read_outwords, Edouard@1912: &__write_outwords, Edouard@1912: (void *)&(server_node->mem_area) Edouard@1912: }; Edouard@1912: Edouard@1912: // Enable thread cancelation. Enabled is default, but set it anyway to be safe. Edouard@1912: pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); Edouard@1912: Edouard@1912: // mb_slave_run() should never return! Edouard@1912: mb_slave_run(server_node->mb_nd /* nd */, callbacks, server_node->slave_id); Edouard@1912: fprintf(stderr, "Modbus plugin: Modbus server for node %%s died unexpectedly!\n", server_node->location); /* should never occur */ Edouard@1912: return NULL; Edouard@1912: } Edouard@1912: Edouard@1912: Edouard@2480: #define timespec_add(ts, sec, nsec) { \ Edouard@2480: ts.tv_sec += sec; \ Edouard@2480: ts.tv_nsec += nsec; \ Edouard@2480: if (ts.tv_nsec >= 1000000000) { \ Edouard@2480: ts.tv_sec ++; \ Edouard@2480: ts.tv_nsec -= 1000000000; \ Edouard@2480: } \ Edouard@2480: } Edouard@2480: Edouard@1912: Edouard@1912: static void *__mb_client_thread(void *_index) { Edouard@1912: int client_node_id = (char *)_index - (char *)NULL; // Use pointer arithmetic (more portable than cast) Edouard@1912: Edouard@1912: // Enable thread cancelation. Enabled is default, but set it anyway to be safe. Edouard@1912: pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); Edouard@1912: msousa@2647: /* loop the communication with the client msousa@2647: * msousa@2647: * When the client thread has difficulty communicating with remote client and/or server (network issues, for example), msousa@2647: * then the communications get delayed and we will fall behind in the period. msousa@2647: * msousa@2647: * This is OK. Note that if the condition variable were to be signaled multiple times while the client thread is inside the same msousa@2647: * Modbus transaction, then all those signals would be ignored. msousa@2647: * However, and since we keep the mutex locked during the communication cycle, it is not possible to signal the condition variable msousa@2647: * during that time (it is only possible while the thread is blocked during the call to pthread_cond_wait(). msousa@2647: * msousa@2647: * This means that when network issues eventually get resolved, we will NOT have a bunch of delayed activations to handle msousa@2647: * in quick succession (which would goble up CPU time). msousa@2647: * msousa@2647: * Notice that the above property is valid whether the communication cycle is run with the mutex locked, or unlocked. msousa@2647: * Since it makes it easier to implement the correct semantics for the other activation methods if the communication cycle msousa@2647: * is run with the mutex locked, then that is what we do. msousa@2647: * msousa@2647: * Note that during all the communication cycle we will keep locked the mutex msousa@2647: * (i.e. the mutex used together with the condition variable that will activate a new communication cycle) msousa@2647: * msousa@2647: * Note that we never get to explicitly unlock this mutex. It will only be unlocked by the pthread_cond_wait() msousa@2647: * call at the end of the cycle. msousa@2647: */ msousa@2647: pthread_mutex_lock(&(client_nodes[client_node_id].mutex)); msousa@2647: Edouard@1912: while (1) { Edouard@1912: /* Edouard@1912: struct timespec cur_time; Edouard@1912: clock_gettime(CLOCK_MONOTONIC, &cur_time); msousa@2687: fprintf(stderr, "Modbus client thread (%%d) - new cycle (%%ld:%%ld)!\n", client_node_id, cur_time.tv_sec, cur_time.tv_nsec); Edouard@1912: */ msousa@3733: msousa@3733: /* Variable use to specify delay to introduce between any two consecutive requests we send out to the same client msousa@3733: * Initially set to 0 since we don't want to introduce a delay before the very first request. msousa@3733: */ msousa@3733: struct timespec inter_request_delay; msousa@3733: inter_request_delay.tv_sec = 0; msousa@3733: inter_request_delay.tv_nsec = 0; msousa@3733: Edouard@1912: int req; Edouard@1912: for (req=0; req < NUMBER_OF_CLIENT_REQTS; req ++){ msousa@2647: /* just do the requests belonging to the client */ Edouard@1912: if (client_requests[req].client_node_id != client_node_id) Edouard@1912: continue; msousa@2647: msousa@2647: /* only do the request if: msousa@2647: * - this request was explictly asked to be executed by the client program msousa@2647: * OR msousa@2647: * - the client thread was activated periodically msousa@2647: * (in which case we execute all the requests belonging to the client node) msousa@2647: */ msousa@2647: if ((client_requests[req].flag_exec_req == 0) && (client_nodes[client_requests[req].client_node_id].periodic_act == 0)) msousa@2647: continue; msousa@2647: msousa@2687: /* msousa@2687: fprintf(stderr, "Modbus client thread (%%d): RUNNING Modbus request %%d (periodic = %%d flag_exec_req = %%d)\n", msousa@2687: client_node_id, req, client_nodes[client_requests[req].client_node_id].periodic_act, client_requests[req].flag_exec_req ); msousa@2687: */ msousa@3733: msousa@3733: /* Insert a delay between any two consecutive requests to the same client msousa@3733: * Needed because some clients will ignore our requests if we send them out too fast. msousa@3733: * msousa@3733: * Note that since we don't want to insert a delay before the very first request we will send, the inter_request_delay variable msousa@3733: * is first initialised to 0. It will be set to the correct delay after the first (and second, third, etc..) request has completed. msousa@3733: */ msousa@3733: clock_nanosleep(CLOCK_MONOTONIC, 0 /* relative sleep */, &inter_request_delay, NULL); msousa@2647: Edouard@1912: int res_tmp = __execute_mb_request(req); msousa@2713: client_requests[req].tn_error_code = 0; // assume success Edouard@1912: switch (res_tmp) { Edouard@1912: case PORT_FAILURE: { Edouard@1912: if (res_tmp != client_nodes[client_node_id].prev_error) Edouard@1912: fprintf(stderr, "Modbus plugin: Error connecting Modbus client %%s to remote server.\n", client_nodes[client_node_id].location); Edouard@1912: client_nodes[client_node_id].prev_error = res_tmp; msousa@2713: client_requests[req].tn_error_code = 1; // error accessing IP network, or serial interface Edouard@1912: break; Edouard@1912: } Edouard@1912: case INVALID_FRAME: { Edouard@1912: if ((res_tmp != client_requests[req].prev_error) && (0 == client_nodes[client_node_id].prev_error)) Edouard@1912: fprintf(stderr, "Modbus plugin: Modbus client request configured at location %%s was unsuccesful. Server/slave returned an invalid/corrupted frame.\n", client_requests[req].location); Edouard@1912: client_requests[req].prev_error = res_tmp; msousa@2713: client_requests[req].tn_error_code = 2; // reply received from server was an invalid frame Edouard@1912: break; Edouard@1912: } Edouard@1912: case TIMEOUT: { Edouard@1912: if ((res_tmp != client_requests[req].prev_error) && (0 == client_nodes[client_node_id].prev_error)) Edouard@1912: fprintf(stderr, "Modbus plugin: Modbus client request configured at location %%s timed out waiting for reply from server.\n", client_requests[req].location); Edouard@1912: client_requests[req].prev_error = res_tmp; msousa@2713: client_requests[req].tn_error_code = 3; // server did not reply before timeout expired Edouard@1912: break; Edouard@1912: } Edouard@1912: case MODBUS_ERROR: { msousa@2713: if (client_requests[req].prev_error != client_requests[req].mb_error_code) { msousa@2713: fprintf(stderr, "Modbus plugin: Modbus client request configured at location %%s was unsuccesful. Server/slave returned error code 0x%%2x", client_requests[req].location, client_requests[req].mb_error_code); msousa@2713: if (client_requests[req].mb_error_code <= MAX_MODBUS_ERROR_CODE ) { msousa@2713: fprintf(stderr, "(%%s)", modbus_error_messages[client_requests[req].mb_error_code]); Edouard@1912: fprintf(stderr, ".\n"); Edouard@1912: } Edouard@1912: } msousa@2713: client_requests[req].prev_error = client_requests[req].mb_error_code; msousa@2713: client_requests[req].tn_error_code = 4; // server returned a valid Modbus error frame Edouard@1912: break; Edouard@1912: } Edouard@1912: default: { Edouard@1912: if ((res_tmp >= 0) && (client_nodes[client_node_id].prev_error != 0)) { Edouard@1912: fprintf(stderr, "Modbus plugin: Modbus client %%s has reconnected to server/slave.\n", client_nodes[client_node_id].location); Edouard@1912: } Edouard@1912: if ((res_tmp >= 0) && (client_requests[req] .prev_error != 0)) { Edouard@1912: fprintf(stderr, "Modbus plugin: Modbus client request configured at location %%s has succesfully resumed comunication.\n", client_requests[req].location); Edouard@1912: } Edouard@1912: client_nodes[client_node_id].prev_error = 0; Edouard@1912: client_requests[req] .prev_error = 0; Edouard@1912: break; Edouard@1912: } Edouard@1912: } msousa@2713: msousa@2714: /* Set the flag_tn_error_code and flag_mb_error_code that are mapped onto msousa@2714: * located BYTE variables, so the user program msousa@2713: * knows how the communication is going. msousa@2713: */ msousa@2714: client_requests[req].flag_mb_error_code = client_requests[req].mb_error_code; msousa@2714: client_requests[req].flag_tn_error_code = client_requests[req].tn_error_code; msousa@2713: msousa@3733: /* We have just finished executing a client transaction request. msousa@2647: * If the current cycle was activated by user request we reset the flag used to ask to run it msousa@2647: */ msousa@2647: if (0 != client_requests[req].flag_exec_req) { msousa@2647: client_requests[req].flag_exec_req = 0; msousa@2647: client_requests[req].flag_exec_started = 0; msousa@2647: } msousa@2647: msousa@3733: /* We have just finished executing a client transaction request. msousa@3733: * Set the inter request delay before we send the next request. Value of delay is set by user in beremiz GUI msousa@3733: */ msousa@3733: inter_request_delay.tv_sec = client_nodes[client_node_id].req_delay / 1000; /* ms to seconds */ msousa@3733: inter_request_delay.tv_nsec = (client_nodes[client_node_id].req_delay %% 1000) * 1000 * 1000; /* ms to ns */ msousa@3733: msousa@2647: //fprintf(stderr, "Modbus plugin: RUNNING<---> of Modbus request %%d (periodic = %%d flag_exec_req = %%d)\n", msousa@2647: // req, client_nodes[client_requests[req].client_node_id].periodic_act, client_requests[req].flag_exec_req ); msousa@2647: } msousa@2647: msousa@2647: // Wait for signal (from timer or explicit request from user program) before starting the next cycle msousa@2647: { msousa@2647: // No need to lock the mutex. Is is already locked just before the while(1) loop. msousa@2647: // Read the comment there to understand why. msousa@2647: // pthread_mutex_lock(&(client_nodes[client_node_id].mutex)); msousa@2647: msousa@2647: /* the client thread has just finished a cycle, so all the flags used to signal an activation msousa@2647: * and specify the activation source (periodic, user request, ...) msousa@2647: * get reset here, before waiting for a new activation. msousa@2647: */ msousa@2647: client_nodes[client_node_id].periodic_act = 0; msousa@2647: client_nodes[client_node_id].execute_req = 0; msousa@2647: msousa@2647: while (client_nodes[client_node_id].execute_req == 0) msousa@2647: pthread_cond_wait(&(client_nodes[client_node_id].condv), msousa@2647: &(client_nodes[client_node_id].mutex)); msousa@2647: msousa@2647: // We run the communication cycle with the mutex locked. msousa@2647: // Read the comment just above the while(1) to understand why. msousa@2647: // pthread_mutex_unlock(&(client_nodes[client_node_id].mutex)); msousa@2647: } Edouard@1912: } Edouard@1912: Edouard@1912: // humour the compiler. Edouard@1912: return NULL; Edouard@1912: } Edouard@1912: Edouard@1912: msousa@2647: Edouard@2683: Edouard@2683: msousa@2687: /* Thread that simply implements a periodic 'timer', msousa@2687: * i.e. periodically sends signal to the thread running __mb_client_thread() msousa@2647: * msousa@2687: * Note that we do not use a posix timer (timer_create() ) because there doesn't seem to be a way msousa@2687: * of having the timer notify the thread that is portable across Xenomai and POSIX. msousa@2687: * - SIGEV_THREAD : not supported by Xenomai msousa@2687: * - SIGEV_THREAD_ID : Linux specific (i.e. non POSIX) msousa@2687: * Even so, I did not get it to work under Linux (issues with the header files) msousa@2687: * - SIGEV_SIGNAL : Will not work, as signal is sent to random thread in process! msousa@2647: */ Edouard@2683: static void *__mb_client_timer_thread(void *_index) { Edouard@2683: int client_node_id = (char *)_index - (char *)NULL; // Use pointer arithmetic (more portable than cast) msousa@2687: struct timespec next_cycle; Edouard@2683: Edouard@2684: int period_sec = client_nodes[client_node_id].comm_period / 1000; /* comm_period is in ms */ Edouard@2684: int period_nsec = (client_nodes[client_node_id].comm_period %%1000)*1000000; /* comm_period is in ms */ Edouard@2684: msousa@2687: // Enable thread cancelation. Enabled is default, but set it anyway to be safe. msousa@2687: pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); msousa@2687: msousa@2687: if (client_nodes[client_node_id].comm_period <= 0) { msousa@2687: // No periodic activation required => nothing to do! msousa@2687: while (1) pause(); // wait to be canceled when program terminates (shutdown() is called) msousa@2687: return NULL; // not really necessary, just makes it easier to understand the code. Edouard@2684: } Edouard@2684: msousa@2687: // get the current time msousa@2687: clock_gettime(CLOCK_MONOTONIC, &next_cycle); msousa@2687: msousa@2687: while(1) { msousa@2687: // Determine absolute time instant for starting the next cycle msousa@2687: struct timespec prev_cycle, now; msousa@2687: prev_cycle = next_cycle; msousa@2687: timespec_add(next_cycle, period_sec, period_nsec); msousa@2687: msousa@2687: /* NOTE: msousa@2687: * It is probably un-necessary to check for overflow of timer! msousa@2687: * Even in 32 bit systems this will take at least 68 years since the computer booted msousa@2687: * (remember, we are using CLOCK_MONOTONIC, which should start counting from 0 msousa@2687: * every time the system boots). On 64 bit systems, it will take over msousa@2687: * 10^11 years to overflow. msousa@2647: */ msousa@2687: clock_gettime(CLOCK_MONOTONIC, &now); msousa@2687: if (next_cycle.tv_sec < prev_cycle.tv_sec) { msousa@2687: /* Timer overflow. See NOTE B above */ msousa@2687: next_cycle = now; msousa@2687: timespec_add(next_cycle, period_sec, period_nsec); msousa@2687: } msousa@2687: msousa@2687: while (0 != clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_cycle, NULL)); msousa@2688: msousa@2688: /* signal the client node's condition variable on which the client node's thread should be waiting... */ msousa@2688: /* Since the communication cycle is run with the mutex locked, we use trylock() instead of lock() */ msousa@2688: if (pthread_mutex_trylock (&(client_nodes[client_node_id].mutex)) == 0) { msousa@2688: client_nodes[client_node_id].execute_req = 1; // tell the thread to execute msousa@2688: client_nodes[client_node_id].periodic_act = 1; // tell the thread the activation was done by periodic timer msousa@2688: pthread_cond_signal (&(client_nodes[client_node_id].condv)); msousa@2688: pthread_mutex_unlock(&(client_nodes[client_node_id].mutex)); msousa@2688: } else { msousa@2688: /* We never get to signal the thread for activation. But that is OK. msousa@2688: * If it still in the communication cycle (during which the mutex is kept locked) msousa@2688: * then that means that the communication cycle is falling behing in the periodic msousa@2688: * communication cycle, and we therefore need to skip a period. msousa@2688: */ msousa@2688: } Edouard@2683: } Edouard@2683: msousa@2688: return NULL; // humour the compiler -> will never be executed! Edouard@2683: } msousa@2647: msousa@2647: Edouard@1913: int __cleanup_%(locstr)s (); Edouard@1912: int __init_%(locstr)s (int argc, char **argv){ Edouard@1912: int index; Edouard@1912: msousa@2654: for (index=0; index < NUMBER_OF_CLIENT_NODES;index++) { Edouard@1912: client_nodes[index].mb_nd = -1; msousa@2654: /* see comment in mb_runtime.h to understad why we need to initialize these entries */ msousa@2654: switch (client_nodes[index].node_address.naf) { msousa@2654: case naf_tcp: msousa@2654: client_nodes[index].node_address.addr.tcp.host = client_nodes[index].str1; msousa@2654: client_nodes[index].node_address.addr.tcp.service = client_nodes[index].str2; msousa@2654: break; msousa@2654: case naf_rtu: msousa@2654: client_nodes[index].node_address.addr.rtu.device = client_nodes[index].str1; msousa@2654: break; msousa@2654: } msousa@2654: } msousa@2654: msousa@2654: for (index=0; index < NUMBER_OF_SERVER_NODES;index++) { Edouard@1912: // mb_nd with negative numbers indicate how far it has been initialised (or not) Edouard@1912: // -2 --> no modbus node created; no thread created Edouard@1912: // -1 --> modbus node created!; no thread created Edouard@1912: // >=0 --> modbus node created!; thread created! Edouard@1912: server_nodes[index].mb_nd = -2; msousa@2722: server_nodes[index].mem_area.flag_write_req_flag = 0; msousa@2721: server_nodes[index].mem_area.flag_write_req_counter = 0; msousa@2721: server_nodes[index].mem_area.flag_read_req_counter = 0; msousa@2722: server_nodes[index].mem_area.flag_read_req_flag = 0; msousa@2655: /* see comment in mb_runtime.h to understad why we need to initialize these entries */ msousa@2655: switch (server_nodes[index].node_address.naf) { msousa@2655: case naf_tcp: msousa@2655: server_nodes[index].node_address.addr.tcp.host = server_nodes[index].str1; msousa@2655: server_nodes[index].node_address.addr.tcp.service = server_nodes[index].str2; msousa@2655: break; msousa@2655: case naf_rtu: msousa@2655: server_nodes[index].node_address.addr.rtu.device = server_nodes[index].str1; msousa@2655: break; msousa@2655: } msousa@2654: } Edouard@1912: Edouard@1912: /* modbus library init */ Edouard@1912: /* Note that TOTAL_xxxNODE_COUNT are the nodes required by _ALL_ the instances of the modbus Edouard@1912: * extension currently in the user's project. This file (MB_xx.c) is handling only one instance, Edouard@1912: * but must initialize the library for all instances. Only the first call to mb_slave_and_master_init() Edouard@1912: * will result in memory being allocated. All subsequent calls (by other MB_xx,c files) will be ignored Edouard@1912: * by the mb_slave_and_master_init() funtion, as long as they are called with the same arguments. Edouard@1912: */ Edouard@1912: if (mb_slave_and_master_init(TOTAL_TCPNODE_COUNT, TOTAL_RTUNODE_COUNT, TOTAL_ASCNODE_COUNT) <0) { Edouard@1912: fprintf(stderr, "Modbus plugin: Error starting modbus library\n"); Edouard@1912: // return imediately. Do NOT goto error_exit, as we did not get to Edouard@1912: // start the modbus library! Edouard@1912: return -1; Edouard@1912: } Edouard@1912: msousa@2647: /* init each client request */ Edouard@1912: /* Must be done _before_ launching the client threads!! */ Edouard@1912: for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++){ msousa@2647: /* make sure flags connected to user program MB transaction start request are all reset */ msousa@2647: client_requests[index].flag_exec_req = 0; msousa@2647: client_requests[index].flag_exec_started = 0; msousa@2647: /* init the mutex for each client request */ msousa@2647: /* Must be done _before_ launching the client threads!! */ Edouard@1912: if (pthread_mutex_init(&(client_requests[index].coms_buf_mutex), NULL)) { Edouard@1912: fprintf(stderr, "Modbus plugin: Error initializing request for modbus client node %%s\n", client_nodes[client_requests[index].client_node_id].location); Edouard@1912: goto error_exit; Edouard@1912: } Edouard@1912: } Edouard@1912: Edouard@1912: /* init each client connection to remote modbus server, and launch thread */ Edouard@1912: /* NOTE: All client_nodes[].init_state are initialised to 0 in the code Edouard@1912: * generated by the modbus plugin Edouard@1912: */ Edouard@1912: for (index=0; index < NUMBER_OF_CLIENT_NODES;index++){ Edouard@1912: /* establish client connection */ Edouard@1912: client_nodes[index].mb_nd = mb_master_connect (client_nodes[index].node_address); Edouard@1912: if (client_nodes[index].mb_nd < 0){ Edouard@1912: fprintf(stderr, "Modbus plugin: Error creating modbus client node %%s\n", client_nodes[index].location); Edouard@1912: goto error_exit; Edouard@1912: } Edouard@1912: client_nodes[index].init_state = 1; // we have created the node Edouard@1912: msousa@2647: /* initialize the mutex variable that will be used by the thread handling the client node */ Edouard@2685: bzero(&(client_nodes[index].mutex), sizeof(pthread_mutex_t)); msousa@2647: if (pthread_mutex_init(&(client_nodes[index].mutex), NULL) < 0) { msousa@2647: fprintf(stderr, "Modbus plugin: Error creating mutex for modbus client node %%s\n", client_nodes[index].location); msousa@2647: goto error_exit; msousa@2647: } msousa@2647: client_nodes[index].init_state = 2; // we have created the mutex msousa@2647: msousa@2647: /* initialize the condition variable that will be used by the thread handling the client node */ Edouard@2685: bzero(&(client_nodes[index].condv), sizeof(pthread_cond_t)); msousa@2647: if (pthread_cond_init(&(client_nodes[index].condv), NULL) < 0) { msousa@2647: fprintf(stderr, "Modbus plugin: Error creating condition variable for modbus client node %%s\n", client_nodes[index].location); msousa@2647: goto error_exit; msousa@2647: } msousa@2647: client_nodes[index].execute_req = 0; //variable associated with condition variable msousa@2647: client_nodes[index].init_state = 3; // we have created the condition variable msousa@2647: Edouard@2683: /* launch a thread to handle this client node timer */ Edouard@2683: { Edouard@2683: int res = 0; Edouard@2683: pthread_attr_t attr; Edouard@2683: res |= pthread_attr_init(&attr); edouard@4049: #ifdef MODBUS_STACK_SIZE edouard@4049: res |= pthread_attr_setstacksize(&attr, MODBUS_STACK_SIZE); edouard@4049: #endif Edouard@2683: res |= pthread_create(&(client_nodes[index].timer_thread_id), &attr, &__mb_client_timer_thread, (void *)((char *)NULL + index)); Edouard@2683: if (res != 0) { Edouard@3859: fprintf(stderr, "Modbus plugin: Error (%%d) starting timer thread for modbus client node %%s\n", res, client_nodes[index].location); Edouard@2683: goto error_exit; Edouard@2683: } Edouard@2683: } msousa@2647: client_nodes[index].init_state = 4; // we have created the timer msousa@2647: Edouard@1912: /* launch a thread to handle this client node */ Edouard@1912: { Edouard@1912: int res = 0; Edouard@1912: pthread_attr_t attr; Edouard@1912: res |= pthread_attr_init(&attr); edouard@4049: #ifdef MODBUS_STACK_SIZE edouard@4049: res |= pthread_attr_setstacksize(&attr, MODBUS_STACK_SIZE); edouard@4049: #endif Edouard@1912: res |= pthread_create(&(client_nodes[index].thread_id), &attr, &__mb_client_thread, (void *)((char *)NULL + index)); Edouard@1912: if (res != 0) { Edouard@3859: fprintf(stderr, "Modbus plugin: Error (%%d) starting thread for modbus client node %%s\n", res, client_nodes[index].location); Edouard@1912: goto error_exit; Edouard@1912: } Edouard@1912: } msousa@2647: client_nodes[index].init_state = 5; // we have created the thread Edouard@1912: } Edouard@1912: Edouard@1912: /* init each local server */ Edouard@1912: /* NOTE: All server_nodes[].init_state are initialised to 0 in the code Edouard@1912: * generated by the modbus plugin Edouard@1912: */ Edouard@1912: for (index=0; index < NUMBER_OF_SERVER_NODES;index++){ Edouard@1912: /* create the modbus server */ Edouard@1912: server_nodes[index].mb_nd = mb_slave_new (server_nodes[index].node_address); Edouard@1912: if (server_nodes[index].mb_nd < 0){ Edouard@1912: fprintf(stderr, "Modbus plugin: Error creating modbus server node %%s\n", server_nodes[index].location); Edouard@1912: goto error_exit; Edouard@1912: } Edouard@1912: server_nodes[index].init_state = 1; // we have created the node Edouard@1912: Edouard@1912: /* launch a thread to handle this server node */ Edouard@1912: { Edouard@1912: int res = 0; Edouard@1912: pthread_attr_t attr; Edouard@1912: res |= pthread_attr_init(&attr); edouard@4049: #ifdef MODBUS_STACK_SIZE edouard@4049: res |= pthread_attr_setstacksize(&attr, MODBUS_STACK_SIZE); edouard@4049: #endif Edouard@1912: res |= pthread_create(&(server_nodes[index].thread_id), &attr, &__mb_server_thread, (void *)&(server_nodes[index])); Edouard@1912: if (res != 0) { Edouard@3859: fprintf(stderr, "Modbus plugin: Error (%%d) starting modbus server thread for node %%s\n", res, server_nodes[index].location); Edouard@1912: goto error_exit; Edouard@1912: } Edouard@1912: } Edouard@1912: server_nodes[index].init_state = 2; // we have created the node and thread Edouard@1912: } Edouard@1912: Edouard@1912: return 0; Edouard@1912: Edouard@1912: error_exit: Edouard@1912: __cleanup_%(locstr)s (); Edouard@1912: return -1; Edouard@1912: } Edouard@1912: Edouard@1912: Edouard@1912: Edouard@1912: Edouard@1912: Edouard@1912: void __publish_%(locstr)s (){ Edouard@1912: int index; Edouard@1912: Edouard@1912: for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++){ msousa@2647: /* synchronize the PLC and MB buffers only for the output requests */ Edouard@1912: if (client_requests[index].req_type == req_output){ msousa@2647: msousa@2647: // lock the mutex brefore copying the data Edouard@2011: if(pthread_mutex_trylock(&(client_requests[index].coms_buf_mutex)) == 0){ msousa@2647: msousa@2647: // Check if user configured this MB request to be activated whenever the data to be written changes msousa@2647: if (client_requests[index].write_on_change) { msousa@2647: // Let's check if the data did change... msousa@2647: // compare the data in plcv_buffer to coms_buffer msousa@2647: int res; msousa@2647: res = memcmp((void *)client_requests[index].coms_buffer /* buf 1 */, msousa@2647: (void *)client_requests[index].plcv_buffer /* buf 2*/, msousa@2647: REQ_BUF_SIZE * sizeof(u16) /* size in bytes */); msousa@2647: msousa@2647: // if data changed, activate execution request msousa@2647: if (0 != res) msousa@2647: client_requests[index].flag_exec_req = 1; msousa@2647: } msousa@2647: Edouard@2011: // copy from plcv_buffer to coms_buffer Edouard@2011: memcpy((void *)client_requests[index].coms_buffer /* destination */, Edouard@2011: (void *)client_requests[index].plcv_buffer /* source */, Edouard@2011: REQ_BUF_SIZE * sizeof(u16) /* size in bytes */); Edouard@2011: pthread_mutex_unlock(&(client_requests[index].coms_buf_mutex)); Edouard@2011: } Edouard@1912: } msousa@2647: /* if the user program set the execution request flag, then activate the thread msousa@2647: * that handles this Modbus client transaction so it gets a chance to be executed msousa@2647: * (but don't activate the thread if it has already been activated!) msousa@2647: * msousa@2647: * NOTE that we do this, for both the IN and OUT mapped location, under this msousa@2647: * __publish_() function. The scan cycle of the PLC works as follows: msousa@2647: * - call __retrieve_() msousa@2647: * - execute user programs msousa@2647: * - call __publish_() msousa@2647: * - insert until time to start next periodic/cyclic scan cycle msousa@2647: * msousa@2647: * In an attempt to be able to run the MB transactions during the msousa@2647: * interval in which not much is going on, we handle the user program msousa@2647: * requests to execute a specific MB transaction in this __publish_() msousa@2647: * function. msousa@2647: */ msousa@2647: if ((client_requests[index].flag_exec_req != 0) && (0 == client_requests[index].flag_exec_started)) { msousa@2647: int client_node_id = client_requests[index].client_node_id; msousa@2688: msousa@2688: /* We TRY to signal the client thread. msousa@2688: * We do this because this function can be called at the end of the PLC scan cycle msousa@2688: * and we don't want it to block at that time. msousa@2688: */ msousa@2688: if (pthread_mutex_trylock(&(client_nodes[client_node_id].mutex)) == 0) { msousa@2688: client_nodes[client_node_id].execute_req = 1; // tell the thread to execute msousa@2688: pthread_cond_signal (&(client_nodes[client_node_id].condv)); msousa@2688: pthread_mutex_unlock(&(client_nodes[client_node_id].mutex)); msousa@2688: /* - upon success, set flag_exec_started msousa@2688: * - both flags (flag_exec_req and flag_exec_started) will be reset msousa@2688: * once the transaction has completed. msousa@2688: */ msousa@2688: client_requests[index].flag_exec_started = 1; msousa@2688: } else { msousa@2688: /* The mutex is locked => the client thread is currently executing MB transactions. msousa@2688: * We will try to activate it in the next PLC cycle... msousa@2688: * For now, do nothing. msousa@2688: */ msousa@2688: } msousa@2647: } msousa@2647: } Edouard@1912: } Edouard@1912: Edouard@1912: Edouard@1912: Edouard@1912: Edouard@1912: Edouard@1912: void __retrieve_%(locstr)s (){ Edouard@1912: int index; Edouard@1912: Edouard@1912: for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++){ Edouard@1912: /*just do the input requests */ Edouard@1912: if (client_requests[index].req_type == req_input){ Edouard@2011: if(pthread_mutex_trylock(&(client_requests[index].coms_buf_mutex)) == 0){ Edouard@2011: // copy from coms_buffer to plcv_buffer Edouard@2011: memcpy((void *)client_requests[index].plcv_buffer /* destination */, Edouard@2011: (void *)client_requests[index].coms_buffer /* source */, Edouard@2011: REQ_BUF_SIZE * sizeof(u16) /* size in bytes */); Edouard@2011: pthread_mutex_unlock(&(client_requests[index].coms_buf_mutex)); Edouard@2011: } Edouard@1912: } Edouard@1912: } Edouard@1912: } Edouard@1912: Edouard@1912: Edouard@1912: Edouard@1912: Edouard@1912: Edouard@1912: int __cleanup_%(locstr)s (){ Edouard@1912: int index, close; Edouard@1912: int res = 0; Edouard@1912: Edouard@1912: /* kill thread and close connections of each modbus client node */ Edouard@1912: for (index=0; index < NUMBER_OF_CLIENT_NODES; index++) { Edouard@1912: close = 0; msousa@2647: if (client_nodes[index].init_state >= 5) { Edouard@1912: // thread was launched, so we try to cancel it! Edouard@1912: close = pthread_cancel(client_nodes[index].thread_id); Edouard@1912: close |= pthread_join (client_nodes[index].thread_id, NULL); Edouard@1912: if (close < 0) msousa@2647: fprintf(stderr, "Modbus plugin: Error closing thread for modbus client node %%s\n", client_nodes[index].location); msousa@2647: } msousa@2647: res |= close; msousa@2647: msousa@2647: close = 0; msousa@2647: if (client_nodes[index].init_state >= 4) { msousa@2687: // timer thread was launched, so we try to cancel it! msousa@2687: close = pthread_cancel(client_nodes[index].timer_thread_id); Edouard@2683: close |= pthread_join (client_nodes[index].timer_thread_id, NULL); msousa@2647: if (close < 0) Edouard@2683: fprintf(stderr, "Modbus plugin: Error closing timer thread for modbus client node %%s\n", client_nodes[index].location); Edouard@2683: msousa@2647: } msousa@2647: res |= close; msousa@2647: msousa@2647: close = 0; msousa@2647: if (client_nodes[index].init_state >= 3) { msousa@2647: // condition variable was created, so we try to destroy it! msousa@2647: close = pthread_cond_destroy(&(client_nodes[index].condv)); msousa@2647: if (close < 0) msousa@2647: fprintf(stderr, "Modbus plugin: Error destroying condition variable for modbus client node %%s\n", client_nodes[index].location); msousa@2647: } msousa@2647: res |= close; msousa@2647: msousa@2647: close = 0; msousa@2647: if (client_nodes[index].init_state >= 2) { msousa@2647: // mutex was created, so we try to destroy it! msousa@2647: close = pthread_mutex_destroy(&(client_nodes[index].mutex)); msousa@2647: if (close < 0) msousa@2647: fprintf(stderr, "Modbus plugin: Error destroying mutex for modbus client node %%s\n", client_nodes[index].location); Edouard@1912: } Edouard@1912: res |= close; Edouard@1912: Edouard@1912: close = 0; Edouard@1912: if (client_nodes[index].init_state >= 1) { Edouard@1912: // modbus client node was created, so we try to close it! Edouard@1912: close = mb_master_close (client_nodes[index].mb_nd); Edouard@1912: if (close < 0){ Edouard@1912: fprintf(stderr, "Modbus plugin: Error closing modbus client node %%s\n", client_nodes[index].location); Edouard@1912: // We try to shut down as much as possible, so we do not return noW! Edouard@1912: } Edouard@1912: client_nodes[index].mb_nd = -1; Edouard@1912: } Edouard@1912: res |= close; Edouard@1912: client_nodes[index].init_state = 0; Edouard@1912: } Edouard@1912: msousa@2687: //fprintf(stderr, "Modbus plugin: __cleanup_%%s() 5 close=%%d res=%%d\n", client_nodes[index].location, close, res); Edouard@1912: /* kill thread and close connections of each modbus server node */ Edouard@1912: for (index=0; index < NUMBER_OF_SERVER_NODES; index++) { Edouard@1912: close = 0; Edouard@1912: if (server_nodes[index].init_state >= 2) { Edouard@1912: // thread was launched, so we try to cancel it! Edouard@1912: close = pthread_cancel(server_nodes[index].thread_id); Edouard@1912: close |= pthread_join (server_nodes[index].thread_id, NULL); Edouard@1912: if (close < 0) Edouard@1912: fprintf(stderr, "Modbus plugin: Error closing thread for modbus server %%s\n", server_nodes[index].location); Edouard@1912: } Edouard@1912: res |= close; Edouard@1912: Edouard@1912: close = 0; Edouard@1912: if (server_nodes[index].init_state >= 1) { Edouard@1912: // modbus server node was created, so we try to close it! Edouard@1912: close = mb_slave_close (server_nodes[index].mb_nd); Edouard@1912: if (close < 0) { Edouard@1912: fprintf(stderr, "Modbus plugin: Error closing node for modbus server %%s (%%d)\n", server_nodes[index].location, server_nodes[index].mb_nd); Edouard@1912: // We try to shut down as much as possible, so we do not return noW! Edouard@1912: } Edouard@1912: server_nodes[index].mb_nd = -1; Edouard@1912: } Edouard@1912: res |= close; Edouard@1912: server_nodes[index].init_state = 0; Edouard@1912: } Edouard@1912: Edouard@1912: /* destroy the mutex of each client request */ Edouard@1912: for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++) { Edouard@1912: if (pthread_mutex_destroy(&(client_requests[index].coms_buf_mutex))) { Edouard@1912: fprintf(stderr, "Modbus plugin: Error destroying request for modbus client node %%s\n", client_nodes[client_requests[index].client_node_id].location); Edouard@1912: // We try to shut down as much as possible, so we do not return noW! Edouard@1912: res |= -1; Edouard@1912: } Edouard@1912: } Edouard@1912: Edouard@1912: /* modbus library close */ Edouard@1912: //fprintf(stderr, "Shutting down modbus library...\n"); Edouard@1912: if (mb_slave_and_master_done()<0) { Edouard@1912: fprintf(stderr, "Modbus plugin: Error shutting down modbus library\n"); Edouard@1912: res |= -1; Edouard@1912: } Edouard@1912: Edouard@1912: return res; Edouard@1912: } Edouard@1912: msousa@2654: msousa@2654: msousa@2654: msousa@2654: msousa@2654: /**********************************************/ msousa@2654: /** Functions for Beremiz web interface. **/ msousa@2654: /**********************************************/ msousa@2654: msousa@2654: /* msousa@2654: * Beremiz has a program to run on the PLC (Beremiz_service.py) msousa@2654: * to handle downloading of compiled programs, start/stop of PLC, etc. msousa@2654: * (see runtime/PLCObject.py for start/stop, loading, ...) msousa@2654: * msousa@2654: * This service also includes a web server to access PLC state (start/stop) msousa@2654: * and to change some basic confiuration parameters. msousa@2654: * (see runtime/NevowServer.py for the web server) msousa@2654: * msousa@2654: * The web server allows for extensions, where additional configuration msousa@2654: * parameters may be changed on the running/downloaded PLC. msousa@2654: * Modbus plugin also comes with an extension to the web server, through msousa@2654: * which the basic Modbus plugin configuration parameters may be changed msousa@2654: * msousa@2654: * These parameters are changed _after_ the code (.so file) is loaded into msousa@2654: * memmory. These changes may be applied before (or after) the code starts msousa@2654: * running (i.e. before or after __init_() ets called)! msousa@2654: * msousa@2654: * The following functions are never called from other C code. They are msousa@3733: * called instead from the python code in modbus/web_settings.py, that msousa@2654: * implements the web server extension for configuring Modbus parameters. msousa@2654: */ msousa@2654: msousa@2654: msousa@2654: /* The number of Cient nodes (i.e. the number of entries in the client_nodes array) msousa@2654: * The number of Server nodes (i.e. the numb. of entries in the server_nodes array) msousa@2654: * msousa@2654: * These variables are also used by the Modbus web config code to determine msousa@2654: * whether the current loaded PLC includes the Modbus plugin msousa@2654: * (so it should make the Modbus parameter web interface visible to the user). msousa@2654: */ msousa@2654: const int __modbus_plugin_client_node_count = NUMBER_OF_CLIENT_NODES; msousa@2654: const int __modbus_plugin_server_node_count = NUMBER_OF_SERVER_NODES; msousa@2654: const int __modbus_plugin_param_string_size = MODBUS_PARAM_STRING_SIZE; msousa@2654: msousa@2654: msousa@2654: msousa@3733: /* NOTE: We could have the python code in modbus/web_settings.py msousa@2654: * directly access the server_node_t and client_node_t structures, msousa@2654: * however this would create a tight coupling between these two msousa@2654: * disjoint pieces of code. msousa@2654: * Any change to the server_node_t or client_node_t structures would msousa@2654: * require the python code to be changed accordingly. I have therefore msousa@2654: * opted to create get/set functions, one for each parameter. msousa@2654: * msousa@2654: * We also convert the enumerated constants naf_ascii, etc... msousa@2654: * (from node_addr_family_t in modbus/mb_addr.h) msousa@2654: * into strings so as to decouple the python code that will be calling msousa@2654: * these functions from the Modbus library code definitions. msousa@2654: */ msousa@2654: const char *addr_type_str[] = { msousa@2654: [naf_ascii] = "ascii", msousa@2654: [naf_rtu ] = "rtu", msousa@2654: [naf_tcp ] = "tcp" msousa@2654: }; msousa@2654: msousa@2654: msousa@2654: #define __safe_strcnpy(str_dest, str_orig, max_size) { \ msousa@2654: strncpy(str_dest, str_orig, max_size); \ msousa@2654: str_dest[max_size - 1] = '\0'; \ msousa@2654: } msousa@2654: msousa@2654: msousa@2654: /* NOTE: The host, port and device parameters are strings that may be changed msousa@2654: * (by calling the following functions) after loading the compiled code msousa@2654: * (.so file) into memory, but before the code starts running msousa@2654: * (i.e. before __init_() gets called). msousa@2654: * This means that the host, port and device parameters may be changed msousa@2654: * _before_ they get mapped onto the str1 and str2 variables by __init_(), msousa@2654: * which is why the following functions must access the str1 and str2 msousa@2654: * parameters directly. msousa@2654: */ msousa@2654: const char * __modbus_get_ClientNode_config_name(int nodeid) {return client_nodes[nodeid].config_name; } msousa@2654: const char * __modbus_get_ClientNode_host (int nodeid) {return client_nodes[nodeid].str1; } msousa@2654: const char * __modbus_get_ClientNode_port (int nodeid) {return client_nodes[nodeid].str2; } msousa@2654: const char * __modbus_get_ClientNode_device (int nodeid) {return client_nodes[nodeid].str1; } msousa@2654: int __modbus_get_ClientNode_baud (int nodeid) {return client_nodes[nodeid].node_address.addr.rtu.baud; } msousa@2654: int __modbus_get_ClientNode_parity (int nodeid) {return client_nodes[nodeid].node_address.addr.rtu.parity; } msousa@2654: int __modbus_get_ClientNode_stop_bits (int nodeid) {return client_nodes[nodeid].node_address.addr.rtu.stop_bits;} msousa@2654: u64 __modbus_get_ClientNode_comm_period(int nodeid) {return client_nodes[nodeid].comm_period; } msousa@3733: u64 __modbus_get_ClientNode_req_delay (int nodeid) {return client_nodes[nodeid].req_delay; } msousa@2654: const char * __modbus_get_ClientNode_addr_type (int nodeid) {return addr_type_str[client_nodes[nodeid].node_address.naf];} msousa@2654: msousa@2654: const char * __modbus_get_ServerNode_config_name(int nodeid) {return server_nodes[nodeid].config_name; } msousa@2665: const char * __modbus_get_ServerNode_host (int nodeid) {char*x=server_nodes[nodeid].str1; return (x[0]=='\0'?"#ANY#":x); } msousa@2655: const char * __modbus_get_ServerNode_port (int nodeid) {return server_nodes[nodeid].str2; } msousa@2655: const char * __modbus_get_ServerNode_device (int nodeid) {return server_nodes[nodeid].str1; } msousa@2654: int __modbus_get_ServerNode_baud (int nodeid) {return server_nodes[nodeid].node_address.addr.rtu.baud; } msousa@2654: int __modbus_get_ServerNode_parity (int nodeid) {return server_nodes[nodeid].node_address.addr.rtu.parity; } msousa@2654: int __modbus_get_ServerNode_stop_bits (int nodeid) {return server_nodes[nodeid].node_address.addr.rtu.stop_bits;} msousa@2655: u8 __modbus_get_ServerNode_slave_id (int nodeid) {return server_nodes[nodeid].slave_id; } msousa@2654: const char * __modbus_get_ServerNode_addr_type (int nodeid) {return addr_type_str[server_nodes[nodeid].node_address.naf];} msousa@2654: msousa@2654: msousa@2654: void __modbus_set_ClientNode_host (int nodeid, const char * value) {__safe_strcnpy(client_nodes[nodeid].str1, value, MODBUS_PARAM_STRING_SIZE);} msousa@2654: void __modbus_set_ClientNode_port (int nodeid, const char * value) {__safe_strcnpy(client_nodes[nodeid].str2, value, MODBUS_PARAM_STRING_SIZE);} msousa@2654: void __modbus_set_ClientNode_device (int nodeid, const char * value) {__safe_strcnpy(client_nodes[nodeid].str1, value, MODBUS_PARAM_STRING_SIZE);} msousa@2654: void __modbus_set_ClientNode_baud (int nodeid, int value) {client_nodes[nodeid].node_address.addr.rtu.baud = value;} msousa@2654: void __modbus_set_ClientNode_parity (int nodeid, int value) {client_nodes[nodeid].node_address.addr.rtu.parity = value;} msousa@2654: void __modbus_set_ClientNode_stop_bits (int nodeid, int value) {client_nodes[nodeid].node_address.addr.rtu.stop_bits = value;} msousa@2654: void __modbus_set_ClientNode_comm_period(int nodeid, u64 value) {client_nodes[nodeid].comm_period = value;} msousa@3733: void __modbus_set_ClientNode_req_delay (int nodeid, u64 value) {client_nodes[nodeid].req_delay = value;} msousa@2654: msousa@2654: msousa@2665: void __modbus_set_ServerNode_host (int nodeid, const char * value) {if (strcmp(value,"#ANY#")==0) value = ""; msousa@2665: __safe_strcnpy(server_nodes[nodeid].str1, value, MODBUS_PARAM_STRING_SIZE);} msousa@2655: void __modbus_set_ServerNode_port (int nodeid, const char * value) {__safe_strcnpy(server_nodes[nodeid].str2, value, MODBUS_PARAM_STRING_SIZE);} msousa@2655: void __modbus_set_ServerNode_device (int nodeid, const char * value) {__safe_strcnpy(server_nodes[nodeid].str1, value, MODBUS_PARAM_STRING_SIZE);} msousa@2655: void __modbus_set_ServerNode_baud (int nodeid, int value) {server_nodes[nodeid].node_address.addr.rtu.baud = value;} msousa@2655: void __modbus_set_ServerNode_parity (int nodeid, int value) {server_nodes[nodeid].node_address.addr.rtu.parity = value;} msousa@2655: void __modbus_set_ServerNode_stop_bits (int nodeid, int value) {server_nodes[nodeid].node_address.addr.rtu.stop_bits = value;} msousa@2655: void __modbus_set_ServerNode_slave_id (int nodeid, u8 value) {server_nodes[nodeid].slave_id = value;} msousa@2655: