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 <http://www.gnu.org/licenses/>.
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@2654: DEF_REQ_SEND_RETRIES, 0 /* 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