--- a/runtime/BACnet_config.py Sun Jun 07 23:47:32 2020 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,490 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# This file is part of Beremiz runtime.
-#
-# Copyright (C) 2020: Mario de Sousa
-#
-# See COPYING.Runtime file for copyrights details.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-
-import json
-import os
-import ctypes
-
-from formless import annotate, webform
-
-
-
-# reference to the PLCObject in runtime/PLCObject.py
-# PLCObject is a singleton, created in runtime/__init__.py
-_plcobj = None
-
-# reference to the Nevow web server (a.k.a as NS in Beremiz_service.py)
-# (Note that NS will reference the NevowServer.py _module_, and not an object/class)
-_NS = None
-
-
-# WorkingDir: the directory on which Beremiz_service.py is running, and where
-# all the files downloaded to the PLC get stored
-_WorkingDir = None
-
-
-# Will contain references to the C functions
-# (implemented in beremiz/bacnet/runtime/server.c)
-# used to get/set the BACnet specific configuration paramters
-GetParamFuncs = {}
-SetParamFuncs = {}
-
-
-# Upon PLC load, this Dictionary is initialised with the BACnet configuration
-# hardcoded in the C file
-# (i.e. the configuration inserted in Beremiz IDE when project was compiled)
-_DefaultConfiguration = None
-
-
-# Dictionary that contains the BACnet configuration currently being shown
-# on the web interface
-# This configuration will almost always be identical to the current
-# configuration in the PLC (i.e., the current state stored in the
-# C variables in the .so file).
-# The configuration viewed on the web will only be different to the current
-# configuration when the user edits the configuration, and when
-# the user asks to save the edited configuration but it contains an error.
-_WebviewConfiguration = None
-
-
-# Dictionary that stores the BACnet configuration currently stored in a file
-# Currently only used to decide whether or not to show the "Delete" button on the
-# web interface (only shown if _SavedConfiguration is not None)
-_SavedConfiguration = None
-
-
-# File to which the new BACnet configuration gets stored on the PLC
-# Note that the stored configuration is likely different to the
-# configuration hardcoded in C generated code (.so file), so
-# this file should be persistent across PLC reboots so we can
-# re-configure the PLC (change values of variables in .so file)
-# before it gets a chance to start running
-#
-#_BACnetConfFilename = None
-_BACnetConfFilename = "/tmp/BeremizBACnetConfig.json"
-
-
-
-
-class BN_StrippedString(annotate.String):
- def __init__(self, *args, **kwargs):
- annotate.String.__init__(self, strip = True, *args, **kwargs)
-
-
-
-BACnet_parameters = [
- # param. name label ctype type annotate type
- # (C code var name) (used on web interface) (C data type) (web data type)
- # (annotate.String,
- # annotate.Integer, ...)
- ("network_interface" , _("Network Interface") , ctypes.c_char_p, BN_StrippedString),
- ("port_number" , _("UDP Port Number") , ctypes.c_char_p, BN_StrippedString),
- ("comm_control_passwd" , _("BACnet Communication Control Password") , ctypes.c_char_p, annotate.String),
- ("device_id" , _("BACnet Device ID") , ctypes.c_int, annotate.Integer),
- ("device_name" , _("BACnet Device Name") , ctypes.c_char_p, annotate.String),
- ("device_location" , _("BACnet Device Location") , ctypes.c_char_p, annotate.String),
- ("device_description" , _("BACnet Device Description") , ctypes.c_char_p, annotate.String),
- ("device_appsoftware_ver" , _("BACnet Device Application Software Version"), ctypes.c_char_p, annotate.String)
- ]
-
-
-
-
-
-
-def _CheckPortnumber(port_number):
- """ check validity of the port number """
- try:
- portnum = int(port_number)
- if (portnum < 0) or (portnum > 65535):
- raise Exception
- except Exception:
- return False
-
- return True
-
-
-
-def _CheckDeviceID(device_id):
- """
- # check validity of the Device ID
- # NOTE: BACnet device (object) IDs are 22 bits long (not counting the 10 bits for the type ID)
- # so the Device instance ID is limited from 0 to 22^2-1 = 4194303
- # However, 4194303 is reserved for special use (similar to NULL pointer), so last
- # valid ID becomes 4194302
- """
- try:
- devid = int(device_id)
- if (devid < 0) or (devid > 4194302):
- raise Exception
- except Exception:
- return False
-
- return True
-
-
-
-
-
-def _CheckConfiguration(BACnetConfig):
- res = True
- res = res and _CheckPortnumber(BACnetConfig["port_number"])
- res = res and _CheckDeviceID (BACnetConfig["device_id"])
- return res
-
-
-
-def _CheckWebConfiguration(BACnetConfig):
- res = True
-
- # check the port number
- if not _CheckPortnumber(BACnetConfig["port_number"]):
- raise annotate.ValidateError(
- {"port_number": "Invalid port number: " + str(BACnetConfig["port_number"])},
- _("BACnet configuration error:"))
- res = False
-
- if not _CheckDeviceID(BACnetConfig["device_id"]):
- raise annotate.ValidateError(
- {"device_id": "Invalid device ID: " + str(BACnetConfig["device_id"])},
- _("BACnet configuration error:"))
- res = False
-
- return res
-
-
-
-
-
-
-def _SetSavedConfiguration(BACnetConfig):
- """ Stores in a file a dictionary containing the BACnet parameter configuration """
- with open(os.path.realpath(_BACnetConfFilename), 'w') as f:
- json.dump(BACnetConfig, f, sort_keys=True, indent=4)
- global _SavedConfiguration
- _SavedConfiguration = BACnetConfig
-
-
-def _DelSavedConfiguration():
- """ Deletes the file cotaining the persistent BACnet configuration """
- if os.path.exists(_BACnetConfFilename):
- os.remove(_BACnetConfFilename)
-
-
-def _GetSavedConfiguration():
- """
- # Returns a dictionary containing the BACnet parameter configuration
- # that was last saved to file. If no file exists, then return None
- """
- try:
- #if os.path.isfile(_BACnetConfFilename):
- saved_config = json.load(open(_BACnetConfFilename))
- except Exception:
- return None
-
- if _CheckConfiguration(saved_config):
- return saved_config
- else:
- return None
-
-
-def _GetPLCConfiguration():
- """
- # Returns a dictionary containing the current BACnet parameter configuration
- # stored in the C variables in the loaded PLC (.so file)
- """
- current_config = {}
- for par_name, x1, x2, x3 in BACnet_parameters:
- value = GetParamFuncs[par_name]()
- if value is not None:
- current_config[par_name] = value
-
- return current_config
-
-
-def _SetPLCConfiguration(BACnetConfig):
- """
- # Stores the BACnet parameter configuration into the
- # the C variables in the loaded PLC (.so file)
- """
- for par_name in BACnetConfig:
- value = BACnetConfig[par_name]
- #_plcobj.LogMessage("BACnet web server extension::_SetPLCConfiguration() Setting "
- # + par_name + " to " + str(value) )
- if value is not None:
- SetParamFuncs[par_name](value)
- # update the configuration shown on the web interface
- global _WebviewConfiguration
- _WebviewConfiguration = _GetPLCConfiguration()
-
-
-
-def _GetWebviewConfigurationValue(ctx, argument):
- """
- # Callback function, called by the web interface (NevowServer.py)
- # to fill in the default value of each parameter
- """
- try:
- return _WebviewConfiguration[argument.name]
- except Exception:
- return ""
-
-
-# The configuration of the web form used to see/edit the BACnet parameters
-webFormInterface = [(name, web_dtype (label=web_label, default=_GetWebviewConfigurationValue))
- for name, web_label, c_dtype, web_dtype in BACnet_parameters]
-
-
-
-def _updateWebInterface():
- """
- # Add/Remove buttons to/from the web interface depending on the current state
- #
- # - If there is a saved state => add a delete saved state button
- """
-
- # Add a "Delete Saved Configuration" button if there is a saved configuration!
- if _SavedConfiguration is None:
- _NS.ConfigurableSettings.delSettings("BACnetConfigDelSaved")
- else:
- _NS.ConfigurableSettings.addSettings(
- "BACnetConfigDelSaved", # name
- _("BACnet Configuration"), # description
- [], # fields (empty, no parameters required!)
- _("Delete Configuration Stored in Persistent Storage"), # button label
- OnButtonDel, # callback
- "BACnetConfigParm") # Add after entry xxxx
-
-
-def OnButtonSave(**kwargs):
- """
- # Function called when user clicks 'Save' button in web interface
- # The function will configure the BACnet plugin in the PLC with the values
- # specified in the web interface. However, values must be validated first!
- """
-
- #_plcobj.LogMessage("BACnet web server extension::OnButtonSave() Called")
-
- newConfig = {}
- for par_name, x1, x2, x3 in BACnet_parameters:
- value = kwargs.get(par_name, None)
- if value is not None:
- newConfig[par_name] = value
-
- global _WebviewConfiguration
- _WebviewConfiguration = newConfig
-
- # First check if configuration is OK.
- if not _CheckWebConfiguration(newConfig):
- return
-
- # store to file the new configuration so that
- # we can recoup the configuration the next time the PLC
- # has a cold start (i.e. when Beremiz_service.py is retarted)
- _SetSavedConfiguration(newConfig)
-
- # Configure PLC with the current BACnet parameters
- _SetPLCConfiguration(newConfig)
-
- # File has just been created => Delete button must be shown on web interface!
- _updateWebInterface()
-
-
-
-
-def OnButtonDel(**kwargs):
- """
- # Function called when user clicks 'Delete' button in web interface
- # The function will delete the file containing the persistent
- # BACnet configution
- """
-
- # Delete the file
- _DelSavedConfiguration()
- # Set the current configuration to the default (hardcoded in C)
- _SetPLCConfiguration(_DefaultConfiguration)
- # Reset global variable
- global _SavedConfiguration
- _SavedConfiguration = None
- # File has just been deleted => Delete button on web interface no longer needed!
- _updateWebInterface()
-
-
-
-def OnButtonShowCur(**kwargs):
- """
- # Function called when user clicks 'Show Current PLC Configuration' button in web interface
- # The function will load the current PLC configuration into the web form
- """
-
- global _WebviewConfiguration
- _WebviewConfiguration = _GetPLCConfiguration()
- # File has just been deleted => Delete button on web interface no longer needed!
- _updateWebInterface()
-
-
-
-
-def OnLoadPLC():
- """
- # Callback function, called (by PLCObject.py) when a new PLC program
- # (i.e. XXX.so file) is transfered to the PLC runtime
- # and oaded into memory
- """
-
- #_plcobj.LogMessage("BACnet web server extension::OnLoadPLC() Called...")
-
- if _plcobj.PLClibraryHandle is None:
- # PLC was loaded but we don't have access to the library of compiled code (.so lib)?
- # Hmm... This shold never occur!!
- return
-
- # Get the location (in the Config. Node Tree of Beremiz IDE) the BACnet plugin
- # occupies in the currently loaded PLC project (i.e., the .so file)
- # If the "__bacnet_plugin_location" C variable is not present in the .so file,
- # we conclude that the currently loaded PLC does not have the BACnet plugin
- # included (situation (2b) described above init())
- try:
- location = ctypes.c_char_p.in_dll(_plcobj.PLClibraryHandle, "__bacnet_plugin_location")
- except Exception:
- # Loaded PLC does not have the BACnet plugin => nothing to do
- # (i.e. do _not_ configure and make available the BACnet web interface)
- return
-
- # Map the get/set functions (written in C code) we will be using to get/set the configuration parameters
- for name, web_label, c_dtype, web_dtype in BACnet_parameters:
- GetParamFuncName = "__bacnet_" + location.value + "_get_ConfigParam_" + name
- SetParamFuncName = "__bacnet_" + location.value + "_set_ConfigParam_" + name
-
- GetParamFuncs[name] = getattr(_plcobj.PLClibraryHandle, GetParamFuncName)
- GetParamFuncs[name].restype = c_dtype
- GetParamFuncs[name].argtypes = None
-
- SetParamFuncs[name] = getattr(_plcobj.PLClibraryHandle, SetParamFuncName)
- SetParamFuncs[name].restype = None
- SetParamFuncs[name].argtypes = [c_dtype]
-
- # Default configuration is the configuration done in Beremiz IDE
- # whose parameters get hardcoded into C, and compiled into the .so file
- # We read the default configuration from the .so file before the values
- # get changed by the user using the web server, or by the call (further on)
- # to _SetPLCConfiguration(SavedConfiguration)
- global _DefaultConfiguration
- _DefaultConfiguration = _GetPLCConfiguration()
-
- # Show the current PLC configuration on the web interface
- global _WebviewConfiguration
- _WebviewConfiguration = _GetPLCConfiguration()
-
- # Read from file the last used configuration, which is likely
- # different to the hardcoded configuration.
- # We Reset the current configuration (i.e., the config stored in the
- # variables of .so file) to this saved configuration
- # so the PLC will start off with this saved configuration instead
- # of the hardcoded (in Beremiz C generated code) configuration values.
- #
- # Note that _SetPLCConfiguration() will also update
- # _WebviewConfiguration , if necessary.
- global _SavedConfiguration
- _SavedConfiguration = _GetSavedConfiguration()
- if _SavedConfiguration is not None:
- if _CheckConfiguration(_SavedConfiguration):
- _SetPLCConfiguration(_SavedConfiguration)
-
- # Configure the web interface to include the BACnet config parameters
- _NS.ConfigurableSettings.addSettings(
- "BACnetConfigParm", # name
- _("BACnet Configuration"), # description
- webFormInterface, # fields
- _("Save Configuration to Persistent Storage"), # button label
- OnButtonSave) # callback
-
- # Add a "View Current Configuration" button
- _NS.ConfigurableSettings.addSettings(
- "BACnetConfigViewCur", # name
- _("BACnet Configuration"), # description
- [], # fields (empty, no parameters required!)
- _("Show Current PLC Configuration"), # button label
- OnButtonShowCur) # callback
-
- # Add the Delete button to the web interface, if required
- _updateWebInterface()
-
-
-
-
-
-def OnUnLoadPLC():
- """
- # Callback function, called (by PLCObject.py) when a PLC program is unloaded from memory
- """
-
- #_plcobj.LogMessage("BACnet web server extension::OnUnLoadPLC() Called...")
-
- # Delete the BACnet specific web interface extensions
- # (Safe to ask to delete, even if it has not been added!)
- _NS.ConfigurableSettings.delSettings("BACnetConfigParm")
- _NS.ConfigurableSettings.delSettings("BACnetConfigViewCur")
- _NS.ConfigurableSettings.delSettings("BACnetConfigDelSaved")
- GetParamFuncs = {}
- SetParamFuncs = {}
- _WebviewConfiguration = None
- _SavedConfiguration = None
-
-
-
-
-# The Beremiz_service.py service, along with the integrated web server it launches
-# (i.e. Nevow web server, in runtime/NevowServer.py), will go through several states
-# once started:
-# (1) Web server is started, but no PLC is loaded
-# (2) PLC is loaded (i.e. the PLC compiled code is loaded)
-# (a) The loaded PLC includes the BACnet plugin
-# (b) The loaded PLC does not have the BACnet plugin
-#
-# During (1) and (2a):
-# we configure the web server interface to not have the BACnet web configuration extension
-# During (2b)
-# we configure the web server interface to include the BACnet web configuration extension
-#
-# plcobj : reference to the PLCObject defined in PLCObject.py
-# NS : reference to the web server (i.e. the NevowServer.py module)
-# WorkingDir: the directory on which Beremiz_service.py is running, and where
-# all the files downloaded to the PLC get stored, including
-# the .so file with the compiled C generated code
-def init(plcobj, NS, WorkingDir):
- #plcobj.LogMessage("BACnet web server extension::init(plcobj, NS, " + WorkingDir + ") Called")
- global _WorkingDir
- _WorkingDir = WorkingDir
- global _plcobj
- _plcobj = plcobj
- global _NS
- _NS = NS
- global _BACnetConfFilename
- if _BACnetConfFilename is None:
- _BACnetConfFilename = os.path.join(WorkingDir, "BACnetConfig.json")
-
- _plcobj.RegisterCallbackLoad ("BACnet_Settins_Extension", OnLoadPLC)
- _plcobj.RegisterCallbackUnLoad("BACnet_Settins_Extension", OnUnLoadPLC)
- OnUnLoadPLC() # init is called before the PLC gets loaded... so we make sure we have the correct state