--- a/Beremiz_service.py Thu Jan 28 14:50:26 2021 +0000
+++ b/Beremiz_service.py Thu Jan 28 14:51:16 2021 +0000
@@ -30,6 +30,7 @@
import sys
import getopt
import threading
+import shlex
from threading import Thread, Semaphore, Lock, currentThread
from builtins import str as text
from past.builtins import execfile
@@ -44,6 +45,10 @@
from runtime.Stunnel import ensurePSK
import util.paths as paths
+try:
+ from runtime.spawn_subprocess import Popen
+except ImportError:
+ from subprocess import Popen
def version():
from version import app_version
@@ -72,7 +77,7 @@
try:
- opts, argv = getopt.getopt(sys.argv[1:], "i:p:n:x:t:a:w:c:e:s:h", ["help", "version"])
+ opts, argv = getopt.getopt(sys.argv[1:], "i:p:n:x:t:a:w:c:e:s:h", ["help", "version", "status-change=", "on-plc-start=", "on-plc-stop="])
except getopt.GetoptError as err:
# print help information and exit:
print(str(err)) # will print something like "option -a not recognized"
@@ -93,6 +98,13 @@
havetwisted = False
extensions = []
+statuschange = []
+def status_change_call_factory(wanted, args):
+ def status_change_call(status):
+ if wanted is None or status is wanted:
+ cmd = shlex.split(args.format(status))
+ Popen(cmd)
+ return status_change_call
for o, a in opts:
if o == "-h" or o == "--help":
@@ -101,6 +113,12 @@
if o == "--version":
version()
sys.exit()
+ if o == "--on-plc-start":
+ statuschange.append(status_change_call_factory(PlcStatus.Started, a))
+ elif o == "--on-plc-stop":
+ statuschange.append(status_change_call_factory(PlcStatus.Stopped, a))
+ elif o == "--status-change":
+ statuschange.append(status_change_call_factory(None, a))
elif o == "-i":
if len(a.split(".")) == 4:
interface = a
@@ -401,7 +419,6 @@
havetwisted = False
pyruntimevars = {}
-statuschange = []
if havetwisted:
if havewx:
@@ -475,37 +492,16 @@
installThreadExcepthook()
havewamp = False
-haveBNconf = False
-haveMBconf = False
-
if havetwisted:
if webport is not None:
try:
import runtime.NevowServer as NS # pylint: disable=ungrouped-imports
+ NS.WorkingDir = WorkingDir
except Exception:
LogMessageAndException(_("Nevow/Athena import failed :"))
webport = None
- NS.WorkingDir = WorkingDir # bug? what happens if import fails?
-
- # Try to add support for BACnet configuration via web server interface
- # NOTE:BACnet web config only makes sense if web server is available
- if webport is not None:
- try:
- import runtime.BACnet_config as BNconf
- haveBNconf = True
- except Exception:
- LogMessageAndException(_("BACnet configuration web interface - import failed :"))
-
- # Try to add support for Modbus configuration via web server interface
- # NOTE:Modbus web config only makes sense if web server is available
- if webport is not None:
- try:
- import runtime.Modbus_config as MBconf
- haveMBconf = True
- except Exception:
- LogMessageAndException(_("Modbus configuration web interface - import failed :"))
-
+
try:
import runtime.WampClient as WC # pylint: disable=ungrouped-imports
WC.WorkingDir = WorkingDir
@@ -527,8 +523,6 @@
runtime.CreatePLCObjectSingleton(
WorkingDir, argv, statuschange, evaluator, pyruntimevars)
-plcobj = runtime.GetPLCObjectSingleton()
-
pyroserver = PyroServer(servicename, interface, port)
if havewx:
@@ -544,18 +538,6 @@
except Exception:
LogMessageAndException(_("Nevow Web service failed. "))
- if haveBNconf:
- try:
- BNconf.init(plcobj, NS, WorkingDir)
- except Exception:
- LogMessageAndException(_("BACnet web configuration failed startup. "))
-
- if haveMBconf:
- try:
- MBconf.init(plcobj, NS, WorkingDir)
- except Exception:
- LogMessageAndException(_("Modbus web configuration failed startup. "))
-
if havewamp:
try:
WC.SetServer(pyroserver)
@@ -619,6 +601,7 @@
pyroserver.Quit()
pyro_thread.join()
+plcobj = runtime.GetPLCObjectSingleton()
plcobj.StopPLC()
plcobj.UnLoadPLC()
--- a/ConfigTreeNode.py Thu Jan 28 14:50:26 2021 +0000
+++ b/ConfigTreeNode.py Thu Jan 28 14:51:16 2021 +0000
@@ -678,6 +678,3 @@
raise UserAddressedException(message)
-class UserAddressedException(Exception):
- pass
-
--- a/POULibrary.py Thu Jan 28 14:50:26 2021 +0000
+++ b/POULibrary.py Thu Jan 28 14:51:16 2021 +0000
@@ -26,6 +26,10 @@
from __future__ import absolute_import
from weakref import ref
+# Exception type for problems that user has to take action in order to fix
+class UserAddressedException(Exception):
+ pass
+
class POULibrary(object):
def __init__(self, CTR, LibName, TypeStack):
@@ -59,6 +63,11 @@
# Pure python or IEC libs doesn't produce C code
return ((""), [], False), ""
+ def FatalError(self, message):
+ """ Raise an exception that will trigger error message intended to
+ the user, but without backtrace since it is not a software error """
+
+ raise UserAddressedException(message)
def SimplePOULibraryFactory(path):
class SimplePOULibrary(POULibrary):
--- a/ProjectController.py Thu Jan 28 14:50:26 2021 +0000
+++ b/ProjectController.py Thu Jan 28 14:51:16 2021 +0000
@@ -63,7 +63,8 @@
import targets
from runtime.typemapping import DebugTypesSize, UnpackDebugBuffer
from runtime import PlcStatus
-from ConfigTreeNode import ConfigTreeNode, XSDSchemaErrorMessage, UserAddressedException
+from ConfigTreeNode import ConfigTreeNode, XSDSchemaErrorMessage
+from POULibrary import UserAddressedException
base_folder = paths.AbsParentDir(__file__)
@@ -1441,7 +1442,8 @@
PlcStatus.Stopped: {"_Run": True,
"_Transfer": True,
"_Connect": False,
- "_Disconnect": True},
+ "_Disconnect": True,
+ "_Repair": True},
PlcStatus.Empty: {"_Transfer": True,
"_Connect": False,
"_Disconnect": True},
@@ -1978,6 +1980,13 @@
"shown": False,
},
{
+ "bitmap": "Disconnect",
+ "name": _("Disconnect"),
+ "tooltip": _("Disconnect from PLC"),
+ "method": "_Disconnect",
+ "shown": False,
+ },
+ {
"bitmap": "Repair",
"name": _("Repair"),
"tooltip": _("Repair broken PLC"),
@@ -1985,13 +1994,6 @@
"shown": False,
},
{
- "bitmap": "Disconnect",
- "name": _("Disconnect"),
- "tooltip": _("Disconnect from PLC"),
- "method": "_Disconnect",
- "shown": False,
- },
- {
"bitmap": "IDManager",
"name": _("ID Manager"),
"tooltip": _("Manage secure connection identities"),
--- a/bacnet/bacnet.py Thu Jan 28 14:50:26 2021 +0000
+++ b/bacnet/bacnet.py Thu Jan 28 14:51:16 2021 +0000
@@ -36,6 +36,7 @@
from bacnet.BacnetSlaveEditor import ObjectProperties
from PLCControler import LOCATION_CONFNODE, LOCATION_VAR_MEMORY
from ConfigTreeNode import ConfigTreeNode
+import util.paths as paths
base_folder = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]
base_folder = os.path.join(base_folder, "..")
@@ -775,5 +776,20 @@
# fobject = file object, already open'ed for read() !!
#
# extra_files -> files that will be downloaded to the PLC!
- return [(Generated_BACnet_c_mainfile_name, CFLAGS)], LDFLAGS, True
+
+ websettingfile = open(paths.AbsNeighbourFile(__file__, "web_settings.py"), 'r')
+ websettingcode = websettingfile.read()
+ websettingfile.close()
+
+ location_str = "_".join(map(str, self.GetCurrentLocation()))
+ websettingcode = websettingcode % locals()
+
+ runtimefile_path = os.path.join(buildpath, "runtime_bacnet_websettings.py")
+ runtimefile = open(runtimefile_path, 'w')
+ runtimefile.write(websettingcode)
+ runtimefile.close()
+
+ return ([(Generated_BACnet_c_mainfile_name, CFLAGS)], LDFLAGS, True,
+ ("runtime_bacnet_websettings_%s.py" % location_str, open(runtimefile_path, "rb")),
+ )
#return [(Generated_BACnet_c_mainfile_name, CFLAGS)], LDFLAGS, True, ('extrafile1.txt', extra_file_handle)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bacnet/web_settings.py Thu Jan 28 14:51:16 2021 +0000
@@ -0,0 +1,402 @@
+#!/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
+
+import runtime.NevowServer as NS
+
+
+# Will contain references to the C functions
+# (implemented in beremiz/bacnet/runtime/server.c)
+# used to get/set the BACnet specific configuration paramters
+BacnetGetParamFuncs = {}
+BacnetSetParamFuncs = {}
+
+
+# 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)
+_BacnetDefaultConfiguration = 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.
+_BacnetWebviewConfiguration = 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 _BacnetSavedConfiguration is not None)
+_BacnetSavedConfiguration = 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 = os.path.join(WorkingDir, "bacnetconf.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 _CheckBacnetPortnumber(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 _CheckBacnetDeviceID(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 _CheckBacnetConfiguration(BACnetConfig):
+ res = True
+ res = res and _CheckBacnetPortnumber(BACnetConfig["port_number"])
+ res = res and _CheckBacnetDeviceID (BACnetConfig["device_id"])
+ return res
+
+
+
+def _CheckBacnetWebConfiguration(BACnetConfig):
+ res = True
+
+ # check the port number
+ if not _CheckBacnetPortnumber(BACnetConfig["port_number"]):
+ raise annotate.ValidateError(
+ {"port_number": "Invalid port number: " + str(BACnetConfig["port_number"])},
+ _("BACnet configuration error:"))
+ res = False
+
+ if not _CheckBacnetDeviceID(BACnetConfig["device_id"]):
+ raise annotate.ValidateError(
+ {"device_id": "Invalid device ID: " + str(BACnetConfig["device_id"])},
+ _("BACnet configuration error:"))
+ res = False
+
+ return res
+
+
+
+
+
+
+def _SetBacnetSavedConfiguration(BACnetConfig):
+ """ Stores in a file a dictionary containing the BACnet parameter configuration """
+ global _BacnetSavedConfiguration
+
+ if BACnetConfig == _BacnetDefaultConfiguration :
+ _DelBacnetSavedConfiguration()
+ _BacnetSavedConfiguration = None
+ else :
+ with open(os.path.realpath(_BACnetConfFilename), 'w') as f:
+ json.dump(BACnetConfig, f, sort_keys=True, indent=4)
+ _BacnetSavedConfiguration = BACnetConfig
+
+
+def _DelBacnetSavedConfiguration():
+ """ Deletes the file cotaining the persistent BACnet configuration """
+ if os.path.exists(_BACnetConfFilename):
+ os.remove(_BACnetConfFilename)
+
+
+def _GetBacnetSavedConfiguration():
+ """
+ # 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 _CheckBacnetConfiguration(saved_config):
+ return saved_config
+ else:
+ return None
+
+
+def _GetBacnetPLCConfiguration():
+ """
+ # 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 = BacnetGetParamFuncs[par_name]()
+ if value is not None:
+ current_config[par_name] = value
+
+ return current_config
+
+
+def _SetBacnetPLCConfiguration(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]
+ #PLCObject.LogMessage("BACnet web server extension::_SetBacnetPLCConfiguration() Setting "
+ # + par_name + " to " + str(value) )
+ if value is not None:
+ BacnetSetParamFuncs[par_name](value)
+ # update the configuration shown on the web interface
+ global _BacnetWebviewConfiguration
+ _BacnetWebviewConfiguration = _GetBacnetPLCConfiguration()
+
+
+
+def _GetBacnetWebviewConfigurationValue(ctx, argument):
+ """
+ # Callback function, called by the web interface (NevowServer.py)
+ # to fill in the default value of each parameter
+ """
+ try:
+ return _BacnetWebviewConfiguration[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=_GetBacnetWebviewConfigurationValue))
+ for name, web_label, c_dtype, web_dtype in BACnet_parameters]
+
+
+def OnBacnetButtonSave(**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!
+ """
+
+ #PLCObject.LogMessage("BACnet web server extension::OnBacnetButtonSave() 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
+
+
+ # First check if configuration is OK.
+ if not _CheckBacnetWebConfiguration(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)
+ _SetBacnetSavedConfiguration(newConfig)
+
+ # Configure PLC with the current BACnet parameters
+ _SetBacnetPLCConfiguration(newConfig)
+
+
+
+def OnBacnetButtonReset(**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
+ _DelBacnetSavedConfiguration()
+ # Set the current configuration to the default (hardcoded in C)
+ _SetBacnetPLCConfiguration(_BacnetDefaultConfiguration)
+ # Reset global variable
+ global _BacnetSavedConfiguration
+ _BacnetSavedConfiguration = None
+
+
+
+# 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
+ # (i.e. XXX.so file) is transfered to the PLC runtime
+ # and oaded into memory
+ """
+
+ #PLCObject.LogMessage("BACnet web server extension::OnLoadPLC() Called...")
+
+ if PLCObject.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
+
+ # 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:
+ # 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
+
+ # XXX TODO : stop reading from PLC .so file. This code is template code
+ # that can use modbus extension build data
+ BacnetGetParamFuncs[name] = getattr(PLCObject.PLClibraryHandle, GetParamFuncName)
+ BacnetGetParamFuncs[name].restype = c_dtype
+ BacnetGetParamFuncs[name].argtypes = None
+
+ BacnetSetParamFuncs[name] = getattr(PLCObject.PLClibraryHandle, SetParamFuncName)
+ BacnetSetParamFuncs[name].restype = None
+ BacnetSetParamFuncs[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 _SetBacnetPLCConfiguration(BacnetSavedConfiguration)
+ global _BacnetDefaultConfiguration
+ _BacnetDefaultConfiguration = _GetBacnetPLCConfiguration()
+
+ # Show the current PLC configuration on the web interface
+ global _BacnetWebviewConfiguration
+ _BacnetWebviewConfiguration = _GetBacnetPLCConfiguration()
+
+ # 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 _SetBacnetPLCConfiguration() will also update
+ # _BacnetWebviewConfiguration , if necessary.
+ global _BacnetSavedConfiguration
+ _BacnetSavedConfiguration = _GetBacnetSavedConfiguration()
+ if _BacnetSavedConfiguration is not None:
+ if _CheckBacnetConfiguration(_BacnetSavedConfiguration):
+ _SetBacnetPLCConfiguration(_BacnetSavedConfiguration)
+
+ WebSettings = NS.newExtensionSetting("BACnet extension", "bacnet_token")
+
+ # Configure the web interface to include the BACnet config parameters
+ WebSettings.addSettings(
+ "BACnetConfigParm", # name
+ _("BACnet Configuration"), # description
+ webFormInterface, # fields
+ _("Apply"), # button label
+ OnBacnetButtonSave) # callback
+
+ # Add the Delete button to the web interface
+ WebSettings.addSettings(
+ "BACnetConfigDelSaved", # name
+ _("BACnet Configuration"), # description
+ [ ("status",
+ annotate.String(label=_("Current state"),
+ immutable=True,
+ default=lambda *k:getBacnetConfigStatus())),
+ ], # fields (empty, no parameters required!)
+ _("Reset"), # button label
+ OnBacnetButtonReset)
+
+
+
+def getBacnetConfigStatus():
+ if _BacnetWebviewConfiguration == _BacnetDefaultConfiguration :
+ 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
+ """
+
+ #PLCObject.LogMessage("BACnet web server extension::OnUnLoadPLC() Called...")
+
+ NS.removeExtensionSetting("bacnet_token")
+
+ BacnetGetParamFuncs = {}
+ BacnetSetParamFuncs = {}
+ _BacnetWebviewConfiguration = None
+ _BacnetSavedConfiguration = None
+
+
+
+
--- a/etherlab/CommonEtherCATFunction.py Thu Jan 28 14:50:26 2021 +0000
+++ b/etherlab/CommonEtherCATFunction.py Thu Jan 28 14:51:16 2021 +0000
@@ -50,10 +50,9 @@
return name.getcontent()
return default
-# --------------------------------------------------
+#--------------------------------------------------
# Remote Exec Etherlab Commands
-# --------------------------------------------------
-
+#--------------------------------------------------
# --------------------- for master ---------------------------
MASTER_STATE = """
@@ -84,18 +83,21 @@
returnVal = result
"""
-# ethercat sdos -p (slave position)
-SLAVE_SDO = """
+# ethercat upload -p (slave position) -t (type) (index) (sub index)
+SDO_UPLOAD = """
import commands
-result = commands.getoutput("ethercat sdos -p %d")
-returnVal =result
-"""
-
-# ethercat upload -p (slave position) (main index) (sub index)
-GET_SLOW_SDO = """
-import commands
-result = commands.getoutput("ethercat upload -p %d %s %s")
-returnVal =result
+sdo_data = []
+input_data = "%s"
+slave_pos = %d
+command_string = ""
+for sdo_token in input_data.split(","):
+ if len(sdo_token) > 1:
+ sdo_token = sdo_token.strip()
+ type, idx, subidx = sdo_token.split(" ")
+ command_string = "ethercat upload -p " + str(slave_pos) + " -t " + type + " " + idx + " " + subidx
+ result = commands.getoutput(command_string)
+ sdo_data.append(result)
+returnVal =sdo_data
"""
# ethercat download -p (slave position) (main index) (sub index) (value)
@@ -119,6 +121,25 @@
returnVal =result
"""
+# ethercat reg_read -p (slave position) (address) (size)
+MULTI_REG_READ = """
+import commands
+output = []
+addr, size = range(2)
+slave_num = %d
+reg_info_str = "%s"
+reg_info_list = reg_info_str.split("|")
+for slave_idx in range(slave_num):
+ for reg_info in reg_info_list:
+ param = reg_info.split(",")
+ result = commands.getoutput("ethercat reg_read -p "
+ + str(slave_idx) + " "
+ + param[addr] + " "
+ + param[size])
+ output.append(str(slave_idx) + "," + param[addr] + "," + result)
+returnVal = output
+"""
+
# ethercat sii_write -p (slave position) - (contents)
SII_WRITE = """
import subprocess
@@ -143,6 +164,12 @@
returnVal =result
"""
+# ethercat pdos
+PDOS = """
+import commands
+result = commands.getoutput("ethercat pdos -p 0")
+returnVal =result
+"""
# --------------------------------------------------
# Common Method For EtherCAT Management
@@ -152,14 +179,40 @@
# ----- Data Structure for ethercat management ----
SlaveState = ""
+ # SDO base data type for Ethercatmaster
+ BaseDataTypes = {
+ "bool": ["BOOLEAN", "BOOL", "BIT"],
+ "uint8": ["BYTE", "USINT", "BIT1", "BIT2", "BIT3", "BIT4", "BIT5", "BIT6",
+ "BIT7", "BIT8", "BITARR8", "UNSIGNED8"],
+ "uint16": ["BITARR16", "UNSIGNED16", "UINT"],
+ "uint32": ["BITARR32", "UNSIGNED24", "UINT24", "UNSIGNED32", "UDINT"],
+ "uint64": ["UNSINED40", "UINT40", "UNSIGNED48", "UINT48", "UNSIGNED56",
+ "UINT56", "UNSIGNED64", "ULINT"],
+ "int8": ["INTEGER8", "SINT"],
+ "int16": ["INTEGER16", "INT"],
+ "int32": ["INTEGER24", "INT24", "INTEGER32", "DINT"],
+ "int64": ["INTEGER40", "INT40", "INTEGER48", "INT48", "INTEGER56", "INT56",
+ "INTEGER64", "LINT"],
+ "float": ["REAL", "REAL32"],
+ "double": ["LREAL", "REAL64"],
+ "string": ["VISUBLE_STRING", "STRING(n)"],
+ "octet_string": ["OCTET_STRING"],
+ "unicode_string": ["UNICODE_STRING"]
+ }
+
# category of SDO data
DatatypeDescription, CommunicationObject, ManufacturerSpecific, \
ProfileSpecific, Reserved, AllSDOData = range(6)
- # store the execution result of "ethercat sdos" command into SaveSDOData.
- SaveSDOData = []
-
- # Flags for checking "write" permission of OD entries
+ # SDO data informations: index, sub-index, type, bit size, category-name
+ SDOVariables = []
+ SDOSubEntryData = []
+
+ # defalut value of SDO data in XML
+ # Not Used
+ DefaultValueDic = {}
+
+ # Flags for checking "write" permission of OD entries
CheckPREOP = False
CheckSAFEOP = False
CheckOP = False
@@ -241,15 +294,6 @@
# -------------------------------------------------------------------------------
# Used SDO Management
# -------------------------------------------------------------------------------
- def GetSlaveSDOFromSlave(self):
- """
- Get SDO objects information of current slave using "ethercat sdos -p %d" command.
- Command example : "ethercat sdos -p 0"
- @return return_val : execution results of "ethercat sdos" command (need to be parsed later)
- """
- _error, return_val = self.Controler.RemoteExec(SLAVE_SDO % (self.Controler.GetSlavePos()), return_val=None)
- return return_val
-
def SDODownload(self, data_type, idx, sub_idx, value):
"""
Set an SDO object value to user-specified value using "ethercat download" command.
@@ -258,11 +302,14 @@
@param idx : index of the SDO entry
@param sub_idx : subindex of the SDO entry
@param value : value of SDO entry
- """
- _error, _return_val = self.Controler.RemoteExec(
- SDO_DOWNLOAD % (data_type, self.Controler.GetSlavePos(), idx, sub_idx, value),
- return_val=None)
-
+ """
+ valid_type = self.GetValidDataType(data_type)
+ _error, return_val = self.Controler.RemoteExec(
+ SDO_DOWNLOAD%(valid_type, self.Controler.GetSlavePos(),
+ idx, sub_idx, value), return_val = None)
+
+ return return_val
+
def BackupSDODataSet(self):
"""
Back-up current SDO entry information to restore the SDO data
@@ -282,12 +329,316 @@
for dummy in range(6):
self.SaveSDOData.append([])
+ def GetAllSDOValuesFromSlave(self):
+ """
+ Get SDO values of All SDO entries.
+ @return return_val: list of result of "SDO_UPLOAD"
+ """
+ entry_infos = ""
+ alldata_idx = len(self.SDOVariables)
+ counter = 0
+ for category in self.SDOVariables:
+ counter +=1
+ # for avoid redundant repetition
+ if counter == alldata_idx:
+ continue
+
+ for entry in category:
+ valid_type = self.GetValidDataType(entry["type"])
+ for_command_string = "%s %s %s ," % \
+ (valid_type, entry["idx"], entry["subIdx"])
+ entry_infos += for_command_string
+
+ error, return_val = self.Controler.RemoteExec(SDO_UPLOAD%(entry_infos, self.Controler.GetSlavePos()), return_val = None)
+
+ return return_val
+
+ def GetSDOValuesFromSlave(self, entries_info):
+ """
+ Get SDO values of some SDO entries.
+ @param entries_info: dictionary of SDO entries that is wanted to know the value.
+ @return return_val: list of result of "SDO_UPLOAD"
+ """
+ entry_infos = ""
+
+ entries_info_list = entries_info.items()
+ entries_info_list.sort()
+
+ for (idx, subIdx), entry in entries_info_list:
+ valid_type = self.GetValidDataType(entry["type"])
+ for_command_string = "%s %s %s ," % \
+ (valid_type, str(idx), str(subIdx))
+ entry_infos += for_command_string
+
+ error, return_val = self.Controler.RemoteExec(SDO_UPLOAD%(entry_infos, self.Controler.GetSlavePos()), return_val = None)
+
+ return return_val
+
+ def ExtractObjects(self):
+ """
+ Extract object type items from imported ESI xml.
+ And they are stuctured as dictionary.
+ @return objects: dictionary of objects
+ """
+ objects = {}
+
+ slave = self.Controler.CTNParent.GetSlave(self.Controler.GetSlavePos())
+ type_infos = slave.getType()
+ device, alignment = self.Controler.CTNParent.GetModuleInfos(type_infos)
+
+ if device is not None :
+ for dictionary in device.GetProfileDictionaries():
+ dictionary.load()
+ for object in dictionary.getObjects().getObject():
+ object_index = ExtractHexDecValue(object.getIndex().getcontent())
+ objects[(object_index)] = object
+
+ return objects
+
+ def ExtractAllDataTypes(self):
+ """
+ Extract all data types from imported ESI xml.
+ @return dataTypes: dictionary of datatypes
+ """
+ dataTypes = {}
+
+ slave = self.Controler.CTNParent.GetSlave(self.Controler.GetSlavePos())
+ type_infos = slave.getType()
+ device, alignment = self.Controler.CTNParent.GetModuleInfos(type_infos)
+
+ for dictionary in device.GetProfileDictionaries():
+ dictionary.load()
+
+ datatypes = dictionary.getDataTypes()
+ if datatypes is not None:
+
+ for datatype in datatypes.getDataType():
+ dataTypes[datatype.getName()] = datatype
+ return dataTypes
+
+ def IsBaseDataType(self, datatype):
+ """
+ Check if the datatype is a base data type.
+ @return baseTypeFlag: true if datatype is a base data type, unless false
+ """
+ baseTypeFlag = False
+ for baseDataTypeList in self.BaseDataTypes.values():
+ if datatype in baseDataTypeList:
+ baseTypeFlag = True
+ break
+ return baseTypeFlag
+
+ def GetBaseDataType(self, datatype):
+ """
+ Get a base data type corresponding the datatype.
+ @param datatype: Some data type (string format)
+ @return base data type
+ """
+ if self.IsBaseDataType(datatype):
+ return datatype
+ elif not datatype.find("STRING") == -1:
+ return datatype
+ else:
+ datatypes = self.ExtractAllDataTypes()
+ base_datatype = datatypes[datatype].getBaseType()
+ return self.GetBaseDataType(base_datatype)
+
+ def GetValidDataType(self, datatype):
+ """
+ Convert the datatype into a data type that is possible to download/upload
+ in etherlab master stack.
+ @param datatype: Some data type (string format)
+ @return base_type: vaild data type
+ """
+ base_type = self.GetBaseDataType(datatype)
+
+ if re.match("STRING\([0-9]*\)", datatype) is not None:
+ return "string"
+ else:
+ for key, value in self.BaseDataTypes.items():
+ if base_type in value:
+ return key
+ return base_type
+
+ # Not Used
+ def GetAllEntriesList(self):
+ """
+ Get All entries information that includes index, sub-index, name,
+ type, bit size, PDO mapping, and default value.
+ @return self.entries: dictionary of entry
+ """
+ slave = self.Controler.CTNParent.GetSlave(self.Controler.GetSlavePos())
+ type_infos = slave.getType()
+ device, alignment = self.Controler.CTNParent.GetModuleInfos(type_infos)
+ self.entries = device.GetEntriesList()
+ datatypes = self.ExtractAllDataTypes()
+ objects = self.ExtractObjects()
+ entries_list = self.entries.items()
+ entries_list.sort()
+
+ # append sub entries
+ for (index, subidx), entry in entries_list:
+ # entry_* is string type
+ entry_type = entry["Type"]
+ entry_index = entry["Index"]
+
+ try:
+ object_info = objects[index].getInfo()
+ except:
+ continue
+
+ if object_info is not None:
+ obj_content = object_info.getcontent()
+
+ typeinfo = datatypes.get(entry_type, None)
+ bitsize = typeinfo.getBitSize()
+ type_content = typeinfo.getcontent()
+
+ # ArrayInfo type
+ if type_content is not None and type_content["name"] == "ArrayInfo":
+ for arrayinfo in type_content["value"]:
+ element_num = arrayinfo.getElements()
+ first_subidx = arrayinfo.getLBound()
+ for offset in range(element_num):
+ new_subidx = int(first_subidx) + offset
+ entry_subidx = hex(new_subidx)
+ if obj_content["value"][new_subidx]["name"] == "SubItem":
+ subitem = obj_content["value"][new_subidx]["value"]
+ subname = subitem[new_subidx].getName()
+ if subname is not None:
+ entry_name = "%s - %s" % \
+ (ExtractName(objects[index].getName()), subname)
+ else:
+ entry_name = ExtractName(objects[index].getName())
+ self.entries[(index, new_subidx)] = {
+ "Index": entry_index,
+ "SubIndex": entry_subidx,
+ "Name": entry_name,
+ "Type": typeinfo.getBaseType(),
+ "BitSize": str(bitsize/element_num),
+ "Access": entry["Access"],
+ "PDOMapping": entry["PDOMapping"]}
+ try:
+ value_info = subitem[new_subidx].getInfo().getcontent()\
+ ["value"][0]["value"][0]
+ self.AppendDefaultValue(index, new_subidx, value_info)
+ except:
+ pass
+
+ try:
+ value_info = subitem[subidx].getInfo().getcontent()\
+ ["value"][0]["value"][0]
+ self.AppendDefaultValue(index, subidx, value_info)
+ except:
+ pass
+
+ # EnumInfo type
+ elif type_content is not None and type_content["name"] == "EnumInfo":
+ self.entries[(index, subidx)]["EnumInfo"] = {}
+
+ for enuminfo in type_content["value"]:
+ text = enuminfo.getText()
+ enum = enuminfo.getEnum()
+ self.entries[(index, subidx)]["EnumInfo"][str(enum)] = text
+
+ self.entries[(index, subidx)]["DefaultValue"] = "0x00"
+
+ # another types
+ else:
+ if subidx == 0x00:
+ tmp_subidx = 0x00
+
+ try:
+ if obj_content["value"][tmp_subidx]["name"] == "SubItem":
+ sub_name = entry["Name"].split(" - ")[1]
+ for num in range(len(obj_content["value"])):
+ if sub_name == \
+ obj_content["value"][num]["value"][num].getName():
+ subitem_content = obj_content["value"][tmp_subidx]\
+ ["value"][tmp_subidx]
+ value_info = subitem_content.getInfo().getcontent()\
+ ["value"][0]["value"][0]
+ tmp_subidx += 1
+ break
+ else:
+ value_info = None
+
+ else:
+ value_info = \
+ obj_content["value"][tmp_subidx]["value"][tmp_subidx]
+
+ self.AppendDefaultValue(index, subidx, value_info)
+
+ except:
+ pass
+
+ return self.entries
+
+ # Not Used
+ def AppendDefaultValue(self, index, subidx, value_info=None):
+ """
+ Get the default value from the ESI xml
+ @param index: entry index
+ @param subidx: entry sub index
+ @param value_info: dictionary of infomation about default value
+
+ """
+ # there is not default value.
+ if value_info == None:
+ return
+
+ raw_value = value_info["value"]
+
+ # default value is hex binary type.
+ if value_info["name"] == "DefaultData":
+ raw_value_bit = list(hex(raw_value).split("0x")[1])
+
+ datatype = self.GetValidDataType(self.entries[(index, subidx)]["Type"])
+ if datatype is "string" or datatype is "octet_string":
+
+ if "L" in raw_value_bit:
+ raw_value_bit.remove("L")
+
+ default_value = "".join(raw_value_bit).decode("hex")
+
+ elif datatype is "unicode_string":
+ default_value = "".join(raw_value_bit).decode("hex").\
+ decode("utf-8")
+
+ else:
+ bit_num = len(raw_value_bit)
+ # padding
+ if not bit_num%2 == 0:
+ raw_value_bit.insert(0, "0")
+
+ default_value_bit = []
+
+ # little endian -> big endian
+ for num in range(bit_num):
+ if num%2 == 0:
+ default_value_bit.insert(0, raw_value_bit[num])
+ default_value_bit.insert(1, raw_value_bit[num+1])
+
+ default_value = "0x%s" % "".join(default_value_bit)
+
+ # default value is string type.
+ # this case is not tested yet.
+ elif value_info["name"] == "DefaultString":
+ default_value = raw_value
+
+ # default value is Hex or Dec type.
+ elif value_info["name"] == "DefaultValue":
+ default_value = "0x" + hex(ExtractHexDecValue(raw_value))
+
+ self.entries[(index, subidx)]["DefaultValue"] = default_value
+
# -------------------------------------------------------------------------------
# Used PDO Monitoring
# -------------------------------------------------------------------------------
def RequestPDOInfo(self):
"""
- Load slave information from RootClass (XML data) and parse the information (calling SlavePDOData() method).
+ Load slave information from RootClass (XML data) and parse the information
+ (calling SlavePDOData() method).
"""
# Load slave information from ESI XML file (def EthercatMaster.py)
slave = self.Controler.CTNParent.GetSlave(self.Controler.GetSlavePos())
@@ -314,6 +665,13 @@
pdo_index = ExtractHexDecValue(pdo.getIndex().getcontent())
entries = pdo.getEntry()
pdo_name = ExtractName(pdo.getName())
+ excludes = pdo.getExclude()
+ exclude_list = []
+ Sm = pdo.getSm()
+
+ if excludes :
+ for exclude in excludes:
+ exclude_list.append(ExtractHexDecValue(exclude.getcontent()))
# Initialize entry number count
count = 0
@@ -329,13 +687,15 @@
"entry_index": index,
"subindex": subindex,
"name": ExtractName(entry.getName()),
- "bitlen": entry.getBitLen(),
- "type": entry.getDataType().getcontent()
+ "bitlen": entry.getBitLen()
}
+ if entry.getDataType() is not None:
+ entry_infos["type"] = entry.getDataType().getcontent()
self.TxPDOInfo.append(entry_infos)
count += 1
- categorys = {"pdo_index": pdo_index, "name": pdo_name, "number_of_entry": count}
+ categorys = {"pdo_index" : pdo_index, "name" : pdo_name, "sm" : Sm,
+ "number_of_entry" : count, "exclude_list" : exclude_list}
self.TxPDOCategory.append(categorys)
# Parsing RxPDO entries
@@ -344,6 +704,13 @@
pdo_index = ExtractHexDecValue(pdo.getIndex().getcontent())
entries = pdo.getEntry()
pdo_name = ExtractName(pdo.getName())
+ excludes = pdo.getExclude()
+ exclude_list = []
+ Sm = pdo.getSm()
+
+ if excludes :
+ for exclude in excludes:
+ exclude_list.append(ExtractHexDecValue(exclude.getcontent()))
# Initialize entry number count
count = 0
@@ -359,14 +726,16 @@
"entry_index": index,
"subindex": subindex,
"name": ExtractName(entry.getName()),
- "bitlen": str(entry.getBitLen()),
- "type": entry.getDataType().getcontent()
+ "bitlen": str(entry.getBitLen())
}
+ if entry.getDataType() is not None:
+ entry_infos["type"] = entry.getDataType().getcontent()
self.RxPDOInfo.append(entry_infos)
count += 1
- categorys = {"pdo_index": pdo_index, "name": pdo_name, "number_of_entry": count}
- self.RxPDOCategory.append(categorys)
+ categorys = {"pdo_index" : pdo_index, "name" : pdo_name, "sm" : Sm,
+ "number_of_entry" : count, "exclude_list" : exclude_list}
+ self.RxPDOCategory.append(categorys)
def GetTxPDOCategory(self):
"""
@@ -404,10 +773,10 @@
"""
Initialize PDO management data structure.
"""
- self.TxPDOInfos = []
- self.TxPDOCategorys = []
- self.RxPDOInfos = []
- self.RxPDOCategorys = []
+ self.TxPDOInfo = []
+ self.TxPDOCategory = []
+ self.RxPDOInfo = []
+ self.RxPDOCategory = []
# -------------------------------------------------------------------------------
# Used EEPROM Management
@@ -496,11 +865,11 @@
smartview_infos[cfg] = str(int(bootstrap_data[4*iter+2:4*(iter+1)]+bootstrap_data[4*iter:4*iter+2], 16))
# get protocol (profile) types supported by mailbox; <Device>-<Mailbox>
- mb = device.getMailbox()
- if mb is not None:
- for mailbox_protocol in mailbox_protocols:
- if getattr(mb, "get%s" % mailbox_protocol)() is not None:
- smartview_infos["supported_mailbox"] += "%s, " % mailbox_protocol
+ with device.getMailbox() as mb:
+ if mb is not None:
+ for mailbox_protocol in mailbox_protocols:
+ if getattr(mb, "get%s" % mailbox_protocol)() is not None:
+ smartview_infos["supported_mailbox"] += "%s, " % mailbox_protocol
smartview_infos["supported_mailbox"] = smartview_infos["supported_mailbox"].strip(", ")
# get standard configuration of mailbox; <Device>-<Sm>
@@ -515,24 +884,24 @@
pass
# get device identity from <Device>-<Type>
- # vendor ID; by default, pre-defined value in self.ModulesLibrary
- # if device type in 'vendor' item equals to actual slave device type, set 'vendor_id' to vendor ID.
+ # vendor ID; by default, pre-defined value in self.ModulesLibrary
+ # if device type in 'vendor' item equals to actual slave device type, set 'vendor_id' to vendor ID.
for vendor_id, vendor in self.Controler.CTNParent.CTNParent.ModulesLibrary.Library.iteritems():
for available_device in vendor["groups"][vendor["groups"].keys()[0]]["devices"]:
if available_device[0] == type_infos["device_type"]:
smartview_infos["vendor_id"] = "0x" + "{:0>8x}".format(vendor_id)
- # product code;
+ # product code;
if device.getType().getProductCode() is not None:
product_code = device.getType().getProductCode()
smartview_infos["product_code"] = "0x"+"{:0>8x}".format(ExtractHexDecValue(product_code))
- # revision number;
+ # revision number;
if device.getType().getRevisionNo() is not None:
revision_no = device.getType().getRevisionNo()
smartview_infos["revision_no"] = "0x"+"{:0>8x}".format(ExtractHexDecValue(revision_no))
- # serial number;
+ # serial number;
if device.getType().getSerialNo() is not None:
serial_no = device.getType().getSerialNo()
smartview_infos["serial_no"] = "0x"+"{:0>8x}".format(ExtractHexDecValue(serial_no))
@@ -548,14 +917,14 @@
@param decnum : decimal value
@return hex_data : hexadecimal representation of input value in decimal
"""
- value = "%x" % decnum
+ value = "%x" % int(decnum, 16)
value_len = len(value)
if (value_len % 2) == 0:
hex_len = value_len
else:
hex_len = (value_len // 2) * 2 + 2
- hex_data = ("{:0>"+str(hex_len)+"x}").format(decnum)
+ hex_data = ("{:0>"+str(hex_len)+"x}").format(int(decnum, 16))
return hex_data
@@ -750,7 +1119,7 @@
data = ""
for eeprom_element in device.getEeprom().getcontent():
if eeprom_element["name"] == "BootStrap":
- data = "{:0>16x}".format(eeprom_element)
+ data = "{:0>16x}".format(int(eeprom_element,16))
eeprom += self.GenerateEEPROMList(data, 0, 16)
# get Standard Mailbox for EEPROM offset 0x0030-0x0037; <Device>-<sm>
@@ -794,11 +1163,11 @@
# get supported mailbox protocols for EEPROM offset 0x0038-0x0039;
data = 0
- mb = device.getMailbox()
- if mb is not None:
- for bit, mbprot in enumerate(mailbox_protocols):
- if getattr(mb, "get%s" % mbprot)() is not None:
- data += 1 << bit
+ with device.getMailbox() as mb:
+ if mb is not None:
+ for bit, mbprot in enumerate(mailbox_protocols):
+ if getattr(mb, "get%s" % mbprot)() is not None:
+ data += 1 << bit
data = "{:0>4x}".format(data)
eeprom.append(data[2:4])
eeprom.append(data[0:2])
@@ -808,11 +1177,12 @@
eeprom.append("00")
# get EEPROM size for EEPROM offset 0x007c-0x007d;
+ # Modify by jblee because of update IDE module (minidom -> lxml)
data = ""
- for eeprom_element in device.getEeprom().getcontent():
- if eeprom_element["name"] == "ByteSize":
- eeprom_size = int(str(eeprom_element))
- data = "{:0>4x}".format(int(eeprom_element)//1024*8-1)
+ for eeprom_element in device.getEeprom().getchildren():
+ if eeprom_element.tag == "ByteSize":
+ eeprom_size = int(objectify.fromstring(eeprom_element.tostring()).text)
+ data = "{:0>4x}".format(eeprom_size/1024*8-1)
if data == "":
eeprom.append("00")
@@ -822,7 +1192,7 @@
eeprom.append(data[0:2])
# Version for EEPROM 0x007e-0x007f;
- # According to "EtherCAT Slave Device Description(V0.3.0)"
+ # According to "EtherCAT Slave Device Description(V0.3.0)"
eeprom.append("01")
eeprom.append("00")
@@ -915,7 +1285,7 @@
vendor_specific_data += "{:0>2x}".format(ord(data[character]))
data = ""
- # element2-1; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<GroupType>
+ # element2-1; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<GroupType>
data = device.getGroupType()
if data is not None and isinstance(data, text):
for vendor_spec_string in vendor_spec_strings:
@@ -978,6 +1348,7 @@
vendor_specific_data += "{:0>2x}".format(len(data))
for character in range(len(data)):
vendor_specific_data += "{:0>2x}".format(ord(data[character]))
+
data = ""
# element4; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Name(LcId is "1033" or "1"?)>
@@ -1183,42 +1554,41 @@
# word 3 : Physical Layer Port info. and CoE Details
eeprom.append("01") # Physical Layer Port info - assume 01
# CoE Details; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<CoE>
- coe_details = 0
- mb = device.getMailbox()
coe_details = 1 # sdo enabled
- if mb is not None:
- coe = mb.getCoE()
- if coe is not None:
- for bit, flag in enumerate(["SdoInfo", "PdoAssign", "PdoConfig",
- "PdoUpload", "CompleteAccess"]):
- if getattr(coe, "get%s" % flag)() is not None:
- coe_details += 1 << bit
- eeprom.append("{:0>2x}".format(coe_details))
-
- # word 4 : FoE Details and EoE Details
- # FoE Details; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<FoE>
- if mb is not None and mb.getFoE() is not None:
- eeprom.append("01")
- else:
- eeprom.append("00")
- # EoE Details; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<EoE>
- if mb is not None and mb.getEoE() is not None:
- eeprom.append("01")
- else:
- eeprom.append("00")
-
- # word 5 : SoE Channels(reserved) and DS402 Channels
- # SoE Details; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<SoE>
- if mb is not None and mb.getSoE() is not None:
- eeprom.append("01")
- else:
- eeprom.append("00")
- # DS402Channels; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<CoE>: DS402Channels
- ds402ch = False
- if mb is not None:
- coe = mb.getCoE()
- if coe is not None:
- ds402ch = coe.getDS402Channels()
+ with device.getMailbox() as mb
+ if mb is not None:
+ coe = mb.getCoE()
+ if coe is not None:
+ for bit, flag in enumerate(["SdoInfo", "PdoAssign", "PdoConfig",
+ "PdoUpload", "CompleteAccess"]):
+ if getattr(coe, "get%s" % flag)() is not None:
+ coe_details += 1 << bit
+ eeprom.append("{:0>2x}".format(coe_details))
+
+ # word 4 : FoE Details and EoE Details
+ # FoE Details; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<FoE>
+ if mb is not None and mb.getFoE() is not None:
+ eeprom.append("01")
+ else:
+ eeprom.append("00")
+ # EoE Details; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<EoE>
+ if mb is not None and mb.getEoE() is not None:
+ eeprom.append("01")
+ else:
+ eeprom.append("00")
+
+ # word 5 : SoE Channels(reserved) and DS402 Channels
+ # SoE Details; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<SoE>
+ if mb is not None and mb.getSoE() is not None:
+ eeprom.append("01")
+ else:
+ eeprom.append("00")
+ # DS402Channels; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<CoE>: DS402Channels
+ ds402ch = False
+ if mb is not None:
+ coe = mb.getCoE()
+ if coe is not None:
+ ds402ch = coe.getDS402Channels()
eeprom.append("01" if ds402ch in [True, 1] else "00")
# word 6 : SysmanClass(reserved) and Flags
@@ -1546,6 +1916,24 @@
return_val=None)
return return_val
+ def MultiRegRead(self, slave_num, reg_infos):
+ """
+
+ @slave_num:
+ @param addr_info:
+ @return return_val:
+ """
+ reg_info_str = ""
+ for reg_info in reg_infos:
+ reg_info_str = reg_info_str + "%s|" % reg_info
+ reg_info_str = reg_info_str.strip("|")
+
+ _error, return_val = self.Controler.RemoteExec(\
+ MULTI_REG_READ%(slave_num, reg_info_str),
+ return_val = None)
+
+ return return_val
+
def RegWrite(self, address, data):
"""
Write data to slave ESC register using "ethercat reg_write -p %d %s %s" command.
@@ -1567,6 +1955,40 @@
_error, _return_val = self.Controler.RemoteExec(RESCAN % (self.Controler.GetSlavePos()), return_val=None)
# -------------------------------------------------------------------------------
+ # Used DC Configuration
+ #-------------------------------------------------------------------------------
+ def LoadESIData(self):
+ return_data = []
+ slave = self.Controler.CTNParent.GetSlave(self.Controler.GetSlavePos())
+ type_infos = slave.getType()
+ device, alignment = self.Controler.CTNParent.GetModuleInfos(type_infos)
+ if device.getDc() is not None:
+ for OpMode in device.getDc().getOpMode():
+ temp_data = {
+ "desc" : OpMode.getDesc() if OpMode.getDesc() is not None else "Unused",
+ "assign_activate" : OpMode.getAssignActivate() \
+ if OpMode.getAssignActivate() is not None else "#x0000",
+ "cycletime_sync0" : OpMode.getCycleTimeSync0().getcontent() \
+ if OpMode.getCycleTimeSync0() is not None else None,
+ "shifttime_sync0" : OpMode.getShiftTimeSync0().getcontent() \
+ if OpMode.getShiftTimeSync0() is not None else None,
+ "cycletime_sync1" : OpMode.getShiftTimeSync1().getcontent() \
+ if OpMode.getShiftTimeSync1() is not None else None,
+ "shifttime_sync1" : OpMode.getShiftTimeSync1().getcontent() \
+ if OpMode.getShiftTimeSync1() is not None else None
+ }
+
+ if OpMode.getCycleTimeSync0() is not None:
+ temp_data["cycletime_sync0_factor"] = OpMode.getCycleTimeSync0().getFactor()
+
+ if OpMode.getCycleTimeSync1() is not None:
+ temp_data["cycletime_sync1_factor"] = OpMode.getCycleTimeSync1().getFactor()
+
+ return_data.append(temp_data)
+
+ return return_data
+
+ #-------------------------------------------------------------------------------
# Common Use Methods
# -------------------------------------------------------------------------------
def CheckConnect(self, cyclic_flag):
--- a/etherlab/ConfigEditor.py Thu Jan 28 14:50:26 2021 +0000
+++ b/etherlab/ConfigEditor.py Thu Jan 28 14:51:16 2021 +0000
@@ -149,6 +149,9 @@
self.VariablesGrid.SetItemText(item, str(idx), 0)
else:
value = entry.get(colname, "")
+ # add jblee
+ if value is None:
+ value = ""
if colname == "Access":
value = GetAccessValue(value, entry.get("PDOMapping", ""))
self.VariablesGrid.SetItemText(item, value, col)
@@ -617,9 +620,7 @@
self.MasterStateEditor = wx.ScrolledWindow(prnt, style=wx.TAB_TRAVERSAL | wx.HSCROLL | wx.VSCROLL)
self.MasterStateEditor.Bind(wx.EVT_SIZE, self.OnResize)
- self.MasterStateEditor_Panel_Main_Sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=5)
- self.MasterStateEditor_Panel_Main_Sizer.AddGrowableCol(0)
- self.MasterStateEditor_Panel_Main_Sizer.AddGrowableRow(0)
+ self.MasterStateEditor_Panel_Main_Sizer = wx.BoxSizer(wx.VERTICAL)
self.MasterStateEditor_Panel = MasterStatePanelClass(self.MasterStateEditor, self.Controler)
@@ -628,6 +629,18 @@
self.MasterStateEditor.SetSizer(self.MasterStateEditor_Panel_Main_Sizer)
return self.MasterStateEditor
+ def OnResize2(self, event):
+ self.MasterStateEditor.GetBestSize()
+ xstart, ystart = self.MasterStateEditor.GetViewStart()
+ window_size = self.MasterStateEditor.GetClientSize()
+ maxx, maxy = self.MasterStateEditor.GetMinSize()
+ posx = max(0, min(xstart, (maxx - window_size[0]) / SCROLLBAR_UNIT))
+ posy = max(0, min(ystart, (maxy - window_size[1]) / SCROLLBAR_UNIT))
+ self.MasterStateEditor.Scroll(posx, posy)
+ self.MasterStateEditor.SetScrollbars(SCROLLBAR_UNIT, SCROLLBAR_UNIT,
+ maxx / SCROLLBAR_UNIT, maxy / SCROLLBAR_UNIT, posx, posy)
+ event.Skip()
+
def _create_EthercatMasterEditor(self, prnt):
self.EthercatMasterEditor = wx.ScrolledWindow(prnt,
style=wx.TAB_TRAVERSAL | wx.HSCROLL | wx.VSCROLL)
--- a/etherlab/EtherCATManagementEditor.py Thu Jan 28 14:50:26 2021 +0000
+++ b/etherlab/EtherCATManagementEditor.py Thu Jan 28 14:51:16 2021 +0000
@@ -71,11 +71,15 @@
panels = [
("Slave State", SlaveStatePanelClass, []),
("SDO Management", SDOPanelClass, []),
- ("PDO Monitoring", PDOPanelClass, []),
+ ("PDO Mapping", PDOPanelClass, [
+ ("Rx PDO", RxPDOPanelClass),
+ ("Tx PDO", TxPDOPanelClass)]),
+ ("MDP Setting", MDPPanel, []),
("ESC Management", EEPROMAccessPanel, [
- ("Smart View", SlaveSiiSmartView),
- ("Hex View", HexView)]),
- ("Register Access", RegisterAccessPanel, [])
+ ("Smart View", SlaveSiiSmartView),
+ ("Hex View", HexView)]),
+ ("Register Access", RegisterAccessPanel, []),
+ ("DC Configuration", DCConfigPanel, [])
]
for pname, pclass, subs in panels:
self.AddPage(pclass(self, self.Controler), pname)
@@ -230,7 +234,9 @@
Event handler for slave state transition button click (Init, PreOP, SafeOP, OP button)
@param event : wx.EVT_BUTTON object
"""
- check_connect_flag = self.Controler.CommonMethod.CheckConnect(False)
+ # Check whether beremiz connected or not.
+ # If this method is called cyclically, set the cyclic flag true
+ check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = False)
if check_connect_flag:
state_dic = ["INIT", "PREOP", "SAFEOP", "OP"]
@@ -255,7 +261,12 @@
Timer event handler for periodic slave state monitoring (Default period: 1 sec = 1000 msec).
@param event : wx.TIMER object
"""
- check_connect_flag = self.Controler.CommonMethod.CheckConnect(True)
+ if self.IsShownOnScreen() is False:
+ return
+
+ # Check whether beremiz connected or not.
+ # If this method is called cyclically, set the cyclic flag true
+ check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = True)
if check_connect_flag:
returnVal = self.Controler.CommonMethod.GetSlaveStateFromSlave()
line = returnVal.split("\n")
@@ -285,9 +296,15 @@
- start slave state monitoring thread
@param event : wx.EVT_BUTTON object
"""
- self.SlaveStateThread = wx.Timer(self)
- # set timer period (1000 ms)
- self.SlaveStateThread.Start(1000)
+ # Check whether beremiz connected or not.
+ # If this method is called cyclically, set the cyclic flag true
+ check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = False)
+ if check_connect_flag:
+ self.SlaveStateThread = wx.Timer(self)
+ # set timer period (2000 ms)
+ self.SlaveStateThread.Start(2000)
+ else:
+ pass
def CurrentStateThreadStop(self, event):
"""
@@ -318,143 +335,218 @@
self.Controler = controler
+ self.SDOMonitorEntries = {}
+ #----------------------------- SDO Monitor -------------------------------#
+ self.RBList = ["ON","OFF"]
+
+ self.SDOMonitorRB = wx.RadioBox(self, label=_("monitoring"),
+ choices=self.RBList, majorDimension=2)
+
+ self.SDOMonitorRB.SetSelection(1)
+ self.Bind(wx.EVT_RADIOBOX, self.OnRadioBox, self.SDOMonitorRB)
+
+ self.SDOMonitorGrid = wx.grid.Grid(self,size=wx.Size(850,150),style=wx.EXPAND
+ |wx.ALIGN_CENTER_HORIZONTAL
+ |wx.ALIGN_CENTER_VERTICAL)
+ self.SDOMonitorGrid.Bind(gridlib.EVT_GRID_CELL_LEFT_DCLICK,
+ self.onMonitorGridDoubleClick)
+
+ #----------------------------- SDO Entries ----------------------------#
+ self.SDOUpdateBtn = wx.Button(self, label=_("update"))
+ self.SDOUpdateBtn.Bind(wx.EVT_BUTTON, self.OnSDOUpdate)
+
+ self.SDOTraceThread = None
+ self.SDOMonitoringFlag = False
+ self.SDOValuesList = []
+ # Default SDO Page Number
+ self.SDOPageNum = 2
+
+ #----------------------------- Sizer --------------------------------------#
self.SDOManagementMainSizer = wx.BoxSizer(wx.VERTICAL)
- self.SDOManagementInnerMainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=10)
-
- self.SDOUpdate = wx.Button(self, label=_('update'))
- self.SDOUpdate.Bind(wx.EVT_BUTTON, self.SDOInfoUpdate)
-
- self.CallSDONoteBook = SDONoteBook(self, controler=self.Controler)
- self.SDOManagementInnerMainSizer.Add(self.SDOUpdate)
- self.SDOManagementInnerMainSizer.Add(self.CallSDONoteBook, wx.ALL | wx.EXPAND)
-
- self.SDOManagementMainSizer.Add(self.SDOManagementInnerMainSizer)
-
+ self.SDOInfoBox = wx.StaticBox(self, label=_("SDO Entries"))
+ self.SDOMonitorBox = wx.StaticBox(self, label=_("SDO Monitor"))
+
+ self.SDONoteBook = SDONoteBook(self, controler=self.Controler)
+ self.SDOInfoBoxSizer = wx.StaticBoxSizer(self.SDOInfoBox, orient=wx.VERTICAL)
+ self.SDOMonitorBoxSizer = wx.StaticBoxSizer(self.SDOMonitorBox,
+ orient=wx.VERTICAL)
+ self.SDOInfoBoxSizer.Add(self.SDOUpdateBtn)
+
+ self.SDOInfoBoxSizer.Add(self.SDONoteBook, wx.ALL|wx.EXPAND)
+ self.SDOMonitorBoxSizer.Add(self.SDOMonitorRB)
+ self.SDOMonitorBoxSizer.Add(self.SDOMonitorGrid)
+ self.SDOManagementMainSizer.AddMany([self.SDOInfoBoxSizer,
+ self.SDOMonitorBoxSizer])
self.SetSizer(self.SDOManagementMainSizer)
-
- def SDOInfoUpdate(self, event):
- """
- Evenet handler for SDO "update" button.
- - Load SDO data from current slave
- @param event : wx.EVT_BUTTON object
- """
- self.Controler.CommonMethod.SaveSDOData = []
+
+ #----------------------------- fill the contents --------------------------#
+ #self.entries = self.Controler.CTNParent.CTNParent.GetEntriesList()
+
+ slave = self.Controler.CTNParent.GetSlave(self.Controler.GetSlavePos())
+ type_infos = slave.getType()
+ device, alignment = self.Controler.CTNParent.GetModuleInfos(type_infos)
+ self.entries = device.GetEntriesList()
+
+ self.Controler.CommonMethod.SDOVariables = []
+ self.Controler.CommonMethod.SDOSubEntryData = []
self.Controler.CommonMethod.ClearSDODataSet()
- self.SDOFlag = False
-
- # Check whether beremiz connected or not.
- check_connect_flag = self.Controler.CommonMethod.CheckConnect(False)
+ self.SDOParserXML(self.entries)
+ self.SDONoteBook.CreateNoteBook()
+ self.CreateSDOMonitorGrid()
+ self.Refresh()
+
+ def OnSDOUpdate(self, event):
+ SlavePos = self.Controler.GetSlavePos()
+ num = self.SDOPageNum - 1
+ if num < 0:
+ for i in range(len(self.Controler.CommonMethod.SDOVariables)):
+ data = self.Controler.GetCTRoot()._connector.GetSDOEntriesData(
+ self.Controler.CommonMethod.SDOVariables[i], SlavePos)
+ self.Controler.CommonMethod.SDOVariables[i] = data
+ else :
+ SDOUploadEntries = self.Controler.CommonMethod.SDOVariables[num]
+ data = self.Controler.GetCTRoot()._connector.GetSDOEntriesData(SDOUploadEntries, SlavePos)
+ self.Controler.CommonMethod.SDOVariables[num] = data
+
+ self.SDONoteBook.CreateNoteBook()
+ self.Refresh()
+
+ def OnRadioBox(self, event):
+ """
+ There are two selections that are on and off.
+ If the on is selected, the monitor thread begins to run.
+ If the off is selected, the monitor thread gets off.
+ @param event: wx.EVT_RADIOBOX object
+ """
+ on, off = range(2)
+
+ if event.GetInt() == on:
+ CheckThreadFlag = self.SDOMonitoringThreadOn()
+ if not CheckThreadFlag:
+ self.SDOMonitorRB.SetSelection(off)
+ elif event.GetInt() == off:
+ self.SDOMonitoringThreadOff()
+
+ def SDOMonitoringThreadOn(self):
+ check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = False)
if check_connect_flag:
- self.SDOs = self.Controler.CommonMethod.GetSlaveSDOFromSlave()
- # SDOFlag is "False", user click "Cancel" button
- self.SDOFlag = self.SDOParser()
-
- if self.SDOFlag:
- self.CallSDONoteBook.CreateNoteBook()
- self.Refresh()
-
- def SDOParser(self):
- """
- Parse SDO data set that obtain "SDOInfoUpdate" Method
- @return True or False
- """
-
- slaveSDO_progress = wx.ProgressDialog(_("Slave SDO Monitoring"), _("Now Uploading..."),
- maximum=len(self.SDOs.splitlines()),
- parent=self,
- style=wx.PD_CAN_ABORT | wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME |
- wx.PD_ESTIMATED_TIME | wx.PD_REMAINING_TIME |
- wx.PD_AUTO_HIDE | wx.PD_SMOOTH)
-
- # If keep_going flag is False, SDOParser method is stop and return "False".
- keep_going = True
- count = 0
-
- # SDO data example
- # SDO 0x1000, "Device type"
- # 0x1000:00,r-r-r-,uint32,32 bit,"Device type",0x00020192, 131474
- for details_line in self.SDOs.splitlines():
- count += 1
- line_token = details_line.split("\"")
- # len(line_token[2]) case : SDO 0x1000, "Device type"
- if len(line_token[2]) == 0:
- title_name = line_token[1]
- # else case : 0x1000:00,r-r-r-,uint32,32 bit,"Device type",0x00020192, 131474
- else:
- # line_token = ['0x1000:00,r-r-r-,uint32,32 bit,', 'Device type', ',0x00020192, 131474']
- token_head, name, token_tail = line_token
-
- # token_head = ['0x1000:00', 'r-r-r-', 'uint32', '32 bit', '']
- token_head = token_head.split(",")
- ful_idx, access, type, size, _empty = token_head
- # ful_idx.split(":") = ['0x1000', '00']
- idx, sub_idx = ful_idx.split(":")
-
- # token_tail = ['', '0x00020192', '131474']
- token_tail = token_tail.split(",")
- try:
- _empty, hex_val, _dec_val = token_tail
-
- # SDO data is not return "dec value"
- # line example :
- # 0x1702:01,rwr-r-,uint32,32 bit," 1st mapping", ----
- except Exception:
- _empty, hex_val = token_tail
-
- name_after_check = self.StringTest(name)
-
- # convert hex type
- sub_idx = "0x" + sub_idx
-
- if type == "octet_string":
- hex_val = ' ---- '
-
- # SResult of SlaveSDO data parsing. (data type : dictionary)
- self.Data = {'idx': idx.strip(), 'subIdx': sub_idx.strip(), 'access': access.strip(),
- 'type': type.strip(), 'size': size.strip(), 'name': name_after_check.strip("\""),
- 'value': hex_val.strip(), "category": title_name.strip("\"")}
-
- category_divide_value = [0x1000, 0x2000, 0x6000, 0xa000, 0xffff]
-
- for count in range(len(category_divide_value)):
- if int(idx, 0) < category_divide_value[count]:
- self.Controler.CommonMethod.SaveSDOData[count].append(self.Data)
- break
-
- self.Controler.CommonMethod.SaveSDOData[self.AllSDOData].append(self.Data)
-
- if count >= len(self.SDOs.splitlines()) // 2:
- (keep_going, _skip) = slaveSDO_progress.Update(count, "Please waiting a moment!!")
- else:
- (keep_going, _skip) = slaveSDO_progress.Update(count)
-
- # If user click "Cancel" loop suspend immediately
- if not keep_going:
- break
-
- slaveSDO_progress.Destroy()
- return keep_going
-
- def StringTest(self, check_string):
- """
- Test value 'name' is alphanumeric
- @param check_string : input data for check
- @return result : output data after check
- """
- # string.printable is print this result
- # '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
- # !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c
- allow_range = string.printable
- result = check_string
- for i in range(0, len(check_string)):
- # string.isalnum() is check whether string is alphanumeric or not
- if check_string[len(check_string)-1-i:len(check_string)-i] in allow_range:
- result = check_string[:len(check_string) - i]
- break
- return result
-
-
-# -------------------------------------------------------------------------------
+ self.SetSDOTraceValues(self.SDOMonitorEntries)
+ self.Controler.GetCTRoot()._connector.GetSDOData()
+ self.SDOTraceThread = Thread(target=self.SDOMonitorThreadProc)
+ self.SDOMonitoringFlag = True
+ self.SDOTraceThread.start()
+ return check_connect_flag
+
+ def SDOMonitoringThreadOff(self):
+ check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = False)
+ if check_connect_flag:
+ self.SDOMonitoringFlag = False
+ if self.SDOTraceThread is not None:
+ self.SDOTraceThread.join()
+ self.SDOTraceThread = None
+ self.Controler.GetCTRoot()._connector.StopSDOThread()
+
+ def SetSDOTraceValues(self, SDOMonitorEntries):
+ SlavePos = self.Controler.GetSlavePos()
+ check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = True)
+ if check_connect_flag:
+ self.Controler.GetCTRoot()._connector.SetSDOTraceValues(SDOMonitorEntries, SlavePos)
+
+ def SDOMonitorThreadProc(self):
+ while self.SDOMonitoringFlag and self.Controler.GetCTRoot()._connector.PLCStatus != "Started":
+ self.SDOValuesList = self.Controler.GetCTRoot()._connector.GetSDOData()
+ LocalData = self.SDOValuesList[0].items()
+ LocalData.sort()
+ if self.SDOValuesList[1] != self.Controler.GetSlavePos():
+ continue
+ row = 0
+ for (idx, subidx), data in LocalData:
+ self.SDOMonitorGrid.SetCellValue(row, 6, str(data["value"]))
+ row += 1
+ time.sleep(0.5)
+
+ def CreateSDOMonitorGrid(self):
+ """
+ It creates SDO Monitor table and specifies cell size and labels.
+ """
+ self.SDOMonitorGrid.CreateGrid(0,7)
+ SDOCellSize = [(0, 65), (1, 65), (2, 50), (3, 70),
+ (4, 40), (5, 450), (6, 85)]
+
+ for (index, size) in SDOCellSize:
+ self.SDOMonitorGrid.SetColSize(index, size)
+
+ self.SDOMonitorGrid.SetRowLabelSize(0)
+
+ SDOTableLabel = [(0, "Index"), (1, "Subindex"), (2, "Access"),
+ (3, "Type"), (4, "Size"), (5, "Name"), (6, "Value")]
+
+ for (index, label) in SDOTableLabel:
+ self.SDOMonitorGrid.SetColLabelValue(index, label)
+ self.SDOMonitorGrid.SetColLabelAlignment(index, wx.ALIGN_CENTER)
+
+ def onMonitorGridDoubleClick(self, event):
+ """
+ Event Handler for double click on the SDO entries table.
+ It adds the entry into the SDO monitor table.
+ If the entry is already in the SDO monitor table,
+ then it's removed from the SDO monitor table.
+ @pram event: gridlib.EVT_GRID_CELL_LEFT_DCLICK object
+ """
+ row = event.GetRow()
+ idx = self.SDOMonitorGrid.GetCellValue(row, 0)
+ subIdx = self.SDOMonitorGrid.GetCellValue(row, 1)
+
+ del self.SDOMonitorEntries[(idx, subIdx)]
+ self.SDOMonitorGrid.DeleteRows(row, 1)
+ # add jblee
+ self.SetSDOTraceValues(self.SDOMonitorEntries)
+ self.SDOMonitorGrid.Refresh()
+
+ def SDOParserXML(self, entries):
+ """
+ Parse SDO data set that obtain "ESI file"
+ @param entries: SDO entry list
+ """
+ entries_list = entries.items()
+ entries_list.sort()
+ self.ForDefaultValueFlag = False
+ self.CompareValue = ""
+ self.sub_entry_value_list = []
+
+ for (index, subidx), entry in entries_list:
+ # exclude entry that isn't in the objects
+ check_mapping = entry["PDOMapping"]
+ if check_mapping is "T" or check_mapping is "R":
+ if "PDO index" not in entry.keys():
+ continue
+
+ idx = "0" + entry["Index"].strip("#")
+ #subidx = hex(int(entry["SubIndex"], 0))
+ try :
+ subidx = "0x" + entry["SubIndex"]
+ except :
+ subidx = "0x0"
+ datatype = entry["Type"]
+
+ try :
+ default_value = entry["DefaultData"]
+ except :
+ default_value = " --- "
+ # Result of SlaveSDO data parsing. (data type : dictionary)
+ self.Data = {'idx':idx, 'subIdx':subidx, 'access':entry["Access"],
+ 'type':datatype, 'size': str(entry["BitSize"]),
+ 'name':entry["Name"], 'value':default_value}
+ category_divide_value = [0x1000, 0x2000, 0x6000, 0xa000, 0xffff]
+
+ for count in range(len(category_divide_value)) :
+ if int(idx, 0) < category_divide_value[count]:
+ self.Controler.CommonMethod.SDOVariables[count].append(self.Data)
+ break
+
+ self.Controler.CommonMethod.SDOSubEntryData = self.sub_entry_value_list
+
+#-------------------------------------------------------------------------------
# For SDO Notebook (divide category)
# -------------------------------------------------------------------------------
class SDONoteBook(wx.Notebook):
@@ -494,17 +586,35 @@
self.DeleteAllPages()
+ self.Controler.CommonMethod.SDOVariables[5] = []
+ for i in range(4):
+ self.Controler.CommonMethod.SDOVariables[5] += self.Controler.CommonMethod.SDOVariables[i]
+
for txt, count in page_texts:
- self.Data = self.Controler.CommonMethod.SaveSDOData[count]
- self.Win = SlaveSDOTable(self, self.Data)
+ self.Data = self.Controler.CommonMethod.SDOVariables[count]
+ self.SubEntryData = self.Controler.CommonMethod.SDOSubEntryData
+ self.Win = SlaveSDOTable(self, self.Data, self.SubEntryData)
self.AddPage(self.Win, txt)
+ # add jblee
+ def OnPageChanged(self, event):
+ old = event.GetOldSelection()
+ new = event.GetSelection()
+ sel = self.GetSelection()
+ self.parent.SDOPageNum = new
+ event.Skip()
+
+ def OnPageChanging(self, event):
+ old = event.GetOldSelection()
+ new = event.GetSelection()
+ sel = self.GetSelection()
+ event.Skip()
# -------------------------------------------------------------------------------
# For SDO Grid (fill index, subindex, etc...)
# -------------------------------------------------------------------------------
class SlaveSDOTable(wx.grid.Grid):
- def __init__(self, parent, data):
+ def __init__(self, parent, data, fixed_value):
"""
Constructor
@param parent: Reference to the parent SDOPanelClass class
@@ -518,30 +628,31 @@
self.SDOFlag = True
if data is None:
self.SDOs = []
- else:
+ self.sub_entry_value = []
+ else :
self.SDOs = data
-
- self.CreateGrid(len(self.SDOs), 8)
- SDOCellSize = [(0, 65), (1, 65), (2, 50), (3, 55),
- (4, 40), (5, 200), (6, 250), (7, 85)]
-
+ self.sub_entry_value = fixed_value
+
+ self.CreateGrid(len(self.SDOs), 7)
+ SDOCellSize = [(0, 65), (1, 65), (2, 50), (3, 70),
+ (4, 40), (5, 400), (6, 135)]
+
for (index, size) in SDOCellSize:
self.SetColSize(index, size)
self.SetRowLabelSize(0)
SDOTableLabel = [(0, "Index"), (1, "Subindex"), (2, "Access"),
- (3, "Type"), (4, "Size"), (5, "Category"),
- (6, "Name"), (7, "Value")]
+ (3, "Type"), (4, "Size"), (5, "Name"), (6, "Value")]
for (index, label) in SDOTableLabel:
self.SetColLabelValue(index, label)
- self.SetColLabelAlignment(index, wx.ALIGN_CENTRE)
+ self.SetColLabelAlignment(index, wx.ALIGN_CENTER)
attr = wx.grid.GridCellAttr()
- # for SDO download
- self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.SDOModifyDialog)
+ # for SDO download and monitoring
+ self.Bind(gridlib.EVT_GRID_CELL_LEFT_DCLICK, self.onGridDoubleClick)
for i in range(7):
self.SetColAttr(i, attr)
@@ -554,56 +665,45 @@
"""
Cell is filled by new parsing data
"""
- sdo_list = ['idx', 'subIdx', 'access', 'type', 'size', 'category', 'name', 'value']
+ sdo_list = ['idx', 'subIdx', 'access', 'type', 'size', 'name', 'value']
+ count = 0
for row_idx in range(len(self.SDOs)):
for col_idx in range(len(self.SDOs[row_idx])):
- self.SetCellValue(row_idx, col_idx, self.SDOs[row_idx][sdo_list[col_idx]])
+
+ # the top entries that have sub index is shaded.
+ if int(self.SDOs[row_idx]['subIdx'], 16) == 0x00:
+ try:
+ if int(self.SDOs[row_idx + 1]['subIdx'], 16) is not 0x00:
+ self.SetCellBackgroundColour(row_idx, col_idx, \
+ wx.LIGHT_GREY)
+ except:
+ pass
+
+ if self.SDOs[row_idx][sdo_list[col_idx]] == "modifying":
+ if len(self.sub_entry_value) == count:
+ continue
+ self.SetCellValue(row_idx, col_idx, self.sub_entry_value[count])
+ count += 1
+ else :
+ self.SetCellValue(row_idx, col_idx, \
+ self.SDOs[row_idx][sdo_list[col_idx]])
+
self.SetReadOnly(row_idx, col_idx, True)
if col_idx < 5:
self.SetCellAlignment(row_idx, col_idx, wx.ALIGN_CENTRE, wx.ALIGN_CENTRE)
def CheckSDODataAccess(self, row):
"""
- CheckSDODataAccess method is checking that access data has "w"
- Access field consist 6 char, if mean
- rw rw rw
- (preop) (safeop) (op)
- Example Access field : rwrwrw, rwrw--
+ check that access field has "rw"
@param row : Selected cell by user
@return Write_flag : If data has "w", flag is true
"""
- write_flag = False
check = self.SDOs[row]['access']
- if check[1:2] == 'w':
- self.Controler.CommonMethod.Check_PREOP = True
- write_flag = True
- if check[3:4] == 'w':
- self.Controler.CommonMethod.Check_SAFEOP = True
- write_flag = True
- if check[5:] == 'w':
- self.Controler.CommonMethod.Check_OP = True
- write_flag = True
-
- return write_flag
-
- def DecideSDODownload(self, state):
- """
- compare current state and "access" field,
- result notify SDOModifyDialog method
- @param state : current slave state
- @return True or False
- """
- # Example of 'state' parameter : "0 0:0 PREOP + EL9800 (V4.30) (PIC24, SPI, ET1100)"
- state = state[self.Controler.GetSlavePos()].split(" ")[2]
- if state == "PREOP" and self.Controler.CommonMethod.Check_PREOP:
+ if check == "rw":
return True
- elif state == "SAFEOP" and self.Controler.CommonMethod.Check_SAFEOP:
- return True
- elif state == "OP" and self.Controler.CommonMethod.Check_OP:
- return True
-
- return False
-
+ else:
+ return False
+
def ClearStateFlag(self):
"""
Initialize StateFlag
@@ -613,51 +713,112 @@
self.Controler.CommonMethod.Check_SAFEOP = False
self.Controler.CommonMethod.Check_OP = False
- def SDOModifyDialog(self, event):
+ def onGridDoubleClick (self, event):
"""
Create dialog for SDO value modify
- if user enter data, perform command "ethercat download"
- @param event : wx.grid.EVT_GRID_CELL_LEFT_DCLICK object
+ if user enter data, perform command "ethercat download"
+ @param event : gridlib.EVT_GRID_CELL_LEFT_DCLICK object
"""
self.ClearStateFlag()
-
- # CheckSDODataAccess is checking that OD(Object Dictionary) has "w"
- if event.GetCol() == 7 and self.CheckSDODataAccess(event.GetRow()):
- dlg = wx.TextEntryDialog(
- self,
- _("Enter hex or dec value (if enter dec value, it automatically conversed hex value)"),
- "SDOModifyDialog",
- style=wx.OK | wx.CANCEL)
-
- start_value = self.GetCellValue(event.GetRow(), event.GetCol())
+
+ # CheckSDODataAccess is checking that OD(Object Dictionary) has "w"
+ if event.GetCol() == 6 and self.CheckSDODataAccess(event.GetRow()) :
+ dlg = wx.TextEntryDialog (self,
+ "Enter hex or dec value (if enter dec value, " \
+ + "it automatically conversed hex value)",
+ "SDOModifyDialog", style = wx.OK | wx.CANCEL)
+
+ start_value = self.GetCellValue(event.GetRow(), event.GetCol())
dlg.SetValue(start_value)
-
+
if dlg.ShowModal() == wx.ID_OK:
- try:
- int(dlg.GetValue(), 0)
- # check "Access" field
- if self.DecideSDODownload(self.Controler.CommonMethod.SlaveState[self.Controler.GetSlavePos()]):
+ # Check whether beremiz connected or not.
+ # If this method is called cyclically, set the cyclic flag true
+ check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = False)
+ if check_connect_flag:
+ try :
+ input_val = hex(int(dlg.GetValue(), 0))
# Request "SDODownload"
- self.Controler.CommonMethod.SDODownload(
- self.SDOs[event.GetRow()]['type'],
- self.SDOs[event.GetRow()]['idx'],
- self.SDOs[event.GetRow()]['subIdx'],
- dlg.GetValue())
-
- self.SetCellValue(event.GetRow(), event.GetCol(), hex(int(dlg.GetValue(), 0)))
- else:
- self.Controler.CommonMethod.CreateErrorDialog(_('You cannot SDO download this state'))
- # Error occured process of "int(variable)"
- # User input is not hex, dec value
- except ValueError:
- self.Controler.CommonMethod.CreateErrorDialog(_('You can input only hex, dec value'))
-
-
-# -------------------------------------------------------------------------------
-# For PDO Monitoring Panel
+ return_val = self.Controler.CommonMethod.SDODownload(
+ self.SDOs[event.GetRow()]["type"],
+ self.SDOs[event.GetRow()]["idx"],
+ self.SDOs[event.GetRow()]["subIdx"],
+ dlg.GetValue())
+ if return_val is "":
+ SDOUploadEntry = {"idx" : self.SDOs[event.GetRow()]["idx"],
+ "subIdx" : self.SDOs[event.GetRow()]["subIdx"],
+ "size" : self.SDOs[event.GetRow()]["size"]
+ }
+ data = self.Controler.GetCTRoot()._connector.GetSDOEntryData(
+ SDOUploadEntry, self.Controler.GetSlavePos())
+ hex_val = hex(data)[:-1]
+
+ # download data check
+ if input_val == hex_val:
+ display_val = "%s(%d)" % (hex_val, data)
+ self.SetCellValue(event.GetRow(), event.GetCol(),
+ display_val)
+ else :
+ self.Controler.CommonMethod.CreateErrorDialog(\
+ 'SDO Value not completely download, please try again')
+ else:
+ self.Controler.GetCTRoot().logger.write_error(return_val)
+
+ # Error occured process of "int(variable)"
+ # User input is not hex, dec value
+ except ValueError:
+ self.Controler.CommonMethod.CreateErrorDialog(\
+ 'You can input only hex, dec value')
+ else:
+ SDOPanel = self.parent.parent
+ row = event.GetRow()
+
+ idx = self.SDOs[row]["idx"]
+ subIdx = self.SDOs[row]["subIdx"]
+ SDOPanel.SDOMonitorEntries[(idx, subIdx)] = {
+ "access": self.SDOs[row]["access"],
+ "type": self.SDOs[row]["type"],
+ "size": self.SDOs[row]["size"],
+ "name": self.SDOs[row]["name"],
+ # add jblee
+ "value": ""}
+
+ del_rows = SDOPanel.SDOMonitorGrid.GetNumberRows()
+
+ try:
+ SDOPanel.SDOMonitorGrid.DeleteRows(0, del_rows)
+ except:
+ pass
+
+ SDOPanel.SDOMonitorGrid.AppendRows(len(SDOPanel.SDOMonitorEntries))
+ SDOPanel.SetSDOTraceValues(SDOPanel.SDOMonitorEntries)
+
+ SME_list = SDOPanel.SDOMonitorEntries.items()
+ SME_list.sort()
+
+ gridRow = 0
+ for (idx, subIdx), entry in SME_list:
+ SDOPanel.SDOMonitorGrid.SetCellValue(gridRow, 0, str(idx))
+ SDOPanel.SDOMonitorGrid.SetCellValue(gridRow, 1, str(subIdx))
+ for col, key in [(2, "access"),
+ (3, "type"),
+ (4, "size"),
+ (5, "name")]:
+ SDOPanel.SDOMonitorGrid.SetCellValue(gridRow, col, entry[key])
+ for col in range(7):
+ SDOPanel.SDOMonitorGrid.SetReadOnly(gridRow, col, True)
+ if col < 5 :
+ SDOPanel.SDOMonitorGrid.SetCellAlignment(\
+ gridRow, col, wx.ALIGN_CENTER, wx.ALIGN_CENTER)
+ gridRow += 1
+
+ SDOPanel.SDOMonitorGrid.Refresh()
+
+#-------------------------------------------------------------------------------
+# For PDO Mapping Panel
# PDO Class UI : Panel -> Choicebook (RxPDO, TxPDO) ->
# Notebook (PDO Index) -> Grid (PDO entry)
-# -------------------------------------------------------------------------------
+#-------------------------------------------------------------------------------
class PDOPanelClass(wx.Panel):
def __init__(self, parent, controler):
"""
@@ -667,16 +828,152 @@
"""
wx.Panel.__init__(self, parent, -1)
self.Controler = controler
-
- self.PDOMonitoringEditorMainSizer = wx.BoxSizer(wx.VERTICAL)
- self.PDOMonitoringEditorInnerMainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=10)
-
- self.CallPDOChoicebook = PDOChoicebook(self, controler=self.Controler)
- self.PDOMonitoringEditorInnerMainSizer.Add(self.CallPDOChoicebook, wx.ALL)
-
- self.PDOMonitoringEditorMainSizer.Add(self.PDOMonitoringEditorInnerMainSizer)
-
- self.SetSizer(self.PDOMonitoringEditorMainSizer)
+ sizer = wx.FlexGridSizer(cols=1, hgap=20,rows=3, vgap=20)
+ line = wx.StaticText(self, -1, "\n In order to control Ethercat device, user must select proper PDO set.\
+ \n Each PDO sets describe operation modes (CSP, CSV, CST) supported by Ethercat devices.\
+ \n\n PDOs have two types, RxPDO and TxPDO.\
+ \n - RxPDO refers to the Receive Process Data Object. It means the control parameters which sent from controller to the EtherCAT Slave device.\
+ \n In general, ControlWord (0x6040), Modes of Operations (0x6060), and TargetPosition (0x607A) are regarded as RxPDO.\
+ \n - TxPDO refers to the Transmit Process Data Object. It used to report status of EtherCAT Slave device to the controller in order to calibrate their next actuation.\
+ \n StatusWord (0x6041), Modes of Operation Display (0x6061), and ActualPosition (0x607A) include in TxPDO.\
+ \n\n PDO Mapping feature provides available RxPDO and TxPDO sets which defined in Ethercat slave description XML.\
+ \n If there is no selection of PDO set, first set (0x1600, 0x1A00) will be chosen as default configuration.")
+
+ sizer.Add(line)
+ self.SetSizer(sizer)
+
+class RxPDOPanelClass(wx.Panel):
+ def __init__(self, parent, controler):
+ """
+ Constructor
+ @param parent: Reference to the parent EtherCATManagementTreebook class
+ @param controler: _EthercatSlaveCTN class in EthercatSlave.py
+ """
+ wx.Panel.__init__(self, parent, -1)
+ self.Controler = controler
+
+ # add jblee
+ #self.PDOIndexList = ["RxPDO"]
+ self.PDOIndexList = []
+ self.LoadPDOSelectData()
+
+ #HSAHN ADD. 2015.7.26 PDO Select Function ADD
+ self.Controler.CommonMethod.RequestPDOInfo()
+ self.PDOcheckBox = []
+ self.rx_pdo_entries = self.Controler.CommonMethod.GetRxPDOCategory()
+ if len(self.rx_pdo_entries):
+ for i in range(len(self.rx_pdo_entries)):
+ self.PDOcheckBox.append(wx.CheckBox(self, label=str(hex(self.rx_pdo_entries[i]['pdo_index'])), size=(120,15)))
+ if not self.Controler.SelectedRxPDOIndex and self.rx_pdo_entries[i]['sm'] is not None:
+ self.PDOcheckBox[-1].SetValue(True)
+ self.Controler.SelectedRxPDOIndex.append(int(self.PDOcheckBox[-1].GetLabel(), 0))
+ self.InitSavePDO()
+ elif self.rx_pdo_entries[i]['pdo_index'] in self.Controler.SelectedRxPDOIndex:
+ self.PDOIndexList.append(str(hex(self.rx_pdo_entries[i]['pdo_index'])))
+ self.PDOcheckBox[-1].SetValue(True)
+
+ for cb in self.PDOcheckBox:
+ self.Bind(wx.EVT_CHECKBOX, self.PDOSelectCheck, cb)
+
+ self.PDOListBox = wx.StaticBox(self, label=_("PDO Mapping Select"))
+ self.PDOListBoxSizer = wx.StaticBoxSizer(self.PDOListBox, orient=wx.HORIZONTAL)
+ self.RxPDOListBox = wx.StaticBox(self, label=_("RxPDO"))
+ self.RxPDOListBoxSizer = wx.StaticBoxSizer(self.RxPDOListBox, orient=wx.VERTICAL)
+ self.RxPDOListBoxInnerSizer = wx.FlexGridSizer(cols=3, hgap=5, rows=10, vgap=5)
+ self.RxPDOListBoxInnerSizer.AddMany(self.PDOcheckBox[0:len(self.rx_pdo_entries)])
+ self.RxPDOListBoxSizer.Add(self.RxPDOListBoxInnerSizer)
+ self.PDOListBoxSizer.Add(self.RxPDOListBoxSizer)
+ self.PDOWarningText = wx.StaticText(self, -1,
+ " *Warning*\n\n By default configuration, \n\n first mapping set is selected. \n\n Choose the PDO mapping!",
+ size=(220, -1))
+ self.PDOListBoxSizer.Add(self.PDOWarningText)
+
+ self.PDOMonitoringEditorMainSizer = wx.BoxSizer(wx.VERTICAL)
+ self.PDOMonitoringEditorInnerMainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=10)
+
+
+ self.CallPDOChoicebook = PDONoteBook(self, controler=self.Controler, name="Rx")
+ self.PDOMonitoringEditorInnerMainSizer.Add(self.CallPDOChoicebook, wx.ALL)
+
+ self.PDOInformationBox = wx.StaticBox(self, label=_("RxPDO Mapping List"))
+ self.PDOInformationBoxSizer = wx.StaticBoxSizer(self.PDOInformationBox, orient=wx.VERTICAL)
+ self.PDOInformationBoxSizer.Add(self.PDOMonitoringEditorInnerMainSizer)
+
+ self.PDOMonitoringEditorMainSizer.Add(self.PDOListBoxSizer)
+ self.PDOMonitoringEditorMainSizer.Add(self.PDOInformationBoxSizer)
+ self.SetSizer(self.PDOMonitoringEditorMainSizer)
+
+ # add jblee
+ self.PDOExcludeCheck()
+ else:
+ sizer = wx.FlexGridSizer(cols=1, hgap=20,rows=3, vgap=20)
+ line = wx.StaticText(self, -1, "\n This device does not support RxPDO.")
+
+ sizer.Add(line)
+ self.SetSizer(sizer)
+
+ def LoadPDOSelectData(self):
+ RxPDOData = self.Controler.BaseParams.getRxPDO()
+ RxPDOs = []
+ if RxPDOData != "None":
+ RxPDOs = RxPDOData.split()
+ if RxPDOs :
+ for RxPDO in RxPDOs :
+ self.Controler.SelectedRxPDOIndex.append(int(RxPDO, 0))
+
+ def PDOSelectCheck(self, event):
+ # add jblee for Save User Select
+ cb = event.GetEventObject()
+ # prevent duplicated check
+ if cb.GetValue() and int(cb.GetLabel(), 0) not in self.Controler.SelectedRxPDOIndex:
+ self.Controler.SelectedRxPDOIndex.append(int(cb.GetLabel(), 0))
+ self.PDOIndexList.append(cb.GetLabel())
+ else:
+ self.Controler.SelectedRxPDOIndex.remove(int(cb.GetLabel(), 0))
+ self.PDOIndexList.remove(cb.GetLabel())
+
+ data = ""
+ for PDOIndex in self.PDOIndexList:
+ data = data + " " + PDOIndex
+
+ self.Controler.BaseParams.setRxPDO(data)
+ self.Controler.GetCTRoot().CTNRequestSave()
+
+ self.PDOExcludeCheck()
+
+ def InitSavePDO(self):
+ for PDOIndex in self.Controler.SelectedRxPDOIndex:
+ self.PDOIndexList.append(str(hex(PDOIndex)))
+
+ data = ""
+ for PDOIndex in self.PDOIndexList:
+ data = data + " " + PDOIndex
+
+ self.Controler.BaseParams.setRxPDO(data)
+
+ # 2016.06.21
+ # add jblee for check exclude pdo list
+ def PDOExcludeCheck(self):
+ #files = os.listdir(self.Controler.CTNPath())
+ #filepath = os.path.join(self.Controler.CTNPath(), "DataForPDO.txt")
+ CurIndexs = self.Controler.SelectedRxPDOIndex
+ for CB in self.PDOcheckBox:
+ if len(CB.GetLabel().split()) > 1:
+ CB.Enable()
+ CB.SetLabel(CB.GetLabel().split()[0])
+
+ for pdo in self.rx_pdo_entries:
+ for CurIndex in CurIndexs:
+ if pdo["pdo_index"] == CurIndex:
+ ex_list = pdo["exclude_list"]
+ for ex_item in ex_list:
+ for CB in self.PDOcheckBox:
+ if CB.GetLabel() == hex(ex_item):
+ CB.SetLabel(CB.GetLabel() + " (Excluded)")
+ CB.Disable()
+
+ def RefreshPDOInfo(self):
+ pass
def PDOInfoUpdate(self):
"""
@@ -684,28 +981,142 @@
"""
self.Controler.CommonMethod.RequestPDOInfo()
self.CallPDOChoicebook.Destroy()
- self.CallPDOChoicebook = PDOChoicebook(self, controler=self.Controler)
+ self.CallPDOChoicebook = PDOChoicebook(self, controler=self.Controler, name="Rx")
self.Refresh()
-
-# -------------------------------------------------------------------------------
-# For PDO Choicebook (divide Tx, Rx PDO)
-# -------------------------------------------------------------------------------
-class PDOChoicebook(wx.Choicebook):
+class TxPDOPanelClass(wx.Panel):
def __init__(self, parent, controler):
"""
Constructor
- @param parent: Reference to the parent PDOPanelClass class
+ @param parent: Reference to the parent EtherCATManagementTreebook class
@param controler: _EthercatSlaveCTN class in EthercatSlave.py
"""
- wx.Choicebook.__init__(self, parent, id=-1, size=(500, 500), style=wx.CHB_DEFAULT)
+ wx.Panel.__init__(self, parent, -1)
self.Controler = controler
- RxWin = PDONoteBook(self, controler=self.Controler, name="Rx")
- TxWin = PDONoteBook(self, controler=self.Controler, name="Tx")
- self.AddPage(RxWin, "RxPDO")
- self.AddPage(TxWin, "TxPDO")
-
+ # add jblee
+ self.PDOIndexList = []
+ self.LoadPDOSelectData()
+
+ #HSAHN ADD. 2015.7.26 PDO Select Function ADD
+ self.Controler.CommonMethod.RequestPDOInfo()
+ self.PDOcheckBox = []
+ self.tx_pdo_entries = self.Controler.CommonMethod.GetTxPDOCategory()
+ if len(self.tx_pdo_entries):
+ for i in range(len(self.tx_pdo_entries)):
+ self.PDOcheckBox.append(wx.CheckBox(self, label=str(hex(self.tx_pdo_entries[i]['pdo_index'])), size=(120,15)))
+ if not self.Controler.SelectedTxPDOIndex and self.tx_pdo_entries[i]['sm'] is not None:
+ self.PDOcheckBox[-1].SetValue(True)
+ self.Controler.SelectedTxPDOIndex.append(int(self.PDOcheckBox[-1].GetLabel(), 0))
+ self.InitSavePDO()
+ elif self.tx_pdo_entries[i]['pdo_index'] in self.Controler.SelectedTxPDOIndex:
+ self.PDOIndexList.append(str(hex(self.tx_pdo_entries[i]['pdo_index'])))
+ self.PDOcheckBox[-1].SetValue(True)
+ for cb in self.PDOcheckBox:
+ self.Bind(wx.EVT_CHECKBOX, self.PDOSelectCheck, cb)
+
+ self.PDOListBox = wx.StaticBox(self, label=_("PDO Mapping Select"))
+ self.PDOListBoxSizer = wx.StaticBoxSizer(self.PDOListBox, orient=wx.HORIZONTAL)
+ self.TxPDOListBox = wx.StaticBox(self, label=_("TxPDO"))
+ self.TxPDOListBoxSizer = wx.StaticBoxSizer(self.TxPDOListBox, orient=wx.VERTICAL)
+ self.TxPDOListBoxInnerSizer = wx.FlexGridSizer(cols=3, hgap=5, rows=10, vgap=5)
+ self.TxPDOListBoxInnerSizer.AddMany(self.PDOcheckBox[0:len(self.tx_pdo_entries)])
+ self.TxPDOListBoxSizer.Add(self.TxPDOListBoxInnerSizer)
+ self.PDOListBoxSizer.Add(self.TxPDOListBoxSizer)
+ self.PDOWarningText = wx.StaticText(self, -1,
+ " *Warning*\n\n By default configuration, \n\n first mapping set is selected. \n\n Choose the PDO mapping!",
+ size=(220, -1))
+ self.PDOListBoxSizer.Add(self.PDOWarningText)
+
+ self.PDOMonitoringEditorMainSizer = wx.BoxSizer(wx.VERTICAL)
+ self.PDOMonitoringEditorInnerMainSizer = wx.FlexGridSizer(cols=1, hgap=10, rows=2, vgap=10)
+
+ self.CallPDOChoicebook = PDONoteBook(self, controler=self.Controler, name="Tx")
+ self.PDOMonitoringEditorInnerMainSizer.Add(self.CallPDOChoicebook, wx.ALL)
+
+ self.PDOInformationBox = wx.StaticBox(self, label=_("TxPDO Mapping List"))
+ self.PDOInformationBoxSizer = wx.StaticBoxSizer(self.PDOInformationBox, orient=wx.VERTICAL)
+ self.PDOInformationBoxSizer.Add(self.PDOMonitoringEditorInnerMainSizer)
+
+ self.PDOMonitoringEditorMainSizer.Add(self.PDOListBoxSizer)
+ self.PDOMonitoringEditorMainSizer.Add(self.PDOInformationBoxSizer)
+ self.SetSizer(self.PDOMonitoringEditorMainSizer)
+
+ # add jblee
+ self.PDOExcludeCheck()
+ else:
+ sizer = wx.FlexGridSizer(cols=1, hgap=20,rows=3, vgap=20)
+ line = wx.StaticText(self, -1, "\n This device does not support TxPDO.")
+
+ sizer.Add(line)
+ self.SetSizer(sizer)
+
+ def LoadPDOSelectData(self):
+ TxPDOData = self.Controler.BaseParams.getTxPDO()
+ TxPDOs = []
+ if TxPDOData != "None":
+ TxPDOs = TxPDOData.split()
+ if TxPDOs :
+ for TxPDO in TxPDOs :
+ self.Controler.SelectedTxPDOIndex.append(int(TxPDO, 0))
+
+ def PDOSelectCheck(self, event):
+ # add jblee for Save User Select
+ cb = event.GetEventObject()
+ # prevent duplicated check
+ if cb.GetValue() and int(cb.GetLabel(), 0) not in self.Controler.SelectedTxPDOIndex:
+ self.Controler.SelectedTxPDOIndex.append(int(cb.GetLabel(), 0))
+ self.PDOIndexList.append(cb.GetLabel())
+ else:
+ self.Controler.SelectedTxPDOIndex.remove(int(cb.GetLabel(), 0))
+ self.PDOIndexList.remove(cb.GetLabel())
+
+ data = ""
+ for PDOIndex in self.PDOIndexList:
+ data = data + " " + PDOIndex
+
+ self.Controler.BaseParams.setTxPDO(data)
+ self.Controler.GetCTRoot().CTNRequestSave()
+
+ self.PDOExcludeCheck()
+
+ def InitSavePDO(self):
+ for PDOIndex in self.Controler.SelectedTxPDOIndex:
+ self.PDOIndexList.append(str(hex(PDOIndex)))
+
+ data = ""
+ for PDOIndex in self.PDOIndexList:
+ data = data + " " + PDOIndex
+
+ self.Controler.BaseParams.setTxPDO(data)
+
+ # 2016.06.21
+ # add jblee for check exclude pdo list
+ def PDOExcludeCheck(self):
+ CurIndexs = self.Controler.SelectedTxPDOIndex
+ for CB in self.PDOcheckBox:
+ if len(CB.GetLabel().split()) > 1:
+ CB.Enable()
+ CB.SetLabel(CB.GetLabel().split()[0])
+
+ for pdo in self.tx_pdo_entries:
+ for CurIndex in CurIndexs:
+ if pdo["pdo_index"] == CurIndex:
+ ex_list = pdo["exclude_list"]
+ for ex_item in ex_list:
+ for CB in self.PDOcheckBox:
+ if CB.GetLabel() == hex(ex_item):
+ CB.SetLabel(CB.GetLabel() + " (Excluded)")
+ CB.Disable()
+
+ def PDOInfoUpdate(self):
+ """
+ Call RequestPDOInfo method and create Choicebook
+ """
+ self.Controler.CommonMethod.RequestPDOInfo()
+ self.CallPDOChoicebook.Destroy()
+ self.CallPDOChoicebook = PDOChoicebook(self, controler=self.Controler, name="Tx")
+ self.Refresh()
# -------------------------------------------------------------------------------
# For PDO Notebook (divide PDO index)
@@ -718,14 +1129,12 @@
@param name: identifier whether RxPDO or TxPDO
@param controler: _EthercatSlaveCTN class in EthercatSlave.py
"""
- wx.Notebook.__init__(self, parent, id=-1, size=(640, 400))
+ wx.Notebook.__init__(self, parent, id=-1, size=(600, 400))
self.Controler = controler
count = 0
page_texts = []
- self.Controler.CommonMethod.RequestPDOInfo()
-
if name == "Tx":
# obtain pdo_info and pdo_entry
# pdo_info include (PDO index, name, number of entry)
@@ -820,12 +1229,10 @@
self.SetRowSize(row_idx, 25)
start_value += 1
-
-# -------------------------------------------------------------------------------
-# For EEPROM Access Main Panel
-# (This class explain EEPROM Access)
-# -------------------------------------------------------------------------------
-class EEPROMAccessPanel(wx.Panel):
+#-------------------------------------------------------------------------------
+# For MDP Main Panel
+#-------------------------------------------------------------------------------
+class MDPPanel(wx.Panel):
def __init__(self, parent, controler):
"""
Constructor
@@ -833,6 +1240,185 @@
@param controler: _EthercatSlaveCTN class in EthercatSlave.py
"""
wx.Panel.__init__(self, parent, -1)
+ self.parent = parent
+ self.Controler = controler
+
+ sizer = wx.FlexGridSizer(cols=3, hgap=20, rows=1, vgap=20)
+
+ # Include Module ListBox
+ leftInnerSizer = wx.FlexGridSizer(cols=1, hgap=10,rows=2, vgap=10)
+ # Include Add, Delete Button
+ middleInnerSizer = wx.FlexGridSizer(cols=1, hgap=10,rows=2, vgap=10)
+ # Include Slot ListBox
+ rightInnerSizer = wx.FlexGridSizer(cols=1, hgap=10,rows=2, vgap=10)
+
+ # Get Module Name as Array
+ # MDPArray = {SlaveName, [data0, data1, ...], SlotIndexIncrement, SlotPdoIncrement}
+ # data = [ModuleName, ModuleInfo, [PDOInfo1, PDOInfo2, ...]]
+ # PDOInfo = {Index, Name, BitSize, Access, PDOMapping, SubIndex, Type}
+ slave = self.Controler.CTNParent.GetSlave(self.Controler.GetSlavePos())
+ type_infos = slave.getType()
+ MDPArray = self.Controler.CTNParent.CTNParent.GetMDPInfos(type_infos)
+
+ NameSet = []
+ if MDPArray:
+ for info in MDPArray[0][1]:
+ NameSet.append(info[0])
+
+ # Module ListBox
+ self.ModuleLabel = wx.StaticText(self, -1, "Module")
+ self.ModuleListBox = wx.ListBox(self, size = (150, 200), choices = NameSet)
+ #self.ModuleListBox = wx.ListBox(self, size = (150, 200), choices = [])
+ self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnModuleListBoxDCClick, self.ModuleListBox)
+
+ # Button
+ self.AddButton = wx.Button(self, label=_(" Add Module "))
+ self.DeleteButton = wx.Button(self, label=_("Delete Module"))
+
+ # Button Event Mapping
+ self.AddButton.Bind(wx.EVT_BUTTON, self.OnAddButton)
+ self.DeleteButton.Bind(wx.EVT_BUTTON, self.OnDeleteButton)
+
+ # Slot ListBox
+ self.SlotLabel = wx.StaticText(self, -1, "Slot")
+ self.SlotListBox = wx.ListBox(self, size = (150, 200))
+ self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnSlotListBoxDCClick, self.SlotListBox)
+ self.SelectModule = []
+
+ # Add Object Each Sizer
+ leftInnerSizer.AddMany([self.ModuleLabel, self.ModuleListBox])
+ middleInnerSizer.Add(self.AddButton, 0, wx.TOP, 100)
+ middleInnerSizer.Add(self.DeleteButton, 0, wx.BOTTOM, 120)
+ rightInnerSizer.AddMany([self.SlotLabel, self.SlotListBox])
+
+ sizer.AddMany([leftInnerSizer, middleInnerSizer, rightInnerSizer])
+
+ self.SetSizer(sizer)
+
+ self.InitMDPSet()
+
+ def InitMDPSet(self):
+ files = os.listdir(self.Controler.CTNPath())
+ filepath = os.path.join(self.Controler.CTNPath(), "DataForMDP.txt")
+ try:
+ moduleDataFile = open(filepath, 'r')
+ lines = moduleDataFile.readlines()
+
+ for line in lines:
+ if line == "\n":
+ continue
+ module_pos = line.split()[-1]
+ name_len_limit = len(line) - len(module_pos) - 2
+ module_name = line[0:name_len_limit]
+
+ self.SelectModule.append((module_name, int(module_pos)))
+
+ localModuleInfo = []
+ count = 1
+ for (item, pos) in self.SelectModule:
+ slotString = "Slot %d %s : " % (count, item.split()[1]) + item.split()[0]
+ localModuleInfo.append(slotString)
+ count += 1
+ self.SlotListBox.SetItems(localModuleInfo)
+
+ except:
+ moduleDataFile = open(filepath, 'w')
+
+ moduleDataFile.close()
+
+ def OnAddButton(self, event):
+ files = os.listdir(self.Controler.CTNPath())
+ filepath = os.path.join(self.Controler.CTNPath(), "DataForMDP.txt")
+ moduleDataFile = open(filepath, 'w')
+
+ selectNum = self.ModuleListBox.GetSelection()
+ if selectNum >= 0:
+ selectStr = self.ModuleListBox.GetString(selectNum)
+ self.SelectModule.append((selectStr, selectNum))
+ localModuleInfo = []
+ count = 1
+ for (item, pos) in self.SelectModule:
+ slotString = "Slot %d %s : " % (count, item.split()[1]) + item.split()[0]
+ localModuleInfo.append(slotString)
+ count += 1
+ self.SlotListBox.SetItems(localModuleInfo)
+
+ moduleDataFile.close()
+
+ def OnDeleteButton(self, event):
+ files = os.listdir(self.Controler.CTNPath())
+ filepath = os.path.join(self.Controler.CTNPath(), "DataForMDP.txt")
+ moduleDataFile = open(filepath, 'w')
+
+ selectNum = self.SlotListBox.GetSelection()
+ if selectNum >= 0:
+ selectStr = self.SlotListBox.GetString(selectNum)
+ self.SelectModule.pop(selectNum)
+ localModuleInfo = []
+ count = 1
+ for (item, pos) in self.SelectModule:
+ moduleDataFile.write(item + " " + str(pos) + "\n")
+ slotString = "Slot %d %s : " % (count, item.split()[1]) + item.split()[0]
+ localModuleInfo.append(slotString)
+ count += 1
+ self.SlotListBox.SetItems(localModuleInfo)
+
+ moduleDataFile.close()
+
+ def OnModuleListBoxDCClick(self, event):
+ files = os.listdir(self.Controler.CTNPath())
+ filepath = os.path.join(self.Controler.CTNPath(), "DataForMDP.txt")
+ moduleDataFile = open(filepath, 'w')
+
+ selectNum = self.ModuleListBox.GetSelection()
+ if selectNum >= 0:
+ selectStr = self.ModuleListBox.GetString(selectNum)
+ self.SelectModule.append((selectStr, selectNum))
+ localModuleInfo = []
+ count = 1
+ for (item, pos) in self.SelectModule:
+ moduleDataFile.write(item + " " + str(pos) + "\n")
+ slotString = "Slot %d %s : " % (count, item.split()[1]) + item.split()[0]
+ localModuleInfo.append(slotString)
+ module = self.Controler.CTNParent.CTNParent.GetSelectModule(pos)
+ self.Controler.CommonMethod.SavePDOData(module)
+ count += 1
+ self.SlotListBox.SetItems(localModuleInfo)
+
+ moduleDataFile.close()
+
+ def OnSlotListBoxDCClick(self, event):
+ files = os.listdir(self.Controler.CTNPath())
+ filepath = os.path.join(self.Controler.CTNPath(), "DataForMDP.txt")
+ moduleDataFile = open(filepath, 'w')
+
+ selectNum = self.SlotListBox.GetSelection()
+ if selectNum >= 0:
+ selectStr = self.SlotListBox.GetString(selectNum)
+ self.SelectModule.pop(selectNum)
+ localModuleInfo = []
+ count = 1
+ for (item, pos) in self.SelectModule:
+ moduleDataFile.write(item + " " + str(pos) + "\n")
+ slotString = "Slot %d %s : " % (count, item.split()[1]) + item.split()[0]
+ localModuleInfo.append(slotString)
+ count += 1
+ self.SlotListBox.SetItems(localModuleInfo)
+
+ moduleDataFile.close()
+
+# -------------------------------------------------------------------------------
+# For EEPROM Access Main Panel
+# (This class explain EEPROM Access)
+# -------------------------------------------------------------------------------
+class EEPROMAccessPanel(wx.Panel):
+ def __init__(self, parent, controler):
+ """
+ Constructor
+ @param parent: Reference to the parent EtherCATManagementTreebook class
+ @param controler: _EthercatSlaveCTN class in EthercatSlave.py
+ """
+ wx.Panel.__init__(self, parent, -1)
sizer = wx.FlexGridSizer(cols=1, hgap=20, rows=3, vgap=20)
line = wx.StaticText(self, -1, "\n EEPROM Access is composed to SmartView and HexView. \
@@ -858,22 +1444,24 @@
self.parent = parent
self.Controler = controler
- self.PDIType = {
- 0: ['none', '00000000'],
- 4: ['Digital I/O', '00000100'],
- 5: ['SPI Slave', '00000101'],
- 7: ['EtherCAT Bridge (port3)', '00000111'],
- 8: ['uC async. 16bit', '00001000'],
- 9: ['uC async. 8bit', '00001001'],
- 10: ['uC sync. 16bit', '00001010'],
- 11: ['uC sync. 8bit', '00001011'],
- 16: ['32 Digtal Input and 0 Digital Output', '00010000'],
- 17: ['24 Digtal Input and 8 Digital Output', '00010001'],
- 18: ['16 Digtal Input and 16 Digital Output', '00010010'],
- 19: ['8 Digtal Input and 24 Digital Output', '00010011'],
- 20: ['0 Digtal Input and 32 Digital Output', '00010100'],
- 128: ['On-chip bus', '11111111']
- }
+ # PDI Type 1, 13 unknown Type, Fix Future
+ self.PDIType = {0 :['none', '00000000'],
+ 1 :['unknown', '00000000'],
+ 4 :['Digital I/O', '00000100'],
+ 5 :['SPI Slave', '00000101'],
+ 7 :['EtherCAT Bridge (port3)', '00000111'],
+ 8 :['uC async. 16bit', '00001000'],
+ 9 :['uC async. 8bit', '00001001'],
+ 10 :['uC sync. 16bit', '00001010'],
+ 11 :['uC sync. 8bit', '00001011'],
+ 13 :['unknown', '00000000'],
+ 16 :['32 Digtal Input and 0 Digital Output', '00010000'],
+ 17 :['24 Digtal Input and 8 Digital Output', '00010001'],
+ 18 :['16 Digtal Input and 16 Digital Output','00010010'],
+ 19 :['8 Digtal Input and 24 Digital Output', '00010011'],
+ 20 :['0 Digtal Input and 32 Digital Output', '00010100'],
+ 128:['On-chip bus', '11111111']
+ }
sizer = wx.FlexGridSizer(cols=1, hgap=5, rows=2, vgap=5)
button_sizer = wx.FlexGridSizer(cols=2, hgap=5, rows=1, vgap=5)
@@ -935,7 +1523,8 @@
@param event : wx.EVT_BUTTON object
"""
# Check whether beremiz connected or not.
- check_connect_flag = self.Controler.CommonMethod.CheckConnect(False)
+ # If this method is called cyclically, set the cyclic flag true
+ check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = False)
if check_connect_flag:
self.SiiBinary = self.Controler.CommonMethod.LoadData()
self.SetEEPROMData()
@@ -1257,7 +1846,8 @@
@param event : wx.EVT_BUTTON object
"""
# Check whether beremiz connected or not.
- check_connect_flag = self.Controler.CommonMethod.CheckConnect(False)
+ # If this method is called cyclically, set the cyclic flag true
+ check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = False)
if check_connect_flag:
# load from EEPROM data and parsing
self.SiiBinary = self.Controler.CommonMethod.LoadData()
@@ -1274,7 +1864,8 @@
"""
# Check whether beremiz connected or not,
# and whether status is "Started" or not.
- check_connect_flag = self.Controler.CommonMethod.CheckConnect(False)
+ # If this method is called cyclically, set the cyclic flag true
+ check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = False)
if check_connect_flag:
status, _log_count = self.Controler.GetCTRoot()._connector.GetPLCstatus()
if status is not PlcStatus.Started:
@@ -1439,7 +2030,7 @@
# flag for compact view
self.CompactFlag = False
- # main grid의 rows and cols
+ # main grid rows and cols
self.MainRow = [512, 512, 512, 512]
self.MainCol = 4
@@ -1675,7 +2266,8 @@
@param event: wx.EVT_BUTTON object
"""
# Check whether beremiz connected or not.
- check_connect_flag = self.Controler.CommonMethod.CheckConnect(False)
+ # If this method is called cyclically, set the cyclic flag true
+ check_connect_flag = self.Controler.CommonMethod.CheckConnect(cyclic_flag = False)
if check_connect_flag:
self.LoadData()
self.BasicSetData()
@@ -2059,8 +2651,7 @@
@param parent: wx.ScrollWindow object
@Param controler: _EthercatSlaveCTN class in EthercatSlave.py
"""
- wx.Panel.__init__(self, parent, -1, (0, 0),
- size=wx.DefaultSize, style=wx.SUNKEN_BORDER)
+ wx.Panel.__init__(self, parent)
self.Controler = controler
self.parent = parent
self.StaticBox = {}
@@ -2070,21 +2661,25 @@
# ----------------------- Main Sizer and Update Button --------------------------------------------
self.MasterStateSizer = {"main": wx.BoxSizer(wx.VERTICAL)}
for key, attr in [
- ("innerMain", [1, 10, 2, 10]),
- ("innerTopHalf", [2, 10, 1, 10]),
- ("innerBottomHalf", [2, 10, 1, 10]),
- ("innerMasterState", [2, 10, 3, 10]),
- ("innerDeviceInfo", [4, 10, 3, 10]),
- ("innerFrameInfo", [4, 10, 5, 10])]:
+ ("innerTop", [2, 10, 1, 10]),
+ ("innerMiddle", [1, 10, 1, 10]),
+ ("innerBottom", [1, 10, 1, 10]),
+ ("innerMasterState", [2, 10, 3, 10]),
+ ("innerDeviceInfo", [4, 10, 3, 10]),
+ ("innerFrameInfo", [4, 10, 5, 10]),
+ ("innerSlaveInfo", [1, 10, 2, 10])]:
self.MasterStateSizer[key] = wx.FlexGridSizer(cols=attr[0], hgap=attr[1], rows=attr[2], vgap=attr[3])
- self.UpdateButton = wx.Button(self, label=_('Update'))
- self.UpdateButton.Bind(wx.EVT_BUTTON, self.OnButtonClick)
-
- for key, label in [
- ('masterState', 'EtherCAT Master State'),
- ('deviceInfo', 'Ethernet Network Card Information'),
- ('frameInfo', 'Network Frame Information')]:
+ self.MSUpdateButton = wx.Button(self, label=_("Update"))
+ self.MSUpdateButton.Bind(wx.EVT_BUTTON, self.OnMSUpdateButtonClick)
+ self.SIUpdateButton = wx.Button(self, label=_("Update"))
+ self.SIUpdateButton.Bind(wx.EVT_BUTTON, self.OnSIUpdateButtonClick)
+
+ for key, label in [
+ ("masterState", "EtherCAT Master State"),
+ ("deviceInfo", "Ethernet Network Card Information"),
+ ("frameInfo", "Network Frame Information"),
+ ("slaveInfo", "Slave Information")]:
self.StaticBox[key] = wx.StaticBox(self, label=_(label))
self.MasterStateSizer[key] = wx.StaticBoxSizer(self.StaticBox[key])
@@ -2114,10 +2709,12 @@
# ----------------------- Network Frame Information -----------------------------------------------
for key, label in [
- ('Tx frame rate [1/s]', 'Tx Frame Rate [1/s]:'),
- ('Rx frame rate [1/s]', 'Tx Rate [kByte/s]:'),
- ('Loss rate [1/s]', 'Loss Rate [1/s]:'),
- ('Frame loss [%]', 'Frame Loss [%]:')]:
+ ("Tx frame rate [1/s]", "Tx Frame Rate [1/s]:"),
+ ("Tx rate [KByte/s]", "Tx Rate [KByte/s]:"),
+ ("Rx frame rate [1/s]", "Rx Frame Rate [1/s]:"),
+ ("Rx rate [KByte/s]", "Rx Rate [KByte/s]:"),
+ ("Loss rate [1/s]", "Loss Rate [1/s]:"),
+ ("Frame loss [%]", "Frame Loss [%]:")]:
self.StaticText[key] = wx.StaticText(self, label=_(label))
self.MasterStateSizer['innerFrameInfo'].Add(self.StaticText[key])
self.TextCtrl[key] = {}
@@ -2127,21 +2724,30 @@
self.MasterStateSizer['frameInfo'].AddSizer(self.MasterStateSizer['innerFrameInfo'])
+ # ------------------------------- Slave Information -----------------------------------------------
+ self.SITreeListCtrl = SITreeListCtrl(self, self.Controler)
+ self.MasterStateSizer["innerSlaveInfo"].AddMany([self.SIUpdateButton,
+ self.SITreeListCtrl])
+ self.MasterStateSizer["slaveInfo"].AddSizer(
+ self.MasterStateSizer["innerSlaveInfo"])
+
# --------------------------------- Main Sizer ----------------------------------------------------
+ self.MasterStateSizer["main"].Add(self.MSUpdateButton)
for key, sub, in [
- ('innerTopHalf', ['masterState', 'deviceInfo']),
- ('innerBottomHalf', ['frameInfo']),
- ('innerMain', ['innerTopHalf', 'innerBottomHalf'])
- ]:
+ ("innerTop", [
+ "masterState", "deviceInfo"]),
+ ("innerMiddle", [
+ "frameInfo"]),
+ ("innerBottom", [
+ "slaveInfo"]),
+ ("main", [
+ "innerTop", "innerMiddle", "innerBottom"])]:
for key2 in sub:
self.MasterStateSizer[key].AddSizer(self.MasterStateSizer[key2])
- self.MasterStateSizer['main'].AddSizer(self.UpdateButton)
- self.MasterStateSizer['main'].AddSizer(self.MasterStateSizer['innerMain'])
-
- self.SetSizer(self.MasterStateSizer['main'])
-
- def OnButtonClick(self, event):
+ self.SetSizer(self.MasterStateSizer["main"])
+
+ def OnMSUpdateButtonClick(self, event):
"""
Handle the event of the 'Update' button.
Update the data of the master state.
@@ -2159,3 +2765,818 @@
self.TextCtrl[key].SetValue(self.MasterState[key][0])
else:
self.Controler.CommonMethod.CreateErrorDialog(_('PLC not connected!'))
+
+ def OnSIUpdateButtonClick(self, event):
+ """
+ Handle the event of the radio box in the slave information
+ @param event: wx.EVT_RADIOBOX object
+ """
+ if self.Controler.GetCTRoot()._connector is not None:
+ self.SITreeListCtrl.UpdateSI()
+
+ else :
+ self.Controler.CommonMethod.CreateErrorDialog('PLC not connected!')
+
+
+#-------------------------------------------------------------------------------
+# For Slave Information Panel
+#-------------------------------------------------------------------------------
+class SITreeListCtrl(wx.Panel):
+
+ EC_Addrs = ["0x0300", "0x0302", "0x0304", "0x0306", "0x0301", "0x0303", "0x0305",
+ "0x0307", "0x0308", "0x0309", "0x030A", "0x030B", "0x030C", "0x030D",
+ "0x0310", "0x0311", "0x0312", "0x0313", "0x0442", "0x0443"]
+
+ def __init__(self, parent, controler):
+ """
+ Constructor
+ @param parent: Reference to the MasterStatePanel class
+ @param Controler: _EthercatCTN class in EthercatMaster.py
+ """
+
+ wx.Panel.__init__(self, parent, -1, size=wx.Size(750, 350))
+
+ self.Controler=controler
+
+ self.Tree = wx.gizmos.TreeListCtrl(self, -1, size=wx.Size(750,350),
+ style=wx.TR_HAS_BUTTONS
+ |wx.TR_HIDE_ROOT
+ |wx.TR_ROW_LINES
+ |wx.TR_COLUMN_LINES
+ |wx.TR_FULL_ROW_HIGHLIGHT)
+ for label, width in [
+ ("name", 400),
+ ("position", 100),
+ ("state", 100),
+ ("error", 100)]:
+ self.Tree.AddColumn(label, width=width)
+
+ self.Tree.SetMainColumn(0)
+
+ def UpdateSI(self):
+ """
+ Update the data of the slave information.
+ """
+ position, not_used, state, not_used, name = range(5)
+
+ slave_node = []
+ slave_info_list = []
+ error_counter= []
+
+ # get slave informations (name, position, state)
+ slaves_infos = self.Controler.CommonMethod.GetSlaveStateFromSlave()
+ slave_info_lines = slaves_infos.splitlines()
+
+ for line in slave_info_lines:
+ slave_info_list.append(line.split(None,4))
+
+ slave_num = len(slave_info_lines)
+
+ reg_info = []
+ for ec in self.EC_Addrs:
+ reg_info.append(ec + ",0x001")
+
+ # get error counts of slaves
+ err_count_list = self.Controler.CommonMethod.MultiRegRead(slave_num, reg_info)
+
+ self.Tree.DeleteAllItems()
+
+ root = self.Tree.AddRoot("")
+ ec_list_idx = 0
+
+ for slave_idx in range(slave_num):
+ slave_node = self.Tree.AppendItem(root, "")
+
+ # set name, postion, state
+ col_num = 0
+ for info_idx in [name, position, state]:
+ self.Tree.SetItemText(slave_node,
+ slave_info_list[slave_idx][info_idx], col_num)
+ col_num += 1
+
+ error_counter = {}
+ ec_idx = 0
+
+ # set error counter's name and default value
+ for ec, sub_ecs in [("Port Error Counters 0/1/2/3",[
+ "Invaild Frame Counter 0/1/2/3",
+ "RX Error Counter 0/1/2/3"]),
+ ("Forward RX Error Counter 0/1/2/3", []),
+ ("ECAT Processing Unit Error Counter", []),
+ ("PDI Error Counter", []),
+ ("Lost Link Counter 0/1/2/3", []),
+ ("Watchdog Counter Process Data", []),
+ ("Watchdog Counter PDI", [])]:
+ ec_sub_idx = 0
+ ec_name = ec
+ tree_node = self.Tree.AppendItem(slave_node, "%s" % ec)
+
+ if ec_name.find("0/1/2/3") > 0:
+ num_ports = 4
+ err_count = [0, 0, 0, 0]
+ else:
+ num_ports = 1
+ err_count = [0]
+
+ error_counter[(ec_idx, ec_sub_idx)] = {
+ "name": ec_name,
+ "tree_node": tree_node,
+ "num_ports": num_ports,
+ "err_count": err_count}
+
+ for sub_ec in sub_ecs:
+ ec_sub_idx += 1
+ ec_name = sub_ec
+ tree_node = self.Tree.AppendItem(\
+ error_counter[(ec_idx, 0)]["tree_node"],
+ "%s" % sub_ec)
+
+ if ec_name.find("0/1/2/3") > 0:
+ num_ports = 4
+ err_count = [0, 0, 0, 0]
+ else:
+ num_ports = 1
+ err_count = [0]
+
+ error_counter[(ec_idx, ec_sub_idx)] = {
+ "name": ec_name,
+ "tree_node": tree_node,
+ "num_ports": num_ports,
+ "err_count": err_count}
+
+ for port_num in range(num_ports):
+ try:
+ error_counter[(ec_idx, ec_sub_idx)]["err_count"][port_num] += \
+ int(err_count_list[ec_list_idx].split(",")[2], 16)
+ except:
+ error_counter[(ec_idx, ec_sub_idx)]["err_count"][port_num] = -1
+
+ ec_list_idx += 1
+
+ if ec_sub_idx > 0:
+ for port_num in range(num_ports):
+ err_sum = 0
+ for sub_idx in range(1, ec_sub_idx+1):
+ err_sum += error_counter[(ec_idx, sub_idx)]\
+ ["err_count"][port_num]
+ error_counter[(ec_idx, 0)]["err_count"][port_num] = err_sum
+
+ else:
+ for port_num in range(num_ports):
+ try:
+ error_counter[(ec_idx, ec_sub_idx)]["err_count"][port_num] += \
+ int(err_count_list[ec_list_idx].split(",")[2], 16)
+ except:
+ error_counter[(ec_idx, ec_sub_idx)]["err_count"][port_num] = -1
+ ec_list_idx += 1
+
+ ec_idx += 1
+
+ # set texts in "error" column.
+ ec_info_list = error_counter.items()
+ ec_info_list.sort()
+
+ err_checker = "none"
+
+ for (idx, sub_idx), ec_info in ec_info_list:
+ ec_text = ""
+ for port_num in range(ec_info["num_ports"]):
+ if ec_info["err_count"][port_num] != 0:
+ err_checker = "occurred"
+
+ if ec_info["err_count"][port_num] < 0:
+ ec_text = "reg I/O error"
+ else:
+ ec_text = ec_text + "%d/" % ec_info["err_count"][port_num]
+
+ ec_text = ec_text.strip("/")
+
+ self.Tree.SetItemText(ec_info["tree_node"], ec_text, col_num)
+
+ self.Tree.SetItemText(slave_node, err_checker, col_num)
+
+class DCConfigPanel(wx.Panel):
+ def __init__(self, parent, controler):
+ """
+ Constructor
+ @param parent: Reference to the MasterStatePanel class
+ @param Controler: _EthercatCTN class in EthercatMaster.py
+ """
+
+ wx.Panel.__init__(self, parent, -1, size=wx.Size(750, 350))
+
+ self.Controler = controler
+ self.parent = parent
+
+ self.ESI_DC_Data = self.Controler.CommonMethod.LoadESIData()
+
+ # initialize SlaveStatePanel UI dictionaries
+ self.StaticBoxDic = {}
+ self.StaticTextDic = {}
+ self.TextCtrlDic = {}
+ self.ComboBoxDic = {}
+ self.CheckBoxDic = {}
+ self.RadioButtonDic = {}
+ OperationModeComboList = []
+ Sync1CycleComboList = []
+
+ for ESI_Data in self.ESI_DC_Data:
+ OperationModeComboList.append(ESI_Data["desc"])
+
+ UnitComboList = [ "/100", "/ 50", "/ 40", "/ 30", "/ 25", "/ 20", "/16",
+ "/ 10", "/ 8", "/ 5", "/ 4", "/ 3", "/ 2", "x 1", "x 2", "x 3", "x 4",
+ "x 5", "x 8", "x 10", "x 16", "x 20", "x 25", "x 30", "x 40", "x 50",
+ "x 100"
+ ]
+
+ UnitComboListPlus = [ "/100", "/ 50", "/ 40", "/ 30", "/ 25", "/ 20", "/16",
+ "/ 10", "/ 8", "/ 5", "/ 4", "/ 3", "/ 2", "x 0", "x 1", "x 2", "x 3",
+ "x 4", "x 5", "x 8", "x 10", "x 16", "x 20", "x 25", "x 30", "x 40",
+ "x 50", "x 100"
+ ]
+
+ for i in range(1024):
+ Sync1CycleComboList.append("x " + str(i + 1))
+
+ # iniitalize BoxSizer and FlexGridSizer
+ self.SizerDic = {
+ "DCConfig_main_sizer" : wx.BoxSizer(wx.VERTICAL),
+ "DCConfig_inner_main_sizer" : wx.FlexGridSizer(cols=1, hgap=50, rows=2, vgap=10),
+ "CyclicMode_InnerSizer" : wx.FlexGridSizer(cols=1, hgap=5, rows=2, vgap=5),
+ "SyncMode_InnerSizer" : wx.FlexGridSizer(cols=2, hgap=5, rows=1, vgap=5),
+ "OperationMode_InnerSizer" : wx.FlexGridSizer(cols=2, hgap=100, rows=2, vgap=10),
+ "CheckEnable_InnerSizer" : wx.FlexGridSizer(cols=2, hgap=10, rows=1, vgap=10),
+ "Sync0_InnerSizer" : wx.FlexGridSizer(cols=1, hgap=15, rows=3, vgap=10),
+ "Sync0_CycleTimeSizer" : wx.FlexGridSizer(cols=2, hgap=10, rows=2, vgap=5),
+ "Sync0_ShiftTimeSizer" : wx.FlexGridSizer(cols=2, hgap=20, rows=2, vgap=5),
+ "Sync1_InnerSizer" : wx.FlexGridSizer(cols=1, hgap=15, rows=3, vgap=10),
+ "Sync1_CycleTimeSizer" : wx.FlexGridSizer(cols=2, hgap=10, rows=2, vgap=5),
+ "Sync1_ShiftTimeSizer" : wx.FlexGridSizer(cols=2, hgap=20, rows=2, vgap=5)
+ }
+
+ # initialize StaticBox and StaticBoxSizer
+ for box_name, box_label in [
+ ("CyclicModeBox", "Cyclic Mode"),
+ ("Sync0Box", "Sync0"),
+ ("Sync0CycleTimeBox", "Cycle Time (us):"),
+ ("Sync0ShiftTimeBox", "Shift Time (us):"),
+ ("Sync1Box", "Sync1"),
+ ("Sync1CycleTimeBox", "Cycle Time (us):"),
+ ("Sync1ShiftTimeBox", "Shift Time (us):")
+ ]:
+ self.StaticBoxDic[box_name] = wx.StaticBox(self, label=_(box_label))
+ self.SizerDic[box_name] = wx.StaticBoxSizer(self.StaticBoxDic[box_name])
+
+ for statictext_name, statictext_label in [
+ ("MainLabel", "Distributed Clock"),
+ ("OperationModeLabel", "Operation Mode:"),
+ ("SyncUnitCycleLabel", "Sync Unit Cycle (us)"),
+ ("Sync0ShiftTimeUserDefinedLabel", "User Defined"),
+ ("Sync1ShiftTimeUserDefinedLabel", "User Defined"),
+ ("BlankObject", ""),
+ ("BlankObject1", "")
+ ]:
+ self.StaticTextDic[statictext_name] = wx.StaticText(self, label=_(statictext_label))
+
+ for textctl_name in [
+ ("SyncUnitCycle_Ctl"),
+ ("Sync0CycleTimeUserDefined_Ctl"),
+ ("Sync0ShiftTimeUserDefined_Ctl"),
+ ("Sync1CycleTimeUserDefined_Ctl"),
+ ("Sync1ShiftTimeUserDefined_Ctl"),
+ ]:
+ self.TextCtrlDic[textctl_name] = wx.TextCtrl(
+ self, size=wx.Size(130, 24), style=wx.TE_READONLY)
+
+ for checkbox_name, checkbox_label in [
+ ("DCEnable", "Enable"),
+ ("Sync0Enable", "Enable Sync0"),
+ ("Sync1Enable", "Enable Sync1")
+ ]:
+ self.CheckBoxDic[checkbox_name] = wx.CheckBox(self, -1, checkbox_label)
+
+ for combobox_name, combobox_list, size in [
+ ("OperationModeChoice", OperationModeComboList, 250),
+ ("Sync0UnitCycleChoice", UnitComboList, 130),
+ ("Sync1UnitCycleChoice", UnitComboList, 130)
+ ]:
+ self.ComboBoxDic[combobox_name] = wx.ComboBox(self, size=wx.Size(size, 24),
+ choices = combobox_list, style = wx.CB_DROPDOWN | wx.CB_READONLY)
+
+ for radiobutton_name, radiobutton_label in [
+ ("Sync0CycleTimeUnitRadioButton", "Sync Unit Cycle"),
+ ("Sync0CycleTimeUserDefinedRadioButton", "User Defined"),
+ ("Sync1CycleTimeUnitRadioButton", "Sync Unit Cycle"),
+ ("Sync1CycleTimeUserDefinedRadioButton", "User Defined")
+ ]:
+ self.RadioButtonDic[radiobutton_name] = wx.RadioButton(
+ self, label = radiobutton_label, style = wx.RB_SINGLE)
+
+
+ self.ApplyButton = wx.Button(self, label="Apply")
+
+ # binding event
+ self.Bind(wx.EVT_CHECKBOX, self.CheckDCEnable, self.CheckBoxDic["DCEnable"])
+ #self.Bind(wx.EVT_COMBOBOX, self.SelectOperationMode, self.ComboBoxDic["OperationModeChoice"])
+ #self.Bind(wx.EVT_COMBOBOX, self.SelectUnitCycle, self.ComboBoxDic["Sync0UnitChoice"])
+ self.Bind(wx.EVT_RADIOBUTTON, self.SelectSync0CycleTime,
+ self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"])
+ self.Bind(wx.EVT_RADIOBUTTON, self.SelectSync0CycleTime,
+ self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"])
+ self.Bind(wx.EVT_RADIOBUTTON, self.SelectSync1CycleTime,
+ self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"])
+ self.Bind(wx.EVT_RADIOBUTTON, self.SelectSync1CycleTime,
+ self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"])
+ self.Bind(wx.EVT_CHECKBOX, self.CheckSync0Enable, self.CheckBoxDic["Sync0Enable"])
+ self.Bind(wx.EVT_CHECKBOX, self.CheckSync1Enable, self.CheckBoxDic["Sync1Enable"])
+ self.Bind(wx.EVT_BUTTON, self.OnClickApplyButton, self.ApplyButton)
+
+ # sync1 shifttime box contents
+ self.SizerDic["Sync1_ShiftTimeSizer"].AddMany([
+ self.StaticTextDic["Sync1ShiftTimeUserDefinedLabel"],
+ self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"]
+ ])
+
+ # sync1 shifttime box
+ self.SizerDic["Sync1ShiftTimeBox"].Add(self.SizerDic["Sync1_ShiftTimeSizer"])
+
+ # sync1 cycletime box contents
+ self.SizerDic["Sync1_CycleTimeSizer"].AddMany([
+ self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"],
+ self.ComboBoxDic["Sync1UnitCycleChoice"],
+ self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"],
+ self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"]
+ ])
+
+ # sync0 cycletime box
+ self.SizerDic["Sync1CycleTimeBox"].Add(self.SizerDic["Sync1_CycleTimeSizer"])
+
+ self.SizerDic["Sync1_InnerSizer"].AddMany([
+ self.CheckBoxDic["Sync1Enable"], self.SizerDic["Sync1CycleTimeBox"],
+ self.SizerDic["Sync1ShiftTimeBox"]
+ ])
+
+ # sync1 box
+ self.SizerDic["Sync1Box"].Add(self.SizerDic["Sync1_InnerSizer"])
+
+ # sync0 shifttime box contents
+ self.SizerDic["Sync0_ShiftTimeSizer"].AddMany([
+ self.StaticTextDic["Sync0ShiftTimeUserDefinedLabel"],
+ self.TextCtrlDic["Sync0ShiftTimeUserDefined_Ctl"]
+ ])
+
+ # sync0 shifttime box
+ self.SizerDic["Sync0ShiftTimeBox"].Add(self.SizerDic["Sync0_ShiftTimeSizer"])
+
+ # sync0 cycletime box contents
+ self.SizerDic["Sync0_CycleTimeSizer"].AddMany([
+ self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"],
+ self.ComboBoxDic["Sync0UnitCycleChoice"],
+ self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"],
+ self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"]
+ ])
+
+ # sync0 cycletime box
+ self.SizerDic["Sync0CycleTimeBox"].Add(self.SizerDic["Sync0_CycleTimeSizer"])
+
+ self.SizerDic["Sync0_InnerSizer"].AddMany([
+ self.CheckBoxDic["Sync0Enable"], self.SizerDic["Sync0CycleTimeBox"],
+ self.SizerDic["Sync0ShiftTimeBox"]
+ ])
+
+ # sync0 box
+ self.SizerDic["Sync0Box"].Add(self.SizerDic["Sync0_InnerSizer"])
+
+ # sync0, sync1 box
+ self.SizerDic["SyncMode_InnerSizer"].AddMany([
+ self.SizerDic["Sync0Box"], self.SizerDic["Sync1Box"]
+ ])
+
+ # CyclicMode Box
+ self.SizerDic["CheckEnable_InnerSizer"].AddMany([
+ self.StaticTextDic["SyncUnitCycleLabel"],
+ self.TextCtrlDic["SyncUnitCycle_Ctl"]
+ ])
+
+ self.SizerDic["OperationMode_InnerSizer"].AddMany([
+ self.StaticTextDic["OperationModeLabel"],
+ self.ComboBoxDic["OperationModeChoice"],
+ self.CheckBoxDic["DCEnable"], self.SizerDic["CheckEnable_InnerSizer"]
+ ])
+
+ self.SizerDic["CyclicMode_InnerSizer"].AddMany([
+ self.SizerDic["OperationMode_InnerSizer"],
+ self.SizerDic["SyncMode_InnerSizer"]
+ ])
+
+ self.SizerDic["CyclicModeBox"].Add(self.SizerDic["CyclicMode_InnerSizer"])
+
+ # Main Sizer
+ self.SizerDic["DCConfig_inner_main_sizer"].AddMany([
+ self.StaticTextDic["MainLabel"], self.ApplyButton,
+ self.SizerDic["CyclicModeBox"]
+ ])
+
+ self.SizerDic["DCConfig_main_sizer"].Add(self.SizerDic["DCConfig_inner_main_sizer"])
+
+ self.SetSizer(self.SizerDic["DCConfig_main_sizer"])
+
+ self.Centre()
+
+ self.UIOnOffSet(False)
+ self.LoadProjectDCData()
+
+ def UIOnOffSet(self, activate):
+ if activate :
+ for object in self.RadioButtonDic:
+ self.RadioButtonDic[object].Enable()
+
+ for object in self.ComboBoxDic:
+ if object == "OperationModeChoice":
+ continue
+ self.ComboBoxDic[object].Enable()
+
+ for object in self.TextCtrlDic:
+ if object in ["SyncUnitCycle_Ctl", "InputReference_Ctl"]:
+ continue
+ self.TextCtrlDic[object].Enable()
+
+ for object in self.CheckBoxDic:
+ if object == "DCEnable":
+ continue
+ self.CheckBoxDic[object].Enable()
+
+ # initial set or DC enable uncheck
+ else :
+ for object in self.RadioButtonDic:
+ self.RadioButtonDic[object].Disable()
+
+ for object in self.ComboBoxDic:
+ if object == "OperationModeChoice":
+ continue
+ self.ComboBoxDic[object].Disable()
+
+ for object in self.TextCtrlDic:
+ if object == "SyncUnitCycle_Ctl":
+ continue
+ self.TextCtrlDic[object].Disable()
+
+ for object in self.CheckBoxDic:
+ if object == "DCEnable":
+ continue
+ self.CheckBoxDic[object].Disable()
+
+ for data in self.ESI_DC_Data:
+ index = self.Controler.ExtractHexDecValue(data["assign_activate"])
+ if index == 0:
+ config_name = data["desc"]
+ self.ComboBoxDic["OperationModeChoice"].SetStringSelection(config_name)
+
+ def CheckSync0Enable(self, evt):
+ if evt.GetInt():
+ self.ComboBoxDic["Sync0UnitCycleChoice"].Enable()
+ self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"].Enable()
+ self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"].Enable()
+ self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].Enable()
+ self.TextCtrlDic["Sync0ShiftTimeUserDefined_Ctl"].Enable()
+ else :
+ self.ComboBoxDic["Sync0UnitCycleChoice"].Disable()
+ self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"].Disable()
+ self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"].Disable()
+ self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].Disable()
+ self.TextCtrlDic["Sync0ShiftTimeUserDefined_Ctl"].Disable()
+ self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"].SetValue(False)
+ self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"].SetValue(False)
+ self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].SetValue("")
+ self.TextCtrlDic["Sync0ShiftTimeUserDefined_Ctl"].SetValue("")
+
+ def CheckSync1Enable(self, evt):
+ if evt.GetInt():
+ self.ComboBoxDic["Sync1UnitCycleChoice"].Enable()
+ self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].Enable()
+ self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].Enable()
+ self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].Enable()
+ self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"].Enable()
+ else :
+ self.ComboBoxDic["Sync1UnitCycleChoice"].Disable()
+ self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].Disable()
+ self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].Disable()
+ self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].Disable()
+ self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"].Disable()
+ self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].SetValue(False)
+ self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].SetValue(False)
+ self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].SetValue("")
+ self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"].SetValue("")
+
+ def CheckDCEnable(self, evt):
+ ns_mode = 1
+ task_cycle_ns = self.GetInterval(ns_mode)
+ sync0_cycle_factor = None
+ sync1_cycle_factor = None
+
+ #task_cycle_ns = self.Controler.GetCTRoot()._Ticktime
+ if (task_cycle_ns > 0):
+ self.UIOnOffSet(evt.GetInt())
+
+ if evt.GetInt():
+ # default select DC enable sync0
+ default_list_num = 0
+ config_name = self.ESI_DC_Data[default_list_num]["desc"]
+ assign_act = self.ESI_DC_Data[default_list_num]["assign_activate"]
+ sync0_cycle_time_ns = self.ESI_DC_Data[default_list_num]["cycletime_sync0"]
+ if sync0_cycle_time_ns == 0 :
+ sync0_cycle_factor = self.ESI_DC_Data[default_list_num]["cycletime_sync0_factor"]
+ sync0_shift_time_ns = self.ESI_DC_Data[default_list_num]["shifttime_sync0"]
+ sync1_cycle_time_ns = self.ESI_DC_Data[default_list_num]["cycletime_sync1"]
+ if sync1_cycle_time_ns == 0 :
+ sync1_cycle_factor = self.ESI_DC_Data[default_list_num]["cycletime_sync1_factor"]
+ sync1_shift_time_ns = self.ESI_DC_Data[default_list_num]["shifttime_sync1"]
+
+ cal_assign_act = self.Controler.ExtractHexDecValue(assign_act)
+ sync0_cycle_time_us = str(int(sync0_cycle_time_ns) / 1000)
+ sync0_shift_time_us = str(int(sync0_shift_time_ns) / 1000)
+ sync1_cycle_time_us = str(int(sync1_cycle_time_ns) / 1000)
+ sync1_shift_time_us = str(int(sync1_shift_time_ns) / 1000)
+
+ task_cycle_to_us = str(int(task_cycle_ns) / 1000)
+
+ # DC sync0 mode
+ if cal_assign_act == 768:
+ # Disable About Sync1 Objects
+ self.CheckBoxDic["Sync1Enable"].SetValue(False)
+ self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].Disable()
+ self.ComboBoxDic["Sync1UnitCycleChoice"].Disable()
+ self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].Disable()
+ self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].Disable()
+ self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"].Disable()
+
+ else :
+ self.CheckBoxDic["Sync1Enable"].SetValue(True)
+ if sync1_cycle_factor is not None:
+ self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].SetValue(True)
+ self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].SetValue(False)
+ self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].Disable()
+ self.SetSyncUnitCycle(sync1_cycle_factor,
+ self.ComboBoxDic["Sync1UnitCycleChoice"])
+ else :
+ self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].SetValue(False)
+ self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].SetValue(True)
+ self.ComboBoxDic["Sync1UnitCycleChoice"].Disable()
+ self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].SetValue(sync1_cycle_time_us)
+
+ self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"].SetValue(sync1_shift_time_us)
+
+ # Set Sync0 Objects
+ self.CheckBoxDic["Sync0Enable"].SetValue(True)
+ if sync0_cycle_factor is not None:
+ self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"].SetValue(True)
+ self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"].SetValue(False)
+ self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].Disable()
+ self.SetSyncUnitCycle(sync0_cycle_factor,
+ self.ComboBoxDic["Sync0UnitCycleChoice"])
+ else :
+ self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"].SetValue(False)
+ self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"].SetValue(True)
+ self.ComboBoxDic["Sync0UnitCycleChoice"].Disable()
+ self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].SetValue(sync0_cycle_time_us)
+
+ self.TextCtrlDic["Sync0ShiftTimeUserDefined_Ctl"].SetValue(sync0_shift_time_us)
+
+ self.ComboBoxDic["OperationModeChoice"].SetStringSelection(config_name)
+ self.TextCtrlDic["SyncUnitCycle_Ctl"].SetValue(task_cycle_to_us)
+ else :
+ self.CheckBoxDic["Sync0Enable"].SetValue(False)
+ self.CheckBoxDic["Sync1Enable"].SetValue(False)
+ self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"].SetValue(False)
+ self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"].SetValue(False)
+ self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].SetValue(False)
+ self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].SetValue(False)
+ self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].SetValue("")
+ self.TextCtrlDic["Sync0ShiftTimeUserDefined_Ctl"].SetValue("")
+ self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].SetValue("")
+ self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"].SetValue("")
+
+ else :
+ self.UIOnOffSet(False)
+ #error_str = "DC Enable is not possble, please set task interval"
+ error_str = "Can't Set DC Enable"
+ self.Controler.CommonMethod.CreateErrorDialog(error_str)
+
+ def SetSyncUnitCycle(self, factor, object):
+ # factor > 0 ==> * factor, factor < 0 ==> / factor
+ factor_to_int = int(factor)
+ if factor_to_int > 0:
+ lists = object.GetStrings()
+
+ for token in lists:
+ temp = token.split(" ")
+ if (temp[0] == "x") and (int(temp[1]) == factor_to_int):
+ object.SetStringSelection(token)
+ return True
+
+ else :
+ lists = object.GetStrings()
+
+ for token in lists:
+ temp = token.split(" ")
+ if (temp[0] == "/") and (int(temp[1]) == factor_to_int):
+ object.SetStringSelection(token)
+ return True
+
+ return False
+
+ def GetInterval(self, mode):
+ project_infos = self.Controler.GetCTRoot().GetProjectInfos()
+ for project_info_list in project_infos["values"]:
+ if project_info_list["name"] == "Resources" :
+ token = project_info_list["values"][0]["tagname"]
+
+ tasks, instances = self.Controler.GetCTRoot().GetEditedResourceInfos(token)
+ try:
+ task_cycle_ns = self.ParseTime(tasks[0]["Interval"])
+ except :
+ task_cycle_ns = 0
+ task_cycle_us = int(task_cycle_ns) / 1000
+
+ # mode == 1 ==> return ns
+ # mode == 2 ==> return us
+
+ if mode == 1:
+ return task_cycle_ns
+ if mode == 2:
+ return str(task_cycle_us)
+
+ def ParseTime(self, input):
+ # input example : 't#1ms'
+ # temp.split('#') -> ['t', '1ms']
+ temp = input.split('#')
+
+ # temp[1] : '1ms'
+ # temp[-2:] : 'ms'
+ # temp[:-2] : '1'
+ if temp[1][-2:] == "ms":
+ # convert nanosecond unit
+ result = int(temp[1][:-2]) * 1000000
+ elif temp[1][-2:] == "us":
+ result = int(temp[1][:-2]) * 1000
+
+ return str(result)
+
+ def SelectSync0CycleTime(self, evt):
+ selected_object = evt.GetEventObject()
+
+ if selected_object.GetLabel() == "User Defined" :
+ self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"].SetValue(False)
+ self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].Enable()
+ self.ComboBoxDic["Sync0UnitCycleChoice"].Disable()
+ elif selected_object.GetLabel() == "Sync Unit Cycle" :
+ self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"].SetValue(False)
+ self.ComboBoxDic["Sync0UnitCycleChoice"].Enable()
+ self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].Disable()
+
+ def SelectSync1CycleTime(self, evt):
+ selected_object = evt.GetEventObject()
+
+ if selected_object.GetLabel() == "User Defined" :
+ self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].SetValue(False)
+ self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].Enable()
+ self.ComboBoxDic["Sync1UnitCycleChoice"].Disable()
+ elif selected_object.GetLabel() == "Sync Unit Cycle" :
+ self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].SetValue(False)
+ self.ComboBoxDic["Sync1UnitCycleChoice"].Enable()
+ self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].Disable()
+
+ def GetCycle(self, period, section):
+ temp = section.split(" ")
+
+ if temp[0] == "x":
+ result = int(period) * int(temp[1])
+ elif temp[0] == "/" :
+ result = int(period) / int(temp[1])
+ else :
+ result = ""
+
+ return result
+
+ def OnClickApplyButton(self, evt):
+ us_mode = 2
+ dc_enable = self.CheckBoxDic["DCEnable"].GetValue()
+ dc_desc = self.ComboBoxDic["OperationModeChoice"].GetStringSelection()
+ dc_assign_activate = self.ESI_DC_Data[0]["assign_activate"]
+ dc_assign_activate_mod = dc_assign_activate.split('x')[1]
+
+ if self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"].GetValue():
+ temp = self.ComboBoxDic["Sync0UnitCycleChoice"].GetStringSelection()
+ dc_sync0_cycle = "1_" + str(self.GetCycle(self.GetInterval(us_mode), temp))
+ elif self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"].GetValue():
+ dc_sync0_cycle = "2_" + self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].GetValue()
+ else :
+ dc_sync0_cycle = ""
+
+ if self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].GetValue():
+ temp = self.ComboBoxDic["Sync1UnitCycleChoice"].GetStringSelection()
+ dc_sync1_cycle = "1_" + self.GetCycle(self.GetInterval(us_mode), temp)
+ elif self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].GetValue():
+ dc_sync1_cycle = "2_" + self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].GetValue()
+ else :
+ dc_sync1_cycle = ""
+
+ dc_sync0_shift = self.TextCtrlDic["Sync0ShiftTimeUserDefined_Ctl"].GetValue()
+ dc_sync1_shift = self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"].GetValue()
+
+ self.Controler.BaseParams.setDC_Enable(dc_enable)
+ self.Controler.BaseParams.setDC_Desc(dc_desc)
+ self.Controler.BaseParams.setDC_Assign_Activate(dc_assign_activate_mod)
+ if dc_sync0_cycle:
+ self.Controler.BaseParams.setDC_Sync0_Cycle_Time(dc_sync0_cycle)
+ if dc_sync0_shift:
+ self.Controler.BaseParams.setDC_Sync0_Shift_Time(dc_sync0_shift)
+ if dc_sync1_cycle:
+ self.Controler.BaseParams.setDC_Sync1_Cycle_Time(dc_sync1_cycle)
+ if dc_sync1_shift:
+ self.Controler.BaseParams.setDC_Sync1_Shift_Time(dc_sync1_shift)
+ project_infos = self.Controler.GetCTRoot().CTNRequestSave()
+
+ def GetSymbol(self, period, cycle):
+ cmp1 = int(period)
+ cmp2 = int(cycle)
+
+ if cmp1 == cmp2 :
+ return "x 1"
+ elif cmp2 > cmp1 :
+ temp = cmp2 / cmp1
+ result = "x " + str(temp)
+ else :
+ temp = cmp1 / cmp2
+ result = "/ " + str(temp)
+
+ return result
+
+ def SetSyncCycle(self, period, sync0_cycle, sync1_cycle):
+ if sync0_cycle != "None":
+ self.CheckBoxDic["Sync0Enable"].SetValue(True)
+ temp = sync0_cycle.split("_")
+ if temp[0] == "1":
+ symbol = self.GetSymbol(period, temp[1])
+ self.ComboBoxDic["Sync0UnitCycleChoice"].SetStringSelection(symbol)
+ self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].Disable()
+ self.RadioButtonDic["Sync0CycleTimeUnitRadioButton"].SetValue(True)
+ else :
+ self.TextCtrlDic["Sync0CycleTimeUserDefined_Ctl"].SetValue(temp[1])
+ self.ComboBoxDic["Sync0UnitCycleChoice"].Disable()
+ self.RadioButtonDic["Sync0CycleTimeUserDefinedRadioButton"].SetValue(True)
+
+ if sync1_cycle != "None":
+ self.CheckBoxDic["Sync1Enable"].SetValue(True)
+ temp = sync1_cycle.split("_")
+ if temp[0] == "1":
+ symbol = self.GetSymbol(period, temp[1])
+ self.ComboBoxDic["Sync1UnitChoice"].SetStringSelection(symbol)
+ self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].Disable()
+ self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].SetValue(True)
+ else :
+ self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].SetValue(temp[1])
+ self.ComboBoxDic["Sync1UnitChoice"].Disable()
+ self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].SetValue(True)
+
+ def LoadProjectDCData(self):
+ ns_mode = 1
+ task_cycle_ns = self.GetInterval(ns_mode)
+ task_cycle_to_us = int(task_cycle_ns) / 1000
+ dc_enable = self.Controler.BaseParams.getDC_Enable()
+ dc_desc = self.Controler.BaseParams.getDC_Desc()
+ dc_assign_activate = self.Controler.BaseParams.getDC_Assign_Activate()
+ dc_sync0_cycle = self.Controler.BaseParams.getDC_Sync0_Cycle_Time()
+ dc_sync0_shift = self.Controler.BaseParams.getDC_Sync0_Shift_Time()
+ dc_sync1_cycle = self.Controler.BaseParams.getDC_Sync1_Cycle_Time()
+ dc_sync1_shift = self.Controler.BaseParams.getDC_Sync1_Shift_Time()
+
+ self.UIOnOffSet(dc_enable)
+
+ if dc_enable:
+ self.CheckBoxDic["DCEnable"].SetValue(dc_enable)
+ self.ComboBoxDic["OperationModeChoice"].SetStringSelection(dc_desc)
+ self.TextCtrlDic["SyncUnitCycle_Ctl"].SetValue(str(task_cycle_to_us))
+ self.SetSyncCycle(str(task_cycle_to_us), dc_sync0_cycle, dc_sync1_cycle)
+ if dc_sync0_shift != "None":
+ self.TextCtrlDic["Sync0ShiftTimeUserDefined_Ctl"].SetValue(dc_sync0_shift)
+ if dc_sync1_shift != "None":
+ self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"].SetValue(dc_sync1_shift)
+
+ if dc_assign_activate == "300":
+ self.CheckBoxDic["Sync1Enable"].SetValue(False)
+ self.RadioButtonDic["Sync1CycleTimeUnitRadioButton"].Disable()
+ self.ComboBoxDic["Sync1UnitCycleChoice"].Disable()
+ self.RadioButtonDic["Sync1CycleTimeUserDefinedRadioButton"].Disable()
+ self.TextCtrlDic["Sync1CycleTimeUserDefined_Ctl"].Disable()
+ self.TextCtrlDic["Sync1ShiftTimeUserDefined_Ctl"].Disable()
+
+
+
+
+
+
+
+
+
+
--- a/etherlab/EthercatCFileGenerator.py Thu Jan 28 14:50:26 2021 +0000
+++ b/etherlab/EthercatCFileGenerator.py Thu Jan 28 14:51:16 2021 +0000
@@ -70,6 +70,37 @@
}
"""
+SLAVE_OUTPUT_PDO_DEFAULT_VALUE_BIT = """
+ {
+ uint8_t value[%(data_size)d];
+ if (ecrt_master_sdo_upload(master, %(slave)d, 0x%(index).4x, 0x%(subindex).2x, (uint8_t *)value, %(data_size)d, &result_size, &abort_code)) {
+ SLOGF(LOG_CRITICAL, "EtherCAT failed to get default value for output PDO in slave %(device_type)s at alias %(alias)d and position %(position)d. Error: %%ud", abort_code);
+ goto ecat_failed;
+ }
+ %(real_var)s = EC_READ_%(data_type)s((uint8_t *)value, %(subindex)d);
+ }
+"""
+
+
+SLAVE_INPUT_PDO_DEFAULT_VALUE = """
+ {
+ uint8_t value[%(data_size)d];
+ if (ecrt_master_sdo_upload(master, %(slave)d, 0x%(index).4x, 0x%(subindex).2x, (uint8_t *)value, %(data_size)d, &result_size, &abort_code)) {
+ SLOGF(LOG_CRITICAL, "EtherCAT failed to get default value for input PDO in slave %(device_type)s at alias %(alias)d and position %(position)d. Error: %%ud", abort_code);
+ goto ecat_failed;
+ }
+ %(real_var)s = EC_READ_%(data_type)s((uint8_t *)value);
+ }
+"""
+
+DC_VARIABLE ="""
+#define DC_ENABLE %(dc_flag)d
+"""
+
+CONFIG_DC = """
+ ecrt_slave_config_dc (slave%(slave)d, 0x0%(assign_activate)ld, %(sync0_cycle_time)d, %(sync0_shift_time)d, %(sync1_cycle_time)d, %(sync1_shift_time)d);
+"""
+
def ConfigureVariable(entry_infos, str_completion):
entry_infos["data_type"] = DATATYPECONVERSION.get(entry_infos["var_type"], None)
@@ -192,10 +223,15 @@
"pdos_configuration_declaration": "",
"slaves_declaration": "",
"slaves_configuration": "",
+ # add jblee
+ "slaves_input_pdos_default_values_extraction": "",
"slaves_output_pdos_default_values_extraction": "",
"slaves_initialization": "",
"retrieve_variables": [],
"publish_variables": [],
+ #-----------This Code templete for dc -------------------#
+ "dc_variable" : "",
+ "config_dc": ""
}
# Initialize variable storing variable mapping state
@@ -208,6 +244,9 @@
# Initialize dictionary storing alias auto-increment position values
alias = {}
+ # add jblee
+ slotNumber = 1
+
# Generating code for each slave
for (slave_idx, slave_alias, slave) in self.Slaves:
type_infos = slave.getType()
@@ -244,11 +283,14 @@
# If device support CanOpen over Ethernet, adding code for calling
# init commands when initializing slave in master code template strings
initCmds = []
+
for initCmd in device_coe.getInitCmd():
initCmds.append({
"Index": ExtractHexDecValue(initCmd.getIndex()),
"Subindex": ExtractHexDecValue(initCmd.getSubIndex()),
- "Value": initCmd.getData().getcontent()})
+ #"Value": initCmd.getData().getcontent()})
+ "Value": int(initCmd.getData().text, 16)})
+
initCmds.extend(slave.getStartupCommands())
for initCmd in initCmds:
index = initCmd["Index"]
@@ -314,12 +356,106 @@
pdos_index = []
exclusive_pdos = {}
selected_pdos = []
- for pdo, pdo_type in ([(pdo, "Inputs") for pdo in device.getTxPdo()] +
- [(pdo, "Outputs") for pdo in device.getRxPdo()]):
-
+
+ # add jblee
+ TxPdoData = []
+ RxPdoData = []
+ PdoData = []
+
+ # add jblee
+ if len(device.getTxPdo() + device.getRxPdo()) > 0:
+ for pdo in device.getTxPdo():
+ PdoData.append((pdo, "Inputs"))
+ for pdo in device.getRxPdo():
+ PdoData.append((pdo, "Outputs"))
+
+ # mod jblee
+ #for pdo, pdo_type in ([(pdo, "Inputs") for pdo in device.getTxPdo()] +
+ # [(pdo, "Outputs") for pdo in device.getRxPdo()]):
+ #for pdo, pdo_type in (TxPdoData + RxPdoData):
+ data_files = os.listdir(self.Controler.CTNPath())
+ PDODataList = []
+ MDPData = []
+ RxPDOData = self.Controler.GetChildByIECLocation((slave_idx,)).BaseParams.getRxPDO()
+ TxPDOData = self.Controler.GetChildByIECLocation((slave_idx,)).BaseParams.getTxPDO()
+ PDOList = RxPDOData.split() + TxPDOData.split()
+ for PDOIndex in PDOList:
+ if PDOIndex in ["RxPDO", "TxPDO", "None"]:
+ continue
+ PDODataList.append(int(PDOIndex, 0))
+
+ # add jblee for DC Configuration
+ dc_enable = self.Controler.GetChildByIECLocation((slave_idx,)).BaseParams.getDC_Enable()
+ sync0_cycle_time = 0
+ sync0_shift_time = 0
+ sync1_cycle_time = 0
+ sync1_shift_time = 0
+ if dc_enable :
+ sync0_cycle_token = self.Controler.GetChildByIECLocation((slave_idx,)).BaseParams.getDC_Sync0_Cycle_Time()
+ if sync0_cycle_token != "None":
+ sync0_cycle_time = int(sync0_cycle_token.split("_")[1]) * 1000
+ sync0_shift_token = self.Controler.GetChildByIECLocation((slave_idx,)).BaseParams.getDC_Sync0_Shift_Time()
+ if sync0_shift_token != "None":
+ sync0_shift_time = int(sync0_shift_token) * 1000
+ sync1_cycle_token = self.Controler.GetChildByIECLocation((slave_idx,)).BaseParams.getDC_Sync1_Cycle_Time()
+ if sync1_cycle_token != "None":
+ sync1_cycle_time = int(sync1_cycle_token.split("_")[1]) * 1000
+ sync1_shift_token = self.Controler.GetChildByIECLocation((slave_idx,)).BaseParams.getDC_Sync1_Shift_Time()
+ if sync1_shift_token != "None":
+ sync1_shift_time = int(sync1_shift_token) * 1000
+
+ dc_config_data = {
+ "slave" : slave_idx,
+ "assign_activate" : int(self.Controler.GetChildByIECLocation((slave_idx,)).BaseParams.getDC_Assign_Activate()),
+ "sync0_cycle_time" : sync0_cycle_time,
+ "sync0_shift_time" : sync0_shift_time,
+ "sync1_cycle_time" : sync1_cycle_time,
+ "sync1_shift_time" : sync1_shift_time,
+ }
+
+ if dc_enable and not str_completion["dc_variable"] :
+ str_completion["dc_variable"] += DC_VARIABLE % {"dc_flag" : dc_enable}
+ str_completion["config_dc"] += CONFIG_DC % dc_config_data
+
+ for data_file in data_files:
+ slave_path = os.path.join(self.Controler.CTNPath(), data_file)
+ if os.path.isdir(slave_path):
+ CheckConfNodePath = os.path.join(slave_path, "baseconfnode.xml")
+ confNodeFile = open(CheckConfNodePath, 'r')
+ checklines = confNodeFile.readlines()
+ confNodeFile.close()
+ # checklines(ex) : <BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="0" Name="EthercatSlave_0"/>
+ # checklines[1].split() : [<BaseParams, xmlns:xsd="http://www.w3.org/2001/XMLSchema",
+ # IEC_Channel="0", Name="EthercatSlave_0"/>]
+ # checklines[1].split()[2] : IEC_Channel="0"
+ # checklines[1].split()[2].split("\"") = [IEC_Channel=, 0, ]
+ pos_check = int(checklines[1].split()[2].split("\"")[1])
+ if slave_idx == pos_check:
+ MDPDataFilePath = os.path.join(slave_path, "DataForMDP.txt")
+ if os.path.isfile(MDPDataFilePath):
+ MDPDataFile = open(MDPDataFilePath, 'r')
+ MDPData = MDPDataFile.readlines()
+ MDPDataFile.close()
+
+ for MDPLine in MDPData:
+ if MDPLine == "\n":
+ continue
+ module_pos = int(MDPLine.split()[-1])
+ module = self.Controler.CTNParent.GetSelectModule(module_pos)
+ for pdo in module.getTxPdo():
+ PdoData.append((pdo, "Inputs"))
+ PDODataList.append(ExtractHexDecValue(pdo.getIndex().getcontent()))
+ for pdo in module.getRxPdo():
+ PdoData.append((pdo, "Outputs"))
+ PDODataList.append(ExtractHexDecValue(pdo.getIndex().getcontent()))
+
+ for pdo, pdo_type in PdoData:
pdo_index = ExtractHexDecValue(pdo.getIndex().getcontent())
pdos_index.append(pdo_index)
+ if PDODataList and (pdo_index in PDODataList):
+ continue
+
excluded_list = pdo.getExclude()
if len(excluded_list) > 0:
exclusion_list = [pdo_index]
@@ -330,12 +466,14 @@
exclusion_scope = exclusive_pdos.setdefault(tuple(exclusion_list), [])
entries = pdo.getEntry()
+
pdo_mapping_match = {
"index": pdo_index,
"matching": 0,
"count": len(entries),
"assigned": pdo.getSm() is not None
}
+
exclusion_scope.append(pdo_mapping_match)
for entry in entries:
@@ -364,30 +502,57 @@
for pdo in exclusion_scope[start_excluding_index:]
if PdoAssign or not pdo["assigned"]])
- for pdo, pdo_type in ([(pdo, "Inputs") for pdo in device.getTxPdo()] +
- [(pdo, "Outputs") for pdo in device.getRxPdo()]):
- entries = pdo.getEntry()
-
+ # mod jblee
+ #for pdo, pdo_type in ([(pdo, "Inputs") for pdo in device.getTxPdo()] +
+ # [(pdo, "Outputs") for pdo in device.getRxPdo()]):
+ #for pdo, pdo_type in (TxPdoData + RxPdoData):
+ entry_check_list = []
+ index_padding = 1
+ for pdo, pdo_type in PdoData:
+ entries = pdo.getEntry()
+
pdo_index = ExtractHexDecValue(pdo.getIndex().getcontent())
if pdo_index in excluded_pdos:
continue
-
- pdo_needed = pdo_index in selected_pdos
-
- entries_infos = []
-
+ if PDODataList and (pdo_index not in PDODataList):
+ continue
+
+ #pdo_needed = pdo_index in selected_pdos
+ pdo_needed = pdo_index in PDODataList
+
+ if len(MDPData) > 0:
+ pdo_index += index_padding
+ index_padding += 1
+
+ entries_infos = []
for entry in entries:
index = ExtractHexDecValue(entry.getIndex().getcontent())
subindex = ExtractHexDecValue(entry.getSubIndex())
+
+ # add jblee
+ if len(MDPData) > 0:
+ increse = self.Controler.CTNParent.GetMDPInfos(type_infos)
+ if increse and index != 0:
+ index += int(increse[0][2]) * slotNumber
+
entry_infos = {
"index": index,
"subindex": subindex,
"name": ExtractName(entry.getName()),
"bitlen": entry.getBitLen(),
}
+
+ entry_infos.update(type_infos)
+ #temp_data = " {0x%(index).4x, 0x%(subindex).2x, %(bitlen)d}, /* %(name)s */" % entry_infos
+ check_data = "{0x%(index).4x, 0x%(subindex).2x, %(bitlen)d}" % entry_infos
+ if entry_check_list and check_data in entry_check_list:
+ if (entry_infos["index"] == 0) or (entry_infos["name"] == None):
+ pass
+ else:
entry_infos.update(type_infos)
entries_infos.append(" {0x%(index).4x, 0x%(subindex).2x, %(bitlen)d}, /* %(name)s */" % entry_infos)
-
+ entry_check_list.append(check_data)
+
entry_declaration = slave_variables.get((index, subindex), None)
if entry_declaration is not None and not entry_declaration["mapped"]:
pdo_needed = True
@@ -421,9 +586,26 @@
ConfigureVariable(entry_infos, str_completion)
- str_completion["slaves_output_pdos_default_values_extraction"] += \
- SLAVE_OUTPUT_PDO_DEFAULT_VALUE % entry_infos
-
+ if entry_infos["data_type"] == "BIT" :
+ str_completion["slaves_output_pdos_default_values_extraction"] += \
+ SLAVE_OUTPUT_PDO_DEFAULT_VALUE_BIT % entry_infos
+ else :
+ str_completion["slaves_output_pdos_default_values_extraction"] += \
+ SLAVE_OUTPUT_PDO_DEFAULT_VALUE % entry_infos
+
+ elif pdo_type == "Inputs" and entry.getDataType() is not None and device_coe is not None:
+ data_type = entry.getDataType().getcontent()
+ entry_infos["dir"] = "I"
+ entry_infos["data_size"] = max(1, entry_infos["bitlen"] / 8)
+ entry_infos["data_type"] = DATATYPECONVERSION.get(data_type)
+ entry_infos["var_type"] = data_type
+ entry_infos["real_var"] = "slave%(slave)d_%(index).4x_%(subindex).2x_default" % entry_infos
+
+ ConfigureVariable(entry_infos, str_completion)
+
+ str_completion["slaves_input_pdos_default_values_extraction"] += \
+ SLAVE_INPUT_PDO_DEFAULT_VALUE % entry_infos
+
if pdo_needed:
for excluded in pdo.getExclude():
excluded_index = ExtractHexDecValue(excluded.getcontent())
@@ -448,6 +630,9 @@
"entries_number": len(entries_infos),
"fixed": pdo.getFixed() is True})
+ # for MDP
+ slotNumber += 1
+
if PdoConfig and PdoAssign:
dynamic_pdos = {}
dynamic_pdos_number = 0
@@ -541,8 +726,8 @@
pdo_offset = 0
entry_offset = 0
+ slotNumber = 1
for sync_manager_infos in sync_managers:
-
for pdo_infos in sync_manager_infos["pdos"]:
pdo_infos["offset"] = entry_offset
pdo_entries = pdo_infos["entries"]
@@ -564,17 +749,17 @@
pdos_infos[element] = "\n".join(pdos_infos[element])
str_completion["pdos_configuration_declaration"] += SLAVE_PDOS_CONFIGURATION_DECLARATION % pdos_infos
-
- for (index, subindex), entry_declaration in slave_variables.iteritems():
- if not entry_declaration["mapped"]:
- message = _("Entry index 0x{a1:.4x}, subindex 0x{a2:.2x} not mapped for device {a3}").\
- format(a1=index, a2=subindex, a3=type_infos["device_type"])
- self.Controler.GetCTRoot().logger.write_warning(_("Warning: ") + message + "\n")
-
- for element in ["used_pdo_entry_offset_variables_declaration",
- "used_pdo_entry_configuration",
- "located_variables_declaration",
- "retrieve_variables",
+
+ #for (index, subindex), entry_declaration in slave_variables.iteritems():
+ # if not entry_declaration["mapped"]:
+ # message = _("Entry index 0x%4.4x, subindex 0x%2.2x not mapped for device %s") % \
+ # (index, subindex, type_infos["device_type"])
+ # self.Controler.GetCTRoot().logger.write_warning(_("Warning: ") + message + "\n")
+
+ for element in ["used_pdo_entry_offset_variables_declaration",
+ "used_pdo_entry_configuration",
+ "located_variables_declaration",
+ "retrieve_variables",
"publish_variables"]:
str_completion[element] = "\n".join(str_completion[element])
--- a/etherlab/EthercatCIA402Slave.py Thu Jan 28 14:50:26 2021 +0000
+++ b/etherlab/EthercatCIA402Slave.py Thu Jan 28 14:51:16 2021 +0000
@@ -201,11 +201,69 @@
("%%IW%s.402" % ".".join(map(str, self.GetCurrentLocation())),
"location", "AXIS_REF", self.CTNName(), ""))
+ # add jblee
+ """
+ def LoadPDOSelectData(self):
+ ReadData = []
+ files = os.listdir(self.CTNPath())
+ filepath = os.path.join(self.CTNPath(), "DataForPDO.txt")
+ if os.path.isfile(filepath):
+ PDODataRead = open(filepath, 'r')
+ ReadData = PDODataRead.readlines()
+ PDODataRead.close()
+
+ if len(ReadData) > 1:
+ for data in ReadData[0].split() :
+ if data == "RxPDO":
+ continue
+ self.SelectedRxPDOIndex.append(int(data, 0))
+
+ for data in ReadData[1].split() :
+ if data == "TxPDO":
+ continue
+ self.SelectedTxPDOIndex.append(int(data, 0))
+ """
+
+ def LoadPDOSelectData(self):
+ RxPDOData = self.BaseParams.getRxPDO()
+ RxPDOs = []
+ if RxPDOData != "None":
+ RxPDOs = RxPDOData.split()
+ if RxPDOs :
+ for RxPDO in RxPDOs :
+ self.SelectedRxPDOIndex.append(int(RxPDO, 0))
+
+ TxPDOData = self.BaseParams.getTxPDO()
+ TxPDOs = []
+ if TxPDOData != "None":
+ TxPDOs = TxPDOData.split()
+ if TxPDOs :
+ for TxPDO in TxPDOs :
+ self.SelectedTxPDOIndex.append(int(TxPDO, 0))
+
+ def LoadDefaultPDOSet(self):
+ ReturnData = []
+ rx_pdo_entries = self.CommonMethod.GetRxPDOCategory()
+ if len(rx_pdo_entries):
+ for i in range(len(rx_pdo_entries)):
+ if rx_pdo_entries[i]['sm'] is not None:
+ ReturnData.append(rx_pdo_entries[i]['pdo_index'])
+
+ tx_pdo_entries = self.CommonMethod.GetTxPDOCategory()
+ if len(tx_pdo_entries):
+ for i in range(len(tx_pdo_entries)):
+ if tx_pdo_entries[i]['sm'] is not None:
+ ReturnData.append(tx_pdo_entries[i]['pdo_index'])
+
+ if ReturnData :
+ return ReturnData
+ else :
+ return [5632, 6656]
+
def CTNGenerate_C(self, buildpath, locations):
current_location = self.GetCurrentLocation()
location_str = "_".join(map(str, current_location))
- slave_pos = self.GetSlavePos()
# Open CIA402 node code template file
plc_cia402node_filepath = os.path.join(os.path.split(__file__)[0],
@@ -213,160 +271,204 @@
plc_cia402node_file = open(plc_cia402node_filepath, 'r')
plc_cia402node_code = plc_cia402node_file.read()
plc_cia402node_file.close()
-
- # Init list of generated strings for each code template file section
- fieldbus_interface_declaration = []
- fieldbus_interface_definition = []
- init_axis_params = []
- extra_variables_retrieve = []
- extra_variables_publish = []
- extern_located_variables_declaration = []
- entry_variables = []
- init_entry_variables = []
-
- # Fieldbus interface code sections
+ # HSAHN 150726
+ # add "default_variables_retrieve": [], "default_variables_publish": [],
+ # As PDO mapping object, it will add auto-complete code.
+ # add "modeofop_homing_method", "modeofop_computation_mode" by jblee
+ str_completion = {
+ "slave_pos": self.GetSlavePos(),
+ "location": location_str,
+ "MCL_headers": Headers,
+ "extern_located_variables_declaration": [],
+ "fieldbus_interface_declaration": [],
+ "fieldbus_interface_definition": [],
+ "entry_variables": [],
+ "init_axis_params": [],
+ "init_entry_variables": [],
+ "default_variables_retrieve": [],
+ "default_variables_publish": [],
+ "extra_variables_retrieve": [],
+ "extra_variables_publish": [],
+ "modeofop_homing_method": [],
+ "modeofop_computation_mode": []
+ }
+
for blocktype_infos in FIELDBUS_INTERFACE_GLOBAL_INSTANCES:
- blocktype = blocktype_infos["blocktype"]
- ucase_blocktype = blocktype.upper()
- blockname = "_".join([ucase_blocktype, location_str])
-
- extract_inputs = "\n".join([
- """\
- __SET_VAR(%s->, %s,, %s);""" % (blockname, input_name, input_value)
- for (input_name, input_value) in
- [("EXECUTE", "__GET_VAR(data__->EXECUTE)")] + [
- (input["name"].upper(),
- "__GET_VAR(data__->%s)" % input["name"].upper())
- for input in blocktype_infos["inputs"]
- ]])
-
- return_outputs = "\n".join([
- """\
- __SET_VAR(data__->,%(output_name)s,,
- __GET_VAR(%(blockname)s->%(output_name)s));""" % {
- "output_name": output_name,
- "blockname": blockname
+ texts = {
+ "blocktype": blocktype_infos["blocktype"],
+ "ucase_blocktype": blocktype_infos["blocktype"].upper(),
+ "location": "_".join(map(str, current_location))
+ }
+ texts["blockname"] = "%(ucase_blocktype)s_%(location)s" % texts
+
+ inputs = [{"input_name": "POS", "input_value": str(self.GetSlavePos())},
+ {"input_name": "EXECUTE", "input_value": "__GET_VAR(data__->EXECUTE)"}] +\
+ [{"input_name": input["name"].upper(),
+ "input_value": "__GET_VAR(data__->%s)" % input["name"].upper()}
+ for input in blocktype_infos["inputs"]]
+ input_texts = []
+ for input_infos in inputs:
+ input_infos.update(texts)
+ input_texts.append(BLOCK_INPUT_TEMPLATE % input_infos)
+ texts["extract_inputs"] = "\n".join(input_texts)
+
+ outputs = [{"output_name": output} for output in ["DONE", "BUSY", "ERROR"]] + \
+ [{"output_name": output["name"].upper()} for output in blocktype_infos["outputs"]]
+ output_texts = []
+ for output_infos in outputs:
+ output_infos.update(texts)
+ output_texts.append(BLOCK_OUTPUT_TEMPLATE % output_infos)
+ texts["return_outputs"] = "\n".join(output_texts)
+
+ str_completion["fieldbus_interface_declaration"].append(
+ BLOCK_FUNCTION_TEMPLATE % texts)
+
+ str_completion["fieldbus_interface_definition"].append(
+ BLOCK_FUNTION_DEFINITION_TEMPLATE % texts)
+
+ variables = NODE_VARIABLES[:]
+
+#HSAHN
+#2015. 7. 24 PDO Variable
+ #if PDO is not selected, use 1st PDO set
+ self.LoadPDOSelectData()
+ if not self.SelectedRxPDOIndex and not self.SelectedTxPDOIndex :
+ self.SelectedPDOIndex = self.LoadDefaultPDOSet()
+ else :
+ self.SelectedPDOIndex = self.SelectedRxPDOIndex + self.SelectedTxPDOIndex
+
+ add_idx = []
+ for i in range(len(ADD_NODE_VARIABLES)):
+ add_idx.append(ADD_NODE_VARIABLES[i]['index'])
+
+ self.CommonMethod.RequestPDOInfo()
+ pdo_info = self.CommonMethod.GetRxPDOCategory() + self.CommonMethod.GetTxPDOCategory()
+ pdo_entry = self.CommonMethod.GetRxPDOInfo() + self.CommonMethod.GetTxPDOInfo()
+ list_index = 0
+ ModeOfOpFlag = False
+ ModeOfOpDisplayFlag = False
+ for i in range(len(pdo_info)):
+ #if pdo_index is in the SelectedPDOIndex: put the PDO mapping information intto the "used" object
+ if pdo_info[i]['pdo_index'] in self.SelectedPDOIndex:
+ used = pdo_entry[list_index:list_index + pdo_info[i]['number_of_entry']]
+ for used_data in used:
+ # 24672 -> 0x6060, Mode of Operation
+ if used_data['entry_index'] == 24672:
+ ModeOfOpFlag = True
+ # 24673 -> 0x6061, Mode of Operation Display
+ elif used_data["entry_index"] == 24673:
+ ModeOfOpDisplayFlag = True
+
+ if used_data['entry_index'] in add_idx:
+ idx = add_idx.index(used_data['entry_index'])
+ adder = list([ADD_NODE_VARIABLES[idx]['name'], ADD_NODE_VARIABLES[idx]['index'], \
+ ADD_NODE_VARIABLES[idx]['sub-index'], ADD_NODE_VARIABLES[idx]['type'], \
+ ADD_NODE_VARIABLES[idx]['direction']])
+ variables.append(adder)
+ if ADD_NODE_VARIABLES[idx]['direction'] == "Q":
+ parsed_string = ADD_NODE_VARIABLES[idx]['name'].replace("Target", "")
+ # add jblee
+ check_q_data = " *(AxsPub.Target%s) = AxsPub.axis->Raw%sSetPoint;" %(parsed_string, parsed_string)
+ if check_q_data not in str_completion["default_variables_publish"]:
+ str_completion["default_variables_publish"].append(check_q_data)
+ elif ADD_NODE_VARIABLES[idx]['direction'] == "I":
+ parsed_string = ADD_NODE_VARIABLES[idx]['name'].replace("Actual", "")
+ # add jblee
+ check_i_data = " AxsPub.axis->ActualRaw%s = *(AxsPub.Actual%s);" %(parsed_string, parsed_string)
+ if check_i_data not in str_completion["default_variables_retrieve"]:
+ str_completion["default_variables_retrieve"].append(check_i_data)
+ list_index += pdo_info[i]['number_of_entry']
+#HSAHN END
+
+ params = self.CTNParams[1].getElementInfos(self.CTNParams[0])
+ for param in params["children"]:
+ if param["name"] in EXTRA_NODE_VARIABLES_DICT:
+ if param["value"]:
+ extra_variables = EXTRA_NODE_VARIABLES_DICT.get(param["name"])
+ for variable_infos in extra_variables:
+ var_infos = {
+ "location": location_str,
+ "name": variable_infos["description"][0]
+ }
+ variables.append(variable_infos["description"])
+ retrieve_template = variable_infos.get("retrieve", DEFAULT_RETRIEVE)
+ publish_template = variable_infos.get("publish", DEFAULT_PUBLISH)
+
+ if retrieve_template is not None:
+ str_completion["extra_variables_retrieve"].append(
+ retrieve_template % var_infos)
+ if publish_template is not None:
+ str_completion["extra_variables_publish"].append(
+ publish_template % var_infos)
+
+ #elif param["value"] is not None:
+ if param["value"] is not None:
+ param_infos = {
+ "location": location_str,
+ "param_name": param["name"],
}
- for output_name in ["DONE", "BUSY", "ERROR"] + [
- output["name"].upper()
- for output in blocktype_infos["outputs"]]
- ])
-
- loc_dict = {
- "ucase_blocktype": ucase_blocktype,
- "blocktype": blocktype,
- "blockname": blockname,
- "location_str": location_str,
- "extract_inputs": extract_inputs,
- "return_outputs": return_outputs,
- }
-
- fieldbus_interface_declaration.append("""
-extern void ETHERLAB%(ucase_blocktype)s_body__(ETHERLAB%(ucase_blocktype)s* data__);
-void __%(blocktype)s_%(location_str)s(MC_%(ucase_blocktype)s *data__) {
-__DECLARE_GLOBAL_PROTOTYPE(ETHERLAB%(ucase_blocktype)s, %(blockname)s);
-ETHERLAB%(ucase_blocktype)s* %(blockname)s = __GET_GLOBAL_%(blockname)s();
-__SET_VAR(%(blockname)s->, POS,, AxsPub.axis->NetworkPosition);
-%(extract_inputs)s
-ETHERLAB%(ucase_blocktype)s_body__(%(blockname)s);
-%(return_outputs)s
-}""" % loc_dict)
-
- fieldbus_interface_definition.append("""\
- AxsPub.axis->__mcl_func_MC_%(blocktype)s = __%(blocktype)s_%(location_str)s;\
-""" % loc_dict)
-
- # Get a copy list of default variables to map
- variables = NODE_VARIABLES[:]
-
- # Set AxisRef public struct members value
- node_params = self.CTNParams[1].getElementInfos(self.CTNParams[0])
- for param in node_params["children"]:
- param_name = param["name"]
-
- # Param is optional variables section enable flag
- extra_node_variable_infos = EXTRA_NODE_VARIABLES_DICT.get(param_name)
- if extra_node_variable_infos is not None:
- param_name = param_name.replace("Enable", "") + "Enabled"
-
- if not param["value"]:
- continue
-
- # Optional variables section is enabled
- for variable_infos in extra_node_variable_infos:
- var_name = variable_infos["description"][0]
-
- # Add each variables defined in section description to the
- # list of variables to map
- variables.append(variable_infos["description"])
-
- # Add code to publish or retrive variable
- coded = [
- ("retrieve",
- extra_variables_retrieve,
- " AxsPub.axis->%(var_name)s = *(AxsPub.%(var_name)s);"),
- ("publish",
- extra_variables_publish,
- " *(AxsPub.%(var_name)s) = AxsPub.axis->%(var_name)s;")
- ]
- for var_exchange_dir, _str_list, default_template in coded:
- template = variable_infos.get(var_exchange_dir, default_template)
- if template is not None:
- extra_variables_publish.append(template % locals())
-
- # Set AxisRef public struct member value if defined
- if param["value"] is not None:
- param_value = ({True: "1", False: "0"}[param["value"]]
- if param["type"] == "boolean"
- else str(param["value"]))
-
- init_axis_params.append("""\
- AxsPub.axis->%(param_name)s = %(param_value)s;""" % {
- "param_value": param_value,
- "param_name": param_name,
- })
-
- # Add each variable in list of variables to map to master list of
- # variables to add to network configuration
- for name, index, subindex, var_type, dir in variables:
- var_size = self.GetSizeOfType(var_type)
- loc_dict = {
- "var_size": var_size,
- "var_type": var_type,
- "name:": name,
- "location_str": location_str,
- "index": index,
- "subindex": subindex,
- }
- var_name = """\
-__%(dir)s%(var_size)s%(location_str)s_%(index)d_%(subindex)d""" % loc_dict
- loc_dict["var_name"] = var_name
-
- extern_located_variables_declaration.append(
- "IEC_%(var_type)s *%(var_name)s;" % loc_dict)
- entry_variables.append(
- " IEC_%(var_type)s *%(name)s;" % loc_dict)
- init_entry_variables.append(
- " AxsPub.%(name)s = %(var_name)s;" % loc_dict)
-
+ if param["type"] == "boolean":
+ param_infos["param_value"] = {True: "1", False: "0"}[param["value"]]
+ param_infos["param_name"] = param["name"].replace("Enable", "") + "Enabled"
+ if param["value"] == False:
+ continue
+ else:
+ param_infos["param_value"] = str(param["value"])
+ # param_name = param_name.replace("Enable", "") + "Enabled"
+ str_completion["init_axis_params"].append(
+ " __CIA402Node_%(location)s.axis->%(param_name)s = %(param_value)s;" % param_infos)
+
+ check_variable = []
+ for variable in variables:
+ # add jblee
+ if variable in check_variable:
+ continue
+
+ var_infos = dict(zip(["name", "index", "subindex", "var_type", "dir"], variable))
+ var_infos["location"] = location_str
+ var_infos["var_size"] = self.GetSizeOfType(var_infos["var_type"])
+ var_infos["var_name"] = "__%(dir)s%(var_size)s%(location)s_%(index)d_%(subindex)d" % var_infos
+
+ # add jblee
+ if var_infos["index"] in [24672] and ModeOfOpFlag:
+ str_completion["modeofop_homing_method"].append(MODEOFOP_HOMING_METHOD_TEMPLATE)
+ str_completion["modeofop_computation_mode"].append(MODEOFOP_COMPUTATION_MODE_TEMPLATE)
+
+ # add jblee
+ if var_infos["index"] in [24672, 24673] and (not ModeOfOpFlag or not ModeOfOpDisplayFlag):
+ continue
+
+ str_completion["extern_located_variables_declaration"].append(
+ "IEC_%(var_type)s *%(var_name)s;" % var_infos)
+ str_completion["entry_variables"].append(
+ " IEC_%(var_type)s *%(name)s;" % var_infos)
+ str_completion["init_entry_variables"].append(
+ " __CIA402Node_%(location)s.%(name)s = %(var_name)s;" % var_infos)
+
self.CTNParent.FileGenerator.DeclareVariable(
- slave_pos, index, subindex, var_type, dir, var_name)
-
- # Add newline between string in list of generated strings for sections
- [fieldbus_interface_declaration, fieldbus_interface_definition,
- init_axis_params, extra_variables_retrieve, extra_variables_publish,
- extern_located_variables_declaration, entry_variables,
- init_entry_variables] = map("\n".join, [
- fieldbus_interface_declaration, fieldbus_interface_definition,
- init_axis_params, extra_variables_retrieve, extra_variables_publish,
- extern_located_variables_declaration, entry_variables,
- init_entry_variables])
-
- # Write generated content to CIA402 node file
- Gen_CIA402Nodefile_path = os.path.join(buildpath,
- "cia402node_%s.c" % location_str)
+ self.GetSlavePos(), var_infos["index"], var_infos["subindex"],
+ var_infos["var_type"], var_infos["dir"], var_infos["var_name"])
+
+ # add jblee
+ check_variable.append(variable)
+
+ for element in ["extern_located_variables_declaration",
+ "fieldbus_interface_declaration",
+ "fieldbus_interface_definition",
+ "entry_variables",
+ "init_axis_params",
+ "init_entry_variables",
+ "default_variables_retrieve",
+ "default_variables_publish",
+ "extra_variables_retrieve",
+ "extra_variables_publish",
+ "modeofop_homing_method",
+ "modeofop_computation_mode"]:
+ str_completion[element] = "\n".join(str_completion[element])
+
+ Gen_CIA402Nodefile_path = os.path.join(buildpath, "cia402node_%s.c"%location_str)
cia402nodefile = open(Gen_CIA402Nodefile_path, 'w')
- cia402nodefile.write(plc_cia402node_code % locals())
+ cia402nodefile.write(plc_cia402node_code % str_completion)
cia402nodefile.close()
return [(Gen_CIA402Nodefile_path, '"-I%s"' % os.path.abspath(self.GetCTRoot().GetIECLibPath()))], "", True
--- a/etherlab/EthercatMaster.py Thu Jan 28 14:50:26 2021 +0000
+++ b/etherlab/EthercatMaster.py Thu Jan 28 14:51:16 2021 +0000
@@ -243,20 +243,20 @@
config_filepath = self.ConfigFileName()
config_is_saved = False
self.Config = None
- if os.path.isfile(config_filepath):
- config_xmlfile = open(config_filepath, 'r')
- try:
- self.Config, error = \
- EtherCATConfigParser.LoadXMLString(config_xmlfile.read())
- if error is None:
- config_is_saved = True
- except Exception as e:
- error = str(e)
- config_xmlfile.close()
-
- if error is not None:
- self.GetCTRoot().logger.write_error(
- _("Couldn't load %s network configuration file.") % self.CTNName())
+ # if os.path.isfile(config_filepath):
+ # config_xmlfile = open(config_filepath, 'r')
+ # try:
+ # self.Config, error = \
+ # EtherCATConfigParser.LoadXMLString(config_xmlfile.read())
+ # if error is None:
+ # config_is_saved = True
+ # except Exception as e:
+ # error = str(e)
+ # config_xmlfile.close()
+
+ # if error is not None:
+ # self.GetCTRoot().logger.write_error(
+ # _("Couldn't load %s network configuration file.") % self.CTNName())
if self.Config is None:
self.Config = EtherCATConfigParser.CreateElement("EtherCATConfig")
@@ -282,12 +282,28 @@
if self.ProcessVariables is None:
self.ProcessVariables = ProcessVariablesParser.CreateElement("ProcessVariables")
- if config_is_saved and process_is_saved:
+ #if config_is_saved and process_is_saved:
+ if process_is_saved:
self.CreateBuffer(True)
else:
self.CreateBuffer(False)
self.OnCTNSave()
+ if os.path.isfile(config_filepath):
+ config_xmlfile = open(config_filepath, 'r')
+ try:
+ self.Config, error = \
+ EtherCATConfigParser.LoadXMLString(config_xmlfile.read())
+ if error is None:
+ config_is_saved = True
+ except Exception, e:
+ error = e.message
+ config_xmlfile.close()
+
+ if error is not None:
+ self.GetCTRoot().logger.write_error(
+ _("Couldn't load %s network configuration file.") % CTNName)
+
# ----------- call ethercat mng. function --------------
self.CommonMethod = _CommonSlave(self)
@@ -306,7 +322,7 @@
type_infos = dialog.GetValueInfos()
device, _module_extra_params = self.GetModuleInfos(type_infos)
if device is not None:
- if HAS_MCL and _EthercatCIA402SlaveCTN.NODE_PROFILE in device.GetProfileNumbers():
+ if HAS_MCL and str(_EthercatCIA402SlaveCTN.NODE_PROFILE) in device.GetProfileNumbers():
ConfNodeType = "EthercatCIA402Slave"
else:
ConfNodeType = "EthercatSlave"
@@ -577,14 +593,62 @@
return infos
return None
- def GetSlaveVariables(self, slave_pos=None, limits=None, device=None):
+ def GetSlaveVariables(self, slave_pos=None, limits=None, device=None, module=None):
+ # add jblee
+ files = os.listdir(self.CTNPath())
+ moduleNames = []
+ modulePos = 1
+ for file in files:
+ filepath = os.path.join(self.CTNPath(), file)
+ if os.path.isdir(filepath):
+ MDPFilePath = os.path.join(filepath, "DataForMDP.txt")
+ CheckConfNodePath = os.path.join(filepath, "baseconfnode.xml")
+
+ try :
+ moduleDataFile = open(MDPFilePath, 'r')
+ confNodeFile = open(CheckConfNodePath, 'r')
+
+ lines = moduleDataFile.readlines()
+ checklines = confNodeFile.readlines()
+
+ moduleDataFile.close()
+ confNodeFile.close()
+
+ module_info = self.GetModuleEntryList()
+ # checklines(ex) : <BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="0" Name="EthercatSlave_0"/>
+ # checklines[1].split() : [<BaseParams, xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ # IEC_Channel="0", Name="EthercatSlave_0"/>]
+ # checklines[1].split()[2] : IEC_Channel="0"
+ # checklines[1].split()[2].split("\"") = [IEC_Channel=, 0, ]
+ pos_check = int(checklines[1].split()[2].split("\"")[1])
+
+ if slave_pos != pos_check:
+ continue
+
+ for line in lines:
+ if line == "\n":
+ continue
+ # module_name : ST-1214, ST-2314, ...
+ # if user add module => ST-1214 3EA, ST-2314 3EA
+ # each result_module_name :
+ # (ST-1214, Module 1), (ST-1214, Module 2), (ST-1214, Module 3)
+ # (ST-2314, Module 4), (ST-2314, Module 5), (ST-2314, Module 6)
+ module_name = line.split()[0]
+ result_module_name = module_name + ", Module %d" % modulePos
+ moduleNames.append(result_module_name)
+ modulePos += 1
+ except :
+ pass
+
if device is None and slave_pos is not None:
slave = self.GetSlave(slave_pos)
if slave is not None:
type_infos = slave.getType()
device, _module_extra_params = self.GetModuleInfos(type_infos)
if device is not None:
+ # Test OD
entries = device.GetEntriesList(limits)
+ #entries = self.CTNParent.GetEntriesList()
entries_list = entries.items()
entries_list.sort()
entries = []
@@ -602,6 +666,49 @@
current_entry["children"].append(entry)
else:
entries.append(entry)
+
+ increment = self.CTNParent.GetModuleIncrement()[0]
+ count = 1
+ #print module_info
+ # moduleNameAndPos : (ST-1214, Module 1), (ST-1214, Module 2), ... ,
+ # moduleNameAndPos.split(",") : ["ST-1214", " Module 1"]
+ # moduleNameAndPos.split(",")[0] : "ST-1214"
+ for moduleNameAndPos in moduleNames:
+ moduleName = moduleNameAndPos.split(",")[0]
+ modulePosName = moduleNameAndPos.split(",")[1]
+ idx_increment = int(increment) * count
+
+ for MDP_entry in module_info.get(moduleName):
+ LocalMDPEntry = []
+ #print MDP_entry
+ local_idx = MDP_entry["Index"]
+ if ExtractHexDecValue(local_idx) == 0: #and local_idx[0] == "#":
+ temp_index = ExtractHexDecValue(local_idx)
+ else :
+ temp_index = ExtractHexDecValue(MDP_entry["Index"]) + idx_increment
+ #temp_index = ExtractHexDecValue(MDP_entry["Index"]) + idx_increment
+ entry_index = hex(temp_index)
+ entry_subidx = MDP_entry["SubIndex"]
+ entry_name = MDP_entry["Name"] + ", " + " " + \
+ moduleName + " - " + modulePosName
+ entry_type = MDP_entry["Type"]
+ entry_bitsize = MDP_entry["BitSize"]
+ entry_access = MDP_entry["Access"]
+ mapping_type = MDP_entry["PDOMapping"]
+
+ LocalMDPEntry.append({
+ "Index": entry_index,
+ "SubIndex": entry_subidx,
+ "Name": entry_name,
+ "Type": entry_type,
+ "BitSize": entry_bitsize,
+ "Access": entry_access,
+ "PDOMapping": mapping_type,
+ "children": ""})
+ entries.append(LocalMDPEntry[0])
+ count += 1
+
+ #print entries
return entries
return []
@@ -634,6 +741,10 @@
def GetModuleInfos(self, type_infos):
return self.CTNParent.GetModuleInfos(type_infos)
+ # add jblee
+ def GetModuleEntryList(self):
+ return self.CTNParent.GetModuleEntryList()
+
def GetSlaveTypesLibrary(self, profile_filter=None):
return self.CTNParent.GetModulesLibrary(profile_filter)
@@ -643,6 +754,53 @@
def GetDeviceLocationTree(self, slave_pos, current_location, device_name):
slave = self.GetSlave(slave_pos)
vars = []
+
+ # add jblee
+ files = os.listdir(self.CTNPath())
+ moduleNames = []
+ modulePos = 1
+ for file in files:
+ filepath = os.path.join(self.CTNPath(), file)
+ if os.path.isdir(filepath):
+ MDPFilePath = os.path.join(filepath, "DataForMDP.txt")
+ CheckConfNodePath = os.path.join(filepath, "baseconfnode.xml")
+
+ try :
+ moduleDataFile = open(MDPFilePath, 'r')
+ confNodeFile = open(CheckConfNodePath, 'r')
+
+ lines = moduleDataFile.readlines()
+ checklines = confNodeFile.readlines()
+
+ moduleDataFile.close()
+ confNodeFile.close()
+
+ module_info = self.GetModuleEntryList()
+ # checklines(ex) : <BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="0" Name="EthercatSlave_0"/>
+ # checklines[1].split() : [<BaseParams, xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ # IEC_Channel="0", Name="EthercatSlave_0"/>]
+ # checklines[1].split()[2] : IEC_Channel="0"
+ # checklines[1].split()[2].split("\"") = [IEC_Channel=, 0, ]
+ pos_check = int(checklines[1].split()[2].split("\"")[1])
+
+ if slave_pos != pos_check:
+ continue
+
+ for line in lines:
+ if line == "\n":
+ continue
+ # module_name : ST-1214, ST-2314, ...
+ # if user add module => ST-1214 3EA, ST-2314 3EA
+ # each result_module_name :
+ # (ST-1214, Module 1), (ST-1214, Module 2), (ST-1214, Module 3)
+ # (ST-2314, Module 4), (ST-2314, Module 5), (ST-2314, Module 6)
+ module_name = line.split()[0]
+ result_module_name = module_name + ", Module %d" % modulePos
+ moduleNames.append(result_module_name)
+ modulePos += 1
+ except :
+ pass
+
if slave is not None:
type_infos = slave.getType()
@@ -681,6 +839,39 @@
"children": [],
})
+ # add jblee for MDP
+ if not entries :
+ increment = self.CTNParent.GetModuleIncrement()[0]
+ count = 1
+ for moduleNameAndPos in moduleNames:
+ moduleName = moduleNameAndPos.split(",")[0]
+ idx_increment = int(increment) * count
+ for MDP_entry in module_info.get(moduleName):
+ local_idx = MDP_entry["Index"]
+ if ExtractHexDecValue(local_idx) != 0 and local_idx[0] == "#":
+ index = ExtractHexDecValue(local_idx) + idx_increment
+ else :
+ index = ExtractHexDecValue(MDP_entry["Index"])
+ subindex = int(MDP_entry["SubIndex"])
+ var_class = VARCLASSCONVERSION.get(MDP_entry["PDOMapping"], None)
+ if var_class is not None:
+ if var_class == LOCATION_VAR_INPUT:
+ var_dir = "%I"
+ else:
+ var_dir = "%Q"
+ var_size = self.GetSizeOfType(MDP_entry["Type"])
+ result_name = MDP_entry["Name"] + ", " + moduleNameAndPos
+ vars.append({"name": "0x%4.4x-0x%2.2x: %s" % (index, subindex, result_name),
+ "type": var_class,
+ "size": var_size,
+ "IEC_type": MDP_entry["Type"],
+ "var_name": "%s_%4.4x_%2.2x" % ("_".join(moduleName.split()), index, subindex),
+ "location": "%s%s%s"%(var_dir, var_size, ".".join(map(str, current_location +
+ (index, subindex)))),
+ "description": "",
+ "children": []})
+ count += 1
+
return vars
def CTNTestModified(self):
@@ -688,7 +879,6 @@
def OnCTNSave(self, from_project_path=None):
config_filepath = self.ConfigFileName()
-
config_xmlfile = open(config_filepath, "w")
config_xmlfile.write(etree.tostring(
self.Config,
--- a/etherlab/EthercatSlave.py Thu Jan 28 14:50:26 2021 +0000
+++ b/etherlab/EthercatSlave.py Thu Jan 28 14:51:16 2021 +0000
@@ -73,6 +73,8 @@
def __init__(self):
# ----------- call ethercat mng. function --------------
self.CommonMethod = _CommonSlave(self)
+ self.SelectedRxPDOIndex = []
+ self.SelectedTxPDOIndex = []
def GetIconName(self):
return "Slave"
--- a/etherlab/entries_list.xslt Thu Jan 28 14:50:26 2021 +0000
+++ b/etherlab/entries_list.xslt Thu Jan 28 14:51:16 2021 +0000
@@ -1,1 +1,1 @@
-<xsl:stylesheet xmlns:func="http://exslt.org/functions" xmlns:dyn="http://exslt.org/dynamic" xmlns:str="http://exslt.org/strings" xmlns:math="http://exslt.org/math" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="ns" xmlns:yml="http://fdik.org/yml" xmlns:set="http://exslt.org/sets" version="1.0" xmlns:ns="entries_list_ns" exclude-result-prefixes="ns" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:output method="xml"/><xsl:variable name="space" select="' '"/><xsl:param name="autoindent" select="4"/><xsl:param name="min_index"/><xsl:param name="max_index"/><xsl:template match="text()"><xsl:param name="_indent" select="0"/></xsl:template><xsl:template match="Device"><xsl:param name="_indent" select="0"/><xsl:apply-templates select="Profile/Dictionary/Objects/Object"><xsl:with-param name="_indent" select="$_indent + (1) * $autoindent"/></xsl:apply-templates><xsl:for-each select="RxPdo"><xsl:call-template name="pdo_entries"><xsl:with-param name="direction" select="'Receive'"/></xsl:call-template></xsl:for-each><xsl:for-each select="TxPdo"><xsl:call-template name="pdo_entries"><xsl:with-param name="direction" select="'Transmit'"/></xsl:call-template></xsl:for-each></xsl:template><xsl:template match="Object"><xsl:param name="_indent" select="0"/><xsl:variable name="index"><xsl:value-of select="ns:HexDecValue(Index/text())"/></xsl:variable><xsl:variable name="entry_name"><xsl:value-of select="ns:EntryName(Name)"/></xsl:variable><xsl:choose><xsl:when test="$index >= $min_index and $index <= $max_index"><xsl:variable name="datatype_name"><xsl:value-of select="Type/text()"/></xsl:variable><xsl:choose><xsl:when test="ancestor::Dictionary/child::DataTypes/DataType[Name/text()=$datatype_name][SubItem]"><xsl:apply-templates select="ancestor::Dictionary/child::DataTypes/DataType[Name/text()=$datatype_name][SubItem]"><xsl:with-param name="_indent" select="$_indent + (1) * $autoindent"/><xsl:with-param name="index"><xsl:value-of select="$index"/></xsl:with-param><xsl:with-param name="entry_name"><xsl:value-of select="$entry_name"/></xsl:with-param></xsl:apply-templates></xsl:when><xsl:otherwise><xsl:variable name="subindex"><xsl:text>0</xsl:text></xsl:variable><xsl:variable name="entry"><xsl:value-of select="ns:AddEntry($index, $subindex, $entry_name, $datatype_name, BitSize/text(), Flags/Access/text(), Flags/PdoMapping/text())"/></xsl:variable></xsl:otherwise></xsl:choose></xsl:when></xsl:choose></xsl:template><xsl:template match="DataType"><xsl:param name="_indent" select="0"/><xsl:param name="index"/><xsl:param name="entry_name"/><xsl:for-each select="SubItem"><xsl:variable name="subindex"><xsl:value-of select="ns:HexDecValue(SubIdx/text())"/></xsl:variable><xsl:variable name="subentry_name"><xsl:value-of select="$entry_name"/><xsl:text> - </xsl:text><xsl:value-of select="ns:EntryName(DisplayName, Name/text())"/></xsl:variable><xsl:variable name="entry"><xsl:value-of select="ns:AddEntry($index, $subindex, $subentry_name, Type/text(), BitSize/text(), Flags/Access/text(), Flags/PdoMapping/text())"/></xsl:variable></xsl:for-each></xsl:template><xsl:template name="pdo_entries"><xsl:param name="_indent" select="0"/><xsl:param name="direction"/><xsl:variable name="pdo_index"><xsl:value-of select="ns:HexDecValue(Index/text())"/></xsl:variable><xsl:variable name="pdo_name"><xsl:value-of select="ns:EntryName(Name)"/></xsl:variable><xsl:for-each select="Entry"><xsl:variable name="index"><xsl:value-of select="ns:HexDecValue(Index/text())"/></xsl:variable><xsl:choose><xsl:when test="$index >= $min_index and $index <= $max_index"><xsl:variable name="subindex"><xsl:value-of select="ns:HexDecValue(SubIndex/text())"/></xsl:variable><xsl:variable name="subentry_name"><xsl:value-of select="ns:EntryName(Name)"/></xsl:variable><xsl:variable name="access"><xsl:choose><xsl:when test="$direction='Transmit'"><xsl:text>ro</xsl:text></xsl:when><xsl:otherwise><xsl:text>wo</xsl:text></xsl:otherwise></xsl:choose></xsl:variable><xsl:variable name="pdo_mapping"><xsl:choose><xsl:when test="$direction='Transmit'"><xsl:text>T</xsl:text></xsl:when><xsl:otherwise><xsl:text>R</xsl:text></xsl:otherwise></xsl:choose></xsl:variable><xsl:variable name="entry"><xsl:value-of select="ns:AddEntry($index, $subindex, $subentry_name, DataType/text(), BitLen/text(), $access, $pdo_mapping, $pdo_index, $pdo_name, $direction)"/></xsl:variable></xsl:when></xsl:choose></xsl:for-each></xsl:template></xsl:stylesheet>
\ No newline at end of file
+<xsl:stylesheet xmlns:func="http://exslt.org/functions" xmlns:dyn="http://exslt.org/dynamic" xmlns:str="http://exslt.org/strings" xmlns:math="http://exslt.org/math" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="ns" xmlns:yml="http://fdik.org/yml" xmlns:set="http://exslt.org/sets" version="1.0" xmlns:ns="entries_list_ns" exclude-result-prefixes="ns" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:output method="xml"/><xsl:variable name="space" select="' '"/><xsl:param name="autoindent" select="4"/><xsl:param name="min_index"/><xsl:param name="max_index"/><xsl:template match="text()"><xsl:param name="_indent" select="0"/></xsl:template><xsl:template match="Device"><xsl:param name="_indent" select="0"/><xsl:apply-templates select="Profile/Dictionary/Objects/Object"><xsl:with-param name="_indent" select="$_indent + (1) * $autoindent"/></xsl:apply-templates><xsl:for-each select="RxPdo"><xsl:call-template name="pdo_entries"><xsl:with-param name="direction" select="'Receive'"/></xsl:call-template></xsl:for-each><xsl:for-each select="TxPdo"><xsl:call-template name="pdo_entries"><xsl:with-param name="direction" select="'Transmit'"/></xsl:call-template></xsl:for-each></xsl:template><xsl:template match="Object"><xsl:param name="_indent" select="0"/><xsl:variable name="index"><xsl:value-of select="ns:HexDecValue(Index/text())"/></xsl:variable><xsl:variable name="entry_name"><xsl:value-of select="ns:EntryName(Name)"/></xsl:variable><xsl:choose><xsl:when test="$index >= $min_index and $index <= $max_index"><xsl:variable name="datatype_name"><xsl:value-of select="Type/text()"/></xsl:variable><xsl:variable name="default_value"><xsl:value-of select="Info/DefaultData/text()"/></xsl:variable><xsl:choose><xsl:when test="ancestor::Dictionary/child::DataTypes/DataType[Name/text()=$datatype_name][SubItem]"><xsl:apply-templates select="ancestor::Dictionary/child::DataTypes/DataType[Name/text()=$datatype_name][SubItem]"><xsl:with-param name="_indent" select="$_indent + (1) * $autoindent"/><xsl:with-param name="index"><xsl:value-of select="$index"/></xsl:with-param><xsl:with-param name="entry_name"><xsl:value-of select="$entry_name"/></xsl:with-param><xsl:with-param name="sub_default_value"><xsl:value-of select="Info"/></xsl:with-param></xsl:apply-templates></xsl:when><xsl:otherwise><xsl:variable name="subindex"><xsl:text>0</xsl:text></xsl:variable><xsl:variable name="sub_entry_flag"><xsl:text>0</xsl:text></xsl:variable><xsl:variable name="entry"><xsl:value-of select="ns:AddEntry($index, $subindex, $entry_name, $datatype_name, BitSize/text(), Flags/Access/text(), Flags/PdoMapping/text(), $default_value, $sub_entry_flag)"/></xsl:variable></xsl:otherwise></xsl:choose></xsl:when></xsl:choose></xsl:template><xsl:template match="DataType"><xsl:param name="_indent" select="0"/><xsl:param name="index"/><xsl:param name="entry_name"/><xsl:param name="sub_default_value"/><xsl:for-each select="SubItem"><xsl:variable name="subindex"><xsl:value-of select="ns:HexDecValue(SubIdx/text())"/></xsl:variable><xsl:variable name="subentry_name"><xsl:value-of select="$entry_name"/><xsl:text> - </xsl:text><xsl:value-of select="ns:EntryName(DisplayName, Name/text())"/></xsl:variable><xsl:variable name="sub_entry_flag"><xsl:text>1</xsl:text></xsl:variable><xsl:variable name="entry"><xsl:value-of select="ns:AddEntry($index, $subindex, $subentry_name, Type/text(), BitSize/text(), Flags/Access/text(), Flags/PdoMapping/text(), $sub_default_value, $sub_entry_flag)"/></xsl:variable></xsl:for-each></xsl:template><xsl:template name="pdo_entries"><xsl:param name="_indent" select="0"/><xsl:param name="direction"/><xsl:variable name="pdo_index"><xsl:value-of select="ns:HexDecValue(Index/text())"/></xsl:variable><xsl:variable name="pdo_name"><xsl:value-of select="ns:EntryName(Name)"/></xsl:variable><xsl:for-each select="Entry"><xsl:variable name="index"><xsl:value-of select="ns:HexDecValue(Index/text())"/></xsl:variable><xsl:choose><xsl:when test="$index >= $min_index and $index <= $max_index"><xsl:variable name="subindex"><xsl:value-of select="ns:HexDecValue(SubIndex/text())"/></xsl:variable><xsl:variable name="subentry_name"><xsl:value-of select="ns:EntryName(Name)"/></xsl:variable><xsl:variable name="access"><xsl:choose><xsl:when test="$direction='Transmit'"><xsl:text>ro</xsl:text></xsl:when><xsl:otherwise><xsl:text>wo</xsl:text></xsl:otherwise></xsl:choose></xsl:variable><xsl:variable name="pdo_mapping"><xsl:choose><xsl:when test="$direction='Transmit'"><xsl:text>T</xsl:text></xsl:when><xsl:otherwise><xsl:text>R</xsl:text></xsl:otherwise></xsl:choose></xsl:variable><xsl:variable name="entry"><xsl:value-of select="ns:AddEntry($index, $subindex, $subentry_name, DataType/text(), BitLen/text(), $access, $pdo_mapping, $pdo_index, $pdo_name, $direction)"/></xsl:variable></xsl:when></xsl:choose></xsl:for-each></xsl:template></xsl:stylesheet>
\ No newline at end of file
--- a/etherlab/entries_list.ysl2 Thu Jan 28 14:50:26 2021 +0000
+++ b/etherlab/entries_list.ysl2 Thu Jan 28 14:51:16 2021 +0000
@@ -38,17 +38,20 @@
choose {
when "$index >= $min_index and $index <= $max_index" {
variable "datatype_name" > «Type/text()»
+ variable "default_value" > «Info/DefaultData/text()»
choose {
when "ancestor::Dictionary/child::DataTypes/DataType[Name/text()=$datatype_name][SubItem]" {
apply "ancestor::Dictionary/child::DataTypes/DataType[Name/text()=$datatype_name][SubItem]" {
with "index" > «$index»
with "entry_name" > «$entry_name»
+ with "sub_default_value" > «Info/SubItem/Info/DefaultData/text()»
}
}
otherwise {
variable "subindex" > 0
+ variable "sub_entry_flag" > 0
variable "entry" {
- > «ns:AddEntry($index, $subindex, $entry_name, $datatype_name, BitSize/text(), Flags/Access/text(), Flags/PdoMapping/text())»
+ > «ns:AddEntry($index, $subindex, $entry_name, $datatype_name, BitSize/text(), Flags/Access/text(), Flags/PdoMapping/text(), $default_value, $sub_entry_flag)»
}
}
}
@@ -59,11 +62,13 @@
template "DataType" {
param "index";
param "entry_name";
+ param "sub_default_value"
foreach "SubItem" {
variable "subindex" > «ns:HexDecValue(SubIdx/text())»
variable "subentry_name" > «$entry_name» - «ns:EntryName(DisplayName, Name/text())»
+ variable "sub_entry_flag" > 1
variable "entry" {
- > «ns:AddEntry($index, $subindex, $subentry_name, Type/text(), BitSize/text(), Flags/Access/text(), Flags/PdoMapping/text())»
+ > «ns:AddEntry($index, $subindex, $subentry_name, Type/text(), BitSize/text(), Flags/Access/text(), Flags/PdoMapping/text(), $sub_default_value, $sub_entry_flag)»
}
}
}
--- a/etherlab/etherlab.py Thu Jan 28 14:50:26 2021 +0000
+++ b/etherlab/etherlab.py Thu Jan 28 14:51:16 2021 +0000
@@ -39,6 +39,13 @@
return etree.XPath(xpath)
+EtherCATBaseParser = GenerateParserFromXSD(os.path.join(os.path.dirname(__file__), "EtherCATBase.xsd"))
+
+
+def EtherCATBase_XPath(xpath):
+ return etree.XPath(xpath)
+
+
def HexDecValue(context, *args):
return str(ExtractHexDecValue(args[0][0]))
@@ -59,6 +66,18 @@
("PDO name", str, ""),
("PDO type", str, "")]
+# Read DefaultValue from ESI file
+# Add by jblee 151229
+ENTRY_INFOS_KEYS_FOR_DV = [
+ ("Index", lambda x: "#x%4.4X" % int(x), "#x0000"),
+ ("SubIndex", str, "0"),
+ ("Name", str, ""),
+ ("Type", str, ""),
+ ("BitSize", int, 0),
+ ("Access", str, ""),
+ ("PDOMapping", str, ""),
+ ("DefaultValue", str, ""),
+ ("Sub_entry_flag", str, "0")]
class EntryListFactory(object):
@@ -67,11 +86,17 @@
def AddEntry(self, context, *args):
index, subindex = map(lambda x: int(x[0]), args[:2])
- new_entry_infos = {
+ if len(args) > 9:
+ new_entry_infos = {
key: translate(arg[0]) if len(arg) > 0 else default
for (key, translate, default), arg
in zip(ENTRY_INFOS_KEYS, args)}
-
+ else:
+ new_entry_infos = {
+ key: translate(arg[0]) if len(arg) > 0 else default
+ for (key, translate, default), arg
+ in zip(ENTRY_INFOS_KEYS_FOR_DV, args)}
+
if (index, subindex) != (0, 0):
entry_infos = self.Entries.get((index, subindex))
if entry_infos is not None:
@@ -88,7 +113,8 @@
cls = EtherCATInfoParser.GetElementClass("DeviceType")
if cls:
-
+ cls.DataTypes = None
+
profile_numbers_xpath = EtherCATInfo_XPath("Profile/ProfileNo")
def GetProfileNumbers(self):
@@ -102,25 +128,151 @@
return None
setattr(cls, "getCoE", getCoE)
+ # Modify by jblee
+ def ExtractDataTypes(self):
+ #self.DataTypes = {}
+ #self.DT = {}
+ DT = {}
+ objects = []
+
+ # get Profile Field
+ for profile in self.getProfile():
+ # get each (ProfileNo, Dictionary) Field as child
+ for child in profile.getchildren():
+ # child.text is not None -> ProfileNo, is None -> Dictionary
+ if child.text is None:
+ # get each (DataTypes, Objects) Field
+ dataTypes = child.getDataTypes()
+ objects = child.getObjects()
+
+ for dataType in dataTypes.getDataType():
+ #if dataType.getName() is not None:
+ # print dataType.getName(), dataType
+ DT[dataType.getName()] = dataType
+
+ return DT, objects
+ setattr(cls, "ExtractDataTypes", ExtractDataTypes)
+
def GetEntriesList(self, limits=None):
+ DataTypes, objects = self.ExtractDataTypes()
+
entries = {}
- factory = EntryListFactory(entries)
-
- entries_list_xslt_tree = etree.XSLT(
- entries_list_xslt, extensions={
- ("entries_list_ns", "AddEntry"): factory.AddEntry,
- ("entries_list_ns", "HexDecValue"): HexDecValue,
- ("entries_list_ns", "EntryName"): EntryName})
- entries_list_xslt_tree(self, **dict(zip(
- ["min_index", "max_index"],
- map(lambda x: etree.XSLT.strparam(str(x)),
- limits if limits is not None else [0x0000, 0xFFFF])
- )))
-
+ # get each Object Field
+ for object in objects:
+ # Object Field mendatory : Index, Name, Type, BitSize
+ # Frequently Use : Info, Flags
+ # Info Field -> DefaultData, SubItem
+ # Flags Field -> Access, Category, PdoMapping
+ object_index = object.getIndex().getcontent()
+ index = ExtractHexDecValue(object_index)
+ if limits is None or limits[0] <= index <= limits[1]:
+ object_type = object.getType()
+ object_name = ExtractName(object.getName())
+ object_size = object.getBitSize()
+ defaultData = ""
+ object_access = ""
+ object_PDOMapping_data = ""
+
+ object_type_infos = DataTypes.get(object_type, None)
+ subItem_infos = object_type_infos.getchildren()
+ countSubIndex = 0
+ if len(subItem_infos) > 2:
+ for subItem_info in subItem_infos:
+ if subItem_info.tag == "SubItem" :
+ subItemName = subItem_info.getName()
+ subIdx = subItem_info.getSubIdx()
+ if subIdx is not None:
+ object_subidx = ExtractHexDecValue(subIdx)
+ else:
+ object_subidx = ExtractHexDecValue(countSubIndex)
+ subType = subItem_info.getType()
+ subBitSize = subItem_info.getBitSize()
+ subFlags = subItem_info.getFlags()
+ subAccess = ""
+ subPDOMapping_data = ""
+ if subFlags is not None:
+ subAccess = subFlags.getAccess().getcontent()
+ subPDOMapping = subFlags.getPdoMapping()
+ if subPDOMapping is not None:
+ subPDOMapping_data = subFlags.getPdoMapping().upper()
+
+ entries[(index, object_subidx)] = {
+ "Index": object_index,
+ "SubIndex": subIdx,
+ "Name": "%s - %s" %
+ (object_name.decode("utf-8"),
+ subItemName.decode("utf-8")),
+ "Type": subType,
+ "BitSize": subBitSize,
+ "Access": subAccess,
+ "PDOMapping": subPDOMapping_data}
+
+ countSubIndex += 1
+
+ info = object.getInfo()
+ # subItemTest : check subItem
+ countSubIndex = 0
+ if info is not None:
+ subItems = info.getchildren()
+ if len(subItems) > 1:
+ for subItem in subItems:
+ defaultdata_subidx = ExtractHexDecValue(countSubIndex)
+ defaultData = subItem.getchildren()[1].findtext("DefaultData")
+ entry = entries.get((index, defaultdata_subidx), None)
+ if entry is not None:
+ entry["DefaultData"] = defaultData
+ countSubIndex += 1
+
+ else :
+ info = object.getInfo()
+ if info is not None:
+ subItems = info.getchildren()
+ if len(subItems) <= 1:
+ defaultData = subItems[0].text
+
+ object_flag = object.getFlags()
+ object_access = object_flag.getAccess().getcontent()
+ object_PDOMapping = object_flag.getPdoMapping()
+ if object_PDOMapping is not None:
+ object_PDOMapping_data = object_flag.getPdoMapping().upper()
+ entries[(index, 0)] = {
+ "Index": object_index,
+ "SubIndex": "0",
+ "Name": object_name,
+ "Type": object_type,
+ "BitSize": object_size,
+ "DefaultData" : defaultData,
+ "Access": object_access,
+ "PDOMapping": object_PDOMapping_data}
+
+ for TxPdo in self.getTxPdo():
+ ExtractPdoInfos(TxPdo, "Transmit", entries, limits)
+ for RxPdo in self.getRxPdo():
+ ExtractPdoInfos(RxPdo, "Receive", entries, limits)
+
return entries
setattr(cls, "GetEntriesList", GetEntriesList)
+# def GetEntriesList(self, limits=None):
+# entries = {}
+
+# factory = EntryListFactory(entries)
+
+# entries_list_xslt_tree = etree.XSLT(
+# entries_list_xslt, extensions = {
+# ("entries_list_ns", "AddEntry"): factory.AddEntry,
+# ("entries_list_ns", "HexDecValue"): HexDecValue,
+# ("entries_list_ns", "EntryName"): EntryName})
+# entries_list_xslt_tree(self, **dict(zip(
+# ["min_index", "max_index"],
+# map(lambda x: etree.XSLT.strparam(str(x)),
+# limits if limits is not None else [0x0000, 0xFFFF])
+# )))
+#
+# return entries
+# setattr(cls, "GetEntriesList", GetEntriesList)
+
def GetSyncManagers(self):
sync_managers = []
for sync_manager in self.getSm():
@@ -155,6 +307,40 @@
SortGroupItems(item)
group["children"].sort(GroupItemCompare)
+def ExtractPdoInfos(pdo, pdo_type, entries, limits=None):
+ pdo_index = pdo.getIndex().getcontent()
+ pdo_name = ExtractName(pdo.getName())
+ exclude = pdo.getExclude()
+ for pdo_entry in pdo.getEntry():
+ entry_index = pdo_entry.getIndex().getcontent()
+ entry_subindex = pdo_entry.getSubIndex()
+ index = ExtractHexDecValue(entry_index)
+ subindex = ExtractHexDecValue(entry_subindex)
+ object_size = pdo_entry.getBitLen()
+
+ if limits is None or limits[0] <= index <= limits[1]:
+ entry = entries.get((index, subindex), None)
+ if entry is not None:
+ entry["PDO index"] = pdo_index
+ entry["PDO name"] = pdo_name
+ entry["PDO type"] = pdo_type
+ else:
+ entry_type = pdo_entry.getDataType()
+ if entry_type is not None:
+ if pdo_type == "Transmit":
+ access = "ro"
+ pdomapping = "T"
+ else:
+ access = "wo"
+ pdomapping = "R"
+ entries[(index, subindex)] = {
+ "Index": entry_index,
+ "SubIndex": entry_subindex,
+ "Name": ExtractName(pdo_entry.getName()),
+ "Type": entry_type.getcontent(),
+ "BitSize": object_size,
+ "Access": access,
+ "PDOMapping": pdomapping}
class ModulesLibrary(object):
@@ -211,10 +397,21 @@
groups_xpath = EtherCATInfo_XPath("Descriptions/Groups/Group")
devices_xpath = EtherCATInfo_XPath("Descriptions/Devices/Device")
+ module_xpath = EtherCATBase_XPath("Descriptions/Modules/Module")
def LoadModules(self):
self.Library = {}
-
+ # add by jblee for Modular Device Profile
+ self.MDPList = []
+ self.ModuleList = []
+ self.MDPEntryList = {}
+ dtDic = {}
+ self.idxIncrement = 0
+ self.slotIncrement = 0
+ # add by jblee for PDO Mapping
+ self.DataTypes = {}
+ self.ObjectDictionary = {}
+
files = os.listdir(self.Path)
for file in files:
filepath = os.path.join(self.Path, file)
@@ -224,9 +421,9 @@
xmlfile = open(filepath, 'r')
try:
self.modules_infos, error = EtherCATInfoParser.LoadXMLString(xmlfile.read())
- if error is not None:
- self.GetCTRoot().logger.write_warning(
- XSDSchemaErrorMessage % (filepath + error))
+ # if error is not None:
+ # self.GetCTRoot().logger.write_warning(
+ # XSDSchemaErrorMessage % (filepath + error))
except Exception as exc:
self.modules_infos, error = None, text(exc)
xmlfile.close()
@@ -241,6 +438,9 @@
for group in self.groups_xpath(self.modules_infos):
group_type = group.getType()
+ # add for XmlToEeprom Func by jblee.
+ self.LcId_data = group.getchildren()[1]
+ self.Image16x14_data = group.getchildren()[2]
vendor_category["groups"].setdefault(
group_type,
@@ -248,8 +448,9 @@
"name": ExtractName(group.getName(), group_type),
"parent": group.getParentGroup(),
"order": group.getSortOrder(),
- # "value": group.getcontent()["value"],
"devices": [],
+ # add jblee for support Moduler Device Profile (MDP)
+ "modules": []})
})
for device in self.devices_xpath(self.modules_infos):
@@ -259,13 +460,98 @@
vendor_category["groups"][device_group]["devices"].append(
(device.getType().getcontent(), device))
- else:
-
- self.GetCTRoot().logger.write_error(
- _("Couldn't load {a1} XML file:\n{a2}").format(a1=filepath, a2=error))
+ # ------------------ Test Section --------------------#
+ slots = device.getSlots()
+ if slots is not None:
+ for slot in slots.getSlot():
+ self.idxIncrement = slot.getSlotIndexIncrement()
+ self.slotIncrement = slot.getSlotPdoIncrement()
+ for child in slot.getchildren():
+ if child.tag == "ModuleClass":
+ child_class = child.getClass()
+ child_name = child.getName()
+
+ # -------------------- Test Section ----------------------------------#
+ LocalMDPList = []
+ for module in self.module_xpath(self.modules_infos):
+ module_type = module.getType().getModuleClass()
+ module_name = module.getName()
+ LocalMDPData = ExtractName(module_name) + " (" + module_type + ")"
+
+ self.ModuleList.append(module)
+ try :
+ module_pdos = module.getTxPdo()
+ module_pdos += module.getRxPdo()
+ for module_pdo in module_pdos:
+ device_name = ExtractName(module_name)
+ pdo_index = module_pdo.getIndex().getcontent()
+ pdo_name = ExtractName(module_pdo.getName())
+ pdo_entry = module_pdo.getEntry()
+ if module_pdo.tag == "TxPdo":
+ mapping_type = "T"
+ else :
+ mapping_type = "R"
+
+ LocalMDPEntry = []
+ for entry in pdo_entry:
+ entry_index = entry.getIndex().getcontent()
+ entry_subidx = entry.getSubIndex()
+ entry_name = ExtractName(entry.getName())
+ entry_bitsize = entry.getBitLen()
+ try :
+ entry_type = entry.getDataType().getcontent()
+ except :
+ entry_type = ""
+
+ LocalMDPEntry.append({
+ "Index": entry_index,
+ "SubIndex": entry_subidx,
+ "Name": "%s - %s" % (pdo_name, entry_name),
+ "Type": entry_type,
+ "BitSize": entry_bitsize,
+ "Access": "",
+ "PDOMapping": mapping_type})
+
+ self.MDPEntryList[device_name] = LocalMDPEntry
+
+ LocalMDPList.append([LocalMDPData, module, LocalMDPEntry])
+ except :
+ LocalMDPList.append([LocalMDPData, module, []])
+
+ if LocalMDPList:
+ vendor_category["groups"][device_group]["modules"].append(
+ (device.getType().getcontent(), LocalMDPList, self.idxIncrement, self.slotIncrement))
+ #self.MDPList.append([device.getType().getcontent(), LocalMDPList,
+ # self.idxIncrement, self.slotIncrement])
+
+ # --------------------------------------------------------------------- #
+
+ # else:
+ # self.GetCTRoot().logger.write_error(
+ # _("Couldn't load {a1} XML file:\n{a2}").format(a1=filepath, a2=error))
return self.Library
+ # add jblee
+ def GetMDPList(self):
+ return self.MDPList
+
+ # add jblee
+ def GetSelectModule(self, idx):
+ return self.ModuleList[idx]
+
+ # add jblee
+ def GetModuleEntryList(self):
+ return self.MDPEntryList
+
+ # add jblee
+ def GetModuleIncrement(self):
+ return (self.idxIncrement, self.slotIncrement)
+
+ # add jblee
+ #def GetEntriesList(self):
+ # return self.ObjectDictionary
+
def GetModulesLibrary(self, profile_filter=None):
if self.Library is None:
self.LoadModules()
@@ -332,6 +618,18 @@
return device_infos, self.GetModuleExtraParams(vendor, product_code, revision_number)
return None, None
+ # add jblee for MDP
+ def GetMDPInfos(self, module_infos):
+ vendor = ExtractHexDecValue(module_infos["vendor"])
+ vendor_infos = self.Library.get(vendor)
+ if vendor_infos is not None:
+ for group_name, group_infos in vendor_infos["groups"].iteritems():
+ return group_infos["modules"]
+ #for device_type, module_list, idx_inc, slot_inc in group_infos["modules"]:
+ # return module_list, idx_inc, slot_inc
+
+ #return None, None, None
+
def ImportModuleLibrary(self, filepath):
if os.path.isfile(filepath):
shutil.copy(filepath, self.Path)
@@ -452,8 +750,31 @@
def GetModulesLibrary(self, profile_filter=None):
return self.ModulesLibrary.GetModulesLibrary(profile_filter)
+ # add jblee
+ def GetMDPList(self):
+ return self.ModulesLibrary.GetMDPList()
+
+ # add jblee
+ def GetSelectModule(self, idx):
+ return self.ModulesLibrary.GetSelectModule(idx)
+
+ # add jblee
+ def GetModuleEntryList(self):
+ return self.ModulesLibrary.GetModuleEntryList()
+
+ # add jblee
+ def GetModuleIncrement(self):
+ return self.ModulesLibrary.GetModuleIncrement()
+
+ # add jblee
+ #def GetEntriesList(self, limits = None):
+ # return self.ModulesLibrary.GetEntriesList()
+
def GetVendors(self):
return self.ModulesLibrary.GetVendors()
def GetModuleInfos(self, module_infos):
return self.ModulesLibrary.GetModuleInfos(module_infos)
+
+ def GetMDPInfos(self, module_infos):
+ return self.ModulesLibrary.GetMDPInfos(module_infos)
--- a/etherlab/plc_cia402node.c Thu Jan 28 14:50:26 2021 +0000
+++ b/etherlab/plc_cia402node.c Thu Jan 28 14:51:16 2021 +0000
@@ -3,6 +3,8 @@
Template C code used to produce target Ethercat C CIA402 code
Copyright (C) 2011-2014: Laurent BESSARD, Edouard TISSERANT
+ RTES Lab : CRKim, JBLee, youcu
+ Higen Motor : Donggu Kang
Distributed under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2 of the License, or
@@ -33,7 +35,25 @@
xxxx xxxx x0xx 1111 | Fault reaction active
xxxx xxxx x0xx 1000 | Fault
*/
+
+//ssh_add
+/* From CiA402, Page 63 Statusword for homing mode
+
+ Table 106 - Definition of bit 10, bit 12, bit 13
+
+xx00 x0xx xxxx xxxx | Homing procedure is in progress
+xx00 x1xx xxxx xxxx | Homing procedure is interrupted or not started
+xx01 x0xx xxxx xxxx | Homing is attained, but target is not reached
+xx01 x1xx xxxx xxxx | Homing procedure is completed successfully
+xx10 x0xx xxxx xxxx | Homing error occurred, velocity is not 0
+xx10 x1xx xxxx xxxx | Homing error occurred, velocity is 0
+xx11 xxxx xxxx xxxx | reserved
+*/
+
#define FSAFromStatusWord(SW) (SW & 0x006f)
+//ssh_add
+#define HomingStatusWord(SW) (SW & 0x3400)
+#define FaultFromStatusWord(SW) (SW & 0x0008)
#define NotReadyToSwitchOn 0b00000000 FSA_sep 0b00100000
#define SwitchOnDisabled 0b01000000 FSA_sep 0b01100000
#define ReadyToSwitchOn 0b00100001
@@ -43,7 +63,16 @@
#define FaultReactionActive 0b00001111 FSA_sep 0b00101111
#define Fault 0b00001000 FSA_sep 0b00101000
-// SatusWord bits :
+//ssh_add
+#define HomingInProgress 0b0000000000000000
+#define HomingNotRunning 0b0000010000000000
+#define HomingNotReached 0b0001000000000000
+#define Homing_Completed 0b0001010000000000
+#define HomingErrorInVelo 0b0010000000000000
+#define HomingErrorNotVelo 0b0010010000000000
+#define HomingReserved 0b0011000000000000 FSA_sep 0b0011010000000000
+
+// StatusWord bits :
#define SW_ReadyToSwitchOn 0x0001
#define SW_SwitchedOn 0x0002
#define SW_OperationEnabled 0x0004
@@ -56,6 +85,10 @@
#define SW_TargetReached 0x0400
#define SW_InternalLimitActive 0x0800
+//ssh_add
+#define SW_HomingAttained 0x1000
+#define SW_HomingError 0x2000
+
// ControlWord bits :
#define SwitchOn 0x0001
#define EnableVoltage 0x0002
@@ -64,11 +97,15 @@
#define FaultReset 0x0080
#define Halt 0x0100
-
-IEC_INT beremiz__IW%(location_str)s = %(slave_pos)s;
-IEC_INT *__IW%(location_str)s = &beremiz__IW%(location_str)s;
-IEC_INT beremiz__IW%(location_str)s_402;
-IEC_INT *__IW%(location_str)s_402 = &beremiz__IW%(location_str)s_402;
+//ssh_add
+//#define Homing_OperationStart 0x0010
+#define Homing_OperationStart_Origin 0x0010
+#define Homing_OperationStart_Edit 0x001F
+
+IEC_INT beremiz__IW%(location)s = %(slave_pos)s;
+IEC_INT *__IW%(location)s = &beremiz__IW%(location)s;
+IEC_INT beremiz__IW%(location)s_402;
+IEC_INT *__IW%(location)s_402 = &beremiz__IW%(location)s_402;
%(MCL_headers)s
@@ -104,7 +141,7 @@
axis_s* axis;
} __CIA402Node;
-#define AxsPub __CIA402Node_%(location_str)s
+#define AxsPub __CIA402Node_%(location)s
static __CIA402Node AxsPub;
@@ -112,25 +149,24 @@
%(fieldbus_interface_declaration)s
-int __init_%(location_str)s()
+int __init_%(location)s()
{
__FirstTick = 1;
%(init_entry_variables)s
- *(AxsPub.ModesOfOperation) = 0x08;
return 0;
}
-void __cleanup_%(location_str)s()
-{
-}
-
-void __retrieve_%(location_str)s()
+void __cleanup_%(location)s()
+{
+}
+
+void __retrieve_%(location)s()
{
if (__FirstTick) {
- *__IW%(location_str)s_402 = __MK_Alloc_AXIS_REF();
+ *__IW%(location)s_402 = __MK_Alloc_AXIS_REF();
AxsPub.axis =
- __MK_GetPublic_AXIS_REF(*__IW%(location_str)s_402);
- AxsPub.axis->NetworkPosition = beremiz__IW%(location_str)s;
+ __MK_GetPublic_AXIS_REF(*__IW%(location)s_402);
+ AxsPub.axis->NetworkPosition = beremiz__IW%(location)s;
%(init_axis_params)s
%(fieldbus_interface_definition)s
__FirstTick = 0;
@@ -146,15 +182,13 @@
AxsPub.axis->PowerFeedback = FSA == OperationEnabled;
}
#undef FSA_sep
- AxsPub.axis->ActualRawPosition = *(AxsPub.ActualPosition);
- AxsPub.axis->ActualRawVelocity = *(AxsPub.ActualVelocity);
- AxsPub.axis->ActualRawTorque = *(AxsPub.ActualTorque);
+%(default_variables_retrieve)s
// Extra variables retrieve
%(extra_variables_retrieve)s
}
-void __publish_%(location_str)s()
+void __publish_%(location)s()
{
IEC_BOOL power =
((*(AxsPub.StatusWord) & SW_VoltageEnabled) != 0)
@@ -181,37 +215,61 @@
CW |= SwitchOn | EnableVoltage | QuickStop | EnableOperation;
}
break;
- case Fault :
- /* TODO reset fault only when MC_Reset */
- CW &= ~(SwitchOn | EnableVoltage | QuickStop | EnableOperation);
- CW |= FaultReset;
- break;
+ //ssh_check
+// case Fault :
+// /* TODO reset fault only when MC_Reset */
+// AxsPub.axis->DriveFault = 1;
+// CW &= ~(SwitchOn | EnableVoltage | QuickStop | EnableOperation);
+// CW |= FaultReset;
+// break;
default:
break;
}
+ //ssh_add
+ if(FaultFromStatusWord(*(AxsPub.StatusWord)) == SW_Fault)
+ AxsPub.axis->DriveFault = 1;
+ else{
+ AxsPub.axis->DriveFault = 0;
+ AxsPub.axis->DriveFaultReset = 0;
+ }
+ if(AxsPub.axis->DriveFaultReset){
+ CW &= ~(SwitchOn | EnableVoltage | QuickStop | EnableOperation);
+ CW |= FaultReset;
+ }
+
+ //ssh_add
+ switch (HomingStatusWord(*(AxsPub.StatusWord))) {
+ case HomingInProgress:
+ break;
+ case HomingNotRunning:
+ break;
+ case HomingNotReached:
+ break;
+ case Homing_Completed:
+ if(!AxsPub.axis->HomingCompleted)
+ AxsPub.axis->HomingCompleted = 1;
+ break;
+ case HomingErrorInVelo:
+ case HomingErrorNotVelo:
+ if(!AxsPub.axis->HomingCompleted)
+ AxsPub.axis->HomingCompleted = 1;
+ break;
+ case HomingReserved:
+ break;
+ }
#undef FSA_sep
- *(AxsPub.ControlWord) = CW;
+
+ //ssh_add
+%(modeofop_homing_method)s
+
+ *(AxsPub.ControlWord) = CW;
+
// CIA402 node modes of operation computation according to axis motion mode
- switch (AxsPub.axis->AxisMotionMode) {
- case mc_mode_cst:
- *(AxsPub.ModesOfOperation) = 0x0a;
- break;
- case mc_mode_csv:
- *(AxsPub.ModesOfOperation) = 0x09;
- break;
- default:
- *(AxsPub.ModesOfOperation) = 0x08;
- break;
- }
+%(modeofop_computation_mode)s
// Default variables publish
- *(AxsPub.TargetPosition) =
- AxsPub.axis->RawPositionSetPoint;
- *(AxsPub.TargetVelocity) =
- AxsPub.axis->RawVelocitySetPoint;
- *(AxsPub.TargetTorque) =
- AxsPub.axis->RawTorqueSetPoint;
+%(default_variables_publish)s
// Extra variables publish
%(extra_variables_publish)s
--- a/etherlab/plc_etherlab.c Thu Jan 28 14:50:26 2021 +0000
+++ b/etherlab/plc_etherlab.c Thu Jan 28 14:51:16 2021 +0000
@@ -32,6 +32,43 @@
%(used_pdo_entry_configuration)s
{}
};
+
+// Distributed Clock variables;
+%(dc_variable)s
+unsigned long long comp_period_ns = 500000ULL;
+
+int comp_count = 1;
+int comp_count_max;
+
+#define DC_FILTER_CNT 1024
+
+// EtherCAT slave-time-based DC Synchronization variables.
+static uint64_t dc_start_time_ns = 0LL;
+static uint64_t dc_time_ns = 0;
+static uint8_t dc_started = 0;
+static int32_t dc_diff_ns = 0;
+static int32_t prev_dc_diff_ns = 0;
+static int64_t dc_diff_total_ns = 0LL;
+static int64_t dc_delta_total_ns = 0LL;
+static int dc_filter_idx = 0;
+static int64_t dc_adjust_ns;
+static int64_t system_time_base = 0LL;
+
+static uint64_t dc_first_app_time = 0LL;
+
+unsigned long long frame_period_ns = 0ULL;
+
+int debug_count = 0;
+int slave_dc_used = 0;
+
+void dc_init(void);
+uint64_t system_time_ns(void);
+RTIME system2count(uint64_t time);
+void sync_distributed_clocks(void);
+void update_master_clock(void);
+RTIME calculate_sleeptime(uint64_t wakeup_time);
+uint64_t calculate_first(void);
+
/*****************************************************************************/
%(pdos_configuration_declaration)s
@@ -50,7 +87,7 @@
LogMessage(level, sbuf, slen);\
}
-/* Beremiz plugin functions */
+/* EtherCAT plugin functions */
int __init_%(location)s(int argc,char **argv)
{
uint32_t abort_code;
@@ -81,10 +118,34 @@
ecrt_master_set_send_interval(master, common_ticktime__);
// slaves initialization
+/*
%(slaves_initialization)s
+*/
+ // configure DC SYNC0/1 Signal
+%(config_dc)s
+
+ // select reference clock
+#if DC_ENABLE
+ {
+ int ret;
+
+ ret = ecrt_master_select_reference_clock(master, slave0);
+ if (ret <0) {
+ fprintf(stderr, "Failed to select reference clock : %%s\n",
+ strerror(-ret));
+ return ret;
+ }
+ }
+#endif
// extracting default value for not mapped entry in output PDOs
+/*
%(slaves_output_pdos_default_values_extraction)s
+*/
+
+#if DC_ENABLE
+ dc_init();
+#endif
if (ecrt_master_activate(master)){
SLOGF(LOG_CRITICAL, "EtherCAT Master activation failed");
@@ -126,17 +187,20 @@
}
+/*
static RTIME _last_occur=0;
static RTIME _last_publish=0;
RTIME _current_lag=0;
RTIME _max_jitter=0;
static inline RTIME max(RTIME a,RTIME b){return a>b?a:b;}
+*/
void __publish_%(location)s(void)
{
%(publish_variables)s
ecrt_domain_queue(domain1);
{
+ /*
RTIME current_time = rt_timer_read();
// Limit spining max 1/5 of common_ticktime
RTIME maxdeadline = current_time + (common_ticktime__ / 5);
@@ -162,7 +226,281 @@
//Consuming security margin ?
_last_occur = current_time; //Drift forward
}
- }
+ */
+ }
+
+#if DC_ENABLE
+ if (comp_count == 0)
+ sync_distributed_clocks();
+#endif
+
ecrt_master_send(master);
first_sent = 1;
-}
+
+#if DC_ENABLE
+ if (comp_count == 0)
+ update_master_clock();
+
+ comp_count++;
+
+ if (comp_count == comp_count_max)
+ comp_count = 0;
+#endif
+
+}
+
+/* Test Function For Parameter (SDO) Set */
+
+/*
+void GetSDOData(void){
+ uint32_t abort_code, test_value;
+ size_t result_size;
+ uint8_t value[4];
+
+ abort_code = 0;
+ result_size = 0;
+ test_value = 0;
+
+ if (ecrt_master_sdo_upload(master, 0, 0x1000, 0x0, (uint8_t *)value, 4, &result_size, &abort_code)) {
+ SLOGF(LOG_CRITICAL, "EtherCAT failed to get SDO Value");
+ }
+ test_value = EC_READ_S32((uint8_t *)value);
+ SLOGF(LOG_INFO, "SDO Value %%d", test_value);
+}
+*/
+
+int GetMasterData(void){
+ master = ecrt_open_master(0);
+ if (!master) {
+ SLOGF(LOG_CRITICAL, "EtherCAT master request failed!");
+ return -1;
+ }
+ return 0;
+}
+
+void ReleaseMasterData(void){
+ ecrt_release_master(master);
+}
+
+uint32_t GetSDOData(uint16_t slave_pos, uint16_t idx, uint8_t subidx, int size){
+ uint32_t abort_code, return_value;
+ size_t result_size;
+ uint8_t value[size];
+
+ abort_code = 0;
+ result_size = 0;
+
+ if (ecrt_master_sdo_upload(master, slave_pos, idx, subidx, (uint8_t *)value, size, &result_size, &abort_code)) {
+ SLOGF(LOG_CRITICAL, "EtherCAT failed to get SDO Value %%d %%d", idx, subidx);
+ }
+
+ return_value = EC_READ_S32((uint8_t *)value);
+ //SLOGF(LOG_INFO, "SDO Value %%d", return_value);
+
+ return return_value;
+}
+
+/*****************************************************************************/
+
+void dc_init(void)
+{
+ slave_dc_used = 1;
+
+ frame_period_ns = common_ticktime__;
+ if (frame_period_ns <= comp_period_ns) {
+ comp_count_max = comp_period_ns / frame_period_ns;
+ comp_count = 0;
+ } else {
+ comp_count_max = 1;
+ comp_count = 0;
+ }
+
+ /* Set the initial master time */
+ dc_start_time_ns = system_time_ns();
+ dc_time_ns = dc_start_time_ns;
+
+ /* by woonggy */
+ dc_first_app_time = dc_start_time_ns;
+
+ /*
+ * Attention : The initial application time is also used for phase
+ * calculation for the SYNC0/1 interrupts. Please be sure to call it at
+ * the correct phase to the realtime cycle.
+ */
+ ecrt_master_application_time(master, dc_start_time_ns);
+}
+
+/****************************************************************************/
+
+/*
+ * Get the time in ns for the current cpu, adjusted by system_time_base.
+ *
+ * \attention Rather than calling rt_timer_read() directly, all application
+ * time calls should use this method instead.
+ *
+ * \ret The time in ns.
+ */
+uint64_t system_time_ns(void)
+{
+ RTIME time = rt_timer_read(); // wkk
+
+ if (unlikely(system_time_base > (SRTIME) time)) {
+ fprintf(stderr, "%%s() error: system_time_base greater than"
+ " system time (system_time_base: %%ld, time: %%llu\n",
+ __func__, system_time_base, time);
+ return time;
+ }
+ else {
+ return time - system_time_base;
+ }
+}
+
+/****************************************************************************/
+
+// Convert system time to Xenomai time in counts (via the system_time_base).
+RTIME system2count(uint64_t time)
+{
+ RTIME ret;
+
+ if ((system_time_base < 0) &&
+ ((uint64_t) (-system_time_base) > time)) {
+ fprintf(stderr, "%%s() error: system_time_base less than"
+ " system time (system_time_base: %%I64d, time: %%ld\n",
+ __func__, system_time_base, time);
+ ret = time;
+ }
+ else {
+ ret = time + system_time_base;
+ }
+
+ return (RTIME) rt_timer_ns2ticks(ret); // wkk
+}
+
+/*****************************************************************************/
+
+// Synchronise the distributed clocks
+void sync_distributed_clocks(void)
+{
+ uint32_t ref_time = 0;
+ RTIME prev_app_time = dc_time_ns;
+
+ // get reference clock time to synchronize master cycle
+ if(!ecrt_master_reference_clock_time(master, &ref_time)) {
+ dc_diff_ns = (uint32_t) prev_app_time - ref_time;
+ }
+ // call to sync slaves to ref slave
+ ecrt_master_sync_slave_clocks(master);
+ // set master time in nano-seconds
+ dc_time_ns = system_time_ns();
+ ecrt_master_application_time(master, dc_time_ns);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the sign of a number
+ * ie -1 for -ve value, 0 for 0, +1 for +ve value
+ * \ret val the sign of the value
+ */
+#define sign(val) \
+ ({ typeof (val) _val = (val); \
+ ((_val > 0) - (_val < 0)); })
+
+/*****************************************************************************/
+
+/*
+ * Update the master time based on ref slaves time diff
+ * called after the ethercat frame is sent to avoid time jitter in
+ * sync_distributed_clocks()
+ */
+void update_master_clock(void)
+{
+ // calc drift (via un-normalised time diff)
+ int32_t delta = dc_diff_ns - prev_dc_diff_ns;
+ prev_dc_diff_ns = dc_diff_ns;
+
+ // normalise the time diff
+ dc_diff_ns = dc_diff_ns >= 0 ?
+ ((dc_diff_ns + (int32_t)(frame_period_ns / 2)) %%
+ (int32_t)frame_period_ns) - (frame_period_ns / 2) :
+ ((dc_diff_ns - (int32_t)(frame_period_ns / 2)) %%
+ (int32_t)frame_period_ns) - (frame_period_ns / 2) ;
+
+ // only update if primary master
+ if (dc_started) {
+ // add to totals
+ dc_diff_total_ns += dc_diff_ns;
+ dc_delta_total_ns += delta;
+ dc_filter_idx++;
+
+ if (dc_filter_idx >= DC_FILTER_CNT) {
+ dc_adjust_ns += dc_delta_total_ns >= 0 ?
+ ((dc_delta_total_ns + (DC_FILTER_CNT / 2)) / DC_FILTER_CNT) :
+ ((dc_delta_total_ns - (DC_FILTER_CNT / 2)) / DC_FILTER_CNT) ;
+
+ // and add adjustment for general diff (to pull in drift)
+ dc_adjust_ns += sign(dc_diff_total_ns / DC_FILTER_CNT);
+
+ // limit crazy numbers (0.1%% of std cycle time)
+ if (dc_adjust_ns < -1000) {
+ dc_adjust_ns = -1000;
+ }
+ if (dc_adjust_ns > 1000) {
+ dc_adjust_ns = 1000;
+ }
+ // reset
+ dc_diff_total_ns = 0LL;
+ dc_delta_total_ns = 0LL;
+ dc_filter_idx = 0;
+ }
+ // add cycles adjustment to time base (including a spot adjustment)
+ system_time_base += dc_adjust_ns + sign(dc_diff_ns);
+ }
+ else {
+ dc_started = (dc_diff_ns != 0);
+
+ if (dc_started) {
+#if DC_ENABLE && DEBUG_MODE
+ // output first diff
+ fprintf(stderr, "First master diff: %%d\n", dc_diff_ns);
+#endif
+ // record the time of this initial cycle
+ dc_start_time_ns = dc_time_ns;
+ }
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Calculate the sleeptime
+ */
+RTIME calculate_sleeptime(uint64_t wakeup_time)
+{
+ RTIME wakeup_count = system2count (wakeup_time);
+ RTIME current_count = rt_timer_read();
+
+ if ((wakeup_count < current_count) || (wakeup_count > current_count + (50 * frame_period_ns))) {
+ fprintf(stderr, "%%s(): unexpected wake time! wc = %%lld\tcc = %%lld\n", __func__, wakeup_count, current_count);
+ }
+
+ return wakeup_count;
+}
+
+/*****************************************************************************/
+
+/*
+ * Calculate the sleeptime
+ */
+uint64_t calculate_first(void)
+{
+ uint64_t dc_remainder = 0LL;
+ uint64_t dc_phase_set_time = 0LL;
+
+ dc_phase_set_time = system_time_ns()+ frame_period_ns * 10;
+ dc_remainder = (dc_phase_set_time - dc_first_app_time) %% frame_period_ns;
+
+ return dc_phase_set_time + frame_period_ns - dc_remainder;
+}
+
+/*****************************************************************************/
--- a/etherlab/pous.xml Thu Jan 28 14:50:26 2021 +0000
+++ b/etherlab/pous.xml Thu Jan 28 14:51:16 2021 +0000
@@ -110,13 +110,13 @@
VALUE := 'None';
END_IF;
1:
- {if (AcquireSDOLock()) __SET_VAR(data__->,STATE,, 2)}
+ {if (AcquireSDOLock()) __SET_VAR(data__->,STATE, 2)}
2:
IF PY0.ACK THEN
STATE := 3;
END_IF;
3:
- {if (HasAnswer()) __SET_VAR(data__->,STATE,, 4)}
+ {if (HasAnswer()) __SET_VAR(data__->,STATE, 4)}
4:
IF PY1.ACK THEN
ACK := 1;
@@ -231,13 +231,13 @@
ERROR := 0;
END_IF;
1:
- {if (AcquireSDOLock()) __SET_VAR(data__->,STATE,, 2)}
+ {if (AcquireSDOLock()) __SET_VAR(data__->,STATE, 2)}
2:
IF PY0.ACK THEN
STATE := 3;
END_IF;
3:
- {if (HasAnswer()) __SET_VAR(data__->,STATE,, 4)}
+ {if (HasAnswer()) __SET_VAR(data__->,STATE, 4)}
4:
IF PY1.ACK THEN
ACK := 1;
--- a/etherlab/runtime_etherlab.py Thu Jan 28 14:50:26 2021 +0000
+++ b/etherlab/runtime_etherlab.py Thu Jan 28 14:51:16 2021 +0000
@@ -1,3 +1,14 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# this file is part of beremiz
+#
+# copyright (c) 2011-2014: laurent bessard, edouard tisserant
+# rtes lab : crkim, jblee, youcu
+# higen motor : donggu kang
+#
+# see copying file for copyrights details.
+
from __future__ import absolute_import
import os
import signal
--- a/modbus/mb_runtime.c Thu Jan 28 14:50:26 2021 +0000
+++ b/modbus/mb_runtime.c Thu Jan 28 14:51:16 2021 +0000
@@ -25,8 +25,10 @@
#include <stdio.h>
#include <string.h> /* required for memcpy() */
+#include <errno.h>
#include <time.h>
#include <signal.h>
+#include <unistd.h> /* required for pause() */
#include "mb_slave_and_master.h"
#include "MB_%(locstr)s.h"
@@ -336,24 +338,10 @@
static void *__mb_client_thread(void *_index) {
int client_node_id = (char *)_index - (char *)NULL; // Use pointer arithmetic (more portable than cast)
- struct timespec next_cycle;
- int period_sec = client_nodes[client_node_id].comm_period / 1000; /* comm_period is in ms */
- int period_nsec = (client_nodes[client_node_id].comm_period %%1000)*1000000; /* comm_period is in ms */
// Enable thread cancelation. Enabled is default, but set it anyway to be safe.
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
- // configure the timer for periodic activation
- {
- struct itimerspec timerspec;
- timerspec.it_interval.tv_sec = period_sec;
- timerspec.it_interval.tv_nsec = period_nsec;
- timerspec.it_value = timerspec.it_interval;
-
- if (timer_settime(client_nodes[client_node_id].timer_id, 0 /* flags */, &timerspec, NULL) < 0)
- fprintf(stderr, "Modbus plugin: Error configuring periodic activation timer for Modbus client %%s.\n", client_nodes[client_node_id].location);
- }
-
/* loop the communication with the client
*
* When the client thread has difficulty communicating with remote client and/or server (network issues, for example),
@@ -383,7 +371,7 @@
/*
struct timespec cur_time;
clock_gettime(CLOCK_MONOTONIC, &cur_time);
- fprintf(stderr, "Modbus client thread - new cycle (%%ld:%%ld)!\n", cur_time.tv_sec, cur_time.tv_nsec);
+ fprintf(stderr, "Modbus client thread (%%d) - new cycle (%%ld:%%ld)!\n", client_node_id, cur_time.tv_sec, cur_time.tv_nsec);
*/
int req;
for (req=0; req < NUMBER_OF_CLIENT_REQTS; req ++){
@@ -400,8 +388,10 @@
if ((client_requests[req].flag_exec_req == 0) && (client_nodes[client_requests[req].client_node_id].periodic_act == 0))
continue;
- //fprintf(stderr, "Modbus plugin: RUNNING<###> of Modbus request %%d (periodic = %%d flag_exec_req = %%d)\n",
- // req, client_nodes[client_requests[req].client_node_id].periodic_act, client_requests[req].flag_exec_req );
+ /*
+ fprintf(stderr, "Modbus client thread (%%d): RUNNING Modbus request %%d (periodic = %%d flag_exec_req = %%d)\n",
+ client_node_id, req, client_nodes[client_requests[req].client_node_id].periodic_act, client_requests[req].flag_exec_req );
+ */
int res_tmp = __execute_mb_request(req);
client_requests[req].tn_error_code = 0; // assume success
@@ -500,47 +490,77 @@
-/* Function to activate a client node's thread */
-/* returns -1 if it could not send the signal */
-static int __signal_client_thread(int client_node_id) {
- /* We TRY to signal the client thread.
- * We do this because this function can be called at the end of the PLC scan cycle
- * and we don't want it to block at that time.
- */
- if (pthread_mutex_trylock(&(client_nodes[client_node_id].mutex)) != 0)
- return -1;
- client_nodes[client_node_id].execute_req = 1; // tell the thread to execute
- pthread_cond_signal (&(client_nodes[client_node_id].condv));
- pthread_mutex_unlock(&(client_nodes[client_node_id].mutex));
- return 0;
-}
-
-
-
-/* Function that will be called whenever a client node's periodic timer expires. */
-/* The client node's thread will be waiting on a condition variable, so this function simply signals that
- * condition variable.
+
+
+/* Thread that simply implements a periodic 'timer',
+ * i.e. periodically sends signal to the thread running __mb_client_thread()
*
- * The same callback function is called by the timers of all client nodes. The id of the client node
- * in question will be passed as a parameter to the call back function.
+ * Note that we do not use a posix timer (timer_create() ) because there doesn't seem to be a way
+ * of having the timer notify the thread that is portable across Xenomai and POSIX.
+ * - SIGEV_THREAD : not supported by Xenomai
+ * - SIGEV_THREAD_ID : Linux specific (i.e. non POSIX)
+ * Even so, I did not get it to work under Linux (issues with the header files)
+ * - SIGEV_SIGNAL : Will not work, as signal is sent to random thread in process!
*/
-void __client_node_timer_callback_function(union sigval sigev_value) {
- /* signal the client node's condition variable on which the client node's thread should be waiting... */
- /* Since the communication cycle is run with the mutex locked, we use trylock() instead of lock() */
- //pthread_mutex_lock (&(client_nodes[sigev_value.sival_int].mutex));
- if (pthread_mutex_trylock (&(client_nodes[sigev_value.sival_int].mutex)) != 0)
- /* we never get to signal the thread for activation. But that is OK.
- * If it still in the communication cycle (during which the mutex is kept locked)
- * then that means that the communication cycle is falling behing in the periodic
- * communication cycle, and we therefore need to skip a period.
+static void *__mb_client_timer_thread(void *_index) {
+ int client_node_id = (char *)_index - (char *)NULL; // Use pointer arithmetic (more portable than cast)
+ struct timespec next_cycle;
+
+ int period_sec = client_nodes[client_node_id].comm_period / 1000; /* comm_period is in ms */
+ int period_nsec = (client_nodes[client_node_id].comm_period %%1000)*1000000; /* comm_period is in ms */
+
+ // Enable thread cancelation. Enabled is default, but set it anyway to be safe.
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
+ if (client_nodes[client_node_id].comm_period <= 0) {
+ // No periodic activation required => nothing to do!
+ while (1) pause(); // wait to be canceled when program terminates (shutdown() is called)
+ return NULL; // not really necessary, just makes it easier to understand the code.
+ }
+
+ // get the current time
+ clock_gettime(CLOCK_MONOTONIC, &next_cycle);
+
+ while(1) {
+ // Determine absolute time instant for starting the next cycle
+ struct timespec prev_cycle, now;
+ prev_cycle = next_cycle;
+ timespec_add(next_cycle, period_sec, period_nsec);
+
+ /* NOTE:
+ * It is probably un-necessary to check for overflow of timer!
+ * Even in 32 bit systems this will take at least 68 years since the computer booted
+ * (remember, we are using CLOCK_MONOTONIC, which should start counting from 0
+ * every time the system boots). On 64 bit systems, it will take over
+ * 10^11 years to overflow.
*/
- return;
- client_nodes[sigev_value.sival_int].execute_req = 1; // tell the thread to execute
- client_nodes[sigev_value.sival_int].periodic_act = 1; // tell the thread the activation was done by periodic timer
- pthread_cond_signal (&(client_nodes[sigev_value.sival_int].condv));
- pthread_mutex_unlock(&(client_nodes[sigev_value.sival_int].mutex));
-}
-
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ if (next_cycle.tv_sec < prev_cycle.tv_sec) {
+ /* Timer overflow. See NOTE B above */
+ next_cycle = now;
+ timespec_add(next_cycle, period_sec, period_nsec);
+ }
+
+ while (0 != clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_cycle, NULL));
+
+ /* signal the client node's condition variable on which the client node's thread should be waiting... */
+ /* Since the communication cycle is run with the mutex locked, we use trylock() instead of lock() */
+ if (pthread_mutex_trylock (&(client_nodes[client_node_id].mutex)) == 0) {
+ client_nodes[client_node_id].execute_req = 1; // tell the thread to execute
+ client_nodes[client_node_id].periodic_act = 1; // tell the thread the activation was done by periodic timer
+ pthread_cond_signal (&(client_nodes[client_node_id].condv));
+ pthread_mutex_unlock(&(client_nodes[client_node_id].mutex));
+ } else {
+ /* We never get to signal the thread for activation. But that is OK.
+ * If it still in the communication cycle (during which the mutex is kept locked)
+ * then that means that the communication cycle is falling behing in the periodic
+ * communication cycle, and we therefore need to skip a period.
+ */
+ }
+ }
+
+ return NULL; // humour the compiler -> will never be executed!
+}
int __cleanup_%(locstr)s ();
@@ -625,6 +645,7 @@
client_nodes[index].init_state = 1; // we have created the node
/* initialize the mutex variable that will be used by the thread handling the client node */
+ bzero(&(client_nodes[index].mutex), sizeof(pthread_mutex_t));
if (pthread_mutex_init(&(client_nodes[index].mutex), NULL) < 0) {
fprintf(stderr, "Modbus plugin: Error creating mutex for modbus client node %%s\n", client_nodes[index].location);
goto error_exit;
@@ -632,6 +653,7 @@
client_nodes[index].init_state = 2; // we have created the mutex
/* initialize the condition variable that will be used by the thread handling the client node */
+ bzero(&(client_nodes[index].condv), sizeof(pthread_cond_t));
if (pthread_cond_init(&(client_nodes[index].condv), NULL) < 0) {
fprintf(stderr, "Modbus plugin: Error creating condition variable for modbus client node %%s\n", client_nodes[index].location);
goto error_exit;
@@ -639,22 +661,17 @@
client_nodes[index].execute_req = 0; //variable associated with condition variable
client_nodes[index].init_state = 3; // we have created the condition variable
- /* initialize the timer that will be used to periodically activate the client node */
- {
- // start off by reseting the flag that will be set whenever the timer expires
- client_nodes[index].periodic_act = 0;
-
- struct sigevent evp;
- evp.sigev_notify = SIGEV_THREAD; /* Notification method - call a function in a new thread context */
- evp.sigev_value.sival_int = index; /* Data passed to function upon notification - used to indentify which client node to activate */
- evp.sigev_notify_function = __client_node_timer_callback_function; /* function to call upon timer expiration */
- evp.sigev_notify_attributes = NULL; /* attributes for new thread in which sigev_notify_function will be called/executed */
-
- if (timer_create(CLOCK_MONOTONIC, &evp, &(client_nodes[index].timer_id)) < 0) {
- fprintf(stderr, "Modbus plugin: Error creating timer for modbus client node %%s\n", client_nodes[index].location);
- goto error_exit;
- }
- }
+ /* launch a thread to handle this client node timer */
+ {
+ int res = 0;
+ pthread_attr_t attr;
+ res |= pthread_attr_init(&attr);
+ res |= pthread_create(&(client_nodes[index].timer_thread_id), &attr, &__mb_client_timer_thread, (void *)((char *)NULL + index));
+ if (res != 0) {
+ fprintf(stderr, "Modbus plugin: Error starting timer thread for modbus client node %%s\n", client_nodes[index].location);
+ goto error_exit;
+ }
+ }
client_nodes[index].init_state = 4; // we have created the timer
/* launch a thread to handle this client node */
@@ -758,13 +775,26 @@
*/
if ((client_requests[index].flag_exec_req != 0) && (0 == client_requests[index].flag_exec_started)) {
int client_node_id = client_requests[index].client_node_id;
- if (__signal_client_thread(client_node_id) >= 0) {
- /* - upon success, set flag_exec_started
- * - both flags (flag_exec_req and flag_exec_started) will be reset
- * once the transaction has completed.
- */
- client_requests[index].flag_exec_started = 1;
- }
+
+ /* We TRY to signal the client thread.
+ * We do this because this function can be called at the end of the PLC scan cycle
+ * and we don't want it to block at that time.
+ */
+ if (pthread_mutex_trylock(&(client_nodes[client_node_id].mutex)) == 0) {
+ client_nodes[client_node_id].execute_req = 1; // tell the thread to execute
+ pthread_cond_signal (&(client_nodes[client_node_id].condv));
+ pthread_mutex_unlock(&(client_nodes[client_node_id].mutex));
+ /* - upon success, set flag_exec_started
+ * - both flags (flag_exec_req and flag_exec_started) will be reset
+ * once the transaction has completed.
+ */
+ client_requests[index].flag_exec_started = 1;
+ } else {
+ /* The mutex is locked => the client thread is currently executing MB transactions.
+ * We will try to activate it in the next PLC cycle...
+ * For now, do nothing.
+ */
+ }
}
}
}
@@ -812,10 +842,12 @@
close = 0;
if (client_nodes[index].init_state >= 4) {
- // timer was created, so we try to destroy it!
- close = timer_delete(client_nodes[index].timer_id);
+ // timer thread was launched, so we try to cancel it!
+ close = pthread_cancel(client_nodes[index].timer_thread_id);
+ close |= pthread_join (client_nodes[index].timer_thread_id, NULL);
if (close < 0)
- fprintf(stderr, "Modbus plugin: Error destroying timer for modbus client node %%s\n", client_nodes[index].location);
+ fprintf(stderr, "Modbus plugin: Error closing timer thread for modbus client node %%s\n", client_nodes[index].location);
+
}
res |= close;
@@ -851,6 +883,7 @@
client_nodes[index].init_state = 0;
}
+//fprintf(stderr, "Modbus plugin: __cleanup_%%s() 5 close=%%d res=%%d\n", client_nodes[index].location, close, res);
/* kill thread and close connections of each modbus server node */
for (index=0; index < NUMBER_OF_SERVER_NODES; index++) {
close = 0;
--- a/modbus/mb_runtime.h Thu Jan 28 14:50:26 2021 +0000
+++ b/modbus/mb_runtime.h Thu Jan 28 14:51:16 2021 +0000
@@ -127,7 +127,7 @@
u64 comm_period;// period to use when periodically sending requests to remote server
int prev_error; // error code of the last printed error message (0 when no error)
pthread_t thread_id; // thread handling all communication for this client node
- timer_t timer_id; // timer used to periodically activate this client node's thread
+ pthread_t timer_thread_id; // thread handling periodical timer for this client node
pthread_mutex_t mutex; // mutex to be used with the following condition variable
pthread_cond_t condv; // used to signal the client thread when to start new modbus transactions
int execute_req; /* used, in association with condition variable,
--- a/modbus/modbus.py Thu Jan 28 14:50:26 2021 +0000
+++ b/modbus/modbus.py Thu Jan 28 14:51:16 2021 +0000
@@ -30,6 +30,7 @@
from modbus.mb_utils import *
from ConfigTreeNode import ConfigTreeNode
from PLCControler import LOCATION_CONFNODE, LOCATION_VAR_MEMORY
+import util.paths as paths
base_folder = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]
base_folder = os.path.join(base_folder, "..")
@@ -302,6 +303,8 @@
#
#
+# XXX TODO "Configuration_Name" should disapear in favor of CTN Name, which is already unique
+
class _ModbusTCPclientPlug(object):
XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
@@ -379,6 +382,8 @@
#
#
+# XXX TODO "Configuration_Name" should disapear in favor of CTN Name, which is already unique
+
class _ModbusTCPserverPlug(object):
# NOTE: the Port number is a 'string' and not an 'integer'!
# This is because the underlying modbus library accepts strings
@@ -542,6 +547,8 @@
#
#
+# XXX TODO "Configuration_Name" should disapear in favor of CTN Name, which is already unique
+
class _ModbusRTUclientPlug(object):
XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
@@ -633,6 +640,7 @@
#
#
+# XXX TODO "Configuration_Name" should disapear in favor of CTN Name, which is already unique
class _ModbusRTUslavePlug(object):
XSD = """<?xml version="1.0" encoding="ISO-8859-1" ?>
@@ -1236,4 +1244,18 @@
# LDFLAGS.append(" -lws2_32 ") # on windows we need to load winsock
# library!
- return [(Gen_MB_c_path, ' -I"' + ModbusPath + '"')], LDFLAGS, True
+ websettingfile = open(paths.AbsNeighbourFile(__file__, "web_settings.py"), 'r')
+ websettingcode = websettingfile.read()
+ websettingfile.close()
+
+ location_str = "_".join(map(str, self.GetCurrentLocation()))
+ websettingcode = websettingcode % locals()
+
+ runtimefile_path = os.path.join(buildpath, "runtime_modbus_websettings.py")
+ runtimefile = open(runtimefile_path, 'w')
+ runtimefile.write(websettingcode)
+ runtimefile.close()
+
+ return ([(Gen_MB_c_path, ' -I"' + ModbusPath + '"')], LDFLAGS, True,
+ ("runtime_modbus_websettings_%s.py" % location_str, open(runtimefile_path, "rb")),
+ )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/modbus/web_settings.py Thu Jan 28 14:51:16 2021 +0000
@@ -0,0 +1,628 @@
+#!/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
+
+
+
+
+##############################################################################################
+# This file implements an extension to the web server embedded in the Beremiz_service.py #
+# runtime manager (webserver is in runtime/NevowServer.py). #
+# #
+# The extension implemented in this file allows for runtime configuration #
+# of Modbus plugin parameters #
+##############################################################################################
+
+
+
+import json
+import os
+import ctypes
+import string
+import hashlib
+
+from formless import annotate, webform
+
+import runtime.NevowServer as NS
+
+# Directory in which to store the persistent configurations
+# Should be a directory that does not get wiped on reboot!
+_ModbusConfFiledir = WorkingDir
+
+# List of all Web Extension Setting nodes we are handling.
+# One WebNode each for:
+# - Modbus TCP client
+# - Modbus TCP server
+# - Modbus RTU client
+# - Modbus RTU slave
+# configured in the loaded PLC (i.e. the .so file loaded into memory)
+# Each entry will be a dictionary. See _AddWebNode() for the details
+# of the data structure in each entry.
+_WebNodeList = []
+
+
+
+
+class MB_StrippedString(annotate.String):
+ def __init__(self, *args, **kwargs):
+ annotate.String.__init__(self, strip = True, *args, **kwargs)
+
+
+class MB_StopBits(annotate.Choice):
+ _choices = [0, 1, 2]
+
+ def coerce(self, val, configurable):
+ return int(val)
+ def __init__(self, *args, **kwargs):
+ annotate.Choice.__init__(self, choices = self._choices, *args, **kwargs)
+
+
+class MB_Baud(annotate.Choice):
+ _choices = [110, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]
+
+ def coerce(self, val, configurable):
+ return int(val)
+ def __init__(self, *args, **kwargs):
+ annotate.Choice.__init__(self, choices = self._choices, *args, **kwargs)
+
+
+class MB_Parity(annotate.Choice):
+ # For more info on what this class really does, have a look at the code in
+ # file twisted/nevow/annotate.py
+ # grab this code from $git clone https://github.com/twisted/nevow/
+ #
+ # Warning: do _not_ name this variable choice[] without underscore, as that name is
+ # already used for another similar variable by the underlying class annotate.Choice
+ _choices = [ 0, 1, 2 ]
+ _label = ["none", "odd", "even"]
+
+ def choice_to_label(self, key):
+ #PLCObject.LogMessage("Modbus web server extension::choice_to_label() " + str(key))
+ return self._label[key]
+
+ def coerce(self, val, configurable):
+ """Coerce a value with the help of an object, which is the object
+ we are configuring.
+ """
+ # Basically, make sure the value the user introduced is valid, and transform
+ # into something that is valid if necessary or mark it as an error
+ # (by raising an exception ??).
+ #
+ # We are simply using this functions to transform the input value (a string)
+ # into an integer. Note that although the available options are all
+ # integers (0, 1 or 2), even though what is shown on the user interface
+ # are actually strings, i.e. the labels), these parameters are for some
+ # reason being parsed as strings, so we need to map them back to an
+ # integer.
+ #
+ #PLCObject.LogMessage("Modbus web server extension::coerce " + val )
+ return int(val)
+
+ def __init__(self, *args, **kwargs):
+ annotate.Choice.__init__(self,
+ choices = self._choices,
+ stringify = self.choice_to_label,
+ *args, **kwargs)
+
+
+
+# Parameters we will need to get from the C code, but that will not be shown
+# on the web interface. Common to all modbus entry types (client/server, tcp/rtu/ascii)
+#
+# The annotate type entry is basically useless and is completely ignored.
+# We kee that entry so that this list can later be correctly merged with the
+# following lists...
+General_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, ...)
+ ("config_name" , _("") , ctypes.c_char_p, annotate.String),
+ ("addr_type" , _("") , ctypes.c_char_p, annotate.String)
+ ]
+
+# Parameters we will need to get from the C code, and that _will_ be shown
+# on the web interface.
+TCPclient_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, ...)
+ ("host" , _("Remote IP Address") , ctypes.c_char_p, MB_StrippedString),
+ ("port" , _("Remote Port Number") , ctypes.c_char_p, MB_StrippedString),
+ ("comm_period" , _("Invocation Rate (ms)") , ctypes.c_ulonglong, annotate.Integer )
+ ]
+
+RTUclient_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, ...)
+ ("device" , _("Serial Port") , ctypes.c_char_p, MB_StrippedString),
+ ("baud" , _("Baud Rate") , ctypes.c_int, MB_Baud ),
+ ("parity" , _("Parity") , ctypes.c_int, MB_Parity ),
+ ("stop_bits" , _("Stop Bits") , ctypes.c_int, MB_StopBits ),
+ ("comm_period" , _("Invocation Rate (ms)") , ctypes.c_ulonglong, annotate.Integer)
+ ]
+
+TCPserver_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, ...)
+ ("host" , _("Local IP Address") , ctypes.c_char_p, MB_StrippedString),
+ ("port" , _("Local Port Number") , ctypes.c_char_p, MB_StrippedString),
+ ("slave_id" , _("Slave ID") , ctypes.c_ubyte, annotate.Integer )
+ ]
+
+RTUslave_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, ...)
+ ("device" , _("Serial Port") , ctypes.c_char_p, MB_StrippedString),
+ ("baud" , _("Baud Rate") , ctypes.c_int, MB_Baud ),
+ ("parity" , _("Parity") , ctypes.c_int, MB_Parity ),
+ ("stop_bits" , _("Stop Bits") , ctypes.c_int, MB_StopBits ),
+ ("slave_id" , _("Slave ID") , ctypes.c_ulonglong, annotate.Integer)
+ ]
+
+
+
+
+# Dictionary containing List of Web viewable parameters
+# Note: the dictionary key must be the same as the string returned by the
+# __modbus_get_ClientNode_addr_type()
+# __modbus_get_ServerNode_addr_type()
+# functions implemented in C (see modbus/mb_runtime.c)
+_client_WebParamListDict = {}
+_client_WebParamListDict["tcp" ] = TCPclient_parameters
+_client_WebParamListDict["rtu" ] = RTUclient_parameters
+_client_WebParamListDict["ascii"] = [] # (Note: ascii not yet implemented in Beremiz modbus plugin)
+
+_server_WebParamListDict = {}
+_server_WebParamListDict["tcp" ] = TCPserver_parameters
+_server_WebParamListDict["rtu" ] = RTUslave_parameters
+_server_WebParamListDict["ascii"] = [] # (Note: ascii not yet implemented in Beremiz modbus plugin)
+
+WebParamListDictDict = {}
+WebParamListDictDict['client'] = _client_WebParamListDict
+WebParamListDictDict['server'] = _server_WebParamListDict
+
+
+
+
+
+
+def _SetModbusSavedConfiguration(WebNode_id, newConfig):
+ """ Stores a dictionary in a persistant file containing the Modbus parameter configuration """
+ WebNode_entry = _WebNodeList[WebNode_id]
+
+ if WebNode_entry["DefaultConfiguration"] == newConfig:
+
+ _DelModbusSavedConfiguration(WebNode_id)
+ WebNode_entry["ModbusSavedConfiguration"] = None
+
+ else:
+
+ # Add the addr_type and node_type to the data that will be saved to file
+ # This allows us to confirm the saved data contains the correct addr_type
+ # when loading from file
+ save_info = {}
+ save_info["addr_type"] = ["addr_type"]
+ save_info["node_type"] = WebNode_entry["node_type"]
+ save_info["config" ] = newConfig
+
+ filename = WebNode_entry["filename"]
+
+ with open(os.path.realpath(filename), 'w') as f:
+ json.dump(save_info, f, sort_keys=True, indent=4)
+
+ WebNode_entry["ModbusSavedConfiguration"] = newConfig
+
+
+
+
+def _DelModbusSavedConfiguration(WebNode_id):
+ """ Deletes the file cotaining the persistent Modbus configuration """
+ filename = _WebNodeList[WebNode_id]["filename"]
+
+ if os.path.exists(filename):
+ os.remove(filename)
+
+
+
+
+def _GetModbusSavedConfiguration(WebNode_id):
+ """
+ Returns a dictionary containing the Modbus parameter configuration
+ that was last saved to file. If no file exists, or file contains
+ wrong addr_type (i.e. 'tcp', 'rtu' or 'ascii' -> does not match the
+ addr_type of the WebNode_id), then return None
+ """
+ filename = _WebNodeList[WebNode_id]["filename"]
+ try:
+ #if os.path.isfile(filename):
+ save_info = json.load(open(filename))
+ except Exception:
+ return None
+
+ if save_info["addr_type"] != _WebNodeList[WebNode_id]["addr_type"]:
+ return None
+ if save_info["node_type"] != _WebNodeList[WebNode_id]["node_type"]:
+ return None
+ if "config" not in save_info:
+ return None
+
+ saved_config = save_info["config"]
+
+ #if _CheckConfiguration(saved_config):
+ # return saved_config
+ #else:
+ # return None
+
+ return saved_config
+
+
+
+def _GetModbusPLCConfiguration(WebNode_id):
+ """
+ Returns a dictionary containing the current Modbus parameter configuration
+ stored in the C variables in the loaded PLC (.so file)
+ """
+ current_config = {}
+ C_node_id = _WebNodeList[WebNode_id]["C_node_id"]
+ WebParamList = _WebNodeList[WebNode_id]["WebParamList"]
+ GetParamFuncs = _WebNodeList[WebNode_id]["GetParamFuncs"]
+
+ for par_name, x1, x2, x3 in WebParamList:
+ value = GetParamFuncs[par_name](C_node_id)
+ if value is not None:
+ current_config[par_name] = value
+
+ return current_config
+
+
+
+def _SetModbusPLCConfiguration(WebNode_id, newconfig):
+ """
+ Stores the Modbus parameter configuration into the
+ the C variables in the loaded PLC (.so file)
+ """
+ C_node_id = _WebNodeList[WebNode_id]["C_node_id"]
+ SetParamFuncs = _WebNodeList[WebNode_id]["SetParamFuncs"]
+
+ for par_name in newconfig:
+ value = newconfig[par_name]
+ if value is not None:
+ SetParamFuncs[par_name](C_node_id, value)
+
+
+
+
+def _GetModbusWebviewConfigurationValue(ctx, WebNode_id, argument):
+ """
+ Callback function, called by the web interface (NevowServer.py)
+ to fill in the default value of each parameter of the web form
+
+ Note that the real callback function is a dynamically created function that
+ will simply call this function to do the work. It will also pass the WebNode_id
+ as a parameter.
+ """
+ try:
+ return _WebNodeList[WebNode_id]["WebviewConfiguration"][argument.name]
+ except Exception:
+ return ""
+
+
+
+def OnModbusButtonSave(**kwargs):
+ """
+ Function called when user clicks 'Save' button in web interface
+ The function will configure the Modbus plugin in the PLC with the values
+ specified in the web interface. However, values must be validated first!
+
+ Note that this function does not get called directly. The real callback
+ function is the dynamic __OnButtonSave() function, which will add the
+ "WebNode_id" argument, and call this function to do the work.
+ """
+
+ #PLCObject.LogMessage("Modbus web server extension::OnModbusButtonSave() Called")
+
+ newConfig = {}
+ WebNode_id = kwargs.get("WebNode_id", None)
+ WebParamList = _WebNodeList[WebNode_id]["WebParamList"]
+
+ for par_name, x1, x2, x3 in WebParamList:
+ value = kwargs.get(par_name, None)
+ if value is not None:
+ newConfig[par_name] = value
+
+ # First check if configuration is OK.
+ # Note that this is not currently required, as we use drop down choice menus
+ # for baud, parity and sop bits, so the values should always be correct!
+ #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)
+ _SetModbusSavedConfiguration(WebNode_id, newConfig)
+
+ # Configure PLC with the current Modbus parameters
+ _SetModbusPLCConfiguration(WebNode_id, newConfig)
+
+ # Update the viewable configuration
+ # The PLC may have coerced the values on calling _SetModbusPLCConfiguration()
+ # so we do not set it directly to newConfig
+ _WebNodeList[WebNode_id]["WebviewConfiguration"] = _GetModbusPLCConfiguration(WebNode_id)
+
+
+
+def OnModbusButtonReset(**kwargs):
+ """
+ Function called when user clicks 'Delete' button in web interface
+ The function will delete the file containing the persistent
+ Modbus configution
+ """
+
+ WebNode_id = kwargs.get("WebNode_id", None)
+
+ # Delete the file
+ _DelModbusSavedConfiguration(WebNode_id)
+
+ # Set the current configuration to the default (hardcoded in C)
+ new_config = _WebNodeList[WebNode_id]["DefaultConfiguration"]
+ _SetModbusPLCConfiguration(WebNode_id, new_config)
+
+ #Update the webviewconfiguration
+ _WebNodeList[WebNode_id]["WebviewConfiguration"] = new_config
+
+ # Reset ModbusSavedConfiguration
+ _WebNodeList[WebNode_id]["ModbusSavedConfiguration"] = None
+
+
+
+
+
+def _AddWebNode(C_node_id, node_type, GetParamFuncs, SetParamFuncs):
+ """
+ Load from the compiled code (.so file, aloready loaded into memmory)
+ the configuration parameters of a specific Modbus plugin node.
+ This function works with both client and server nodes, depending on the
+ Get/SetParamFunc dictionaries passed to it (either the client or the server
+ node versions of the Get/Set functions)
+ """
+ WebNode_entry = {}
+
+ # Get the config_name from the C code...
+ config_name = GetParamFuncs["config_name"](C_node_id)
+ # Get the addr_type from the C code...
+ # addr_type will be one of "tcp", "rtu" or "ascii"
+ addr_type = GetParamFuncs["addr_type" ](C_node_id)
+ # For some operations we cannot use the config name (e.g. filename to store config)
+ # because the user may be using characters that are invalid for that purpose ('/' for
+ # example), so we create a hash of the config_name, and use that instead.
+ config_hash = hashlib.md5(config_name).hexdigest()
+
+ #PLCObject.LogMessage("Modbus web server extension::_AddWebNode("+str(C_node_id)+") config_name="+config_name)
+
+ # Add the new entry to the global list
+ # Note: it is OK, and actually necessary, to do this _before_ seting all the parameters in WebNode_entry
+ # WebNode_entry will be stored as a reference, so we can later insert parameters at will.
+ global _WebNodeList
+ _WebNodeList.append(WebNode_entry)
+ WebNode_id = len(_WebNodeList) - 1
+
+ # store all WebNode relevant data for future reference
+ #
+ # Note that "WebParamList" will reference one of:
+ # - TCPclient_parameters, TCPserver_parameters, RTUclient_parameters, RTUslave_parameters
+ WebNode_entry["C_node_id" ] = C_node_id
+ WebNode_entry["config_name" ] = config_name
+ WebNode_entry["config_hash" ] = config_hash
+ WebNode_entry["filename" ] = os.path.join(_ModbusConfFiledir, "Modbus_config_" + config_hash + ".json")
+ WebNode_entry["GetParamFuncs"] = GetParamFuncs
+ WebNode_entry["SetParamFuncs"] = SetParamFuncs
+ WebNode_entry["WebParamList" ] = WebParamListDictDict[node_type][addr_type]
+ WebNode_entry["addr_type" ] = addr_type # 'tcp', 'rtu', or 'ascii' (as returned by C function)
+ WebNode_entry["node_type" ] = node_type # 'client', 'server'
+
+
+ # Dictionary that contains the Modbus 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 an edited configuration that contains an error.
+ WebNode_entry["WebviewConfiguration"] = None
+
+ # Upon PLC load, this Dictionary is initialised with the Modbus configuration
+ # hardcoded in the C file
+ # (i.e. the configuration inserted in Beremiz IDE when project was compiled)
+ WebNode_entry["DefaultConfiguration"] = _GetModbusPLCConfiguration(WebNode_id)
+ WebNode_entry["WebviewConfiguration"] = WebNode_entry["DefaultConfiguration"]
+
+ # Dictionary that stores the Modbus 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 "ModbusSavedConfiguration" is not None)
+ SavedConfig = _GetModbusSavedConfiguration(WebNode_id)
+ WebNode_entry["ModbusSavedConfiguration"] = SavedConfig
+
+ if SavedConfig is not None:
+ _SetModbusPLCConfiguration(WebNode_id, SavedConfig)
+ WebNode_entry["WebviewConfiguration"] = SavedConfig
+
+ # Define the format for the web form used to show/change the current parameters
+ # We first declare a dynamic function to work as callback to obtain the default values for each parameter
+ # Note: We transform every parameter into a string
+ # This is not strictly required for parameters of type annotate.Integer that will correctly
+ # accept the default value as an Integer python object
+ # This is obviously also not required for parameters of type annotate.String, that are
+ # always handled as strings.
+ # However, the annotate.Choice parameters (and all parameters that derive from it,
+ # sucn as Parity, Baud, etc.) require the default value as a string
+ # even though we store it as an integer, which is the data type expected
+ # by the set_***() C functions in mb_runtime.c
+ def __GetWebviewConfigurationValue(ctx, argument):
+ return str(_GetModbusWebviewConfigurationValue(ctx, WebNode_id, argument))
+
+ webFormInterface = [(name, web_dtype (label=web_label, default=__GetWebviewConfigurationValue))
+ for name, web_label, c_dtype, web_dtype in WebNode_entry["WebParamList"]]
+
+ # Configure the web interface to include the Modbus config parameters
+ def __OnButtonSave(**kwargs):
+ OnModbusButtonSave(WebNode_id=WebNode_id, **kwargs)
+
+ WebSettings = NS.newExtensionSetting("Modbus #"+ str(WebNode_id), config_hash)
+
+ WebSettings.addSettings(
+ "ModbusConfigParm" + config_hash, # name (internal, may not contain spaces, ...)
+ _("Modbus Configuration: ") + config_name, # description (user visible label)
+ webFormInterface, # fields
+ _("Apply"), # button label
+ __OnButtonSave) # callback
+
+ def __OnButtonReset(**kwargs):
+ return OnModbusButtonReset(WebNode_id = WebNode_id, **kwargs)
+
+ def getModbusConfigStatus():
+ 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:getModbusConfigStatus())),
+ ], # fields (empty, no parameters required!)
+ _("Reset"), # button label
+ __OnButtonReset)
+
+
+
+
+def _runtime_modbus_websettings_%(location_str)s_init():
+ """
+ Callback function, called (by PLCObject.py) when a new PLC program
+ (i.e. XXX.so file) is transfered to the PLC runtime
+ and loaded into memory
+ """
+
+ #PLCObject.LogMessage("Modbus web server extension::OnLoadPLC() Called...")
+
+ if PLCObject.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 number of Modbus Client and Servers (Modbus plugin)
+ # configured in the currently loaded PLC project (i.e., the .so file)
+ # If the "__modbus_plugin_client_node_count"
+ # or the "__modbus_plugin_server_node_count" C variables
+ # are not present in the .so file we conclude that the currently loaded
+ # PLC does not have the Modbus plugin included (situation (2b) described above init())
+ try:
+ # XXX TODO : stop reading from PLC .so file. This code is template code
+ # that can use modbus extension build data, such as client node count.
+ client_count = ctypes.c_int.in_dll(PLCObject.PLClibraryHandle, "__modbus_plugin_client_node_count").value
+ server_count = ctypes.c_int.in_dll(PLCObject.PLClibraryHandle, "__modbus_plugin_server_node_count").value
+ except Exception:
+ # Loaded PLC does not have the Modbus plugin => nothing to do
+ # (i.e. do _not_ configure and make available the Modbus web interface)
+ return
+
+ if client_count < 0: client_count = 0
+ if server_count < 0: server_count = 0
+
+ if (client_count == 0) and (server_count == 0):
+ # The Modbus plugin in the loaded PLC does not have any client and servers configured
+ # => nothing to do (i.e. do _not_ configure and make available the Modbus web interface)
+ return
+
+ # Map the get/set functions (written in C code) we will be using to get/set the configuration parameters
+ # Will contain references to the C functions (implemented in beremiz/modbus/mb_runtime.c)
+ GetClientParamFuncs = {}
+ SetClientParamFuncs = {}
+ GetServerParamFuncs = {}
+ SetServerParamFuncs = {}
+
+ # XXX TODO : stop reading from PLC .so file. This code is template code
+ # that can use modbus extension build data
+ for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters + General_parameters:
+ ParamFuncName = "__modbus_get_ClientNode_" + name
+ GetClientParamFuncs[name] = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
+ GetClientParamFuncs[name].restype = c_dtype
+ GetClientParamFuncs[name].argtypes = [ctypes.c_int]
+
+ for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters:
+ ParamFuncName = "__modbus_set_ClientNode_" + name
+ SetClientParamFuncs[name] = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
+ SetClientParamFuncs[name].restype = None
+ SetClientParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
+
+ # XXX TODO : stop reading from PLC .so file. This code is template code
+ # that can use modbus extension build data
+ for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters + General_parameters:
+ ParamFuncName = "__modbus_get_ServerNode_" + name
+ GetServerParamFuncs[name] = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
+ GetServerParamFuncs[name].restype = c_dtype
+ GetServerParamFuncs[name].argtypes = [ctypes.c_int]
+
+ for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters:
+ ParamFuncName = "__modbus_set_ServerNode_" + name
+ SetServerParamFuncs[name] = getattr(PLCObject.PLClibraryHandle, ParamFuncName)
+ SetServerParamFuncs[name].restype = None
+ SetServerParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
+
+ for node_id in range(client_count):
+ _AddWebNode(node_id, "client" ,GetClientParamFuncs, SetClientParamFuncs)
+
+ for node_id in range(server_count):
+ _AddWebNode(node_id, "server", GetServerParamFuncs, SetServerParamFuncs)
+
+
+
+
+
+def _runtime_modbus_websettings_%(location_str)s_cleanup():
+ """
+ Callback function, called (by PLCObject.py) when a PLC program is unloaded from memory
+ """
+
+ #PLCObject.LogMessage("Modbus web server extension::OnUnLoadPLC() Called...")
+
+ # Delete the Modbus specific web interface extensions
+ # (Safe to ask to delete, even if it has not been added!)
+ global _WebNodeList
+ for index, WebNode_entry in enumerate(_WebNodeList):
+ config_hash = WebNode_entry["config_hash"]
+ NS.removeExtensionSetting(config_hash)
+
+ # Dele all entries...
+ _WebNodeList = []
+
--- a/py_ext/PythonFileCTNMixin.py Thu Jan 28 14:50:26 2021 +0000
+++ b/py_ext/PythonFileCTNMixin.py Thu Jan 28 14:51:16 2021 +0000
@@ -119,7 +119,7 @@
"opts": repr(variable.getopts()),
"configname": configname.upper(),
"uppername": variable.getname().upper(),
- "IECtype": variable.gettype(),
+ "IECtype": self.GetCTRoot().GetBaseType(variable.gettype()),
"initial": repr(variable.getinitial()),
"pyextname": pyextname
},
@@ -242,8 +242,8 @@
varpubonchangefmt = """\
if(!AtomicCompareExchange(&__%(name)s_rlock, 0, 1)){
IEC_%(IECtype)s tmp = __GET_VAR(%(configname)s__%(uppername)s);
- if(__%(name)s_rbuffer != tmp){
- __%(name)s_rbuffer = %(configname)s__%(uppername)s.value;
+ if(NE_%(IECtype)s(1, NULL, __%(name)s_rbuffer, tmp)){
+ __%(name)s_rbuffer = tmp;
PYTHON_POLL_body__(__%(name)s_notifier);
}
AtomicCompareExchange((long*)&__%(name)s_rlock, 1, 0);
--- a/runtime/BACnet_config.py Thu Jan 28 14:50:26 2021 +0000
+++ /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
--- a/runtime/Modbus_config.py Thu Jan 28 14:50:26 2021 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,705 +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
-
-
-
-
-##############################################################################################
-# This file implements an extension to the web server embedded in the Beremiz_service.py #
-# runtime manager (webserver is in runtime/NevowServer.py). #
-# #
-# The extension implemented in this file allows for runtime configuration #
-# of Modbus plugin parameters #
-##############################################################################################
-
-
-
-import json
-import os
-import ctypes
-import string
-import hashlib
-
-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
-
-# Directory in which to store the persistent configurations
-# Should be a directory that does not get wiped on reboot!
-_ModbusConfFiledir = "/tmp"
-
-# List of all Web Extension Setting nodes we are handling.
-# One WebNode each for:
-# - Modbus TCP client
-# - Modbus TCP server
-# - Modbus RTU client
-# - Modbus RTU slave
-# configured in the loaded PLC (i.e. the .so file loaded into memory)
-# Each entry will be a dictionary. See _AddWebNode() for the details
-# of the data structure in each entry.
-_WebNodeList = []
-
-
-
-
-class MB_StrippedString(annotate.String):
- def __init__(self, *args, **kwargs):
- annotate.String.__init__(self, strip = True, *args, **kwargs)
-
-
-class MB_StopBits(annotate.Choice):
- _choices = [0, 1, 2]
-
- def coerce(self, val, configurable):
- return int(val)
- def __init__(self, *args, **kwargs):
- annotate.Choice.__init__(self, choices = self._choices, *args, **kwargs)
-
-
-class MB_Baud(annotate.Choice):
- _choices = [110, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]
-
- def coerce(self, val, configurable):
- return int(val)
- def __init__(self, *args, **kwargs):
- annotate.Choice.__init__(self, choices = self._choices, *args, **kwargs)
-
-
-class MB_Parity(annotate.Choice):
- # For more info on what this class really does, have a look at the code in
- # file twisted/nevow/annotate.py
- # grab this code from $git clone https://github.com/twisted/nevow/
- #
- # Warning: do _not_ name this variable choice[] without underscore, as that name is
- # already used for another similar variable by the underlying class annotate.Choice
- _choices = [ 0, 1, 2 ]
- _label = ["none", "odd", "even"]
-
- def choice_to_label(self, key):
- #_plcobj.LogMessage("Modbus web server extension::choice_to_label() " + str(key))
- return self._label[key]
-
- def coerce(self, val, configurable):
- """Coerce a value with the help of an object, which is the object
- we are configuring.
- """
- # Basically, make sure the value the user introduced is valid, and transform
- # into something that is valid if necessary or mark it as an error
- # (by raising an exception ??).
- #
- # We are simply using this functions to transform the input value (a string)
- # into an integer. Note that although the available options are all
- # integers (0, 1 or 2), even though what is shown on the user interface
- # are actually strings, i.e. the labels), these parameters are for some
- # reason being parsed as strings, so we need to map them back to an
- # integer.
- #
- #_plcobj.LogMessage("Modbus web server extension::coerce " + val )
- return int(val)
-
- def __init__(self, *args, **kwargs):
- annotate.Choice.__init__(self,
- choices = self._choices,
- stringify = self.choice_to_label,
- *args, **kwargs)
-
-
-
-# Parameters we will need to get from the C code, but that will not be shown
-# on the web interface. Common to all modbus entry types (client/server, tcp/rtu/ascii)
-#
-# The annotate type entry is basically useless and is completely ignored.
-# We kee that entry so that this list can later be correctly merged with the
-# following lists...
-General_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, ...)
- ("config_name" , _("") , ctypes.c_char_p, annotate.String),
- ("addr_type" , _("") , ctypes.c_char_p, annotate.String)
- ]
-
-# Parameters we will need to get from the C code, and that _will_ be shown
-# on the web interface.
-TCPclient_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, ...)
- ("host" , _("Remote IP Address") , ctypes.c_char_p, MB_StrippedString),
- ("port" , _("Remote Port Number") , ctypes.c_char_p, MB_StrippedString),
- ("comm_period" , _("Invocation Rate (ms)") , ctypes.c_ulonglong, annotate.Integer )
- ]
-
-RTUclient_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, ...)
- ("device" , _("Serial Port") , ctypes.c_char_p, MB_StrippedString),
- ("baud" , _("Baud Rate") , ctypes.c_int, MB_Baud ),
- ("parity" , _("Parity") , ctypes.c_int, MB_Parity ),
- ("stop_bits" , _("Stop Bits") , ctypes.c_int, MB_StopBits ),
- ("comm_period" , _("Invocation Rate (ms)") , ctypes.c_ulonglong, annotate.Integer)
- ]
-
-TCPserver_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, ...)
- ("host" , _("Local IP Address") , ctypes.c_char_p, MB_StrippedString),
- ("port" , _("Local Port Number") , ctypes.c_char_p, MB_StrippedString),
- ("slave_id" , _("Slave ID") , ctypes.c_ubyte, annotate.Integer )
- ]
-
-RTUslave_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, ...)
- ("device" , _("Serial Port") , ctypes.c_char_p, MB_StrippedString),
- ("baud" , _("Baud Rate") , ctypes.c_int, MB_Baud ),
- ("parity" , _("Parity") , ctypes.c_int, MB_Parity ),
- ("stop_bits" , _("Stop Bits") , ctypes.c_int, MB_StopBits ),
- ("slave_id" , _("Slave ID") , ctypes.c_ulonglong, annotate.Integer)
- ]
-
-
-
-
-# Dictionary containing List of Web viewable parameters
-# Note: the dictionary key must be the same as the string returned by the
-# __modbus_get_ClientNode_addr_type()
-# __modbus_get_ServerNode_addr_type()
-# functions implemented in C (see modbus/mb_runtime.c)
-_client_WebParamListDict = {}
-_client_WebParamListDict["tcp" ] = TCPclient_parameters
-_client_WebParamListDict["rtu" ] = RTUclient_parameters
-_client_WebParamListDict["ascii"] = [] # (Note: ascii not yet implemented in Beremiz modbus plugin)
-
-_server_WebParamListDict = {}
-_server_WebParamListDict["tcp" ] = TCPserver_parameters
-_server_WebParamListDict["rtu" ] = RTUslave_parameters
-_server_WebParamListDict["ascii"] = [] # (Note: ascii not yet implemented in Beremiz modbus plugin)
-
-WebParamListDictDict = {}
-WebParamListDictDict['client'] = _client_WebParamListDict
-WebParamListDictDict['server'] = _server_WebParamListDict
-
-
-
-
-
-
-def _SetSavedConfiguration(WebNode_id, newConfig):
- """ Stores a dictionary in a persistant file containing the Modbus parameter configuration """
-
- # Add the addr_type and node_type to the data that will be saved to file
- # This allows us to confirm the saved data contains the correct addr_type
- # when loading from file
- save_info = {}
- save_info["addr_type"] = _WebNodeList[WebNode_id]["addr_type"]
- save_info["node_type"] = _WebNodeList[WebNode_id]["node_type"]
- save_info["config" ] = newConfig
-
- filename = _WebNodeList[WebNode_id]["filename"]
-
- with open(os.path.realpath(filename), 'w') as f:
- json.dump(save_info, f, sort_keys=True, indent=4)
-
- _WebNodeList[WebNode_id]["SavedConfiguration"] = newConfig
-
-
-
-
-def _DelSavedConfiguration(WebNode_id):
- """ Deletes the file cotaining the persistent Modbus configuration """
- filename = _WebNodeList[WebNode_id]["filename"]
-
- if os.path.exists(filename):
- os.remove(filename)
-
-
-
-
-def _GetSavedConfiguration(WebNode_id):
- """
- Returns a dictionary containing the Modbus parameter configuration
- that was last saved to file. If no file exists, or file contains
- wrong addr_type (i.e. 'tcp', 'rtu' or 'ascii' -> does not match the
- addr_type of the WebNode_id), then return None
- """
- filename = _WebNodeList[WebNode_id]["filename"]
- try:
- #if os.path.isfile(filename):
- save_info = json.load(open(filename))
- except Exception:
- return None
-
- if save_info["addr_type"] != _WebNodeList[WebNode_id]["addr_type"]:
- return None
- if save_info["node_type"] != _WebNodeList[WebNode_id]["node_type"]:
- return None
- if "config" not in save_info:
- return None
-
- saved_config = save_info["config"]
-
- #if _CheckConfiguration(saved_config):
- # return saved_config
- #else:
- # return None
-
- return saved_config
-
-
-
-def _GetPLCConfiguration(WebNode_id):
- """
- Returns a dictionary containing the current Modbus parameter configuration
- stored in the C variables in the loaded PLC (.so file)
- """
- current_config = {}
- C_node_id = _WebNodeList[WebNode_id]["C_node_id"]
- WebParamList = _WebNodeList[WebNode_id]["WebParamList"]
- GetParamFuncs = _WebNodeList[WebNode_id]["GetParamFuncs"]
-
- for par_name, x1, x2, x3 in WebParamList:
- value = GetParamFuncs[par_name](C_node_id)
- if value is not None:
- current_config[par_name] = value
-
- return current_config
-
-
-
-def _SetPLCConfiguration(WebNode_id, newconfig):
- """
- Stores the Modbus parameter configuration into the
- the C variables in the loaded PLC (.so file)
- """
- C_node_id = _WebNodeList[WebNode_id]["C_node_id"]
- SetParamFuncs = _WebNodeList[WebNode_id]["SetParamFuncs"]
-
- for par_name in newconfig:
- value = newconfig[par_name]
- if value is not None:
- SetParamFuncs[par_name](C_node_id, value)
-
-
-
-
-def _GetWebviewConfigurationValue(ctx, WebNode_id, argument):
- """
- Callback function, called by the web interface (NevowServer.py)
- to fill in the default value of each parameter of the web form
-
- Note that the real callback function is a dynamically created function that
- will simply call this function to do the work. It will also pass the WebNode_id
- as a parameter.
- """
- try:
- return _WebNodeList[WebNode_id]["WebviewConfiguration"][argument.name]
- except Exception:
- return ""
-
-
-
-
-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
- The function will configure the Modbus plugin in the PLC with the values
- specified in the web interface. However, values must be validated first!
-
- Note that this function does not get called directly. The real callback
- function is the dynamic __OnButtonSave() function, which will add the
- "WebNode_id" argument, and call this function to do the work.
- """
-
- #_plcobj.LogMessage("Modbus web server extension::OnButtonSave() Called")
-
- newConfig = {}
- WebNode_id = kwargs.get("WebNode_id", None)
- WebParamList = _WebNodeList[WebNode_id]["WebParamList"]
-
- for par_name, x1, x2, x3 in WebParamList:
- value = kwargs.get(par_name, None)
- if value is not None:
- newConfig[par_name] = value
-
- # First check if configuration is OK.
- # Note that this is not currently required, as we use drop down choice menus
- # for baud, parity and sop bits, so the values should always be correct!
- #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(WebNode_id, newConfig)
-
- # Configure PLC with the current Modbus parameters
- _SetPLCConfiguration(WebNode_id, newConfig)
-
- # Update the viewable configuration
- # The PLC may have coerced the values on calling _SetPLCConfiguration()
- # 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):
- """
- Function called when user clicks 'Delete' button in web interface
- The function will delete the file containing the persistent
- Modbus configution
- """
-
- WebNode_id = kwargs.get("WebNode_id", None)
-
- # Delete the file
- _DelSavedConfiguration(WebNode_id)
-
- # Set the current configuration to the default (hardcoded in C)
- new_config = _WebNodeList[WebNode_id]["DefaultConfiguration"]
- _SetPLCConfiguration(WebNode_id, new_config)
-
- #Update the webviewconfiguration
- _WebNodeList[WebNode_id]["WebviewConfiguration"] = new_config
-
- # 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)
-
-
-
-
-def _AddWebNode(C_node_id, node_type, GetParamFuncs, SetParamFuncs):
- """
- Load from the compiled code (.so file, aloready loaded into memmory)
- the configuration parameters of a specific Modbus plugin node.
- This function works with both client and server nodes, depending on the
- Get/SetParamFunc dictionaries passed to it (either the client or the server
- node versions of the Get/Set functions)
- """
- WebNode_entry = {}
-
- # Get the config_name from the C code...
- config_name = GetParamFuncs["config_name"](C_node_id)
- # Get the addr_type from the C code...
- # addr_type will be one of "tcp", "rtu" or "ascii"
- addr_type = GetParamFuncs["addr_type" ](C_node_id)
- # For some operations we cannot use the config name (e.g. filename to store config)
- # because the user may be using characters that are invalid for that purpose ('/' for
- # example), so we create a hash of the config_name, and use that instead.
- config_hash = hashlib.md5(config_name).hexdigest()
-
- #_plcobj.LogMessage("Modbus web server extension::_AddWebNode("+str(C_node_id)+") config_name="+config_name)
-
- # Add the new entry to the global list
- # Note: it is OK, and actually necessary, to do this _before_ seting all the parameters in WebNode_entry
- # WebNode_entry will be stored as a reference, so we can later insert parameters at will.
- global _WebNodeList
- _WebNodeList.append(WebNode_entry)
- WebNode_id = len(_WebNodeList) - 1
-
- # store all WebNode relevant data for future reference
- #
- # Note that "WebParamList" will reference one of:
- # - TCPclient_parameters, TCPserver_parameters, RTUclient_parameters, RTUslave_parameters
- WebNode_entry["C_node_id" ] = C_node_id
- WebNode_entry["config_name" ] = config_name
- WebNode_entry["config_hash" ] = config_hash
- WebNode_entry["filename" ] = os.path.join(_ModbusConfFiledir, "Modbus_config_" + config_hash + ".json")
- WebNode_entry["GetParamFuncs"] = GetParamFuncs
- WebNode_entry["SetParamFuncs"] = SetParamFuncs
- WebNode_entry["WebParamList" ] = WebParamListDictDict[node_type][addr_type]
- WebNode_entry["addr_type" ] = addr_type # 'tcp', 'rtu', or 'ascii' (as returned by C function)
- WebNode_entry["node_type" ] = node_type # 'client', 'server'
-
-
- # Dictionary that contains the Modbus 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 an edited configuration that contains an error.
- WebNode_entry["WebviewConfiguration"] = None
-
- # Upon PLC load, this Dictionary is initialised with the Modbus configuration
- # hardcoded in the C file
- # (i.e. the configuration inserted in Beremiz IDE when project was compiled)
- WebNode_entry["DefaultConfiguration"] = _GetPLCConfiguration(WebNode_id)
- WebNode_entry["WebviewConfiguration"] = WebNode_entry["DefaultConfiguration"]
-
- # Dictionary that stores the Modbus 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)
- SavedConfig = _GetSavedConfiguration(WebNode_id)
- WebNode_entry["SavedConfiguration"] = SavedConfig
-
- if SavedConfig is not None:
- _SetPLCConfiguration(WebNode_id, SavedConfig)
- WebNode_entry["WebviewConfiguration"] = SavedConfig
-
- # Define the format for the web form used to show/change the current parameters
- # We first declare a dynamic function to work as callback to obtain the default values for each parameter
- # Note: We transform every parameter into a string
- # This is not strictly required for parameters of type annotate.Integer that will correctly
- # accept the default value as an Integer python object
- # This is obviously also not required for parameters of type annotate.String, that are
- # always handled as strings.
- # However, the annotate.Choice parameters (and all parameters that derive from it,
- # sucn as Parity, Baud, etc.) require the default value as a string
- # even though we store it as an integer, which is the data type expected
- # by the set_***() C functions in mb_runtime.c
- def __GetWebviewConfigurationValue(ctx, argument):
- return str(_GetWebviewConfigurationValue(ctx, WebNode_id, argument))
-
- webFormInterface = [(name, web_dtype (label=web_label, default=__GetWebviewConfigurationValue))
- for name, web_label, c_dtype, web_dtype in WebNode_entry["WebParamList"]]
-
- # Configure the web interface to include the Modbus config parameters
- def __OnButtonSave(**kwargs):
- OnButtonSave(WebNode_id=WebNode_id, **kwargs)
-
- _NS.ConfigurableSettings.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
- __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 OnLoadPLC():
- """
- Callback function, called (by PLCObject.py) when a new PLC program
- (i.e. XXX.so file) is transfered to the PLC runtime
- and loaded into memory
- """
-
- #_plcobj.LogMessage("Modbus 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 number of Modbus Client and Servers (Modbus plugin)
- # configured in the currently loaded PLC project (i.e., the .so file)
- # If the "__modbus_plugin_client_node_count"
- # or the "__modbus_plugin_server_node_count" C variables
- # are not present in the .so file we conclude that the currently loaded
- # PLC does not have the Modbus plugin included (situation (2b) described above init())
- try:
- client_count = ctypes.c_int.in_dll(_plcobj.PLClibraryHandle, "__modbus_plugin_client_node_count").value
- server_count = ctypes.c_int.in_dll(_plcobj.PLClibraryHandle, "__modbus_plugin_server_node_count").value
- except Exception:
- # Loaded PLC does not have the Modbus plugin => nothing to do
- # (i.e. do _not_ configure and make available the Modbus web interface)
- return
-
- if client_count < 0: client_count = 0
- if server_count < 0: server_count = 0
-
- if (client_count == 0) and (server_count == 0):
- # The Modbus plugin in the loaded PLC does not have any client and servers configured
- # => nothing to do (i.e. do _not_ configure and make available the Modbus web interface)
- return
-
- # Map the get/set functions (written in C code) we will be using to get/set the configuration parameters
- # Will contain references to the C functions (implemented in beremiz/modbus/mb_runtime.c)
- GetClientParamFuncs = {}
- SetClientParamFuncs = {}
- GetServerParamFuncs = {}
- SetServerParamFuncs = {}
-
- for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters + General_parameters:
- ParamFuncName = "__modbus_get_ClientNode_" + name
- GetClientParamFuncs[name] = getattr(_plcobj.PLClibraryHandle, ParamFuncName)
- GetClientParamFuncs[name].restype = c_dtype
- GetClientParamFuncs[name].argtypes = [ctypes.c_int]
-
- for name, web_label, c_dtype, web_dtype in TCPclient_parameters + RTUclient_parameters:
- ParamFuncName = "__modbus_set_ClientNode_" + name
- SetClientParamFuncs[name] = getattr(_plcobj.PLClibraryHandle, ParamFuncName)
- SetClientParamFuncs[name].restype = None
- SetClientParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
-
- for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters + General_parameters:
- ParamFuncName = "__modbus_get_ServerNode_" + name
- GetServerParamFuncs[name] = getattr(_plcobj.PLClibraryHandle, ParamFuncName)
- GetServerParamFuncs[name].restype = c_dtype
- GetServerParamFuncs[name].argtypes = [ctypes.c_int]
-
- for name, web_label, c_dtype, web_dtype in TCPserver_parameters + RTUslave_parameters:
- ParamFuncName = "__modbus_set_ServerNode_" + name
- SetServerParamFuncs[name] = getattr(_plcobj.PLClibraryHandle, ParamFuncName)
- SetServerParamFuncs[name].restype = None
- SetServerParamFuncs[name].argtypes = [ctypes.c_int, c_dtype]
-
- for node_id in range(client_count):
- _AddWebNode(node_id, "client" ,GetClientParamFuncs, SetClientParamFuncs)
-
- for node_id in range(server_count):
- _AddWebNode(node_id, "server", GetServerParamFuncs, SetServerParamFuncs)
-
-
-
-
-
-def OnUnLoadPLC():
- """
- Callback function, called (by PLCObject.py) when a PLC program is unloaded from memory
- """
-
- #_plcobj.LogMessage("Modbus web server extension::OnUnLoadPLC() Called...")
-
- # Delete the Modbus specific web interface extensions
- # (Safe to ask to delete, even if it has not been added!)
- 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)
-
- # Dele all entries...
- _WebNodeList = []
-
-
-
-# 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 Modbus plugin
-# (b) The loaded PLC does not have the Modbus plugin
-#
-# During (1) and (2a):
-# we configure the web server interface to not have the Modbus web configuration extension
-# During (2b)
-# we configure the web server interface to include the Modbus web configuration extension
-#
-# PS: reference to the pyroserver (i.e., the server object of Beremiz_service.py)
-# (NOTE: PS.plcobj is a reference to 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):
- #PS.plcobj.LogMessage("Modbus web server extension::init(PS, NS, " + WorkingDir + ") Called")
- global _WorkingDir
- _WorkingDir = WorkingDir
- global _plcobj
- _plcobj = plcobj
- global _NS
- _NS = NS
-
- _plcobj.RegisterCallbackLoad ("Modbus_Settins_Extension", OnLoadPLC)
- _plcobj.RegisterCallbackUnLoad("Modbus_Settins_Extension", OnUnLoadPLC)
- OnUnLoadPLC() # init is called before the PLC gets loaded... so we make sure we have the correct state
--- a/runtime/NevowServer.py Thu Jan 28 14:50:26 2021 +0000
+++ b/runtime/NevowServer.py Thu Jan 28 14:51:16 2021 +0000
@@ -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(display, token):
+ global extensions_settings_od
+ settings = ConfigurableBindings()
+ extensions_settings_od[token] = (settings, display)
+ return settings
+
+def removeExtensionSetting(token):
+ global extensions_settings_od
+ extensions_settings_od.pop(token)
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,27 @@
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_'):
+ token = name[13:]
+ def configurable_something(ctx):
+ settings, _display = extensions_settings_od[token]
+ return settings
+ 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 token in extensions_settings_od:
+ _settings, display = extensions_settings_od[token]
+ res += [tags.h2[display], webform.renderForms(token)]
+ return res
docFactory = loaders.stan([tags.html[
tags.head[
@@ -260,12 +270,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):
--- a/runtime/PLCObject.py Thu Jan 28 14:50:26 2021 +0000
+++ b/runtime/PLCObject.py Thu Jan 28 14:51:16 2021 +0000
@@ -99,9 +99,6 @@
self.TraceLock = Lock()
self.Traces = []
self.DebugToken = 0
- # Callbacks used by web settings extensions (e.g.: BACnet_config.py, Modbus_config.py)
- self.LoadCallbacks = {} # list of functions to call when PLC is loaded
- self.UnLoadCallbacks = {} # list of functions to call when PLC is unloaded
self._init_blobs()
@@ -170,22 +167,6 @@
return self._loading_error, 0, 0, 0
return None
- def RegisterCallbackLoad(self, ExtensionName, ExtensionCallback):
- """
- Register function to be called when PLC is loaded
- ExtensionName: a string with the name of the extension asking to register the callback
- ExtensionCallback: the function to be called...
- """
- self.LoadCallbacks[ExtensionName] = ExtensionCallback
-
- def RegisterCallbackUnLoad(self, ExtensionName, ExtensionCallback):
- """
- Register function to be called when PLC is unloaded
- ExtensionName: a string with the name of the extension asking to register the callback
- ExtensionCallback: the function to be called...
- """
- self.UnLoadCallbacks[ExtensionName] = ExtensionCallback
-
def _GetMD5FileName(self):
return os.path.join(self.workingdir, "lasttransferedPLC.md5")
@@ -289,8 +270,6 @@
res = self._LoadPLC()
if res:
self.PythonRuntimeInit()
- for name, callbackFunc in self.LoadCallbacks.items():
- callbackFunc()
else:
self._FreePLC()
@@ -299,8 +278,6 @@
@RunInMain
def UnLoadPLC(self):
self.PythonRuntimeCleanup()
- for name, callbackFunc in self.UnLoadCallbacks.items():
- callbackFunc()
self._FreePLC()
def _InitPLCStubCalls(self):
@@ -474,17 +451,27 @@
self.PythonThreadCond.notify()
self.PythonThreadCondLock.release()
+ def _fail(self, msg):
+ self.LogMessage(0, msg)
+ self.PLCStatus = PlcStatus.Broken
+ self.StatusChange()
+
+ def PreStartPLC(self):
+ """
+ Here goes actions to be taken just before PLC starts,
+ with all libraries and python object already created.
+ For example : restore saved proprietary parameters
+ """
+ pass
+
@RunInMain
def StartPLC(self):
- def fail(msg):
- self.LogMessage(0, msg)
- self.PLCStatus = PlcStatus.Broken
- self.StatusChange()
-
if self.PLClibraryHandle is None:
if not self.LoadPLC():
- fail(_("Problem starting PLC : can't load PLC"))
+ self._fail(_("Problem starting PLC : can't load PLC"))
+
+ self.PreStartPLC()
if self.CurrentPLCFilename is not None and self.PLCStatus == PlcStatus.Stopped:
c_argv = ctypes.c_char_p * len(self.argv)
@@ -495,7 +482,7 @@
self.PythonThreadCommand("Activate")
self.LogMessage("PLC started")
else:
- fail(_("Problem starting PLC : error %d" % res))
+ self._fail(_("Problem starting PLC : error %d" % res))
@RunInMain
def StopPLC(self):
@@ -533,7 +520,7 @@
@RunInMain
def SeedBlob(self, seed):
blob = (mkstemp(dir=self.tmpdir) + (hashlib.new('md5'),))
- _fobj, _path, md5sum = blob
+ _fd, _path, md5sum = blob
md5sum.update(seed)
newBlobID = md5sum.digest()
self.blobs[newBlobID] = blob
@@ -546,17 +533,17 @@
if blob is None:
return None
- fobj, _path, md5sum = blob
+ fd, _path, md5sum = blob
md5sum.update(data)
newBlobID = md5sum.digest()
- os.write(fobj, data)
+ os.write(fd, data)
self.blobs[newBlobID] = blob
return newBlobID
@RunInMain
def PurgeBlobs(self):
- for fobj, _path, _md5sum in self.blobs.values():
- os.close(fobj)
+ for fd, _path, _md5sum in self.blobs.values():
+ os.close(fd)
self._init_blobs()
def _BlobAsFile(self, blobID, newpath):
@@ -565,8 +552,11 @@
if blob is None:
raise Exception(_("Missing data to create file: {}").format(newpath))
- fobj, path, _md5sum = blob
- os.close(fobj)
+ fd, path, _md5sum = blob
+ fobj = os.fdopen(fd)
+ fobj.flush()
+ os.fsync(fd)
+ fobj.close()
shutil.move(path, newpath)
def _extra_files_log_path(self):
@@ -622,9 +612,6 @@
# Create new PLC file
self._BlobAsFile(plc_object, new_PLC_filename)
- # Store new PLC filename based on md5 key
- open(self._GetMD5FileName(), "w").write(md5sum)
-
# Then write the files
log = open(extra_files_log, "w")
for fname, blobID in extrafiles:
@@ -632,6 +619,12 @@
self._BlobAsFile(blobID, fpath)
log.write(fname+'\n')
+ # Store new PLC filename based on md5 key
+ with open(self._GetMD5FileName(), "w") as f:
+ f.write(md5sum)
+ f.flush()
+ os.fsync(f.fileno())
+
# Store new PLC filename
self.CurrentPLCFilename = NewFileName
except Exception:
--- a/tests/python/plc.xml Thu Jan 28 14:50:26 2021 +0000
+++ b/tests/python/plc.xml Thu Jan 28 14:51:16 2021 +0000
@@ -1,7 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<project xmlns="http://www.plcopen.org/xml/tc6_0201" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xsi:schemaLocation="http://www.plcopen.org/xml/tc6_0201">
<fileHeader companyName="" productName="Beremiz" productVersion="0.0" creationDateTime="2008-12-14T16:21:19" contentDescription="This example shows many features in Beremiz: 1. How to implement python extensions. 2. How to implement basic C extension. 3. How to use C code in IEC POUs. 4. How to call C functions from python code. 5. How to avoid race conditions between IEC, C and python code. 6. How to convert betweet different IEC types. "/>
- <contentHeader name="Beremiz Python Support Tests" modificationDateTime="2019-09-24T11:49:14">
+ <contentHeader name="Beremiz Python Support Tests" modificationDateTime="2020-06-17T13:19:14">
<coordinateInfo>
<pageSize x="1024" y="1024"/>
<fbd>
@@ -246,6 +246,25 @@
<INT/>
</type>
</variable>
+ <variable name="Grumpf">
+ <type>
+ <string/>
+ </type>
+ </variable>
+ </externalVars>
+ <localVars>
+ <variable name="RTC0">
+ <type>
+ <derived name="RTC"/>
+ </type>
+ </variable>
+ </localVars>
+ <externalVars>
+ <variable name="SomeVarName">
+ <type>
+ <DINT/>
+ </type>
+ </variable>
</externalVars>
</interface>
<body>
@@ -255,7 +274,7 @@
<connectionPointOut>
<relPosition x="160" y="15"/>
</connectionPointOut>
- <expression>'time.sleep(1)'</expression>
+ <expression>'666'</expression>
</inVariable>
<block localId="5" width="125" height="80" typeName="python_eval" instanceName="py1" executionOrderId="0">
<position x="686" y="400"/>
@@ -1118,23 +1137,12 @@
</connectionPointOut>
<expression>Second_Python_Var</expression>
</inVariable>
- <outVariable localId="47" height="30" width="130" executionOrderId="0" negated="false">
- <position x="200" y="1385"/>
- <connectionPointIn>
- <relPosition x="0" y="15"/>
- <connection refLocalId="59">
- <position x="200" y="1400"/>
- <position x="130" y="1400"/>
- </connection>
- </connectionPointIn>
- <expression>Test_Python_Var</expression>
- </outVariable>
<inVariable localId="59" height="30" width="30" executionOrderId="0" negated="false">
<position x="100" y="1385"/>
<connectionPointOut>
<relPosition x="30" y="15"/>
</connectionPointOut>
- <expression>23</expression>
+ <expression>1</expression>
</inVariable>
<block localId="61" typeName="function0" executionOrderId="0" height="45" width="111">
<position x="760" y="1170"/>
@@ -1300,6 +1308,162 @@
]]></xhtml:p>
</content>
</comment>
+ <outVariable localId="72" executionOrderId="0" height="30" width="60" negated="false">
+ <position x="1065" y="1970"/>
+ <connectionPointIn>
+ <relPosition x="0" y="15"/>
+ <connection refLocalId="76" formalParameter="OUT">
+ <position x="1065" y="1985"/>
+ <position x="1025" y="1985"/>
+ <position x="1025" y="1995"/>
+ <position x="985" y="1995"/>
+ </connection>
+ </connectionPointIn>
+ <expression>Grumpf</expression>
+ </outVariable>
+ <inVariable localId="73" executionOrderId="0" height="30" width="85" negated="false">
+ <position x="625" y="1940"/>
+ <connectionPointOut>
+ <relPosition x="85" y="15"/>
+ </connectionPointOut>
+ <expression>BOOL#TRUE</expression>
+ </inVariable>
+ <inVariable localId="74" executionOrderId="0" height="30" width="70" negated="false">
+ <position x="625" y="1975"/>
+ <connectionPointOut>
+ <relPosition x="70" y="15"/>
+ </connectionPointOut>
+ <expression>Test_DT</expression>
+ </inVariable>
+ <block localId="75" typeName="RTC" instanceName="RTC0" executionOrderId="0" height="90" width="65">
+ <position x="760" y="1925"/>
+ <inputVariables>
+ <variable formalParameter="IN">
+ <connectionPointIn>
+ <relPosition x="0" y="35"/>
+ <connection refLocalId="73">
+ <position x="760" y="1960"/>
+ <position x="735" y="1960"/>
+ <position x="735" y="1955"/>
+ <position x="710" y="1955"/>
+ </connection>
+ </connectionPointIn>
+ </variable>
+ <variable formalParameter="PDT">
+ <connectionPointIn>
+ <relPosition x="0" y="70"/>
+ <connection refLocalId="74">
+ <position x="760" y="1995"/>
+ <position x="727" y="1995"/>
+ <position x="727" y="1990"/>
+ <position x="695" y="1990"/>
+ </connection>
+ </connectionPointIn>
+ </variable>
+ </inputVariables>
+ <inOutVariables/>
+ <outputVariables>
+ <variable formalParameter="Q">
+ <connectionPointOut>
+ <relPosition x="65" y="35"/>
+ </connectionPointOut>
+ </variable>
+ <variable formalParameter="CDT">
+ <connectionPointOut>
+ <relPosition x="65" y="70"/>
+ </connectionPointOut>
+ </variable>
+ </outputVariables>
+ </block>
+ <block localId="76" typeName="DT_TO_STRING" executionOrderId="0" height="40" width="110">
+ <position x="875" y="1965"/>
+ <inputVariables>
+ <variable formalParameter="IN">
+ <connectionPointIn>
+ <relPosition x="0" y="30"/>
+ <connection refLocalId="75" formalParameter="CDT">
+ <position x="875" y="1995"/>
+ <position x="825" y="1995"/>
+ </connection>
+ </connectionPointIn>
+ </variable>
+ </inputVariables>
+ <inOutVariables/>
+ <outputVariables>
+ <variable formalParameter="OUT">
+ <connectionPointOut>
+ <relPosition x="110" y="30"/>
+ </connectionPointOut>
+ </variable>
+ </outputVariables>
+ </block>
+ <block localId="77" typeName="ADD" executionOrderId="0" height="60" width="65">
+ <position x="170" y="1370"/>
+ <inputVariables>
+ <variable formalParameter="IN1">
+ <connectionPointIn>
+ <relPosition x="0" y="30"/>
+ <connection refLocalId="59">
+ <position x="170" y="1400"/>
+ <position x="130" y="1400"/>
+ </connection>
+ </connectionPointIn>
+ </variable>
+ <variable formalParameter="IN2">
+ <connectionPointIn>
+ <relPosition x="0" y="50"/>
+ <connection refLocalId="78">
+ <position x="170" y="1420"/>
+ <position x="160" y="1420"/>
+ <position x="160" y="1450"/>
+ <position x="390" y="1450"/>
+ <position x="390" y="1400"/>
+ <position x="380" y="1400"/>
+ </connection>
+ </connectionPointIn>
+ </variable>
+ </inputVariables>
+ <inOutVariables/>
+ <outputVariables>
+ <variable formalParameter="OUT">
+ <connectionPointOut>
+ <relPosition x="65" y="30"/>
+ </connectionPointOut>
+ </variable>
+ </outputVariables>
+ </block>
+ <outVariable localId="47" executionOrderId="0" height="30" width="130" negated="false">
+ <position x="625" y="1335"/>
+ <connectionPointIn>
+ <relPosition x="0" y="15"/>
+ <connection refLocalId="79">
+ <position x="625" y="1350"/>
+ <position x="590" y="1350"/>
+ </connection>
+ </connectionPointIn>
+ <expression>Test_Python_Var</expression>
+ </outVariable>
+ <inVariable localId="79" executionOrderId="0" height="25" width="30" negated="false">
+ <position x="560" y="1340"/>
+ <connectionPointOut>
+ <relPosition x="30" y="10"/>
+ </connectionPointOut>
+ <expression>23</expression>
+ </inVariable>
+ <inOutVariable localId="78" executionOrderId="0" height="30" width="100" negatedOut="false" negatedIn="false">
+ <position x="280" y="1385"/>
+ <connectionPointIn>
+ <relPosition x="0" y="15"/>
+ <connection refLocalId="77" formalParameter="OUT">
+ <position x="280" y="1400"/>
+ <position x="235" y="1400"/>
+ </connection>
+ </connectionPointIn>
+ <connectionPointOut>
+ <relPosition x="100" y="15"/>
+ </connectionPointOut>
+ <expression>SomeVarName</expression>
+ </inOutVariable>
</FBD>
</body>
</pou>
@@ -1447,7 +1611,7 @@
<configurations>
<configuration name="config">
<resource name="res_pytest">
- <task name="pytest_task" interval="T#1ms" priority="0"/>
+ <task name="pytest_task" priority="0" interval="T#500ms"/>
<globalVars>
<variable name="TOTO">
<type>
--- a/tests/python/py_ext_0@py_ext/pyfile.xml Thu Jan 28 14:50:26 2021 +0000
+++ b/tests/python/py_ext_0@py_ext/pyfile.xml Thu Jan 28 14:51:16 2021 +0000
@@ -1,13 +1,17 @@
<?xml version='1.0' encoding='utf-8'?>
<PyFile xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<variables>
- <variable name="SomeVarName" type="DINT"/>
- <variable name="Grumpf" type="STRING"/>
+ <variable name="SomeVarName" type="DINT" onchange="MyFunc"/>
+ <variable name="Grumpf" type="STRING" initial="'mhoo'" onchange="MyFunc"/>
</variables>
<globals>
<xhtml:p><![CDATA[
print "All python PLC globals variables :", PLCGlobalsDesc
print "Current extention name :", __ext_name__
+
+def MyFunc(*args):
+ print args
+
]]></xhtml:p>
</globals>
<init>
--- a/tests/python/python@py_ext/pyfile.xml Thu Jan 28 14:50:26 2021 +0000
+++ b/tests/python/python@py_ext/pyfile.xml Thu Jan 28 14:51:16 2021 +0000
@@ -20,6 +20,7 @@
print "Failed Python_to_C_Call failed"
res = None
print "Python read PLC global :",PLCGlobals.Test_Python_Var
+ print "Python read PLC global Grumpf :",PLCGlobals.Grumpf
PLCGlobals.Second_Python_Var = 789
sys.stdout.flush()
return res
--- a/util/ProcessLogger.py Thu Jan 28 14:50:26 2021 +0000
+++ b/util/ProcessLogger.py Thu Jan 28 14:51:16 2021 +0000
@@ -31,6 +31,7 @@
from threading import Timer, Lock, Thread, Semaphore
import signal
+_debug = os.path.exists("BEREMIZ_DEBUG")
class outputThread(Thread):
"""
@@ -77,6 +78,7 @@
timeout=None, outlimit=None, errlimit=None,
endlog=None, keyword=None, kill_it=False, cwd=None,
encoding=None, output_encoding=None):
+ assert(logger)
self.logger = logger
if not isinstance(Command, list):
self.Command_str = Command
@@ -174,8 +176,9 @@
self.endlog()
def log_the_end(self, ecode, pid):
- self.logger.write(self.Command_str + "\n")
- self.logger.write_warning(_("exited with status {a1} (pid {a2})\n").format(a1=str(ecode), a2=str(pid)))
+ if self.logger is not None:
+ self.logger.write(self.Command_str + "\n")
+ self.logger.write_warning(_("exited with status {a1} (pid {a2})\n").format(a1=str(ecode), a2=str(pid)))
def finish(self, pid, ecode):
# avoid running function before start is finished
@@ -184,7 +187,7 @@
if self.timeout:
self.timeout.cancel()
self.exitcode = ecode
- if self.exitcode != 0:
+ if _debug or self.exitcode != 0:
self.log_the_end(ecode, pid)
if self.finish_callback is not None:
self.finish_callback(self, ecode, pid)