diff -r a6e7dd8bac36 -r 34da877021d5 runtime/eRPCServer.py --- /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