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: msousa@1909: Edouard@1919: from __future__ import absolute_import Edouard@1918: import os andrej@2432: from six.moves import xrange andrej@2432: Edouard@1919: from modbus.mb_utils import * msousa@1909: from ConfigTreeNode import ConfigTreeNode Edouard@1919: from PLCControler import LOCATION_CONFNODE, LOCATION_VAR_MEMORY Edouard@2669: import util.paths as paths msousa@1909: Edouard@2736: ModbusPath = paths.ThirdPartyPath("Modbus") Edouard@1918: Edouard@1918: Edouard@1918: # Edouard@1918: # Edouard@1918: # Edouard@1918: # C L I E N T R E Q U E S T # Edouard@1918: # Edouard@1918: # Edouard@1918: # msousa@1909: msousa@1909: Edouard@1919: class _RequestPlug(object): msousa@1909: XSD = """ msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@2647: msousa@1909: msousa@1909: msousa@1909: msousa@1909: """ msousa@1909: Edouard@1918: def GetParamsAttributes(self, path=None): Edouard@1918: infos = ConfigTreeNode.GetParamsAttributes(self, path=path) msousa@1909: for element in infos: msousa@1909: if element["name"] == "ModbusRequest": msousa@1909: for child in element["children"]: msousa@1909: if child["name"] == "Function": msousa@1909: list = modbus_function_dict.keys() msousa@1909: list.sort() msousa@1909: child["type"] = list msousa@1909: return infos Edouard@1918: msousa@1909: def GetVariableLocationTree(self): msousa@1909: current_location = self.GetCurrentLocation() msousa@1909: name = self.BaseParams.getName() msousa@1909: address = self.GetParamsAttributes()[0]["children"][3]["value"] Edouard@1918: count = self.GetParamsAttributes()[0]["children"][2]["value"] Edouard@1918: function = self.GetParamsAttributes()[0]["children"][0]["value"] msousa@1909: # 'BOOL' or 'WORD' Edouard@1918: datatype = modbus_function_dict[function][3] msousa@1909: # 1 or 16 Edouard@1918: datasize = modbus_function_dict[function][4] msousa@1909: # 'Q' for coils and holding registers, 'I' for input discretes and input registers Edouard@1919: # datazone = modbus_function_dict[function][5] msousa@1909: # 'X' for bits, 'W' for words Edouard@1918: datatacc = modbus_function_dict[function][6] msousa@1909: # 'Coil', 'Holding Register', 'Input Discrete' or 'Input Register' Edouard@1918: dataname = modbus_function_dict[function][7] msousa@2647: # start off with a boolean entry msousa@2647: # This is a flag used to allow the user program to control when to msousa@2647: # execute the Modbus request. msousa@2647: # NOTE: If the Modbus request has a 'current_location' of msousa@2647: # %QX1.2.3 msousa@2647: # then the execution control flag will be msousa@2647: # %QX1.2.3.0.0 msousa@2647: # and all the Modbus registers/coils will be msousa@2647: # %QX1.2.3.0 msousa@2647: # %QX1.2.3.1 msousa@2647: # %QX1.2.3.2 msousa@2647: # .. msousa@2647: # %QX1.2.3.n msousa@1909: entries = [] msousa@2647: entries.append({ msousa@2713: "name": "Execute request flag", msousa@2647: "type": LOCATION_VAR_MEMORY, msousa@2647: "size": 1, # BOOL flag msousa@2647: "IEC_type": "BOOL", # BOOL flag msousa@2647: "var_name": "var_name", msousa@2647: "location": "X" + ".".join([str(i) for i in current_location]) + ".0.0", msousa@2713: "description": "Modbus request execution control flag", msousa@2713: "children": []}) msousa@2713: entries.append({ msousa@2714: "name": "Modbus Request Status flag", msousa@2713: "type": LOCATION_VAR_MEMORY, msousa@2714: "size": 8, # BYTE flag msousa@2714: "IEC_type": "BYTE", # BYTE flag msousa@2713: "var_name": "var_name", msousa@2714: "location": "B" + ".".join([str(i) for i in current_location]) + ".0.1", msousa@2717: "description": "Modbus request status flag (0 -> OK, 1 -> Network error, 2 -> Received invalid frame, 3 -> Timeout, 4 -> Received error frame)", msousa@2647: "children": []}) msousa@2714: entries.append({ msousa@2714: "name": "Modbus Error Code", msousa@2714: "type": LOCATION_VAR_MEMORY, msousa@2714: "size": 8, # BYTE flag msousa@2714: "IEC_type": "BYTE", # BYTE flag msousa@2714: "var_name": "var_name", msousa@2714: "location": "B" + ".".join([str(i) for i in current_location]) + ".0.2", msousa@2714: "description": "Modbus Error Code received in Modbus error frame", msousa@2714: "children": []}) Edouard@1918: for offset in range(address, address + count): msousa@1909: entries.append({ msousa@1909: "name": dataname + " " + str(offset), msousa@1909: "type": LOCATION_VAR_MEMORY, msousa@1909: "size": datasize, msousa@1909: "IEC_type": datatype, Edouard@2589: "var_name": "MB_" + "".join([w[0] for w in dataname.split()]) + "_" + str(offset), msousa@1909: "location": datatacc + ".".join([str(i) for i in current_location]) + "." + str(offset), msousa@1909: "description": "description", msousa@1909: "children": []}) Edouard@1918: return {"name": name, Edouard@1918: "type": LOCATION_CONFNODE, Edouard@1918: "location": ".".join([str(i) for i in current_location]) + ".x", Edouard@1918: "children": entries} msousa@1909: msousa@1909: def CTNGenerate_C(self, buildpath, locations): msousa@1909: """ msousa@1909: Generate C code msousa@1909: @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) msousa@1909: @param locations: List of complete variables locations \ msousa@1909: [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) msousa@1909: "NAME" : name of the variable (generally "__IW0_1_2" style) msousa@1909: "DIR" : direction "Q","I" or "M" msousa@1909: "SIZE" : size "X", "B", "W", "D", "L" msousa@1909: "LOC" : tuple of interger for IEC location (0,1,2,...) msousa@1909: }, ...] msousa@1909: @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND msousa@1909: """ msousa@1909: return [], "", False Edouard@1918: Edouard@1918: msousa@2717: Edouard@1918: # Edouard@1918: # Edouard@1918: # Edouard@1918: # S E R V E R M E M O R Y A R E A # Edouard@1918: # Edouard@1918: # Edouard@1918: # Edouard@1918: Edouard@1918: # dictionary implementing: Edouard@1918: # key - string with the description we want in the request plugin GUI Edouard@1918: # list - (modbus function number, request type, max count value) Edouard@1918: modbus_memtype_dict = { Edouard@1918: "01 - Coils": ('1', 'rw_bits', 65536, "BOOL", 1, "Q", "X", "Coil"), Edouard@1918: "02 - Input Discretes": ('2', 'ro_bits', 65536, "BOOL", 1, "I", "X", "Input Discrete"), Edouard@1918: "03 - Holding Registers": ('3', 'rw_words', 65536, "WORD", 16, "Q", "W", "Holding Register"), Edouard@1918: "04 - Input Registers": ('4', 'ro_words', 65536, "WORD", 16, "I", "W", "Input Register"), Edouard@1918: } Edouard@1918: msousa@1909: Edouard@1919: class _MemoryAreaPlug(object): msousa@1909: XSD = """ msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: """ msousa@1909: Edouard@1918: def GetParamsAttributes(self, path=None): Edouard@1918: infos = ConfigTreeNode.GetParamsAttributes(self, path=path) msousa@1909: for element in infos: msousa@1909: if element["name"] == "MemoryArea": msousa@1909: for child in element["children"]: msousa@1909: if child["name"] == "MemoryAreaType": msousa@1909: list = modbus_memtype_dict.keys() msousa@1909: list.sort() msousa@1909: child["type"] = list msousa@1909: return infos msousa@1909: msousa@1909: def GetVariableLocationTree(self): msousa@1909: current_location = self.GetCurrentLocation() msousa@1909: name = self.BaseParams.getName() msousa@1909: address = self.GetParamsAttributes()[0]["children"][2]["value"] Edouard@1918: count = self.GetParamsAttributes()[0]["children"][1]["value"] Edouard@1918: function = self.GetParamsAttributes()[0]["children"][0]["value"] msousa@1909: # 'BOOL' or 'WORD' Edouard@1918: datatype = modbus_memtype_dict[function][3] msousa@1909: # 1 or 16 Edouard@1918: datasize = modbus_memtype_dict[function][4] msousa@1909: # 'Q' for coils and holding registers, 'I' for input discretes and input registers Edouard@1919: # datazone = modbus_memtype_dict[function][5] msousa@1909: # 'X' for bits, 'W' for words Edouard@1918: datatacc = modbus_memtype_dict[function][6] msousa@1909: # 'Coil', 'Holding Register', 'Input Discrete' or 'Input Register' Edouard@1918: dataname = modbus_memtype_dict[function][7] msousa@1909: entries = [] Edouard@1918: for offset in range(address, address + count): msousa@1909: entries.append({ msousa@1909: "name": dataname + " " + str(offset), msousa@1909: "type": LOCATION_VAR_MEMORY, msousa@1909: "size": datasize, msousa@1909: "IEC_type": datatype, Edouard@2589: "var_name": "MB_" + "".join([w[0] for w in dataname.split()]) + "_" + str(offset), msousa@1909: "location": datatacc + ".".join([str(i) for i in current_location]) + "." + str(offset), msousa@1909: "description": "description", msousa@1909: "children": []}) Edouard@1918: return {"name": name, Edouard@1918: "type": LOCATION_CONFNODE, Edouard@1918: "location": ".".join([str(i) for i in current_location]) + ".x", Edouard@1918: "children": entries} msousa@1909: msousa@1909: def CTNGenerate_C(self, buildpath, locations): msousa@1909: """ msousa@1909: Generate C code msousa@1909: @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) msousa@1909: @param locations: List of complete variables locations \ msousa@1909: [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) msousa@1909: "NAME" : name of the variable (generally "__IW0_1_2" style) msousa@1909: "DIR" : direction "Q","I" or "M" msousa@1909: "SIZE" : size "X", "B", "W", "D", "L" msousa@1909: "LOC" : tuple of interger for IEC location (0,1,2,...) msousa@1909: }, ...] msousa@1909: @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND msousa@1909: """ msousa@1909: return [], "", False msousa@1909: Edouard@1918: Edouard@1918: # Edouard@1918: # Edouard@1918: # Edouard@1918: # T C P C L I E N T # Edouard@1918: # Edouard@1918: # Edouard@1918: # msousa@1909: Edouard@2677: # XXX TODO "Configuration_Name" should disapear in favor of CTN Name, which is already unique Edouard@2677: Edouard@1919: class _ModbusTCPclientPlug(object): msousa@1909: XSD = """ msousa@1909: msousa@1909: msousa@1909: msousa@2654: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@2647: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: """ Edouard@1918: # NOTE: Max value of 2147483647 (i32_max) for Invocation_Rate_in_ms Edouard@1918: # corresponds to aprox 25 days. Edouard@1918: CTNChildrenTypes = [("ModbusRequest", _RequestPlug, "Request")] msousa@1909: # TODO: Replace with CTNType !!! msousa@1909: PlugType = "ModbusTCPclient" msousa@1909: msousa@2654: msousa@2654: def __init__(self): msousa@2654: # NOTE: msousa@2654: # The ModbusTCPclient attribute is added dynamically by ConfigTreeNode._AddParamsMembers() msousa@2654: # It will be an XML parser object created by msousa@2654: # GenerateParserFromXSDstring(self.XSD).CreateRoot() msousa@2654: msousa@2654: # Set the default value for the "Configuration_Name" parameter msousa@2654: # The default value will need to be different for each instance of the msousa@2654: # _ModbusTCPclientPlug class, so we cannot hardcode the default value in the XSD above msousa@2654: # This value will be used by the web interface msousa@2654: # (i.e. the extension to the web server used to configure the Modbus parameters). msousa@2654: # (The web server is run/activated/started by Beremiz_service.py) msousa@2654: # (The web server code is found in runtime/NevowServer.py) msousa@2654: # (The Modbus extension to the web server is found in runtime/Modbus_config.py) msousa@2654: loc_str = ".".join(map(str, self.GetCurrentLocation())) msousa@2654: self.ModbusTCPclient.setConfiguration_Name("Modbus TCP Client " + loc_str) msousa@2654: msousa@1909: # Return the number of (modbus library) nodes this specific TCP client will need msousa@1909: # return type: (tcp nodes, rtu nodes, ascii nodes) msousa@1909: def GetNodeCount(self): msousa@1909: return (1, 0, 0) msousa@1909: msousa@2662: def GetConfigName(self): msousa@2662: """ Return the node's Configuration_Name """ msousa@2662: return self.ModbusTCPclient.getConfiguration_Name() msousa@2662: msousa@1909: def CTNGenerate_C(self, buildpath, locations): msousa@1909: """ msousa@1909: Generate C code msousa@1909: @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) msousa@1909: @param locations: List of complete variables locations \ msousa@1909: [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) msousa@1909: "NAME" : name of the variable (generally "__IW0_1_2" style) msousa@1909: "DIR" : direction "Q","I" or "M" msousa@1909: "SIZE" : size "X", "B", "W", "D", "L" msousa@1909: "LOC" : tuple of interger for IEC location (0,1,2,...) msousa@1909: }, ...] msousa@1909: @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND msousa@1909: """ msousa@1909: return [], "", False msousa@1909: msousa@1909: Edouard@1918: # Edouard@1918: # Edouard@1918: # Edouard@1918: # T C P S E R V E R # Edouard@1918: # Edouard@1918: # Edouard@1918: # msousa@1909: Edouard@2677: # XXX TODO "Configuration_Name" should disapear in favor of CTN Name, which is already unique Edouard@2677: Edouard@1919: class _ModbusTCPserverPlug(object): msousa@1909: # NOTE: the Port number is a 'string' and not an 'integer'! Edouard@1918: # This is because the underlying modbus library accepts strings msousa@1909: # (e.g.: well known port names!) msousa@1909: XSD = """ msousa@1909: msousa@1909: msousa@1909: msousa@2654: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: """ Edouard@1918: CTNChildrenTypes = [("MemoryArea", _MemoryAreaPlug, "Memory Area")] msousa@1909: # TODO: Replace with CTNType !!! msousa@1909: PlugType = "ModbusTCPserver" msousa@1909: msousa@2654: def __init__(self): msousa@2654: # NOTE: msousa@2654: # The ModbusServerNode attribute is added dynamically by ConfigTreeNode._AddParamsMembers() msousa@2654: # It will be an XML parser object created by msousa@2654: # GenerateParserFromXSDstring(self.XSD).CreateRoot() msousa@2654: msousa@2654: # Set the default value for the "Configuration_Name" parameter msousa@2654: # The default value will need to be different for each instance of the msousa@2654: # _ModbusTCPclientPlug class, so we cannot hardcode the default value in the XSD above msousa@2654: # This value will be used by the web interface msousa@2654: # (i.e. the extension to the web server used to configure the Modbus parameters). msousa@2654: # (The web server is run/activated/started by Beremiz_service.py) msousa@2654: # (The web server code is found in runtime/NevowServer.py) msousa@2654: # (The Modbus extension to the web server is found in runtime/Modbus_config.py) msousa@2654: loc_str = ".".join(map(str, self.GetCurrentLocation())) msousa@2654: self.ModbusServerNode.setConfiguration_Name("Modbus TCP Server " + loc_str) msousa@2654: msousa@1909: # Return the number of (modbus library) nodes this specific TCP server will need msousa@1909: # return type: (tcp nodes, rtu nodes, ascii nodes) msousa@1909: def GetNodeCount(self): msousa@1909: return (1, 0, 0) msousa@1909: msousa@2663: # Return a list with a single tuple conatining the (location, IP address, port number) msousa@2663: # location : location of this node in the configuration tree msousa@1909: # port number: IP port used by this Modbus/IP server msousa@2663: # IP address : IP address of the network interface on which the server will be listening msousa@2663: # ("", "*", or "#ANY#" => listening on all interfaces!) msousa@1909: def GetIPServerPortNumbers(self): msousa@2663: port = self.ModbusServerNode.getLocal_Port_Number() msousa@2663: addr = self.ModbusServerNode.getLocal_IP_Address() msousa@2663: return [(self.GetCurrentLocation(), addr, port)] msousa@1909: msousa@2662: def GetConfigName(self): msousa@2662: """ Return the node's Configuration_Name """ msousa@2662: return self.ModbusServerNode.getConfiguration_Name() msousa@2662: msousa@2721: def GetVariableLocationTree(self): msousa@2721: current_location = self.GetCurrentLocation() msousa@2721: name = self.BaseParams.getName() msousa@2721: # start off with flags that count the number of Modbus requests/transactions msousa@2721: # handled by this Modbus server/slave. msousa@2721: # These flags are mapped onto located variables and therefore available to the user programs msousa@2721: # May be used to detect communication errors. msousa@2721: # execute the Modbus request. msousa@2721: # NOTE: If the Modbus slave has a 'current_location' of msousa@2721: # %QX1.2 msousa@2722: # then the "Modbus Read Request Counter" will be %MD1.2.0 msousa@2722: # then the "Modbus Write Request Counter" will be %MD1.2.1 msousa@2722: # then the "Modbus Read Request Flag" will be %MD1.2.2 msousa@2722: # then the "Modbus Write Request Flag" will be %MD1.2.3 msousa@2721: # msousa@2721: # Note that any MemoryArea contained under this server/slave msousa@2721: # will ocupy the locations of type msousa@2721: # %MX or %MW msousa@2721: # which will never clash with the %MD used here. msousa@2721: # Additionaly, any MemoryArea contained under this server/slave msousa@2721: # will ocupy locations with msousa@2721: # %M1.2.a.b (with a and b being numbers in range 0, 1, ...) msousa@2721: # and therefore never ocupy the locations msousa@2721: # %M1.2.0 msousa@2721: # %M1.2.1 msousa@2722: # %M1.2.2 msousa@2722: # %M1.2.3 msousa@2721: # used by the following flags/counters. msousa@2721: entries = [] msousa@2721: entries.append({ msousa@2721: "name": "Modbus Read Request Counter", msousa@2721: "type": LOCATION_VAR_MEMORY, msousa@2721: "size": 32, # UDINT flag msousa@2721: "IEC_type": "UDINT", # UDINT flag msousa@2721: "var_name": "var_name", msousa@2721: "location": "D" + ".".join([str(i) for i in current_location]) + ".0", msousa@2721: "description": "Modbus read request counter", msousa@2721: "children": []}) msousa@2721: entries.append({ msousa@2721: "name": "Modbus Write Request Counter", msousa@2721: "type": LOCATION_VAR_MEMORY, msousa@2721: "size": 32, # UDINT flag msousa@2721: "IEC_type": "UDINT", # UDINT flag msousa@2721: "var_name": "var_name", msousa@2721: "location": "D" + ".".join([str(i) for i in current_location]) + ".1", msousa@2721: "description": "Modbus write request counter", msousa@2721: "children": []}) msousa@2722: entries.append({ msousa@2722: "name": "Modbus Read Request Flag", msousa@2722: "type": LOCATION_VAR_MEMORY, msousa@2722: "size": 1, # BOOL flag msousa@2722: "IEC_type": "BOOL", # BOOL flag msousa@2722: "var_name": "var_name", msousa@2722: "location": "X" + ".".join([str(i) for i in current_location]) + ".2", msousa@2722: "description": "Modbus read request flag", msousa@2722: "children": []}) msousa@2722: entries.append({ msousa@2722: "name": "Modbus write Request Flag", msousa@2722: "type": LOCATION_VAR_MEMORY, msousa@2722: "size": 1, # BOOL flag msousa@2722: "IEC_type": "BOOL", # BOOL flag msousa@2722: "var_name": "var_name", msousa@2722: "location": "X" + ".".join([str(i) for i in current_location]) + ".3", msousa@2722: "description": "Modbus write request flag", msousa@2722: "children": []}) msousa@2721: # recursively call all the Memory Areas under this Modbus server/save msousa@2721: # i.e., all the children objects which will be of class _MemoryAreaPlug msousa@2721: for child in self.IECSortedChildren(): msousa@2721: entries.append(child.GetVariableLocationTree()) msousa@2721: msousa@2721: return {"name": name, msousa@2721: "type": LOCATION_CONFNODE, msousa@2721: "location": ".".join([str(i) for i in current_location]) + ".x", msousa@2721: "children": entries} msousa@2721: msousa@2721: msousa@1909: def CTNGenerate_C(self, buildpath, locations): msousa@1909: """ msousa@1909: Generate C code msousa@1909: @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) msousa@1909: @param locations: List of complete variables locations \ msousa@1909: [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) msousa@1909: "NAME" : name of the variable (generally "__IW0_1_2" style) msousa@1909: "DIR" : direction "Q","I" or "M" msousa@1909: "SIZE" : size "X", "B", "W", "D", "L" msousa@1909: "LOC" : tuple of interger for IEC location (0,1,2,...) msousa@1909: }, ...] msousa@1909: @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND msousa@1909: """ msousa@1909: return [], "", False msousa@1909: msousa@1909: Edouard@1918: # Edouard@1918: # Edouard@1918: # Edouard@1918: # R T U C L I E N T # Edouard@1918: # Edouard@1918: # Edouard@1918: # msousa@1909: Edouard@2677: # XXX TODO "Configuration_Name" should disapear in favor of CTN Name, which is already unique Edouard@2677: Edouard@1919: class _ModbusRTUclientPlug(object): msousa@1909: XSD = """ msousa@1909: msousa@1909: msousa@1909: msousa@2654: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@2647: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: """ Edouard@1918: # NOTE: Max value of 2147483647 (i32_max) for Invocation_Rate_in_ms Edouard@1918: # corresponds to aprox 25 days. Edouard@1918: CTNChildrenTypes = [("ModbusRequest", _RequestPlug, "Request")] msousa@1909: # TODO: Replace with CTNType !!! msousa@1909: PlugType = "ModbusRTUclient" msousa@1909: msousa@2654: def __init__(self): msousa@2654: # NOTE: msousa@2654: # The ModbusRTUclient attribute is added dynamically by ConfigTreeNode._AddParamsMembers() msousa@2654: # It will be an XML parser object created by msousa@2654: # GenerateParserFromXSDstring(self.XSD).CreateRoot() msousa@2654: msousa@2654: # Set the default value for the "Configuration_Name" parameter msousa@2654: # The default value will need to be different for each instance of the msousa@2654: # _ModbusTCPclientPlug class, so we cannot hardcode the default value in the XSD above msousa@2654: # This value will be used by the web interface msousa@2654: # (i.e. the extension to the web server used to configure the Modbus parameters). msousa@2654: # (The web server is run/activated/started by Beremiz_service.py) msousa@2654: # (The web server code is found in runtime/NevowServer.py) msousa@2654: # (The Modbus extension to the web server is found in runtime/Modbus_config.py) msousa@2654: loc_str = ".".join(map(str, self.GetCurrentLocation())) msousa@2654: self.ModbusRTUclient.setConfiguration_Name("Modbus RTU Client " + loc_str) msousa@2654: Edouard@1918: def GetParamsAttributes(self, path=None): Edouard@1918: infos = ConfigTreeNode.GetParamsAttributes(self, path=path) msousa@1909: for element in infos: msousa@1909: if element["name"] == "ModbusRTUclient": msousa@1909: for child in element["children"]: msousa@1909: if child["name"] == "Baud_Rate": msousa@1909: child["type"] = modbus_serial_baudrate_list msousa@1909: if child["name"] == "Stop_Bits": msousa@1909: child["type"] = modbus_serial_stopbits_list msousa@1909: if child["name"] == "Parity": msousa@1909: child["type"] = modbus_serial_parity_dict.keys() msousa@1909: return infos Edouard@1918: msousa@1909: # Return the number of (modbus library) nodes this specific RTU client will need msousa@1909: # return type: (tcp nodes, rtu nodes, ascii nodes) msousa@1909: def GetNodeCount(self): msousa@1909: return (0, 1, 0) msousa@1909: msousa@2662: def GetConfigName(self): msousa@2662: """ Return the node's Configuration_Name """ msousa@2662: return self.ModbusRTUclient.getConfiguration_Name() msousa@2662: msousa@1909: def CTNGenerate_C(self, buildpath, locations): msousa@1909: """ msousa@1909: Generate C code msousa@1909: @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) msousa@1909: @param locations: List of complete variables locations \ msousa@1909: [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) msousa@1909: "NAME" : name of the variable (generally "__IW0_1_2" style) msousa@1909: "DIR" : direction "Q","I" or "M" msousa@1909: "SIZE" : size "X", "B", "W", "D", "L" msousa@1909: "LOC" : tuple of interger for IEC location (0,1,2,...) msousa@1909: }, ...] msousa@1909: @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND msousa@1909: """ msousa@1909: return [], "", False msousa@1909: msousa@1909: Edouard@1918: # Edouard@1918: # Edouard@1918: # Edouard@1918: # R T U S L A V E # Edouard@1918: # Edouard@1918: # Edouard@1918: # msousa@1909: Edouard@2677: # XXX TODO "Configuration_Name" should disapear in favor of CTN Name, which is already unique msousa@1909: Edouard@1919: class _ModbusRTUslavePlug(object): msousa@1909: XSD = """ msousa@1909: msousa@1909: msousa@1909: msousa@2654: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: """ Edouard@1918: CTNChildrenTypes = [("MemoryArea", _MemoryAreaPlug, "Memory Area")] msousa@1909: # TODO: Replace with CTNType !!! msousa@1909: PlugType = "ModbusRTUslave" msousa@1909: msousa@2654: def __init__(self): msousa@2654: # NOTE: msousa@2654: # The ModbusRTUslave attribute is added dynamically by ConfigTreeNode._AddParamsMembers() msousa@2654: # It will be an XML parser object created by msousa@2654: # GenerateParserFromXSDstring(self.XSD).CreateRoot() msousa@2654: msousa@2654: # Set the default value for the "Configuration_Name" parameter msousa@2654: # The default value will need to be different for each instance of the msousa@2654: # _ModbusTCPclientPlug class, so we cannot hardcode the default value in the XSD above msousa@2654: # This value will be used by the web interface msousa@2654: # (i.e. the extension to the web server used to configure the Modbus parameters). msousa@2654: # (The web server is run/activated/started by Beremiz_service.py) msousa@2654: # (The web server code is found in runtime/NevowServer.py) msousa@2654: # (The Modbus extension to the web server is found in runtime/Modbus_config.py) msousa@2654: loc_str = ".".join(map(str, self.GetCurrentLocation())) msousa@2654: self.ModbusRTUslave.setConfiguration_Name("Modbus RTU Slave " + loc_str) msousa@2654: Edouard@1918: def GetParamsAttributes(self, path=None): Edouard@1918: infos = ConfigTreeNode.GetParamsAttributes(self, path=path) msousa@1909: for element in infos: msousa@1909: if element["name"] == "ModbusRTUslave": msousa@1909: for child in element["children"]: msousa@1909: if child["name"] == "Baud_Rate": msousa@1909: child["type"] = modbus_serial_baudrate_list msousa@1909: if child["name"] == "Stop_Bits": msousa@1909: child["type"] = modbus_serial_stopbits_list msousa@1909: if child["name"] == "Parity": msousa@1909: child["type"] = modbus_serial_parity_dict.keys() msousa@1909: return infos Edouard@1918: msousa@1909: # Return the number of (modbus library) nodes this specific RTU slave will need msousa@1909: # return type: (tcp nodes, rtu nodes, ascii nodes) msousa@1909: def GetNodeCount(self): msousa@1909: return (0, 1, 0) msousa@1909: msousa@2662: def GetConfigName(self): msousa@2662: """ Return the node's Configuration_Name """ msousa@2662: return self.ModbusRTUslave.getConfiguration_Name() msousa@2662: msousa@2721: def GetVariableLocationTree(self): msousa@2721: current_location = self.GetCurrentLocation() msousa@2721: name = self.BaseParams.getName() msousa@2721: # start off with flags that count the number of Modbus requests/transactions msousa@2721: # handled by this Modbus server/slave. msousa@2721: # These flags are mapped onto located variables and therefore available to the user programs msousa@2721: # May be used to detect communication errors. msousa@2721: # execute the Modbus request. msousa@2721: # NOTE: If the Modbus slave has a 'current_location' of msousa@2721: # %QX1.2 msousa@2722: # then the "Modbus Read Request Counter" will be %MD1.2.0 msousa@2722: # then the "Modbus Write Request Counter" will be %MD1.2.1 msousa@2722: # then the "Modbus Read Request Flag" will be %MD1.2.2 msousa@2722: # then the "Modbus Write Request Flag" will be %MD1.2.3 msousa@2721: # msousa@2721: # Note that any MemoryArea contained under this server/slave msousa@2721: # will ocupy the locations of type msousa@2721: # %MX or %MW msousa@2721: # which will never clash with the %MD used here. msousa@2721: # Additionaly, any MemoryArea contained under this server/slave msousa@2721: # will ocupy locations with msousa@2721: # %M1.2.a.b (with a and b being numbers in range 0, 1, ...) msousa@2721: # and therefore never ocupy the locations msousa@2721: # %M1.2.0 msousa@2721: # %M1.2.1 msousa@2722: # %M1.2.2 msousa@2722: # %M1.2.3 msousa@2721: # used by the following flags/counters. msousa@2721: entries = [] msousa@2721: entries.append({ msousa@2721: "name": "Modbus Read Request Counter", msousa@2721: "type": LOCATION_VAR_MEMORY, msousa@2721: "size": 32, # UDINT flag msousa@2721: "IEC_type": "UDINT", # UDINT flag msousa@2721: "var_name": "var_name", msousa@2721: "location": "D" + ".".join([str(i) for i in current_location]) + ".0", msousa@2721: "description": "Modbus read request counter", msousa@2721: "children": []}) msousa@2721: entries.append({ msousa@2721: "name": "Modbus Write Request Counter", msousa@2721: "type": LOCATION_VAR_MEMORY, msousa@2721: "size": 32, # UDINT flag msousa@2721: "IEC_type": "UDINT", # UDINT flag msousa@2721: "var_name": "var_name", msousa@2721: "location": "D" + ".".join([str(i) for i in current_location]) + ".1", msousa@2721: "description": "Modbus write request counter", msousa@2721: "children": []}) msousa@2722: entries.append({ msousa@2722: "name": "Modbus Read Request Flag", msousa@2722: "type": LOCATION_VAR_MEMORY, msousa@2722: "size": 1, # BOOL flag msousa@2722: "IEC_type": "BOOL", # BOOL flag msousa@2722: "var_name": "var_name", msousa@2722: "location": "X" + ".".join([str(i) for i in current_location]) + ".2", msousa@2722: "description": "Modbus read request flag", msousa@2722: "children": []}) msousa@2722: entries.append({ msousa@2722: "name": "Modbus write Request Flag", msousa@2722: "type": LOCATION_VAR_MEMORY, msousa@2722: "size": 1, # BOOL flag msousa@2722: "IEC_type": "BOOL", # BOOL flag msousa@2722: "var_name": "var_name", msousa@2722: "location": "X" + ".".join([str(i) for i in current_location]) + ".3", msousa@2722: "description": "Modbus write request flag", msousa@2722: "children": []}) msousa@2721: # recursively call all the Memory Areas under this Modbus server/save msousa@2721: # i.e., all the children objects which will be of class _MemoryAreaPlug msousa@2721: for child in self.IECSortedChildren(): msousa@2721: entries.append(child.GetVariableLocationTree()) msousa@2721: msousa@2721: return {"name": name, msousa@2721: "type": LOCATION_CONFNODE, msousa@2721: "location": ".".join([str(i) for i in current_location]) + ".x", msousa@2721: "children": entries} msousa@2721: msousa@2721: msousa@1909: def CTNGenerate_C(self, buildpath, locations): msousa@1909: """ msousa@1909: Generate C code msousa@1909: @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5) msousa@1909: @param locations: List of complete variables locations \ msousa@1909: [{"IEC_TYPE" : the IEC type (i.e. "INT", "STRING", ...) msousa@1909: "NAME" : name of the variable (generally "__IW0_1_2" style) msousa@1909: "DIR" : direction "Q","I" or "M" msousa@1909: "SIZE" : size "X", "B", "W", "D", "L" msousa@1909: "LOC" : tuple of interger for IEC location (0,1,2,...) msousa@1909: }, ...] msousa@1909: @return: [(C_file_name, CFLAGS),...] , LDFLAGS_TO_APPEND msousa@1909: """ msousa@1909: return [], "", False msousa@1909: Edouard@1918: Edouard@1918: def _lt_to_str(loctuple): Edouard@1918: return '.'.join(map(str, loctuple)) Edouard@1918: Edouard@1918: Edouard@1918: # Edouard@1918: # Edouard@1918: # Edouard@1918: # R O O T C L A S S # Edouard@1918: # Edouard@1918: # Edouard@1918: # Edouard@1919: class RootClass(object): msousa@1909: XSD = """ msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: msousa@1909: """ Edouard@1919: CTNChildrenTypes = [("ModbusTCPclient", _ModbusTCPclientPlug, "Modbus TCP Client"), Edouard@1919: ("ModbusTCPserver", _ModbusTCPserverPlug, "Modbus TCP Server"), Edouard@1919: ("ModbusRTUclient", _ModbusRTUclientPlug, "Modbus RTU Client"), Edouard@1919: ("ModbusRTUslave", _ModbusRTUslavePlug, "Modbus RTU Slave")] Edouard@1918: msousa@1909: # Return the number of (modbus library) nodes this specific instance of the modbus plugin will need msousa@1909: # return type: (tcp nodes, rtu nodes, ascii nodes) msousa@1909: def GetNodeCount(self): Edouard@1918: max_remote_tcpclient = self.GetParamsAttributes()[ Edouard@1918: 0]["children"][0]["value"] msousa@1909: total_node_count = (max_remote_tcpclient, 0, 0) msousa@1909: for child in self.IECSortedChildren(): msousa@1909: # ask each child how many nodes it needs, and add them all up. Edouard@1918: total_node_count = tuple( Edouard@1918: x1 + x2 for x1, x2 in zip(total_node_count, child.GetNodeCount())) msousa@1909: return total_node_count msousa@1909: msousa@2662: # Return a list with tuples of the (location, port numbers) used by all the Modbus/IP servers msousa@1909: def GetIPServerPortNumbers(self): msousa@1909: IPServer_port_numbers = [] msousa@1909: for child in self.IECSortedChildren(): msousa@1909: if child.CTNType == "ModbusTCPserver": msousa@1909: IPServer_port_numbers.extend(child.GetIPServerPortNumbers()) msousa@1909: return IPServer_port_numbers msousa@1909: msousa@2662: # Return a list with tuples of the (location, configuration_name) used by all the Modbus nodes (tcp/rtu, clients/servers) msousa@2662: def GetConfigNames(self): msousa@2662: Node_Configuration_Names = [] msousa@2662: for child in self.IECSortedChildren(): msousa@2662: Node_Configuration_Names.extend([(child.GetCurrentLocation(), child.GetConfigName())]) msousa@2662: return Node_Configuration_Names msousa@2662: msousa@1909: def CTNGenerate_C(self, buildpath, locations): Edouard@1918: # print "#############" Edouard@1918: # print self.__class__ Edouard@1918: # print type(self) Edouard@1918: # print "self.CTNType >>>" Edouard@1918: # print self.CTNType Edouard@1918: # print "type(self.CTNType) >>>" Edouard@1918: # print type(self.CTNType) Edouard@1918: # print "#############" Edouard@1918: Edouard@1919: loc_dict = {"locstr": "_".join(map(str, self.GetCurrentLocation()))} Edouard@1918: msousa@1909: # Determine the number of (modbus library) nodes ALL instances of the modbus plugin will need msousa@1909: # total_node_count: (tcp nodes, rtu nodes, ascii nodes) msousa@2663: # msousa@2663: # Also get a list with tuples of (location, IP address, port number) used by all the Modbus/IP server nodes msousa@1909: # This list is later used to search for duplicates in port numbers! msousa@2663: # IPServer_port_numbers = [(location, IP address, port number), ...] msousa@2663: # location : tuple similar to (0, 3, 1) representing the location in the configuration tree "0.3.1.x" msousa@2662: # IPserver_port_number: a number (i.e. port number used by the Modbus/IP server) msousa@2663: # IP address : IP address of the network interface on which the server will be listening msousa@2663: # ("", "*", or "#ANY#" => listening on all interfaces!) msousa@2663: # msousa@2662: # Also get a list with tuples of (location, Configuration_Name) used by all the Modbus nodes msousa@2662: # This list is later used to search for duplicates in Configuration Names! msousa@2662: # Node_Configuration_Names = [(location, Configuration_Name), ...] msousa@2663: # location : tuple similar to (0, 3, 1) representing the location in the configuration tree "0.3.1.x" msousa@2662: # Configuration_Name: the "Configuration_Name" string msousa@1909: total_node_count = (0, 0, 0) msousa@2662: IPServer_port_numbers = [] msousa@2662: Node_Configuration_Names = [] msousa@1909: for CTNInstance in self.GetCTRoot().IterChildren(): msousa@1909: if CTNInstance.CTNType == "modbus": msousa@2662: # ask each modbus plugin instance how many nodes it needs, and add them all up. msousa@2662: total_node_count = tuple(x1 + x2 for x1, x2 in zip(total_node_count, CTNInstance.GetNodeCount())) msousa@2662: IPServer_port_numbers. extend(CTNInstance.GetIPServerPortNumbers()) msousa@2662: Node_Configuration_Names.extend(CTNInstance.GetConfigNames ()) msousa@2662: msousa@2662: # Search for use of duplicate Configuration_Names by Modbus nodes msousa@2662: # Configuration Names are used by the web server running on the PLC msousa@2662: # (more precisely, run by Beremiz_service.py) to identify and allow msousa@2662: # changing the Modbus parameters after the program has been downloaded msousa@2662: # to the PLC (but before it is started) msousa@2662: # With clashes in the configuration names, the Modbus nodes will not be msousa@2662: # distinguasheble on the web interface! msousa@2662: for i in range(0, len(Node_Configuration_Names) - 1): msousa@2662: for j in range(i + 1, len(Node_Configuration_Names)): msousa@2662: if Node_Configuration_Names[i][1] == Node_Configuration_Names[j][1]: msousa@2662: error_message = _("Error: Modbus plugin nodes %{a1}.x and %{a2}.x use the same Configuration_Name \"{a3}\".\n").format( msousa@2662: a1=_lt_to_str(Node_Configuration_Names[i][0]), msousa@2662: a2=_lt_to_str(Node_Configuration_Names[j][0]), msousa@2662: a3=Node_Configuration_Names[j][1]) msousa@2662: self.FatalError(error_message) msousa@1909: msousa@1909: # Search for use of duplicate port numbers by Modbus/IP servers msousa@2663: # Note: We only consider duplicate port numbers if using the same network interface! msousa@2663: i = 0 msousa@2663: for loc1, addr1, port1 in IPServer_port_numbers[:-1]: msousa@2663: i = i + 1 msousa@2663: for loc2, addr2, port2 in IPServer_port_numbers[i:]: msousa@2663: if (port1 == port2) and ( msousa@2663: (addr1 == addr2) # on the same network interface msousa@2663: or (addr1 == "") or (addr1 == "*") or (addr1 == "#ANY#") # or one (or both) of the servers msousa@2663: or (addr2 == "") or (addr2 == "*") or (addr2 == "#ANY#") # use all available network interfaces msousa@2663: ): msousa@2663: error_message = _("Error: Modbus plugin nodes %{a1}.x and %{a2}.x use same port number \"{a3}\" " + msousa@2663: "on the same (or overlapping) network interfaces \"{a4}\" and \"{a5}\".\n").format( msousa@2663: a1=_lt_to_str(loc1), a2=_lt_to_str(loc2), a3=port1, a4=addr1, a5=addr2) msousa@2648: self.FatalError(error_message) Edouard@1918: Edouard@1918: # Determine the current location in Beremiz's project configuration Edouard@1918: # tree msousa@1909: current_location = self.GetCurrentLocation() Edouard@1918: msousa@1909: # define a unique name for the generated C and h files msousa@1909: prefix = "_".join(map(str, current_location)) Edouard@1918: Gen_MB_c_path = os.path.join(buildpath, "MB_%s.c" % prefix) Edouard@1918: Gen_MB_h_path = os.path.join(buildpath, "MB_%s.h" % prefix) Edouard@1918: c_filename = os.path.join(os.path.split(__file__)[0], "mb_runtime.c") Edouard@1918: h_filename = os.path.join(os.path.split(__file__)[0], "mb_runtime.h") msousa@1909: msousa@1909: tcpclient_reqs_count = 0 msousa@1909: rtuclient_reqs_count = 0 msousa@1909: ascclient_reqs_count = 0 msousa@1909: tcpclient_node_count = 0 msousa@1909: rtuclient_node_count = 0 msousa@1909: ascclient_node_count = 0 msousa@1909: tcpserver_node_count = 0 msousa@1909: rtuserver_node_count = 0 msousa@1909: ascserver_node_count = 0 msousa@1909: nodeid = 0 msousa@1909: client_nodeid = 0 msousa@1909: client_requestid = 0 msousa@1909: server_id = 0 Edouard@1918: msousa@1909: server_node_list = [] msousa@1909: client_node_list = [] msousa@1909: client_request_list = [] msousa@1909: server_memarea_list = [] msousa@1909: loc_vars = [] Edouard@1918: loc_vars_list = [] # list of variables already declared in C code! msousa@1909: for child in self.IECSortedChildren(): Edouard@1918: # print "<<<<<<<<<<<<<" Edouard@1918: # print "child (self.IECSortedChildren())----->" Edouard@1918: # print child.__class__ Edouard@1918: # print ">>>>>>>>>>>>>" Edouard@1918: # msousa@1909: if child.PlugType == "ModbusTCPserver": msousa@1909: tcpserver_node_count += 1 msousa@1909: new_node = GetTCPServerNodePrinted(self, child) msousa@1909: if new_node is None: Edouard@1918: return [], "", False msousa@2721: server_node_list.append(new_node) msousa@2722: # We currently add 4 flags/counters to each Modbus server/slave Edouard@1918: # msousa@2722: # We add the Modbus read/write counter/flag to each Modbus slave/server msousa@2722: # to allow the user program to determine if the slave is being actively msousa@2722: # read from or written by by a remote Modbus client. msousa@2721: for iecvar in child.GetLocations(): msousa@2721: #print "child" + repr(iecvar) msousa@2721: if (len(iecvar["LOC"]) == 3) and (str(iecvar["NAME"]) not in loc_vars_list): msousa@2721: # Add if it is a "Modbus Read Request Counter" (mapped onto %MDa.b.0), so last number is a '0' msousa@2721: if iecvar["LOC"][2] == 0: msousa@2721: loc_vars.append("u32 *" + str(iecvar["NAME"]) + " = &server_nodes[%d].mem_area.flag_read_req_counter;" % (server_id)) msousa@2721: loc_vars_list.append(str(iecvar["NAME"])) msousa@2721: # Add if it is a "Modbus Write Request Counter" (mapped onto %MDa.b.1), so last number is a '1' msousa@2721: if iecvar["LOC"][2] == 1: msousa@2721: loc_vars.append("u32 *" + str(iecvar["NAME"]) + " = &server_nodes[%d].mem_area.flag_write_req_counter;" % (server_id)) msousa@2721: loc_vars_list.append(str(iecvar["NAME"])) msousa@2722: # Add if it is a "Modbus Read Request Flag" (mapped onto %MDa.b.2), so last number is a '2' msousa@2722: if iecvar["LOC"][2] == 2: msousa@2722: loc_vars.append("u8 *" + str(iecvar["NAME"]) + " = &server_nodes[%d].mem_area.flag_read_req_flag;" % (server_id)) msousa@2722: loc_vars_list.append(str(iecvar["NAME"])) msousa@2722: # Add if it is a "Modbus Write Request Counter" (mapped onto %MDa.b.3), so last number is a '3' msousa@2722: if iecvar["LOC"][2] == 3: msousa@2722: loc_vars.append("u8 *" + str(iecvar["NAME"]) + " = &server_nodes[%d].mem_area.flag_write_req_flag;" % (server_id)) msousa@2722: loc_vars_list.append(str(iecvar["NAME"])) msousa@2721: msousa@1909: for subchild in child.IECSortedChildren(): msousa@2721: new_memarea = GetTCPServerMemAreaPrinted(self, subchild, nodeid) msousa@1909: if new_memarea is None: Edouard@1918: return [], "", False msousa@1909: server_memarea_list.append(new_memarea) msousa@2721: function = subchild.GetParamsAttributes()[0]["children"][0]["value"] msousa@1909: # 'ro_bits', 'rw_bits', 'ro_words' or 'rw_words' Edouard@1918: memarea = modbus_memtype_dict[function][1] msousa@1909: for iecvar in subchild.GetLocations(): msousa@2721: if len(iecvar["LOC"]) == 4: msousa@2721: #print "subchild" + repr(iecvar) msousa@2721: absloute_address = iecvar["LOC"][3] msousa@2721: start_address = int(GetCTVal(subchild, 2)) msousa@2721: relative_addr = absloute_address - start_address msousa@2721: # test if relative address in request specified range msousa@2721: if relative_addr in xrange(int(GetCTVal(subchild, 1))): msousa@2721: if str(iecvar["NAME"]) not in loc_vars_list: msousa@2721: loc_vars.append("u16 *" + str(iecvar["NAME"]) + " = &server_nodes[%d].mem_area.%s[%d];" % ( msousa@2721: server_id, memarea, absloute_address)) msousa@2721: loc_vars_list.append(str(iecvar["NAME"])) msousa@1909: server_id += 1 Edouard@1918: # msousa@1909: if child.PlugType == "ModbusRTUslave": msousa@1909: rtuserver_node_count += 1 msousa@1909: new_node = GetRTUSlaveNodePrinted(self, child) msousa@1909: if new_node is None: Edouard@1918: return [], "", False msousa@1909: server_node_list.append(new_node) msousa@2722: # We currently add 4 flags/counters to each Modbus server/slave Edouard@1918: # msousa@2722: # We add the Modbus read/write counter/flag to each Modbus slave/server msousa@2722: # to allow the user program to determine if the slave is being actively msousa@2722: # read from or written by by a remote Modbus client. msousa@2722: for iecvar in child.GetLocations(): msousa@2722: #print "child" + repr(iecvar) msousa@2722: if (len(iecvar["LOC"]) == 3) and (str(iecvar["NAME"]) not in loc_vars_list): msousa@2722: # Add if it is a "Modbus Read Request Counter" (mapped onto %MDa.b.0), so last number is a '0' msousa@2722: if iecvar["LOC"][2] == 0: msousa@2722: loc_vars.append("u32 *" + str(iecvar["NAME"]) + " = &server_nodes[%d].mem_area.flag_read_req_counter;" % (server_id)) msousa@2722: loc_vars_list.append(str(iecvar["NAME"])) msousa@2722: # Add if it is a "Modbus Write Request Counter" (mapped onto %MDa.b.1), so last number is a '1' msousa@2722: if iecvar["LOC"][2] == 1: msousa@2722: loc_vars.append("u32 *" + str(iecvar["NAME"]) + " = &server_nodes[%d].mem_area.flag_write_req_counter;" % (server_id)) msousa@2722: loc_vars_list.append(str(iecvar["NAME"])) msousa@2722: # Add if it is a "Modbus Read Request Flag" (mapped onto %MDa.b.2), so last number is a '2' msousa@2722: if iecvar["LOC"][2] == 2: msousa@2722: loc_vars.append("u8 *" + str(iecvar["NAME"]) + " = &server_nodes[%d].mem_area.flag_read_req_flag;" % (server_id)) msousa@2722: loc_vars_list.append(str(iecvar["NAME"])) msousa@2722: # Add if it is a "Modbus Write Request Counter" (mapped onto %MDa.b.3), so last number is a '3' msousa@2722: if iecvar["LOC"][2] == 3: msousa@2722: loc_vars.append("u8 *" + str(iecvar["NAME"]) + " = &server_nodes[%d].mem_area.flag_write_req_flag;" % (server_id)) msousa@2722: loc_vars_list.append(str(iecvar["NAME"])) msousa@2722: msousa@1909: for subchild in child.IECSortedChildren(): Edouard@1918: new_memarea = GetTCPServerMemAreaPrinted( Edouard@1918: self, subchild, nodeid) msousa@1909: if new_memarea is None: Edouard@1918: return [], "", False msousa@1909: server_memarea_list.append(new_memarea) msousa@2721: function = subchild.GetParamsAttributes()[0]["children"][0]["value"] msousa@1909: # 'ro_bits', 'rw_bits', 'ro_words' or 'rw_words' Edouard@1918: memarea = modbus_memtype_dict[function][1] msousa@1909: for iecvar in subchild.GetLocations(): msousa@2721: if len(iecvar["LOC"]) == 4: msousa@2721: # print repr(iecvar) msousa@2721: absloute_address = iecvar["LOC"][3] msousa@2721: start_address = int(GetCTVal(subchild, 2)) msousa@2721: relative_addr = absloute_address - start_address msousa@2721: # test if relative address in request specified range msousa@2721: if relative_addr in xrange(int(GetCTVal(subchild, 1))): msousa@2721: if str(iecvar["NAME"]) not in loc_vars_list: msousa@2721: loc_vars.append("u16 *" + str(iecvar["NAME"]) + " = &server_nodes[%d].mem_area.%s[%d];" % ( msousa@2721: server_id, memarea, absloute_address)) msousa@2721: loc_vars_list.append(str(iecvar["NAME"])) msousa@1909: server_id += 1 Edouard@1918: # msousa@1909: if child.PlugType == "ModbusTCPclient": msousa@1909: tcpclient_reqs_count += len(child.IECSortedChildren()) msousa@1909: new_node = GetTCPClientNodePrinted(self, child) msousa@1909: if new_node is None: Edouard@1918: return [], "", False msousa@1909: client_node_list.append(new_node) msousa@1909: for subchild in child.IECSortedChildren(): Edouard@1918: new_req = GetClientRequestPrinted( Edouard@1918: self, subchild, client_nodeid) msousa@1909: if new_req is None: Edouard@1918: return [], "", False msousa@1909: client_request_list.append(new_req) msousa@1909: for iecvar in subchild.GetLocations(): Edouard@1918: # absloute address - start address Edouard@1919: relative_addr = iecvar["LOC"][3] - int(GetCTVal(subchild, 3)) msousa@2647: # test if the located variable msousa@2647: # (a) has relative address in request specified range msousa@2647: # AND is NOT msousa@2717: # (b) is a flag added by this modbus plugin. msousa@2717: # We currently add 3 flags: An execution control flag msousa@2717: # and another two status flags. msousa@2717: # We add the "Execution Control Flag" to each client request (one flag per request) msousa@2717: # to allow the user program to control when to execute the request (if not executed periodically) msousa@2647: # While all Modbus registers/coils are mapped onto a location msousa@2647: # with 4 numbers (e.g. %QX0.1.2.55), this control flag is mapped msousa@2647: # onto a location with 4 numbers (e.g. %QX0.1.2.0.0), where the last msousa@2647: # two numbers are always '0.0', and the first two identify the request. msousa@2647: # In the following if, we check for this condition by checking msousa@2717: # if there are at least 4 or more number in the location's address. msousa@2647: if ( relative_addr in xrange(int(GetCTVal(subchild, 2))) # condition (a) explained above msousa@2647: and len(iecvar["LOC"]) < 5): # condition (b) explained above msousa@1909: if str(iecvar["NAME"]) not in loc_vars_list: msousa@2713: loc_vars.append("u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].plcv_buffer[%d];" % (client_requestid, relative_addr)) msousa@1909: loc_vars_list.append(str(iecvar["NAME"])) msousa@2647: # Now add the located variable in case it is a flag (condition (b) above msousa@2647: if len(iecvar["LOC"]) >= 5: # condition (b) explained above msousa@2647: if str(iecvar["NAME"]) not in loc_vars_list: msousa@2713: # Add if it is a Execution Request Flag (mapped onto %QXa.b.c.0.0), so last number is a '0' msousa@2713: if iecvar["LOC"][4] == 0: msousa@2715: loc_vars.append("u8 *" + str(iecvar["NAME"]) + " = &client_requests[%d].flag_exec_req;" % (client_requestid)) msousa@2713: loc_vars_list.append(str(iecvar["NAME"])) msousa@2714: # Add if it is a "Modbus Request Status flag" (mapped onto %QWa.b.c.0.1), so last number is a '1' msousa@2717: # -> will store the result of the last executed MB transaction msousa@2717: # 1 -> error accessing IP network, or serial interface msousa@2717: # 2 -> reply received from server was an invalid frame msousa@2717: # 3 -> server did not reply before timeout expired msousa@2717: # 4 -> server returned a valid Modbus error frame msousa@2717: # -> will be reset (set to 0) once this MB transaction has completed sucesfully msousa@2713: if iecvar["LOC"][4] == 1: msousa@2714: loc_vars.append("u8 *" + str(iecvar["NAME"]) + " = &client_requests[%d].flag_tn_error_code;" % (client_requestid)) msousa@2714: loc_vars_list.append(str(iecvar["NAME"])) msousa@2714: # Add if it is a "Modbus Error code" (mapped onto %QWa.b.c.0.2), so last number is a '2' msousa@2717: # -> if "Modbus Request Status flag" is 4, this flag will store the MB error code returned by the MB server in a MB error frame msousa@2717: # -> will be reset (set to 0) once this MB transaction has completed succesfully msousa@2714: if iecvar["LOC"][4] == 2: msousa@2714: loc_vars.append("u8 *" + str(iecvar["NAME"]) + " = &client_requests[%d].flag_mb_error_code;" % (client_requestid)) msousa@2713: loc_vars_list.append(str(iecvar["NAME"])) msousa@1909: client_requestid += 1 msousa@1909: tcpclient_node_count += 1 msousa@1909: client_nodeid += 1 Edouard@1918: # msousa@1909: if child.PlugType == "ModbusRTUclient": msousa@1909: rtuclient_reqs_count += len(child.IECSortedChildren()) msousa@1909: new_node = GetRTUClientNodePrinted(self, child) msousa@1909: if new_node is None: Edouard@1918: return [], "", False msousa@1909: client_node_list.append(new_node) msousa@1909: for subchild in child.IECSortedChildren(): Edouard@1918: new_req = GetClientRequestPrinted( Edouard@1918: self, subchild, client_nodeid) msousa@1909: if new_req is None: Edouard@1918: return [], "", False msousa@1909: client_request_list.append(new_req) msousa@1909: for iecvar in subchild.GetLocations(): Edouard@1918: # absloute address - start address Edouard@1919: relative_addr = iecvar["LOC"][3] - int(GetCTVal(subchild, 3)) msousa@2647: # test if the located variable msousa@2647: # (a) has relative address in request specified range msousa@2647: # AND is NOT msousa@2717: # (b) is a flag added by this modbus plugin. msousa@2717: # We currently add 3 flags: An execution control flag msousa@2717: # and another two status flags. msousa@2717: # We add the "Execution Control Flag" to each client request (one flag per request) msousa@2717: # to allow the user program to control when to execute the request (if not executed periodically) msousa@2647: # While all Modbus registers/coils are mapped onto a location msousa@2647: # with 4 numbers (e.g. %QX0.1.2.55), this control flag is mapped msousa@2647: # onto a location with 4 numbers (e.g. %QX0.1.2.0.0), where the last msousa@2647: # two numbers are always '0.0', and the first two identify the request. msousa@2647: # In the following if, we check for this condition by checking msousa@2713: # if there are at least 4 or more number in the location's address. msousa@2647: if ( relative_addr in xrange(int(GetCTVal(subchild, 2))) # condition (a) explained above msousa@2647: and len(iecvar["LOC"]) < 5): # condition (b) explained above msousa@1909: if str(iecvar["NAME"]) not in loc_vars_list: Edouard@1918: loc_vars.append( Edouard@1918: "u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].plcv_buffer[%d];" % (client_requestid, relative_addr)) msousa@1909: loc_vars_list.append(str(iecvar["NAME"])) msousa@2647: # Now add the located variable in case it is a flag (condition (b) above msousa@2647: if len(iecvar["LOC"]) >= 5: # condition (b) explained above msousa@2647: if str(iecvar["NAME"]) not in loc_vars_list: msousa@2713: # Add if it is a Execution Request Flag (mapped onto %QXa.b.c.0.0), so last number is a '0' msousa@2713: if iecvar["LOC"][4] == 0: msousa@2715: loc_vars.append("u8 *" + str(iecvar["NAME"]) + " = &client_requests[%d].flag_exec_req;" % (client_requestid)) msousa@2713: loc_vars_list.append(str(iecvar["NAME"])) msousa@2714: # Add if it is a "Modbus Request Status flag" (mapped onto %QWa.b.c.0.1), so last number is a '1' msousa@2717: # -> will store the result of the last executed MB transaction msousa@2717: # 1 -> error accessing IP network, or serial interface msousa@2717: # 2 -> reply received from server was an invalid frame msousa@2717: # 3 -> server did not reply before timeout expired msousa@2717: # 4 -> server returned a valid Modbus error frame msousa@2717: # -> will be reset (set to 0) once this MB transaction has completed sucesfully msousa@2713: if iecvar["LOC"][4] == 1: msousa@2714: loc_vars.append("u8 *" + str(iecvar["NAME"]) + " = &client_requests[%d].flag_tn_error_code;" % (client_requestid)) msousa@2714: loc_vars_list.append(str(iecvar["NAME"])) msousa@2714: # Add if it is a "Modbus Error code" (mapped onto %QWa.b.c.0.2), so last number is a '2' msousa@2717: # -> if "Modbus Request Status flag" is 4, this flag will store the MB error code returned by the MB server in a MB error frame msousa@2717: # -> will be reset (set to 0) once this MB transaction has completed succesfully msousa@2714: if iecvar["LOC"][4] == 2: msousa@2714: loc_vars.append("u8 *" + str(iecvar["NAME"]) + " = &client_requests[%d].flag_mb_error_code;" % (client_requestid)) msousa@2713: loc_vars_list.append(str(iecvar["NAME"])) msousa@1909: client_requestid += 1 msousa@1909: rtuclient_node_count += 1 msousa@1909: client_nodeid += 1 msousa@1909: nodeid += 1 Edouard@1918: Edouard@1918: loc_dict["loc_vars"] = "\n".join(loc_vars) Edouard@1918: loc_dict["server_nodes_params"] = ",\n\n".join(server_node_list) Edouard@1918: loc_dict["client_nodes_params"] = ",\n\n".join(client_node_list) Edouard@1918: loc_dict["client_req_params"] = ",\n\n".join(client_request_list) msousa@1909: loc_dict["tcpclient_reqs_count"] = str(tcpclient_reqs_count) msousa@1909: loc_dict["tcpclient_node_count"] = str(tcpclient_node_count) msousa@1909: loc_dict["tcpserver_node_count"] = str(tcpserver_node_count) msousa@1909: loc_dict["rtuclient_reqs_count"] = str(rtuclient_reqs_count) msousa@1909: loc_dict["rtuclient_node_count"] = str(rtuclient_node_count) msousa@1909: loc_dict["rtuserver_node_count"] = str(rtuserver_node_count) msousa@1909: loc_dict["ascclient_reqs_count"] = str(ascclient_reqs_count) msousa@1909: loc_dict["ascclient_node_count"] = str(ascclient_node_count) msousa@1909: loc_dict["ascserver_node_count"] = str(ascserver_node_count) Edouard@1918: loc_dict["total_tcpnode_count"] = str(total_node_count[0]) Edouard@1918: loc_dict["total_rtunode_count"] = str(total_node_count[1]) Edouard@1918: loc_dict["total_ascnode_count"] = str(total_node_count[2]) Edouard@1918: loc_dict["max_remote_tcpclient"] = int( Edouard@1918: self.GetParamsAttributes()[0]["children"][0]["value"]) Edouard@1918: Edouard@1918: # get template file content into a string, format it with dict Edouard@1918: # and write it to proper .h file msousa@1909: mb_main = open(h_filename).read() % loc_dict Edouard@1918: f = open(Gen_MB_h_path, 'w') msousa@1909: f.write(mb_main) msousa@1909: f.close() Edouard@1918: # same thing as above, but now to .c file msousa@1909: mb_main = open(c_filename).read() % loc_dict Edouard@1918: f = open(Gen_MB_c_path, 'w') msousa@1909: f.write(mb_main) msousa@1909: f.close() msousa@1909: msousa@1909: LDFLAGS = [] msousa@1909: LDFLAGS.append(" \"-L" + ModbusPath + "\"") Edouard@2333: LDFLAGS.append(" \"" + os.path.join(ModbusPath, "libmb.a") + "\"") msousa@1909: LDFLAGS.append(" \"-Wl,-rpath," + ModbusPath + "\"") Edouard@1918: # LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_slave_and_master.o") + "\"") Edouard@1918: # LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_slave.o") + "\"") Edouard@1918: # LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_master.o") + "\"") Edouard@1918: # LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_tcp.o") + "\"") Edouard@1918: # LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_rtu.o") + "\"") Edouard@1918: # LDFLAGS.append("\"" + os.path.join(ModbusPath, "mb_ascii.o") + "\"") Edouard@1918: # LDFLAGS.append("\"" + os.path.join(ModbusPath, "sin_util.o") + "\"") Edouard@1913: # Target is ARM with linux and not win on x86 so winsock2 (ws2_32) library is useless !!! Edouard@1918: # if os.name == 'nt': # other possible values: 'posix' 'os2' 'ce' 'java' 'riscos' Edouard@1918: # LDFLAGS.append(" -lws2_32 ") # on windows we need to load winsock Edouard@1918: # library! Edouard@1918: Edouard@2669: websettingfile = open(paths.AbsNeighbourFile(__file__, "web_settings.py"), 'r') Edouard@2669: websettingcode = websettingfile.read() Edouard@2669: websettingfile.close() Edouard@2669: Edouard@2669: location_str = "_".join(map(str, self.GetCurrentLocation())) Edouard@2669: websettingcode = websettingcode % locals() Edouard@2669: Edouard@2669: runtimefile_path = os.path.join(buildpath, "runtime_modbus_websettings.py") Edouard@2669: runtimefile = open(runtimefile_path, 'w') Edouard@2669: runtimefile.write(websettingcode) Edouard@2669: runtimefile.close() Edouard@2669: Edouard@2669: return ([(Gen_MB_c_path, ' -I"' + ModbusPath + '"')], LDFLAGS, True, Edouard@2703: ("runtime_%s_modbus_websettings.py" % location_str, open(runtimefile_path, "rb")), Edouard@2669: )