# HG changeset patch # User Edouard Tisserant # Date 1708437213 -3600 # Node ID fc3621302cfe2b4549d63a74bf8ba4837d90788e # Parent df7ce3e1f44d94a4c2eefcb381a15fc4c6d62416# Parent 5b2f3a915a432ec8c37efa126a4fa20fe0695f22 merge diff -r 5b2f3a915a43 -r fc3621302cfe Beremiz_service.py --- 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: diff -r 5b2f3a915a43 -r fc3621302cfe ProjectController.py --- 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: diff -r 5b2f3a915a43 -r fc3621302cfe connectors/ConnectorBase.py --- 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, diff -r 5b2f3a915a43 -r fc3621302cfe connectors/ERPC/PSK_Adapter.py --- /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) + + diff -r 5b2f3a915a43 -r fc3621302cfe connectors/ERPC/__init__.py --- /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[^\s:]+):?(?P\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 diff -r 5b2f3a915a43 -r fc3621302cfe connectors/ERPC_dialog.py --- /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 '' diff -r 5b2f3a915a43 -r fc3621302cfe connectors/PYRO/PSK_Adapter.py --- 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'(?PPYROLOCPSK)://(?P[^\s:]+):?(?P\d+)?/(?P\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") diff -r 5b2f3a915a43 -r fc3621302cfe connectors/PYRO/__init__.py --- 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 diff -r 5b2f3a915a43 -r fc3621302cfe connectors/PYRO_dialog.py --- 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 '' diff -r 5b2f3a915a43 -r fc3621302cfe connectors/WAMP/__init__.py --- 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) diff -r 5b2f3a915a43 -r fc3621302cfe connectors/__init__.py --- 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() diff -r 5b2f3a915a43 -r fc3621302cfe editors/Viewer.py --- 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): diff -r 5b2f3a915a43 -r fc3621302cfe erpc_interface/__init__.py --- /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 +# + diff -r 5b2f3a915a43 -r fc3621302cfe erpc_interface/erpc_PLCObject.erpc --- /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 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 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 orders, out uint32 debugtoken) -> uint32 + StartPLC() -> uint32 + StopPLC(out bool success) -> uint32 +} diff -r 5b2f3a915a43 -r fc3621302cfe erpc_interface/erpc_PLCObject/__init__.py --- /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 diff -r 5b2f3a915a43 -r fc3621302cfe erpc_interface/erpc_PLCObject/client.py --- /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 + + diff -r 5b2f3a915a43 -r fc3621302cfe erpc_interface/erpc_PLCObject/common.py --- /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 + + 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__() + diff -r 5b2f3a915a43 -r fc3621302cfe erpc_interface/erpc_PLCObject/interface.py --- /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() + + diff -r 5b2f3a915a43 -r fc3621302cfe erpc_interface/erpc_PLCObject/server.py --- /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) + + diff -r 5b2f3a915a43 -r fc3621302cfe requirements.txt --- 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 diff -r 5b2f3a915a43 -r fc3621302cfe runtime/PLCObject.py --- 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() diff -r 5b2f3a915a43 -r fc3621302cfe runtime/PyroServer.py --- 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 diff -r 5b2f3a915a43 -r fc3621302cfe runtime/Stunnel.py --- 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 ("","") diff -r 5b2f3a915a43 -r fc3621302cfe runtime/eRPCServer.py --- /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 diff -r 5b2f3a915a43 -r fc3621302cfe runtime/typemapping.py --- 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)) diff -r 5b2f3a915a43 -r fc3621302cfe targets/plc_debug.c --- 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)){