blob: b4c7aa752680fa81e2aa68f3f6243d3c89be0073 [file] [log] [blame]
Constantin Ziesche857c7ab2020-02-25 11:24:51 +01001/*******************************************************************************
2* Copyright (c) 2020 Robert Bosch GmbH
3* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
4*
5* This program and the accompanying materials are made available under the
6* terms of the Eclipse Public License 2.0 which is available at
7* http://www.eclipse.org/legal/epl-2.0
8*
9* SPDX-License-Identifier: EPL-2.0
10*******************************************************************************/
11using BaSyx.API.AssetAdministrationShell;
12using BaSyx.Models.Core.AssetAdministrationShell.Generics;
13using BaSyx.Models.Core.AssetAdministrationShell.Implementations;
14using BaSyx.Utils.ResultHandling;
15using BaSyx.Utils.Client;
16using System.Collections.Generic;
17using System;
18using Newtonsoft.Json;
19using System.Reflection;
20using System.Linq.Expressions;
21using System.Linq;
Constantin Ziesche857c7ab2020-02-25 11:24:51 +010022using BaSyx.Models.Connectivity.Descriptors;
23using BaSyx.Models.Core.Common;
24using BaSyx.Models.Core.AssetAdministrationShell.Generics.SubmodelElementTypes;
Constantin Zieschefa612082020-04-03 09:54:56 +020025using BaSyx.Models.Communication;
26using System.Threading.Tasks;
Constantin Ziesche8b4a64d2020-06-25 11:52:09 +020027using BaSyx.Models.Extensions;
Constantin Ziesche857c7ab2020-02-25 11:24:51 +010028
29namespace BaSyx.API.Components
30{
31 public class SubmodelServiceProvider : ISubmodelServiceProvider
32 {
Constantin Ziesche857c7ab2020-02-25 11:24:51 +010033 public ISubmodel Submodel { get; protected set; }
34 public ISubmodelDescriptor ServiceDescriptor { get; internal set; }
35
36 private Dictionary<string, Delegate> methodCalledHandler;
37 private Dictionary<string, PropertyHandler> propertyHandler;
38 private Dictionary<string, Action<IValue>> updateFunctions;
39 private Dictionary<string, EventDelegate> eventDelegates;
Constantin Zieschefa612082020-04-03 09:54:56 +020040 private Dictionary<string, InvocationResponse> invocationResults;
Constantin Ziesche857c7ab2020-02-25 11:24:51 +010041
42 private IMessageClient messageClient;
43
44 private readonly GetPropertyValueHandler GenericPropertyGetHandler = de => { return new ElementValue(de.Value, de.ValueType); };
45 private readonly SetPropertyValueHandler GenericPropertySetHandler = (de, val) => { de.Value = val.Value; };
46
47 public SubmodelServiceProvider()
48 {
49 methodCalledHandler = new Dictionary<string, Delegate>();
50 propertyHandler = new Dictionary<string, PropertyHandler>();
51 updateFunctions = new Dictionary<string, Action<IValue>>();
52 eventDelegates = new Dictionary<string, EventDelegate>();
Constantin Zieschefa612082020-04-03 09:54:56 +020053 invocationResults = new Dictionary<string, InvocationResponse>();
Constantin Ziesche857c7ab2020-02-25 11:24:51 +010054 }
55
56 public SubmodelServiceProvider(ISubmodel submodel) : this()
57 {
58 BindTo(submodel);
59 }
60 public SubmodelServiceProvider(ISubmodel submodel, ISubmodelDescriptor submodelDescriptor) : this()
61 {
62 Submodel = submodel;
63 ServiceDescriptor = submodelDescriptor;
64 }
65
66 public void BindTo(ISubmodel element)
67 {
68 Submodel = element;
69 ServiceDescriptor = new SubmodelDescriptor(element, null);
70 }
71 public ISubmodel GetBinding()
72 {
73 return Submodel;
74 }
75
76 public void UseInMemoryPropertyHandler()
77 {
78 if(Submodel.Properties?.Count() > 0)
79 foreach (var property in Submodel.Properties)
80 {
81 RegisterPropertyHandler(property.IdShort,
82 new PropertyHandler(property.Get ?? GenericPropertyGetHandler, property.Set ?? GenericPropertySetHandler));
83 }
84 }
85
86 public void UsePropertyHandler(PropertyHandler propertyHandler)
87 {
88 if (Submodel.Properties?.Count() > 0)
89 foreach (var property in Submodel.Properties)
90 RegisterPropertyHandler(property.IdShort, propertyHandler);
91 }
92
93 public void UseOperationHandler(MethodCalledHandler methodCalledHandler)
94 {
95 if (Submodel.Operations?.Count() > 0)
96 foreach (var operation in Submodel.Operations)
97 RegisterMethodCalledHandler(operation.IdShort, methodCalledHandler);
98 }
99
100 public IResult<IEvent> CreateEvent(IEvent eventable)
101 {
102 if (Submodel == null)
103 return new Result<IEvent>(false, new NotFoundMessage("Submodel"));
104
105 return Submodel.SubmodelElements.Create(eventable);
106 }
107
108 public IResult<IOperation> CreateOperation(IOperation operation)
109 {
110 if (Submodel == null)
111 return new Result<IOperation>(false, new NotFoundMessage("Submodel"));
112
113 return Submodel.SubmodelElements.Create(operation);
114 }
115
116 public IResult<IProperty> CreateProperty(IProperty property)
117 {
118 if (Submodel == null)
119 return new Result<IProperty>(false, new NotFoundMessage("Submodel"));
120
121 RegisterPropertyHandler(property.IdShort,
122 new PropertyHandler(property.Get ?? GenericPropertyGetHandler, property.Set ?? GenericPropertySetHandler));
123
124 return Submodel.SubmodelElements.Create(property);
125 }
126
127 public IResult DeleteEvent(string eventId)
128 {
129 if (Submodel == null)
130 return new Result(false, new NotFoundMessage("Submodel"));
131
132 if (Submodel.Events == null)
133 return new Result(false, new NotFoundMessage(eventId));
134 return Submodel.SubmodelElements.Delete(eventId);
135 }
136
137 public IResult DeleteOperation(string operationId)
138 {
139 if (Submodel == null)
140 return new Result(false, new NotFoundMessage("Submodel"));
141
142 if (Submodel.Operations == null)
143 return new Result(false, new NotFoundMessage(operationId));
144 return Submodel.SubmodelElements.Delete(operationId);
145 }
146
147 public IResult DeleteProperty(string propertyId)
148 {
149 if (Submodel == null)
150 return new Result(false, new NotFoundMessage("Submodel"));
151
152 if (Submodel.Properties == null)
153 return new Result(false, new NotFoundMessage(propertyId));
154
155 if (propertyHandler.ContainsKey(propertyId))
156 propertyHandler.Remove(propertyId);
157
158 return Submodel.SubmodelElements.Delete(propertyId);
159 }
160
161 public MethodCalledHandler RetrieveMethodCalledHandler(string operationId)
162 {
163 if (methodCalledHandler.TryGetValue(operationId, out Delegate handler))
164 return (MethodCalledHandler)handler;
165 else
166 return null;
167 }
168
169 public Delegate RetrieveMethodDelegate(string operationId)
170 {
171 if (methodCalledHandler.TryGetValue(operationId, out Delegate handler))
172 return handler;
173 else
174 return null;
175 }
176
177 public PropertyHandler RetrievePropertyHandler(string propertyId)
178 {
179 if (propertyHandler.TryGetValue(propertyId, out PropertyHandler handler))
180 return handler;
181 else
182 return null;
183 }
184
185 public void RegisterPropertyHandler(string propertyId, PropertyHandler handler)
186 {
187 if (!propertyHandler.ContainsKey(propertyId))
188 propertyHandler.Add(propertyId, handler);
189 else
190 propertyHandler[propertyId] = handler;
191 }
192
193 public void RegisterMethodCalledHandler(string operationId, MethodCalledHandler handler)
194 {
195 if (!methodCalledHandler.ContainsKey(operationId))
196 methodCalledHandler.Add(operationId, handler);
197 else
198 methodCalledHandler[operationId] = handler;
199 }
200
201 public void RegisterMethodCalledHandler(string operationId, Delegate handler)
202 {
203 if (!methodCalledHandler.ContainsKey(operationId))
204 methodCalledHandler.Add(operationId, handler);
205 else
206 methodCalledHandler[operationId] = handler;
207 }
208
209 public void RegisterEventDelegate(string eventId, EventDelegate eventDelegate)
210 {
211 if (!eventDelegates.ContainsKey(eventId))
212 eventDelegates.Add(eventId, eventDelegate);
213 else
214 eventDelegates[eventId] = eventDelegate;
215 }
216
217 public void RegisterMethodCalledHandler(string operationId, MethodInfo methodInfo, object target)
218 {
219 var parameters = from parameter in methodInfo.GetParameters() select parameter.ParameterType;
220 Delegate del = methodInfo.CreateDelegate(Expression.GetDelegateType(parameters.Concat(new[] { methodInfo.ReturnType }).ToArray()), target);
221 RegisterMethodCalledHandler(operationId, del);
222 }
223
Constantin Zieschefa612082020-04-03 09:54:56 +0200224 public IResult<CallbackResponse> InvokeOperationAsync(string operationId, InvocationRequest invocationRequest)
Constantin Ziesche857c7ab2020-02-25 11:24:51 +0100225 {
226 if (Submodel == null)
Constantin Zieschefa612082020-04-03 09:54:56 +0200227 return new Result<CallbackResponse>(false, new NotFoundMessage("Submodel"));
228 if (invocationRequest == null)
229 return new Result<CallbackResponse>(new ArgumentNullException(nameof(invocationRequest)));
Constantin Ziesche857c7ab2020-02-25 11:24:51 +0100230
231 var operation_Retrieved = RetrieveOperation(operationId);
232 if (operation_Retrieved.Success && operation_Retrieved.Entity != null)
233 {
Constantin Zieschefa612082020-04-03 09:54:56 +0200234 Delegate methodDelegate;
Constantin Ziesche857c7ab2020-02-25 11:24:51 +0100235 if (operation_Retrieved.Entity.OnMethodCalled != null)
Constantin Zieschefa612082020-04-03 09:54:56 +0200236 methodDelegate = operation_Retrieved.Entity.OnMethodCalled;
Constantin Ziesche857c7ab2020-02-25 11:24:51 +0100237 else if (methodCalledHandler.TryGetValue(operationId, out Delegate handler))
Constantin Zieschefa612082020-04-03 09:54:56 +0200238 methodDelegate = handler;
239 else
240 return new Result<CallbackResponse>(false, new NotFoundMessage($"MethodHandler for {operationId}"));
241
242 var invocationTask = Task.Run(() =>
Constantin Ziesche857c7ab2020-02-25 11:24:51 +0100243 {
Constantin Zieschefa612082020-04-03 09:54:56 +0200244 InvocationResponse invocationResponse = new InvocationResponse(invocationRequest.RequestId);
245 SetInvocationResult(operationId, invocationRequest.RequestId, ref invocationResponse);
246
247 try
248 {
249 invocationResponse.ExecutionState = ExecutionState.Running;
250 Task<OperationResult> taskResult = (Task<OperationResult>)methodDelegate.DynamicInvoke(operation_Retrieved.Entity, invocationRequest.InputArguments, invocationResponse.OutputArguments);
251 invocationResponse.OperationResult = taskResult.Result;
252 invocationResponse.ExecutionState = ExecutionState.Completed;
253 }
254 catch (Exception e)
255 {
256 invocationResponse.ExecutionState = ExecutionState.Failed;
257 invocationResponse.OperationResult = new OperationResult(e);
258 }
259 });
260 string endpoint = ServiceDescriptor?.Endpoints?.FirstOrDefault()?.Address;
261 CallbackResponse callbackResponse = new CallbackResponse(invocationRequest.RequestId);
262 if (string.IsNullOrEmpty(endpoint))
263 callbackResponse.CallbackUrl = new Uri($"/operations/{operationId}/invocationList/{invocationRequest.RequestId}", UriKind.Relative);
264 else
265 callbackResponse.CallbackUrl = new Uri($"{endpoint}/operations/{operationId}/invocationList/{invocationRequest.RequestId}", UriKind.Absolute);
266 return new Result<CallbackResponse>(true, callbackResponse);
267 }
268 return new Result<CallbackResponse>(operation_Retrieved);
269 }
270
271 private void SetInvocationResult(string operationId, string requestId, ref InvocationResponse invocationResponse)
272 {
273 string key = string.Join("_", operationId, requestId);
274 if (invocationResults.ContainsKey(key))
275 {
276 invocationResults[key] = invocationResponse;
277 }
278 else
279 {
280 invocationResults.Add(key, invocationResponse);
281 }
282 }
283
284 public IResult<InvocationResponse> GetInvocationResult(string operationId, string requestId)
285 {
286 string key = string.Join("_", operationId, requestId);
287 if (invocationResults.ContainsKey(key))
288 {
289 return new Result<InvocationResponse>(true, invocationResults[key]);
290 }
291 else
292 {
293 return new Result<InvocationResponse>(false, new NotFoundMessage($"Request with id {requestId}"));
294 }
295 }
296
297
298 public IResult<InvocationResponse> InvokeOperation(string operationId, InvocationRequest invocationRequest)
299 {
300 if (Submodel == null)
301 return new Result<InvocationResponse>(false, new NotFoundMessage("Submodel"));
302
303 var operation_Retrieved = RetrieveOperation(operationId);
304 if (operation_Retrieved.Success && operation_Retrieved.Entity != null)
305 {
306 Delegate methodDelegate;
307 if (operation_Retrieved.Entity.OnMethodCalled != null)
308 methodDelegate = operation_Retrieved.Entity.OnMethodCalled;
309 else if (methodCalledHandler.TryGetValue(operationId, out Delegate handler))
310 methodDelegate = handler;
311 else
312 return new Result<InvocationResponse>(false, new NotFoundMessage($"MethodHandler for {operationId}"));
313 try
314 {
315 InvocationResponse invocationResponse = new InvocationResponse(invocationRequest.RequestId);
316 invocationResponse.ExecutionState = ExecutionState.Running;
317 Task<OperationResult> taskResult = (Task<OperationResult>)methodDelegate.DynamicInvoke(operation_Retrieved.Entity, invocationRequest.InputArguments, invocationResponse.OutputArguments);
318 invocationResponse.OperationResult = taskResult.Result;
319 invocationResponse.ExecutionState = ExecutionState.Completed;
320
321 return new Result<InvocationResponse>(true, invocationResponse);
322 }
323 catch (Exception e)
324 {
325 return new Result<InvocationResponse>(e);
Constantin Ziesche857c7ab2020-02-25 11:24:51 +0100326 }
327 }
Constantin Zieschefa612082020-04-03 09:54:56 +0200328 return new Result<InvocationResponse>(operation_Retrieved);
Constantin Ziesche857c7ab2020-02-25 11:24:51 +0100329 }
330
331 public IResult ThrowEvent(IPublishableEvent publishableEvent, string topic = "/", Action<IMessagePublishedEventArgs> MessagePublished = null, byte qosLevel = 2, bool retain = false)
332 {
333 if (messageClient == null || !messageClient.IsConnected)
334 return new Result(false, new Message(MessageType.Warning, "MessageClient is not initialized or not connected"));
335
336 if (publishableEvent == null)
337 return new Result(new ArgumentNullException("publishableEvent"));
338
339 if (eventDelegates.TryGetValue(publishableEvent.Name, out EventDelegate eventDelegate))
340 eventDelegate.Invoke(this, publishableEvent);
341
Constantin Zieschefa612082020-04-03 09:54:56 +0200342 string message = JsonConvert.SerializeObject(publishableEvent, Formatting.Indented);
Constantin Ziesche857c7ab2020-02-25 11:24:51 +0100343 return messageClient.Publish(topic, message, MessagePublished, qosLevel, retain);
344 }
345
346 public IResult<IEvent> RetrieveEvent(string eventId)
347 {
348 if (Submodel == null)
349 return new Result<IEvent>(false, new NotFoundMessage("Submodel"));
350
351 if (Submodel.Events == null)
352 return new Result<IEvent>(false, new NotFoundMessage(eventId));
353 return Submodel.SubmodelElements.Retrieve<IEvent>(eventId);
354 }
355
356 public IResult<IElementContainer<IEvent>> RetrieveEvents()
357 {
358 if (Submodel == null)
359 return new Result<ElementContainer<IEvent>>(false, new NotFoundMessage("Submodel"));
360
361 if(Submodel.Events == null)
362 return new Result<ElementContainer<IEvent>>(false, new NotFoundMessage("Events"));
363 return Submodel.SubmodelElements.RetrieveAll<IEvent>();
364 }
365
366 public IResult<IOperation> RetrieveOperation(string operationId)
367 {
368 if (Submodel == null)
369 return new Result<IOperation>(false, new NotFoundMessage("Submodel"));
370
371 if (Submodel.Operations == null)
372 return new Result<IOperation>(false, new NotFoundMessage(operationId));
373 return Submodel.SubmodelElements.Retrieve<IOperation>(operationId);
374 }
375
376 public IResult<IElementContainer<IOperation>> RetrieveOperations()
377 {
378 if (Submodel == null)
379 return new Result<ElementContainer<IOperation>>(false, new NotFoundMessage("Submodel"));
380
381 if (Submodel.Operations == null)
382 return new Result<ElementContainer<IOperation>>(false, new NotFoundMessage("Operations"));
383 return Submodel.SubmodelElements.RetrieveAll<IOperation>();
384 }
385
386 public IResult<IElementContainer<IProperty>> RetrieveProperties()
387 {
388 if (Submodel == null)
389 return new Result<ElementContainer<IProperty>>(false, new NotFoundMessage("Submodel"));
390
391 if (Submodel.Properties == null)
392 return new Result<ElementContainer<IProperty>>(false, new NotFoundMessage("Properties"));
393 return Submodel.SubmodelElements.RetrieveAll<IProperty>();
394 }
395
396 public IResult<IProperty> RetrieveProperty(string propertyId)
397 {
398 if (Submodel == null)
399 return new Result<IProperty>(false, new NotFoundMessage("Submodel"));
400
401 if (Submodel.Properties == null)
402 return new Result<IProperty>(false, new NotFoundMessage(propertyId));
403 return Submodel.SubmodelElements.Retrieve<IProperty>(propertyId);
404 }
405
406 public IResult<IValue> RetrievePropertyValue(string propertyId)
407 {
408 if (Submodel == null)
409 return new Result<IValue>(false, new NotFoundMessage("Submodel"));
410
411 if (propertyHandler.TryGetValue(propertyId, out PropertyHandler handler) && handler.GetHandler != null)
412 {
413 var property = RetrieveProperty(propertyId);
414 if(property.Success && property.Entity != null)
415 return new Result<IValue>(true, handler.GetHandler.Invoke(property.Entity));
416 else
417 return new Result<IValue>(false, new Message(MessageType.Error, "property not found"));
418 }
419 else
420 return new Result<IValue>(false, new Message(MessageType.Error, "propertyHandler or GetHandler not found"));
421 }
422
423
424 public IResult UpdatePropertyValue(string propertyId, IValue value)
425 {
426 if (Submodel == null)
427 return new Result(false, new NotFoundMessage("Submodel"));
428
429 if (propertyHandler.TryGetValue(propertyId, out PropertyHandler handler) && handler.SetHandler != null)
430 {
431 var property = RetrieveProperty(propertyId);
432 if (property.Success && property.Entity != null)
433 {
434 handler.SetHandler.Invoke(property.Entity, value);
435 return new Result(true);
436 }
437 else
438 return new Result<IValue>(false, new Message(MessageType.Error, "property not found"));
439 }
440 else
441 return new Result<IValue>(false, new Message(MessageType.Error, "propertyHandler or SetHandler not found"));
442 }
443
444 public virtual void ConfigureEventHandler(IMessageClient messageClient)
445 {
446 this.messageClient = messageClient;
447 }
448
449 public virtual void SubscribeUpdates(string propertyId, Action<IValue> updateFunction)
450 {
451 if (!updateFunctions.ContainsKey(propertyId))
452 updateFunctions.Add(propertyId, updateFunction);
453 else
454 updateFunctions[propertyId] = updateFunction;
455 }
456
457 public virtual void PublishUpdate(string propertyId, IValue value)
458 {
459 if (updateFunctions.TryGetValue(propertyId, out Action<IValue> updateFunction))
460 updateFunction.Invoke(value);
461
462 }
463
464 public IResult<ISubmodel> RetrieveSubmodel()
465 {
466 var submodel = GetBinding();
467 return new Result<ISubmodel>(submodel != null, submodel);
468 }
469
470 public IResult<ISubmodelElement> CreateSubmodelElement(ISubmodelElement submodelElement)
471 {
472 if (Submodel == null)
473 return new Result<ISubmodelElement>(false, new NotFoundMessage("Submodel"));
474
475 return Submodel.SubmodelElements.Create(submodelElement);
476 }
477
478 public IResult<IElementContainer<ISubmodelElement>> RetrieveSubmodelElements()
479 {
480 if (Submodel == null)
481 return new Result<ElementContainer<ISubmodelElement>>(false, new NotFoundMessage("Submodel"));
482
483 if (Submodel.SubmodelElements == null)
484 return new Result<ElementContainer<ISubmodelElement>>(false, new NotFoundMessage("SubmodelElements"));
485 return Submodel.SubmodelElements.RetrieveAll();
486 }
487
488 public IResult<ISubmodelElement> RetrieveSubmodelElement(string submodelElementId)
489 {
490 if (Submodel == null)
491 return new Result<ISubmodelElement>(false, new NotFoundMessage("Submodel"));
492
493 if (Submodel.SubmodelElements == null)
494 return new Result<ISubmodelElement>(false, new NotFoundMessage(submodelElementId));
Constantin Ziesche8b4a64d2020-06-25 11:52:09 +0200495
496 IResult<ISubmodelElement> result;
497 if(submodelElementId.Contains("/"))
498 {
499 string[] smeIds = submodelElementId.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
500 IResult<ISubmodelElement> firstElement = Submodel.SubmodelElements.Retrieve(smeIds[0]);
501 result = RetrieveSubordinateElement(firstElement, smeIds.Skip(1));
502 }
503 else
504 {
505 result = Submodel.SubmodelElements.Retrieve(submodelElementId);
506 }
507 return result;
508 }
509
510 private IResult<ISubmodelElement> RetrieveSubordinateElement(IResult<ISubmodelElement> smElement, IEnumerable<string> idShorts)
511 {
512 if(smElement.Success && smElement.Entity != null)
513 {
514 if(smElement.Entity.ModelType == ModelType.SubmodelElementCollection)
515 {
516 ISubmodelElementCollection smeCollection = smElement.Entity.ToModelElement<ISubmodelElementCollection>();
517 if(idShorts?.Count() > 0 && smeCollection.Value?.Count > 0)
518 {
519 IResult<ISubmodelElement> nextElement = smeCollection.Value.Retrieve(idShorts.First());
520 if (idShorts.Count() > 1)
521 return RetrieveSubordinateElement(nextElement, idShorts.Skip(1));
522 else
523 return nextElement;
524 }
525 }
526 else
527 {
528 if (idShorts.Count() > 0)
529 return new Result<ISubmodelElement>(false, new NotFoundMessage(string.Join("/", idShorts)));
530 }
531 }
532 return smElement;
Constantin Ziesche857c7ab2020-02-25 11:24:51 +0100533 }
534
535 public IResult<IValue> RetrieveSubmodelElementValue(string submodelElementId)
536 {
537 if (Submodel == null)
538 return new Result<IValue>(false, new NotFoundMessage("Submodel"));
539
540 if (Submodel.SubmodelElements == null)
541 return new Result<IValue>(false, new NotFoundMessage(submodelElementId));
542
543 return new Result<IValue>(true,
544 new ElementValue(
545 (Submodel.SubmodelElements[submodelElementId] as dynamic).Value,
546 (Submodel.SubmodelElements[submodelElementId] as dynamic).ValueType as DataType));
547 }
548
549 public IResult UpdateSubmodelElement(string submodelElementId, ISubmodelElement submodelElement)
550 {
551 if (Submodel == null)
552 return new Result(false, new NotFoundMessage("Submodel"));
553
554 if (Submodel.SubmodelElements == null)
555 return new Result(false, new NotFoundMessage(submodelElementId));
556
557 return Submodel.SubmodelElements.Update(submodelElementId, submodelElement);
558 }
559
560 public IResult DeleteSubmodelElement(string submodelElementId)
561 {
562 if (Submodel == null)
563 return new Result(false, new NotFoundMessage("Submodel"));
564
565 if (Submodel.SubmodelElements == null)
566 return new Result(false, new NotFoundMessage(submodelElementId));
567
568 return Submodel.SubmodelElements.Delete(submodelElementId);
569 }
Constantin Zieschefa612082020-04-03 09:54:56 +0200570
571
Constantin Ziesche857c7ab2020-02-25 11:24:51 +0100572 }
573}