BACnet and Modbus : Simpler configuration management. NevowServer.py now allows each extension to create and delete multiple configuration forms in the setting page, deprecating delSettings and addAfter.
--- a/bacnet/web_settings.py Fri Jun 12 10:30:23 2020 +0200
+++ b/bacnet/web_settings.py Fri Jun 12 14:39:32 2020 +0200
@@ -243,27 +243,6 @@
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
@@ -279,8 +258,6 @@
if value is not None:
newConfig[par_name] = value
- global _WebviewConfiguration
- _WebviewConfiguration = newConfig
# First check if configuration is OK.
if not _CheckWebConfiguration(newConfig):
@@ -294,13 +271,9 @@
# 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):
+
+
+def OnButtonReset(**kwargs):
"""
# Function called when user clicks 'Delete' button in web interface
# The function will delete the file containing the persistent
@@ -314,25 +287,10 @@
# 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()
-
-
-
-
+
+
+
+# location_str is replaced by extension's value in CTNGenerateC call
def _runtime_bacnet_websettings_%(location_str)s_init():
"""
# Callback function, called (by PLCObject.py) when a new PLC program
@@ -346,23 +304,12 @@
# 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(PLCObject.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
+ # location_str is replaced by extension's value in CTNGenerateC call
+ GetParamFuncName = "__bacnet_%(location_str)s_get_ConfigParam_" + name
+ SetParamFuncName = "__bacnet_%(location_str)s_set_ConfigParam_" + name
GetParamFuncs[name] = getattr(PLCObject.PLClibraryHandle, GetParamFuncName)
GetParamFuncs[name].restype = c_dtype
@@ -399,29 +346,37 @@
if _CheckConfiguration(_SavedConfiguration):
_SetPLCConfiguration(_SavedConfiguration)
+ WebSettings = NS.newExtensionSetting("BACnet")
+
# Configure the web interface to include the BACnet config parameters
- NS.ConfigurableSettings.addSettings(
+ WebSettings.addSettings(
"BACnetConfigParm", # name
_("BACnet Configuration"), # description
webFormInterface, # fields
- _("Save Configuration to Persistent Storage"), # button label
+ _("Apply"), # button label
OnButtonSave) # callback
-
- # Add a "View Current Configuration" button
- NS.ConfigurableSettings.addSettings(
- "BACnetConfigViewCur", # name
+
+ # Add the Delete button to the web interface
+ WebSettings.addSettings(
+ "BACnetConfigDelSaved", # 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()
-
-
-
-
-
+ [ ("status",
+ annotate.String(label=_("Current state"),
+ immutable=True,
+ default=lambda *k:getConfigStatus())),
+ ], # fields (empty, no parameters required!)
+ _("Reset"), # button label
+ OnButtonReset)
+
+
+
+def getConfigStatus():
+ if _WebviewConfiguration == _DefaultConfiguration :
+ return "Unchanged"
+ return "Modified"
+
+
+# location_str is replaced by extension's value in CTNGenerateC call
def _runtime_bacnet_websettings_%(location_str)s_cleanup():
"""
# Callback function, called (by PLCObject.py) when a PLC program is unloaded from memory
@@ -429,11 +384,8 @@
#PLCObject.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")
+ NS.removeExtensionSetting("BACnet")
+
GetParamFuncs = {}
SetParamFuncs = {}
_WebviewConfiguration = None
--- a/modbus/web_settings.py Fri Jun 12 10:30:23 2020 +0200
+++ b/modbus/web_settings.py Fri Jun 12 14:39:32 2020 +0200
@@ -328,33 +328,6 @@
-
-def _updateWebInterface(WebNode_id):
- """
- 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
- """
-
- config_hash = _WebNodeList[WebNode_id]["config_hash"]
- config_name = _WebNodeList[WebNode_id]["config_name"]
-
- # Add a "Delete Saved Configuration" button if there is a saved configuration!
- if _WebNodeList[WebNode_id]["SavedConfiguration"] is None:
- NS.ConfigurableSettings.delSettings("ModbusConfigDelSaved" + config_hash)
- else:
- def __OnButtonDel(**kwargs):
- return OnButtonDel(WebNode_id = WebNode_id, **kwargs)
-
- NS.ConfigurableSettings.addSettings(
- "ModbusConfigDelSaved" + config_hash, # name (internal, may not contain spaces, ...)
- _("Modbus Configuration: ") + config_name, # description (user visible label)
- [], # fields (empty, no parameters required!)
- _("Delete Configuration Stored in Persistent Storage"), # button label
- __OnButtonDel, # callback
- "ModbusConfigParm" + config_hash) # Add after entry xxxx
-
-
-
def OnButtonSave(**kwargs):
"""
Function called when user clicks 'Save' button in web interface
@@ -396,13 +369,9 @@
# so we do not set it directly to newConfig
_WebNodeList[WebNode_id]["WebviewConfiguration"] = _GetPLCConfiguration(WebNode_id)
- # File has just been created => Delete button must be shown on web interface!
- _updateWebInterface(WebNode_id)
-
-
-
-
-def OnButtonDel(**kwargs):
+
+
+def OnButtonReset(**kwargs):
"""
Function called when user clicks 'Delete' button in web interface
The function will delete the file containing the persistent
@@ -424,25 +393,7 @@
# Reset SavedConfiguration
_WebNodeList[WebNode_id]["SavedConfiguration"] = None
- # File has just been deleted => Delete button on web interface no longer needed!
- _updateWebInterface(WebNode_id)
-
-
-
-
-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
-
- Note that this function does not get called directly. The real callback
- function is the dynamic __OnButtonShowCur() function, which will add the
- "WebNode_id" argument, and call this function to do the work.
- """
- WebNode_id = kwargs.get("WebNode_id", None)
-
- _WebNodeList[WebNode_id]["WebviewConfiguration"] = _GetPLCConfiguration(WebNode_id)
-
+
@@ -537,26 +488,33 @@
def __OnButtonSave(**kwargs):
OnButtonSave(WebNode_id=WebNode_id, **kwargs)
- NS.ConfigurableSettings.addSettings(
+ WebSettings = NS.newExtensionSetting("Modbus "+config_hash)
+
+ WebSettings.addSettings(
"ModbusConfigParm" + config_hash, # name (internal, may not contain spaces, ...)
_("Modbus Configuration: ") + config_name, # description (user visible label)
webFormInterface, # fields
- _("Save Configuration to Persistent Storage"), # button label
+ _("Apply"), # button label
__OnButtonSave) # callback
- # Add a "View Current Configuration" button
- def __OnButtonShowCur(**kwargs):
- OnButtonShowCur(WebNode_id=WebNode_id, **kwargs)
-
- NS.ConfigurableSettings.addSettings(
- "ModbusConfigViewCur" + config_hash, # name (internal, may not contain spaces, ...)
- _("Modbus Configuration: ") + config_name, # description (user visible label)
- [], # fields (empty, no parameters required!)
- _("Show Current PLC Configuration"), # button label
- __OnButtonShowCur) # callback
-
- # Add the Delete button to the web interface, if required
- _updateWebInterface(WebNode_id)
+ def __OnButtonReset(**kwargs):
+ return OnButtonReset(WebNode_id = WebNode_id, **kwargs)
+
+ def getConfigStatus():
+ if WebNode_entry["WebviewConfiguration"] == WebNode_entry["DefaultConfiguration"]:
+ return "Unchanged"
+ return "Modified"
+
+ WebSettings.addSettings(
+ "ModbusConfigDelSaved" + config_hash, # name (internal, may not contain spaces, ...)
+ _("Modbus Configuration: ") + config_name, # description (user visible label)
+ [ ("status",
+ annotate.String(label=_("Current state"),
+ immutable=True,
+ default=lambda *k:getConfigStatus())),
+ ], # fields (empty, no parameters required!)
+ _("Reset"), # button label
+ __OnButtonReset)
@@ -567,7 +525,6 @@
(i.e. XXX.so file) is transfered to the PLC runtime
and loaded into memory
"""
- print("_runtime_modbus_websettings_init")
#PLCObject.LogMessage("Modbus web server extension::OnLoadPLC() Called...")
@@ -651,9 +608,7 @@
global _WebNodeList
for WebNode_entry in _WebNodeList:
config_hash = WebNode_entry["config_hash"]
- NS.ConfigurableSettings.delSettings("ModbusConfigParm" + config_hash)
- NS.ConfigurableSettings.delSettings("ModbusConfigViewCur" + config_hash)
- NS.ConfigurableSettings.delSettings("ModbusConfigDelSaved" + config_hash)
+ NS.removeExtensionSetting("Modbus "+config_hash)
# Dele all entries...
_WebNodeList = []
--- a/runtime/NevowServer.py Fri Jun 12 10:30:23 2020 +0200
+++ b/runtime/NevowServer.py Fri Jun 12 14:39:32 2020 +0200
@@ -26,6 +26,7 @@
from __future__ import absolute_import
from __future__ import print_function
import os
+import collections
import platform as platform_module
from zope.interface import implements
from nevow import appserver, inevow, tags, loaders, athena, url, rend
@@ -165,8 +166,7 @@
setattr(self, 'bind_' + name, _bind)
self.bindingsNames.append(name)
- def addSettings(self, name, desc, fields, btnlabel, callback,
- addAfterName = None):
+ def addSettings(self, name, desc, fields, btnlabel, callback):
def _bind(ctx):
return annotate.MethodBinding(
'action_' + name,
@@ -180,32 +180,20 @@
setattr(self, 'action_' + name, callback)
- if addAfterName not in self.bindingsNames:
- # Just append new setting if not yet present
- if name not in self.bindingsNames:
- self.bindingsNames.append(name)
- else:
- # We need to insert new setting
- # imediately _after_ addAfterName
+ self.bindingsNames.append(name)
- # First remove new setting if already present
- # to make sure it goes into correct place
- if name in self.bindingsNames:
- self.bindingsNames.remove(name)
- # Now add new setting in correct place
- self.bindingsNames.insert(
- self.bindingsNames.index(addAfterName)+1,
- name)
-
-
-
- def delSettings(self, name):
- if name in self.bindingsNames:
- self.bindingsNames.remove(name)
-
ConfigurableSettings = ConfigurableBindings()
+def newExtensionSetting(ext_name):
+ global extensions_settings_od
+ settings = ConfigurableBindings()
+ extensions_settings_od[ext_name] = settings
+ return settings
+
+def removeExtensionSetting(ext_name):
+ global extensions_settings_od
+ extensions_settings_od.pop(ext_name)
class ISettings(annotate.TypedInterface):
platform = annotate.String(label=_("Platform"),
@@ -233,6 +221,7 @@
customSettingsURLs = {
}
+extensions_settings_od = collections.OrderedDict()
class SettingsPage(rend.Page):
# We deserve a slash
@@ -243,6 +232,25 @@
child_webinterface_css = File(paths.AbsNeighbourFile(__file__, 'webinterface.css'), 'text/css')
implements(ISettings)
+
+ def __getattr__(self, name):
+ global extensions_settings_od
+ if name.startswith('configurable_'):
+ ext_name = name[13:]
+ def configurable_something(ctx):
+ return extensions_settings_od[ext_name]
+ return configurable_something
+ raise AttributeError
+
+ def extensions_settings(self, context, data):
+ """ Project extensions settings
+ Extensions added to Configuration Tree in IDE have their setting rendered here
+ """
+ global extensions_settings_od
+ res = []
+ for ext_name in extensions_settings_od:
+ res += [tags.h2[ext_name], webform.renderForms(ext_name)]
+ return res
docFactory = loaders.stan([tags.html[
tags.head[
@@ -260,12 +268,16 @@
webform.renderForms('staticSettings'),
tags.h1["Extensions settings:"],
webform.renderForms('dynamicSettings'),
+ extensions_settings
]]])
def configurable_staticSettings(self, ctx):
return configurable.TypedInterfaceConfigurable(self)
def configurable_dynamicSettings(self, ctx):
+ """ Runtime Extensions settings
+ Extensions loaded through Beremiz_service -e or optional runtime features render setting forms here
+ """
return ConfigurableSettings
def sendLogMessage(self, level, message, **kwargs):