问题
I'm using the Azure IoT Edge runtime running Linux containers on a Windows host OS. I have two modules, ModuleA and ModuleB. ModuleA have a registered direct method called "MethodA" and ModuleB have a registered direct method called "MethodB".
When I invoke MethodA I want the method to invoke MethodB located in another module (but running in the same IoT Edge runtime).
I'm using the Azure IoT SDK for c# and in the Init() function of ModuleA I have:
await ioTHubModuleClient.SetMethodHandlerAsync("MethodA", MethodA, ioTHubModuleClient);
And in the Init() function of ModuleB I have:
await ioTHubModuleClient.SetMethodHandlerAsync("MethodB", MethodB, null);
The MethodA (that acts like a proxy method):
static async Task<MethodResponse> MethodA(MethodRequest methodRequest, object moduleClient)
{
try
{
ModuleClient ioTHubModuleClient = (ModuleClient)moduleClient;
// Get deviced id of this device, exposed as a system variable by the iot edge runtime
var deviceId = System.Environment.GetEnvironmentVariable("IOTEDGE_DEVICEID");
// Create the request
MethodRequest request = new MethodRequest("MethodB", Encoding.UTF8.GetBytes("{ \"Message\": \"Hello\" }"));
// Execute request
var resp = await ioTHubModuleClient.InvokeMethodAsync(deviceId, "ModuleB", request);
return resp;
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
return await Task.FromResult(new MethodResponse(500));
}
}
The MethodB that I try to invoke from MethodA:
private static Task<MethodResponse> MethodB(MethodRequest methodRequest, object userContext)
{
Console.WriteLine("MethodB has been called");
// Get data but we do not do anything with it
var data = Encoding.UTF8.GetString(methodRequest.Data);
Console.WriteLine("Received data: " + data.ToString());
var methodResponse = new MethodResponse(Encoding.UTF8.GetBytes("{\"status\": \"ok\"}"), 200);
return Task.FromResult(methodResponse);
}
Note that I can invoke these two methods separately just fine from the azure portal or by using the Azure SDK for C# -> ServiceClient class.
My program crashes on the line
var resp = await ioTHubModuleClient.InvokeMethodAsync(deviceId, "ModuleB", request);
in MethodA and in the VS Code debugger I get the exception
Exception thrown: 'Microsoft.Azure.Devices.Client.Exceptions.IotHubCommunicationException' in System.Private.CoreLib.dll
And when I check the logs for my Message/StackTrace-print I get:
An error occurred while sending the request. at Microsoft.Azure.Devices.Client.Transport.HttpClientHelper.d__21.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.Azure.Devices.Client.Transport.HttpClientHelper.d__172.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.Azure.Devices.Client.ModuleClient.d__57.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter1.GetResult() at OrderGateway.Program.d__3.MoveNext() in /app/Program.cs:line 92
I've tried to initialize the ModuleClient using both Amqp with TCP/WebSocket_Only as well as Mqtt with TCP/WebSocket_Only. I've also tried to run the IoT Edge runtime with these modules on a Ubuntu 18.04 VM without any success.
Even when I try to invoke a method located in the same module with the ioTHubModuleClient.InvokeMethodAsync() I get the same exception / stacktrace...
I also tried to invoke the MethodB from the Init() function of ModuleA to try and not invoke a direct method in the context of a callback but I do get the same exception.
There is an open issue on github that you can find here: https://github.com/Azure/iotedge/issues/204
But it feels that it have kind of stalled and I don't get any help.
From my understanding this should be possible but I don't know what I'm missing?
回答1:
I just made this scenario to work, but It's a little bit different than your description.
So, here is what I did: 1-Created 2 Modules, ModuleA and ModuleB; 2-Added your MethodA on ModuleA and your MethodB in Module B; 3-On Module B (Not ModuleA), I added this code:
while(true)
{
try
{
Console.WriteLine($"Invoking method On moduleA");
var deviceId = System.Environment.GetEnvironmentVariable("IOTEDGE_DEVICEID");
MethodRequest request = new MethodRequest("MethodA", Encoding.UTF8.GetBytes("{ \"Message\": \"Hello\" }"));
var response = await ioTHubModuleClient.InvokeMethodAsync(deviceId, "ModuleA", request).ConfigureAwait(false);
Console.WriteLine($"Received response with status {response.Status}");
}
catch (Exception ex)
{
Console.WriteLine($"Error invoking method {ex}");
}
await Task.Delay(TimeSpan.FromSeconds(20)).ConfigureAwait(false);
}
That is it. At the begin you will get an exception like this one:
Error invoking method Microsoft.Azure.Devices.Client.Exceptions.DeviceNotFoundException: Device {"message":"Client angelodTransparentGateway/ModuleA not found","exceptionMessage":"","status":0,"payload":null} not registered
at Microsoft.Azure.Devices.Client.Transport.HttpClientHelper.ExecuteAsync(HttpMethod httpMethod, Uri requestUri, Func3 modifyRequestMessageAsync, Func
2 isSuccessful, Func3 processResponseMessageAsync, IDictionary
2 errorMappingOverrides, CancellationToken cancellationToken)
at Microsoft.Azure.Devices.Client.Transport.HttpClientHelper.PostAsync[T1,T2](Uri requestUri, T1 entity, IDictionary2 errorMappingOverrides, IDictionary
2 customHeaders, CancellationToken cancellationToken)
at Microsoft.Azure.Devices.Client.ModuleClient.InvokeMethodAsync(Uri uri, MethodRequest methodRequest, CancellationToken cancellationToken)
at ModuleB.Program.Init() in /app/Program.cs:line 74
But this is by design, and it's just because it takes time for modules to be created after a deployment, but in less than 1 minute I was able to get the succesfull result:
MethodA has been called Received data: {"Message":"Hello"}
If you call MethodB from ModuleA the MethodA won't get called. But just to make sure it worked I also tested module A invoking Module B. Here is the code:
while(true)
{
try
{
Console.WriteLine($"Invoking method On moduleB");
var deviceId = System.Environment.GetEnvironmentVariable("IOTEDGE_DEVICEID");
MethodRequest request = new MethodRequest("MethodB", Encoding.UTF8.GetBytes("{ \"Message\": \"Hello\" }"));
var response = await ioTHubModuleClient.InvokeMethodAsync(deviceId, "ModuleB", request).ConfigureAwait(false);
Console.WriteLine($"Received response with status {response.Status}");
}
catch (Exception ex)
{
Console.WriteLine($"Error invoking method {ex}");
}
await Task.Delay(TimeSpan.FromSeconds(20)).ConfigureAwait(false);
}
Both Worked. Don't forget to use .NET version 2.1 (Update that on your dockerfile).
来源:https://stackoverflow.com/questions/52073548/azure-iot-edge-moduleclient-invoke-direct-method-in-another-module