modbus/modbus.py
branchsvghmi
changeset 2982 1627d552f181
parent 2677 556935640ec0
child 2703 32ffdb32b14e
child 2716 ebb2595504f0
equal deleted inserted replaced
2981:a0932a52e53b 2982:1627d552f181
    28 from six.moves import xrange
    28 from six.moves import xrange
    29 
    29 
    30 from modbus.mb_utils import *
    30 from modbus.mb_utils import *
    31 from ConfigTreeNode import ConfigTreeNode
    31 from ConfigTreeNode import ConfigTreeNode
    32 from PLCControler import LOCATION_CONFNODE, LOCATION_VAR_MEMORY
    32 from PLCControler import LOCATION_CONFNODE, LOCATION_VAR_MEMORY
       
    33 import util.paths as paths
    33 
    34 
    34 base_folder = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]
    35 base_folder = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]
    35 base_folder = os.path.join(base_folder, "..")
    36 base_folder = os.path.join(base_folder, "..")
    36 ModbusPath = os.path.join(base_folder, "Modbus")
    37 ModbusPath = os.path.join(base_folder, "Modbus")
    37 
    38 
    81                     <xsd:minInclusive value="1"/>
    82                     <xsd:minInclusive value="1"/>
    82                     <xsd:maxInclusive value="100000"/>
    83                     <xsd:maxInclusive value="100000"/>
    83                 </xsd:restriction>
    84                 </xsd:restriction>
    84             </xsd:simpleType>
    85             </xsd:simpleType>
    85           </xsd:attribute>
    86           </xsd:attribute>
       
    87           <xsd:attribute name="Write_on_change" type="xsd:boolean" use="optional" default="false"/>
    86         </xsd:complexType>
    88         </xsd:complexType>
    87       </xsd:element>
    89       </xsd:element>
    88     </xsd:schema>
    90     </xsd:schema>
    89     """
    91     """
    90 
    92 
   113         # datazone = modbus_function_dict[function][5]
   115         # datazone = modbus_function_dict[function][5]
   114         # 'X' for bits, 'W' for words
   116         # 'X' for bits, 'W' for words
   115         datatacc = modbus_function_dict[function][6]
   117         datatacc = modbus_function_dict[function][6]
   116         # 'Coil', 'Holding Register', 'Input Discrete' or 'Input Register'
   118         # 'Coil', 'Holding Register', 'Input Discrete' or 'Input Register'
   117         dataname = modbus_function_dict[function][7]
   119         dataname = modbus_function_dict[function][7]
       
   120         # start off with a boolean entry
       
   121         # This is a flag used to allow the user program to control when to 
       
   122         # execute the Modbus request.
       
   123         # NOTE: If the Modbus request has a 'current_location' of
       
   124         #          %QX1.2.3
       
   125         #       then the execution control flag will be
       
   126         #          %QX1.2.3.0.0
       
   127         #       and all the Modbus registers/coils will be
       
   128         #          %QX1.2.3.0
       
   129         #          %QX1.2.3.1
       
   130         #          %QX1.2.3.2
       
   131         #            ..
       
   132         #          %QX1.2.3.n
   118         entries = []
   133         entries = []
       
   134         entries.append({
       
   135             "name": "Exec. request flag",
       
   136             "type": LOCATION_VAR_MEMORY,
       
   137             "size": 1,           # BOOL flag
       
   138             "IEC_type": "BOOL",  # BOOL flag
       
   139             "var_name": "var_name",
       
   140             "location": "X" + ".".join([str(i) for i in current_location]) + ".0.0",
       
   141             "description": "MB request execution control flag",
       
   142             "children": []})        
   119         for offset in range(address, address + count):
   143         for offset in range(address, address + count):
   120             entries.append({
   144             entries.append({
   121                 "name": dataname + " " + str(offset),
   145                 "name": dataname + " " + str(offset),
   122                 "type": LOCATION_VAR_MEMORY,
   146                 "type": LOCATION_VAR_MEMORY,
   123                 "size": datasize,
   147                 "size": datasize,
   258 # T C P    C L I E N T                 #
   282 # T C P    C L I E N T                 #
   259 #
   283 #
   260 #
   284 #
   261 #
   285 #
   262 
   286 
       
   287 # XXX TODO "Configuration_Name" should disapear in favor of CTN Name, which is already unique
       
   288 
   263 class _ModbusTCPclientPlug(object):
   289 class _ModbusTCPclientPlug(object):
   264     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
   290     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
   265     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   291     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   266       <xsd:element name="ModbusTCPclient">
   292       <xsd:element name="ModbusTCPclient">
   267         <xsd:complexType>
   293         <xsd:complexType>
       
   294           <xsd:attribute name="Configuration_Name" type="xsd:string" use="optional" default=""/>
   268           <xsd:attribute name="Remote_IP_Address" type="xsd:string" use="optional" default="localhost"/>
   295           <xsd:attribute name="Remote_IP_Address" type="xsd:string" use="optional" default="localhost"/>
   269           <xsd:attribute name="Remote_Port_Number" type="xsd:string" use="optional" default="502"/>
   296           <xsd:attribute name="Remote_Port_Number" type="xsd:string" use="optional" default="502"/>
   270           <xsd:attribute name="Invocation_Rate_in_ms" use="optional" default="100">
   297           <xsd:attribute name="Invocation_Rate_in_ms" use="optional" default="100">
   271             <xsd:simpleType>
   298             <xsd:simpleType>
   272                 <xsd:restriction base="xsd:unsignedLong">
   299                 <xsd:restriction base="xsd:unsignedLong">
   273                     <xsd:minInclusive value="1"/>
   300                     <xsd:minInclusive value="0"/>
   274                     <xsd:maxInclusive value="2147483647"/>
   301                     <xsd:maxInclusive value="2147483647"/>
   275                 </xsd:restriction>
   302                 </xsd:restriction>
   276             </xsd:simpleType>
   303             </xsd:simpleType>
   277           </xsd:attribute>
   304           </xsd:attribute>
   278         </xsd:complexType>
   305         </xsd:complexType>
   283     # corresponds to aprox 25 days.
   310     # corresponds to aprox 25 days.
   284     CTNChildrenTypes = [("ModbusRequest", _RequestPlug, "Request")]
   311     CTNChildrenTypes = [("ModbusRequest", _RequestPlug, "Request")]
   285     # TODO: Replace with CTNType !!!
   312     # TODO: Replace with CTNType !!!
   286     PlugType = "ModbusTCPclient"
   313     PlugType = "ModbusTCPclient"
   287 
   314 
       
   315 
       
   316     def __init__(self):
       
   317         # NOTE:
       
   318         # The ModbusTCPclient attribute is added dynamically by ConfigTreeNode._AddParamsMembers()
       
   319         # It will be an XML parser object created by
       
   320         # GenerateParserFromXSDstring(self.XSD).CreateRoot()
       
   321         
       
   322         # Set the default value for the "Configuration_Name" parameter
       
   323         # The default value will need to be different for each instance of the 
       
   324         # _ModbusTCPclientPlug class, so we cannot hardcode the default value in the XSD above
       
   325         # This value will be used by the web interface 
       
   326         #   (i.e. the extension to the web server used to configure the Modbus parameters).
       
   327         #   (The web server is run/activated/started by Beremiz_service.py)
       
   328         #   (The web server code is found in runtime/NevowServer.py)
       
   329         #   (The Modbus extension to the web server is found in runtime/Modbus_config.py)
       
   330         loc_str = ".".join(map(str, self.GetCurrentLocation()))
       
   331         self.ModbusTCPclient.setConfiguration_Name("Modbus TCP Client " + loc_str)
       
   332         
   288     # Return the number of (modbus library) nodes this specific TCP client will need
   333     # Return the number of (modbus library) nodes this specific TCP client will need
   289     #   return type: (tcp nodes, rtu nodes, ascii nodes)
   334     #   return type: (tcp nodes, rtu nodes, ascii nodes)
   290     def GetNodeCount(self):
   335     def GetNodeCount(self):
   291         return (1, 0, 0)
   336         return (1, 0, 0)
       
   337 
       
   338     def GetConfigName(self):
       
   339         """ Return the node's Configuration_Name """
       
   340         return self.ModbusTCPclient.getConfiguration_Name()
   292 
   341 
   293     def CTNGenerate_C(self, buildpath, locations):
   342     def CTNGenerate_C(self, buildpath, locations):
   294         """
   343         """
   295         Generate C code
   344         Generate C code
   296         @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5)
   345         @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5)
   312 # T C P    S E R V E R                 #
   361 # T C P    S E R V E R                 #
   313 #
   362 #
   314 #
   363 #
   315 #
   364 #
   316 
   365 
       
   366 # XXX TODO "Configuration_Name" should disapear in favor of CTN Name, which is already unique
       
   367 
   317 class _ModbusTCPserverPlug(object):
   368 class _ModbusTCPserverPlug(object):
   318     # NOTE: the Port number is a 'string' and not an 'integer'!
   369     # NOTE: the Port number is a 'string' and not an 'integer'!
   319     # This is because the underlying modbus library accepts strings
   370     # This is because the underlying modbus library accepts strings
   320     # (e.g.: well known port names!)
   371     # (e.g.: well known port names!)
   321     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
   372     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
   322     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   373     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   323       <xsd:element name="ModbusServerNode">
   374       <xsd:element name="ModbusServerNode">
   324         <xsd:complexType>
   375         <xsd:complexType>
       
   376           <xsd:attribute name="Configuration_Name" type="xsd:string" use="optional" default=""/>
   325           <xsd:attribute name="Local_IP_Address" type="xsd:string" use="optional"  default="#ANY#"/>
   377           <xsd:attribute name="Local_IP_Address" type="xsd:string" use="optional"  default="#ANY#"/>
   326           <xsd:attribute name="Local_Port_Number" type="xsd:string" use="optional" default="502"/>
   378           <xsd:attribute name="Local_Port_Number" type="xsd:string" use="optional" default="502"/>
   327           <xsd:attribute name="SlaveID" use="optional" default="0">
   379           <xsd:attribute name="SlaveID" use="optional" default="0">
   328             <xsd:simpleType>
   380             <xsd:simpleType>
   329                 <xsd:restriction base="xsd:integer">
   381                 <xsd:restriction base="xsd:integer">
   338     """
   390     """
   339     CTNChildrenTypes = [("MemoryArea", _MemoryAreaPlug, "Memory Area")]
   391     CTNChildrenTypes = [("MemoryArea", _MemoryAreaPlug, "Memory Area")]
   340     # TODO: Replace with CTNType !!!
   392     # TODO: Replace with CTNType !!!
   341     PlugType = "ModbusTCPserver"
   393     PlugType = "ModbusTCPserver"
   342 
   394 
       
   395     def __init__(self):
       
   396         # NOTE:
       
   397         # The ModbusServerNode attribute is added dynamically by ConfigTreeNode._AddParamsMembers()
       
   398         # It will be an XML parser object created by
       
   399         # GenerateParserFromXSDstring(self.XSD).CreateRoot()
       
   400         
       
   401         # Set the default value for the "Configuration_Name" parameter
       
   402         # The default value will need to be different for each instance of the 
       
   403         # _ModbusTCPclientPlug class, so we cannot hardcode the default value in the XSD above
       
   404         # This value will be used by the web interface 
       
   405         #   (i.e. the extension to the web server used to configure the Modbus parameters).
       
   406         #   (The web server is run/activated/started by Beremiz_service.py)
       
   407         #   (The web server code is found in runtime/NevowServer.py)
       
   408         #   (The Modbus extension to the web server is found in runtime/Modbus_config.py)
       
   409         loc_str = ".".join(map(str, self.GetCurrentLocation()))
       
   410         self.ModbusServerNode.setConfiguration_Name("Modbus TCP Server " + loc_str)
       
   411         
   343     # Return the number of (modbus library) nodes this specific TCP server will need
   412     # Return the number of (modbus library) nodes this specific TCP server will need
   344     #   return type: (tcp nodes, rtu nodes, ascii nodes)
   413     #   return type: (tcp nodes, rtu nodes, ascii nodes)
   345     def GetNodeCount(self):
   414     def GetNodeCount(self):
   346         return (1, 0, 0)
   415         return (1, 0, 0)
   347 
   416 
   348     # Return a list with a single tuple conatining the (location, port number)
   417     # Return a list with a single tuple conatining the (location, IP address, port number)
   349     #     location: location of this node in the configuration tree
   418     #     location   : location of this node in the configuration tree
   350     #     port number: IP port used by this Modbus/IP server
   419     #     port number: IP port used by this Modbus/IP server
       
   420     #     IP address : IP address of the network interface on which the server will be listening
       
   421     #                  ("", "*", or "#ANY#" => listening on all interfaces!)
   351     def GetIPServerPortNumbers(self):
   422     def GetIPServerPortNumbers(self):
   352         port = self.GetParamsAttributes()[0]["children"][1]["value"]
   423         port = self.ModbusServerNode.getLocal_Port_Number()
   353         return [(self.GetCurrentLocation(), port)]
   424         addr = self.ModbusServerNode.getLocal_IP_Address()
       
   425         return [(self.GetCurrentLocation(), addr, port)]
       
   426 
       
   427     def GetConfigName(self):
       
   428         """ Return the node's Configuration_Name """
       
   429         return self.ModbusServerNode.getConfiguration_Name()
   354 
   430 
   355     def CTNGenerate_C(self, buildpath, locations):
   431     def CTNGenerate_C(self, buildpath, locations):
   356         """
   432         """
   357         Generate C code
   433         Generate C code
   358         @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5)
   434         @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5)
   374 # R T U    C L I E N T                 #
   450 # R T U    C L I E N T                 #
   375 #
   451 #
   376 #
   452 #
   377 #
   453 #
   378 
   454 
       
   455 # XXX TODO "Configuration_Name" should disapear in favor of CTN Name, which is already unique
       
   456 
   379 class _ModbusRTUclientPlug(object):
   457 class _ModbusRTUclientPlug(object):
   380     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
   458     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
   381     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   459     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   382       <xsd:element name="ModbusRTUclient">
   460       <xsd:element name="ModbusRTUclient">
   383         <xsd:complexType>
   461         <xsd:complexType>
       
   462           <xsd:attribute name="Configuration_Name" type="xsd:string" use="optional" default=""/>
   384           <xsd:attribute name="Serial_Port" type="xsd:string"  use="optional" default="/dev/ttyS0"/>
   463           <xsd:attribute name="Serial_Port" type="xsd:string"  use="optional" default="/dev/ttyS0"/>
   385           <xsd:attribute name="Baud_Rate"   type="xsd:string"  use="optional" default="9600"/>
   464           <xsd:attribute name="Baud_Rate"   type="xsd:string"  use="optional" default="9600"/>
   386           <xsd:attribute name="Parity"      type="xsd:string"  use="optional" default="even"/>
   465           <xsd:attribute name="Parity"      type="xsd:string"  use="optional" default="even"/>
   387           <xsd:attribute name="Stop_Bits"   type="xsd:string"  use="optional" default="1"/>
   466           <xsd:attribute name="Stop_Bits"   type="xsd:string"  use="optional" default="1"/>
   388           <xsd:attribute name="Invocation_Rate_in_ms" use="optional" default="100">
   467           <xsd:attribute name="Invocation_Rate_in_ms" use="optional" default="100">
   389             <xsd:simpleType>
   468             <xsd:simpleType>
   390                 <xsd:restriction base="xsd:integer">
   469                 <xsd:restriction base="xsd:integer">
   391                     <xsd:minInclusive value="1"/>
   470                     <xsd:minInclusive value="0"/>
   392                     <xsd:maxInclusive value="2147483647"/>
   471                     <xsd:maxInclusive value="2147483647"/>
   393                 </xsd:restriction>
   472                 </xsd:restriction>
   394             </xsd:simpleType>
   473             </xsd:simpleType>
   395           </xsd:attribute>
   474           </xsd:attribute>
   396         </xsd:complexType>
   475         </xsd:complexType>
   401     # corresponds to aprox 25 days.
   480     # corresponds to aprox 25 days.
   402     CTNChildrenTypes = [("ModbusRequest", _RequestPlug, "Request")]
   481     CTNChildrenTypes = [("ModbusRequest", _RequestPlug, "Request")]
   403     # TODO: Replace with CTNType !!!
   482     # TODO: Replace with CTNType !!!
   404     PlugType = "ModbusRTUclient"
   483     PlugType = "ModbusRTUclient"
   405 
   484 
       
   485     def __init__(self):
       
   486         # NOTE:
       
   487         # The ModbusRTUclient attribute is added dynamically by ConfigTreeNode._AddParamsMembers()
       
   488         # It will be an XML parser object created by
       
   489         # GenerateParserFromXSDstring(self.XSD).CreateRoot()
       
   490         
       
   491         # Set the default value for the "Configuration_Name" parameter
       
   492         # The default value will need to be different for each instance of the 
       
   493         # _ModbusTCPclientPlug class, so we cannot hardcode the default value in the XSD above
       
   494         # This value will be used by the web interface 
       
   495         #   (i.e. the extension to the web server used to configure the Modbus parameters).
       
   496         #   (The web server is run/activated/started by Beremiz_service.py)
       
   497         #   (The web server code is found in runtime/NevowServer.py)
       
   498         #   (The Modbus extension to the web server is found in runtime/Modbus_config.py)
       
   499         loc_str = ".".join(map(str, self.GetCurrentLocation()))
       
   500         self.ModbusRTUclient.setConfiguration_Name("Modbus RTU Client " + loc_str)
       
   501         
   406     def GetParamsAttributes(self, path=None):
   502     def GetParamsAttributes(self, path=None):
   407         infos = ConfigTreeNode.GetParamsAttributes(self, path=path)
   503         infos = ConfigTreeNode.GetParamsAttributes(self, path=path)
   408         for element in infos:
   504         for element in infos:
   409             if element["name"] == "ModbusRTUclient":
   505             if element["name"] == "ModbusRTUclient":
   410                 for child in element["children"]:
   506                 for child in element["children"]:
   419     # Return the number of (modbus library) nodes this specific RTU client will need
   515     # Return the number of (modbus library) nodes this specific RTU client will need
   420     #   return type: (tcp nodes, rtu nodes, ascii nodes)
   516     #   return type: (tcp nodes, rtu nodes, ascii nodes)
   421     def GetNodeCount(self):
   517     def GetNodeCount(self):
   422         return (0, 1, 0)
   518         return (0, 1, 0)
   423 
   519 
       
   520     def GetConfigName(self):
       
   521         """ Return the node's Configuration_Name """
       
   522         return self.ModbusRTUclient.getConfiguration_Name()
       
   523 
   424     def CTNGenerate_C(self, buildpath, locations):
   524     def CTNGenerate_C(self, buildpath, locations):
   425         """
   525         """
   426         Generate C code
   526         Generate C code
   427         @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5)
   527         @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5)
   428         @param locations: List of complete variables locations \
   528         @param locations: List of complete variables locations \
   443 # R T U    S L A V E                   #
   543 # R T U    S L A V E                   #
   444 #
   544 #
   445 #
   545 #
   446 #
   546 #
   447 
   547 
       
   548 # XXX TODO "Configuration_Name" should disapear in favor of CTN Name, which is already unique
   448 
   549 
   449 class _ModbusRTUslavePlug(object):
   550 class _ModbusRTUslavePlug(object):
   450     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
   551     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
   451     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   552     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   452       <xsd:element name="ModbusRTUslave">
   553       <xsd:element name="ModbusRTUslave">
   453         <xsd:complexType>
   554         <xsd:complexType>
       
   555           <xsd:attribute name="Configuration_Name" type="xsd:string" use="optional" default=""/>
   454           <xsd:attribute name="Serial_Port" type="xsd:string"  use="optional" default="/dev/ttyS0"/>
   556           <xsd:attribute name="Serial_Port" type="xsd:string"  use="optional" default="/dev/ttyS0"/>
   455           <xsd:attribute name="Baud_Rate"   type="xsd:string"  use="optional" default="9600"/>
   557           <xsd:attribute name="Baud_Rate"   type="xsd:string"  use="optional" default="9600"/>
   456           <xsd:attribute name="Parity"      type="xsd:string"  use="optional" default="even"/>
   558           <xsd:attribute name="Parity"      type="xsd:string"  use="optional" default="even"/>
   457           <xsd:attribute name="Stop_Bits"   type="xsd:string"  use="optional" default="1"/>
   559           <xsd:attribute name="Stop_Bits"   type="xsd:string"  use="optional" default="1"/>
   458           <xsd:attribute name="SlaveID" use="optional" default="1">
   560           <xsd:attribute name="SlaveID" use="optional" default="1">
   469     """
   571     """
   470     CTNChildrenTypes = [("MemoryArea", _MemoryAreaPlug, "Memory Area")]
   572     CTNChildrenTypes = [("MemoryArea", _MemoryAreaPlug, "Memory Area")]
   471     # TODO: Replace with CTNType !!!
   573     # TODO: Replace with CTNType !!!
   472     PlugType = "ModbusRTUslave"
   574     PlugType = "ModbusRTUslave"
   473 
   575 
       
   576     def __init__(self):
       
   577         # NOTE:
       
   578         # The ModbusRTUslave attribute is added dynamically by ConfigTreeNode._AddParamsMembers()
       
   579         # It will be an XML parser object created by
       
   580         # GenerateParserFromXSDstring(self.XSD).CreateRoot()
       
   581         
       
   582         # Set the default value for the "Configuration_Name" parameter
       
   583         # The default value will need to be different for each instance of the 
       
   584         # _ModbusTCPclientPlug class, so we cannot hardcode the default value in the XSD above
       
   585         # This value will be used by the web interface 
       
   586         #   (i.e. the extension to the web server used to configure the Modbus parameters).
       
   587         #   (The web server is run/activated/started by Beremiz_service.py)
       
   588         #   (The web server code is found in runtime/NevowServer.py)
       
   589         #   (The Modbus extension to the web server is found in runtime/Modbus_config.py)
       
   590         loc_str = ".".join(map(str, self.GetCurrentLocation()))
       
   591         self.ModbusRTUslave.setConfiguration_Name("Modbus RTU Slave " + loc_str)
       
   592         
   474     def GetParamsAttributes(self, path=None):
   593     def GetParamsAttributes(self, path=None):
   475         infos = ConfigTreeNode.GetParamsAttributes(self, path=path)
   594         infos = ConfigTreeNode.GetParamsAttributes(self, path=path)
   476         for element in infos:
   595         for element in infos:
   477             if element["name"] == "ModbusRTUslave":
   596             if element["name"] == "ModbusRTUslave":
   478                 for child in element["children"]:
   597                 for child in element["children"]:
   486 
   605 
   487     # Return the number of (modbus library) nodes this specific RTU slave will need
   606     # Return the number of (modbus library) nodes this specific RTU slave will need
   488     #   return type: (tcp nodes, rtu nodes, ascii nodes)
   607     #   return type: (tcp nodes, rtu nodes, ascii nodes)
   489     def GetNodeCount(self):
   608     def GetNodeCount(self):
   490         return (0, 1, 0)
   609         return (0, 1, 0)
       
   610 
       
   611     def GetConfigName(self):
       
   612         """ Return the node's Configuration_Name """
       
   613         return self.ModbusRTUslave.getConfiguration_Name()
   491 
   614 
   492     def CTNGenerate_C(self, buildpath, locations):
   615     def CTNGenerate_C(self, buildpath, locations):
   493         """
   616         """
   494         Generate C code
   617         Generate C code
   495         @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5)
   618         @param current_location: Tupple containing plugin IEC location : %I0.0.4.5 => (0,0,4,5)
   548             # ask each child how many nodes it needs, and add them all up.
   671             # ask each child how many nodes it needs, and add them all up.
   549             total_node_count = tuple(
   672             total_node_count = tuple(
   550                 x1 + x2 for x1, x2 in zip(total_node_count, child.GetNodeCount()))
   673                 x1 + x2 for x1, x2 in zip(total_node_count, child.GetNodeCount()))
   551         return total_node_count
   674         return total_node_count
   552 
   675 
   553     # Return a list with tuples of the (location, port numbers) used by all
   676     # Return a list with tuples of the (location, port numbers) used by all the Modbus/IP servers
   554     # the Modbus/IP servers
       
   555     def GetIPServerPortNumbers(self):
   677     def GetIPServerPortNumbers(self):
   556         IPServer_port_numbers = []
   678         IPServer_port_numbers = []
   557         for child in self.IECSortedChildren():
   679         for child in self.IECSortedChildren():
   558             if child.CTNType == "ModbusTCPserver":
   680             if child.CTNType == "ModbusTCPserver":
   559                 IPServer_port_numbers.extend(child.GetIPServerPortNumbers())
   681                 IPServer_port_numbers.extend(child.GetIPServerPortNumbers())
   560         return IPServer_port_numbers
   682         return IPServer_port_numbers
       
   683 
       
   684     # Return a list with tuples of the (location, configuration_name) used by all the Modbus nodes (tcp/rtu, clients/servers)
       
   685     def GetConfigNames(self):
       
   686         Node_Configuration_Names = []
       
   687         for child in self.IECSortedChildren():
       
   688             Node_Configuration_Names.extend([(child.GetCurrentLocation(), child.GetConfigName())])
       
   689         return Node_Configuration_Names
   561 
   690 
   562     def CTNGenerate_C(self, buildpath, locations):
   691     def CTNGenerate_C(self, buildpath, locations):
   563         # print "#############"
   692         # print "#############"
   564         # print self.__class__
   693         # print self.__class__
   565         # print type(self)
   694         # print type(self)
   571 
   700 
   572         loc_dict = {"locstr": "_".join(map(str, self.GetCurrentLocation()))}
   701         loc_dict = {"locstr": "_".join(map(str, self.GetCurrentLocation()))}
   573 
   702 
   574         # Determine the number of (modbus library) nodes ALL instances of the modbus plugin will need
   703         # Determine the number of (modbus library) nodes ALL instances of the modbus plugin will need
   575         #   total_node_count: (tcp nodes, rtu nodes, ascii nodes)
   704         #   total_node_count: (tcp nodes, rtu nodes, ascii nodes)
   576         # Also get a list with tuples of (location, IP port numbers) used by all the Modbus/IP server nodes
   705         #
       
   706         # Also get a list with tuples of (location, IP address, port number) used by all the Modbus/IP server nodes
   577         #   This list is later used to search for duplicates in port numbers!
   707         #   This list is later used to search for duplicates in port numbers!
   578         #   IPServer_port_numbers = [(location ,IPserver_port_number), ...]
   708         #   IPServer_port_numbers = [(location, IP address, port number), ...]
   579         #       location: tuple similar to (0, 3, 1) representing the location in the configuration tree "0.3.1.x"
   709         #       location            : tuple similar to (0, 3, 1) representing the location in the configuration tree "0.3.1.x"
   580         # IPserver_port_number: a number (i.e. port number used by the
   710         #       IPserver_port_number: a number (i.e. port number used by the Modbus/IP server)
   581         # Modbus/IP server)
   711         #       IP address          : IP address of the network interface on which the server will be listening
       
   712         #                             ("", "*", or "#ANY#" => listening on all interfaces!)
       
   713         #
       
   714         # Also get a list with tuples of (location, Configuration_Name) used by all the Modbus nodes
       
   715         #   This list is later used to search for duplicates in Configuration Names!
       
   716         #   Node_Configuration_Names = [(location, Configuration_Name), ...]
       
   717         #       location          : tuple similar to (0, 3, 1) representing the location in the configuration tree "0.3.1.x"
       
   718         #       Configuration_Name: the "Configuration_Name" string
   582         total_node_count = (0, 0, 0)
   719         total_node_count = (0, 0, 0)
   583         IPServer_port_numbers = []
   720         IPServer_port_numbers    = []
       
   721         Node_Configuration_Names = []
   584         for CTNInstance in self.GetCTRoot().IterChildren():
   722         for CTNInstance in self.GetCTRoot().IterChildren():
   585             if CTNInstance.CTNType == "modbus":
   723             if CTNInstance.CTNType == "modbus":
   586                 # ask each modbus plugin instance how many nodes it needs, and
   724                 # ask each modbus plugin instance how many nodes it needs, and add them all up.
   587                 # add them all up.
   725                 total_node_count = tuple(x1 + x2 for x1, x2 in zip(total_node_count, CTNInstance.GetNodeCount()))
   588                 total_node_count = tuple(x1 + x2 for x1, x2 in zip(
   726                 IPServer_port_numbers.   extend(CTNInstance.GetIPServerPortNumbers())
   589                     total_node_count, CTNInstance.GetNodeCount()))
   727                 Node_Configuration_Names.extend(CTNInstance.GetConfigNames        ())
   590                 IPServer_port_numbers.extend(
   728 
   591                     CTNInstance.GetIPServerPortNumbers())
   729         # Search for use of duplicate Configuration_Names by Modbus nodes
       
   730         # Configuration Names are used by the web server running on the PLC
       
   731         # (more precisely, run by Beremiz_service.py) to identify and allow 
       
   732         # changing the Modbus parameters after the program has been downloaded 
       
   733         # to the PLC (but before it is started)
       
   734         # With clashes in the configuration names, the Modbus nodes will not be
       
   735         # distinguasheble on the web interface!
       
   736         for i in range(0, len(Node_Configuration_Names) - 1):
       
   737             for j in range(i + 1, len(Node_Configuration_Names)):
       
   738                 if Node_Configuration_Names[i][1] == Node_Configuration_Names[j][1]:
       
   739                     error_message = _("Error: Modbus plugin nodes %{a1}.x and %{a2}.x use the same Configuration_Name \"{a3}\".\n").format(
       
   740                                         a1=_lt_to_str(Node_Configuration_Names[i][0]),
       
   741                                         a2=_lt_to_str(Node_Configuration_Names[j][0]),
       
   742                                         a3=Node_Configuration_Names[j][1])
       
   743                     self.FatalError(error_message)
   592 
   744 
   593         # Search for use of duplicate port numbers by Modbus/IP servers
   745         # Search for use of duplicate port numbers by Modbus/IP servers
   594         # print IPServer_port_numbers
   746         # Note: We only consider duplicate port numbers if using the same network interface!
   595         # ..but first define a lambda function to convert a tuple with the config tree location to a nice looking string
   747         i = 0
   596         #   for e.g., convert the tuple (0, 3, 4) to "0.3.4"
   748         for loc1, addr1, port1 in IPServer_port_numbers[:-1]:
   597 
   749             i = i + 1
   598         for i in range(0, len(IPServer_port_numbers) - 1):
   750             for loc2, addr2, port2 in IPServer_port_numbers[i:]:
   599             for j in range(i + 1, len(IPServer_port_numbers)):
   751                 if (port1 == port2) and (
   600                 if IPServer_port_numbers[i][1] == IPServer_port_numbers[j][1]:
   752                           (addr1 == addr2)   # on the same network interface
   601                     self.GetCTRoot().logger.write_warning(
   753                        or (addr1 == "") or (addr1 == "*") or (addr1 == "#ANY#") # or one (or both) of the servers
   602                         _("Error: Modbus/IP Servers %{a1}.x and %{a2}.x use the same port number {a3}.\n").
   754                        or (addr2 == "") or (addr2 == "*") or (addr2 == "#ANY#") # use all available network interfaces
   603                         format(
   755                    ):
   604                             a1=_lt_to_str(IPServer_port_numbers[i][0]),
   756                     error_message = _("Error: Modbus plugin nodes %{a1}.x and %{a2}.x use same port number \"{a3}\" " + 
   605                             a2=_lt_to_str(IPServer_port_numbers[j][0]),
   757                                       "on the same (or overlapping) network interfaces \"{a4}\" and \"{a5}\".\n").format(
   606                             a3=IPServer_port_numbers[j][1]))
   758                                         a1=_lt_to_str(loc1), a2=_lt_to_str(loc2), a3=port1, a4=addr1, a5=addr2)
   607                     raise Exception
   759                     self.FatalError(error_message)
   608                     # TODO: return an error code instead of raising an
       
   609                     # exception
       
   610 
   760 
   611         # Determine the current location in Beremiz's project configuration
   761         # Determine the current location in Beremiz's project configuration
   612         # tree
   762         # tree
   613         current_location = self.GetCurrentLocation()
   763         current_location = self.GetCurrentLocation()
   614 
   764 
   718                         return [], "", False
   868                         return [], "", False
   719                     client_request_list.append(new_req)
   869                     client_request_list.append(new_req)
   720                     for iecvar in subchild.GetLocations():
   870                     for iecvar in subchild.GetLocations():
   721                         # absloute address - start address
   871                         # absloute address - start address
   722                         relative_addr = iecvar["LOC"][3] - int(GetCTVal(subchild, 3))
   872                         relative_addr = iecvar["LOC"][3] - int(GetCTVal(subchild, 3))
   723                         # test if relative address in request specified range
   873                         # test if the located variable 
   724                         if relative_addr in xrange(int(GetCTVal(subchild, 2))):
   874                         #    (a) has relative address in request specified range
       
   875                         #  AND is NOT
       
   876                         #    (b) is a control flag added by this modbus plugin
       
   877                         #        to control its execution at runtime.
       
   878                         #        Currently, we only add the "Execution Control Flag"
       
   879                         #        to each client request (one flag per request)
       
   880                         #        to control when to execute the request (if not executed periodically)
       
   881                         #        While all Modbus registers/coils are mapped onto a location
       
   882                         #        with 4 numbers (e.g. %QX0.1.2.55), this control flag is mapped
       
   883                         #        onto a location with 4 numbers (e.g. %QX0.1.2.0.0), where the last
       
   884                         #        two numbers are always '0.0', and the first two identify the request.
       
   885                         #        In the following if, we check for this condition by checking
       
   886                         #        if their are at least 4 or more number in the location's address.
       
   887                         if (        relative_addr in xrange(int(GetCTVal(subchild, 2)))  # condition (a) explained above
       
   888                             and len(iecvar["LOC"]) < 5):                                  # condition (b) explained above
   725                             if str(iecvar["NAME"]) not in loc_vars_list:
   889                             if str(iecvar["NAME"]) not in loc_vars_list:
   726                                 loc_vars.append(
   890                                 loc_vars.append(
   727                                     "u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].plcv_buffer[%d];" % (client_requestid, relative_addr))
   891                                     "u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].plcv_buffer[%d];" % (client_requestid, relative_addr))
       
   892                                 loc_vars_list.append(str(iecvar["NAME"]))
       
   893                         # Now add the located variable in case it is a flag (condition (b) above
       
   894                         if  len(iecvar["LOC"]) >= 5:       # condition (b) explained above
       
   895                             if str(iecvar["NAME"]) not in loc_vars_list:
       
   896                                 loc_vars.append(
       
   897                                     "u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].flag_exec_req;" % (client_requestid))
   728                                 loc_vars_list.append(str(iecvar["NAME"]))
   898                                 loc_vars_list.append(str(iecvar["NAME"]))
   729                     client_requestid += 1
   899                     client_requestid += 1
   730                 tcpclient_node_count += 1
   900                 tcpclient_node_count += 1
   731                 client_nodeid += 1
   901                 client_nodeid += 1
   732             #
   902             #
   743                         return [], "", False
   913                         return [], "", False
   744                     client_request_list.append(new_req)
   914                     client_request_list.append(new_req)
   745                     for iecvar in subchild.GetLocations():
   915                     for iecvar in subchild.GetLocations():
   746                         # absloute address - start address
   916                         # absloute address - start address
   747                         relative_addr = iecvar["LOC"][3] - int(GetCTVal(subchild, 3))
   917                         relative_addr = iecvar["LOC"][3] - int(GetCTVal(subchild, 3))
   748                         # test if relative address in request specified range
   918                         # test if the located variable 
   749                         if relative_addr in xrange(int(GetCTVal(subchild, 2))):
   919                         #    (a) has relative address in request specified range
       
   920                         #  AND is NOT
       
   921                         #    (b) is a control flag added by this modbus plugin
       
   922                         #        to control its execution at runtime.
       
   923                         #        Currently, we only add the "Execution Control Flag"
       
   924                         #        to each client request (one flag per request)
       
   925                         #        to control when to execute the request (if not executed periodically)
       
   926                         #        While all Modbus registers/coils are mapped onto a location
       
   927                         #        with 4 numbers (e.g. %QX0.1.2.55), this control flag is mapped
       
   928                         #        onto a location with 4 numbers (e.g. %QX0.1.2.0.0), where the last
       
   929                         #        two numbers are always '0.0', and the first two identify the request.
       
   930                         #        In the following if, we check for this condition by checking
       
   931                         #        if their are at least 4 or more number in the location's address.
       
   932                         if (        relative_addr in xrange(int(GetCTVal(subchild, 2)))  # condition (a) explained above
       
   933                             and len(iecvar["LOC"]) < 5):                                  # condition (b) explained above
   750                             if str(iecvar["NAME"]) not in loc_vars_list:
   934                             if str(iecvar["NAME"]) not in loc_vars_list:
   751                                 loc_vars.append(
   935                                 loc_vars.append(
   752                                     "u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].plcv_buffer[%d];" % (client_requestid, relative_addr))
   936                                     "u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].plcv_buffer[%d];" % (client_requestid, relative_addr))
       
   937                                 loc_vars_list.append(str(iecvar["NAME"]))
       
   938                         # Now add the located variable in case it is a flag (condition (b) above
       
   939                         if  len(iecvar["LOC"]) >= 5:       # condition (b) explained above
       
   940                             if str(iecvar["NAME"]) not in loc_vars_list:
       
   941                                 loc_vars.append(
       
   942                                     "u16 *" + str(iecvar["NAME"]) + " = &client_requests[%d].flag_exec_req;" % (client_requestid))
   753                                 loc_vars_list.append(str(iecvar["NAME"]))
   943                                 loc_vars_list.append(str(iecvar["NAME"]))
   754                     client_requestid += 1
   944                     client_requestid += 1
   755                 rtuclient_node_count += 1
   945                 rtuclient_node_count += 1
   756                 client_nodeid += 1
   946                 client_nodeid += 1
   757             nodeid += 1
   947             nodeid += 1
   801         # Target is ARM with linux and not win on x86 so winsock2 (ws2_32) library is useless !!!
   991         # Target is ARM with linux and not win on x86 so winsock2 (ws2_32) library is useless !!!
   802         # if os.name == 'nt':   # other possible values: 'posix' 'os2' 'ce' 'java' 'riscos'
   992         # if os.name == 'nt':   # other possible values: 'posix' 'os2' 'ce' 'java' 'riscos'
   803         # LDFLAGS.append(" -lws2_32 ")  # on windows we need to load winsock
   993         # LDFLAGS.append(" -lws2_32 ")  # on windows we need to load winsock
   804         # library!
   994         # library!
   805 
   995 
   806         return [(Gen_MB_c_path, ' -I"' + ModbusPath + '"')], LDFLAGS, True
   996         websettingfile = open(paths.AbsNeighbourFile(__file__, "web_settings.py"), 'r')
       
   997         websettingcode = websettingfile.read()
       
   998         websettingfile.close()
       
   999 
       
  1000         location_str = "_".join(map(str, self.GetCurrentLocation()))
       
  1001         websettingcode = websettingcode % locals()
       
  1002 
       
  1003         runtimefile_path = os.path.join(buildpath, "runtime_modbus_websettings.py")
       
  1004         runtimefile = open(runtimefile_path, 'w')
       
  1005         runtimefile.write(websettingcode)
       
  1006         runtimefile.close()
       
  1007 
       
  1008         return ([(Gen_MB_c_path, ' -I"' + ModbusPath + '"')], LDFLAGS, True,
       
  1009                 ("runtime_modbus_websettings_%s.py" % location_str, open(runtimefile_path, "rb")),
       
  1010         )