--- 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'),
--- /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
--- /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 <http://www.gnu.org/licenses/>.
+#
+# 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 *
--- /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 <http://www.gnu.org/licenses/>.
+ *
+ * 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 <stdio.h>
+#include <string.h> /* 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 <pthread.h>
+
+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;
+}
+
--- /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 <http://www.gnu.org/licenses/>.
+ *
+ * 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
+
--- /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 <http://www.gnu.org/licenses/>.
+#
+# 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
--- /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 <http://www.gnu.org/licenses/>.
+#
+# 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 = """<?xml version="1.0" encoding="ISO-8859-1" ?>
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <xsd:element name="ModbusRequest">
+ <xsd:complexType>
+ <xsd:attribute name="Function" type="xsd:string" use="optional" default="01 - Read Coils"/>
+ <xsd:attribute name="SlaveID" use="optional" default="1">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:integer">
+ <xsd:minInclusive value="0"/>
+ <xsd:maxInclusive value="255"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="Nr_of_Channels" use="optional" default="1">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:integer">
+ <xsd:minInclusive value="1"/>
+ <xsd:maxInclusive value="2000"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="Start_Address" use="optional" default="0">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:integer">
+ <xsd:minInclusive value="0"/>
+ <xsd:maxInclusive value="65535"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="Timeout_in_ms" use="optional" default="10">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:integer">
+ <xsd:minInclusive value="1"/>
+ <xsd:maxInclusive value="100000"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ """
+
+ 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 = """<?xml version="1.0" encoding="ISO-8859-1" ?>
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <xsd:element name="MemoryArea">
+ <xsd:complexType>
+ <xsd:attribute name="MemoryAreaType" type="xsd:string" use="optional" default="01 - Coils"/>
+ <xsd:attribute name="Nr_of_Channels" use="optional" default="1">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:integer">
+ <xsd:minInclusive value="1"/>
+ <xsd:maxInclusive value="65536"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="Start_Address" use="optional" default="0">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:integer">
+ <xsd:minInclusive value="0"/>
+ <xsd:maxInclusive value="65535"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ """
+
+ 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 = """<?xml version="1.0" encoding="ISO-8859-1" ?>
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <xsd:element name="ModbusTCPclient">
+ <xsd:complexType>
+ <xsd:attribute name="Remote_IP_Address" type="xsd:string" use="optional" default="localhost"/>
+ <xsd:attribute name="Remote_Port_Number" type="xsd:string" use="optional" default="502"/>
+ <xsd:attribute name="Invocation_Rate_in_ms" use="optional" default="100">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:unsignedLong">
+ <xsd:minInclusive value="1"/>
+ <xsd:maxInclusive value="2147483647"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ """
+ # 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 = """<?xml version="1.0" encoding="ISO-8859-1" ?>
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <xsd:element name="ModbusServerNode">
+ <xsd:complexType>
+ <xsd:attribute name="Local_IP_Address" type="xsd:string" use="optional" default="#ANY#"/>
+ <xsd:attribute name="Local_Port_Number" type="xsd:string" use="optional" default="502"/>
+ <xsd:attribute name="SlaveID" use="optional" default="0">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:integer">
+ <xsd:minInclusive value="0"/>
+ <xsd:maxInclusive value="255"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ """
+ 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 = """<?xml version="1.0" encoding="ISO-8859-1" ?>
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <xsd:element name="ModbusRTUclient">
+ <xsd:complexType>
+ <xsd:attribute name="Serial_Port" type="xsd:string" use="optional" default="/dev/ttyS0"/>
+ <xsd:attribute name="Baud_Rate" type="xsd:string" use="optional" default="9600"/>
+ <xsd:attribute name="Parity" type="xsd:string" use="optional" default="even"/>
+ <xsd:attribute name="Stop_Bits" type="xsd:string" use="optional" default="1"/>
+ <xsd:attribute name="Invocation_Rate_in_ms" use="optional" default="100">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:integer">
+ <xsd:minInclusive value="1"/>
+ <xsd:maxInclusive value="2147483647"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ """
+ # 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 = """<?xml version="1.0" encoding="ISO-8859-1" ?>
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <xsd:element name="ModbusRTUslave">
+ <xsd:complexType>
+ <xsd:attribute name="Serial_Port" type="xsd:string" use="optional" default="/dev/ttyS0"/>
+ <xsd:attribute name="Baud_Rate" type="xsd:string" use="optional" default="9600"/>
+ <xsd:attribute name="Parity" type="xsd:string" use="optional" default="even"/>
+ <xsd:attribute name="Stop_Bits" type="xsd:string" use="optional" default="1"/>
+ <xsd:attribute name="SlaveID" use="optional" default="1">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:integer">
+ <xsd:minInclusive value="1"/>
+ <xsd:maxInclusive value="255"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ """
+ 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 = """<?xml version="1.0" encoding="ISO-8859-1" ?>
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <xsd:element name="ModbusRoot">
+ <xsd:complexType>
+ <xsd:attribute name="MaxRemoteTCPclients" use="optional" default="10">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:integer">
+ <xsd:minInclusive value="0"/>
+ <xsd:maxInclusive value="65535"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ """
+ 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