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 IDE edouard@3884: # See COPYING file for copyrights details. edouard@3884: edouard@3884: edouard@3884: import os.path edouard@3884: import re edouard@3884: import traceback edouard@3884: from inspect import getmembers, isfunction edouard@3884: edouard@3884: edouard@3884: import erpc edouard@3884: edouard@3884: # eRPC service code edouard@3884: from erpc_interface.erpc_PLCObject.interface import IBeremizPLCObjectService edouard@3884: from erpc_interface.erpc_PLCObject.client import BeremizPLCObjectServiceClient edouard@3887: from erpc_interface.erpc_PLCObject.common import trace_order, extra_file, PLCstatus_enum edouard@3884: edouard@3884: import PSKManagement as PSK edouard@3884: from connectors.ERPC.PSK_Adapter import SSLPSKClientTransport edouard@3884: from connectors.ConnectorBase import ConnectorBase edouard@4041: from connectors.ERPC_URI import per_scheme_model edouard@3884: edouard@3884: enum_to_PLCstatus = dict(map(lambda t:(t[1],t[0]),getmembers(PLCstatus_enum, lambda x:type(x)==int))) edouard@3884: edouard@3884: class MissingCallException(Exception): edouard@3884: pass edouard@3884: edouard@3884: def ExceptionFromERPCReturn(ret): edouard@3884: return {1:Exception, edouard@3884: 2:MissingCallException}.get(ret,ValueError) edouard@3884: edouard@3884: def ReturnAsLastOutput(client_method, obj, args_wrapper, *args): edouard@3884: retval = erpc.Reference() edouard@3884: ret = client_method(obj, *args_wrapper(*args), retval) edouard@3884: if ret != 0: edouard@3884: raise ExceptionFromERPCReturn(ret)(client_method.__name__) edouard@3884: return retval.value edouard@3884: edouard@3884: def TranslatedReturnAsLastOutput(translator): edouard@3884: def wrapper(client_method, obj, args_wrapper, *args): edouard@3884: res = ReturnAsLastOutput(client_method, obj, args_wrapper, *args) edouard@3884: return translator(res) edouard@3884: return wrapper edouard@3884: edouard@3884: ReturnWrappers = { edouard@3884: "AppendChunkToBlob":ReturnAsLastOutput, edouard@3884: "GetLogMessage":TranslatedReturnAsLastOutput( edouard@3884: lambda res:(res.msg, res.tick, res.sec, res.nsec)), edouard@3884: "GetPLCID":TranslatedReturnAsLastOutput( edouard@3884: lambda res:(res.ID, res.PSK)), edouard@3884: "GetPLCstatus":TranslatedReturnAsLastOutput( edouard@3884: lambda res:(enum_to_PLCstatus[res.PLCstatus], res.logcounts)), edouard@3884: "GetTraceVariables":TranslatedReturnAsLastOutput( edouard@3884: lambda res:(enum_to_PLCstatus[res.PLCstatus], edouard@3885: [(sample.tick, bytes(sample.TraceBuffer)) for sample in res.traces])), edouard@3884: "MatchMD5":ReturnAsLastOutput, edouard@3884: "NewPLC":ReturnAsLastOutput, edouard@3884: "SeedBlob":ReturnAsLastOutput, edouard@3885: "SetTraceVariablesList": ReturnAsLastOutput, edouard@3885: "StopPLC":ReturnAsLastOutput, edouard@4032: "ExtendedCall":ReturnAsLastOutput, edouard@3884: } edouard@3884: edouard@3884: ArgsWrappers = { edouard@3884: "NewPLC": edouard@3884: lambda md5sum, plcObjectBlobID, extrafiles: ( edouard@3884: md5sum, plcObjectBlobID, [extra_file(*f) for f in extrafiles]), edouard@3887: "SetTraceVariablesList": edouard@3885: lambda orders : ([ edouard@3887: trace_order(idx, b"" if force is None else force) edouard@3887: for idx, force in orders],) edouard@3884: } edouard@3884: edouard@4040: def rpc_wrapper(method_name, confnodesroot): edouard@4040: client_method = getattr(BeremizPLCObjectServiceClient, method_name) edouard@4040: return_wrapper = ReturnWrappers.get( edouard@4040: method_name, edouard@4040: lambda client_method, obj, args_wrapper, *args: client_method(obj, *args_wrapper(*args))) edouard@4040: args_wrapper = ArgsWrappers.get(method_name, lambda *x:x) edouard@4040: edouard@4040: def exception_wrapper(self, *args): edouard@4040: try: edouard@4040: return return_wrapper(client_method, self, args_wrapper, *args) edouard@4040: except erpc.transport.ConnectionClosed as e: edouard@4040: confnodesroot._SetConnector(None) edouard@4040: confnodesroot.logger.write_error(_("Connection lost!\n")) edouard@4040: except erpc.codec.CodecError as e: edouard@4040: confnodesroot.logger.write_warning(_("ERPC codec error: %s\n") % e) edouard@4040: except erpc.client.RequestError as e: edouard@4040: confnodesroot.logger.write_error(_("ERPC request error: %s\n") % e) edouard@4040: except MissingCallException as e: edouard@4040: confnodesroot.logger.write_warning(_("Remote call not supported: %s\n") % e) edouard@4040: except Exception as e: edouard@4040: errmess = _("Exception calling remote PLC object fucntion %s:\n") % method_name \ edouard@4040: + traceback.format_exc() edouard@4040: confnodesroot.logger.write_error(errmess + "\n") edouard@4040: confnodesroot._SetConnector(None) edouard@4040: edouard@4040: return self.PLCObjDefaults.get(method_name) edouard@4040: return exception_wrapper edouard@4040: edouard@4040: edouard@4040: edouard@3884: def ERPC_connector_factory(uri, confnodesroot): edouard@3884: """ edouard@3884: returns the ERPC connector edouard@3884: """ edouard@3884: confnodesroot.logger.write(_("ERPC connecting to URI : %s\n") % uri) edouard@3884: edouard@3884: # TODO add parsing for serial URI edouard@3884: # ERPC:///dev/ttyXX:baudrate or ERPC://:COM4:baudrate edouard@3884: edouard@3884: try: edouard@4041: scheme, location = uri.split("://",1) edouard@4041: _model, _useID, parser, _builder = per_scheme_model[scheme] edouard@4041: location_data = parser(location) edouard@3884: except Exception as e: edouard@3884: confnodesroot.logger.write_error( edouard@3884: 'Malformed URI "%s": %s\n' % (uri, str(e))) edouard@3884: return None edouard@3884: edouard@3884: PLCObjectERPCProxy = type( edouard@3884: "PLCObjectERPCProxy", edouard@3884: (ConnectorBase, BeremizPLCObjectServiceClient), edouard@4040: {name: rpc_wrapper(name, confnodesroot) edouard@3884: for name,_func in getmembers(IBeremizPLCObjectService, isfunction)}) edouard@3884: edouard@4041: if scheme in ["ERPCS", "ERPC"]: edouard@4041: if scheme=="ERPCS": edouard@4041: ID = location_data["ID"] edouard@4041: if not ID: edouard@3884: confnodesroot.logger.write_error( edouard@4041: f'Invalid URI "{uri}": ERPCS requires PLC ID after "#"\n') edouard@3884: return None edouard@4041: default_port = 4000 edouard@3884: else: edouard@4041: ID = None edouard@4041: if "#" in location: edouard@4041: confnodesroot.logger.write_error( edouard@4041: f'URI "{uri}": Non-encrypted ERPC does not take a PLC ID after "#"\n') edouard@4041: return None edouard@4041: default_port = 3000 edouard@3884: edouard@4041: host = location_data["host"] edouard@4041: port = location_data["port"] edouard@4041: port = int(port) if port else default_port edouard@3884: edouard@4041: try: edouard@4041: if ID: edouard@4041: # load PSK from project edouard@4041: secpath = os.path.join(confnodesroot.ProjectPath, 'psk', ID + '.secret') edouard@4041: if not os.path.exists(secpath): edouard@4041: confnodesroot.logger.write_error( edouard@4041: 'Error: Pre-Shared-Key Secret in %s is missing!\n' % secpath) edouard@4041: return None edouard@4041: secret = open(secpath).read().partition(':')[2].rstrip('\n\r').encode() edouard@4041: transport = SSLPSKClientTransport(host, port, (secret, ID.encode())) # type: ignore edouard@4041: else: edouard@3884: edouard@4041: transport = erpc.transport.TCPTransport(host, port, False) edouard@4041: edouard@4041: clientManager = erpc.client.ClientManager(transport, erpc.basic_codec.BasicCodec) edouard@4041: client = PLCObjectERPCProxy(clientManager) edouard@4041: edouard@4041: except Exception as e: edouard@4041: confnodesroot.logger.write_error( edouard@4041: _("Connection to {loc} failed with exception {ex}\n").format( edouard@4041: loc=uri, ex=str(e))) edouard@4041: return None edouard@4041: edouard@4041: else: edouard@4041: # TODO if serial URI then edouard@4041: # transport = erpc.transport.SerialTransport(device, baudrate) edouard@4041: edouard@3884: confnodesroot.logger.write_error( edouard@4041: _("Unknown scheme {scheme} in URI {uri}\n").format( edouard@4041: scheme=scheme, uri=uri)) edouard@3884: return None edouard@3884: # Check connection is effective. edouard@3884: IDPSK = client.GetPLCID() edouard@3884: if IDPSK: edouard@3884: ID, secret = IDPSK edouard@3884: PSK.UpdateID(confnodesroot.ProjectPath, ID, secret, uri) edouard@3884: else: edouard@3884: confnodesroot.logger.write_warning(_("PLC did not provide identity and security infomation.\n")) edouard@3884: edouard@3884: return client