msousa@1909: #!/usr/bin/env python msousa@1909: # -*- coding: utf-8 -*- msousa@1909: msousa@1909: # This file is part of Beremiz, a Integrated Development Environment for msousa@1909: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival. msousa@1909: # msousa@1909: # Copyright (c) 2016 Mario de Sousa (msousa@fe.up.pt) msousa@1909: # msousa@1909: # This program is free software: you can redistribute it and/or modify msousa@1909: # it under the terms of the GNU General Public License as published by Edouard@2019: # the Free Software Foundation, either version 2 of the License, or msousa@1909: # (at your option) any later version. msousa@1909: # msousa@1909: # This program is distributed in the hope that it will be useful, msousa@1909: # but WITHOUT ANY WARRANTY; without even the implied warranty of msousa@1909: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the msousa@1909: # GNU General Public License for more details. msousa@1909: # msousa@1909: # You should have received a copy of the GNU General Public License msousa@1909: # along with this program. If not, see . Edouard@1918: # msousa@1909: # This code is made available on the understanding that it will not be msousa@1909: # used in safety-critical situations without a full and competent review. msousa@1909: andrej@2432: from __future__ import absolute_import andrej@2437: from __future__ import division andrej@2432: from six.moves import xrange msousa@1909: Edouard@1918: # dictionary implementing: Edouard@1918: # key - string with the description we want in the request plugin GUI Edouard@1918: # tuple - (modbus function number, request type, max count value, Edouard@1918: # data_type, bit_size) Edouard@1918: modbus_function_dict = { Edouard@1918: "01 - Read Coils": ('1', 'req_input', 2000, "BOOL", 1, "Q", "X", "Coil"), Edouard@1918: "02 - Read Input Discretes": ('2', 'req_input', 2000, "BOOL", 1, "I", "X", "Input Discrete"), Edouard@1918: "03 - Read Holding Registers": ('3', 'req_input', 125, "WORD", 16, "Q", "W", "Holding Register"), Edouard@1918: "04 - Read Input Registers": ('4', 'req_input', 125, "WORD", 16, "I", "W", "Input Register"), Edouard@1918: "05 - Write Single coil": ('5', 'req_output', 1, "BOOL", 1, "Q", "X", "Coil"), Edouard@1918: "06 - Write Single Register": ('6', 'req_output', 1, "WORD", 16, "Q", "W", "Holding Register"), Edouard@1918: "15 - Write Multiple Coils": ('15', 'req_output', 1968, "BOOL", 1, "Q", "X", "Coil"), Edouard@1918: "16 - Write Multiple Registers": ('16', 'req_output', 123, "WORD", 16, "Q", "W", "Holding Register")} Edouard@1918: Edouard@1918: Edouard@1918: # Configuration tree value acces helper Edouard@1918: def GetCTVal(child, index): Edouard@1918: return child.GetParamsAttributes()[0]["children"][index]["value"] Edouard@1918: Edouard@1918: Edouard@1918: # Configuration tree value acces helper, for multiple values Edouard@1918: def GetCTVals(child, indexes): Edouard@1918: return map(lambda index: GetCTVal(child, index), indexes) msousa@1909: msousa@1909: msousa@1909: def GetTCPServerNodePrinted(self, child): msousa@1909: """ msousa@1909: Outputs a string to be used on C files msousa@1909: params: child - the correspondent subplugin in Beremiz msousa@1909: """ msousa@1909: node_init_template = '''/*node %(locnodestr)s*/ msousa@2655: {"%(locnodestr)s", "%(config_name)s", "%(host)s", "%(port)s", %(slaveid)s, {naf_tcp, {.tcp = {NULL, NULL, DEF_CLOSE_ON_SILENCE}}}, -1 /* mb_nd */, 0 /* init_state */}''' msousa@2654: msousa@2654: location = ".".join(map(str, child.GetCurrentLocation())) msousa@2654: config_name, host, port, slaveid = GetCTVals(child, range(4)) Edouard@1918: if host == "#ANY#": msousa@2655: host = '' Edouard@1918: # slaveid = GetCTVal(child, 2) Edouard@1918: # if int(slaveid) not in xrange(256): Edouard@1918: # self.GetCTRoot().logger.write_error("Error: Wrong slave ID in %s server node\nModbus Plugin C code returns empty\n"%location) Edouard@1918: # return None Edouard@1918: Edouard@1918: node_dict = {"locnodestr": location, msousa@2654: "config_name": config_name, Edouard@1918: "host": host, Edouard@1918: "port": port, Edouard@1919: "slaveid": slaveid} Edouard@1918: return node_init_template % node_dict msousa@1909: msousa@1909: msousa@1909: def GetTCPServerMemAreaPrinted(self, child, nodeid): msousa@1909: """ msousa@1909: Outputs a string to be used on C files msousa@1909: params: child - the correspondent subplugin in Beremiz msousa@1909: nodeid - on C code, each request has it's own parent node (sequential, 0..NUMBER_OF_NODES) msousa@1909: It's this parameter. msousa@1909: return: None - if any definition error found msousa@1909: The string that should be added on C code - if everything goes allright msousa@1909: """ msousa@1909: request_dict = {} msousa@1909: msousa@1909: request_dict["locreqstr"] = "_".join(map(str, child.GetCurrentLocation())) msousa@1909: request_dict["nodeid"] = str(nodeid) Edouard@1918: request_dict["address"] = GetCTVal(child, 2) msousa@1909: if int(request_dict["address"]) not in xrange(65536): Edouard@1918: self.GetCTRoot().logger.write_error( Edouard@1918: "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) Edouard@1918: return None Edouard@1918: request_dict["count"] = GetCTVal(child, 1) msousa@2664: if int(request_dict["count"]) not in xrange(1, 65537): Edouard@1918: self.GetCTRoot().logger.write_error( Edouard@1918: "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) Edouard@1918: return None Edouard@1918: if (int(request_dict["address"]) + int(request_dict["count"])) not in xrange(1, 65537): Edouard@1918: self.GetCTRoot().logger.write_error( Edouard@1918: "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) Edouard@1918: return None Edouard@1918: msousa@1909: return "" msousa@1909: msousa@1909: Edouard@1918: modbus_serial_baudrate_list = [ Edouard@1918: "110", "300", "600", "1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200"] msousa@1909: modbus_serial_stopbits_list = ["1", "2"] Edouard@1918: modbus_serial_parity_dict = {"none": 0, "odd": 1, "even": 2} msousa@1909: msousa@1909: msousa@1909: def GetRTUSlaveNodePrinted(self, child): msousa@1909: """ msousa@1909: Outputs a string to be used on C files msousa@1909: params: child - the correspondent subplugin in Beremiz msousa@1909: """ msousa@1909: node_init_template = '''/*node %(locnodestr)s*/ msousa@2655: {"%(locnodestr)s", "%(config_name)s", "%(device)s", "",%(slaveid)s, {naf_rtu, {.rtu = {NULL, %(baud)s /*baud*/, %(parity)s /*parity*/, 8 /*data bits*/, %(stopbits)s, 0 /* ignore echo */}}}, -1 /* mb_nd */, 0 /* init_state */}''' msousa@2654: msousa@2654: location = ".".join(map(str, child.GetCurrentLocation())) msousa@2654: config_name, device, baud, parity, stopbits, slaveid = GetCTVals(child, range(6)) msousa@2654: msousa@2654: node_dict = {"locnodestr": location, msousa@2654: "config_name": config_name, Edouard@1918: "device": device, Edouard@1918: "baud": baud, Edouard@1918: "parity": modbus_serial_parity_dict[parity], Edouard@1918: "stopbits": stopbits, Edouard@1919: "slaveid": slaveid} Edouard@1918: return node_init_template % node_dict Edouard@1918: msousa@1909: msousa@1909: def GetRTUClientNodePrinted(self, child): msousa@1909: """ msousa@1909: Outputs a string to be used on C files msousa@1909: params: child - the correspondent subplugin in Beremiz msousa@1909: """ msousa@1909: node_init_template = '''/*node %(locnodestr)s*/ msousa@2654: {"%(locnodestr)s", "%(config_name)s", "%(device)s", "", {naf_rtu, {.rtu = {NULL, %(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 */}''' msousa@2654: msousa@2654: location = ".".join(map(str, child.GetCurrentLocation())) msousa@2654: config_name, device, baud, parity, stopbits, coms_period = GetCTVals(child, range(6)) msousa@2654: msousa@2654: node_dict = {"locnodestr": location, msousa@2654: "config_name": config_name, Edouard@1918: "device": device, Edouard@1918: "baud": baud, Edouard@1918: "parity": modbus_serial_parity_dict[parity], Edouard@1918: "stopbits": stopbits, Edouard@1919: "coms_period": coms_period} Edouard@1918: return node_init_template % node_dict msousa@1909: msousa@1909: msousa@1909: def GetTCPClientNodePrinted(self, child): msousa@1909: """ msousa@1909: Outputs a string to be used on C files msousa@1909: params: child - the correspondent subplugin in Beremiz msousa@1909: """ msousa@1909: node_init_template = '''/*node %(locnodestr)s*/ msousa@2654: {"%(locnodestr)s", "%(config_name)s", "%(host)s", "%(port)s", {naf_tcp, {.tcp = {NULL, NULL, DEF_CLOSE_ON_SILENCE}}}, -1 /* mb_nd */, 0 /* init_state */, %(coms_period)s /* communication period */, 0 /* prev_error */}''' msousa@2654: msousa@2654: location = ".".join(map(str, child.GetCurrentLocation())) msousa@2654: config_name, host, port, coms_period = GetCTVals(child, range(4)) msousa@2654: msousa@2654: node_dict = {"locnodestr": location, msousa@2654: "config_name": config_name, Edouard@1918: "host": host, Edouard@1918: "port": port, Edouard@1919: "coms_period": coms_period} Edouard@1918: return node_init_template % node_dict msousa@1909: msousa@1909: msousa@1909: def GetClientRequestPrinted(self, child, nodeid): msousa@1909: """ msousa@1909: Outputs a string to be used on C files msousa@1909: params: child - the correspondent subplugin in Beremiz msousa@1909: nodeid - on C code, each request has it's own parent node (sequential, 0..NUMBER_OF_NODES) msousa@1909: It's this parameter. msousa@1909: return: None - if any definition error found msousa@1909: The string that should be added on C code - if everything goes allright msousa@1909: """ msousa@1909: msousa@1909: req_init_template = '''/*request %(locreqstr)s*/ msousa@1909: {"%(locreqstr)s", %(nodeid)s, %(slaveid)s, %(iotype)s, %(func_nr)s, %(address)s , %(count)s, msousa@2713: DEF_REQ_SEND_RETRIES, 0 /* mb_error_code */, 0 /* tn_error_code */, 0 /* prev_code */, {%(timeout_s)d, %(timeout_ns)d} /* timeout */, %(write_on_change)d /* write_on_change */, msousa@1909: {%(buffer)s}, {%(buffer)s}}''' Edouard@1918: Edouard@1918: timeout = int(GetCTVal(child, 4)) andrej@2437: timeout_s = timeout // 1000 Edouard@1918: timeout_ms = timeout - (timeout_s * 1000) Edouard@1918: timeout_ns = timeout_ms * 1000000 Edouard@1918: Edouard@1918: request_dict = { Edouard@1918: "locreqstr": "_".join(map(str, child.GetCurrentLocation())), Edouard@1918: "nodeid": str(nodeid), Edouard@1918: "slaveid": GetCTVal(child, 1), Edouard@1918: "address": GetCTVal(child, 3), Edouard@1918: "count": GetCTVal(child, 2), msousa@2647: "write_on_change": GetCTVal(child, 5), Edouard@1918: "timeout": timeout, Edouard@1918: "timeout_s": timeout_s, Edouard@1918: "timeout_ns": timeout_ns, Edouard@1918: "buffer": ",".join(['0'] * int(GetCTVal(child, 2))), Edouard@1918: "func_nr": modbus_function_dict[GetCTVal(child, 0)][0], Edouard@1918: "iotype": modbus_function_dict[GetCTVal(child, 0)][1], Edouard@1918: "maxcount": modbus_function_dict[GetCTVal(child, 0)][2]} Edouard@1918: msousa@1909: if int(request_dict["slaveid"]) not in xrange(256): Edouard@1918: self.GetCTRoot().logger.write_error( Edouard@1918: "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) msousa@1909: return None msousa@1909: if int(request_dict["address"]) not in xrange(65536): Edouard@1918: self.GetCTRoot().logger.write_error( Edouard@1918: "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) Edouard@1918: return None Edouard@1918: if int(request_dict["count"]) not in xrange(1, 1 + int(request_dict["maxcount"])): Edouard@1918: self.GetCTRoot().logger.write_error( Edouard@1918: "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) Edouard@1918: return None Edouard@1918: if (int(request_dict["address"]) + int(request_dict["count"])) not in xrange(1, 65537): Edouard@1918: self.GetCTRoot().logger.write_error( Edouard@1918: "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) Edouard@1918: return None msousa@2647: if (request_dict["write_on_change"] and (request_dict["iotype"] == 'req_input')): msousa@2647: self.GetCTRoot().logger.write_error( msousa@2647: "Modbus plugin: (warning) MB client request node %(locreqstr)s has option 'write_on_change' enabled.\nModbus plugin: This option will be ignored by the Modbus read function.\n" % request_dict) msousa@2647: # NOTE: this is only a warning (we don't wish to abort code generation) so following line must be left commented out! msousa@2647: # return None msousa@2647: Edouard@1918: msousa@1909: return req_init_template % request_dict