edouard@3884: #!/usr/bin/env python edouard@3884: # -*- coding: utf-8 -*- edouard@3884: edouard@3884: # Written by Edouard TISSERANT (C) 2024 edouard@3884: # This file is part of Beremiz runtime edouard@3884: # See COPYING.Runtime file for copyrights details. edouard@3884: edouard@3884: import sys edouard@3884: import traceback edouard@3884: from inspect import getmembers, isfunction edouard@3884: edouard@3884: import erpc edouard@3884: edouard@3884: # eRPC service code edouard@3887: from erpc_interface.erpc_PLCObject.common import PSKID, PLCstatus, TraceVariables, trace_sample, PLCstatus_enum, log_message edouard@3884: from erpc_interface.erpc_PLCObject.interface import IBeremizPLCObjectService edouard@3884: from erpc_interface.erpc_PLCObject.server import BeremizPLCObjectServiceService edouard@3884: edouard@3884: from runtime import GetPLCObjectSingleton as PLC edouard@3884: from runtime.loglevels import LogLevelsDict edouard@3884: from runtime.ServicePublisher import ServicePublisher edouard@3884: edouard@3884: edouard@3884: def ReturnAsLastOutput(method, args_wrapper, *args): edouard@3884: args[-1].value = method(*args_wrapper(*args[:-1])) edouard@3884: return 0 edouard@3884: edouard@3884: def TranslatedReturnAsLastOutput(translator): edouard@3884: def wrapper(method, args_wrapper, *args): edouard@3884: args[-1].value = translator(method(*args_wrapper(*args[:-1]))) edouard@3884: return 0 edouard@3884: return wrapper edouard@3884: edouard@3884: edouard@3884: ReturnWrappers = { edouard@3884: "AppendChunkToBlob":ReturnAsLastOutput, edouard@3884: "GetLogMessage":TranslatedReturnAsLastOutput( edouard@3884: lambda res:log_message(*res)), edouard@3884: "GetPLCID":TranslatedReturnAsLastOutput( edouard@3884: lambda res:PSKID(*res)), edouard@3884: "GetPLCstatus":TranslatedReturnAsLastOutput( edouard@3884: lambda res:PLCstatus(getattr(PLCstatus_enum, res[0]),res[1])), edouard@3884: "GetTraceVariables":TranslatedReturnAsLastOutput( edouard@3885: lambda res:TraceVariables(getattr(PLCstatus_enum, res[0]),[trace_sample(*sample) for sample in res[1]])), edouard@3884: "MatchMD5":ReturnAsLastOutput, edouard@3884: "NewPLC":ReturnAsLastOutput, edouard@3884: "SeedBlob":ReturnAsLastOutput, edouard@3885: "SetTraceVariablesList": ReturnAsLastOutput, edouard@3885: "StopPLC":ReturnAsLastOutput, edouard@3884: } edouard@3884: edouard@3884: ArgsWrappers = { edouard@3884: "AppendChunkToBlob": edouard@3884: lambda data, blobID:(data, bytes(blobID)), edouard@3884: "NewPLC": edouard@3884: lambda md5sum, plcObjectBlobID, extrafiles: ( edouard@3884: md5sum, bytes(plcObjectBlobID), [(f.fname, bytes(f.blobID)) for f in extrafiles]), edouard@3884: "SetTraceVariablesList": edouard@3887: lambda orders : ([(order.idx, None if len(order.force)==0 else bytes(order.force)) for order in orders],) edouard@3884: } edouard@3884: edouard@3884: def rpc_wrapper(method_name): edouard@3884: PLCobj = PLC() edouard@3884: method=getattr(PLCobj, method_name) edouard@3884: args_wrapper = ArgsWrappers.get(method_name, lambda *x:x) edouard@3884: return_wrapper = ReturnWrappers.get(method_name, edouard@3884: lambda method, args_wrapper, *args: method(*args_wrapper(*args))) edouard@3884: edouard@3884: def exception_wrapper(self, *args): edouard@3884: try: edouard@3884: return_wrapper(method, args_wrapper, *args) edouard@3884: return 0 edouard@3884: except Exception as e: edouard@3884: print(traceback.format_exc()) edouard@3884: PLCobj.LogMessage(CRITICAL_LOG_LEVEL, f'eRPC call {method_name} Exception "{str(e)}"') edouard@3884: raise edouard@3884: edouard@3884: return exception_wrapper edouard@3884: edouard@3884: edouard@3884: class eRPCServer(object): edouard@3884: def __init__(self, servicename, ip_addr, port): edouard@3884: self.continueloop = True edouard@3884: self.server = None edouard@3884: self.transport = None edouard@3884: self.servicename = servicename edouard@3884: self.ip_addr = ip_addr edouard@3884: self.port = port edouard@3884: self.servicepublisher = None edouard@3884: edouard@3884: def _to_be_published(self): edouard@3884: return self.servicename is not None and \ edouard@3884: self.ip_addr not in ["", "localhost", "127.0.0.1"] edouard@3884: edouard@3884: def PrintServerInfo(self): edouard@3884: print(_("eRPC port :"), self.port) edouard@3884: edouard@3884: if self._to_be_published(): edouard@3884: print(_("Publishing service on local network")) edouard@3884: edouard@3884: if sys.stdout: edouard@3884: sys.stdout.flush() edouard@3884: edouard@3884: def Loop(self, when_ready): edouard@3884: if self._to_be_published(): edouard@3884: self.Publish() edouard@3884: edouard@3905: # service handler calls PLC object though erpc_stubs's wrappers edouard@3905: handler = type( edouard@3905: "PLCObjectServiceHandlder", edouard@3905: (IBeremizPLCObjectService,), edouard@3905: {name: rpc_wrapper(name) edouard@3905: for name,_func in getmembers(IBeremizPLCObjectService, isfunction)})() edouard@3905: edouard@3905: service = BeremizPLCObjectServiceService(handler) edouard@3905: edouard@3905: # TODO initialize Serial transport layer if selected edouard@3905: # transport = erpc.transport.SerialTransport(device, baudrate) edouard@3905: edouard@3905: # initialize TCP transport layer edouard@3905: self.transport = erpc.transport.TCPTransport(self.ip_addr, int(self.port), True) edouard@3905: edouard@3905: self.server = erpc.simple_server.SimpleServer(self.transport, erpc.basic_codec.BasicCodec) edouard@3905: self.server.add_service(service) edouard@3905: edouard@3905: when_ready() edouard@3905: edouard@3884: while self.continueloop: edouard@3884: edouard@3905: try: edouard@3905: self.server.run() edouard@3905: except erpc.transport.ConnectionClosed: edouard@3905: PLC().LogMessage(LogLevelsDict["DEBUG"], 'eRPC client disconnected') edouard@3905: except Exception as e: edouard@3905: self.Unpublish() edouard@3905: #TODO crash better edouard@3905: raise e edouard@3884: edouard@3884: edouard@3884: def Restart(self): edouard@3884: self.server.stop() edouard@3884: edouard@3884: def Quit(self): edouard@3884: self.continueloop = False edouard@3884: self.server.stop() edouard@3884: edouard@3884: def Publish(self): edouard@3884: self.servicepublisher = ServicePublisher("ERPC") edouard@3884: self.servicepublisher.RegisterService(self.servicename, edouard@3884: self.ip_addr, self.port) edouard@3884: edouard@3884: def Unpublish(self): edouard@3884: if self.servicepublisher is not None: edouard@3884: self.servicepublisher.UnRegisterService() edouard@3884: self.servicepublisher = None