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() */
msousa@2647: #include
msousa@2647: #include
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,
Edouard@1912: &(client_requests[request_id].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,
Edouard@1912: &(client_requests[request_id].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,
Edouard@1912: &(client_requests[request_id].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,
Edouard@1912: &(client_requests[request_id].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,
Edouard@1912: &(client_requests[request_id].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,
Edouard@1912: &(client_requests[request_id].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,
Edouard@1912: &(client_requests[request_id].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,
Edouard@1912: &(client_requests[request_id].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:
Edouard@1912: static int __read_inbits (void *mem_map, u16 start_addr, u16 bit_count, u8 *data_bytes)
Edouard@1912: {return __pack_bits(((server_mem_t *)mem_map)->ro_bits, start_addr, bit_count, data_bytes);}
Edouard@1912: static int __read_outbits (void *mem_map, u16 start_addr, u16 bit_count, u8 *data_bytes)
Edouard@1912: {return __pack_bits(((server_mem_t *)mem_map)->rw_bits, start_addr, bit_count, data_bytes);}
Edouard@1912: static int __write_outbits (void *mem_map, u16 start_addr, u16 bit_count, u8 *data_bytes)
Edouard@1912: {return __unpack_bits(((server_mem_t *)mem_map)->rw_bits, start_addr, bit_count, data_bytes); }
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:
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:
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:
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: struct timespec next_cycle;
Edouard@1912: int period_sec = client_nodes[client_node_id].comm_period / 1000; /* comm_period is in ms */
Edouard@1912: int period_nsec = (client_nodes[client_node_id].comm_period %%1000)*1000000; /* comm_period is in ms */
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: // configure the timer for periodic activation
msousa@2647: {
msousa@2647: struct itimerspec timerspec;
msousa@2647: timerspec.it_interval.tv_sec = period_sec;
msousa@2647: timerspec.it_interval.tv_nsec = period_nsec;
msousa@2647: timerspec.it_value = timerspec.it_interval;
msousa@2647:
msousa@2647: if (timer_settime(client_nodes[client_node_id].timer_id, 0 /* flags */, &timerspec, NULL) < 0)
msousa@2647: fprintf(stderr, "Modbus plugin: Error configuring periodic activation timer for Modbus client %%s.\n", client_nodes[client_node_id].location);
msousa@2647: }
msousa@2647:
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);
Edouard@1912: fprintf(stderr, "Modbus client thread - new cycle (%%ld:%%ld)!\n", cur_time.tv_sec, cur_time.tv_nsec);
Edouard@1912: */
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@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:
Edouard@1912: int res_tmp = __execute_mb_request(req);
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;
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;
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;
Edouard@1912: break;
Edouard@1912: }
Edouard@1912: case MODBUS_ERROR: {
Edouard@1912: if (client_requests[req].prev_error != client_requests[req].error_code) {
Edouard@1912: 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].error_code);
Edouard@1912: if (client_requests[req].error_code <= MAX_MODBUS_ERROR_CODE ) {
Edouard@1912: fprintf(stderr, "(%%s)", modbus_error_messages[client_requests[req].error_code]);
Edouard@1912: fprintf(stderr, ".\n");
Edouard@1912: }
Edouard@1912: }
Edouard@1912: client_requests[req].prev_error = client_requests[req].error_code;
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@2647:
msousa@2647: /* We have just finished excuting a client transcation 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@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:
msousa@2647: /* Function to activate a client node's thread */
msousa@2647: /* returns -1 if it could not send the signal */
msousa@2647: static int __signal_client_thread(int client_node_id) {
msousa@2647: /* We TRY to signal the client thread.
msousa@2647: * We do this because this function can be called at the end of the PLC scan cycle
msousa@2647: * and we don't want it to block at that time.
msousa@2647: */
msousa@2647: if (pthread_mutex_trylock(&(client_nodes[client_node_id].mutex)) != 0)
msousa@2647: return -1;
msousa@2647: client_nodes[client_node_id].execute_req = 1; // tell the thread to execute
msousa@2647: pthread_cond_signal (&(client_nodes[client_node_id].condv));
msousa@2647: pthread_mutex_unlock(&(client_nodes[client_node_id].mutex));
msousa@2647: return 0;
msousa@2647: }
msousa@2647:
msousa@2647:
msousa@2647:
msousa@2647: /* Function that will be called whenever a client node's periodic timer expires. */
msousa@2647: /* The client node's thread will be waiting on a condition variable, so this function simply signals that
msousa@2647: * condition variable.
msousa@2647: *
msousa@2647: * The same callback function is called by the timers of all client nodes. The id of the client node
msousa@2647: * in question will be passed as a parameter to the call back function.
msousa@2647: */
msousa@2647: void __client_node_timer_callback_function(union sigval sigev_value) {
msousa@2647: /* signal the client node's condition variable on which the client node's thread should be waiting... */
msousa@2647: /* Since the communication cycle is run with the mutex locked, we use trylock() instead of lock() */
msousa@2647: //pthread_mutex_lock (&(client_nodes[sigev_value.sival_int].mutex));
msousa@2647: if (pthread_mutex_trylock (&(client_nodes[sigev_value.sival_int].mutex)) != 0)
msousa@2647: /* we never get to signal the thread for activation. But that is OK.
msousa@2647: * If it still in the communication cycle (during which the mutex is kept locked)
msousa@2647: * then that means that the communication cycle is falling behing in the periodic
msousa@2647: * communication cycle, and we therefore need to skip a period.
msousa@2647: */
msousa@2647: return;
msousa@2647: client_nodes[sigev_value.sival_int].execute_req = 1; // tell the thread to execute
msousa@2647: client_nodes[sigev_value.sival_int].periodic_act = 1; // tell the thread the activation was done by periodic timer
msousa@2647: pthread_cond_signal (&(client_nodes[sigev_value.sival_int].condv));
msousa@2647: pthread_mutex_unlock(&(client_nodes[sigev_value.sival_int].mutex));
msousa@2647: }
msousa@2647:
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:
Edouard@1912: for (index=0; index < NUMBER_OF_CLIENT_NODES;index++)
Edouard@1912: client_nodes[index].mb_nd = -1;
Edouard@1912: 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;
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 */
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 */
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:
msousa@2647: /* initialize the timer that will be used to periodically activate the client node */
msousa@2647: {
msousa@2647: // start off by reseting the flag that will be set whenever the timer expires
msousa@2647: client_nodes[index].periodic_act = 0;
msousa@2647:
msousa@2647: struct sigevent evp;
msousa@2647: evp.sigev_notify = SIGEV_THREAD; /* Notification method - call a function in a new thread context */
msousa@2647: evp.sigev_value.sival_int = index; /* Data passed to function upon notification - used to indentify which client node to activate */
msousa@2647: evp.sigev_notify_function = __client_node_timer_callback_function; /* function to call upon timer expiration */
msousa@2647: evp.sigev_notify_attributes = NULL; /* attributes for new thread in which sigev_notify_function will be called/executed */
msousa@2647:
msousa@2647: if (timer_create(CLOCK_MONOTONIC, &evp, &(client_nodes[index].timer_id)) < 0) {
msousa@2647: fprintf(stderr, "Modbus plugin: Error creating timer for modbus client node %%s\n", client_nodes[index].location);
msousa@2647: goto error_exit;
msousa@2647: }
msousa@2647: }
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@1912: res |= pthread_create(&(client_nodes[index].thread_id), &attr, &__mb_client_thread, (void *)((char *)NULL + index));
Edouard@1912: if (res != 0) {
msousa@2647: fprintf(stderr, "Modbus plugin: Error starting thread for modbus client node %%s\n", 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@1912: res |= pthread_create(&(server_nodes[index].thread_id), &attr, &__mb_server_thread, (void *)&(server_nodes[index]));
Edouard@1912: if (res != 0) {
Edouard@1912: fprintf(stderr, "Modbus plugin: Error starting modbus server thread for node %%s\n", 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@2647: if (__signal_client_thread(client_node_id) >= 0) {
msousa@2647: /* - upon success, set flag_exec_started
msousa@2647: * - both flags (flag_exec_req and flag_exec_started) will be reset
msousa@2647: * once the transaction has completed.
msousa@2647: */
msousa@2647: client_requests[index].flag_exec_started = 1;
msousa@2647: }
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@2647: // timer was created, so we try to destroy it!
msousa@2647: close = timer_delete(client_nodes[index].timer_id);
msousa@2647: if (close < 0)
msousa@2647: fprintf(stderr, "Modbus plugin: Error destroying timer 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 >= 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:
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: