Replace PYRO with ERPC. Work In Progress.
--- a/Beremiz_service.py Sat Dec 09 01:03:43 2023 +0100
+++ b/Beremiz_service.py Wed Jan 17 22:09:32 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/connectors/ConnectorBase.py Sat Dec 09 01:03:43 2023 +0100
+++ b/connectors/ConnectorBase.py Wed Jan 17 22:09:32 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 Wed Jan 17 22:09:32 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 Wed Jan 17 22:09:32 2024 +0100
@@ -0,0 +1,167 @@
+#!/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, sample.TraceBuffer) for sample in res.traces])),
+ "MatchMD5":ReturnAsLastOutput,
+ "NewPLC":ReturnAsLastOutput,
+ "SeedBlob":ReturnAsLastOutput,
+}
+
+ArgsWrappers = {
+ "NewPLC":
+ lambda md5sum, plcObjectBlobID, extrafiles: (
+ md5sum, plcObjectBlobID, [extra_file(*f) for f in extrafiles]),
+ "SetTraceVariablesList":
+ lambda orders : ([trace_order(*order) for order 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 Wed Jan 17 22:09:32 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 Sat Dec 09 01:03:43 2023 +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 Sat Dec 09 01:03:43 2023 +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 Sat Dec 09 01:03:43 2023 +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 Sat Dec 09 01:03:43 2023 +0100
+++ b/connectors/WAMP/__init__.py Wed Jan 17 22:09:32 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 Sat Dec 09 01:03:43 2023 +0100
+++ b/connectors/__init__.py Wed Jan 17 22:09:32 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()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/erpc_interface/__init__.py Wed Jan 17 22:09:32 2024 +0100
@@ -0,0 +1,6 @@
+#
+# Generated by erpcgen 1.11.0 on Wed Jan 17 21:59:20 2024.
+#
+# AUTOGENERATED - DO NOT EDIT
+#
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/erpc_interface/erpc_PLCObject.erpc Wed Jan 17 22:09:32 2024 +0100
@@ -0,0 +1,72 @@
+/*
+ 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;
+ uint8 iectype;
+ 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) -> uint32
+ StartPLC() -> uint32
+ StopPLC() -> uint32
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/erpc_interface/erpc_PLCObject/__init__.py Wed Jan 17 22:09:32 2024 +0100
@@ -0,0 +1,19 @@
+#
+# Generated by erpcgen 1.11.0 on Wed Jan 17 21:59:20 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 Wed Jan 17 22:09:32 2024 +0100
@@ -0,0 +1,289 @@
+#
+# Generated by erpcgen 1.11.0 on Wed Jan 17 21:59:20 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):
+ # 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)
+ _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):
+ # 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)
+ _result = codec.read_uint32()
+ return _result
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/erpc_interface/erpc_PLCObject/common.py Wed Jan 17 22:09:32 2024 +0100
@@ -0,0 +1,215 @@
+#
+# Generated by erpcgen 1.11.0 on Wed Jan 17 21:59:20 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, iectype=None, force=None):
+ self.idx = idx # uint32
+ self.iectype = iectype # uint8
+ self.force = force # binary
+
+ def _read(self, codec):
+ self.idx = codec.read_uint32()
+ self.iectype = codec.read_uint8()
+ 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.iectype is None:
+ raise ValueError("iectype is None")
+ codec.write_uint8(self.iectype)
+ if self.force is None:
+ raise ValueError("force is None")
+ codec.write_binary(self.force)
+
+ def __str__(self):
+ return "<%s@%x idx=%s iectype=%s force=%s>" % (self.__class__.__name__, id(self), self.idx, self.iectype, 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 Wed Jan 17 22:09:32 2024 +0100
@@ -0,0 +1,67 @@
+#
+# Generated by erpcgen 1.11.0 on Wed Jan 17 21:59:20 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):
+ raise NotImplementedError()
+
+ def StartPLC(self):
+ raise NotImplementedError()
+
+ def StopPLC(self):
+ raise NotImplementedError()
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/erpc_interface/erpc_PLCObject/server.py Wed Jan 17 22:09:32 2024 +0100
@@ -0,0 +1,339 @@
+#
+# Generated by erpcgen 1.11.0 on Wed Jan 17 21:59:20 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):
+ # 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)
+
+ # 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))
+ 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):
+ # Read incoming parameters.
+
+ # Invoke user implementation of remote function.
+ _result = self._handler.StopPLC()
+
+ # 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))
+ codec.write_uint32(_result)
+
+
--- a/requirements.txt Sat Dec 09 01:03:43 2023 +0100
+++ b/requirements.txt Wed Jan 17 22:09:32 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
@@ -30,12 +31,11 @@
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
+sslpsk3==1.1.1
Twisted==22.10.0
txaio==23.1.1
typing_extensions==4.5.0
--- a/runtime/PLCObject.py Sat Dec 09 01:03:43 2023 +0100
+++ b/runtime/PLCObject.py Wed Jan 17 22:09:32 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):
@@ -557,7 +558,7 @@
try:
return self._GetPLCstatus()
except EOFError:
- return (PlcStatus.Disconnected, None)
+ return (PlcStatus.Disconnected, [0]*LogLevelsCount)
@RunInMain
def _GetPLCstatus(self):
--- a/runtime/PyroServer.py Sat Dec 09 01:03:43 2023 +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 Sat Dec 09 01:03:43 2023 +0100
+++ b/runtime/Stunnel.py Wed Jan 17 22:09:32 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 Wed Jan 17 22:09:32 2024 +0100
@@ -0,0 +1,152 @@
+#!/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(res[0],[trace_sample(*sample) for sample in res[1]])),
+ "MatchMD5":ReturnAsLastOutput,
+ "NewPLC":ReturnAsLastOutput,
+ "SeedBlob":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, order.iectype, 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