# HG changeset patch # User Mario de Sousa # Date 1488674274 0 # Node ID bb883e063175bd62bf9432ccea054f6c5575eedb # Parent de4ee16f7c6c168a284064e2e64f529a12b0d16d Add support for Modbus (TCP and RTU) working as master & slave diff -r de4ee16f7c6c -r bb883e063175 features.py --- a/features.py Wed Oct 21 15:00:32 2015 +0100 +++ b/features.py Sun Mar 05 00:37:54 2017 +0000 @@ -5,6 +5,7 @@ catalog = [ ('canfestival', _('CANopen support'), _('Map located variables over CANopen'), 'canfestival.canfestival.RootClass'), + ('modbus', _('Modbus support'), _('Map located variables over Modbus'), 'modbus.modbus.RootClass'), ('c_ext', _('C extension'), _('Add C code accessing located variables synchronously'), 'c_ext.CFile'), ('py_ext', _('Python file'), _('Add Python code executed asynchronously'), 'py_ext.PythonFile'), ('wxglade_hmi', _('WxGlade GUI'), _('Add a simple WxGlade based GUI.'), 'wxglade_hmi.WxGladeHMI'), diff -r de4ee16f7c6c -r bb883e063175 modbus/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modbus/README Sun Mar 05 00:37:54 2017 +0000 @@ -0,0 +1,1 @@ +Modbus diff -r de4ee16f7c6c -r bb883e063175 modbus/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modbus/__init__.py Sun Mar 05 00:37:54 2017 +0000 @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of Beremiz, a Integrated Development Environment for +# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. +# +# Copyright (c) 2016 Mario de Sousa (msousa@fe.up.pt) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# This code is made available on the understanding that it will not be +# used in safety-critical situations without a full and competent review. + + +from modbus import * diff -r de4ee16f7c6c -r bb883e063175 modbus/mb_runtime.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modbus/mb_runtime.c Sun Mar 05 00:37:54 2017 +0000 @@ -0,0 +1,615 @@ +/* File generated by Beremiz (PlugGenerate_C method of Modbus plugin) */ + +/* + * Copyright (c) 2016 Mario de Sousa (msousa@fe.up.pt) + * + * This file is part of the Modbus library for Beremiz and matiec. + * + * This Modbus library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this Modbus library. If not, see . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + + +#include +#include /* required for memcpy() */ +#include "mb_slave_and_master.h" +#include "MB_%(locstr)s.h" + + +#define MAX_MODBUS_ERROR_CODE 11 +static const char *modbus_error_messages[MAX_MODBUS_ERROR_CODE+1] = { + /* 0 */ "", /* un-used -> no error! */ + /* 1 */ "illegal/unsuported function", + /* 2 */ "illegal data address", + /* 3 */ "illegal data value", + /* 4 */ "slave device failure", + /* 5 */ "acknowledge -> slave intends to reply later", + /* 6 */ "slave device busy", + /* 7 */ "negative acknowledge", + /* 8 */ "memory parity error", + /* 9 */ "", /* undefined by Modbus */ + /* 10*/ "gateway path unavalilable", + /* 11*/ "gateway target device failed to respond" +}; + + +/* Execute a modbus client transaction/request */ +static int __execute_mb_request(int request_id){ + switch (client_requests[request_id].mb_function){ + + case 1: /* read coils */ + return read_output_bits(client_requests[request_id].slave_id, + client_requests[request_id].address, + client_requests[request_id].count, + client_requests[request_id].coms_buffer, + (int) client_requests[request_id].count, + client_nodes[client_requests[request_id].client_node_id].mb_nd, + client_requests[request_id].retries, + &(client_requests[request_id].error_code), + &(client_requests[request_id].resp_timeout), + &(client_requests[request_id].coms_buf_mutex)); + + case 2: /* read discrete inputs */ + return read_input_bits( client_requests[request_id].slave_id, + client_requests[request_id].address, + client_requests[request_id].count, + client_requests[request_id].coms_buffer, + (int) client_requests[request_id].count, + client_nodes[client_requests[request_id].client_node_id].mb_nd, + client_requests[request_id].retries, + &(client_requests[request_id].error_code), + &(client_requests[request_id].resp_timeout), + &(client_requests[request_id].coms_buf_mutex)); + + case 3: /* read holding registers */ + return read_output_words(client_requests[request_id].slave_id, + client_requests[request_id].address, + client_requests[request_id].count, + client_requests[request_id].coms_buffer, + (int) client_requests[request_id].count, + client_nodes[client_requests[request_id].client_node_id].mb_nd, + client_requests[request_id].retries, + &(client_requests[request_id].error_code), + &(client_requests[request_id].resp_timeout), + &(client_requests[request_id].coms_buf_mutex)); + + case 4: /* read input registers */ + return read_input_words(client_requests[request_id].slave_id, + client_requests[request_id].address, + client_requests[request_id].count, + client_requests[request_id].coms_buffer, + (int) client_requests[request_id].count, + client_nodes[client_requests[request_id].client_node_id].mb_nd, + client_requests[request_id].retries, + &(client_requests[request_id].error_code), + &(client_requests[request_id].resp_timeout), + &(client_requests[request_id].coms_buf_mutex)); + + case 5: /* write single coil */ + return write_output_bit(client_requests[request_id].slave_id, + client_requests[request_id].address, + client_requests[request_id].coms_buffer[0], + client_nodes[client_requests[request_id].client_node_id].mb_nd, + client_requests[request_id].retries, + &(client_requests[request_id].error_code), + &(client_requests[request_id].resp_timeout), + &(client_requests[request_id].coms_buf_mutex)); + + case 6: /* write single register */ + return write_output_word(client_requests[request_id].slave_id, + client_requests[request_id].address, + client_requests[request_id].coms_buffer[0], + client_nodes[client_requests[request_id].client_node_id].mb_nd, + client_requests[request_id].retries, + &(client_requests[request_id].error_code), + &(client_requests[request_id].resp_timeout), + &(client_requests[request_id].coms_buf_mutex)); + + case 7: break; /* function not yet supported */ + case 8: break; /* function not yet supported */ + case 9: break; /* function not yet supported */ + case 10: break; /* function not yet supported */ + case 11: break; /* function not yet supported */ + case 12: break; /* function not yet supported */ + case 13: break; /* function not yet supported */ + case 14: break; /* function not yet supported */ + + case 15: /* write multiple coils */ + return write_output_bits(client_requests[request_id].slave_id, + client_requests[request_id].address, + client_requests[request_id].count, + client_requests[request_id].coms_buffer, + client_nodes[client_requests[request_id].client_node_id].mb_nd, + client_requests[request_id].retries, + &(client_requests[request_id].error_code), + &(client_requests[request_id].resp_timeout), + &(client_requests[request_id].coms_buf_mutex)); + + case 16: /* write multiple registers */ + return write_output_words(client_requests[request_id].slave_id, + client_requests[request_id].address, + client_requests[request_id].count, + client_requests[request_id].coms_buffer, + client_nodes[client_requests[request_id].client_node_id].mb_nd, + client_requests[request_id].retries, + &(client_requests[request_id].error_code), + &(client_requests[request_id].resp_timeout), + &(client_requests[request_id].coms_buf_mutex)); + + default: break; /* should never occur, if file generation is correct */ + } + + fprintf(stderr, "Modbus plugin: Modbus function %%d not supported\n", request_id); /* should never occur, if file generation is correct */ + return -1; +} + + + +/* pack bits from unpacked_data to packed_data */ +static inline int __pack_bits(u16 *unpacked_data, u16 start_addr, u16 bit_count, u8 *packed_data) { + u8 bit; + u16 byte, coils_processed; + + if ((0 == bit_count) || (65535-start_addr < bit_count-1)) + return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */ + + for( byte = 0, coils_processed = 0; coils_processed < bit_count; byte++) { + packed_data[byte] = 0; + for( bit = 0x01; (bit & 0xFF) && (coils_processed < bit_count); bit <<= 1, coils_processed++ ) { + if(unpacked_data[start_addr + coils_processed]) + packed_data[byte] |= bit; /* set bit */ + else packed_data[byte] &= ~bit; /* reset bit */ + } + } + return 0; +} + + +/* unpack bits from packed_data to unpacked_data */ +static inline int __unpack_bits(u16 *unpacked_data, u16 start_addr, u16 bit_count, u8 *packed_data) { + u8 temp, bit; + u16 byte, coils_processed; + + if ((0 == bit_count) || (65535-start_addr < bit_count-1)) + return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */ + + for(byte = 0, coils_processed = 0; coils_processed < bit_count; byte++) { + temp = packed_data[byte] ; + for(bit = 0x01; (bit & 0xff) && (coils_processed < bit_count); bit <<= 1, coils_processed++) { + unpacked_data[start_addr + coils_processed] = (temp & bit)?1:0; + } + } + return 0; +} + + +static int __read_inbits (void *mem_map, u16 start_addr, u16 bit_count, u8 *data_bytes) + {return __pack_bits(((server_mem_t *)mem_map)->ro_bits, start_addr, bit_count, data_bytes);} +static int __read_outbits (void *mem_map, u16 start_addr, u16 bit_count, u8 *data_bytes) + {return __pack_bits(((server_mem_t *)mem_map)->rw_bits, start_addr, bit_count, data_bytes);} +static int __write_outbits (void *mem_map, u16 start_addr, u16 bit_count, u8 *data_bytes) + {return __unpack_bits(((server_mem_t *)mem_map)->rw_bits, start_addr, bit_count, data_bytes); } + + + +static int __read_inwords (void *mem_map, u16 start_addr, u16 word_count, u16 *data_words) { + u16 count; + // return -ERR_ILLEGAL_FUNCTION; /* function not yet supported *//* ERR_ILLEGAL_FUNCTION defined in mb_util.h */ + + if ((start_addr + word_count) > MEM_AREA_SIZE) + return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */ + + /* use memcpy() as it is more efficient... + for (count = 0; count < word_count ; count++) + data_words[count] = ((server_mem_t *)mem_map)->ro_words[count + start_addr]; + */ + memcpy(/* dest */ (void *)data_words, + /* src */ (void *)&(((server_mem_t *)mem_map)->ro_words[start_addr]), + /* size */ word_count * 2); + return 0; +} + + + +static int __read_outwords (void *mem_map, u16 start_addr, u16 word_count, u16 *data_words) { + u16 count; + // return -ERR_ILLEGAL_FUNCTION; /* function not yet supported *//* ERR_ILLEGAL_FUNCTION defined in mb_util.h */ + + if ((start_addr + word_count) > MEM_AREA_SIZE) + return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */ + + /* use memcpy() as it is more efficient... + for (count = 0; count < word_count ; count++) + data_words[count] = ((server_mem_t *)mem_map)->rw_words[count + start_addr]; + */ + memcpy(/* dest */ (void *)data_words, + /* src */ (void *)&(((server_mem_t *)mem_map)->rw_words[start_addr]), + /* size */ word_count * 2); + return 0; +} + + + + +static int __write_outwords(void *mem_map, u16 start_addr, u16 word_count, u16 *data_words) { + u16 count; + // return -ERR_ILLEGAL_FUNCTION; /* function not yet supported *//* ERR_ILLEGAL_FUNCTION defined in mb_util.h */ + + if ((start_addr + word_count) > MEM_AREA_SIZE) + return -ERR_ILLEGAL_DATA_ADDRESS; /* ERR_ILLEGAL_DATA_ADDRESS defined in mb_util.h */ + + /* WARNING: The data returned in the data_words[] array is not guaranteed to be 16 bit aligned. + * It is not therefore safe to cast it to an u16 data type. + * The following code cannot be used. memcpy() is used instead. + */ + /* + for (count = 0; count < word_count ; count++) + ((server_mem_t *)mem_map)->rw_words[count + start_addr] = data_words[count]; + */ + memcpy(/* dest */ (void *)&(((server_mem_t *)mem_map)->rw_words[start_addr]), + /* src */ (void *)data_words, + /* size */ word_count * 2); + return 0; +} + + + + +#include + +static void *__mb_server_thread(void *_server_node) { + server_node_t *server_node = _server_node; + mb_slave_callback_t callbacks = { + &__read_inbits, + &__read_outbits, + &__write_outbits, + &__read_inwords, + &__read_outwords, + &__write_outwords, + (void *)&(server_node->mem_area) + }; + + // Enable thread cancelation. Enabled is default, but set it anyway to be safe. + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + + // mb_slave_run() should never return! + mb_slave_run(server_node->mb_nd /* nd */, callbacks, server_node->slave_id); + fprintf(stderr, "Modbus plugin: Modbus server for node %%s died unexpectedly!\n", server_node->location); /* should never occur */ + return NULL; +} + + + +static void *__mb_client_thread(void *_index) { + int client_node_id = (char *)_index - (char *)NULL; // Use pointer arithmetic (more portable than cast) + struct timespec next_cycle; + int period_sec = client_nodes[client_node_id].comm_period / 1000; /* comm_period is in ms */ + int period_nsec = (client_nodes[client_node_id].comm_period %%1000)*1000000; /* comm_period is in ms */ + + // Enable thread cancelation. Enabled is default, but set it anyway to be safe. + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + + // get the current time + clock_gettime(CLOCK_MONOTONIC, &next_cycle); + + // loop the communication with the client + while (1) { + /* + struct timespec cur_time; + clock_gettime(CLOCK_MONOTONIC, &cur_time); + fprintf(stderr, "Modbus client thread - new cycle (%%ld:%%ld)!\n", cur_time.tv_sec, cur_time.tv_nsec); + */ + int req; + for (req=0; req < NUMBER_OF_CLIENT_REQTS; req ++){ + /*just do the requests belonging to the client */ + if (client_requests[req].client_node_id != client_node_id) + continue; + int res_tmp = __execute_mb_request(req); + switch (res_tmp) { + case PORT_FAILURE: { + if (res_tmp != client_nodes[client_node_id].prev_error) + fprintf(stderr, "Modbus plugin: Error connecting Modbus client %%s to remote server.\n", client_nodes[client_node_id].location); + client_nodes[client_node_id].prev_error = res_tmp; + break; + } + case INVALID_FRAME: { + if ((res_tmp != client_requests[req].prev_error) && (0 == client_nodes[client_node_id].prev_error)) + 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); + client_requests[req].prev_error = res_tmp; + break; + } + case TIMEOUT: { + if ((res_tmp != client_requests[req].prev_error) && (0 == client_nodes[client_node_id].prev_error)) + fprintf(stderr, "Modbus plugin: Modbus client request configured at location %%s timed out waiting for reply from server.\n", client_requests[req].location); + client_requests[req].prev_error = res_tmp; + break; + } + case MODBUS_ERROR: { + if (client_requests[req].prev_error != client_requests[req].error_code) { + 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); + if (client_requests[req].error_code <= MAX_MODBUS_ERROR_CODE ) { + fprintf(stderr, "(%%s)", modbus_error_messages[client_requests[req].error_code]); + fprintf(stderr, ".\n"); + } + } + client_requests[req].prev_error = client_requests[req].error_code; + break; + } + default: { + if ((res_tmp >= 0) && (client_nodes[client_node_id].prev_error != 0)) { + fprintf(stderr, "Modbus plugin: Modbus client %%s has reconnected to server/slave.\n", client_nodes[client_node_id].location); + } + if ((res_tmp >= 0) && (client_requests[req] .prev_error != 0)) { + fprintf(stderr, "Modbus plugin: Modbus client request configured at location %%s has succesfully resumed comunication.\n", client_requests[req].location); + } + client_nodes[client_node_id].prev_error = 0; + client_requests[req] .prev_error = 0; + break; + } + } + } + // Determine absolute time instant for starting the next cycle + // struct timespec prev_cycle; + // prev_cycle = next_cycle; + next_cycle.tv_sec += period_sec; + next_cycle.tv_nsec += period_nsec; + if (next_cycle.tv_nsec >= 1000000000) { + next_cycle.tv_sec ++; + next_cycle.tv_nsec -= 1000000000; + } + /* It probably does not make sense to check for overflow of timer. + * Even in 32 bit systems this will take at least 68 years since the computer booted + * (remember, we are using CLOCK_MONOTONIC, which should start counting from 0 + * every time the system boots). On 64 bit systems, it will take over + * 10^11 years to overflow. + */ + /* + if (next_cycle.tv_sec) < prev_cycle.tv_sec) { + // we will lose some precision by reading the time again, + // but it is better than the alternative... + clock_gettime(CLOCK_MONOTONIC, &next_cycle); + next_cycle.tv_sec += period_sec; + next_cycle.tv_nsec += period_nsec; + if (next_cycle.tv_nsec >= 1000000000) { + next_cycle.tv_sec ++; + next_cycle.tv_nsec -= 1000000000; + } + } + */ + clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_cycle, NULL); + } + + // humour the compiler. + return NULL; +} + + + +int __init_%(locstr)s (int argc, char **argv){ + int index; + + for (index=0; index < NUMBER_OF_CLIENT_NODES;index++) + client_nodes[index].mb_nd = -1; + for (index=0; index < NUMBER_OF_SERVER_NODES;index++) + // mb_nd with negative numbers indicate how far it has been initialised (or not) + // -2 --> no modbus node created; no thread created + // -1 --> modbus node created!; no thread created + // >=0 --> modbus node created!; thread created! + server_nodes[index].mb_nd = -2; + + /* modbus library init */ + /* Note that TOTAL_xxxNODE_COUNT are the nodes required by _ALL_ the instances of the modbus + * extension currently in the user's project. This file (MB_xx.c) is handling only one instance, + * but must initialize the library for all instances. Only the first call to mb_slave_and_master_init() + * will result in memory being allocated. All subsequent calls (by other MB_xx,c files) will be ignored + * by the mb_slave_and_master_init() funtion, as long as they are called with the same arguments. + */ + if (mb_slave_and_master_init(TOTAL_TCPNODE_COUNT, TOTAL_RTUNODE_COUNT, TOTAL_ASCNODE_COUNT) <0) { + fprintf(stderr, "Modbus plugin: Error starting modbus library\n"); + // return imediately. Do NOT goto error_exit, as we did not get to + // start the modbus library! + return -1; + } + + /* init the mutex for each client request */ + /* Must be done _before_ launching the client threads!! */ + for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++){ + if (pthread_mutex_init(&(client_requests[index].coms_buf_mutex), NULL)) { + fprintf(stderr, "Modbus plugin: Error initializing request for modbus client node %%s\n", client_nodes[client_requests[index].client_node_id].location); + goto error_exit; + } + } + + /* init each client connection to remote modbus server, and launch thread */ + /* NOTE: All client_nodes[].init_state are initialised to 0 in the code + * generated by the modbus plugin + */ + for (index=0; index < NUMBER_OF_CLIENT_NODES;index++){ + /* establish client connection */ + client_nodes[index].mb_nd = mb_master_connect (client_nodes[index].node_address); + if (client_nodes[index].mb_nd < 0){ + fprintf(stderr, "Modbus plugin: Error creating modbus client node %%s\n", client_nodes[index].location); + goto error_exit; + } + client_nodes[index].init_state = 1; // we have created the node + + /* launch a thread to handle this client node */ + { + int res = 0; + pthread_attr_t attr; + res |= pthread_attr_init(&attr); + res |= pthread_create(&(client_nodes[index].thread_id), &attr, &__mb_client_thread, (void *)((char *)NULL + index)); + if (res != 0) { + fprintf(stderr, "Modbus plugin: Error starting modbus client thread for node %%s\n", client_nodes[index].location); + goto error_exit; + } + } + client_nodes[index].init_state = 2; // we have created the node and a thread + } + + /* init each local server */ + /* NOTE: All server_nodes[].init_state are initialised to 0 in the code + * generated by the modbus plugin + */ + for (index=0; index < NUMBER_OF_SERVER_NODES;index++){ + /* create the modbus server */ + server_nodes[index].mb_nd = mb_slave_new (server_nodes[index].node_address); + if (server_nodes[index].mb_nd < 0){ + fprintf(stderr, "Modbus plugin: Error creating modbus server node %%s\n", server_nodes[index].location); + goto error_exit; + } + server_nodes[index].init_state = 1; // we have created the node + + /* launch a thread to handle this server node */ + { + int res = 0; + pthread_attr_t attr; + res |= pthread_attr_init(&attr); + res |= pthread_create(&(server_nodes[index].thread_id), &attr, &__mb_server_thread, (void *)&(server_nodes[index])); + if (res != 0) { + fprintf(stderr, "Modbus plugin: Error starting modbus server thread for node %%s\n", server_nodes[index].location); + goto error_exit; + } + } + server_nodes[index].init_state = 2; // we have created the node and thread + } + + return 0; + +error_exit: + __cleanup_%(locstr)s (); + return -1; +} + + + + + +void __publish_%(locstr)s (){ + int index; + + for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++){ + /*just do the output requests */ + if (client_requests[index].req_type == req_output){ + pthread_mutex_lock(&(client_requests[index].coms_buf_mutex)); + // copy from plcv_buffer to coms_buffer + memcpy((void *)client_requests[index].coms_buffer /* destination */, + (void *)client_requests[index].plcv_buffer /* source */, + REQ_BUF_SIZE * sizeof(u16) /* size in bytes */); + pthread_mutex_unlock(&(client_requests[index].coms_buf_mutex)); + } + } +} + + + + + +void __retrieve_%(locstr)s (){ + int index; + + for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++){ + /*just do the input requests */ + if (client_requests[index].req_type == req_input){ + pthread_mutex_lock(&(client_requests[index].coms_buf_mutex)); + // copy from coms_buffer to plcv_buffer + memcpy((void *)client_requests[index].plcv_buffer /* destination */, + (void *)client_requests[index].coms_buffer /* source */, + REQ_BUF_SIZE * sizeof(u16) /* size in bytes */); + pthread_mutex_unlock(&(client_requests[index].coms_buf_mutex)); + } + } +} + + + + + +int __cleanup_%(locstr)s (){ + int index, close; + int res = 0; + + /* kill thread and close connections of each modbus client node */ + for (index=0; index < NUMBER_OF_CLIENT_NODES; index++) { + close = 0; + if (client_nodes[index].init_state >= 2) { + // thread was launched, so we try to cancel it! + close = pthread_cancel(client_nodes[index].thread_id); + close |= pthread_join (client_nodes[index].thread_id, NULL); + if (close < 0) + fprintf(stderr, "Modbus plugin: Error closing thread for modbus client %%s\n", client_nodes[index].location); + } + res |= close; + + close = 0; + if (client_nodes[index].init_state >= 1) { + // modbus client node was created, so we try to close it! + close = mb_master_close (client_nodes[index].mb_nd); + if (close < 0){ + fprintf(stderr, "Modbus plugin: Error closing modbus client node %%s\n", client_nodes[index].location); + // We try to shut down as much as possible, so we do not return noW! + } + client_nodes[index].mb_nd = -1; + } + res |= close; + client_nodes[index].init_state = 0; + } + + /* kill thread and close connections of each modbus server node */ + for (index=0; index < NUMBER_OF_SERVER_NODES; index++) { + close = 0; + if (server_nodes[index].init_state >= 2) { + // thread was launched, so we try to cancel it! + close = pthread_cancel(server_nodes[index].thread_id); + close |= pthread_join (server_nodes[index].thread_id, NULL); + if (close < 0) + fprintf(stderr, "Modbus plugin: Error closing thread for modbus server %%s\n", server_nodes[index].location); + } + res |= close; + + close = 0; + if (server_nodes[index].init_state >= 1) { + // modbus server node was created, so we try to close it! + close = mb_slave_close (server_nodes[index].mb_nd); + if (close < 0) { + fprintf(stderr, "Modbus plugin: Error closing node for modbus server %%s (%%d)\n", server_nodes[index].location, server_nodes[index].mb_nd); + // We try to shut down as much as possible, so we do not return noW! + } + server_nodes[index].mb_nd = -1; + } + res |= close; + server_nodes[index].init_state = 0; + } + + /* destroy the mutex of each client request */ + for (index=0; index < NUMBER_OF_CLIENT_REQTS; index ++) { + if (pthread_mutex_destroy(&(client_requests[index].coms_buf_mutex))) { + fprintf(stderr, "Modbus plugin: Error destroying request for modbus client node %%s\n", client_nodes[client_requests[index].client_node_id].location); + // We try to shut down as much as possible, so we do not return noW! + res |= -1; + } + } + + /* modbus library close */ + //fprintf(stderr, "Shutting down modbus library...\n"); + if (mb_slave_and_master_done()<0) { + fprintf(stderr, "Modbus plugin: Error shutting down modbus library\n"); + res |= -1; + } + + return res; +} + diff -r de4ee16f7c6c -r bb883e063175 modbus/mb_runtime.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modbus/mb_runtime.h Sun Mar 05 00:37:54 2017 +0000 @@ -0,0 +1,148 @@ +/* File generated by Beremiz (PlugGenerate_C method of modbus Plugin instance) */ + +/* + * Copyright (c) 2016 Mario de Sousa (msousa@fe.up.pt) + * + * This file is part of the Modbus library for Beremiz and matiec. + * + * This Modbus library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this Modbus library. If not, see . + * + * This code is made available on the understanding that it will not be + * used in safety-critical situations without a full and competent review. + */ + +#include "mb_addr.h" +#include "mb_tcp_private.h" +#include "mb_master_private.h" + + + +#define DEF_REQ_SEND_RETRIES 0 + + // Used by the Modbus server node +#define MEM_AREA_SIZE 65536 +typedef struct{ + u16 ro_bits [MEM_AREA_SIZE]; + u16 rw_bits [MEM_AREA_SIZE]; + u16 ro_words[MEM_AREA_SIZE]; + u16 rw_words[MEM_AREA_SIZE]; + } server_mem_t; + +typedef struct{ + const char *location; + u8 slave_id; + node_addr_t node_address; + int mb_nd; // modbus library node used for this server + int init_state; // store how far along the server's initialization has progressed + pthread_t thread_id; // thread handling this server + server_mem_t mem_area; + } server_node_t; + + + // Used by the Modbus client node +typedef struct{ + const char *location; + node_addr_t node_address; + int mb_nd; + int init_state; // store how far along the client's initialization has progressed + u64 comm_period; + int prev_error; // error code of the last printed error message (0 when no error) + pthread_t thread_id; // thread handling all communication with this client + } client_node_t; + + + // Used by the Modbus client plugin +typedef enum { + req_input, + req_output, + no_request /* just for tests to quickly disable a request */ + } iotype_t; + +#define REQ_BUF_SIZE 2000 +typedef struct{ + const char *location; + int client_node_id; + u8 slave_id; + iotype_t req_type; + u8 mb_function; + u16 address; + u16 count; + int retries; + u8 error_code; // modbus error code (if any) of current request + int prev_error; // error code of the last printed error message (0 when no error) + struct timespec resp_timeout; + // buffer used to store located PLC variables + u16 plcv_buffer[REQ_BUF_SIZE]; + // buffer used to store data coming from / going to server + u16 coms_buffer[REQ_BUF_SIZE]; + pthread_mutex_t coms_buf_mutex; // mutex to access coms_buffer[] + } client_request_t; + + +/* The total number of nodes, needed to support _all_ instances of the modbus plugin */ +#define TOTAL_TCPNODE_COUNT %(total_tcpnode_count)s +#define TOTAL_RTUNODE_COUNT %(total_rtunode_count)s +#define TOTAL_ASCNODE_COUNT %(total_ascnode_count)s + +/* Values for instance %(locstr)s of the modbus plugin */ +#define MAX_NUMBER_OF_TCPCLIENTS %(max_remote_tcpclient)s + +#define NUMBER_OF_TCPSERVER_NODES %(tcpserver_node_count)s +#define NUMBER_OF_TCPCLIENT_NODES %(tcpclient_node_count)s +#define NUMBER_OF_TCPCLIENT_REQTS %(tcpclient_reqs_count)s + +#define NUMBER_OF_RTUSERVER_NODES %(rtuserver_node_count)s +#define NUMBER_OF_RTUCLIENT_NODES %(rtuclient_node_count)s +#define NUMBER_OF_RTUCLIENT_REQTS %(rtuclient_reqs_count)s + +#define NUMBER_OF_ASCIISERVER_NODES %(ascserver_node_count)s +#define NUMBER_OF_ASCIICLIENT_NODES %(ascclient_node_count)s +#define NUMBER_OF_ASCIICLIENT_REQTS %(ascclient_reqs_count)s + +#define NUMBER_OF_SERVER_NODES (NUMBER_OF_TCPSERVER_NODES + \ + NUMBER_OF_RTUSERVER_NODES + \ + NUMBER_OF_ASCIISERVER_NODES) + +#define NUMBER_OF_CLIENT_NODES (NUMBER_OF_TCPCLIENT_NODES + \ + NUMBER_OF_RTUCLIENT_NODES + \ + NUMBER_OF_ASCIICLIENT_NODES) + +#define NUMBER_OF_CLIENT_REQTS (NUMBER_OF_TCPCLIENT_REQTS + \ + NUMBER_OF_RTUCLIENT_REQTS + \ + NUMBER_OF_ASCIICLIENT_REQTS) + + +/*initialization following all parameters given by user in application*/ + +static client_node_t client_nodes[NUMBER_OF_CLIENT_NODES] = { +%(client_nodes_params)s +}; + + +static client_request_t client_requests[NUMBER_OF_CLIENT_REQTS] = { +%(client_req_params)s +}; + + +static server_node_t server_nodes[NUMBER_OF_SERVER_NODES] = { +%(server_nodes_params)s +} +; + +/*******************/ +/*located variables*/ +/*******************/ + +%(loc_vars)s + diff -r de4ee16f7c6c -r bb883e063175 modbus/mb_utils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modbus/mb_utils.py Sun Mar 05 00:37:54 2017 +0000 @@ -0,0 +1,236 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of Beremiz, a Integrated Development Environment for +# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. +# +# Copyright (c) 2016 Mario de Sousa (msousa@fe.up.pt) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# This code is made available on the understanding that it will not be +# used in safety-critical situations without a full and competent review. + + + + +#dictionary implementing: +#key - string with the description we want in the request plugin GUI +#tuple - (modbus function number, request type, max count value, data_type, bit_size) +modbus_function_dict = {"01 - Read Coils" : ( '1', 'req_input', 2000, "BOOL", 1 , "Q", "X", "Coil"), + "02 - Read Input Discretes" : ( '2', 'req_input', 2000, "BOOL", 1 , "I", "X", "Input Discrete"), + "03 - Read Holding Registers" : ( '3', 'req_input', 125, "WORD", 16, "Q", "W", "Holding Register"), + "04 - Read Input Registers" : ( '4', 'req_input', 125, "WORD", 16, "I", "W", "Input Register"), + "05 - Write Single coil" : ( '5','req_output', 1, "BOOL", 1 , "Q", "X", "Coil"), + "06 - Write Single Register" : ( '6','req_output', 1, "WORD", 16, "Q", "W", "Holding Register"), + "15 - Write Multiple Coils" : ('15','req_output', 1968, "BOOL", 1 , "Q", "X", "Coil"), + "16 - Write Multiple Registers" : ('16','req_output', 123, "WORD", 16, "Q", "W", "Holding Register"), + } + + + +def GetTCPServerNodePrinted(self, child): + """ + Outputs a string to be used on C files + params: child - the correspondent subplugin in Beremiz + """ + node_init_template = '''/*node %(locnodestr)s*/ +{"%(locnodestr)s", %(slaveid)s, {naf_tcp, {.tcp = {%(host)s, "%(port)s", DEF_CLOSE_ON_SILENCE}}}, -1 /* mb_nd */, 0 /* init_state */}''' + + location = ".".join(map(str, child.GetCurrentLocation())) + host = child.GetParamsAttributes()[0]["children"][0]["value"] + port = child.GetParamsAttributes()[0]["children"][1]["value"] + slaveid = child.GetParamsAttributes()[0]["children"][2]["value"] + if host=="#ANY#": + host='INADDR_ANY' + else: + host='"'+host+'"' + #slaveid = child.GetParamsAttributes()[0]["children"][2]["value"] + #if int(slaveid) not in xrange(256): + #self.GetCTRoot().logger.write_error("Error: Wrong slave ID in %s server node\nModbus Plugin C code returns empty\n"%location) + #return None + + node_dict = {"locnodestr" : location, + "host" : host, + "port" : port, + "slaveid" : slaveid, + } + return node_init_template % node_dict + + + + +def GetTCPServerMemAreaPrinted(self, child, nodeid): + """ + Outputs a string to be used on C files + params: child - the correspondent subplugin in Beremiz + nodeid - on C code, each request has it's own parent node (sequential, 0..NUMBER_OF_NODES) + It's this parameter. + return: None - if any definition error found + The string that should be added on C code - if everything goes allright + """ + request_dict = {} + + request_dict["locreqstr"] = "_".join(map(str, child.GetCurrentLocation())) + request_dict["nodeid"] = str(nodeid) + request_dict["address"] = child.GetParamsAttributes()[0]["children"][2]["value"] + if int(request_dict["address"]) not in xrange(65536): + self.GetCTRoot().logger.write_error("Modbus plugin: Invalid Start Address in server memory area node %(locreqstr)s (Must be in the range [0..65535])\nModbus plugin: Aborting C code generation for this node\n"%request_dict) + return None + request_dict["count"] = child.GetParamsAttributes()[0]["children"][1]["value"] + if int(request_dict["count"]) not in xrange(1, 65536): + self.GetCTRoot().logger.write_error("Modbus plugin: Invalid number of channels in server memory area node %(locreqstr)s (Must be in the range [1..65536-start_address])\nModbus plugin: Aborting C code generation for this node\n"%request_dict) + return None + if (int(request_dict["address"]) + int(request_dict["count"])) not in xrange(1,65537): + self.GetCTRoot().logger.write_error("Modbus plugin: Invalid number of channels in server memory area node %(locreqstr)s (Must be in the range [1..65536-start_address])\nModbus plugin: Aborting C code generation for this node\n"%request_dict) + return None + + return "" + + + + +modbus_serial_baudrate_list = ["110", "300", "600", "1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200"] +modbus_serial_stopbits_list = ["1", "2"] +modbus_serial_parity_dict = {"none": 0, "odd": 1, "even": 2} + + + +def GetRTUSlaveNodePrinted(self, child): + """ + Outputs a string to be used on C files + params: child - the correspondent subplugin in Beremiz + """ + node_init_template = '''/*node %(locnodestr)s*/ +{"%(locnodestr)s", %(slaveid)s, {naf_rtu, {.rtu = {"%(device)s", %(baud)s /*baud*/, %(parity)s /*parity*/, 8 /*data bits*/, %(stopbits)s, 0 /* ignore echo */}}}, -1 /* mb_nd */, 0 /* init_state */}''' + + location = ".".join(map(str, child.GetCurrentLocation())) + device = child.GetParamsAttributes()[0]["children"][0]["value"] + baud = child.GetParamsAttributes()[0]["children"][1]["value"] + parity = child.GetParamsAttributes()[0]["children"][2]["value"] + stopbits = child.GetParamsAttributes()[0]["children"][3]["value"] + slaveid = child.GetParamsAttributes()[0]["children"][4]["value"] + + node_dict = {"locnodestr" : location, + "device" : device, + "baud" : baud, + "parity" : modbus_serial_parity_dict[parity], + "stopbits" : stopbits, + "slaveid" : slaveid + } + return node_init_template % node_dict + + + + + +def GetRTUClientNodePrinted(self, child): + """ + Outputs a string to be used on C files + params: child - the correspondent subplugin in Beremiz + """ + node_init_template = '''/*node %(locnodestr)s*/ +{"%(locnodestr)s", {naf_rtu, {.rtu = {"%(device)s", %(baud)s /*baud*/, %(parity)s /*parity*/, 8 /*data bits*/, %(stopbits)s, 0 /* ignore echo */}}}, -1 /* mb_nd */, 0 /* init_state */, %(coms_period)s /* communication period */}''' + + location = ".".join(map(str, child.GetCurrentLocation())) + device = child.GetParamsAttributes()[0]["children"][0]["value"] + baud = child.GetParamsAttributes()[0]["children"][1]["value"] + parity = child.GetParamsAttributes()[0]["children"][2]["value"] + stopbits = child.GetParamsAttributes()[0]["children"][3]["value"] + coms_period = child.GetParamsAttributes()[0]["children"][4]["value"] + + node_dict = {"locnodestr" : location, + "device" : device, + "baud" : baud, + "parity" : modbus_serial_parity_dict[parity], + "stopbits" : stopbits, + "coms_period" : coms_period + } + return node_init_template % node_dict + + + + +def GetTCPClientNodePrinted(self, child): + """ + Outputs a string to be used on C files + params: child - the correspondent subplugin in Beremiz + """ + node_init_template = '''/*node %(locnodestr)s*/ +{"%(locnodestr)s", {naf_tcp, {.tcp = {"%(host)s", "%(port)s", DEF_CLOSE_ON_SILENCE}}}, -1 /* mb_nd */, 0 /* init_state */, %(coms_period)s /* communication period */, 0 /* prev_error */}''' + + location = ".".join(map(str, child.GetCurrentLocation())) + host = child.GetParamsAttributes()[0]["children"][0]["value"] + port = child.GetParamsAttributes()[0]["children"][1]["value"] + coms_period = child.GetParamsAttributes()[0]["children"][2]["value"] + + node_dict = {"locnodestr" : location, + "host" : host, + "port" : port, + "coms_period" : coms_period + } + return node_init_template % node_dict + + + + +def GetClientRequestPrinted(self, child, nodeid): + """ + Outputs a string to be used on C files + params: child - the correspondent subplugin in Beremiz + nodeid - on C code, each request has it's own parent node (sequential, 0..NUMBER_OF_NODES) + It's this parameter. + return: None - if any definition error found + The string that should be added on C code - if everything goes allright + """ + + req_init_template = '''/*request %(locreqstr)s*/ +{"%(locreqstr)s", %(nodeid)s, %(slaveid)s, %(iotype)s, %(func_nr)s, %(address)s , %(count)s, +DEF_REQ_SEND_RETRIES, 0 /* error_code */, 0 /* prev_code */, {%(timeout_s)d, %(timeout_ns)d} /* timeout */, +{%(buffer)s}, {%(buffer)s}}''' + + timeout = int(child.GetParamsAttributes()[0]["children"][4]["value"]) + timeout_s = int(timeout / 1000) + timeout_ms = timeout - (timeout_s * 1000) + timeout_ns = timeout_ms * 1000000 + + request_dict = {} + + request_dict["locreqstr" ] = "_".join(map(str, child.GetCurrentLocation())) + request_dict["nodeid" ] = str(nodeid) + request_dict["slaveid" ] = child.GetParamsAttributes()[0]["children"][1]["value"] + request_dict["address" ] = child.GetParamsAttributes()[0]["children"][3]["value"] + request_dict["count" ] = child.GetParamsAttributes()[0]["children"][2]["value"] + request_dict["timeout" ] = timeout + request_dict["timeout_s" ] = timeout_s + request_dict["timeout_ns"] = timeout_ns + request_dict["buffer" ] = ",".join(['0'] * int(child.GetParamsAttributes()[0]["children"][2]["value"])) + request_dict["func_nr" ] = modbus_function_dict[child.GetParamsAttributes()[0]["children"][0]["value"]][0] + request_dict["iotype" ] = modbus_function_dict[child.GetParamsAttributes()[0]["children"][0]["value"]][1] + request_dict["maxcount" ] = modbus_function_dict[child.GetParamsAttributes()[0]["children"][0]["value"]][2] + + if int(request_dict["slaveid"]) not in xrange(256): + self.GetCTRoot().logger.write_error("Modbus plugin: Invalid slaveID in TCP client request node %(locreqstr)s (Must be in the range [0..255])\nModbus plugin: Aborting C code generation for this node\n"%request_dict) + return None + if int(request_dict["address"]) not in xrange(65536): + self.GetCTRoot().logger.write_error("Modbus plugin: Invalid Start Address in TCP client request node %(locreqstr)s (Must be in the range [0..65535])\nModbus plugin: Aborting C code generation for this node\n"%request_dict) + return None + if int(request_dict["count"]) not in xrange(1, 1+int(request_dict["maxcount"])): + self.GetCTRoot().logger.write_error("Modbus plugin: Invalid number of channels in TCP client request node %(locreqstr)s (Must be in the range [1..%(maxcount)s])\nModbus plugin: Aborting C code generation for this node\n"%request_dict) + return None + if (int(request_dict["address"]) + int(request_dict["count"])) not in xrange(1,65537): + self.GetCTRoot().logger.write_error("Modbus plugin: Invalid number of channels in TCP client request node %(locreqstr)s (start_address + nr_channels must be less than 65536)\nModbus plugin: Aborting C code generation for this node\n"%request_dict) + return None + + return req_init_template % request_dict diff -r de4ee16f7c6c -r bb883e063175 modbus/modbus.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modbus/modbus.py Sun Mar 05 00:37:54 2017 +0000 @@ -0,0 +1,785 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of Beremiz, a Integrated Development Environment for +# programming IEC 61131-3 automates supporting plcopen standard and CanFestival. +# +# Copyright (c) 2016 Mario de Sousa (msousa@fe.up.pt) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# This code is made available on the understanding that it will not be +# used in safety-critical situations without a full and competent review. + + + + +import os, sys +base_folder = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0] +base_folder = os.path.join(base_folder, "..") +ModbusPath = os.path.join(base_folder, "Modbus") + +from mb_utils import * + +import wx +from ConfigTreeNode import ConfigTreeNode +from PLCControler import LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY + + + +################################################### +################################################### +# # +# C L I E N T R E Q U E S T # +# # +################################################### +################################################### + + +class _RequestPlug: + XSD = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """ + + def GetParamsAttributes(self, path = None): + infos = ConfigTreeNode.GetParamsAttributes(self, path = path) + for element in infos: + if element["name"] == "ModbusRequest": + for child in element["children"]: + if child["name"] == "Function": + list = modbus_function_dict.keys() + list.sort() + child["type"] = list + return infos + + def GetVariableLocationTree(self): + current_location = self.GetCurrentLocation() + name = self.BaseParams.getName() + address = self.GetParamsAttributes()[0]["children"][3]["value"] + count = self.GetParamsAttributes()[0]["children"][2]["value"] + function= self.GetParamsAttributes()[0]["children"][0]["value"] + # 'BOOL' or 'WORD' + datatype= modbus_function_dict[function][3] + # 1 or 16 + datasize= modbus_function_dict[function][4] + # 'Q' for coils and holding registers, 'I' for input discretes and input registers + datazone= modbus_function_dict[function][5] + # 'X' for bits, 'W' for words + datatacc= modbus_function_dict[function][6] + # 'Coil', 'Holding Register', 'Input Discrete' or 'Input Register' + dataname= modbus_function_dict[function][7] + entries = [] + for offset in range(address, address+count): + entries.append({ + "name": dataname + " " + str(offset), + "type": LOCATION_VAR_MEMORY, + "size": datasize, + "IEC_type": datatype, + "var_name": "var_name", + "location": datatacc + ".".join([str(i) for i in current_location]) + "." + str(offset), + "description": "description", + "children": []}) + return {"name": name, + "type": LOCATION_CONFNODE, + "location": ".".join([str(i) for i in current_location]) + ".x", + "children": entries} + + + + + + def CTNGenerate_C(self, buildpath, locations): + """ + Generate C code + @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) + @param locations: List of complete variables locations \ + [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) + "NAME" : name of the variable (generally "__IW0_1_2" style) + "DIR" : direction "Q","I" or "M" + "SIZE" : size "X", "B", "W", "D", "L" + "LOC" : tuple of interger for IEC location (0,1,2,...) + }, ...] + @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND + """ + return [], "", False + + +################################################### +################################################### +# # +# S E R V E R M E M O R Y A R E A # +# # +################################################### +################################################### + +#dictionary implementing: +#key - string with the description we want in the request plugin GUI +#list - (modbus function number, request type, max count value) +modbus_memtype_dict = {"01 - Coils" : ( '1', 'rw_bits', 65536, "BOOL", 1 , "Q", "X", "Coil"), + "02 - Input Discretes" : ( '2', 'ro_bits', 65536, "BOOL", 1 , "I", "X", "Input Discrete"), + "03 - Holding Registers" :( '3', 'rw_words', 65536, "WORD", 16 , "Q", "W", "Holding Register"), + "04 - Input Registers" : ( '4', 'ro_words', 65536, "WORD", 16 , "I", "W", "Input Register"), + } + +class _MemoryAreaPlug: + XSD = """ + + + + + + + + + + + + + + + + + + + + + + + + """ + + def GetParamsAttributes(self, path = None): + infos = ConfigTreeNode.GetParamsAttributes(self, path = path) + for element in infos: + if element["name"] == "MemoryArea": + for child in element["children"]: + if child["name"] == "MemoryAreaType": + list = modbus_memtype_dict.keys() + list.sort() + child["type"] = list + return infos + + def GetVariableLocationTree(self): + current_location = self.GetCurrentLocation() + name = self.BaseParams.getName() + address = self.GetParamsAttributes()[0]["children"][2]["value"] + count = self.GetParamsAttributes()[0]["children"][1]["value"] + function= self.GetParamsAttributes()[0]["children"][0]["value"] + # 'BOOL' or 'WORD' + datatype= modbus_memtype_dict[function][3] + # 1 or 16 + datasize= modbus_memtype_dict[function][4] + # 'Q' for coils and holding registers, 'I' for input discretes and input registers + datazone= modbus_memtype_dict[function][5] + # 'X' for bits, 'W' for words + datatacc= modbus_memtype_dict[function][6] + # 'Coil', 'Holding Register', 'Input Discrete' or 'Input Register' + dataname= modbus_memtype_dict[function][7] + entries = [] + for offset in range(address, address+count): + entries.append({ + "name": dataname + " " + str(offset), + "type": LOCATION_VAR_MEMORY, + "size": datasize, + "IEC_type": datatype, + "var_name": "var_name", + "location": datatacc + ".".join([str(i) for i in current_location]) + "." + str(offset), + "description": "description", + "children": []}) + return {"name": name, + "type": LOCATION_CONFNODE, + "location": ".".join([str(i) for i in current_location]) + ".x", + "children": entries} + + def CTNGenerate_C(self, buildpath, locations): + """ + Generate C code + @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) + @param locations: List of complete variables locations \ + [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) + "NAME" : name of the variable (generally "__IW0_1_2" style) + "DIR" : direction "Q","I" or "M" + "SIZE" : size "X", "B", "W", "D", "L" + "LOC" : tuple of interger for IEC location (0,1,2,...) + }, ...] + @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND + """ + return [], "", False + + +################################################### +################################################### +# # +# T C P C L I E N T # +# # +################################################### +################################################### + +class _ModbusTCPclientPlug: + XSD = """ + + + + + + + + + + + + + + + + + """ + # NOTE: Max value of 2147483647 (i32_max) for Invocation_Rate_in_ms corresponds to aprox 25 days. + CTNChildrenTypes = [("ModbusRequest",_RequestPlug, "Request")] + # TODO: Replace with CTNType !!! + PlugType = "ModbusTCPclient" + + # Return the number of (modbus library) nodes this specific TCP client will need + # return type: (tcp nodes, rtu nodes, ascii nodes) + def GetNodeCount(self): + return (1, 0, 0) + + def CTNGenerate_C(self, buildpath, locations): + """ + Generate C code + @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) + @param locations: List of complete variables locations \ + [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) + "NAME" : name of the variable (generally "__IW0_1_2" style) + "DIR" : direction "Q","I" or "M" + "SIZE" : size "X", "B", "W", "D", "L" + "LOC" : tuple of interger for IEC location (0,1,2,...) + }, ...] + @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND + """ + return [], "", False + + + + +################################################### +################################################### +# # +# T C P S E R V E R # +# # +################################################### +################################################### + +class _ModbusTCPserverPlug: + # NOTE: the Port number is a 'string' and not an 'integer'! + # This is because the underlying modbus library accepts strings + # (e.g.: well known port names!) + XSD = """ + + + + + + + + + + + + + + + + + """ + CTNChildrenTypes = [("MemoryArea",_MemoryAreaPlug, "Memory Area")] + # TODO: Replace with CTNType !!! + PlugType = "ModbusTCPserver" + + # Return the number of (modbus library) nodes this specific TCP server will need + # return type: (tcp nodes, rtu nodes, ascii nodes) + def GetNodeCount(self): + return (1, 0, 0) + + # Return a list with a single tuple conatining the (location, port number) + # location: location of this node in the configuration tree + # port number: IP port used by this Modbus/IP server + def GetIPServerPortNumbers(self): + port = self.GetParamsAttributes()[0]["children"][1]["value"] + return [(self.GetCurrentLocation() , port)] + + def CTNGenerate_C(self, buildpath, locations): + """ + Generate C code + @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) + @param locations: List of complete variables locations \ + [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) + "NAME" : name of the variable (generally "__IW0_1_2" style) + "DIR" : direction "Q","I" or "M" + "SIZE" : size "X", "B", "W", "D", "L" + "LOC" : tuple of interger for IEC location (0,1,2,...) + }, ...] + @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND + """ + return [], "", False + + + + +################################################### +################################################### +# # +# R T U C L I E N T # +# # +################################################### +################################################### + +class _ModbusRTUclientPlug: + XSD = """ + + + + + + + + + + + + + + + + + + + """ + # NOTE: Max value of 2147483647 (i32_max) for Invocation_Rate_in_ms corresponds to aprox 25 days. + CTNChildrenTypes = [("ModbusRequest",_RequestPlug, "Request")] + # TODO: Replace with CTNType !!! + PlugType = "ModbusRTUclient" + + def GetParamsAttributes(self, path = None): + infos = ConfigTreeNode.GetParamsAttributes(self, path = path) + for element in infos: + if element["name"] == "ModbusRTUclient": + for child in element["children"]: + if child["name"] == "Baud_Rate": + child["type"] = modbus_serial_baudrate_list + if child["name"] == "Stop_Bits": + child["type"] = modbus_serial_stopbits_list + if child["name"] == "Parity": + child["type"] = modbus_serial_parity_dict.keys() + return infos + + # Return the number of (modbus library) nodes this specific RTU client will need + # return type: (tcp nodes, rtu nodes, ascii nodes) + def GetNodeCount(self): + return (0, 1, 0) + + def CTNGenerate_C(self, buildpath, locations): + """ + Generate C code + @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) + @param locations: List of complete variables locations \ + [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) + "NAME" : name of the variable (generally "__IW0_1_2" style) + "DIR" : direction "Q","I" or "M" + "SIZE" : size "X", "B", "W", "D", "L" + "LOC" : tuple of interger for IEC location (0,1,2,...) + }, ...] + @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND + """ + return [], "", False + + + +################################################### +################################################### +# # +# R T U S L A V E # +# # +################################################### +################################################### + + +class _ModbusRTUslavePlug: + XSD = """ + + + + + + + + + + + + + + + + + + + """ + CTNChildrenTypes = [("MemoryArea",_MemoryAreaPlug, "Memory Area")] + # TODO: Replace with CTNType !!! + PlugType = "ModbusRTUslave" + + def GetParamsAttributes(self, path = None): + infos = ConfigTreeNode.GetParamsAttributes(self, path = path) + for element in infos: + if element["name"] == "ModbusRTUslave": + for child in element["children"]: + if child["name"] == "Baud_Rate": + child["type"] = modbus_serial_baudrate_list + if child["name"] == "Stop_Bits": + child["type"] = modbus_serial_stopbits_list + if child["name"] == "Parity": + child["type"] = modbus_serial_parity_dict.keys() + return infos + + # Return the number of (modbus library) nodes this specific RTU slave will need + # return type: (tcp nodes, rtu nodes, ascii nodes) + def GetNodeCount(self): + return (0, 1, 0) + + def CTNGenerate_C(self, buildpath, locations): + """ + Generate C code + @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) + @param locations: List of complete variables locations \ + [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) + "NAME" : name of the variable (generally "__IW0_1_2" style) + "DIR" : direction "Q","I" or "M" + "SIZE" : size "X", "B", "W", "D", "L" + "LOC" : tuple of interger for IEC location (0,1,2,...) + }, ...] + @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND + """ + return [], "", False + + + +################################################### +################################################### +# # +# R O O T C L A S S # +# # +################################################### +################################################### +class RootClass: + XSD = """ + + + + + + + + + + + + + + + """ + CTNChildrenTypes = [("ModbusTCPclient",_ModbusTCPclientPlug, "Modbus TCP Client") + ,("ModbusTCPserver",_ModbusTCPserverPlug, "Modbus TCP Server") + ,("ModbusRTUclient",_ModbusRTUclientPlug, "Modbus RTU Client") + ,("ModbusRTUslave", _ModbusRTUslavePlug, "Modbus RTU Slave") + ] + + # Return the number of (modbus library) nodes this specific instance of the modbus plugin will need + # return type: (tcp nodes, rtu nodes, ascii nodes) + def GetNodeCount(self): + max_remote_tcpclient = self.GetParamsAttributes()[0]["children"][0]["value"] + total_node_count = (max_remote_tcpclient, 0, 0) + for child in self.IECSortedChildren(): + # ask each child how many nodes it needs, and add them all up. + total_node_count = tuple(x1 + x2 for x1, x2 in zip(total_node_count, child.GetNodeCount())) + return total_node_count + + # Return a list with tuples of the (location, port numbers) used by all the Modbus/IP servers + def GetIPServerPortNumbers(self): + IPServer_port_numbers = [] + for child in self.IECSortedChildren(): + if child.CTNType == "ModbusTCPserver": + IPServer_port_numbers.extend(child.GetIPServerPortNumbers()) + return IPServer_port_numbers + + def CTNGenerate_C(self, buildpath, locations): + #print "#############" + #print self.__class__ + #print type(self) + #print "self.CTNType >>>" + #print self.CTNType + #print "type(self.CTNType) >>>" + #print type(self.CTNType) + #print "#############" + + loc_dict = {"locstr" : "_".join(map(str,self.GetCurrentLocation())), + } + + # Determine the number of (modbus library) nodes ALL instances of the modbus plugin will need + # total_node_count: (tcp nodes, rtu nodes, ascii nodes) + # Also get a list with tuples of (location, IP port numbers) used by all the Modbus/IP server nodes + # This list is later used to search for duplicates in port numbers! + # IPServer_port_numbers = [(location ,IPserver_port_number), ...] + # location: tuple similar to (0, 3, 1) representing the location in the configuration tree "0.3.1.x" + # IPserver_port_number: a number (i.e. port number used by the Modbus/IP server) + total_node_count = (0, 0, 0) + IPServer_port_numbers = [] + for CTNInstance in self.GetCTRoot().IterChildren(): + if CTNInstance.CTNType == "modbus": + # ask each modbus plugin instance how many nodes it needs, and add them all up. + total_node_count = tuple(x1 + x2 for x1, x2 in zip(total_node_count, CTNInstance.GetNodeCount())) + IPServer_port_numbers.extend(CTNInstance.GetIPServerPortNumbers()) + + # Search for use of duplicate port numbers by Modbus/IP servers + #print IPServer_port_numbers + # ..but first define a lambda function to convert a tuple with the config tree location to a nice looking string + # for e.g., convert the tuple (0, 3, 4) to "0.3.4" + lt_to_str = lambda loctuple: '.'.join(map(str, loctuple)) + for i in range(0, len(IPServer_port_numbers)-1): + for j in range (i+1, len(IPServer_port_numbers)): + if IPServer_port_numbers[i][1] == IPServer_port_numbers[j][1]: + self.GetCTRoot().logger.write_warning(_("Error: Modbus/IP Servers %s.x and %s.x use the same port number %s.\n")%(lt_to_str(IPServer_port_numbers[i][0]), lt_to_str(IPServer_port_numbers[j][0]), IPServer_port_numbers[j][1])) + raise Exception, False + # TODO: return an error code instead of raising an exception + + # Determine the current location in Beremiz's project configuration tree + current_location = self.GetCurrentLocation() + + # define a unique name for the generated C and h files + prefix = "_".join(map(str, current_location)) + Gen_MB_c_path = os.path.join(buildpath, "MB_%s.c"%prefix) + Gen_MB_h_path = os.path.join(buildpath, "MB_%s.h"%prefix) + c_filename = os.path.join(os.path.split(__file__)[0],"mb_runtime.c") + h_filename = os.path.join(os.path.split(__file__)[0],"mb_runtime.h") + + tcpclient_reqs_count = 0 + rtuclient_reqs_count = 0 + ascclient_reqs_count = 0 + tcpclient_node_count = 0 + rtuclient_node_count = 0 + ascclient_node_count = 0 + tcpserver_node_count = 0 + rtuserver_node_count = 0 + ascserver_node_count = 0 + nodeid = 0 + client_nodeid = 0 + client_requestid = 0 + server_id = 0 + + server_node_list = [] + client_node_list = [] + client_request_list = [] + server_memarea_list = [] + loc_vars = [] + loc_vars_list = [] # list of variables already declared in C code! + for child in self.IECSortedChildren(): + #print "<<<<<<<<<<<<<" + #print "child (self.IECSortedChildren())----->" + #print child.__class__ + #print ">>>>>>>>>>>>>" + ###################################### + if child.PlugType == "ModbusTCPserver": + tcpserver_node_count += 1 + new_node = GetTCPServerNodePrinted(self, child) + if new_node is None: + return [],"",False + server_node_list.append(new_node) + ############## + for subchild in child.IECSortedChildren(): + new_memarea = GetTCPServerMemAreaPrinted(self, subchild, nodeid) + if new_memarea is None: + return [],"",False + server_memarea_list.append(new_memarea) + function= subchild.GetParamsAttributes()[0]["children"][0]["value"] + # 'ro_bits', 'rw_bits', 'ro_words' or 'rw_words' + memarea= modbus_memtype_dict[function][1] + for iecvar in subchild.GetLocations(): + #print repr(iecvar) + absloute_address = iecvar["LOC"][3] + start_address = int(subchild.GetParamsAttributes()[0]["children"][2]["value"]) + relative_addr = absloute_address - start_address + #test if relative address in request specified range + if relative_addr in xrange(int(subchild.GetParamsAttributes()[0]["children"][1]["value"])): + if str(iecvar["NAME"]) not in loc_vars_list: + loc_vars.append("u16 *" + str(iecvar["NAME"]) + " = &server_nodes[%d].mem_area.%s[%d];" % (server_id, memarea, absloute_address)) + loc_vars_list.append(str(iecvar["NAME"])) + server_id += 1 + ###################################### + if child.PlugType == "ModbusRTUslave": + rtuserver_node_count += 1 + new_node = GetRTUSlaveNodePrinted(self, child) + if new_node is None: + return [],"",False + server_node_list.append(new_node) + ############## + for subchild in child.IECSortedChildren(): + new_memarea = GetTCPServerMemAreaPrinted(self, subchild, nodeid) + if new_memarea is None: + return [],"",False + server_memarea_list.append(new_memarea) + function= subchild.GetParamsAttributes()[0]["children"][0]["value"] + # 'ro_bits', 'rw_bits', 'ro_words' or 'rw_words' + memarea= modbus_memtype_dict[function][1] + for iecvar in subchild.GetLocations(): + #print repr(iecvar) + absloute_address = iecvar["LOC"][3] + start_address = int(subchild.GetParamsAttributes()[0]["children"][2]["value"]) + relative_addr = absloute_address - start_address + #test if relative address in request specified range + if relative_addr in xrange(int(subchild.GetParamsAttributes()[0]["children"][1]["value"])): + if str(iecvar["NAME"]) not in loc_vars_list: + loc_vars.append("u16 *" + str(iecvar["NAME"]) + " = &server_nodes[%d].mem_area.%s[%d];" % (server_id, memarea, absloute_address)) + loc_vars_list.append(str(iecvar["NAME"])) + server_id += 1 + ###################################### + if child.PlugType == "ModbusTCPclient": + tcpclient_reqs_count += len(child.IECSortedChildren()) + new_node = GetTCPClientNodePrinted(self, child) + if new_node is None: + return [],"",False + client_node_list.append(new_node) + for subchild in child.IECSortedChildren(): + new_req = GetClientRequestPrinted(self, subchild, client_nodeid) + if new_req is None: + return [],"",False + client_request_list.append(new_req) + for iecvar in subchild.GetLocations(): + #absloute address - start address + relative_addr = iecvar["LOC"][3] - int(subchild.GetParamsAttributes()[0]["children"][3]["value"]) + #test if relative address in request specified range + if relative_addr in xrange(int(subchild.GetParamsAttributes()[0]["children"][2]["value"])): + if str(iecvar["NAME"]) not in loc_vars_list: + loc_vars.append("u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].plcv_buffer[%d];" % (client_requestid, relative_addr)) + loc_vars_list.append(str(iecvar["NAME"])) + client_requestid += 1 + tcpclient_node_count += 1 + client_nodeid += 1 + ###################################### + if child.PlugType == "ModbusRTUclient": + rtuclient_reqs_count += len(child.IECSortedChildren()) + new_node = GetRTUClientNodePrinted(self, child) + if new_node is None: + return [],"",False + client_node_list.append(new_node) + for subchild in child.IECSortedChildren(): + new_req = GetClientRequestPrinted(self, subchild, client_nodeid) + if new_req is None: + return [],"",False + client_request_list.append(new_req) + for iecvar in subchild.GetLocations(): + #absloute address - start address + relative_addr = iecvar["LOC"][3] - int(subchild.GetParamsAttributes()[0]["children"][3]["value"]) + #test if relative address in request specified range + if relative_addr in xrange(int(subchild.GetParamsAttributes()[0]["children"][2]["value"])): + if str(iecvar["NAME"]) not in loc_vars_list: + loc_vars.append("u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].plcv_buffer[%d];" % (client_requestid, relative_addr)) + loc_vars_list.append(str(iecvar["NAME"])) + client_requestid += 1 + rtuclient_node_count += 1 + client_nodeid += 1 + nodeid += 1 + + loc_dict["loc_vars"] = "\n".join(loc_vars) + loc_dict["server_nodes_params"] = ",\n\n".join(server_node_list) + loc_dict["client_nodes_params"] = ",\n\n".join(client_node_list) + loc_dict["client_req_params"] = ",\n\n".join(client_request_list) + loc_dict["tcpclient_reqs_count"] = str(tcpclient_reqs_count) + loc_dict["tcpclient_node_count"] = str(tcpclient_node_count) + loc_dict["tcpserver_node_count"] = str(tcpserver_node_count) + loc_dict["rtuclient_reqs_count"] = str(rtuclient_reqs_count) + loc_dict["rtuclient_node_count"] = str(rtuclient_node_count) + loc_dict["rtuserver_node_count"] = str(rtuserver_node_count) + loc_dict["ascclient_reqs_count"] = str(ascclient_reqs_count) + loc_dict["ascclient_node_count"] = str(ascclient_node_count) + loc_dict["ascserver_node_count"] = str(ascserver_node_count) + loc_dict["total_tcpnode_count"] = str(total_node_count[0]) + loc_dict["total_rtunode_count"] = str(total_node_count[1]) + loc_dict["total_ascnode_count"] = str(total_node_count[2]) + loc_dict["max_remote_tcpclient"] = int(self.GetParamsAttributes()[0]["children"][0]["value"]) + + #get template file content into a string, format it with dict + #and write it to proper .h file + mb_main = open(h_filename).read() % loc_dict + f = open(Gen_MB_h_path,'w') + f.write(mb_main) + f.close() + #same thing as above, but now to .c file + mb_main = open(c_filename).read() % loc_dict + f = open(Gen_MB_c_path,'w') + f.write(mb_main) + f.close() + + LDFLAGS = [] + LDFLAGS.append(" \"-L" + ModbusPath + "\"") + LDFLAGS.append(" -lmb ") + LDFLAGS.append(" \"-Wl,-rpath," + ModbusPath + "\"") + #LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_slave_and_master.o") + "\"") + #LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_slave.o") + "\"") + #LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_master.o") + "\"") + #LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_tcp.o") + "\"") + #LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_rtu.o") + "\"") + #LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_ascii.o") + "\"") + #LDFLAGS.append("\"" + os.path.join(ModbusPath, "sin_util.o") + "\"") + if os.name == 'nt': # other possible values: 'posix' 'os2' 'ce' 'java' 'riscos' + LDFLAGS.append(" -lws2_32 ") # on windows we need to load winsock library! + + return [(Gen_MB_c_path, ' -I"'+ModbusPath+'"')], LDFLAGS, True