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: 
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@1918: base_folder = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]
Edouard@1918: base_folder = os.path.join(base_folder, "..")
Edouard@1918: ModbusPath = os.path.join(base_folder, "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 = """<?xml version="1.0" encoding="ISO-8859-1" ?>
msousa@1909:     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
msousa@1909:       <xsd:element name="ModbusRequest">
msousa@1909:         <xsd:complexType>
msousa@1909:           <xsd:attribute name="Function" type="xsd:string" use="optional" default="01 - Read Coils"/>
msousa@1909:           <xsd:attribute name="SlaveID" use="optional" default="1">
msousa@1909:             <xsd:simpleType>
msousa@1909:                 <xsd:restriction base="xsd:integer">
msousa@1909:                     <xsd:minInclusive value="0"/>
msousa@1909:                     <xsd:maxInclusive value="255"/>
msousa@1909:                 </xsd:restriction>
msousa@1909:             </xsd:simpleType>
msousa@1909:           </xsd:attribute>
msousa@1909:           <xsd:attribute name="Nr_of_Channels" use="optional" default="1">
msousa@1909:             <xsd:simpleType>
msousa@1909:                 <xsd:restriction base="xsd:integer">
msousa@1909:                     <xsd:minInclusive value="1"/>
msousa@1909:                     <xsd:maxInclusive value="2000"/>
msousa@1909:                 </xsd:restriction>
msousa@1909:             </xsd:simpleType>
msousa@1909:           </xsd:attribute>
msousa@1909:           <xsd:attribute name="Start_Address" use="optional" default="0">
msousa@1909:             <xsd:simpleType>
msousa@1909:                 <xsd:restriction base="xsd:integer">
msousa@1909:                     <xsd:minInclusive value="0"/>
msousa@1909:                     <xsd:maxInclusive value="65535"/>
msousa@1909:                 </xsd:restriction>
msousa@1909:             </xsd:simpleType>
msousa@1909:           </xsd:attribute>
msousa@1909:           <xsd:attribute name="Timeout_in_ms" use="optional" default="10">
msousa@1909:             <xsd:simpleType>
msousa@1909:                 <xsd:restriction base="xsd:integer">
msousa@1909:                     <xsd:minInclusive value="1"/>
msousa@1909:                     <xsd:maxInclusive value="100000"/>
msousa@1909:                 </xsd:restriction>
msousa@1909:             </xsd:simpleType>
msousa@1909:           </xsd:attribute>
msousa@2647:           <xsd:attribute name="Write_on_change" type="xsd:boolean" use="optional" default="false"/>
msousa@1909:         </xsd:complexType>
msousa@1909:       </xsd:element>
msousa@1909:     </xsd:schema>
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 = """<?xml version="1.0" encoding="ISO-8859-1" ?>
msousa@1909:     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
msousa@1909:       <xsd:element name="MemoryArea">
msousa@1909:         <xsd:complexType>
msousa@1909:           <xsd:attribute name="MemoryAreaType" type="xsd:string" use="optional" default="01 - Coils"/>
msousa@1909:           <xsd:attribute name="Nr_of_Channels" use="optional" default="1">
msousa@1909:             <xsd:simpleType>
msousa@1909:                 <xsd:restriction base="xsd:integer">
msousa@1909:                     <xsd:minInclusive value="1"/>
msousa@1909:                     <xsd:maxInclusive value="65536"/>
msousa@1909:                 </xsd:restriction>
msousa@1909:             </xsd:simpleType>
msousa@1909:           </xsd:attribute>
msousa@1909:           <xsd:attribute name="Start_Address" use="optional" default="0">
msousa@1909:             <xsd:simpleType>
msousa@1909:                 <xsd:restriction base="xsd:integer">
msousa@1909:                     <xsd:minInclusive value="0"/>
msousa@1909:                     <xsd:maxInclusive value="65535"/>
msousa@1909:                 </xsd:restriction>
msousa@1909:             </xsd:simpleType>
msousa@1909:           </xsd:attribute>
msousa@1909:         </xsd:complexType>
msousa@1909:       </xsd:element>
msousa@1909:     </xsd:schema>
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 = """<?xml version="1.0" encoding="ISO-8859-1" ?>
msousa@1909:     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
msousa@1909:       <xsd:element name="ModbusTCPclient">
msousa@1909:         <xsd:complexType>
msousa@2654:           <xsd:attribute name="Configuration_Name" type="xsd:string" use="optional" default=""/>
msousa@1909:           <xsd:attribute name="Remote_IP_Address" type="xsd:string" use="optional" default="localhost"/>
msousa@1909:           <xsd:attribute name="Remote_Port_Number" type="xsd:string" use="optional" default="502"/>
msousa@1909:           <xsd:attribute name="Invocation_Rate_in_ms" use="optional" default="100">
msousa@1909:             <xsd:simpleType>
msousa@1909:                 <xsd:restriction base="xsd:unsignedLong">
msousa@2647:                     <xsd:minInclusive value="0"/>
msousa@1909:                     <xsd:maxInclusive value="2147483647"/>
msousa@1909:                 </xsd:restriction>
msousa@1909:             </xsd:simpleType>
msousa@1909:           </xsd:attribute>
msousa@1909:         </xsd:complexType>
msousa@1909:       </xsd:element>
msousa@1909:     </xsd:schema>
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 = """<?xml version="1.0" encoding="ISO-8859-1" ?>
msousa@1909:     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
msousa@1909:       <xsd:element name="ModbusServerNode">
msousa@1909:         <xsd:complexType>
msousa@2654:           <xsd:attribute name="Configuration_Name" type="xsd:string" use="optional" default=""/>
msousa@1909:           <xsd:attribute name="Local_IP_Address" type="xsd:string" use="optional"  default="#ANY#"/>
msousa@1909:           <xsd:attribute name="Local_Port_Number" type="xsd:string" use="optional" default="502"/>
msousa@1909:           <xsd:attribute name="SlaveID" use="optional" default="0">
msousa@1909:             <xsd:simpleType>
msousa@1909:                 <xsd:restriction base="xsd:integer">
msousa@1909:                     <xsd:minInclusive value="0"/>
msousa@1909:                     <xsd:maxInclusive value="255"/>
msousa@1909:                 </xsd:restriction>
msousa@1909:             </xsd:simpleType>
msousa@1909:           </xsd:attribute>
msousa@1909:         </xsd:complexType>
msousa@1909:       </xsd:element>
msousa@1909:     </xsd:schema>
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 = """<?xml version="1.0" encoding="ISO-8859-1" ?>
msousa@1909:     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
msousa@1909:       <xsd:element name="ModbusRTUclient">
msousa@1909:         <xsd:complexType>
msousa@2654:           <xsd:attribute name="Configuration_Name" type="xsd:string" use="optional" default=""/>
msousa@1909:           <xsd:attribute name="Serial_Port" type="xsd:string"  use="optional" default="/dev/ttyS0"/>
msousa@1909:           <xsd:attribute name="Baud_Rate"   type="xsd:string"  use="optional" default="9600"/>
msousa@1909:           <xsd:attribute name="Parity"      type="xsd:string"  use="optional" default="even"/>
msousa@1909:           <xsd:attribute name="Stop_Bits"   type="xsd:string"  use="optional" default="1"/>
msousa@1909:           <xsd:attribute name="Invocation_Rate_in_ms" use="optional" default="100">
msousa@1909:             <xsd:simpleType>
msousa@1909:                 <xsd:restriction base="xsd:integer">
msousa@2647:                     <xsd:minInclusive value="0"/>
msousa@1909:                     <xsd:maxInclusive value="2147483647"/>
msousa@1909:                 </xsd:restriction>
msousa@1909:             </xsd:simpleType>
msousa@1909:           </xsd:attribute>
msousa@1909:         </xsd:complexType>
msousa@1909:       </xsd:element>
msousa@1909:     </xsd:schema>
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 = """<?xml version="1.0" encoding="ISO-8859-1" ?>
msousa@1909:     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
msousa@1909:       <xsd:element name="ModbusRTUslave">
msousa@1909:         <xsd:complexType>
msousa@2654:           <xsd:attribute name="Configuration_Name" type="xsd:string" use="optional" default=""/>
msousa@1909:           <xsd:attribute name="Serial_Port" type="xsd:string"  use="optional" default="/dev/ttyS0"/>
msousa@1909:           <xsd:attribute name="Baud_Rate"   type="xsd:string"  use="optional" default="9600"/>
msousa@1909:           <xsd:attribute name="Parity"      type="xsd:string"  use="optional" default="even"/>
msousa@1909:           <xsd:attribute name="Stop_Bits"   type="xsd:string"  use="optional" default="1"/>
msousa@1909:           <xsd:attribute name="SlaveID" use="optional" default="1">
msousa@1909:             <xsd:simpleType>
msousa@1909:                 <xsd:restriction base="xsd:integer">
msousa@1909:                     <xsd:minInclusive value="1"/>
msousa@1909:                     <xsd:maxInclusive value="255"/>
msousa@1909:                 </xsd:restriction>
msousa@1909:             </xsd:simpleType>
msousa@1909:           </xsd:attribute>
msousa@1909:         </xsd:complexType>
msousa@1909:       </xsd:element>
msousa@1909:     </xsd:schema>
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 = """<?xml version="1.0" encoding="ISO-8859-1" ?>
msousa@1909:     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
msousa@1909:       <xsd:element name="ModbusRoot">
msousa@1909:         <xsd:complexType>
msousa@1909:           <xsd:attribute name="MaxRemoteTCPclients" use="optional" default="10">
msousa@1909:             <xsd:simpleType>
msousa@1909:                 <xsd:restriction base="xsd:integer">
msousa@1909:                     <xsd:minInclusive value="0"/>
msousa@1909:                     <xsd:maxInclusive value="65535"/>
msousa@1909:                 </xsd:restriction>
msousa@1909:             </xsd:simpleType>
msousa@1909:           </xsd:attribute>
msousa@1909:         </xsd:complexType>
msousa@1909:       </xsd:element>
msousa@1909:     </xsd:schema>
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:         )