bacnet/bacnet.py
changeset 2020 6dddf3070806
child 2250 86f61c4dfe76
equal deleted inserted replaced
2019:92f02bb17c7e 2020:6dddf3070806
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 # This file is part of Beremiz, a Integrated Development Environment for
       
     5 # programming IEC 61131-3 automates supporting plcopen standard.
       
     6 # This files implements the bacnet plugin for Beremiz, adding BACnet server support.
       
     7 #
       
     8 # Copyright (c) 2017 Mario de Sousa (msousa@fe.up.pt)
       
     9 #
       
    10 # This program is free software: you can redistribute it and/or modify
       
    11 # it under the terms of the GNU General Public License as published by
       
    12 # the Free Software Foundation, either version 2 of the License, or
       
    13 # (at your option) any later version.
       
    14 #
       
    15 # This program is distributed in the hope that it will be useful,
       
    16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       
    18 # GNU General Public License for more details.
       
    19 #
       
    20 # You should have received a copy of the GNU General Public License
       
    21 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
       
    22 #    
       
    23 # This code is made available on the understanding that it will not be
       
    24 # used in safety-critical situations without a full and competent review.
       
    25 
       
    26 
       
    27 
       
    28 import os, sys
       
    29 from collections import Counter
       
    30 from datetime    import datetime
       
    31 
       
    32 base_folder           = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]
       
    33 base_folder           = os.path.join(base_folder, "..")
       
    34 BacnetPath            = os.path.join(base_folder, "BACnet")
       
    35 BacnetLibraryPath     = os.path.join(BacnetPath, "lib")
       
    36 BacnetIncludePath     = os.path.join(BacnetPath, "include")
       
    37 BacnetIncludePortPath = os.path.join(BacnetPath, "ports")
       
    38 BacnetIncludePortPath = os.path.join(BacnetIncludePortPath, "linux")
       
    39 
       
    40 import wx
       
    41 import pickle
       
    42 
       
    43 from BacnetSlaveEditor import *
       
    44 from BacnetSlaveEditor import ObjectProperties
       
    45 from ConfigTreeNode    import ConfigTreeNode
       
    46 from PLCControler      import LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY
       
    47 
       
    48 # Parameters to be monkey patched in beremiz customizations
       
    49 BACNET_VENDOR_ID = 9999 
       
    50 BACNET_VENDOR_NAME = "Beremiz.org"
       
    51 BACNET_DEVICE_MODEL_NAME = "Beremiz PLC"
       
    52 
       
    53 ###################################################
       
    54 ###################################################
       
    55 #                                                 #
       
    56 #           S L A V E    D E V I C E              # 
       
    57 #                                                 #
       
    58 ###################################################
       
    59 ###################################################
       
    60 
       
    61 # NOTE: Objects of class _BacnetSlavePlug are never instantiated directly.
       
    62 #       The objects are instead instantiated from class FinalCTNClass
       
    63 #       FinalCTNClass inherits from: - ConfigTreeNode
       
    64 #                                    - The tree node plug (in our case _BacnetSlavePlug)
       
    65 #class _BacnetSlavePlug:
       
    66 class RootClass:
       
    67     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
       
    68     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
       
    69       <xsd:element name="BACnetServerNode">
       
    70         <xsd:complexType>
       
    71           <xsd:attribute name="Network_Interface"      type="xsd:string"  use="optional" default="eth0"/>
       
    72           <xsd:attribute name="UDP_Port_Number"                           use="optional" default="47808">
       
    73             <xsd:simpleType>
       
    74                 <xsd:restriction base="xsd:integer">
       
    75                     <xsd:minInclusive value="0"/>
       
    76                     <xsd:maxInclusive value="65535"/>
       
    77                 </xsd:restriction>
       
    78             </xsd:simpleType>
       
    79           </xsd:attribute>
       
    80           <xsd:attribute name="BACnet_Communication_Control_Password"     
       
    81                                                        type="xsd:string"  use="optional" default="Malba Tahan"/>
       
    82           <xsd:attribute name="BACnet_Device_ID"                          use="optional" default="0">
       
    83             <xsd:simpleType>
       
    84                 <xsd:restriction base="xsd:integer">
       
    85                     <xsd:minInclusive value="0"/>
       
    86                     <xsd:maxInclusive value="4194302"/>
       
    87                 </xsd:restriction>
       
    88             </xsd:simpleType>
       
    89           </xsd:attribute>
       
    90           <xsd:attribute name="BACnet_Device_Name"        type="xsd:string"  use="optional" default="Beremiz device 0"/>
       
    91           <xsd:attribute name="BACnet_Device_Location"    type="xsd:string"  use="optional" default=""/>
       
    92           <xsd:attribute name="BACnet_Device_Description" type="xsd:string"  use="optional" default="Beremiz device 0"/>
       
    93           <xsd:attribute name="BACnet_Device_Application_Software_Version" type="xsd:string"  use="optional" default="1.0"/>
       
    94         </xsd:complexType>
       
    95       </xsd:element>
       
    96     </xsd:schema>
       
    97     """
       
    98     # NOTE: BACnet device (object) IDs are 22 bits long (not counting the 10 bits for the type ID)
       
    99     #       so the Device instance ID is limited from 0 to 22^2-1 = 4194303
       
   100     #       However, 4194303 is reserved for special use (similar to NULL pointer), so last
       
   101     #       valid ID becomes 4194302
       
   102     
       
   103     
       
   104     # The class/object that will render the graphical interface to edit the
       
   105     #    BacnetSlavePlug's configuration parameters. The object of class BacnetSlaveEditorPlug
       
   106     #    will be instantiated by the ConfigTreeNode class.
       
   107     #    This BacnetSlaveEditorPlug object can be accessed from _BacnetSlavePlug as
       
   108     #    'self._View'
       
   109     #    See the following note to understand how this is possible!
       
   110     #
       
   111     # NOTE: Objects of class _BacnetSlavePlug are never instantiated directly.
       
   112     #       The objects are instead instantiated from class FinalCTNClass
       
   113     #       FinalCTNClass inherits from: - ConfigTreeNode
       
   114     #                                    - The tree node plug (in our case _BacnetSlavePlug)
       
   115     #
       
   116     #       This means that objects of class _BacnetSlavePlug may safely access all the members
       
   117     #       of classes ConfigTreeNode as well as FinalCTNClass (since they are always instantiated
       
   118     #       as a FinalCTNClass)
       
   119     EditorType = BacnetSlaveEditorPlug
       
   120     
       
   121     # The following classes follow the model/viewer design pattern
       
   122     #
       
   123     # _BacnetSlavePlug       - contains the model (i.e. configuration parameters)
       
   124     # BacnetSlaveEditorPlug  - contains the viewer (and editor, so it includes the 'controller' part of the 
       
   125     #                                    design pattern which in this case is not separated from the viewer)
       
   126     #
       
   127     # The _BacnetSlavePlug      object is 'permanent', i.e. it exists as long as the beremiz project is open 
       
   128     # The BacnetSlaveEditorPlug object is 'transient', i.e. it exists only while the editor is visible/open
       
   129     #                                                         in the editing panel. It is destoryed whenever
       
   130     #                                                         the user closes the corresponding tab in the 
       
   131     #                                                         editing panel, and a new object is created when
       
   132     #                                                         the editor is re-opened.
       
   133     #
       
   134     # _BacnetSlavePlug contains:  AV_ObjTable, ...
       
   135     #                             (these are the objects that actually store the config parameters or 'model'
       
   136     #                              and are therefore stored to a file)
       
   137     #
       
   138     # _BacnetSlavePlug contains:  AV_VarEditor, ...
       
   139     #                             (these are the objects that implement a grid table to edit/view the 
       
   140     #                              corresponding mode parameters)
       
   141     #  
       
   142     #  Logic:
       
   143     #    - The xx_VarEditor classes inherit from wx.grid.Grid
       
   144     #    - The xx_ObjTable  classes inherit from wx.grid.PyGridTableBase
       
   145     #  To be more precise, the inheritance tree is actually:
       
   146     #    xx_VarEditor -> ObjectGrid -> CustomGrid   -> wx.grid.Grid
       
   147     #    xx_ObjTable  -> ObjectTable -> CustomTable -> wx.grid.PyGridTableBase)
       
   148     #
       
   149     #  Note that wx.grid.Grid is prepared to work with wx.grid.PyGridTableBase as the container of
       
   150     #  data that is displayed and edited in the Grid.
       
   151 
       
   152     
       
   153     ConfNodeMethods = [
       
   154         {"bitmap"  : "ExportSlave",
       
   155          "name"    : _("Export slave"), 
       
   156          "tooltip" : _("Export BACnet slave to EDE file"),
       
   157          "method"  : "_ExportBacnetSlave"},
       
   158     ]
       
   159     
       
   160     def __init__(self):
       
   161         # Initialize the dictionary that stores the current configuration for the Analog/Digital/MultiValued Variables 
       
   162         #   in this BACnet server.
       
   163         self.ObjTablesData = {}
       
   164         self.ObjTablesData[ "AV_Obj"] = [] # Each list will contain an entry for each row in the xxxxVar grid!!
       
   165         self.ObjTablesData[ "AO_Obj"] = [] #   Each entry/row will be a dictionary
       
   166         self.ObjTablesData[ "AI_Obj"] = [] #     Each dictionary will contain all entries/data 
       
   167         self.ObjTablesData[ "BV_Obj"] = [] #     for one row in the grid.
       
   168         self.ObjTablesData[ "BO_Obj"] = [] # Same structure as explained above...
       
   169         self.ObjTablesData[ "BI_Obj"] = [] # Same structure as explained above...
       
   170         self.ObjTablesData["MSV_Obj"] = [] # Same structure as explained above...
       
   171         self.ObjTablesData["MSO_Obj"] = [] # Same structure as explained above...
       
   172         self.ObjTablesData["MSI_Obj"] = [] # Same structure as explained above...
       
   173         
       
   174         self.ObjTablesData["EDEfile_parm"] = {"next_EDE_file_version":1} 
       
   175                                                 # EDE files inlcude extra parameters (ex. file version)
       
   176                                                 # We would like to save the parameters the user configures
       
   177                                                 # so they are available the next time the user opens the project.
       
   178                                                 # Since this plugin is only storing the ObjTablesData[] dict
       
   179                                                 # to file, we add that info to this dictionary too.
       
   180                                                 # Yes, I know this is kind of a hack.
       
   181         
       
   182         filepath = self.GetFileName()
       
   183         if(os.path.isfile(filepath)):
       
   184             self.LoadFromFile(filepath)
       
   185 
       
   186         self.ObjTables = {}
       
   187         self.ObjTables[ "AV_Obj"] = ObjectTable(self, self.ObjTablesData[ "AV_Obj"],  AVObject)
       
   188         self.ObjTables[ "AO_Obj"] = ObjectTable(self, self.ObjTablesData[ "AO_Obj"],  AOObject)
       
   189         self.ObjTables[ "AI_Obj"] = ObjectTable(self, self.ObjTablesData[ "AI_Obj"],  AIObject)
       
   190         self.ObjTables[ "BV_Obj"] = ObjectTable(self, self.ObjTablesData[ "BV_Obj"],  BVObject)
       
   191         self.ObjTables[ "BO_Obj"] = ObjectTable(self, self.ObjTablesData[ "BO_Obj"],  BOObject)
       
   192         self.ObjTables[ "BI_Obj"] = ObjectTable(self, self.ObjTablesData[ "BI_Obj"],  BIObject)
       
   193         self.ObjTables["MSV_Obj"] = ObjectTable(self, self.ObjTablesData["MSV_Obj"], MSVObject)
       
   194         self.ObjTables["MSO_Obj"] = ObjectTable(self, self.ObjTablesData["MSO_Obj"], MSOObject)
       
   195         self.ObjTables["MSI_Obj"] = ObjectTable(self, self.ObjTablesData["MSI_Obj"], MSIObject)
       
   196         #   list containing the data in the table <--^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
       
   197 
       
   198 
       
   199     ######################################
       
   200     # Functions to be called by CTNClass #
       
   201     ######################################
       
   202     # The following functions would be somewhat equvalent to virtual functions/methods in C++ classes
       
   203     #  They will be called by the base class (CTNClass) from which this _BacnetSlavePlug class derives.
       
   204     
       
   205     def GetCurrentNodeName(self):
       
   206         return self.CTNName()
       
   207 
       
   208     def GetFileName(self):
       
   209         return os.path.join(self.CTNPath(), 'bacnet_slave')
       
   210     
       
   211     def OnCTNSave(self, from_project_path=None):
       
   212        return self.SaveToFile(self.GetFileName())
       
   213 
       
   214 
       
   215     def CTNTestModified(self):
       
   216         # self.ChangesToSave: Check whether any of the parameters, defined in the XSD above, were changed.
       
   217         #                     This is handled by the ConfigTreeNode class
       
   218         #                     (Remember that no objects are ever instantiated from _BacnetSlavePlug. 
       
   219         #                      Objects are instead created from FinalCTNClass, which derives from
       
   220         #                      _BacnetSlavePlug and ConfigTreeNode. This means that we can exceptionally
       
   221         #                      consider that all objects of type _BacnetSlavePlug will also be a ConfigTreeNode).
       
   222         result = self.ChangesToSave or self.ObjTables[ "AV_Obj"].ChangesToSave \
       
   223                                     or self.ObjTables[ "AO_Obj"].ChangesToSave \
       
   224                                     or self.ObjTables[ "AI_Obj"].ChangesToSave \
       
   225                                     or self.ObjTables[ "BV_Obj"].ChangesToSave \
       
   226                                     or self.ObjTables[ "BO_Obj"].ChangesToSave \
       
   227                                     or self.ObjTables[ "BI_Obj"].ChangesToSave \
       
   228                                     or self.ObjTables["MSV_Obj"].ChangesToSave \
       
   229                                     or self.ObjTables["MSO_Obj"].ChangesToSave \
       
   230                                     or self.ObjTables["MSI_Obj"].ChangesToSave
       
   231         return result
       
   232 
       
   233     ### Currently not needed. Override _OpenView() in case we need to do some special stuff whenever the editor is opened!
       
   234     ##def _OpenView(self, name=None, onlyopened=False):
       
   235         ##print "_BacnetSlavePlug._OpenView() Called!!!"
       
   236         ##ConfigTreeNode._OpenView(self, name, onlyopened)
       
   237         ###print self._View
       
   238         #####if self._View is not None:
       
   239             #####self._View.SetBusId(self.GetCurrentLocation())
       
   240         ##return self._View
       
   241 
       
   242 
       
   243     def GetVariableLocationTree(self):
       
   244         current_location = self.GetCurrentLocation()
       
   245         # see comment in CTNGenerate_C regarding identical line of code!
       
   246         locstr = ".".join(map(str,current_location))
       
   247         
       
   248         # IDs used by BACnet to identify object types/class.
       
   249         #     OBJECT_ANALOG_INPUT       =  0,
       
   250         #     OBJECT_ANALOG_OUTPUT      =  1,
       
   251         #     OBJECT_ANALOG_VALUE       =  2,
       
   252         #     OBJECT_BINARY_INPUT       =  3,
       
   253         #     OBJECT_BINARY_OUTPUT      =  4,
       
   254         #     OBJECT_BINARY_VALUE       =  5,
       
   255         #     OBJECT_MULTI_STATE_INPUT  = 13,
       
   256         #     OBJECT_MULTI_STATE_OUTPUT = 14,
       
   257         #     OBJECT_MULTI_STATE_VALUE  = 19,
       
   258         #
       
   259         #  Since Binary Value, Analog Value, etc. objects may use the same
       
   260         # object ID (since they have distinct class IDs), we must also distinguish them in some way in
       
   261         # the %MX0.3.4 IEC 61131-3 syntax.
       
   262         #
       
   263         # For this reason we add the BACnet class identifier to the %MX0.5.3 location.
       
   264         # For example, for a BACnet plugin in location '0' of the Beremiz configuration tree,
       
   265         #  all      Binary Values will be mapped onto: %MX0.5.xxx    (xxx is object ID)
       
   266         #  all Multi State Values will be mapped onto: %MB0.19.xxx   (xxx is object ID)
       
   267         #  all      Analog Values will be mapped onto: %MD0.2.xxx    (xxx is object ID)
       
   268         #  etc..
       
   269         #
       
   270         #   Value objects will be mapped onto %M
       
   271         #   Input objects will be mapped onto %I
       
   272         #  Output objects will be mapped onto %Q
       
   273                 
       
   274         BACnetEntries = []
       
   275         BACnetEntries.append(self.GetSlaveLocationTree(
       
   276                       self.ObjTablesData[ "AV_Obj"], 32, 'REAL', 'D', locstr+ '.2', 'Analog Values'))
       
   277         BACnetEntries.append(self.GetSlaveLocationTree(
       
   278                       self.ObjTablesData[ "AO_Obj"], 32, 'REAL', 'D', locstr+ '.1', 'Analog Outputs'))
       
   279         BACnetEntries.append(self.GetSlaveLocationTree(
       
   280                       self.ObjTablesData[ "AI_Obj"], 32, 'REAL', 'D', locstr+ '.0', 'Analog Inputs'))
       
   281         BACnetEntries.append(self.GetSlaveLocationTree(
       
   282                       self.ObjTablesData[ "BV_Obj"],  1, 'BOOL', 'X', locstr+ '.5', 'Binary Values'))
       
   283         BACnetEntries.append(self.GetSlaveLocationTree(
       
   284                       self.ObjTablesData[ "BO_Obj"],  1, 'BOOL', 'X', locstr+ '.4', 'Binary Outputs'))
       
   285         BACnetEntries.append(self.GetSlaveLocationTree(
       
   286                       self.ObjTablesData[ "BI_Obj"],  1, 'BOOL', 'X', locstr+ '.3', 'Binary Inputs'))
       
   287         BACnetEntries.append(self.GetSlaveLocationTree(
       
   288                       self.ObjTablesData["MSV_Obj"],  8, 'BYTE', 'B', locstr+'.19', 'Multi State Values'))
       
   289         BACnetEntries.append(self.GetSlaveLocationTree(
       
   290                       self.ObjTablesData["MSO_Obj"],  8, 'BYTE', 'B', locstr+'.14', 'Multi State Outputs'))
       
   291         BACnetEntries.append(self.GetSlaveLocationTree(
       
   292                       self.ObjTablesData["MSI_Obj"],  8, 'BYTE', 'B', locstr+'.13', 'Multi State Inputs'))
       
   293 
       
   294         return  {"name": self.BaseParams.getName(),
       
   295                  "type": LOCATION_CONFNODE,
       
   296                  "location": locstr + ".x",
       
   297                  "children": BACnetEntries}
       
   298 
       
   299 
       
   300     ############################
       
   301     # Helper functions/methods #
       
   302     ############################
       
   303     # a helper function to GetVariableLocationTree()
       
   304     def GetSlaveLocationTree(self, ObjTablesData, size_in_bits, IECdatatype, location_size, location_str, name):
       
   305         BACnetObjectEntries = []
       
   306         for  xx_ObjProp in ObjTablesData:
       
   307             BACnetObjectEntries.append({
       
   308                 "name": str(xx_ObjProp["Object Identifier"]) + ': ' + xx_ObjProp["Object Name"],
       
   309                 "type": LOCATION_VAR_MEMORY, # LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, or LOCATION_VAR_MEMORY
       
   310                 "size": size_in_bits, # 1 or 16
       
   311                 "IEC_type": IECdatatype, # 'BOOL', 'WORD', ...
       
   312                 "var_name": "var_name", # seems to be ignored??
       
   313                 "location": location_size + location_str + "." + str(xx_ObjProp["Object Identifier"]),
       
   314                 "description": "description", # seems to be ignored?
       
   315                 "children": []})
       
   316         
       
   317         BACnetEntries = []        
       
   318         return  {"name": name,
       
   319                  "type": LOCATION_CONFNODE,
       
   320                  "location": location_str + ".x",
       
   321                  "children": BACnetObjectEntries}
       
   322     
       
   323     
       
   324     # Returns a dictionary with:
       
   325     #      keys: names  of BACnet objects
       
   326     #     value: number of BACnet objects using this same name 
       
   327     #            (values larger than 1 indicates an error as BACnet requires unique names)
       
   328     def GetObjectNamesCount(self):
       
   329         # The dictionary is built by first creating a list containing the names of _ALL_ 
       
   330         # BACnet objects currently configured by the user (using the GUI)
       
   331         ObjectNames = []
       
   332         ObjectNames.extend(self.ObjTables[ "AV_Obj"].GetAllValuesByName("Object Name"))
       
   333         ObjectNames.extend(self.ObjTables[ "AO_Obj"].GetAllValuesByName("Object Name"))
       
   334         ObjectNames.extend(self.ObjTables[ "AI_Obj"].GetAllValuesByName("Object Name"))
       
   335         ObjectNames.extend(self.ObjTables[ "BV_Obj"].GetAllValuesByName("Object Name"))
       
   336         ObjectNames.extend(self.ObjTables[ "BO_Obj"].GetAllValuesByName("Object Name"))
       
   337         ObjectNames.extend(self.ObjTables[ "BI_Obj"].GetAllValuesByName("Object Name"))
       
   338         ObjectNames.extend(self.ObjTables["MSV_Obj"].GetAllValuesByName("Object Name"))
       
   339         ObjectNames.extend(self.ObjTables["MSO_Obj"].GetAllValuesByName("Object Name"))
       
   340         ObjectNames.extend(self.ObjTables["MSI_Obj"].GetAllValuesByName("Object Name"))
       
   341         # This list is then transformed into a collections.Counter class
       
   342         # Which is then transformed into a dictionary using dict()
       
   343         return dict(Counter(ObjectNames))
       
   344       
       
   345     # Check whether the current configuration contains BACnet objects configured
       
   346     # with the same identical object name  (returns True or False)
       
   347     def HasDuplicateObjectNames(self):
       
   348         ObjectNamesCount = self.GetObjectNamesCount()
       
   349         for ObjName in ObjectNamesCount:
       
   350             if ObjectNamesCount[ObjName] > 1:
       
   351                 return True
       
   352         return False
       
   353 
       
   354     # Check whether any object ID is used more than once (not valid in BACnet)
       
   355     # (returns True or False)
       
   356     def HasDuplicateObjectIDs(self):
       
   357         res =        self.ObjTables[ "AV_Obj"].HasDuplicateObjectIDs()
       
   358         res = res or self.ObjTables[ "AO_Obj"].HasDuplicateObjectIDs()
       
   359         res = res or self.ObjTables[ "AI_Obj"].HasDuplicateObjectIDs()
       
   360         res = res or self.ObjTables[ "BV_Obj"].HasDuplicateObjectIDs()
       
   361         res = res or self.ObjTables[ "BO_Obj"].HasDuplicateObjectIDs()
       
   362         res = res or self.ObjTables[ "BI_Obj"].HasDuplicateObjectIDs()
       
   363         res = res or self.ObjTables["MSV_Obj"].HasDuplicateObjectIDs()
       
   364         res = res or self.ObjTables["MSO_Obj"].HasDuplicateObjectIDs()
       
   365         res = res or self.ObjTables["MSI_Obj"].HasDuplicateObjectIDs()
       
   366         return res
       
   367 
       
   368     
       
   369     #######################################################
       
   370     # Methods related to files (saving/loading/exporting) #
       
   371     #######################################################
       
   372     def SaveToFile(self, filepath):
       
   373         # Save node data in file
       
   374         # The configuration data declared in the XSD string will be saved by the ConfigTreeNode class,
       
   375         # so we only need to save the data that is stored in ObjTablesData objects
       
   376         # Note that we do not store the ObjTables objects. ObjTables is of a class that 
       
   377         # contains more stuff we do not need to store. Actually it is a bad idea to store
       
   378         # this extra stuff (as we would make the files we generate dependent on the actual
       
   379         # version of the wx library we are using!!! Remember that ObjTables evetually
       
   380         # derives/inherits from wx.grid.PyGridTableBase). Another reason not to store the whole
       
   381         # object is because it is not pickable (i.e. pickle.dump() cannot handle it)!!
       
   382         try:
       
   383             fd = open(filepath,   "w")
       
   384             pickle.dump(self.ObjTablesData, fd)
       
   385             fd.close()
       
   386             # On successfull save, reset flags to indicate no more changes that need saving
       
   387             self.ObjTables[ "AV_Obj"].ChangesToSave = False
       
   388             self.ObjTables[ "AO_Obj"].ChangesToSave = False
       
   389             self.ObjTables[ "AI_Obj"].ChangesToSave = False
       
   390             self.ObjTables[ "BV_Obj"].ChangesToSave = False
       
   391             self.ObjTables[ "BO_Obj"].ChangesToSave = False
       
   392             self.ObjTables[ "BI_Obj"].ChangesToSave = False
       
   393             self.ObjTables["MSV_Obj"].ChangesToSave = False
       
   394             self.ObjTables["MSO_Obj"].ChangesToSave = False
       
   395             self.ObjTables["MSI_Obj"].ChangesToSave = False
       
   396             return True
       
   397         except:
       
   398             return _("Unable to save to file \"%s\"!")%filepath
       
   399 
       
   400     def LoadFromFile(self, filepath):
       
   401         # Load the data that is saved in SaveToFile()
       
   402         try:
       
   403             fd = open(filepath,   "r")
       
   404             self.ObjTablesData = pickle.load(fd)
       
   405             fd.close()
       
   406             return True
       
   407         except:
       
   408             return _("Unable to load file \"%s\"!")%filepath
       
   409 
       
   410     def _ExportBacnetSlave(self):
       
   411         dialog = wx.FileDialog(self.GetCTRoot().AppFrame, 
       
   412                                _("Choose a file"), 
       
   413                                os.path.expanduser("~"), 
       
   414                                "%s_EDE.csv" % self.CTNName(),  
       
   415                                _("EDE files (*_EDE.csv)|*_EDE.csv|All files|*.*"),
       
   416                                wx.SAVE|wx.OVERWRITE_PROMPT)
       
   417         if dialog.ShowModal() == wx.ID_OK:
       
   418             result = self.GenerateEDEFile(dialog.GetPath())
       
   419             result = False
       
   420             if result:
       
   421                 self.GetCTRoot().logger.write_error(_("Error: Export slave failed\n"))
       
   422         dialog.Destroy()  
       
   423 
       
   424 
       
   425     def GenerateEDEFile(self, filename):
       
   426         template_file_dir     = os.path.join(os.path.split(__file__)[0],"ede_files")
       
   427         
       
   428         #The BACnetServerNode attribute is added dynamically by ConfigTreeNode._AddParamsMembers()
       
   429         # It will be an XML parser object created by GenerateParserFromXSDstring(self.XSD).CreateRoot()
       
   430         BACnet_Device_ID = self.BACnetServerNode.getBACnet_Device_ID()
       
   431         
       
   432         # The EDE file contains a header that includes general project data (name, author, ...)
       
   433         # Instead of asking the user for this data, we get it from the configuration
       
   434         # of the Beremiz project itself.
       
   435         # We ask the root Config Tree Node for the data...
       
   436         ProjProp = {}
       
   437         FileProp = {}
       
   438         CTN_Root      = self.GetCTRoot()   # this should be an object of class ProjectController
       
   439         Project       = CTN_Root.Project   # this should be an object capable of parsing
       
   440                                            # PLCopen XML files. The parser is created automatically
       
   441                                            # (i.e. using GenerateParserFromXSD() from xmlclass module)
       
   442                                            # using the PLCopen XSD file defining the format of the XML.
       
   443                                            # See the file plcopen/plcopen.py
       
   444         if Project is not None:
       
   445           # getcontentHeader() and getfileHeader() are functions that are conditionally defined in
       
   446           # plcopn/plcopen.py    We cannot rely on their existance
       
   447           if getattr(Project, "getcontentHeader", None) is not None:
       
   448             ProjProp = Project.getcontentHeader()
       
   449             # getcontentHeader() returns a dictionary. Available keys are:
       
   450             # "projectName", "projectVersion", "modificationDateTime", 
       
   451             # "organization", "authorName", "language", "pageSize", "scaling"
       
   452           if getattr(Project, "getfileHeader", None) is not None:
       
   453             FileProp = Project.getfileHeader()
       
   454             # getfileHeader() returns a dictionary. Available keys are:
       
   455             # "companyName", "companyURL", "productName", "productVersion",
       
   456             # "productRelease", "creationDateTime", "contentDescription"
       
   457 
       
   458         ProjName   = ""
       
   459         if "projectName" in ProjProp:
       
   460             ProjName    = ProjProp["projectName"]
       
   461         ProjAuthor = ""
       
   462         if "companyName" in FileProp:
       
   463             ProjAuthor += "(" + FileProp["companyName"] + ")"
       
   464         if "authorName" in ProjProp:
       
   465             ProjAuthor  = ProjProp["authorName"] + " " + ProjAuthor
       
   466             
       
   467         projdata_dict = {}
       
   468         projdata_dict["Project Name"]     = ProjName
       
   469         projdata_dict["Project Author"]   = ProjAuthor
       
   470         projdata_dict["Current Time"]     = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
       
   471         projdata_dict["EDE file version"] = self.ObjTablesData["EDEfile_parm"]["next_EDE_file_version"]
       
   472 
       
   473         # Next time we generate an EDE file, use another version!
       
   474         self.ObjTablesData["EDEfile_parm"]["next_EDE_file_version"] += 1
       
   475 
       
   476         AX_params_format = "%(Object Name)s;" + str(BACnet_Device_ID) + ";%(Object Name)s;%(BACnetObjTypeID)s;%(Object Identifier)s;%(Description)s;0;;;%(Settable)s;N;;;;%(Unit ID)s;"
       
   477 
       
   478         BX_params_format = "%(Object Name)s;" + str(BACnet_Device_ID) + ";%(Object Name)s;%(BACnetObjTypeID)s;%(Object Identifier)s;%(Description)s;0;0;1;%(Settable)s;N;;;;;"
       
   479 
       
   480         MSX_params_format = "%(Object Name)s;" + str(BACnet_Device_ID) + ";%(Object Name)s;%(BACnetObjTypeID)s;%(Object Identifier)s;%(Description)s;1;1;%(Number of States)s;%(Settable)s;N;;;;;"
       
   481         
       
   482         Objects_List = []
       
   483         for  ObjType,     params_format   in [
       
   484             ("AV" ,  AX_params_format ), ("AO" ,  AX_params_format ), ("AI" ,  AX_params_format ),
       
   485             ("BV" ,  BX_params_format ), ("BO" ,  BX_params_format ), ("BI" ,  BX_params_format ),
       
   486             ("MSV", MSX_params_format ), ("MSO", MSX_params_format ), ("MSI", MSX_params_format )
       
   487             ]:
       
   488             self.ObjTables[ObjType + "_Obj"].UpdateAllVirtualProperties()
       
   489             for ObjProp in self.ObjTablesData[ObjType + "_Obj"]:
       
   490                 Objects_List.append(params_format % ObjProp)
       
   491         
       
   492         # Normalize filename
       
   493         for extension in ["_EDE.csv", "_ObjTypes.csv", "_StateTexts.csv", "_Units.csv"]:
       
   494             if filename.lower().endswith(extension.lower()):
       
   495                 filename = filename[:-len(extension)]
       
   496         
       
   497         # EDE_header
       
   498         generate_file_name    = filename + "_EDE.csv"
       
   499         template_file_name    = os.path.join(template_file_dir,"template_EDE.csv")
       
   500         generate_file_content = open(template_file_name).read() % projdata_dict
       
   501         generate_file_handle  = open(generate_file_name,'w')
       
   502         generate_file_handle  .write(generate_file_content)
       
   503         generate_file_handle  .write("\n".join(Objects_List))
       
   504         generate_file_handle  .close()
       
   505         
       
   506         # templates of remaining files do not need changes. They are simply copied unchanged!
       
   507         for extension in ["_ObjTypes.csv", "_StateTexts.csv", "_Units.csv"]:
       
   508             generate_file_name    = filename + extension
       
   509             template_file_name    = os.path.join(template_file_dir,"template" + extension)
       
   510             generate_file_content = open(template_file_name).read()
       
   511             generate_file_handle  = open(generate_file_name,'w')
       
   512             generate_file_handle  .write(generate_file_content)
       
   513             generate_file_handle  .close()
       
   514 
       
   515     
       
   516     #############################
       
   517     # Generate the source files #
       
   518     #############################
       
   519     def CTNGenerate_C(self, buildpath, locations):        
       
   520         # Determine the current location in Beremiz's project configuration tree 
       
   521         current_location = self.GetCurrentLocation()
       
   522         # The current location of this plugin in Beremiz's configuration tree, separated by underscores 
       
   523         #  NOTE: Since BACnet plugin currently does not use sub-branches in the tree (in other words, this
       
   524         #        _BacnetSlavePlug class was actually renamed as the RootClass), the current_location_dots
       
   525         #        will actually be a single number (e.g.: 0 or 3 or 6, corresponding to the location
       
   526         #        in which the plugin was inserted in the Beremiz configuration tree on Beremiz's left panel).
       
   527         locstr = "_".join(map(str,current_location))
       
   528 
       
   529         # First check whether all the current parameters (inserted by user in the GUI) are valid...
       
   530         if self.HasDuplicateObjectNames():
       
   531             self.GetCTRoot().logger.write_warning(_("Error: BACnet server '%s.x: %s' contains objects with duplicate object names.\n")%(locstr, self.CTNName()))
       
   532             raise Exception, False
       
   533             # TODO: return an error code instead of raising an exception (currently unsupported by Beremiz)
       
   534 
       
   535         if self.HasDuplicateObjectIDs():
       
   536             self.GetCTRoot().logger.write_warning(_("Error: BACnet server '%s.x: %s' contains objects with duplicate object identifiers.\n")%(locstr, self.CTNName()))
       
   537             raise Exception, False
       
   538             # TODO: return an error code instead of raising an exception (currently unsupported by Beremiz)
       
   539             
       
   540         #-------------------------------------------------------------------------------
       
   541         # Create and populate the loc_dict dictionary with all parameters needed to configure
       
   542         #  the generated source code (.c and .h files)
       
   543         #-------------------------------------------------------------------------------
       
   544         
       
   545         # 1) Create the dictionary (loc_dict = {})
       
   546         loc_dict = {}
       
   547         loc_dict["locstr"]              =         locstr
       
   548 
       
   549         #The BACnetServerNode attribute is added dynamically by ConfigTreeNode._AddParamsMembers()
       
   550         # It will be an XML parser object created by GenerateParserFromXSDstring(self.XSD).CreateRoot()
       
   551         loc_dict["network_interface"]            = self.BACnetServerNode.getNetwork_Interface()
       
   552         loc_dict["port_number"]                  = self.BACnetServerNode.getUDP_Port_Number()
       
   553         loc_dict["BACnet_Device_ID"]             = self.BACnetServerNode.getBACnet_Device_ID()
       
   554         loc_dict["BACnet_Device_Name"]           = self.BACnetServerNode.getBACnet_Device_Name()
       
   555         loc_dict["BACnet_Comm_Control_Password"] = self.BACnetServerNode.getBACnet_Communication_Control_Password()
       
   556         loc_dict["BACnet_Device_Location"]       = self.BACnetServerNode.getBACnet_Device_Location()
       
   557         loc_dict["BACnet_Device_Description"]    = self.BACnetServerNode.getBACnet_Device_Description()
       
   558         loc_dict["BACnet_Device_AppSoft_Version"]= self.BACnetServerNode.getBACnet_Device_Application_Software_Version()
       
   559         loc_dict["BACnet_Vendor_ID"] = BACNET_VENDOR_ID
       
   560         loc_dict["BACnet_Vendor_Name"] = BACNET_VENDOR_NAME
       
   561         loc_dict["BACnet_Model_Name"] = BACNET_DEVICE_MODEL_NAME
       
   562 
       
   563         # 2) Add the data specific to each BACnet object type
       
   564         # For each BACnet object type, start off by creating some intermediate helpful lists
       
   565         #  a) parameters_list containing the strings that will
       
   566         #     be included in the C source code, and which will initialize the struct with the 
       
   567         #     object (Analog Value, Binary Value, or Multi State Value) parameters
       
   568         #  b) locatedvar_list containing the strings that will
       
   569         #     declare the memory to store the located variables, as well as the 
       
   570         #     pointers (required by matiec) that point to that memory.
       
   571 
       
   572         # format for delaring IEC 61131-3 variable (and pointer) onto which BACnet object is mapped
       
   573         locvar_format = '%(Ctype)s ___%(loc)s_%(Object Identifier)s; ' + \
       
   574                         '%(Ctype)s *__%(loc)s_%(Object Identifier)s = &___%(loc)s_%(Object Identifier)s;'
       
   575 
       
   576         # format for initializing a ANALOG_VALUE_DESCR struct in C code
       
   577         #    also valid for ANALOG_INPUT and ANALOG_OUTPUT
       
   578         AX_params_format = '{&___%(loc)s_%(Object Identifier)s, ' + \
       
   579                           '%(Object Identifier)s, "%(Object Name)s", "%(Description)s", %(Unit ID)d}'
       
   580         # format for initializing a BINARY_VALUE_DESCR struct in C code
       
   581         #    also valid for BINARY_INPUT and BINARY_OUTPUT
       
   582         BX_params_format = '{&___%(loc)s_%(Object Identifier)s, ' + \
       
   583                           '%(Object Identifier)s, "%(Object Name)s", "%(Description)s"}'
       
   584 
       
   585         # format for initializing a MULTISTATE_VALUE_DESCR struct in C code
       
   586         #    also valid for MULTISTATE_INPUT and MULTISTATE_OUTPUT
       
   587         MSX_params_format = '{&___%(loc)s_%(Object Identifier)s, ' + \
       
   588                            '%(Object Identifier)s, "%(Object Name)s", "%(Description)s", %(Number of States)s}'
       
   589 
       
   590         AV_locstr  = 'MD' + locstr+'_2'  # see the comment in GetVariableLocationTree() to grok the '_2'
       
   591         AO_locstr  = 'QD' + locstr+'_1'  # see the comment in GetVariableLocationTree() to grok the '_1'
       
   592         AI_locstr  = 'ID' + locstr+'_0'  # see the comment in GetVariableLocationTree() to grok the '_0'
       
   593         BV_locstr  = 'MX' + locstr+'_5'  # see the comment in GetVariableLocationTree() to grok the '_5'
       
   594         BO_locstr  = 'QX' + locstr+'_4'  # see the comment in GetVariableLocationTree() to grok the '_4'
       
   595         BI_locstr  = 'IX' + locstr+'_3'  # see the comment in GetVariableLocationTree() to grok the '_3'
       
   596         MSV_locstr = 'MB' + locstr+'_19' # see the comment in GetVariableLocationTree() to grok the '_19'
       
   597         MSO_locstr = 'QB' + locstr+'_14' # see the comment in GetVariableLocationTree() to grok the '_14'
       
   598         MSI_locstr = 'IB' + locstr+'_13' # see the comment in GetVariableLocationTree() to grok the '_13'
       
   599         
       
   600         
       
   601         for  ObjType,  ObjLocStr,     params_format   in [
       
   602             ("AV"   ,  AV_locstr,  AX_params_format ),
       
   603             ("AO"   ,  AO_locstr,  AX_params_format ),
       
   604             ("AI"   ,  AI_locstr,  AX_params_format ),
       
   605             ("BV"   ,  BV_locstr,  BX_params_format ),
       
   606             ("BO"   ,  BO_locstr,  BX_params_format ),
       
   607             ("BI"   ,  BI_locstr,  BX_params_format ),
       
   608             ("MSV"  , MSV_locstr, MSX_params_format ),
       
   609             ("MSO"  , MSO_locstr, MSX_params_format ),
       
   610             ("MSI"  , MSI_locstr, MSX_params_format )
       
   611             ]:
       
   612             parameters_list = []
       
   613             locatedvar_list = []
       
   614             self.ObjTables[ObjType + "_Obj"].UpdateAllVirtualProperties()
       
   615             for ObjProp in self.ObjTablesData[ObjType + "_Obj"]:
       
   616                 ObjProp["loc" ] = ObjLocStr
       
   617                 parameters_list.append(params_format % ObjProp)
       
   618                 locatedvar_list.append(locvar_format % ObjProp)
       
   619             loc_dict[ ObjType + "_count"]           =          len( parameters_list )
       
   620             loc_dict[ ObjType + "_param"]           =   ",\n".join( parameters_list )
       
   621             loc_dict[ ObjType + "_lvars"]           =    "\n".join( locatedvar_list )
       
   622 
       
   623         #----------------------------------------------------------------------
       
   624         # Create the C source files that implement the BACnet server
       
   625         #----------------------------------------------------------------------
       
   626         
       
   627         # Names of the .c files that will be generated, based on a template file with same name
       
   628         #   (names without '.c'  --> this will be added later)
       
   629         #   main server.c file is handled separately
       
   630         Generated_BACnet_c_mainfile = "server"
       
   631         Generated_BACnet_c_files = ["ai", "ao", "av", "bi", "bo", "bv", "msi", "mso", "msv", "device"]
       
   632 
       
   633         # Names of the .h files that will be generated, based on a template file with same name
       
   634         #   (names without '.h'  --> this will be added later)
       
   635         Generated_BACnet_h_files = ["server", "device", "config_bacnet_for_beremiz",
       
   636                                     "ai", "ao", "av", "bi", "bo", "bv", "msi", "mso", "msv"
       
   637                                    ]
       
   638 
       
   639         # Generate the files with the source code
       
   640         postfix = "_".join(map(str, current_location))
       
   641         template_file_dir     = os.path.join(os.path.split(__file__)[0],"runtime")
       
   642         
       
   643         def generate_file(file_name, extension): 
       
   644             generate_file_name    = os.path.join(buildpath, "%s_%s.%s"%(file_name,postfix,extension))
       
   645             template_file_name    = os.path.join(template_file_dir,"%s.%s"%(file_name,extension))
       
   646             generate_file_content = open(template_file_name).read() % loc_dict
       
   647             generate_file_handle  = open(generate_file_name,'w')
       
   648             generate_file_handle  .write(generate_file_content)
       
   649             generate_file_handle  .close()
       
   650 
       
   651         for file_name in Generated_BACnet_c_files:
       
   652             generate_file(file_name, "c")
       
   653         for file_name in Generated_BACnet_h_files:
       
   654             generate_file(file_name, "h")
       
   655         generate_file(Generated_BACnet_c_mainfile, "c")
       
   656         Generated_BACnet_c_mainfile_name = \
       
   657             os.path.join(buildpath, "%s_%s.%s"%(Generated_BACnet_c_mainfile,postfix,"c"))
       
   658 
       
   659         #----------------------------------------------------------------------
       
   660         # Finally, define the compilation and linking commands and flags
       
   661         #----------------------------------------------------------------------
       
   662         
       
   663         LDFLAGS = []
       
   664         # when using dynamically linked library...
       
   665         #LDFLAGS.append(' -lbacnet')   
       
   666         #LDFLAGS.append(' -L"'+BacnetLibraryPath+'"')
       
   667         #LDFLAGS.append(' "-Wl,-rpath,' + BacnetLibraryPath + '"')
       
   668         # when using static library:
       
   669         LDFLAGS.append(' "'+os.path.join(BacnetLibraryPath, "libbacnet.a")+'"')  
       
   670 
       
   671         CFLAGS   = ' -I"'+BacnetIncludePath+'"'
       
   672         CFLAGS  += ' -I"'+BacnetIncludePortPath+'"'
       
   673         
       
   674         return [(Generated_BACnet_c_mainfile_name, CFLAGS)], LDFLAGS, True
       
   675 
       
   676 
       
   677 
       
   678 ###################################################
       
   679 ###################################################
       
   680 #                                                 #
       
   681 #             R O O T    C L A S S                # 
       
   682 #                                                 #
       
   683 ###################################################
       
   684 ###################################################
       
   685 #class RootClass:
       
   686     #XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
       
   687     #<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
       
   688       #<xsd:element name="BACnetRoot">
       
   689       #</xsd:element>
       
   690     #</xsd:schema>
       
   691     #"""
       
   692     #CTNChildrenTypes = [("BacnetSlave", _BacnetSlavePlug,  "Bacnet Slave")
       
   693                        ##,("XXX",_XXXXPlug, "XXX")
       
   694                        #]
       
   695     
       
   696     #def CTNGenerate_C(self, buildpath, locations):        
       
   697         #return [], "", True