--- a/Beremiz_service.py Tue Feb 20 11:42:02 2024 +0100
+++ b/Beremiz_service.py Tue Feb 20 14:53:33 2024 +0100
@@ -36,7 +36,7 @@
from functools import partial
import runtime
-from runtime.PyroServer import PyroServer
+from runtime.eRPCServer import eRPCServer as RPCServer
from runtime.xenomai import TryPreloadXenomai
from runtime import LogMessageAndException
from runtime import PlcStatus
@@ -270,12 +270,12 @@
TBMENU_CHANGE_INTERFACE = wx.NewIdRef()
TBMENU_LIVE_SHELL = wx.NewIdRef()
TBMENU_WXINSPECTOR = wx.NewIdRef()
- TBMENU_CHANGE_WD = wx.NewIdRef()
+ # TBMENU_CHANGE_WD = wx.NewIdRef()
TBMENU_QUIT = wx.NewIdRef()
- def __init__(self, pyroserver):
+ def __init__(self, rpc_server):
wx.adv.TaskBarIcon.__init__(self)
- self.pyroserver = pyroserver
+ self.rpc_server = rpc_server
# Set the image
self.UpdateIcon(None)
@@ -287,7 +287,7 @@
self.Bind(wx.EVT_MENU, self.OnTaskBarLiveShell, id=self.TBMENU_LIVE_SHELL)
self.Bind(wx.EVT_MENU, self.OnTaskBarWXInspector, id=self.TBMENU_WXINSPECTOR)
self.Bind(wx.EVT_MENU, self.OnTaskBarChangePort, id=self.TBMENU_CHANGE_PORT)
- self.Bind(wx.EVT_MENU, self.OnTaskBarChangeWorkingDir, id=self.TBMENU_CHANGE_WD)
+ # self.Bind(wx.EVT_MENU, self.OnTaskBarChangeWorkingDir, id=self.TBMENU_CHANGE_WD)
self.Bind(wx.EVT_MENU, self.OnTaskBarQuit, id=self.TBMENU_QUIT)
def CreatePopupMenu(self):
@@ -304,7 +304,7 @@
menu.Append(self.TBMENU_CHANGE_NAME, _("Change Name"))
menu.Append(self.TBMENU_CHANGE_INTERFACE, _("Change IP of interface to bind"))
menu.Append(self.TBMENU_CHANGE_PORT, _("Change Port Number"))
- menu.Append(self.TBMENU_CHANGE_WD, _("Change working directory"))
+ # menu.Append(self.TBMENU_CHANGE_WD, _("Change working directory"))
menu.AppendSeparator()
menu.Append(self.TBMENU_LIVE_SHELL, _("Launch a live Python shell"))
menu.Append(self.TBMENU_WXINSPECTOR, _("Launch WX GUI inspector"))
@@ -332,37 +332,37 @@
runtime.GetPLCObjectSingleton().StopPLC()
def OnTaskBarChangeInterface(self, evt):
- ip_addr = self.pyroserver.ip_addr
+ ip_addr = self.rpc_server.ip_addr
ip_addr = '' if ip_addr is None else ip_addr
dlg = ParamsEntryDialog(None, _("Enter the IP of the interface to bind"), defaultValue=ip_addr)
dlg.SetTests([(re.compile(r'\d{1,3}(?:\.\d{1,3}){3}$').match, _("IP is not valid!")),
(lambda x:len([x for x in x.split(".") if 0 <= int(x) <= 255]) == 4,
_("IP is not valid!"))])
if dlg.ShowModal() == wx.ID_OK:
- self.pyroserver.ip_addr = dlg.GetValue()
- self.pyroserver.Restart()
+ self.rpc_server.ip_addr = dlg.GetValue()
+ self.rpc_server.Restart()
def OnTaskBarChangePort(self, evt):
- dlg = ParamsEntryDialog(None, _("Enter a port number "), defaultValue=str(self.pyroserver.port))
+ dlg = ParamsEntryDialog(None, _("Enter a port number "), defaultValue=str(self.rpc_server.port))
dlg.SetTests([(str.isdigit, _("Port number must be an integer!")), (lambda port: 0 <= int(port) <= 65535, _("Port number must be 0 <= port <= 65535!"))])
if dlg.ShowModal() == wx.ID_OK:
- self.pyroserver.port = int(dlg.GetValue())
- self.pyroserver.Restart()
-
- def OnTaskBarChangeWorkingDir(self, evt):
- dlg = wx.DirDialog(None, _("Choose a working directory "), self.pyroserver.workdir, wx.DD_NEW_DIR_BUTTON)
- if dlg.ShowModal() == wx.ID_OK:
- self.pyroserver.workdir = dlg.GetPath()
- self.pyroserver.Restart()
+ self.rpc_server.port = int(dlg.GetValue())
+ self.rpc_server.Restart()
+
+ # def OnTaskBarChangeWorkingDir(self, evt):
+ # dlg = wx.DirDialog(None, _("Choose a working directory "), self.rpc_server.workdir, wx.DD_NEW_DIR_BUTTON)
+ # if dlg.ShowModal() == wx.ID_OK:
+ # self.rpc_server.workdir = dlg.GetPath()
+ # self.rpc_server.Restart()
def OnTaskBarChangeName(self, evt):
- _servicename = self.pyroserver.servicename
+ _servicename = self.rpc_server.servicename
_servicename = '' if _servicename is None else _servicename
dlg = ParamsEntryDialog(None, _("Enter a name "), defaultValue=_servicename)
dlg.SetTests([(lambda name: len(name) != 0, _("Name must not be null!"))])
if dlg.ShowModal() == wx.ID_OK:
- self.pyroserver.servicename = dlg.GetValue()
- self.pyroserver.Restart()
+ self.rpc_server.servicename = dlg.GetValue()
+ self.rpc_server.Restart()
def _LiveShellLocals(self):
return {"locals": runtime.GetPLCObjectSingleton().python_runtime_vars}
@@ -383,7 +383,7 @@
def OnTaskBarQuit(self, evt):
if wx.Platform == '__WXMSW__':
- Thread(target=self.pyroserver.Quit).start()
+ Thread(target=self.rpc_server.Quit).start()
self.RemoveIcon()
wx.CallAfter(wx.GetApp().ExitMainLoop)
@@ -513,10 +513,10 @@
runtime.CreatePLCObjectSingleton(
WorkingDir, argv, statuschange, evaluator, pyruntimevars)
-pyroserver = PyroServer(servicename, interface, port)
+rpc_server = RPCServer(servicename, interface, port)
if havewx:
- taskbar_instance = BeremizTaskBarIcon(pyroserver)
+ taskbar_instance = BeremizTaskBarIcon(rpc_server)
if havetwisted:
if webport is not None:
@@ -533,28 +533,28 @@
except Exception:
LogMessageAndException(_("WAMP client startup failed. "))
-pyro_thread = None
+rpc_server_thread = None
def FirstWorkerJob():
"""
- RPC through pyro/wamp/UI may lead to delegation to Worker,
+ RPC through rpc/wamp/UI may lead to delegation to Worker,
then this function ensures that Worker is already
- created when pyro starts
+ created when rpc starts
"""
- global pyro_thread, pyroserver
-
- pyro_thread_started = Lock()
- pyro_thread_started.acquire()
- pyro_thread = Thread(target=pyroserver.PyroLoop,
- kwargs=dict(when_ready=pyro_thread_started.release),
- name="PyroThread")
-
- pyro_thread.start()
-
- # Wait for pyro thread to be effective
- pyro_thread_started.acquire()
-
- pyroserver.PrintServerInfo()
+ global rpc_server_thread, rpc_server
+
+ rpc_thread_started = Lock()
+ rpc_thread_started.acquire()
+ rpc_server_thread = Thread(target=rpc_server.Loop,
+ kwargs=dict(when_ready=rpc_thread_started.release),
+ name="RPCThread")
+
+ rpc_server_thread.start()
+
+ # Wait for rpc thread to be effective
+ rpc_thread_started.acquire()
+
+ rpc_server.PrintServerInfo()
# Beremiz IDE detects LOCAL:// runtime is ready by looking
# for self.workdir in the daemon's stdout.
@@ -616,8 +616,8 @@
pass
-pyroserver.Quit()
-pyro_thread.join()
+rpc_server.Quit()
+rpc_server_thread.join()
plcobj = runtime.GetPLCObjectSingleton()
try:
--- a/ProjectController.py Tue Feb 20 11:42:02 2024 +0100
+++ b/ProjectController.py Tue Feb 20 14:53:33 2024 +0100
@@ -60,7 +60,7 @@
from plcopen.structures import IEC_KEYWORDS
from plcopen.types_enums import ComputeConfigurationResourceName, ITEM_CONFNODE
import targets
-from runtime.typemapping import DebugTypesSize, UnpackDebugBuffer
+from runtime.typemapping import DebugTypesSize, UnpackDebugBuffer, ValueToIECBytes
from runtime import PlcStatus
from ConfigTreeNode import ConfigTreeNode, XSDSchemaErrorMessage
from POULibrary import UserAddressedException
@@ -1615,8 +1615,10 @@
2 : _("Debug: Too many variables forced. Max 256.\n"),
# FORCE_BUFFER_OVERFLOW
3 : _("Debug: Cumulated forced variables size too large. Max 1KB.\n"),
+ # FORCE_INVALID
+ 3 : _("Debug: Invalid forced value.\n"),
# DEBUG_SUSPENDED
- 4 : _("Debug: suspended.\n")
+ 5 : _("Debug: suspended.\n")
}
def RegisterDebugVarToConnector(self):
@@ -1637,7 +1639,9 @@
IECPath, (None, None))
if Idx is not None:
if IEC_Type in DebugTypesSize:
- Idxs.append((Idx, IEC_Type, fvalue, IECPath))
+ Idxs.append(
+ (Idx, IEC_Type, IECPath,
+ ValueToIECBytes(IEC_Type, fvalue)))
else:
self.logger.write_warning(
_("Debug: Unsupported type to debug '%s'\n") % IEC_Type)
@@ -1649,10 +1653,8 @@
if Idxs:
Idxs.sort()
- IdxsT = list(zip(*Idxs))
- self.TracedIECPath = IdxsT[3]
- self.TracedIECTypes = IdxsT[1]
- res = self._connector.SetTraceVariablesList(list(zip(*IdxsT[0:3])))
+ Idxs, self.TracedIECTypes, self.TracedIECPath, Fvalues, = list(zip(*Idxs))
+ res = self._connector.SetTraceVariablesList(list(zip(Idxs, Fvalues)))
if res is not None and res > 0:
self.DebugToken = res
else:
--- a/connectors/ConnectorBase.py Tue Feb 20 11:42:02 2024 +0100
+++ b/connectors/ConnectorBase.py Tue Feb 20 14:53:33 2024 +0100
@@ -10,7 +10,7 @@
class ConnectorBase(object):
- chuncksize = 1024*1024
+ chuncksize = 0xfff # 4KB
PLCObjDefaults = {
"StartPLC": False,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/connectors/ERPC/PSK_Adapter.py Tue Feb 20 14:53:33 2024 +0100
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This file is part of Beremiz, a Integrated Development Environment for
+# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
+#
+# Copyright (C) 2019: Edouard TISSERANT
+#
+# See COPYING file for copyrights details.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+
+"""
+The TLS-PSK adapter that handles SSL connections instead of regular sockets,
+but using Pre Shared Keys instead of Certificates
+"""
+
+import socket
+import ssl
+
+try:
+ import sslpsk
+except ImportError as e:
+ sslpsk = None
+
+from erpc.transport import TCPTransport
+
+class SSLPSKClientTransport(TCPTransport):
+ def __init__(self, host, port, psk):
+ """ overrides TCPTransport's __init__ to wrap socket in SSl wrapper """
+ super(TCPTransport, self).__init__()
+ self._host = host
+ self._port = port
+ self._isServer = isServer
+ self._sock = None
+
+ if sslpsk is None:
+ raise ImportError("sslpsk module is not available")
+
+ raw_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ raw_sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
+ raw_sock.connect((self._host, self._port))
+ self._sock = sslpsk.wrap_socket(
+ raw_sock, psk=psk, server_side=False,
+ ciphers="PSK-AES256-CBC-SHA", # available in openssl 1.0.2
+ ssl_version=ssl.PROTOCOL_TLSv1)
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/connectors/ERPC/__init__.py Tue Feb 20 14:53:33 2024 +0100
@@ -0,0 +1,171 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Written by Edouard TISSERANT (C) 2024
+# This file is part of Beremiz IDE
+# See COPYING file for copyrights details.
+
+
+import os.path
+import re
+import traceback
+from inspect import getmembers, isfunction
+
+
+import erpc
+
+# eRPC service code
+from erpc_interface.erpc_PLCObject.interface import IBeremizPLCObjectService
+from erpc_interface.erpc_PLCObject.client import BeremizPLCObjectServiceClient
+from erpc_interface.erpc_PLCObject.common import trace_order, extra_file, PLCstatus_enum
+
+import PSKManagement as PSK
+from connectors.ERPC.PSK_Adapter import SSLPSKClientTransport
+from connectors.ConnectorBase import ConnectorBase
+
+enum_to_PLCstatus = dict(map(lambda t:(t[1],t[0]),getmembers(PLCstatus_enum, lambda x:type(x)==int)))
+
+class MissingCallException(Exception):
+ pass
+
+def ExceptionFromERPCReturn(ret):
+ return {1:Exception,
+ 2:MissingCallException}.get(ret,ValueError)
+
+def ReturnAsLastOutput(client_method, obj, args_wrapper, *args):
+ retval = erpc.Reference()
+ ret = client_method(obj, *args_wrapper(*args), retval)
+ if ret != 0:
+ raise ExceptionFromERPCReturn(ret)(client_method.__name__)
+ return retval.value
+
+def TranslatedReturnAsLastOutput(translator):
+ def wrapper(client_method, obj, args_wrapper, *args):
+ res = ReturnAsLastOutput(client_method, obj, args_wrapper, *args)
+ return translator(res)
+ return wrapper
+
+ReturnWrappers = {
+ "AppendChunkToBlob":ReturnAsLastOutput,
+ "GetLogMessage":TranslatedReturnAsLastOutput(
+ lambda res:(res.msg, res.tick, res.sec, res.nsec)),
+ "GetPLCID":TranslatedReturnAsLastOutput(
+ lambda res:(res.ID, res.PSK)),
+ "GetPLCstatus":TranslatedReturnAsLastOutput(
+ lambda res:(enum_to_PLCstatus[res.PLCstatus], res.logcounts)),
+ "GetTraceVariables":TranslatedReturnAsLastOutput(
+ lambda res:(enum_to_PLCstatus[res.PLCstatus],
+ [(sample.tick, bytes(sample.TraceBuffer)) for sample in res.traces])),
+ "MatchMD5":ReturnAsLastOutput,
+ "NewPLC":ReturnAsLastOutput,
+ "SeedBlob":ReturnAsLastOutput,
+ "SetTraceVariablesList": ReturnAsLastOutput,
+ "StopPLC":ReturnAsLastOutput,
+}
+
+ArgsWrappers = {
+ "NewPLC":
+ lambda md5sum, plcObjectBlobID, extrafiles: (
+ md5sum, plcObjectBlobID, [extra_file(*f) for f in extrafiles]),
+ "SetTraceVariablesList":
+ lambda orders : ([
+ trace_order(idx, b"" if force is None else force)
+ for idx, force in orders],)
+}
+
+def ERPC_connector_factory(uri, confnodesroot):
+ """
+ returns the ERPC connector
+ """
+ confnodesroot.logger.write(_("ERPC connecting to URI : %s\n") % uri)
+
+ # TODO add parsing for serial URI
+ # ERPC:///dev/ttyXX:baudrate or ERPC://:COM4:baudrate
+
+ try:
+ _scheme, location = uri.split("://",1)
+ locator, *IDhash = location.split('#',1)
+ x = re.match(r'(?P<host>[^\s:]+):?(?P<port>\d+)?', locator)
+ host = x.group('host')
+ port = x.group('port')
+ if port:
+ port = int(port)
+ else:
+ port = 3000
+ except Exception as e:
+ confnodesroot.logger.write_error(
+ 'Malformed URI "%s": %s\n' % (uri, str(e)))
+ return None
+
+ def rpc_wrapper(method_name):
+ client_method = getattr(BeremizPLCObjectServiceClient, method_name)
+ return_wrapper = ReturnWrappers.get(
+ method_name,
+ lambda client_method, obj, args_wrapper, *args: client_method(obj, *args_wrapper(*args)))
+ args_wrapper = ArgsWrappers.get(method_name, lambda *x:x)
+
+ def exception_wrapper(self, *args):
+ try:
+ print("Clt "+method_name)
+ return return_wrapper(client_method, self, args_wrapper, *args)
+ except erpc.transport.ConnectionClosed as e:
+ confnodesroot._SetConnector(None)
+ confnodesroot.logger.write_error(_("Connection lost!\n"))
+ except erpc.codec.CodecError as e:
+ confnodesroot.logger.write_warning(_("ERPC codec error: %s\n") % e)
+ except erpc.client.RequestError as e:
+ confnodesroot.logger.write_error(_("ERPC request error: %s\n") % e)
+ except MissingCallException as e:
+ confnodesroot.logger.write_warning(_("Remote call not supported: %s\n") % e.message)
+ except Exception as e:
+ errmess = _("Exception calling remote PLC object fucntio %s:\n") % method_name \
+ + traceback.format_exc()
+ confnodesroot.logger.write_error(errmess + "\n")
+ print(errmess)
+ confnodesroot._SetConnector(None)
+
+ return self.PLCObjDefaults.get(method_name)
+ return exception_wrapper
+
+
+ PLCObjectERPCProxy = type(
+ "PLCObjectERPCProxy",
+ (ConnectorBase, BeremizPLCObjectServiceClient),
+ {name: rpc_wrapper(name)
+ for name,_func in getmembers(IBeremizPLCObjectService, isfunction)})
+
+ try:
+ if IDhash:
+ ID = IDhash[0]
+ # load PSK from project
+ secpath = os.path.join(str(confnodesroot.ProjectPath), 'psk', ID + '.secret')
+ if not os.path.exists(secpath):
+ confnodesroot.logger.write_error(
+ 'Error: Pre-Shared-Key Secret in %s is missing!\n' % secpath)
+ return None
+ secret = open(secpath).read().partition(':')[2].rstrip('\n\r')
+ transport = SSLPSKClientTransport(host, port, (secret, ID))
+ else:
+ # TODO if serial URI then
+ # transport = erpc.transport.SerialTransport(device, baudrate)
+
+ transport = erpc.transport.TCPTransport(host, port, False)
+
+ clientManager = erpc.client.ClientManager(transport, erpc.basic_codec.BasicCodec)
+ client = PLCObjectERPCProxy(clientManager)
+
+ except Exception as e:
+ confnodesroot.logger.write_error(
+ _("Connection to {loc} failed with exception {ex}\n").format(
+ loc=locator, ex=str(e)))
+ return None
+
+ # Check connection is effective.
+ IDPSK = client.GetPLCID()
+ if IDPSK:
+ ID, secret = IDPSK
+ PSK.UpdateID(confnodesroot.ProjectPath, ID, secret, uri)
+ else:
+ confnodesroot.logger.write_warning(_("PLC did not provide identity and security infomation.\n"))
+
+ return client
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/connectors/ERPC_dialog.py Tue Feb 20 14:53:33 2024 +0100
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# See COPYING file for copyrights details.
+
+
+
+from itertools import repeat, islice, chain
+
+from connectors.SchemeEditor import SchemeEditor
+
+
+model = [('host', _("Host:")),
+ ('port', _("Port:"))]
+
+# (scheme, model, secure)
+models = [("LOCAL", [], False), ("ERPC", model, False)]
+
+Schemes = list(zip(*models))[0]
+
+_PerSchemeConf = {sch: (mod, sec) for sch, mod, sec in models}
+
+
+class ERPC_dialog(SchemeEditor):
+ def __init__(self, scheme, *args, **kwargs):
+ # ID selector is enabled only on ERPC (secure)
+ self.model, self.EnableIDSelector = _PerSchemeConf[scheme]
+
+ SchemeEditor.__init__(self, scheme, *args, **kwargs)
+
+ # pylint: disable=unused-variable
+ def SetLoc(self, loc):
+ hostport, ID = list(islice(chain(loc.split("#"), repeat("")), 2))
+ host, port = list(islice(chain(hostport.split(":"), repeat("")), 2))
+ self.SetFields(locals())
+
+ def GetLoc(self):
+ if self.model:
+ fields = self.GetFields()
+ template = "{host}"
+ if fields['port']:
+ template += ":{port}"
+ if self.EnableIDSelector:
+ if fields['ID']:
+ template += "#{ID}"
+
+ return template.format(**fields)
+ return ''
--- a/connectors/PYRO/PSK_Adapter.py Tue Feb 20 11:42:02 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,138 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# This file is part of Beremiz, a Integrated Development Environment for
-# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
-#
-# Copyright (C) 2019: Edouard TISSERANT
-#
-# See COPYING file for copyrights details.
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-
-"""
-The TLS-PSK adapter that handles SSL connections instead of regular sockets,
-but using Pre Shared Keys instead of Certificates
-"""
-
-
-
-
-import socket
-import re
-import ssl
-import Pyro
-from Pyro.core import PyroURI
-from Pyro.protocol import _connect_socket, TCPConnection, PYROAdapter
-from Pyro.errors import ConnectionDeniedError, ProtocolError
-from Pyro.util import Log
-
-try:
- import sslpsk
-except ImportError as e:
- print(str(e))
- sslpsk = None
-
-
-class PYROPSKAdapter(PYROAdapter):
- """
- This is essentialy the same as in Pyro/protocol.py
- only raw_sock wrapping into sock through sslpsk.wrap_socket was added
- Pyro unfortunately doesn't allow cleaner customization
- """
-
- def bindToURI(self, URI):
- with self.lock: # only 1 thread at a time can bind the URI
- try:
- self.URI = URI
-
- # This are the statements that differ from Pyro/protocol.py
- raw_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- _connect_socket(raw_sock, URI.address, URI.port, self.timeout)
- sock = sslpsk.wrap_socket(
- raw_sock, psk=Pyro.config.PYROPSK, server_side=False,
- ciphers="PSK-AES256-CBC-SHA", # available in openssl 1.0.2
- ssl_version=ssl.PROTOCOL_TLSv1)
- # all the rest is the same as in Pyro/protocol.py
-
- conn = TCPConnection(sock, sock.getpeername())
- # receive the authentication challenge string, and use that to build the actual identification string.
- try:
- authChallenge = self.recvAuthChallenge(conn)
- except ProtocolError as x:
- # check if we were denied
- if hasattr(x, "partialMsg") and x.partialMsg[:len(self.denyMSG)] == self.denyMSG:
- raise ConnectionDeniedError(Pyro.constants.deniedReasons[int(x.partialMsg[-1])])
- else:
- raise
- # reply with our ident token, generated from the ident passphrase and the challenge
- msg = self._sendConnect(sock, self.newConnValidator.createAuthToken(self.ident, authChallenge, conn.addr, self.URI, None))
- if msg == self.acceptMSG:
- self.conn = conn
- self.conn.connected = 1
- Log.msg('PYROAdapter', 'connected to', str(URI))
- if URI.protocol == 'PYROLOCPSK':
- self.resolvePYROLOC_URI("PYROPSK") # updates self.URI
- elif msg[:len(self.denyMSG)] == self.denyMSG:
- try:
- raise ConnectionDeniedError(Pyro.constants.deniedReasons[int(msg[-1])])
- except (KeyError, ValueError):
- raise ConnectionDeniedError('invalid response')
- except socket.error:
- Log.msg('PYROAdapter', 'connection failed to URI', str(URI))
- raise ProtocolError('connection failed')
-
-
-_getProtocolAdapter = Pyro.protocol.getProtocolAdapter
-
-
-def getProtocolAdapter(protocol):
- if protocol in ('PYROPSK', 'PYROLOCPSK'):
- return PYROPSKAdapter()
- return _getProtocolAdapter(protocol)
-
-
-_processStringURI = Pyro.core.processStringURI
-
-
-def processStringURI(URI):
- x = re.match(r'(?P<protocol>PYROLOCPSK)://(?P<hostname>[^\s:]+):?(?P<port>\d+)?/(?P<name>\S*)', URI)
- if x:
- protocol = x.group('protocol')
- hostname = x.group('hostname')
- port = x.group('port')
- if port:
- port = int(port)
- else:
- port = 0
- name = x.group('name')
- return PyroURI(hostname, name, port, protocol)
- return _processStringURI(URI)
-
-
-def setupPSKAdapter():
- """
- Add PyroAdapter to the list of available in
- Pyro adapters and handle new supported protocols
-
- This function should be called after
- reimport of Pyro module to enable PYROS:// again.
- """
- if sslpsk is not None:
- Pyro.protocol.getProtocolAdapter = getProtocolAdapter
- Pyro.core.processStringURI = processStringURI
- else:
- raise Exception("sslpsk python module unavailable")
--- a/connectors/PYRO/__init__.py Tue Feb 20 11:42:02 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,119 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# This file is part of Beremiz, a Integrated Development Environment for
-# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
-#
-# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
-#
-# See COPYING file for copyrights details.
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-
-from time import sleep
-import copy
-import socket
-import os.path
-
-import Pyro5
-import Pyro5.client
-import Pyro5.errors
-
-# TODO: PSK
-
-import importlib
-
-
-Pyro5.config.SERIALIZER = "msgpack"
-
-
-def PYRO_connector_factory(uri, confnodesroot):
- """
- This returns the connector to Pyro style PLCobject
- """
- confnodesroot.logger.write(_("PYRO connecting to URI : %s\n") % uri)
-
- scheme, location = uri.split("://")
-
- # TODO: use ssl
-
- schemename = "PYRO"
-
- # Try to get the proxy object
- try:
- RemotePLCObjectProxy = Pyro5.client.Proxy(f"{schemename}:PLCObject@{location}")
- except Exception as e:
- confnodesroot.logger.write_error(
- _("Connection to {loc} failed with exception {ex}\n").format(
- loc=location, ex=str(e)))
- return None
-
- RemotePLCObjectProxy._pyroTimeout = 60
-
- class MissingCallException(Exception):
- pass
-
- def PyroCatcher(func, default=None):
- """
- A function that catch a Pyro exceptions, write error to logger
- and return default value when it happen
- """
- def catcher_func(*args, **kwargs):
- try:
- return func(*args, **kwargs)
- except Pyro5.errors.ConnectionClosedError as e:
- confnodesroot._SetConnector(None)
- confnodesroot.logger.write_error(_("Connection lost!\n"))
- except Pyro5.errors.ProtocolError as e:
- confnodesroot.logger.write_error(_("Pyro exception: %s\n") % e)
- except MissingCallException as e:
- confnodesroot.logger.write_warning(_("Remote call not supported: %s\n") % e.message)
- except Exception as e:
- errmess = ''.join(Pyro5.errors.get_pyro_traceback())
- confnodesroot.logger.write_error(errmess + "\n")
- print(errmess)
- confnodesroot._SetConnector(None)
- return default
- return catcher_func
-
- # Check connection is effective.
- # lambda is for getattr of GetPLCstatus to happen inside catcher
- IDPSK = PyroCatcher(RemotePLCObjectProxy.GetPLCID)()
- if IDPSK is None:
- confnodesroot.logger.write_warning(_("PLC did not provide identity and security infomation.\n"))
- else:
- ID, secret = IDPSK
- PSK.UpdateID(confnodesroot.ProjectPath, ID, secret, uri)
-
- class PyroProxyProxy(object):
- """
- A proxy proxy class to handle Beremiz Pyro interface specific behavior.
- And to put Pyro exception catcher in between caller and Pyro proxy
- """
- def __getattr__(self, attrName):
- member = self.__dict__.get(attrName, None)
- if member is None:
- def my_local_func(*args, **kwargs):
- call = RemotePLCObjectProxy.__getattr__(attrName)
- if call is None:
- raise MissingCallException(attrName)
- else:
- return call(*args, **kwargs)
- member = PyroCatcher(my_local_func, self.PLCObjDefaults.get(attrName, None))
- self.__dict__[attrName] = member
- return member
-
- return PyroProxyProxy
--- a/connectors/PYRO_dialog.py Tue Feb 20 11:42:02 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# See COPYING file for copyrights details.
-
-
-
-from itertools import repeat, islice, chain
-
-from connectors.SchemeEditor import SchemeEditor
-
-
-model = [('host', _("Host:")),
- ('port', _("Port:"))]
-
-# (scheme, model, secure)
-models = [("LOCAL", [], False), ("PYRO", model, False)]
-
-Schemes = list(zip(*models))[0]
-
-_PerSchemeConf = {sch: (mod, sec) for sch, mod, sec in models}
-
-
-class PYRO_dialog(SchemeEditor):
- def __init__(self, scheme, *args, **kwargs):
- # ID selector is enabled only on PYROS (secure)
- self.model, self.EnableIDSelector = _PerSchemeConf[scheme]
-
- SchemeEditor.__init__(self, scheme, *args, **kwargs)
-
- # pylint: disable=unused-variable
- def SetLoc(self, loc):
- hostport, ID = list(islice(chain(loc.split("#"), repeat("")), 2))
- host, port = list(islice(chain(hostport.split(":"), repeat("")), 2))
- self.SetFields(locals())
-
- def GetLoc(self):
- if self.model:
- fields = self.GetFields()
- template = "{host}"
- if fields['port']:
- template += ":{port}"
- if self.EnableIDSelector:
- if fields['ID']:
- template += "#{ID}"
-
- return template.format(**fields)
- return ''
--- a/connectors/WAMP/__init__.py Tue Feb 20 11:42:02 2024 +0100
+++ b/connectors/WAMP/__init__.py Tue Feb 20 14:53:33 2024 +0100
@@ -145,7 +145,7 @@
# TODO : GetPLCID()
# TODO : PSK.UpdateID()
- return WampPLCObjectProxy
+ return WampPLCObjectProxy()
WAMP_connector_factory = partial(_WAMP_connector_factory, WampSession)
--- a/connectors/__init__.py Tue Feb 20 11:42:02 2024 +0100
+++ b/connectors/__init__.py Tue Feb 20 14:53:33 2024 +0100
@@ -31,7 +31,7 @@
from os import listdir, path
from connectors.ConnectorBase import ConnectorBase
-connectors_packages = ["PYRO"]
+connectors_packages = ["ERPC", "WAMP"]
def _GetLocalConnectorClassFactory(name):
@@ -71,38 +71,13 @@
"""
_scheme = uri.split("://")[0].upper()
- # commented code to enable for MDNS:// support
- # _scheme, location = uri.split("://")
- # _scheme = _scheme.upper()
-
if _scheme == "LOCAL":
# Local is special case
- # pyro connection to local runtime
+ # ERPC connection to local runtime
# started on demand, listening on random port
- scheme = "PYRO"
+ scheme = "ERPC"
runtime_port = confnodesroot.StartLocalRuntime()
- uri = f"PYRO://{LocalHost}:{runtime_port}"
-
- # commented code to enable for MDNS:// support
- # elif _scheme == "MDNS":
- # try:
- # from zeroconf import Zeroconf
- # r = Zeroconf()
- # i = r.get_service_info(zeroconf_service_type, location)
- # if i is None:
- # raise Exception("'%s' not found" % location)
- # ip = str(socket.inet_ntoa(i.address))
- # port = str(i.port)
- # newlocation = ip + ':' + port
- # confnodesroot.logger.write(_("'{a1}' is located at {a2}\n").format(a1=location, a2=newlocation))
- # location = newlocation
- # # not a bug, but a workaround against obvious downgrade attack
- # scheme = "PYROS"
- # r.close()
- # except Exception:
- # confnodesroot.logger.write_error(_("MDNS resolution failure for '%s'\n") % location)
- # confnodesroot.logger.write_error(traceback.format_exc())
- # return None
+ uri = f"ERPC://{LocalHost}:{runtime_port}"
elif _scheme in connectors:
scheme = _scheme
@@ -111,18 +86,9 @@
else:
return None
- # import module according to uri type and get connector specific baseclass
- # first call to import the module,
- # then call with parameters to create the class
- connector_specific_class = connectors[scheme]()(uri, confnodesroot)
-
- if connector_specific_class is None:
- return None
-
- # new class inheriting from generic and specific connector base classes
- return type(_scheme + "_connector",
- (ConnectorBase, connector_specific_class), {})()
-
+ return (connectors[scheme]
+ () # triggers import
+ (uri, confnodesroot)) # creates object
def EditorClassFromScheme(scheme):
_Import_Dialogs()
--- a/editors/Viewer.py Tue Feb 20 11:42:02 2024 +0100
+++ b/editors/Viewer.py Tue Feb 20 14:53:33 2024 +0100
@@ -634,7 +634,7 @@
add_menu = wx.Menu(title='')
self.AddAddMenuItems(add_menu)
- menu.AppendMenu(-1, _('Add'), add_menu)
+ menu.Append(wx.ID_NEW, _('Add'), add_menu)
menu.AppendSeparator()
@@ -2153,7 +2153,8 @@
event.Skip()
def OnViewerRightDown(self, event):
- self.Editor.CaptureMouse()
+ if not self.Editor.HasCapture():
+ self.Editor.CaptureMouse()
if self.Mode == MODE_SELECTION:
element = self.FindElement(event)
if self.SelectedElement is not None and self.SelectedElement != element:
@@ -2173,6 +2174,8 @@
self.rubberBand.Reset()
self.rubberBand.OnLeftDown(event, dc, self.Scaling)
self.rubberBand.OnLeftUp(event, dc, self.Scaling)
+ if self.Editor.HasCapture():
+ self.Editor.ReleaseMouse()
if self.SelectedElement is not None:
if self.Debug:
Graphic_Element.OnRightUp(self.SelectedElement, event, self.GetLogicalDC(), self.Scaling)
@@ -2181,8 +2184,6 @@
wx.CallAfter(self.SetCurrentCursor, 0)
elif not self.Debug:
self.PopupDefaultMenu(False)
- if self.Editor.HasCapture():
- self.Editor.ReleaseMouse()
event.Skip()
def OnViewerLeftDClick(self, event):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/erpc_interface/__init__.py Tue Feb 20 14:53:33 2024 +0100
@@ -0,0 +1,6 @@
+#
+# Generated by erpcgen 1.11.0 on Mon Jan 22 16:49:00 2024.
+#
+# AUTOGENERATED - DO NOT EDIT
+#
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/erpc_interface/erpc_PLCObject.erpc Tue Feb 20 14:53:33 2024 +0100
@@ -0,0 +1,71 @@
+/*
+ Written by Edouard TISSERANT (C) 2024
+ This file is part of Beremiz runtime and IDE
+ See COPYING.Runtime and COPYING file for copyrights details.
+*/
+
+program erpc_PLCObject
+
+struct PSKID {
+ string ID;
+ string PSK;
+};
+
+enum PLCstatus_enum {
+ Empty
+ Stopped,
+ Started,
+ Broken,
+ Disconnected
+}
+
+struct PLCstatus {
+ PLCstatus_enum PLCstatus;
+ uint32[4] logcounts;
+};
+
+struct trace_sample {
+ uint32 tick;
+ binary TraceBuffer;
+};
+
+struct TraceVariables {
+ PLCstatus_enum PLCstatus;
+ list<trace_sample> traces;
+};
+
+struct extra_file {
+ string fname;
+ binary blobID;
+};
+
+struct trace_order {
+ uint32 idx;
+ binary force;
+};
+
+struct log_message {
+ string msg;
+ uint32 tick;
+ uint32 sec;
+ uint32 nsec;
+};
+
+
+interface BeremizPLCObjectService {
+ AppendChunkToBlob(in binary data, in binary blobID, out binary newBlobID) -> uint32
+ GetLogMessage(in uint8 level, in uint32 msgID, out log_message message) -> uint32
+ GetPLCID(out PSKID plcID) -> uint32
+ GetPLCstatus(out PLCstatus status) -> uint32
+ GetTraceVariables(in uint32 debugToken, out TraceVariables traces) -> uint32
+ MatchMD5(in string MD5, out bool match) -> uint32
+ NewPLC(in string md5sum, in binary plcObjectBlobID, in list<extra_file> extrafiles, out bool success) -> uint32
+ PurgeBlobs() -> uint32
+ /* NOT TO DO : RemoteExec(in ) -> uint32 */
+ RepairPLC() -> uint32
+ ResetLogCount() -> uint32
+ SeedBlob(in binary seed, out binary blobID) -> uint32
+ SetTraceVariablesList(in list<trace_order> orders, out uint32 debugtoken) -> uint32
+ StartPLC() -> uint32
+ StopPLC(out bool success) -> uint32
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/erpc_interface/erpc_PLCObject/__init__.py Tue Feb 20 14:53:33 2024 +0100
@@ -0,0 +1,19 @@
+#
+# Generated by erpcgen 1.11.0 on Mon Jan 22 16:49:00 2024.
+#
+# AUTOGENERATED - DO NOT EDIT
+#
+
+try:
+ from erpc import erpc_version
+ version = erpc_version.ERPC_VERSION
+except ImportError:
+ version = "unknown"
+if version != "1.11.0":
+ raise ValueError("The generated shim code version (1.11.0) is different to the rest of eRPC code (%s). \
+Install newer version by running \"python setup.py install\" in folder erpc/erpc_python/." % repr(version))
+
+from . import common
+from . import client
+from . import server
+from . import interface
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/erpc_interface/erpc_PLCObject/client.py Tue Feb 20 14:53:33 2024 +0100
@@ -0,0 +1,295 @@
+#
+# Generated by erpcgen 1.11.0 on Mon Jan 22 16:49:00 2024.
+#
+# AUTOGENERATED - DO NOT EDIT
+#
+
+import erpc
+from . import common, interface
+
+# Client for BeremizPLCObjectService
+class BeremizPLCObjectServiceClient(interface.IBeremizPLCObjectService):
+ def __init__(self, manager):
+ super(BeremizPLCObjectServiceClient, self).__init__()
+ self._clientManager = manager
+
+ def AppendChunkToBlob(self, data, blobID, newBlobID):
+ assert type(newBlobID) is erpc.Reference, "out parameter must be a Reference object"
+
+ # Build remote function invocation message.
+ request = self._clientManager.create_request()
+ codec = request.codec
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kInvocationMessage,
+ service=self.SERVICE_ID,
+ request=self.APPENDCHUNKTOBLOB_ID,
+ sequence=request.sequence))
+ if data is None:
+ raise ValueError("data is None")
+ codec.write_binary(data)
+ if blobID is None:
+ raise ValueError("blobID is None")
+ codec.write_binary(blobID)
+
+ # Send request and process reply.
+ self._clientManager.perform_request(request)
+ newBlobID.value = codec.read_binary()
+ _result = codec.read_uint32()
+ return _result
+
+ def GetLogMessage(self, level, msgID, message):
+ assert type(message) is erpc.Reference, "out parameter must be a Reference object"
+
+ # Build remote function invocation message.
+ request = self._clientManager.create_request()
+ codec = request.codec
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kInvocationMessage,
+ service=self.SERVICE_ID,
+ request=self.GETLOGMESSAGE_ID,
+ sequence=request.sequence))
+ if level is None:
+ raise ValueError("level is None")
+ codec.write_uint8(level)
+ if msgID is None:
+ raise ValueError("msgID is None")
+ codec.write_uint32(msgID)
+
+ # Send request and process reply.
+ self._clientManager.perform_request(request)
+ message.value = common.log_message()._read(codec)
+ _result = codec.read_uint32()
+ return _result
+
+ def GetPLCID(self, plcID):
+ assert type(plcID) is erpc.Reference, "out parameter must be a Reference object"
+
+ # Build remote function invocation message.
+ request = self._clientManager.create_request()
+ codec = request.codec
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kInvocationMessage,
+ service=self.SERVICE_ID,
+ request=self.GETPLCID_ID,
+ sequence=request.sequence))
+
+ # Send request and process reply.
+ self._clientManager.perform_request(request)
+ plcID.value = common.PSKID()._read(codec)
+ _result = codec.read_uint32()
+ return _result
+
+ def GetPLCstatus(self, status):
+ assert type(status) is erpc.Reference, "out parameter must be a Reference object"
+
+ # Build remote function invocation message.
+ request = self._clientManager.create_request()
+ codec = request.codec
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kInvocationMessage,
+ service=self.SERVICE_ID,
+ request=self.GETPLCSTATUS_ID,
+ sequence=request.sequence))
+
+ # Send request and process reply.
+ self._clientManager.perform_request(request)
+ status.value = common.PLCstatus()._read(codec)
+ _result = codec.read_uint32()
+ return _result
+
+ def GetTraceVariables(self, debugToken, traces):
+ assert type(traces) is erpc.Reference, "out parameter must be a Reference object"
+
+ # Build remote function invocation message.
+ request = self._clientManager.create_request()
+ codec = request.codec
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kInvocationMessage,
+ service=self.SERVICE_ID,
+ request=self.GETTRACEVARIABLES_ID,
+ sequence=request.sequence))
+ if debugToken is None:
+ raise ValueError("debugToken is None")
+ codec.write_uint32(debugToken)
+
+ # Send request and process reply.
+ self._clientManager.perform_request(request)
+ traces.value = common.TraceVariables()._read(codec)
+ _result = codec.read_uint32()
+ return _result
+
+ def MatchMD5(self, MD5, match):
+ assert type(match) is erpc.Reference, "out parameter must be a Reference object"
+
+ # Build remote function invocation message.
+ request = self._clientManager.create_request()
+ codec = request.codec
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kInvocationMessage,
+ service=self.SERVICE_ID,
+ request=self.MATCHMD5_ID,
+ sequence=request.sequence))
+ if MD5 is None:
+ raise ValueError("MD5 is None")
+ codec.write_string(MD5)
+
+ # Send request and process reply.
+ self._clientManager.perform_request(request)
+ match.value = codec.read_bool()
+ _result = codec.read_uint32()
+ return _result
+
+ def NewPLC(self, md5sum, plcObjectBlobID, extrafiles, success):
+ assert type(success) is erpc.Reference, "out parameter must be a Reference object"
+
+ # Build remote function invocation message.
+ request = self._clientManager.create_request()
+ codec = request.codec
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kInvocationMessage,
+ service=self.SERVICE_ID,
+ request=self.NEWPLC_ID,
+ sequence=request.sequence))
+ if md5sum is None:
+ raise ValueError("md5sum is None")
+ codec.write_string(md5sum)
+ if plcObjectBlobID is None:
+ raise ValueError("plcObjectBlobID is None")
+ codec.write_binary(plcObjectBlobID)
+ if extrafiles is None:
+ raise ValueError("extrafiles is None")
+ codec.start_write_list(len(extrafiles))
+ for _i0 in extrafiles:
+ _i0._write(codec)
+
+
+ # Send request and process reply.
+ self._clientManager.perform_request(request)
+ success.value = codec.read_bool()
+ _result = codec.read_uint32()
+ return _result
+
+ def PurgeBlobs(self):
+ # Build remote function invocation message.
+ request = self._clientManager.create_request()
+ codec = request.codec
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kInvocationMessage,
+ service=self.SERVICE_ID,
+ request=self.PURGEBLOBS_ID,
+ sequence=request.sequence))
+
+ # Send request and process reply.
+ self._clientManager.perform_request(request)
+ _result = codec.read_uint32()
+ return _result
+
+ def RepairPLC(self):
+ # Build remote function invocation message.
+ request = self._clientManager.create_request()
+ codec = request.codec
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kInvocationMessage,
+ service=self.SERVICE_ID,
+ request=self.REPAIRPLC_ID,
+ sequence=request.sequence))
+
+ # Send request and process reply.
+ self._clientManager.perform_request(request)
+ _result = codec.read_uint32()
+ return _result
+
+ def ResetLogCount(self):
+ # Build remote function invocation message.
+ request = self._clientManager.create_request()
+ codec = request.codec
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kInvocationMessage,
+ service=self.SERVICE_ID,
+ request=self.RESETLOGCOUNT_ID,
+ sequence=request.sequence))
+
+ # Send request and process reply.
+ self._clientManager.perform_request(request)
+ _result = codec.read_uint32()
+ return _result
+
+ def SeedBlob(self, seed, blobID):
+ assert type(blobID) is erpc.Reference, "out parameter must be a Reference object"
+
+ # Build remote function invocation message.
+ request = self._clientManager.create_request()
+ codec = request.codec
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kInvocationMessage,
+ service=self.SERVICE_ID,
+ request=self.SEEDBLOB_ID,
+ sequence=request.sequence))
+ if seed is None:
+ raise ValueError("seed is None")
+ codec.write_binary(seed)
+
+ # Send request and process reply.
+ self._clientManager.perform_request(request)
+ blobID.value = codec.read_binary()
+ _result = codec.read_uint32()
+ return _result
+
+ def SetTraceVariablesList(self, orders, debugtoken):
+ assert type(debugtoken) is erpc.Reference, "out parameter must be a Reference object"
+
+ # Build remote function invocation message.
+ request = self._clientManager.create_request()
+ codec = request.codec
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kInvocationMessage,
+ service=self.SERVICE_ID,
+ request=self.SETTRACEVARIABLESLIST_ID,
+ sequence=request.sequence))
+ if orders is None:
+ raise ValueError("orders is None")
+ codec.start_write_list(len(orders))
+ for _i0 in orders:
+ _i0._write(codec)
+
+
+ # Send request and process reply.
+ self._clientManager.perform_request(request)
+ debugtoken.value = codec.read_uint32()
+ _result = codec.read_uint32()
+ return _result
+
+ def StartPLC(self):
+ # Build remote function invocation message.
+ request = self._clientManager.create_request()
+ codec = request.codec
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kInvocationMessage,
+ service=self.SERVICE_ID,
+ request=self.STARTPLC_ID,
+ sequence=request.sequence))
+
+ # Send request and process reply.
+ self._clientManager.perform_request(request)
+ _result = codec.read_uint32()
+ return _result
+
+ def StopPLC(self, success):
+ assert type(success) is erpc.Reference, "out parameter must be a Reference object"
+
+ # Build remote function invocation message.
+ request = self._clientManager.create_request()
+ codec = request.codec
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kInvocationMessage,
+ service=self.SERVICE_ID,
+ request=self.STOPPLC_ID,
+ sequence=request.sequence))
+
+ # Send request and process reply.
+ self._clientManager.perform_request(request)
+ success.value = codec.read_bool()
+ _result = codec.read_uint32()
+ return _result
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/erpc_interface/erpc_PLCObject/common.py Tue Feb 20 14:53:33 2024 +0100
@@ -0,0 +1,210 @@
+#
+# Generated by erpcgen 1.11.0 on Mon Jan 22 16:49:00 2024.
+#
+# AUTOGENERATED - DO NOT EDIT
+#
+
+
+# Enumerators data types declarations
+class PLCstatus_enum:
+ Empty = 0
+ Stopped = 1
+ Started = 2
+ Broken = 3
+ Disconnected = 4
+
+
+# Structures data types declarations
+class log_message(object):
+ def __init__(self, msg=None, tick=None, sec=None, nsec=None):
+ self.msg = msg # string
+ self.tick = tick # uint32
+ self.sec = sec # uint32
+ self.nsec = nsec # uint32
+
+ def _read(self, codec):
+ self.msg = codec.read_string()
+ self.tick = codec.read_uint32()
+ self.sec = codec.read_uint32()
+ self.nsec = codec.read_uint32()
+ return self
+
+ def _write(self, codec):
+ if self.msg is None:
+ raise ValueError("msg is None")
+ codec.write_string(self.msg)
+ if self.tick is None:
+ raise ValueError("tick is None")
+ codec.write_uint32(self.tick)
+ if self.sec is None:
+ raise ValueError("sec is None")
+ codec.write_uint32(self.sec)
+ if self.nsec is None:
+ raise ValueError("nsec is None")
+ codec.write_uint32(self.nsec)
+
+ def __str__(self):
+ return "<%s@%x msg=%s tick=%s sec=%s nsec=%s>" % (self.__class__.__name__, id(self), self.msg, self.tick, self.sec, self.nsec)
+
+ def __repr__(self):
+ return self.__str__()
+
+class PSKID(object):
+ def __init__(self, ID=None, PSK=None):
+ self.ID = ID # string
+ self.PSK = PSK # string
+
+ def _read(self, codec):
+ self.ID = codec.read_string()
+ self.PSK = codec.read_string()
+ return self
+
+ def _write(self, codec):
+ if self.ID is None:
+ raise ValueError("ID is None")
+ codec.write_string(self.ID)
+ if self.PSK is None:
+ raise ValueError("PSK is None")
+ codec.write_string(self.PSK)
+
+ def __str__(self):
+ return "<%s@%x ID=%s PSK=%s>" % (self.__class__.__name__, id(self), self.ID, self.PSK)
+
+ def __repr__(self):
+ return self.__str__()
+
+class PLCstatus(object):
+ def __init__(self, PLCstatus=None, logcounts=None):
+ self.PLCstatus = PLCstatus # PLCstatus_enum
+ self.logcounts = logcounts # uint32[4]
+
+
+ def _read(self, codec):
+ self.PLCstatus = codec.read_int32()
+ self.logcounts = []
+ for _i0 in range(4):
+ _v0 = codec.read_uint32()
+ self.logcounts.append(_v0)
+
+ return self
+
+ def _write(self, codec):
+ if self.PLCstatus is None:
+ raise ValueError("PLCstatus is None")
+ codec.write_int32(self.PLCstatus)
+ if self.logcounts is None:
+ raise ValueError("logcounts is None")
+ for _i0 in self.logcounts:
+ codec.write_uint32(_i0)
+
+
+ def __str__(self):
+ return "<%s@%x PLCstatus=%s logcounts=%s>" % (self.__class__.__name__, id(self), self.PLCstatus, self.logcounts)
+
+ def __repr__(self):
+ return self.__str__()
+
+class trace_sample(object):
+ def __init__(self, tick=None, TraceBuffer=None):
+ self.tick = tick # uint32
+ self.TraceBuffer = TraceBuffer # binary
+
+ def _read(self, codec):
+ self.tick = codec.read_uint32()
+ self.TraceBuffer = codec.read_binary()
+ return self
+
+ def _write(self, codec):
+ if self.tick is None:
+ raise ValueError("tick is None")
+ codec.write_uint32(self.tick)
+ if self.TraceBuffer is None:
+ raise ValueError("TraceBuffer is None")
+ codec.write_binary(self.TraceBuffer)
+
+ def __str__(self):
+ return "<%s@%x tick=%s TraceBuffer=%s>" % (self.__class__.__name__, id(self), self.tick, self.TraceBuffer)
+
+ def __repr__(self):
+ return self.__str__()
+
+class TraceVariables(object):
+ def __init__(self, PLCstatus=None, traces=None):
+ self.PLCstatus = PLCstatus # PLCstatus_enum
+ self.traces = traces # list<trace_sample>
+
+ def _read(self, codec):
+ self.PLCstatus = codec.read_int32()
+ _n0 = codec.start_read_list()
+ self.traces = []
+ for _i0 in range(_n0):
+ _v0 = trace_sample()._read(codec)
+ self.traces.append(_v0)
+
+ return self
+
+ def _write(self, codec):
+ if self.PLCstatus is None:
+ raise ValueError("PLCstatus is None")
+ codec.write_int32(self.PLCstatus)
+ if self.traces is None:
+ raise ValueError("traces is None")
+ codec.start_write_list(len(self.traces))
+ for _i0 in self.traces:
+ _i0._write(codec)
+
+
+ def __str__(self):
+ return "<%s@%x PLCstatus=%s traces=%s>" % (self.__class__.__name__, id(self), self.PLCstatus, self.traces)
+
+ def __repr__(self):
+ return self.__str__()
+
+class extra_file(object):
+ def __init__(self, fname=None, blobID=None):
+ self.fname = fname # string
+ self.blobID = blobID # binary
+
+ def _read(self, codec):
+ self.fname = codec.read_string()
+ self.blobID = codec.read_binary()
+ return self
+
+ def _write(self, codec):
+ if self.fname is None:
+ raise ValueError("fname is None")
+ codec.write_string(self.fname)
+ if self.blobID is None:
+ raise ValueError("blobID is None")
+ codec.write_binary(self.blobID)
+
+ def __str__(self):
+ return "<%s@%x fname=%s blobID=%s>" % (self.__class__.__name__, id(self), self.fname, self.blobID)
+
+ def __repr__(self):
+ return self.__str__()
+
+class trace_order(object):
+ def __init__(self, idx=None, force=None):
+ self.idx = idx # uint32
+ self.force = force # binary
+
+ def _read(self, codec):
+ self.idx = codec.read_uint32()
+ self.force = codec.read_binary()
+ return self
+
+ def _write(self, codec):
+ if self.idx is None:
+ raise ValueError("idx is None")
+ codec.write_uint32(self.idx)
+ if self.force is None:
+ raise ValueError("force is None")
+ codec.write_binary(self.force)
+
+ def __str__(self):
+ return "<%s@%x idx=%s force=%s>" % (self.__class__.__name__, id(self), self.idx, self.force)
+
+ def __repr__(self):
+ return self.__str__()
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/erpc_interface/erpc_PLCObject/interface.py Tue Feb 20 14:53:33 2024 +0100
@@ -0,0 +1,67 @@
+#
+# Generated by erpcgen 1.11.0 on Mon Jan 22 16:49:00 2024.
+#
+# AUTOGENERATED - DO NOT EDIT
+#
+
+# Abstract base class for BeremizPLCObjectService
+class IBeremizPLCObjectService(object):
+ SERVICE_ID = 1
+ APPENDCHUNKTOBLOB_ID = 1
+ GETLOGMESSAGE_ID = 2
+ GETPLCID_ID = 3
+ GETPLCSTATUS_ID = 4
+ GETTRACEVARIABLES_ID = 5
+ MATCHMD5_ID = 6
+ NEWPLC_ID = 7
+ PURGEBLOBS_ID = 8
+ REPAIRPLC_ID = 9
+ RESETLOGCOUNT_ID = 10
+ SEEDBLOB_ID = 11
+ SETTRACEVARIABLESLIST_ID = 12
+ STARTPLC_ID = 13
+ STOPPLC_ID = 14
+
+ def AppendChunkToBlob(self, data, blobID, newBlobID):
+ raise NotImplementedError()
+
+ def GetLogMessage(self, level, msgID, message):
+ raise NotImplementedError()
+
+ def GetPLCID(self, plcID):
+ raise NotImplementedError()
+
+ def GetPLCstatus(self, status):
+ raise NotImplementedError()
+
+ def GetTraceVariables(self, debugToken, traces):
+ raise NotImplementedError()
+
+ def MatchMD5(self, MD5, match):
+ raise NotImplementedError()
+
+ def NewPLC(self, md5sum, plcObjectBlobID, extrafiles, success):
+ raise NotImplementedError()
+
+ def PurgeBlobs(self):
+ raise NotImplementedError()
+
+ def RepairPLC(self):
+ raise NotImplementedError()
+
+ def ResetLogCount(self):
+ raise NotImplementedError()
+
+ def SeedBlob(self, seed, blobID):
+ raise NotImplementedError()
+
+ def SetTraceVariablesList(self, orders, debugtoken):
+ raise NotImplementedError()
+
+ def StartPLC(self):
+ raise NotImplementedError()
+
+ def StopPLC(self, success):
+ raise NotImplementedError()
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/erpc_interface/erpc_PLCObject/server.py Tue Feb 20 14:53:33 2024 +0100
@@ -0,0 +1,351 @@
+#
+# Generated by erpcgen 1.11.0 on Mon Jan 22 16:49:00 2024.
+#
+# AUTOGENERATED - DO NOT EDIT
+#
+
+import erpc
+from . import common, interface
+
+# Client for BeremizPLCObjectService
+class BeremizPLCObjectServiceService(erpc.server.Service):
+ def __init__(self, handler):
+ super(BeremizPLCObjectServiceService, self).__init__(interface.IBeremizPLCObjectService.SERVICE_ID)
+ self._handler = handler
+ self._methods = {
+ interface.IBeremizPLCObjectService.APPENDCHUNKTOBLOB_ID: self._handle_AppendChunkToBlob,
+ interface.IBeremizPLCObjectService.GETLOGMESSAGE_ID: self._handle_GetLogMessage,
+ interface.IBeremizPLCObjectService.GETPLCID_ID: self._handle_GetPLCID,
+ interface.IBeremizPLCObjectService.GETPLCSTATUS_ID: self._handle_GetPLCstatus,
+ interface.IBeremizPLCObjectService.GETTRACEVARIABLES_ID: self._handle_GetTraceVariables,
+ interface.IBeremizPLCObjectService.MATCHMD5_ID: self._handle_MatchMD5,
+ interface.IBeremizPLCObjectService.NEWPLC_ID: self._handle_NewPLC,
+ interface.IBeremizPLCObjectService.PURGEBLOBS_ID: self._handle_PurgeBlobs,
+ interface.IBeremizPLCObjectService.REPAIRPLC_ID: self._handle_RepairPLC,
+ interface.IBeremizPLCObjectService.RESETLOGCOUNT_ID: self._handle_ResetLogCount,
+ interface.IBeremizPLCObjectService.SEEDBLOB_ID: self._handle_SeedBlob,
+ interface.IBeremizPLCObjectService.SETTRACEVARIABLESLIST_ID: self._handle_SetTraceVariablesList,
+ interface.IBeremizPLCObjectService.STARTPLC_ID: self._handle_StartPLC,
+ interface.IBeremizPLCObjectService.STOPPLC_ID: self._handle_StopPLC,
+ }
+
+ def _handle_AppendChunkToBlob(self, sequence, codec):
+ # Create reference objects to pass into handler for out/inout parameters.
+ newBlobID = erpc.Reference()
+
+ # Read incoming parameters.
+ data = codec.read_binary()
+ blobID = codec.read_binary()
+
+ # Invoke user implementation of remote function.
+ _result = self._handler.AppendChunkToBlob(data, blobID, newBlobID)
+
+ # Prepare codec for reply message.
+ codec.reset()
+
+ # Construct reply message.
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kReplyMessage,
+ service=interface.IBeremizPLCObjectService.SERVICE_ID,
+ request=interface.IBeremizPLCObjectService.APPENDCHUNKTOBLOB_ID,
+ sequence=sequence))
+ if newBlobID.value is None:
+ raise ValueError("newBlobID.value is None")
+ codec.write_binary(newBlobID.value)
+ codec.write_uint32(_result)
+
+ def _handle_GetLogMessage(self, sequence, codec):
+ # Create reference objects to pass into handler for out/inout parameters.
+ message = erpc.Reference()
+
+ # Read incoming parameters.
+ level = codec.read_uint8()
+ msgID = codec.read_uint32()
+
+ # Invoke user implementation of remote function.
+ _result = self._handler.GetLogMessage(level, msgID, message)
+
+ # Prepare codec for reply message.
+ codec.reset()
+
+ # Construct reply message.
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kReplyMessage,
+ service=interface.IBeremizPLCObjectService.SERVICE_ID,
+ request=interface.IBeremizPLCObjectService.GETLOGMESSAGE_ID,
+ sequence=sequence))
+ if message.value is None:
+ raise ValueError("message.value is None")
+ message.value._write(codec)
+ codec.write_uint32(_result)
+
+ def _handle_GetPLCID(self, sequence, codec):
+ # Create reference objects to pass into handler for out/inout parameters.
+ plcID = erpc.Reference()
+
+ # Read incoming parameters.
+
+ # Invoke user implementation of remote function.
+ _result = self._handler.GetPLCID(plcID)
+
+ # Prepare codec for reply message.
+ codec.reset()
+
+ # Construct reply message.
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kReplyMessage,
+ service=interface.IBeremizPLCObjectService.SERVICE_ID,
+ request=interface.IBeremizPLCObjectService.GETPLCID_ID,
+ sequence=sequence))
+ if plcID.value is None:
+ raise ValueError("plcID.value is None")
+ plcID.value._write(codec)
+ codec.write_uint32(_result)
+
+ def _handle_GetPLCstatus(self, sequence, codec):
+ # Create reference objects to pass into handler for out/inout parameters.
+ status = erpc.Reference()
+
+ # Read incoming parameters.
+
+ # Invoke user implementation of remote function.
+ _result = self._handler.GetPLCstatus(status)
+
+ # Prepare codec for reply message.
+ codec.reset()
+
+ # Construct reply message.
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kReplyMessage,
+ service=interface.IBeremizPLCObjectService.SERVICE_ID,
+ request=interface.IBeremizPLCObjectService.GETPLCSTATUS_ID,
+ sequence=sequence))
+ if status.value is None:
+ raise ValueError("status.value is None")
+ status.value._write(codec)
+ codec.write_uint32(_result)
+
+ def _handle_GetTraceVariables(self, sequence, codec):
+ # Create reference objects to pass into handler for out/inout parameters.
+ traces = erpc.Reference()
+
+ # Read incoming parameters.
+ debugToken = codec.read_uint32()
+
+ # Invoke user implementation of remote function.
+ _result = self._handler.GetTraceVariables(debugToken, traces)
+
+ # Prepare codec for reply message.
+ codec.reset()
+
+ # Construct reply message.
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kReplyMessage,
+ service=interface.IBeremizPLCObjectService.SERVICE_ID,
+ request=interface.IBeremizPLCObjectService.GETTRACEVARIABLES_ID,
+ sequence=sequence))
+ if traces.value is None:
+ raise ValueError("traces.value is None")
+ traces.value._write(codec)
+ codec.write_uint32(_result)
+
+ def _handle_MatchMD5(self, sequence, codec):
+ # Create reference objects to pass into handler for out/inout parameters.
+ match = erpc.Reference()
+
+ # Read incoming parameters.
+ MD5 = codec.read_string()
+
+ # Invoke user implementation of remote function.
+ _result = self._handler.MatchMD5(MD5, match)
+
+ # Prepare codec for reply message.
+ codec.reset()
+
+ # Construct reply message.
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kReplyMessage,
+ service=interface.IBeremizPLCObjectService.SERVICE_ID,
+ request=interface.IBeremizPLCObjectService.MATCHMD5_ID,
+ sequence=sequence))
+ if match.value is None:
+ raise ValueError("match.value is None")
+ codec.write_bool(match.value)
+ codec.write_uint32(_result)
+
+ def _handle_NewPLC(self, sequence, codec):
+ # Create reference objects to pass into handler for out/inout parameters.
+ success = erpc.Reference()
+
+ # Read incoming parameters.
+ md5sum = codec.read_string()
+ plcObjectBlobID = codec.read_binary()
+ _n0 = codec.start_read_list()
+ extrafiles = []
+ for _i0 in range(_n0):
+ _v0 = common.extra_file()._read(codec)
+ extrafiles.append(_v0)
+
+
+ # Invoke user implementation of remote function.
+ _result = self._handler.NewPLC(md5sum, plcObjectBlobID, extrafiles, success)
+
+ # Prepare codec for reply message.
+ codec.reset()
+
+ # Construct reply message.
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kReplyMessage,
+ service=interface.IBeremizPLCObjectService.SERVICE_ID,
+ request=interface.IBeremizPLCObjectService.NEWPLC_ID,
+ sequence=sequence))
+ if success.value is None:
+ raise ValueError("success.value is None")
+ codec.write_bool(success.value)
+ codec.write_uint32(_result)
+
+ def _handle_PurgeBlobs(self, sequence, codec):
+ # Read incoming parameters.
+
+ # Invoke user implementation of remote function.
+ _result = self._handler.PurgeBlobs()
+
+ # Prepare codec for reply message.
+ codec.reset()
+
+ # Construct reply message.
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kReplyMessage,
+ service=interface.IBeremizPLCObjectService.SERVICE_ID,
+ request=interface.IBeremizPLCObjectService.PURGEBLOBS_ID,
+ sequence=sequence))
+ codec.write_uint32(_result)
+
+ def _handle_RepairPLC(self, sequence, codec):
+ # Read incoming parameters.
+
+ # Invoke user implementation of remote function.
+ _result = self._handler.RepairPLC()
+
+ # Prepare codec for reply message.
+ codec.reset()
+
+ # Construct reply message.
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kReplyMessage,
+ service=interface.IBeremizPLCObjectService.SERVICE_ID,
+ request=interface.IBeremizPLCObjectService.REPAIRPLC_ID,
+ sequence=sequence))
+ codec.write_uint32(_result)
+
+ def _handle_ResetLogCount(self, sequence, codec):
+ # Read incoming parameters.
+
+ # Invoke user implementation of remote function.
+ _result = self._handler.ResetLogCount()
+
+ # Prepare codec for reply message.
+ codec.reset()
+
+ # Construct reply message.
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kReplyMessage,
+ service=interface.IBeremizPLCObjectService.SERVICE_ID,
+ request=interface.IBeremizPLCObjectService.RESETLOGCOUNT_ID,
+ sequence=sequence))
+ codec.write_uint32(_result)
+
+ def _handle_SeedBlob(self, sequence, codec):
+ # Create reference objects to pass into handler for out/inout parameters.
+ blobID = erpc.Reference()
+
+ # Read incoming parameters.
+ seed = codec.read_binary()
+
+ # Invoke user implementation of remote function.
+ _result = self._handler.SeedBlob(seed, blobID)
+
+ # Prepare codec for reply message.
+ codec.reset()
+
+ # Construct reply message.
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kReplyMessage,
+ service=interface.IBeremizPLCObjectService.SERVICE_ID,
+ request=interface.IBeremizPLCObjectService.SEEDBLOB_ID,
+ sequence=sequence))
+ if blobID.value is None:
+ raise ValueError("blobID.value is None")
+ codec.write_binary(blobID.value)
+ codec.write_uint32(_result)
+
+ def _handle_SetTraceVariablesList(self, sequence, codec):
+ # Create reference objects to pass into handler for out/inout parameters.
+ debugtoken = erpc.Reference()
+
+ # Read incoming parameters.
+ _n0 = codec.start_read_list()
+ orders = []
+ for _i0 in range(_n0):
+ _v0 = common.trace_order()._read(codec)
+ orders.append(_v0)
+
+
+ # Invoke user implementation of remote function.
+ _result = self._handler.SetTraceVariablesList(orders, debugtoken)
+
+ # Prepare codec for reply message.
+ codec.reset()
+
+ # Construct reply message.
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kReplyMessage,
+ service=interface.IBeremizPLCObjectService.SERVICE_ID,
+ request=interface.IBeremizPLCObjectService.SETTRACEVARIABLESLIST_ID,
+ sequence=sequence))
+ if debugtoken.value is None:
+ raise ValueError("debugtoken.value is None")
+ codec.write_uint32(debugtoken.value)
+ codec.write_uint32(_result)
+
+ def _handle_StartPLC(self, sequence, codec):
+ # Read incoming parameters.
+
+ # Invoke user implementation of remote function.
+ _result = self._handler.StartPLC()
+
+ # Prepare codec for reply message.
+ codec.reset()
+
+ # Construct reply message.
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kReplyMessage,
+ service=interface.IBeremizPLCObjectService.SERVICE_ID,
+ request=interface.IBeremizPLCObjectService.STARTPLC_ID,
+ sequence=sequence))
+ codec.write_uint32(_result)
+
+ def _handle_StopPLC(self, sequence, codec):
+ # Create reference objects to pass into handler for out/inout parameters.
+ success = erpc.Reference()
+
+ # Read incoming parameters.
+
+ # Invoke user implementation of remote function.
+ _result = self._handler.StopPLC(success)
+
+ # Prepare codec for reply message.
+ codec.reset()
+
+ # Construct reply message.
+ codec.start_write_message(erpc.codec.MessageInfo(
+ type=erpc.codec.MessageType.kReplyMessage,
+ service=interface.IBeremizPLCObjectService.SERVICE_ID,
+ request=interface.IBeremizPLCObjectService.STOPPLC_ID,
+ sequence=sequence))
+ if success.value is None:
+ raise ValueError("success.value is None")
+ codec.write_bool(success.value)
+ codec.write_uint32(_result)
+
+
--- a/requirements.txt Tue Feb 20 11:42:02 2024 +0100
+++ b/requirements.txt Tue Feb 20 14:53:33 2024 +0100
@@ -13,6 +13,7 @@
contourpy==1.0.7
cryptography==40.0.2
cycler==0.11.0
+erpc==1.11.0
fonttools==4.39.3
gattrdict==2.0.1
hyperlink==21.0.0
@@ -23,19 +24,18 @@
lxml==4.9.2
matplotlib==3.7.1
msgpack==1.0.5
-Nevow @ git+https://git@github.com/beremiz/nevow-py3.git@cb62cc37824361725c0c0a599802b15210e6aaa3#egg=Nevow
+Nevow @ git+https://git@github.com/beremiz/nevow-py3.git@942d0d68bcd99bedfc5c10d988b4a2eaf8a6420d
numpy==1.24.3
packaging==23.1
Pillow==9.5.0
pycountry==22.3.5
pycparser==2.21
pyparsing==3.0.9
-Pyro5==5.14
python-dateutil==2.8.2
pytz==2023.3
-serpent==1.41
six==1.16.0
sortedcontainers==2.4.0
+sslpsk @ git+https://git@github.com/beremiz/sslpsk.git@9cb31986629b382f7427eec29ddc168ad21c7d7c
Twisted==22.10.0
txaio==23.1.1
typing_extensions==4.5.0
--- a/runtime/PLCObject.py Tue Feb 20 11:42:02 2024 +0100
+++ b/runtime/PLCObject.py Tue Feb 20 14:53:33 2024 +0100
@@ -148,6 +148,7 @@
return int(self._GetLogCount(level))
elif self._loading_error is not None and level == 0:
return 1
+ return 0
@RunInMain
def GetLogMessage(self, level, msgid):
@@ -223,7 +224,7 @@
self._RegisterDebugVariable = self.PLClibraryHandle.RegisterDebugVariable
self._RegisterDebugVariable.restype = ctypes.c_int
- self._RegisterDebugVariable.argtypes = [ctypes.c_int, ctypes.c_void_p]
+ self._RegisterDebugVariable.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_uint32]
self._FreeDebugData = self.PLClibraryHandle.FreeDebugData
self._FreeDebugData.restype = None
@@ -557,7 +558,7 @@
try:
return self._GetPLCstatus()
except EOFError:
- return (PlcStatus.Disconnected, None)
+ return (PlcStatus.Disconnected, [0]*LogLevelsCount)
@RunInMain
def _GetPLCstatus(self):
@@ -722,13 +723,8 @@
if self._suspendDebug(False) == 0:
# keep a copy of requested idx
self._ResetDebugVariables()
- for idx, iectype, force in idxs:
- if force is not None:
- c_type, _unpack_func, pack_func = \
- TypeTranslator.get(iectype,
- (None, None, None))
- force = ctypes.byref(pack_func(c_type, force))
- res = self._RegisterDebugVariable(idx, force)
+ for idx, force in idxs:
+ res = self._RegisterDebugVariable(idx, force, 0 if force is None else len(force))
if res != 0:
self._resumeDebug()
self._suspendDebug(True)
@@ -738,7 +734,7 @@
return self.DebugToken
else:
self._suspendDebug(True)
- return 4 # DEBUG_SUSPENDED
+ return 5 # DEBUG_SUSPENDED
def _TracesSwap(self):
self.LastSwapTrace = time()
--- a/runtime/PyroServer.py Tue Feb 20 11:42:02 2024 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,112 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# This file is part of Beremiz runtime.
-
-# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
-# Copyright (C) 2017: Andrey Skvortsov
-# Copyright (C) 2018: Edouard TISSERANT
-
-# See COPYING file for copyrights details.
-
-
-
-import sys
-import os
-
-import Pyro5
-import Pyro5.server
-
-import runtime
-from runtime.ServicePublisher import ServicePublisher
-
-Pyro5.config.SERIALIZER = "msgpack"
-
-def make_pyro_exposed_stub(method_name):
- stub = lambda self, *args, **kwargs: \
- getattr(self.plc_object_instance, method_name)(*args, **kwargs)
- stub.__name__ = method_name
- Pyro5.server.expose(stub)
- return stub
-
-
-class PLCObjectPyroAdapter(type("PLCObjectPyroStubs", (), {
- name: make_pyro_exposed_stub(name) for name in [
- "AppendChunkToBlob",
- "GetLogMessage",
- "GetPLCID",
- "GetPLCstatus",
- "GetTraceVariables",
- "MatchMD5",
- "NewPLC",
- "PurgeBlobs",
- "RemoteExec",
- "RepairPLC",
- "ResetLogCount",
- "SeedBlob",
- "SetTraceVariablesList",
- "StartPLC",
- "StopPLC"
- ]
-})):
- def __init__(self, plc_object_instance):
- self.plc_object_instance = plc_object_instance
-
-
-class PyroServer(object):
- def __init__(self, servicename, ip_addr, port):
- self.continueloop = True
- self.daemon = None
- self.servicename = servicename
- self.ip_addr = ip_addr
- self.port = port
- self.servicepublisher = None
- self.piper, self.pipew = None, None
-
- def _to_be_published(self):
- return self.servicename is not None and \
- self.ip_addr not in ["", "localhost", "127.0.0.1"]
-
- def PrintServerInfo(self):
- print(_("Pyro port :"), self.port)
-
- if self._to_be_published():
- print(_("Publishing service on local network"))
-
- if sys.stdout:
- sys.stdout.flush()
-
- def PyroLoop(self, when_ready):
- if self._to_be_published():
- self.Publish()
-
- while self.continueloop:
- self.daemon = Pyro5.server.Daemon(host=self.ip_addr, port=self.port)
-
- self.daemon.register(PLCObjectPyroAdapter(runtime.GetPLCObjectSingleton()), "PLCObject")
-
- when_ready()
-
- self.daemon.requestLoop()
-
- self.Unpublish()
-
- def Restart(self):
- self.daemon.shutdown(True)
-
- def Quit(self):
- self.continueloop = False
- self.daemon.shutdown()
- if not sys.platform.startswith('win'):
- if self.pipew is not None:
- os.write(self.pipew, "goodbye")
-
- def Publish(self):
- self.servicepublisher = ServicePublisher("PYRO")
- self.servicepublisher.RegisterService(self.servicename,
- self.ip_addr, self.port)
-
- def Unpublish(self):
- if self.servicepublisher is not None:
- self.servicepublisher.UnRegisterService()
- self.servicepublisher = None
--- a/runtime/Stunnel.py Tue Feb 20 11:42:02 2024 +0100
+++ b/runtime/Stunnel.py Tue Feb 20 14:53:33 2024 +0100
@@ -50,8 +50,8 @@
if not os.path.exists(_PSKpath):
errorlog(
'Error: Pre-Shared-Key Secret in %s is missing!\n' % _PSKpath)
- return None
+ return ("","")
ID, _sep, PSK = open(_PSKpath).read().partition(':')
PSK = PSK.rstrip('\n\r')
return (ID, PSK)
- return None
+ return ("","")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/runtime/eRPCServer.py Tue Feb 20 14:53:33 2024 +0100
@@ -0,0 +1,154 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Written by Edouard TISSERANT (C) 2024
+# This file is part of Beremiz runtime
+# See COPYING.Runtime file for copyrights details.
+
+import sys
+import traceback
+from inspect import getmembers, isfunction
+
+import erpc
+
+# eRPC service code
+from erpc_interface.erpc_PLCObject.common import PSKID, PLCstatus, TraceVariables, trace_sample, PLCstatus_enum, log_message
+from erpc_interface.erpc_PLCObject.interface import IBeremizPLCObjectService
+from erpc_interface.erpc_PLCObject.server import BeremizPLCObjectServiceService
+
+from runtime import GetPLCObjectSingleton as PLC
+from runtime.loglevels import LogLevelsDict
+from runtime.ServicePublisher import ServicePublisher
+
+
+CRITICAL_LOG_LEVEL = LogLevelsDict["CRITICAL"]
+
+def ReturnAsLastOutput(method, args_wrapper, *args):
+ args[-1].value = method(*args_wrapper(*args[:-1]))
+ return 0
+
+def TranslatedReturnAsLastOutput(translator):
+ def wrapper(method, args_wrapper, *args):
+ args[-1].value = translator(method(*args_wrapper(*args[:-1])))
+ return 0
+ return wrapper
+
+
+ReturnWrappers = {
+ "AppendChunkToBlob":ReturnAsLastOutput,
+ "GetLogMessage":TranslatedReturnAsLastOutput(
+ lambda res:log_message(*res)),
+ "GetPLCID":TranslatedReturnAsLastOutput(
+ lambda res:PSKID(*res)),
+ "GetPLCstatus":TranslatedReturnAsLastOutput(
+ lambda res:PLCstatus(getattr(PLCstatus_enum, res[0]),res[1])),
+ "GetTraceVariables":TranslatedReturnAsLastOutput(
+ lambda res:TraceVariables(getattr(PLCstatus_enum, res[0]),[trace_sample(*sample) for sample in res[1]])),
+ "MatchMD5":ReturnAsLastOutput,
+ "NewPLC":ReturnAsLastOutput,
+ "SeedBlob":ReturnAsLastOutput,
+ "SetTraceVariablesList": ReturnAsLastOutput,
+ "StopPLC":ReturnAsLastOutput,
+}
+
+ArgsWrappers = {
+ "AppendChunkToBlob":
+ lambda data, blobID:(data, bytes(blobID)),
+ "NewPLC":
+ lambda md5sum, plcObjectBlobID, extrafiles: (
+ md5sum, bytes(plcObjectBlobID), [(f.fname, bytes(f.blobID)) for f in extrafiles]),
+ "SetTraceVariablesList":
+ lambda orders : ([(order.idx, None if len(order.force)==0 else bytes(order.force)) for order in orders],)
+}
+
+def rpc_wrapper(method_name):
+ PLCobj = PLC()
+ method=getattr(PLCobj, method_name)
+ args_wrapper = ArgsWrappers.get(method_name, lambda *x:x)
+ return_wrapper = ReturnWrappers.get(method_name,
+ lambda method, args_wrapper, *args: method(*args_wrapper(*args)))
+
+ def exception_wrapper(self, *args):
+ try:
+ print("Srv "+method_name)
+ return_wrapper(method, args_wrapper, *args)
+ return 0
+ except Exception as e:
+ print(traceback.format_exc())
+ PLCobj.LogMessage(CRITICAL_LOG_LEVEL, f'eRPC call {method_name} Exception "{str(e)}"')
+ raise
+
+ return exception_wrapper
+
+
+class eRPCServer(object):
+ def __init__(self, servicename, ip_addr, port):
+ self.continueloop = True
+ self.server = None
+ self.transport = None
+ self.servicename = servicename
+ self.ip_addr = ip_addr
+ self.port = port
+ self.servicepublisher = None
+
+ def _to_be_published(self):
+ return self.servicename is not None and \
+ self.ip_addr not in ["", "localhost", "127.0.0.1"]
+
+ def PrintServerInfo(self):
+ print(_("eRPC port :"), self.port)
+
+ if self._to_be_published():
+ print(_("Publishing service on local network"))
+
+ if sys.stdout:
+ sys.stdout.flush()
+
+ def Loop(self, when_ready):
+ if self._to_be_published():
+ self.Publish()
+
+ while self.continueloop:
+
+ # service handler calls PLC object though erpc_stubs's wrappers
+ handler = type(
+ "PLCObjectServiceHandlder",
+ (IBeremizPLCObjectService,),
+ {name: rpc_wrapper(name)
+ for name,_func in getmembers(IBeremizPLCObjectService, isfunction)})()
+
+ service = BeremizPLCObjectServiceService(handler)
+
+ # TODO initialize Serial transport layer if selected
+ # transport = erpc.transport.SerialTransport(device, baudrate)
+
+ # initialize TCP transport layer
+ self.transport = erpc.transport.TCPTransport(self.ip_addr, int(self.port), True)
+
+ self.server = erpc.simple_server.SimpleServer(self.transport, erpc.basic_codec.BasicCodec)
+ self.server.add_service(service)
+
+ when_ready()
+
+ self.server.run()
+
+ self.Unpublish()
+
+ def Restart(self):
+ self.server.stop()
+ self.transport.stop()
+
+ def Quit(self):
+ self.continueloop = False
+ self.server.stop()
+ self.transport.stop()
+
+ def Publish(self):
+ self.servicepublisher = ServicePublisher("ERPC")
+ self.servicepublisher.RegisterService(self.servicename,
+ self.ip_addr, self.port)
+
+ def Unpublish(self):
+ if self.servicepublisher is not None:
+ self.servicepublisher.UnRegisterService()
+ self.servicepublisher = None
--- a/runtime/typemapping.py Tue Feb 20 11:42:02 2024 +0100
+++ b/runtime/typemapping.py Tue Feb 20 14:53:33 2024 +0100
@@ -42,7 +42,7 @@
"USINT": _t(c_uint8),
"BYTE": _t(c_uint8),
"STRING": (IEC_STRING,
- lambda x: x.body[:x.len],
+ lambda x: x.body[:x.len].decode(),
lambda t, x: t(len(x), x.encode() if type(x)==str else x)),
"INT": _t(c_int16),
"UINT": _t(c_uint16),
@@ -101,3 +101,9 @@
if buffoffset and buffoffset == buffsize:
return res
return None
+
+def ValueToIECBytes(iectype, value):
+ if value is None:
+ return None
+ c_type, _unpack_func, pack_func = TypeTranslator[iectype]
+ return bytes(pack_func(c_type, value))
--- a/targets/plc_debug.c Tue Feb 20 11:42:02 2024 +0100
+++ b/targets/plc_debug.c Tue Feb 20 14:53:33 2024 +0100
@@ -348,9 +348,17 @@
#define TRACE_LIST_OVERFLOW 1
#define FORCE_LIST_OVERFLOW 2
#define FORCE_BUFFER_OVERFLOW 3
+#define FORCE_INVALID 4
+
+#define __ForceVariable_checksize(TYPENAME) \
+ if(sizeof(TYPENAME) != force_size) { \
+ error_code = FORCE_BUFFER_OVERFLOW; \
+ goto error_cleanup; \
+ }
#define __ForceVariable_case_t(TYPENAME) \
case TYPENAME##_ENUM : \
+ __ForceVariable_checksize(TYPENAME) \
/* add to force_list*/ \
force_list_addvar_cursor->dbgvardsc_index = idx; \
((__IEC_##TYPENAME##_t *)varp)->flags |= __IEC_FORCE_FLAG; \
@@ -359,6 +367,7 @@
#define __ForceVariable_case_p(TYPENAME) \
case TYPENAME##_P_ENUM : \
case TYPENAME##_O_ENUM : \
+ __ForceVariable_checksize(TYPENAME) \
{ \
char *next_cursor = force_buffer_cursor + sizeof(TYPENAME); \
if(next_cursor <= force_buffer_end ){ \
@@ -389,7 +398,7 @@
void ResetDebugVariables(void);
-int RegisterDebugVariable(dbgvardsc_index_t idx, void* force)
+int RegisterDebugVariable(dbgvardsc_index_t idx, void* force, size_t force_size)
{
int error_code = 0;
if(idx < sizeof(dbgvardsc)/sizeof(dbgvardsc_t)){