Edouard@2020: #!/usr/bin/env python
Edouard@2020: # -*- coding: utf-8 -*-
Edouard@2020: 
Edouard@2020: # This file is part of Beremiz, a Integrated Development Environment for
Edouard@2020: # programming IEC 61131-3 automates supporting plcopen standard.
Edouard@2020: # This files implements the bacnet plugin for Beremiz, adding BACnet server support.
Edouard@2020: #
Edouard@2020: # Copyright (c) 2017 Mario de Sousa (msousa@fe.up.pt)
Edouard@2020: #
Edouard@2020: # This program is free software: you can redistribute it and/or modify
Edouard@2020: # it under the terms of the GNU General Public License as published by
Edouard@2020: # the Free Software Foundation, either version 2 of the License, or
Edouard@2020: # (at your option) any later version.
Edouard@2020: #
Edouard@2020: # This program is distributed in the hope that it will be useful,
Edouard@2020: # but WITHOUT ANY WARRANTY; without even the implied warranty of
Edouard@2020: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Edouard@2020: # GNU General Public License for more details.
Edouard@2020: #
Edouard@2020: # You should have received a copy of the GNU General Public License
Edouard@2020: # along with this program.  If not, see <http://www.gnu.org/licenses/>.
Edouard@2250: #
Edouard@2020: # This code is made available on the understanding that it will not be
Edouard@2020: # used in safety-critical situations without a full and competent review.
Edouard@2020: 
Edouard@2250: from __future__ import absolute_import
Edouard@2250: 
Edouard@2250: import os
Edouard@2020: from collections import Counter
Edouard@2250: from datetime import datetime
Edouard@2250: import pickle
Edouard@2250: 
Edouard@2250: import wx
Edouard@2250: 
Edouard@2250: from bacnet.BacnetSlaveEditor import *
Edouard@2250: from bacnet.BacnetSlaveEditor import ObjectProperties
Edouard@2250: from PLCControler import LOCATION_CONFNODE, LOCATION_VAR_MEMORY
msousa@2649: from ConfigTreeNode import ConfigTreeNode
Edouard@2669: import util.paths as paths
msousa@2649: 
Edouard@2736: BacnetPath = paths.ThirdPartyPath("BACnet")
Edouard@2250: BacnetLibraryPath = os.path.join(BacnetPath, "lib")
Edouard@2250: BacnetIncludePath = os.path.join(BacnetPath, "include")
Edouard@2020: BacnetIncludePortPath = os.path.join(BacnetPath, "ports")
Edouard@2020: BacnetIncludePortPath = os.path.join(BacnetIncludePortPath, "linux")
Edouard@2020: 
Edouard@2020: # Parameters to be monkey patched in beremiz customizations
Edouard@2250: BACNET_VENDOR_ID = 9999
Edouard@2020: BACNET_VENDOR_NAME = "Beremiz.org"
Edouard@2020: BACNET_DEVICE_MODEL_NAME = "Beremiz PLC"
Edouard@2020: 
msousa@2649: 
msousa@2649: # Max String Size of BACnet Paramaters
msousa@2649: BACNET_PARAM_STRING_SIZE = 64
msousa@2649: 
Edouard@2250: #
Edouard@2250: #
Edouard@2250: #
Edouard@2250: # S L A V E    D E V I C E              #
Edouard@2250: #
Edouard@2250: #
Edouard@2250: #
Edouard@2020: 
Edouard@2020: # NOTE: Objects of class _BacnetSlavePlug are never instantiated directly.
Edouard@2020: #       The objects are instead instantiated from class FinalCTNClass
Edouard@2020: #       FinalCTNClass inherits from: - ConfigTreeNode
Edouard@2020: #                                    - The tree node plug (in our case _BacnetSlavePlug)
Edouard@2250: # class _BacnetSlavePlug:
Edouard@2250: 
Edouard@2250: 
Edouard@2250: class RootClass(object):
Edouard@2020:     XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
Edouard@2020:     <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
Edouard@2020:       <xsd:element name="BACnetServerNode">
Edouard@2020:         <xsd:complexType>
Edouard@2020:           <xsd:attribute name="Network_Interface"      type="xsd:string"  use="optional" default="eth0"/>
Edouard@2020:           <xsd:attribute name="UDP_Port_Number"                           use="optional" default="47808">
Edouard@2020:             <xsd:simpleType>
Edouard@2020:                 <xsd:restriction base="xsd:integer">
Edouard@2020:                     <xsd:minInclusive value="0"/>
Edouard@2020:                     <xsd:maxInclusive value="65535"/>
Edouard@2020:                 </xsd:restriction>
Edouard@2020:             </xsd:simpleType>
Edouard@2020:           </xsd:attribute>
Edouard@2250:           <xsd:attribute name="BACnet_Communication_Control_Password"
Edouard@2020:                                                        type="xsd:string"  use="optional" default="Malba Tahan"/>
Edouard@2020:           <xsd:attribute name="BACnet_Device_ID"                          use="optional" default="0">
Edouard@2020:             <xsd:simpleType>
Edouard@2020:                 <xsd:restriction base="xsd:integer">
Edouard@2020:                     <xsd:minInclusive value="0"/>
Edouard@2020:                     <xsd:maxInclusive value="4194302"/>
Edouard@2020:                 </xsd:restriction>
Edouard@2020:             </xsd:simpleType>
Edouard@2020:           </xsd:attribute>
Edouard@2020:           <xsd:attribute name="BACnet_Device_Name"        type="xsd:string"  use="optional" default="Beremiz device 0"/>
Edouard@2020:           <xsd:attribute name="BACnet_Device_Location"    type="xsd:string"  use="optional" default=""/>
Edouard@2020:           <xsd:attribute name="BACnet_Device_Description" type="xsd:string"  use="optional" default="Beremiz device 0"/>
Edouard@2020:           <xsd:attribute name="BACnet_Device_Application_Software_Version" type="xsd:string"  use="optional" default="1.0"/>
Edouard@2020:         </xsd:complexType>
Edouard@2020:       </xsd:element>
Edouard@2020:     </xsd:schema>
Edouard@2020:     """
msousa@2649:     # NOTE; Add the following code/declaration to the aboce XSD in order to activate the
msousa@2649:     #          Override_Parameters_Saved_on_PLC   flag (currenty not in use as it requires further
msousa@2649:     #          analysis how the user would interpret this user interface option.
msousa@2649:     #        <--- snip --->
msousa@2649:     #          <xsd:attribute name="Override_Parameters_Saved_on_PLC" 
msousa@2649:     #                                                   type="xsd:boolean" use="optional" default="true"/>
msousa@2649:     #        <--- snip --->
msousa@2649:     #
Edouard@2020:     # NOTE: BACnet device (object) IDs are 22 bits long (not counting the 10 bits for the type ID)
Edouard@2020:     #       so the Device instance ID is limited from 0 to 22^2-1 = 4194303
Edouard@2020:     #       However, 4194303 is reserved for special use (similar to NULL pointer), so last
Edouard@2020:     #       valid ID becomes 4194302
Edouard@2250: 
Edouard@2020:     # The class/object that will render the graphical interface to edit the
Edouard@2020:     #    BacnetSlavePlug's configuration parameters. The object of class BacnetSlaveEditorPlug
Edouard@2020:     #    will be instantiated by the ConfigTreeNode class.
Edouard@2020:     #    This BacnetSlaveEditorPlug object can be accessed from _BacnetSlavePlug as
Edouard@2020:     #    'self._View'
Edouard@2020:     #    See the following note to understand how this is possible!
Edouard@2020:     #
Edouard@2020:     # NOTE: Objects of class _BacnetSlavePlug are never instantiated directly.
Edouard@2020:     #       The objects are instead instantiated from class FinalCTNClass
Edouard@2020:     #       FinalCTNClass inherits from: - ConfigTreeNode
Edouard@2020:     #                                    - The tree node plug (in our case _BacnetSlavePlug)
Edouard@2020:     #
Edouard@2020:     #       This means that objects of class _BacnetSlavePlug may safely access all the members
Edouard@2020:     #       of classes ConfigTreeNode as well as FinalCTNClass (since they are always instantiated
Edouard@2020:     #       as a FinalCTNClass)
Edouard@2020:     EditorType = BacnetSlaveEditorPlug
Edouard@2250: 
Edouard@2020:     # The following classes follow the model/viewer design pattern
Edouard@2020:     #
Edouard@2020:     # _BacnetSlavePlug       - contains the model (i.e. configuration parameters)
Edouard@2250:     # BacnetSlaveEditorPlug  - contains the viewer (and editor, so it includes the 'controller' part of the
Edouard@2020:     #                                    design pattern which in this case is not separated from the viewer)
Edouard@2020:     #
Edouard@2250:     # The _BacnetSlavePlug      object is 'permanent', i.e. it exists as long as the beremiz project is open
Edouard@2020:     # The BacnetSlaveEditorPlug object is 'transient', i.e. it exists only while the editor is visible/open
Edouard@2020:     #                                                         in the editing panel. It is destoryed whenever
Edouard@2250:     #                                                         the user closes the corresponding tab in the
Edouard@2020:     #                                                         editing panel, and a new object is created when
Edouard@2020:     #                                                         the editor is re-opened.
Edouard@2020:     #
Edouard@2020:     # _BacnetSlavePlug contains:  AV_ObjTable, ...
Edouard@2020:     #                             (these are the objects that actually store the config parameters or 'model'
Edouard@2020:     #                              and are therefore stored to a file)
Edouard@2020:     #
Edouard@2020:     # _BacnetSlavePlug contains:  AV_VarEditor, ...
Edouard@2250:     #                             (these are the objects that implement a grid table to edit/view the
Edouard@2020:     #                              corresponding mode parameters)
Edouard@2250:     #
Edouard@2020:     #  Logic:
Edouard@2020:     #    - The xx_VarEditor classes inherit from wx.grid.Grid
Edouard@2020:     #    - The xx_ObjTable  classes inherit from wx.grid.PyGridTableBase
Edouard@2020:     #  To be more precise, the inheritance tree is actually:
Edouard@2020:     #    xx_VarEditor -> ObjectGrid -> CustomGrid   -> wx.grid.Grid
Edouard@2020:     #    xx_ObjTable  -> ObjectTable -> CustomTable -> wx.grid.PyGridTableBase)
Edouard@2020:     #
Edouard@2020:     #  Note that wx.grid.Grid is prepared to work with wx.grid.PyGridTableBase as the container of
Edouard@2020:     #  data that is displayed and edited in the Grid.
Edouard@2020: 
Edouard@2020:     ConfNodeMethods = [
Edouard@2250:         {"bitmap": "ExportSlave",
Edouard@2250:          "name": _("Export slave"),
Edouard@2250:          "tooltip": _("Export BACnet slave to EDE file"),
Edouard@2250:          "method": "_ExportBacnetSlave"},
Edouard@2020:     ]
Edouard@2250: 
Edouard@2020:     def __init__(self):
Edouard@2250:         # Initialize the dictionary that stores the current configuration for the Analog/Digital/MultiValued Variables
Edouard@2020:         #   in this BACnet server.
Edouard@2020:         self.ObjTablesData = {}
Edouard@2250: 
Edouard@2250:         # Each list will contain an entry for each row in the xxxxVar grid!!
Edouard@2250:         #   Each entry/row will be a dictionary
Edouard@2250:         #     Each dictionary will contain all entries/data
Edouard@2250:         # for one row in the grid.
Edouard@2250: 
Edouard@2250:         self.ObjTablesData["AV_Obj"] = []
Edouard@2250:         self.ObjTablesData["AO_Obj"] = []
Edouard@2250:         self.ObjTablesData["AI_Obj"] = []
Edouard@2250:         self.ObjTablesData["BV_Obj"] = []
Edouard@2250:         self.ObjTablesData["BO_Obj"] = []
Edouard@2250:         self.ObjTablesData["BI_Obj"] = []
Edouard@2250:         self.ObjTablesData["MSV_Obj"] = []
Edouard@2250:         self.ObjTablesData["MSO_Obj"] = []
Edouard@2250:         self.ObjTablesData["MSI_Obj"] = []
Edouard@2250: 
Edouard@2250:         self.ObjTablesData["EDEfile_parm"] = {"next_EDE_file_version": 1}
Edouard@2250: 
Edouard@2250:         # EDE files inlcude extra parameters (ex. file version)
Edouard@2250:         # We would like to save the parameters the user configures
Edouard@2250:         # so they are available the next time the user opens the project.
Edouard@2250:         # Since this plugin is only storing the ObjTablesData[] dict
Edouard@2250:         # to file, we add that info to this dictionary too.
Edouard@2250:         # Yes, I know this is kind of a
Edouard@2250:         # hack.
Edouard@2250: 
Edouard@2020:         filepath = self.GetFileName()
Edouard@2250:         if os.path.isfile(filepath):
Edouard@2020:             self.LoadFromFile(filepath)
Edouard@2020: 
Edouard@2020:         self.ObjTables = {}
Edouard@2250:         self.ObjTables["AV_Obj"] = ObjectTable(
Edouard@2250:             self, self.ObjTablesData["AV_Obj"],  AVObject)
Edouard@2250:         self.ObjTables["AO_Obj"] = ObjectTable(
Edouard@2250:             self, self.ObjTablesData["AO_Obj"],  AOObject)
Edouard@2250:         self.ObjTables["AI_Obj"] = ObjectTable(
Edouard@2250:             self, self.ObjTablesData["AI_Obj"],  AIObject)
Edouard@2250:         self.ObjTables["BV_Obj"] = ObjectTable(
Edouard@2250:             self, self.ObjTablesData["BV_Obj"],  BVObject)
Edouard@2250:         self.ObjTables["BO_Obj"] = ObjectTable(
Edouard@2250:             self, self.ObjTablesData["BO_Obj"],  BOObject)
Edouard@2250:         self.ObjTables["BI_Obj"] = ObjectTable(
Edouard@2250:             self, self.ObjTablesData["BI_Obj"],  BIObject)
Edouard@2250:         self.ObjTables["MSV_Obj"] = ObjectTable(
Edouard@2250:             self, self.ObjTablesData["MSV_Obj"], MSVObject)
Edouard@2250:         self.ObjTables["MSO_Obj"] = ObjectTable(
Edouard@2250:             self, self.ObjTablesData["MSO_Obj"], MSOObject)
Edouard@2250:         self.ObjTables["MSI_Obj"] = ObjectTable(
Edouard@2250:             self, self.ObjTablesData["MSI_Obj"], MSIObject)
Edouard@2250: 
Edouard@2250:     #
Edouard@2020:     # Functions to be called by CTNClass #
Edouard@2250:     #
Edouard@2020:     # The following functions would be somewhat equvalent to virtual functions/methods in C++ classes
Edouard@2250:     # They will be called by the base class (CTNClass) from which this
Edouard@2250:     # _BacnetSlavePlug class derives.
Edouard@2250: 
Edouard@2020:     def GetCurrentNodeName(self):
Edouard@2020:         return self.CTNName()
Edouard@2020: 
Edouard@2020:     def GetFileName(self):
Edouard@2020:         return os.path.join(self.CTNPath(), 'bacnet_slave')
Edouard@2250: 
Edouard@2020:     def OnCTNSave(self, from_project_path=None):
Edouard@2250:         return self.SaveToFile(self.GetFileName())
Edouard@2020: 
Edouard@2020:     def CTNTestModified(self):
Edouard@2020:         # self.ChangesToSave: Check whether any of the parameters, defined in the XSD above, were changed.
Edouard@2020:         #                     This is handled by the ConfigTreeNode class
Edouard@2250:         #                     (Remember that no objects are ever instantiated from _BacnetSlavePlug.
Edouard@2020:         #                      Objects are instead created from FinalCTNClass, which derives from
Edouard@2020:         #                      _BacnetSlavePlug and ConfigTreeNode. This means that we can exceptionally
Edouard@2250:         # consider that all objects of type _BacnetSlavePlug will also be a
Edouard@2250:         # ConfigTreeNode).
Edouard@2250:         result = self.ChangesToSave \
Edouard@2250:             or self.ObjTables["AV_Obj"].ChangesToSave \
Edouard@2250:             or self.ObjTables["AO_Obj"].ChangesToSave \
Edouard@2250:             or self.ObjTables["AI_Obj"].ChangesToSave \
Edouard@2250:             or self.ObjTables["BV_Obj"].ChangesToSave \
Edouard@2250:             or self.ObjTables["BO_Obj"].ChangesToSave \
Edouard@2250:             or self.ObjTables["BI_Obj"].ChangesToSave \
Edouard@2250:             or self.ObjTables["MSV_Obj"].ChangesToSave \
Edouard@2250:             or self.ObjTables["MSO_Obj"].ChangesToSave \
Edouard@2250:             or self.ObjTables["MSI_Obj"].ChangesToSave
Edouard@2020:         return result
Edouard@2020: 
Edouard@2250:     # Currently not needed. Override _OpenView() in case we need to do some special stuff whenever the editor is opened!
Edouard@2250:     # def _OpenView(self, name=None, onlyopened=False):
Edouard@2250:         # print "_BacnetSlavePlug._OpenView() Called!!!"
Edouard@2250:         # ConfigTreeNode._OpenView(self, name, onlyopened)
Edouard@2250:         # print self._View
Edouard@2250:         # if self._View is not None:
Edouard@2250:         #     self._View.SetBusId(self.GetCurrentLocation())
Edouard@2250:         # return self._View
Edouard@2020: 
Edouard@2020:     def GetVariableLocationTree(self):
Edouard@2020:         current_location = self.GetCurrentLocation()
Edouard@2020:         # see comment in CTNGenerate_C regarding identical line of code!
Edouard@2250:         locstr = ".".join(map(str, current_location))
Edouard@2250: 
Edouard@2020:         # IDs used by BACnet to identify object types/class.
Edouard@2020:         #     OBJECT_ANALOG_INPUT       =  0,
Edouard@2020:         #     OBJECT_ANALOG_OUTPUT      =  1,
Edouard@2020:         #     OBJECT_ANALOG_VALUE       =  2,
Edouard@2020:         #     OBJECT_BINARY_INPUT       =  3,
Edouard@2020:         #     OBJECT_BINARY_OUTPUT      =  4,
Edouard@2020:         #     OBJECT_BINARY_VALUE       =  5,
Edouard@2020:         #     OBJECT_MULTI_STATE_INPUT  = 13,
Edouard@2020:         #     OBJECT_MULTI_STATE_OUTPUT = 14,
Edouard@2020:         #     OBJECT_MULTI_STATE_VALUE  = 19,
Edouard@2020:         #
Edouard@2020:         #  Since Binary Value, Analog Value, etc. objects may use the same
Edouard@2020:         # object ID (since they have distinct class IDs), we must also distinguish them in some way in
Edouard@2020:         # the %MX0.3.4 IEC 61131-3 syntax.
Edouard@2020:         #
Edouard@2020:         # For this reason we add the BACnet class identifier to the %MX0.5.3 location.
Edouard@2020:         # For example, for a BACnet plugin in location '0' of the Beremiz configuration tree,
Edouard@2020:         #  all      Binary Values will be mapped onto: %MX0.5.xxx    (xxx is object ID)
Edouard@2020:         #  all Multi State Values will be mapped onto: %MB0.19.xxx   (xxx is object ID)
Edouard@2020:         #  all      Analog Values will be mapped onto: %MD0.2.xxx    (xxx is object ID)
Edouard@2020:         #  etc..
Edouard@2020:         #
Edouard@2020:         #   Value objects will be mapped onto %M
Edouard@2020:         #   Input objects will be mapped onto %I
Edouard@2020:         #  Output objects will be mapped onto %Q
Edouard@2250: 
Edouard@2020:         BACnetEntries = []
Edouard@2020:         BACnetEntries.append(self.GetSlaveLocationTree(
Edouard@2250:             self.ObjTablesData["AV_Obj"], 32, 'REAL', 'D', locstr + '.2', 'Analog Values'))
Edouard@2250:         BACnetEntries.append(self.GetSlaveLocationTree(
Edouard@2250:             self.ObjTablesData["AO_Obj"], 32, 'REAL', 'D', locstr + '.1', 'Analog Outputs'))
Edouard@2250:         BACnetEntries.append(self.GetSlaveLocationTree(
Edouard@2250:             self.ObjTablesData["AI_Obj"], 32, 'REAL', 'D', locstr + '.0', 'Analog Inputs'))
Edouard@2250:         BACnetEntries.append(self.GetSlaveLocationTree(
Edouard@2250:             self.ObjTablesData["BV_Obj"],  1, 'BOOL', 'X', locstr + '.5', 'Binary Values'))
Edouard@2250:         BACnetEntries.append(self.GetSlaveLocationTree(
Edouard@2250:             self.ObjTablesData["BO_Obj"],  1, 'BOOL', 'X', locstr + '.4', 'Binary Outputs'))
Edouard@2250:         BACnetEntries.append(self.GetSlaveLocationTree(
Edouard@2250:             self.ObjTablesData["BI_Obj"],  1, 'BOOL', 'X', locstr + '.3', 'Binary Inputs'))
Edouard@2250:         BACnetEntries.append(self.GetSlaveLocationTree(
Edouard@2250:             self.ObjTablesData["MSV_Obj"],  8, 'BYTE', 'B', locstr + '.19', 'Multi State Values'))
Edouard@2250:         BACnetEntries.append(self.GetSlaveLocationTree(
Edouard@2250:             self.ObjTablesData["MSO_Obj"],  8, 'BYTE', 'B', locstr + '.14', 'Multi State Outputs'))
Edouard@2250:         BACnetEntries.append(self.GetSlaveLocationTree(
Edouard@2250:             self.ObjTablesData["MSI_Obj"],  8, 'BYTE', 'B', locstr + '.13', 'Multi State Inputs'))
Edouard@2250: 
Edouard@2250:         return {"name": self.BaseParams.getName(),
Edouard@2250:                 "type": LOCATION_CONFNODE,
Edouard@2250:                 "location": locstr + ".x",
Edouard@2250:                 "children": BACnetEntries}
Edouard@2250: 
Edouard@2250:     #
Edouard@2020:     # Helper functions/methods #
Edouard@2250:     #
Edouard@2020:     # a helper function to GetVariableLocationTree()
Edouard@2020:     def GetSlaveLocationTree(self, ObjTablesData, size_in_bits, IECdatatype, location_size, location_str, name):
Edouard@2020:         BACnetObjectEntries = []
Edouard@2250:         for xx_ObjProp in ObjTablesData:
Edouard@2020:             BACnetObjectEntries.append({
Edouard@2020:                 "name": str(xx_ObjProp["Object Identifier"]) + ': ' + xx_ObjProp["Object Name"],
Edouard@2250:                 "type": LOCATION_VAR_MEMORY,  # LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, or LOCATION_VAR_MEMORY
Edouard@2250:                 "size": size_in_bits,  # 1 or 16
Edouard@2250:                 "IEC_type": IECdatatype,  # 'BOOL', 'WORD', ...
Edouard@2250:                 "var_name": "var_name",  # seems to be ignored??
Edouard@2020:                 "location": location_size + location_str + "." + str(xx_ObjProp["Object Identifier"]),
Edouard@2250:                 "description": "description",  # seems to be ignored?
Edouard@2020:                 "children": []})
Edouard@2250: 
Edouard@2250:         return {"name": name,
Edouard@2250:                 "type": LOCATION_CONFNODE,
Edouard@2250:                 "location": location_str + ".x",
Edouard@2250:                 "children": BACnetObjectEntries}
Edouard@2250: 
Edouard@2020:     # Returns a dictionary with:
Edouard@2020:     #      keys: names  of BACnet objects
Edouard@2250:     #     value: number of BACnet objects using this same name
Edouard@2020:     #            (values larger than 1 indicates an error as BACnet requires unique names)
Edouard@2020:     def GetObjectNamesCount(self):
Edouard@2250:         # The dictionary is built by first creating a list containing the names of _ALL_
Edouard@2020:         # BACnet objects currently configured by the user (using the GUI)
Edouard@2020:         ObjectNames = []
Edouard@2250:         ObjectNames.extend(
Edouard@2250:             self.ObjTables["AV_Obj"].GetAllValuesByName("Object Name"))
Edouard@2250:         ObjectNames.extend(
Edouard@2250:             self.ObjTables["AO_Obj"].GetAllValuesByName("Object Name"))
Edouard@2250:         ObjectNames.extend(
Edouard@2250:             self.ObjTables["AI_Obj"].GetAllValuesByName("Object Name"))
Edouard@2250:         ObjectNames.extend(
Edouard@2250:             self.ObjTables["BV_Obj"].GetAllValuesByName("Object Name"))
Edouard@2250:         ObjectNames.extend(
Edouard@2250:             self.ObjTables["BO_Obj"].GetAllValuesByName("Object Name"))
Edouard@2250:         ObjectNames.extend(
Edouard@2250:             self.ObjTables["BI_Obj"].GetAllValuesByName("Object Name"))
Edouard@2250:         ObjectNames.extend(
Edouard@2250:             self.ObjTables["MSV_Obj"].GetAllValuesByName("Object Name"))
Edouard@2250:         ObjectNames.extend(
Edouard@2250:             self.ObjTables["MSO_Obj"].GetAllValuesByName("Object Name"))
Edouard@2250:         ObjectNames.extend(
Edouard@2250:             self.ObjTables["MSI_Obj"].GetAllValuesByName("Object Name"))
Edouard@2020:         # This list is then transformed into a collections.Counter class
Edouard@2020:         # Which is then transformed into a dictionary using dict()
Edouard@2020:         return dict(Counter(ObjectNames))
Edouard@2250: 
Edouard@2020:     # Check whether the current configuration contains BACnet objects configured
Edouard@2020:     # with the same identical object name  (returns True or False)
Edouard@2020:     def HasDuplicateObjectNames(self):
Edouard@2020:         ObjectNamesCount = self.GetObjectNamesCount()
Edouard@2020:         for ObjName in ObjectNamesCount:
Edouard@2020:             if ObjectNamesCount[ObjName] > 1:
Edouard@2020:                 return True
Edouard@2020:         return False
Edouard@2020: 
Edouard@2020:     # Check whether any object ID is used more than once (not valid in BACnet)
Edouard@2020:     # (returns True or False)
Edouard@2020:     def HasDuplicateObjectIDs(self):
Edouard@2250:         res = self.ObjTables["AV_Obj"].HasDuplicateObjectIDs()
Edouard@2250:         res = res or self.ObjTables["AO_Obj"].HasDuplicateObjectIDs()
Edouard@2250:         res = res or self.ObjTables["AI_Obj"].HasDuplicateObjectIDs()
Edouard@2250:         res = res or self.ObjTables["BV_Obj"].HasDuplicateObjectIDs()
Edouard@2250:         res = res or self.ObjTables["BO_Obj"].HasDuplicateObjectIDs()
Edouard@2250:         res = res or self.ObjTables["BI_Obj"].HasDuplicateObjectIDs()
Edouard@2020:         res = res or self.ObjTables["MSV_Obj"].HasDuplicateObjectIDs()
Edouard@2020:         res = res or self.ObjTables["MSO_Obj"].HasDuplicateObjectIDs()
Edouard@2020:         res = res or self.ObjTables["MSI_Obj"].HasDuplicateObjectIDs()
Edouard@2020:         return res
Edouard@2020: 
Edouard@2250:     #
Edouard@2020:     # Methods related to files (saving/loading/exporting) #
Edouard@2250:     #
Edouard@2020:     def SaveToFile(self, filepath):
Edouard@2020:         # Save node data in file
Edouard@2020:         # The configuration data declared in the XSD string will be saved by the ConfigTreeNode class,
Edouard@2020:         # so we only need to save the data that is stored in ObjTablesData objects
Edouard@2250:         # Note that we do not store the ObjTables objects. ObjTables is of a class that
Edouard@2020:         # contains more stuff we do not need to store. Actually it is a bad idea to store
Edouard@2020:         # this extra stuff (as we would make the files we generate dependent on the actual
Edouard@2020:         # version of the wx library we are using!!! Remember that ObjTables evetually
Edouard@2020:         # derives/inherits from wx.grid.PyGridTableBase). Another reason not to store the whole
Edouard@2020:         # object is because it is not pickable (i.e. pickle.dump() cannot handle it)!!
Edouard@2020:         try:
Edouard@2020:             fd = open(filepath,   "w")
Edouard@2020:             pickle.dump(self.ObjTablesData, fd)
Edouard@2020:             fd.close()
Edouard@2250:             # On successfull save, reset flags to indicate no more changes that
Edouard@2250:             # need saving
Edouard@2250:             self.ObjTables["AV_Obj"].ChangesToSave = False
Edouard@2250:             self.ObjTables["AO_Obj"].ChangesToSave = False
Edouard@2250:             self.ObjTables["AI_Obj"].ChangesToSave = False
Edouard@2250:             self.ObjTables["BV_Obj"].ChangesToSave = False
Edouard@2250:             self.ObjTables["BO_Obj"].ChangesToSave = False
Edouard@2250:             self.ObjTables["BI_Obj"].ChangesToSave = False
Edouard@2020:             self.ObjTables["MSV_Obj"].ChangesToSave = False
Edouard@2020:             self.ObjTables["MSO_Obj"].ChangesToSave = False
Edouard@2020:             self.ObjTables["MSI_Obj"].ChangesToSave = False
Edouard@2020:             return True
edouard@2309:         except Exception:
Edouard@2250:             return _("Unable to save to file \"%s\"!") % filepath
Edouard@2020: 
Edouard@2020:     def LoadFromFile(self, filepath):
Edouard@2020:         # Load the data that is saved in SaveToFile()
Edouard@2020:         try:
Edouard@2020:             fd = open(filepath,   "r")
Edouard@2020:             self.ObjTablesData = pickle.load(fd)
Edouard@2020:             fd.close()
Edouard@2020:             return True
edouard@2309:         except Exception:
Edouard@2250:             return _("Unable to load file \"%s\"!") % filepath
Edouard@2020: 
Edouard@2020:     def _ExportBacnetSlave(self):
Edouard@2250:         dialog = wx.FileDialog(self.GetCTRoot().AppFrame,
Edouard@2250:                                _("Choose a file"),
Edouard@2250:                                os.path.expanduser("~"),
Edouard@2250:                                "%s_EDE.csv" % self.CTNName(),
Edouard@2020:                                _("EDE files (*_EDE.csv)|*_EDE.csv|All files|*.*"),
Edouard@2250:                                wx.SAVE | wx.OVERWRITE_PROMPT)
Edouard@2020:         if dialog.ShowModal() == wx.ID_OK:
Edouard@2020:             result = self.GenerateEDEFile(dialog.GetPath())
Edouard@2020:             result = False
Edouard@2020:             if result:
Edouard@2250:                 self.GetCTRoot().logger.write_error(
Edouard@2250:                     _("Error: Export slave failed\n"))
Edouard@2250:         dialog.Destroy()
Edouard@2020: 
Edouard@2020:     def GenerateEDEFile(self, filename):
Edouard@2250:         template_file_dir = os.path.join(
Edouard@2250:             os.path.split(__file__)[0], "ede_files")
Edouard@2250: 
Edouard@2250:         # The BACnetServerNode attribute is added dynamically by ConfigTreeNode._AddParamsMembers()
Edouard@2250:         # It will be an XML parser object created by
Edouard@2250:         # GenerateParserFromXSDstring(self.XSD).CreateRoot()
Edouard@2020:         BACnet_Device_ID = self.BACnetServerNode.getBACnet_Device_ID()
Edouard@2250: 
Edouard@2020:         # The EDE file contains a header that includes general project data (name, author, ...)
Edouard@2020:         # Instead of asking the user for this data, we get it from the configuration
Edouard@2020:         # of the Beremiz project itself.
Edouard@2020:         # We ask the root Config Tree Node for the data...
Edouard@2020:         ProjProp = {}
Edouard@2020:         FileProp = {}
Edouard@2250: 
Edouard@2250:         # this should be an object of class ProjectController
Edouard@2250:         CTN_Root = self.GetCTRoot()
Edouard@2250: 
Edouard@2250:         # this should be an object capable of parsing
Edouard@2250:         # PLCopen XML files. The parser is created automatically
Edouard@2250:         # (i.e. using GenerateParserFromXSD() from xmlclass module)
Edouard@2250:         # using the PLCopen XSD file defining the format of the XML.
Edouard@2250:         # See the file plcopen/plcopen.py
Edouard@2250:         Project = CTN_Root.Project
Edouard@2020:         if Project is not None:
Edouard@2250:             # getcontentHeader() and getfileHeader() are functions that are conditionally defined in
Edouard@2250:             # plcopn/plcopen.py    We cannot rely on their existance
Edouard@2250:             if getattr(Project, "getcontentHeader", None) is not None:
Edouard@2250:                 ProjProp = Project.getcontentHeader()
Edouard@2250:                 # getcontentHeader() returns a dictionary. Available keys are:
Edouard@2250:                 # "projectName", "projectVersion", "modificationDateTime",
Edouard@2250:                 # "organization", "authorName", "language", "pageSize", "scaling"
Edouard@2250:             if getattr(Project, "getfileHeader", None) is not None:
Edouard@2250:                 FileProp = Project.getfileHeader()
Edouard@2250:                 # getfileHeader() returns a dictionary. Available keys are:
Edouard@2250:                 # "companyName", "companyURL", "productName", "productVersion",
Edouard@2250:                 # "productRelease", "creationDateTime", "contentDescription"
Edouard@2250: 
Edouard@2250:         ProjName = ""
Edouard@2020:         if "projectName" in ProjProp:
Edouard@2250:             ProjName = ProjProp["projectName"]
Edouard@2020:         ProjAuthor = ""
Edouard@2020:         if "companyName" in FileProp:
Edouard@2020:             ProjAuthor += "(" + FileProp["companyName"] + ")"
Edouard@2020:         if "authorName" in ProjProp:
Edouard@2250:             ProjAuthor = ProjProp["authorName"] + " " + ProjAuthor
Edouard@2250: 
Edouard@2020:         projdata_dict = {}
Edouard@2250:         projdata_dict["Project Name"] = ProjName
Edouard@2250:         projdata_dict["Project Author"] = ProjAuthor
Edouard@2250:         projdata_dict["Current Time"] = datetime.now().strftime(
Edouard@2250:             '%Y-%m-%d %H:%M:%S')
Edouard@2250:         projdata_dict["EDE file version"] = self.ObjTablesData[
Edouard@2250:             "EDEfile_parm"]["next_EDE_file_version"]
Edouard@2020: 
Edouard@2020:         # Next time we generate an EDE file, use another version!
Edouard@2020:         self.ObjTablesData["EDEfile_parm"]["next_EDE_file_version"] += 1
Edouard@2020: 
Edouard@2250:         AX_params_format = "%(Object Name)s;" + str(BACnet_Device_ID) + \
Edouard@2250:             ";%(Object Name)s;%(BACnetObjTypeID)s;%(Object Identifier)s;%(Description)s;0;;;%(Settable)s;N;;;;%(Unit ID)s;"
Edouard@2250: 
Edouard@2250:         BX_params_format = "%(Object Name)s;" + str(BACnet_Device_ID) + \
Edouard@2250:             ";%(Object Name)s;%(BACnetObjTypeID)s;%(Object Identifier)s;%(Description)s;0;0;1;%(Settable)s;N;;;;;"
Edouard@2250: 
Edouard@2250:         MSX_params_format = "%(Object Name)s;" + str(BACnet_Device_ID) + \
Edouard@2250:             ";%(Object Name)s;%(BACnetObjTypeID)s;%(Object Identifier)s;%(Description)s;1;1;%(Number of States)s;%(Settable)s;N;;;;;"
Edouard@2250: 
Edouard@2020:         Objects_List = []
Edouard@2250:         for ObjType, params_format in [("AV",  AX_params_format),
Edouard@2250:                                        ("AO",  AX_params_format),
Edouard@2250:                                        ("AI",  AX_params_format),
Edouard@2250:                                        ("BV",  BX_params_format),
Edouard@2250:                                        ("BO",  BX_params_format),
Edouard@2250:                                        ("BI",  BX_params_format),
Edouard@2250:                                        ("MSV", MSX_params_format),
Edouard@2250:                                        ("MSO", MSX_params_format),
Edouard@2250:                                        ("MSI", MSX_params_format)]:
Edouard@2020:             self.ObjTables[ObjType + "_Obj"].UpdateAllVirtualProperties()
Edouard@2020:             for ObjProp in self.ObjTablesData[ObjType + "_Obj"]:
Edouard@2020:                 Objects_List.append(params_format % ObjProp)
Edouard@2250: 
Edouard@2020:         # Normalize filename
Edouard@2020:         for extension in ["_EDE.csv", "_ObjTypes.csv", "_StateTexts.csv", "_Units.csv"]:
Edouard@2020:             if filename.lower().endswith(extension.lower()):
Edouard@2020:                 filename = filename[:-len(extension)]
Edouard@2250: 
Edouard@2020:         # EDE_header
Edouard@2250:         generate_file_name = filename + "_EDE.csv"
Edouard@2250:         template_file_name = os.path.join(
Edouard@2250:             template_file_dir, "template_EDE.csv")
Edouard@2020:         generate_file_content = open(template_file_name).read() % projdata_dict
Edouard@2250:         generate_file_handle = open(generate_file_name, 'w')
Edouard@2020:         generate_file_handle  .write(generate_file_content)
Edouard@2020:         generate_file_handle  .write("\n".join(Objects_List))
Edouard@2020:         generate_file_handle  .close()
Edouard@2250: 
Edouard@2250:         # templates of remaining files do not need changes. They are simply
Edouard@2250:         # copied unchanged!
Edouard@2020:         for extension in ["_ObjTypes.csv", "_StateTexts.csv", "_Units.csv"]:
Edouard@2250:             generate_file_name = filename + extension
Edouard@2250:             template_file_name = os.path.join(
Edouard@2250:                 template_file_dir, "template" + extension)
Edouard@2020:             generate_file_content = open(template_file_name).read()
Edouard@2250:             generate_file_handle = open(generate_file_name, 'w')
Edouard@2020:             generate_file_handle  .write(generate_file_content)
Edouard@2020:             generate_file_handle  .close()
Edouard@2020: 
msousa@2649: 
msousa@2649: 
msousa@2649:     #
msousa@2649:     # Generate the C source code files
Edouard@2250:     #
Edouard@2250:     def CTNGenerate_C(self, buildpath, locations):
Edouard@2250:         # Determine the current location in Beremiz's project configuration
Edouard@2250:         # tree
Edouard@2020:         current_location = self.GetCurrentLocation()
Edouard@2250:         # The current location of this plugin in Beremiz's configuration tree, separated by underscores
Edouard@2020:         #  NOTE: Since BACnet plugin currently does not use sub-branches in the tree (in other words, this
Edouard@2020:         #        _BacnetSlavePlug class was actually renamed as the RootClass), the current_location_dots
Edouard@2020:         #        will actually be a single number (e.g.: 0 or 3 or 6, corresponding to the location
Edouard@2020:         #        in which the plugin was inserted in the Beremiz configuration tree on Beremiz's left panel).
Edouard@2250:         locstr = "_".join(map(str, current_location))
Edouard@2250: 
Edouard@2250:         # First check whether all the current parameters (inserted by user in
Edouard@2250:         # the GUI) are valid...
Edouard@2020:         if self.HasDuplicateObjectNames():
Edouard@2250:             self.GetCTRoot().logger.write_warning(
andrej@2425:                 _("Error: BACnet server '{a1}.x:{a2}' contains objects with duplicate object names.\n").
andrej@2425:                 format(a1=locstr, a2=self.CTNName()))
Edouard@2250:             raise Exception(False)
Edouard@2250:             # TODO: return an error code instead of raising an exception
Edouard@2250:             # (currently unsupported by Beremiz)
Edouard@2020: 
Edouard@2020:         if self.HasDuplicateObjectIDs():
Edouard@2250:             self.GetCTRoot().logger.write_warning(
andrej@2425:                 _("Error: BACnet server '{a1}.x: {a2}' contains objects with duplicate object identifiers.\n").
andrej@2425:                 format(a1=locstr, a2=self.CTNName()))
Edouard@2250:             raise Exception(False)
Edouard@2250:             # TODO: return an error code instead of raising an exception
Edouard@2250:             # (currently unsupported by Beremiz)
Edouard@2250: 
Edouard@2250:         # -------------------------------------------------------------------------------
Edouard@2020:         # Create and populate the loc_dict dictionary with all parameters needed to configure
Edouard@2020:         #  the generated source code (.c and .h files)
Edouard@2250:         # ----------------------------------------------------------------------
Edouard@2250: 
Edouard@2020:         # 1) Create the dictionary (loc_dict = {})
Edouard@2020:         loc_dict = {}
Edouard@2250:         loc_dict["locstr"] = locstr
Edouard@2250: 
Edouard@2250:         # The BACnetServerNode attribute is added dynamically by ConfigTreeNode._AddParamsMembers()
Edouard@2250:         # It will be an XML parser object created by
Edouard@2250:         # GenerateParserFromXSDstring(self.XSD).CreateRoot()
msousa@2649:         #
msousa@2649:         # Note: Override_Parameters_Saved_on_PLC is converted to an integer by int()
msousa@2649:         #       The above flag is not currently in use. It requires further thinking on how the 
msousa@2649:         #       user will interpret and interact with this user interface...
msousa@2649:         #loc_dict["Override_Parameters_Saved_on_PLC"] = int(self.BACnetServerNode.getOverride_Parameters_Saved_on_PLC())
Edouard@2250:         loc_dict["network_interface"] = self.BACnetServerNode.getNetwork_Interface()
Edouard@2250:         loc_dict["port_number"] = self.BACnetServerNode.getUDP_Port_Number()
Edouard@2250:         loc_dict["BACnet_Device_ID"] = self.BACnetServerNode.getBACnet_Device_ID()
Edouard@2250:         loc_dict["BACnet_Device_Name"] = self.BACnetServerNode.getBACnet_Device_Name()
Edouard@2020:         loc_dict["BACnet_Comm_Control_Password"] = self.BACnetServerNode.getBACnet_Communication_Control_Password()
Edouard@2250:         loc_dict["BACnet_Device_Location"] = self.BACnetServerNode.getBACnet_Device_Location()
Edouard@2250:         loc_dict["BACnet_Device_Description"] = self.BACnetServerNode.getBACnet_Device_Description()
Edouard@2250:         loc_dict["BACnet_Device_AppSoft_Version"] = self.BACnetServerNode.getBACnet_Device_Application_Software_Version()
Edouard@2020:         loc_dict["BACnet_Vendor_ID"] = BACNET_VENDOR_ID
Edouard@2020:         loc_dict["BACnet_Vendor_Name"] = BACNET_VENDOR_NAME
Edouard@2020:         loc_dict["BACnet_Model_Name"] = BACNET_DEVICE_MODEL_NAME
msousa@2649:         loc_dict["BACnet_Param_String_Size"] = BACNET_PARAM_STRING_SIZE
msousa@2649:         
Edouard@2020: 
Edouard@2020:         # 2) Add the data specific to each BACnet object type
Edouard@2020:         # For each BACnet object type, start off by creating some intermediate helpful lists
Edouard@2020:         #  a) parameters_list containing the strings that will
Edouard@2250:         #     be included in the C source code, and which will initialize the struct with the
Edouard@2020:         #     object (Analog Value, Binary Value, or Multi State Value) parameters
Edouard@2020:         #  b) locatedvar_list containing the strings that will
Edouard@2250:         #     declare the memory to store the located variables, as well as the
Edouard@2020:         #     pointers (required by matiec) that point to that memory.
Edouard@2020: 
Edouard@2250:         # format for delaring IEC 61131-3 variable (and pointer) onto which
Edouard@2250:         # BACnet object is mapped
Edouard@2020:         locvar_format = '%(Ctype)s ___%(loc)s_%(Object Identifier)s; ' + \
Edouard@2020:                         '%(Ctype)s *__%(loc)s_%(Object Identifier)s = &___%(loc)s_%(Object Identifier)s;'
Edouard@2020: 
Edouard@2020:         # format for initializing a ANALOG_VALUE_DESCR struct in C code
Edouard@2020:         #    also valid for ANALOG_INPUT and ANALOG_OUTPUT
Edouard@2020:         AX_params_format = '{&___%(loc)s_%(Object Identifier)s, ' + \
Edouard@2250:             '%(Object Identifier)s, "%(Object Name)s", "%(Description)s", %(Unit ID)d}'
Edouard@2020:         # format for initializing a BINARY_VALUE_DESCR struct in C code
Edouard@2020:         #    also valid for BINARY_INPUT and BINARY_OUTPUT
Edouard@2020:         BX_params_format = '{&___%(loc)s_%(Object Identifier)s, ' + \
Edouard@2250:             '%(Object Identifier)s, "%(Object Name)s", "%(Description)s"}'
Edouard@2020: 
Edouard@2020:         # format for initializing a MULTISTATE_VALUE_DESCR struct in C code
Edouard@2020:         #    also valid for MULTISTATE_INPUT and MULTISTATE_OUTPUT
Edouard@2020:         MSX_params_format = '{&___%(loc)s_%(Object Identifier)s, ' + \
Edouard@2250:             '%(Object Identifier)s, "%(Object Name)s", "%(Description)s", %(Number of States)s}'
Edouard@2250: 
Edouard@2250:         # see the comment in GetVariableLocationTree()
Edouard@2250:         AV_locstr = 'MD' + locstr + '_2'
Edouard@2250:         AO_locstr = 'QD' + locstr + '_1'
Edouard@2250:         AI_locstr = 'ID' + locstr + '_0'
Edouard@2250:         BV_locstr = 'MX' + locstr + '_5'
Edouard@2250:         BO_locstr = 'QX' + locstr + '_4'
Edouard@2250:         BI_locstr = 'IX' + locstr + '_3'
Edouard@2250:         MSV_locstr = 'MB' + locstr + '_19'
Edouard@2250:         MSO_locstr = 'QB' + locstr + '_14'
Edouard@2250:         MSI_locstr = 'IB' + locstr + '_13'
Edouard@2250: 
Edouard@2250:         for ObjType,  ObjLocStr,     params_format in [
Edouard@2250:                 ("AV",  AV_locstr,  AX_params_format),
Edouard@2250:                 ("AO",  AO_locstr,  AX_params_format),
Edouard@2250:                 ("AI",  AI_locstr,  AX_params_format),
Edouard@2250:                 ("BV",  BV_locstr,  BX_params_format),
Edouard@2250:                 ("BO",  BO_locstr,  BX_params_format),
Edouard@2250:                 ("BI",  BI_locstr,  BX_params_format),
Edouard@2250:                 ("MSV", MSV_locstr, MSX_params_format),
Edouard@2250:                 ("MSO", MSO_locstr, MSX_params_format),
Edouard@2250:                 ("MSI", MSI_locstr, MSX_params_format)]:
Edouard@2020:             parameters_list = []
Edouard@2020:             locatedvar_list = []
Edouard@2020:             self.ObjTables[ObjType + "_Obj"].UpdateAllVirtualProperties()
Edouard@2020:             for ObjProp in self.ObjTablesData[ObjType + "_Obj"]:
Edouard@2250:                 ObjProp["loc"] = ObjLocStr
Edouard@2020:                 parameters_list.append(params_format % ObjProp)
Edouard@2020:                 locatedvar_list.append(locvar_format % ObjProp)
Edouard@2250:             loc_dict[ObjType + "_count"] = len(parameters_list)
Edouard@2250:             loc_dict[ObjType + "_param"] = ",\n".join(parameters_list)
Edouard@2250:             loc_dict[ObjType + "_lvars"] = "\n".join(locatedvar_list)
Edouard@2250: 
Edouard@2250:         # ----------------------------------------------------------------------
Edouard@2020:         # Create the C source files that implement the BACnet server
Edouard@2250:         # ----------------------------------------------------------------------
Edouard@2250: 
Edouard@2020:         # Names of the .c files that will be generated, based on a template file with same name
Edouard@2020:         #   (names without '.c'  --> this will be added later)
Edouard@2020:         #   main server.c file is handled separately
Edouard@2020:         Generated_BACnet_c_mainfile = "server"
Edouard@2250:         Generated_BACnet_c_files = [
Edouard@2250:             "ai", "ao", "av", "bi", "bo", "bv", "msi", "mso", "msv", "device"]
Edouard@2020: 
Edouard@2020:         # Names of the .h files that will be generated, based on a template file with same name
Edouard@2020:         #   (names without '.h'  --> this will be added later)
Edouard@2250:         Generated_BACnet_h_files = [
Edouard@2250:             "server", "device", "config_bacnet_for_beremiz",
Edouard@2250:             "ai", "ao", "av", "bi", "bo", "bv", "msi", "mso", "msv"
Edouard@2250:         ]
Edouard@2020: 
Edouard@2020:         # Generate the files with the source code
Edouard@2020:         postfix = "_".join(map(str, current_location))
Edouard@2250:         template_file_dir = os.path.join(
Edouard@2250:             os.path.split(__file__)[0], "runtime")
Edouard@2250: 
Edouard@2250:         def generate_file(file_name, extension):
Edouard@2250:             generate_file_name = os.path.join(
Edouard@2250:                 buildpath, "%s_%s.%s" % (file_name, postfix, extension))
Edouard@2250:             template_file_name = os.path.join(
Edouard@2250:                 template_file_dir, "%s.%s" % (file_name, extension))
Edouard@2020:             generate_file_content = open(template_file_name).read() % loc_dict
Edouard@2250:             generate_file_handle = open(generate_file_name, 'w')
Edouard@2250:             generate_file_handle.write(generate_file_content)
Edouard@2250:             generate_file_handle.close()
Edouard@2020: 
Edouard@2020:         for file_name in Generated_BACnet_c_files:
Edouard@2020:             generate_file(file_name, "c")
Edouard@2020:         for file_name in Generated_BACnet_h_files:
Edouard@2020:             generate_file(file_name, "h")
Edouard@2020:         generate_file(Generated_BACnet_c_mainfile, "c")
Edouard@2020:         Generated_BACnet_c_mainfile_name = \
Edouard@2250:             os.path.join(buildpath, "%s_%s.%s" %
Edouard@2250:                          (Generated_BACnet_c_mainfile, postfix, "c"))
Edouard@2250: 
Edouard@2250:         # ----------------------------------------------------------------------
Edouard@2020:         # Finally, define the compilation and linking commands and flags
Edouard@2250:         # ----------------------------------------------------------------------
Edouard@2250: 
Edouard@2020:         LDFLAGS = []
Edouard@2020:         # when using dynamically linked library...
Edouard@2250:         # LDFLAGS.append(' -lbacnet')
Edouard@2250:         # LDFLAGS.append(' -L"'+BacnetLibraryPath+'"')
Edouard@2250:         # LDFLAGS.append(' "-Wl,-rpath,' + BacnetLibraryPath + '"')
Edouard@2020:         # when using static library:
Edouard@2250:         LDFLAGS.append(
Edouard@2250:             ' "' + os.path.join(BacnetLibraryPath, "libbacnet.a") + '"')
Edouard@2250: 
Edouard@2250:         CFLAGS = ' -I"' + BacnetIncludePath + '"'
Edouard@2250:         CFLAGS += ' -I"' + BacnetIncludePortPath + '"'
Edouard@2250: 
msousa@2649:         # ----------------------------------------------------------------------
msousa@2649:         # Create a file containing the default configuration paramters.
msousa@2649:         # Beremiz will then transfer this file to the PLC, where the web server 
msousa@2649:         # will read it to obtain the default configuration parameters.
msousa@2649:         # ----------------------------------------------------------------------
msousa@2649:         # NOTE: This is no loner needed! The web interface will read these 
msousa@2649:         # parameters directly from the compiled C code (.so file)
msousa@2649:         #
msousa@2649:         ### extra_file_name   = os.path.join(buildpath, "%s_%s.%s" % ('bacnet_extrafile', postfix, 'txt'))
msousa@2649:         ### extra_file_handle = open(extra_file_name, 'w')
msousa@2649:         ### 
msousa@2649:         ### proplist = ["network_interface", "port_number", "BACnet_Device_ID", "BACnet_Device_Name", 
msousa@2649:         ###             "BACnet_Comm_Control_Password", "BACnet_Device_Location", 
msousa@2649:         ###             "BACnet_Device_Description", "BACnet_Device_AppSoft_Version"]
msousa@2649:         ### for propname in proplist:
msousa@2649:         ###     extra_file_handle.write("%s:%s\n" % (propname, loc_dict[propname]))
msousa@2649:         ### 
msousa@2649:         ### extra_file_handle.close()
msousa@2649:         ### extra_file_handle = open(extra_file_name, 'r')
msousa@2649: 
msousa@2649:         # Format of data to return:
msousa@2649:         #   [(Cfiles, CFLAGS), ...], LDFLAGS, DoCalls, extra_files
msousa@2649:         # LDFLAGS     = ['flag1', 'flag2', ...]
msousa@2649:         # DoCalls     = true  or  false
msousa@2649:         # extra_files = (fname,fobject), ...
msousa@2649:         # fobject     = file object, already open'ed for read() !!
msousa@2649:         #
msousa@2649:         # extra_files -> files that will be downloaded to the PLC!
Edouard@2669: 
Edouard@2669:         websettingfile = open(paths.AbsNeighbourFile(__file__, "web_settings.py"), 'r')
Edouard@2669:         websettingcode = websettingfile.read()
Edouard@2669:         websettingfile.close()
Edouard@2669: 
Edouard@2669:         location_str = "_".join(map(str, self.GetCurrentLocation()))
Edouard@2669:         websettingcode = websettingcode % locals()
Edouard@2669: 
Edouard@2669:         runtimefile_path = os.path.join(buildpath, "runtime_bacnet_websettings.py")
Edouard@2669:         runtimefile = open(runtimefile_path, 'w')
Edouard@2669:         runtimefile.write(websettingcode)
Edouard@2669:         runtimefile.close()
Edouard@2669: 
Edouard@2669:         return ([(Generated_BACnet_c_mainfile_name, CFLAGS)], LDFLAGS, True,
Edouard@2703:                 ("runtime_%s_bacnet_websettings.py" % location_str, open(runtimefile_path, "rb")),
Edouard@2669:         )
msousa@2649:         #return [(Generated_BACnet_c_mainfile_name, CFLAGS)], LDFLAGS, True, ('extrafile1.txt', extra_file_handle)