|
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 |