connectors/ERPC/__init__.py
changeset 3884 34da877021d5
child 3885 22a009561502
equal deleted inserted replaced
3883:a6e7dd8bac36 3884:34da877021d5
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 # Written by Edouard TISSERANT (C) 2024
       
     5 # This file is part of Beremiz IDE
       
     6 # See COPYING file for copyrights details.
       
     7 
       
     8 
       
     9 import os.path
       
    10 import re
       
    11 import traceback
       
    12 from inspect import getmembers, isfunction
       
    13 
       
    14 
       
    15 import erpc
       
    16 
       
    17 # eRPC service code
       
    18 from erpc_interface.erpc_PLCObject.interface import IBeremizPLCObjectService
       
    19 from erpc_interface.erpc_PLCObject.client import BeremizPLCObjectServiceClient
       
    20 from erpc_interface.erpc_PLCObject.common import trace_order, extra_file, PLCstatus_enum
       
    21 
       
    22 import PSKManagement as PSK
       
    23 from connectors.ERPC.PSK_Adapter import SSLPSKClientTransport
       
    24 from connectors.ConnectorBase import ConnectorBase
       
    25 
       
    26 enum_to_PLCstatus = dict(map(lambda t:(t[1],t[0]),getmembers(PLCstatus_enum, lambda x:type(x)==int)))
       
    27 
       
    28 class MissingCallException(Exception):
       
    29     pass
       
    30 
       
    31 def ExceptionFromERPCReturn(ret):
       
    32     return {1:Exception,
       
    33             2:MissingCallException}.get(ret,ValueError)
       
    34 
       
    35 def ReturnAsLastOutput(client_method, obj, args_wrapper, *args):
       
    36     retval = erpc.Reference()
       
    37     ret = client_method(obj, *args_wrapper(*args), retval)
       
    38     if ret != 0:
       
    39         raise ExceptionFromERPCReturn(ret)(client_method.__name__)
       
    40     return retval.value
       
    41 
       
    42 def TranslatedReturnAsLastOutput(translator):
       
    43     def wrapper(client_method, obj, args_wrapper, *args):
       
    44         res = ReturnAsLastOutput(client_method, obj, args_wrapper, *args)
       
    45         return translator(res)
       
    46     return wrapper
       
    47 
       
    48 ReturnWrappers = {
       
    49     "AppendChunkToBlob":ReturnAsLastOutput,
       
    50     "GetLogMessage":TranslatedReturnAsLastOutput(
       
    51         lambda res:(res.msg, res.tick, res.sec, res.nsec)),
       
    52     "GetPLCID":TranslatedReturnAsLastOutput(
       
    53         lambda res:(res.ID, res.PSK)),
       
    54     "GetPLCstatus":TranslatedReturnAsLastOutput(
       
    55         lambda res:(enum_to_PLCstatus[res.PLCstatus], res.logcounts)),
       
    56     "GetTraceVariables":TranslatedReturnAsLastOutput(
       
    57         lambda res:(enum_to_PLCstatus[res.PLCstatus],
       
    58                     [(sample.tick, sample.TraceBuffer) for sample in res.traces])),
       
    59     "MatchMD5":ReturnAsLastOutput,
       
    60     "NewPLC":ReturnAsLastOutput,
       
    61     "SeedBlob":ReturnAsLastOutput,
       
    62 }
       
    63 
       
    64 ArgsWrappers = {
       
    65     "NewPLC":
       
    66         lambda md5sum, plcObjectBlobID, extrafiles: (
       
    67             md5sum, plcObjectBlobID, [extra_file(*f) for f in extrafiles]),
       
    68     "SetTraceVariablesList": 
       
    69         lambda orders : ([trace_order(*order) for order in orders],)
       
    70 }
       
    71 
       
    72 def ERPC_connector_factory(uri, confnodesroot):
       
    73     """
       
    74     returns the ERPC connector
       
    75     """
       
    76     confnodesroot.logger.write(_("ERPC connecting to URI : %s\n") % uri)
       
    77 
       
    78     # TODO add parsing for serial URI
       
    79     # ERPC:///dev/ttyXX:baudrate or ERPC://:COM4:baudrate
       
    80 
       
    81     try:
       
    82         _scheme, location = uri.split("://",1)
       
    83         locator, *IDhash = location.split('#',1)
       
    84         x = re.match(r'(?P<host>[^\s:]+):?(?P<port>\d+)?', locator)
       
    85         host = x.group('host')
       
    86         port = x.group('port')
       
    87         if port:
       
    88             port = int(port)
       
    89         else:
       
    90             port = 3000
       
    91     except Exception as e:
       
    92         confnodesroot.logger.write_error(
       
    93             'Malformed URI "%s": %s\n' % (uri, str(e)))
       
    94         return None
       
    95 
       
    96     def rpc_wrapper(method_name):
       
    97         client_method = getattr(BeremizPLCObjectServiceClient, method_name)
       
    98         return_wrapper = ReturnWrappers.get(
       
    99             method_name, 
       
   100             lambda client_method, obj, args_wrapper, *args: client_method(obj, *args_wrapper(*args)))
       
   101         args_wrapper = ArgsWrappers.get(method_name, lambda *x:x)
       
   102 
       
   103         def exception_wrapper(self, *args):
       
   104             try:
       
   105                 print("Clt "+method_name)
       
   106                 return return_wrapper(client_method, self, args_wrapper, *args)
       
   107             except erpc.transport.ConnectionClosed as e:
       
   108                 confnodesroot._SetConnector(None)
       
   109                 confnodesroot.logger.write_error(_("Connection lost!\n"))
       
   110             except erpc.codec.CodecError as e:
       
   111                 confnodesroot.logger.write_warning(_("ERPC codec error: %s\n") % e)
       
   112             except erpc.client.RequestError as e:
       
   113                 confnodesroot.logger.write_error(_("ERPC request error: %s\n") % e)                
       
   114             except MissingCallException as e:
       
   115                 confnodesroot.logger.write_warning(_("Remote call not supported: %s\n") % e.message)
       
   116             except Exception as e:
       
   117                 errmess = _("Exception calling remote PLC object fucntio %s:\n") % method_name \
       
   118                           + traceback.format_exc()
       
   119                 confnodesroot.logger.write_error(errmess + "\n")
       
   120                 print(errmess)
       
   121                 confnodesroot._SetConnector(None)
       
   122 
       
   123             return self.PLCObjDefaults.get(method_name)
       
   124         return exception_wrapper
       
   125 
       
   126 
       
   127     PLCObjectERPCProxy = type(
       
   128         "PLCObjectERPCProxy",
       
   129         (ConnectorBase, BeremizPLCObjectServiceClient),
       
   130         {name: rpc_wrapper(name)
       
   131             for name,_func in getmembers(IBeremizPLCObjectService, isfunction)})
       
   132 
       
   133     try:
       
   134         if IDhash:
       
   135             ID = IDhash[0]
       
   136             # load PSK from project
       
   137             secpath = os.path.join(str(confnodesroot.ProjectPath), 'psk', ID + '.secret')
       
   138             if not os.path.exists(secpath):
       
   139                 confnodesroot.logger.write_error(
       
   140                     'Error: Pre-Shared-Key Secret in %s is missing!\n' % secpath)
       
   141                 return None
       
   142             secret = open(secpath).read().partition(':')[2].rstrip('\n\r')
       
   143             transport = SSLPSKClientTransport(host, port, (secret, ID))
       
   144         else:
       
   145             # TODO if serial URI then 
       
   146             # transport = erpc.transport.SerialTransport(device, baudrate)
       
   147 
       
   148             transport = erpc.transport.TCPTransport(host, port, False)
       
   149 
       
   150         clientManager = erpc.client.ClientManager(transport, erpc.basic_codec.BasicCodec)
       
   151         client = PLCObjectERPCProxy(clientManager)
       
   152 
       
   153     except Exception as e:
       
   154         confnodesroot.logger.write_error(
       
   155             _("Connection to {loc} failed with exception {ex}\n").format(
       
   156                 loc=locator, ex=str(e)))
       
   157         return None
       
   158 
       
   159     # Check connection is effective.
       
   160     IDPSK = client.GetPLCID()
       
   161     if IDPSK:
       
   162         ID, secret = IDPSK
       
   163         PSK.UpdateID(confnodesroot.ProjectPath, ID, secret, uri)
       
   164     else:
       
   165         confnodesroot.logger.write_warning(_("PLC did not provide identity and security infomation.\n"))
       
   166 
       
   167     return client