Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Name change for edge module client method to send message(s) #3141

Merged
merged 5 commits into from
Mar 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions SDK v2 migration guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ Find a client you currently use below, read the table of API name changes and us
|:---|:---|:---|
| `DeviceClient` | `IotHubDeviceClient` | Specify the service it is a device client for. |
| `DeviceClient.Dispose()` | `IotHubDeviceClient.DisposeAsync()` | Ensures the client is closed before disposing. |
| `DeviceClient.SendEventAsync(...)` | `IotHubDeviceClient.SendTelemetryAsync(...)` | Even our public documentation calls this telemetry, so we renamed the method to describe this better.¹ |
| `DeviceClient.SendEventBatchAsync(...)` | `IotHubDeviceClient.SendTelemetryBatchAsync(...)` | This is now only supported over AMQP. Support over MQTT has been removed. Also, see¹. |
| `DeviceClient.SendEventAsync(...)` | `IotHubDeviceClient.SendTelemetryAsync(TelemetryMessage, ...)` | Even our public documentation calls this telemetry, so we renamed the method to describe this better.¹ |
| `DeviceClient.SendEventBatchAsync(...)` | `IotHubDeviceClient.SendTelemetryAsync(IEnumerable<TelemetryMessage>, ...)` | This is now only supported over AMQP. Support over MQTT has been removed. Also, see¹. |
| `DeviceClient.SetConnectionStatusChangesHandler(...)` | `IotHubDeviceClient.ConnectionStatusChangeCallback` | Local operation doesn't require being a method. |
| `DeviceClient.SetReceiveMessageHandlerAsync(...)` | `IotHubDeviceClient.SetIncomingMessageCallbackAsync(...)` | Disambiguate from telemetry messages. |
| `DeviceClient.GetTwinAsync(...)` | `IotHubDeviceClient.GetTwinPropertiesAsync(...)` | The device client doesn't get the full twin, just the properties so this helps avoid that confusion.² |
Expand All @@ -196,7 +196,10 @@ The device client and module client share a lot of API surface and underlying im

#### Notable breaking changes

N/A
| v1 API | Equivalent v2 API | Notes |
|:---|:---|:---|
| `ModuleClient.SendEventAsync(string outputName, ...)` | `IotHubModuleClient.SendMessageToRouteAsync(string outputName, ...)` | Change the name to be more descriptive about sending messages between Edge modules.¹ |
| `ModuleClient.SendEventBatchAsync(string outputName, ...)` | `IotHubModuleClient.SendMessagesToRouteAsync(string outputName, ...)` | See¹. |

#### Notable additions

Expand Down
2 changes: 1 addition & 1 deletion e2e/test/iothub/device/TelemetryE2eTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ public static async Task SendBatchMessagesAsync(IotHubDeviceClient deviceClient)
}

await deviceClient.OpenAsync().ConfigureAwait(false);
await deviceClient.SendTelemetryBatchAsync(messagesToBeSent.Keys.ToList()).ConfigureAwait(false);
await deviceClient.SendTelemetryAsync(messagesToBeSent.Keys.ToList()).ConfigureAwait(false);
}

private static async Task SendSingleMessageModuleAsync(IotHubModuleClient moduleClient)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public async Task RunSampleAsync()
// Now sending message to the module itself.
var message = new TelemetryMessage(Encoding.ASCII.GetBytes("Sample message"));
// Setting output name to '*' will send telemetry from all output channels of the module.
await _moduleClient.SendTelemetryAsync(OutputName, message, cts.Token);
await _moduleClient.SendMessageToRouteAsync(OutputName, message, cts.Token);
Console.WriteLine($"\n{DateTime.Now}> Sent telemetry message to the module.");

// Now continue to send messages to the module with every key press of 'M'.
Expand All @@ -59,7 +59,7 @@ public async Task RunSampleAsync()
{
message = new TelemetryMessage(Encoding.ASCII.GetBytes("Sample message"));
// Setting output name to '*' will send telemetry from all output channels of the module.
await _moduleClient.SendTelemetryAsync(OutputName, message, cts.Token);
await _moduleClient.SendMessageToRouteAsync(OutputName, message, cts.Token);
Console.WriteLine($"\n{DateTime.Now}> Sent telemetry message to the module.");
}
}
Expand Down
24 changes: 17 additions & 7 deletions iothub/device/src/IotHubBaseClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public async Task OpenAsync(CancellationToken cancellationToken = default)
/// Sends a telemetry message to IoT hub.
/// </summary>
/// <remarks>
/// The client instance must be opened already.
/// The client instance must be already open.
/// <para>
/// In case of a transient issue, retrying the operation should work. In case of a non-transient issue, inspect
/// the error details and take steps accordingly.
Expand Down Expand Up @@ -171,7 +171,7 @@ public async Task SendTelemetryAsync(TelemetryMessage message, CancellationToken
/// Sends a batch of telemetry message to IoT hub.
/// </summary>
/// <remarks>
/// The client instance must be opened already.
/// The client instance must be already open.
/// <para>
/// This operation is supported only over AMQP.
/// </para>
Expand All @@ -188,7 +188,7 @@ public async Task SendTelemetryAsync(TelemetryMessage message, CancellationToken
/// <exception cref="InvalidOperationException">Thrown if the client instance is not opened already.</exception>
/// <exception cref="InvalidOperationException">When this method is called when the client is configured to use MQTT.</exception>
/// <exception cref="OperationCanceledException">Thrown when the operation has been canceled.</exception>
public async Task SendTelemetryBatchAsync(IEnumerable<TelemetryMessage> messages, CancellationToken cancellationToken = default)
public async Task SendTelemetryAsync(IEnumerable<TelemetryMessage> messages, CancellationToken cancellationToken = default)
{
Argument.AssertNotNullOrEmpty(messages, nameof(messages));
cancellationToken.ThrowIfCancellationRequested();
Expand All @@ -210,13 +210,15 @@ public async Task SendTelemetryBatchAsync(IEnumerable<TelemetryMessage> messages

/// <summary>
/// Sets a callback for receiving a message from the device or module queue using a cancellation token.
/// This instance must be opened already.
/// </summary>
/// <remarks>
/// The client instance must already be open.
/// <para>
/// Calling this API more than once will result in the callback set last overwriting any previously set callback.
/// A method callback can be unset by setting <paramref name="messageCallback"/> to null.
/// This user-supplied callback is awaited by the SDK. All of requests will be processed as they arrive.
/// Exceptions thrown within the callback will be caught and logged by the SDK internally.
/// </para>
/// </remarks>
/// <param name="messageCallback">The callback to be invoked when a cloud-to-device message is received by the client.</param>
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
Expand Down Expand Up @@ -263,13 +265,15 @@ public async Task SetIncomingMessageCallbackAsync(

/// <summary>
/// Sets the callback for all direct method calls from the service.
/// This instance must be opened already.
/// </summary>
/// <remarks>
/// The client instance must already be open.
/// <para>
/// Calling this API more than once will result in the callback set last overwriting any previously set callback.
/// A method callback can be unset by setting <paramref name="directMethodCallback"/> to null.
/// This user-supplied callback is awaited by the SDK. All of requests will be processed as they arrive.
/// Exceptions thrown within the callback will be caught and logged by the SDK internally.
/// </para>
/// </remarks>
/// <param name="directMethodCallback">The callback to be invoked when any method is invoked by the cloud service.</param>
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
Expand Down Expand Up @@ -311,7 +315,7 @@ public async Task SetDirectMethodCallbackAsync(
/// Retrieve the twin properties for the current client.
/// </summary>
/// <remarks>
/// The client instance must be opened already.
/// The client instance must be already open.
/// <para>
/// This API gives you the client's view of the twin. For more information on twins in IoT hub, see <see href="https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins"/>.
/// </para>
Expand All @@ -330,6 +334,9 @@ public async Task<TwinProperties> GetTwinPropertiesAsync(CancellationToken cance
/// <summary>
/// Push reported property changes up to the service.
/// </summary>
/// <remarks>
/// The client instance must be already open.
/// </remarks>
/// <param name="reportedProperties">Reported properties to push</param>
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
/// <returns>The new version of the updated twin if the update was successful.</returns>
Expand All @@ -345,13 +352,16 @@ public async Task<long> UpdateReportedPropertiesAsync(ReportedProperties reporte

/// <summary>
/// Set a callback that will be called whenever the client receives a desired state update
/// from the service. The client instance must be opened already.
/// from the service.
/// </summary>
/// <remarks>
/// The client instance must be already open.
/// <para>
/// Calling this API more than once will result in the callback set last overwriting any previously set callback.
/// This user-supplied callback is "fire-and-forget" and the SDK doesn't wait on it. All of requests will be processed as they arrive.
/// The users are responsible to handle exceptions within their callback implementation.
/// A method callback can be unset by setting <paramref name="callback"/> to null.
/// </para>
/// <para>
/// This has the side-effect of subscribing to the PATCH topic on the service.
/// </para>
Expand Down
64 changes: 26 additions & 38 deletions iothub/device/src/IotHubModuleClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,100 +108,94 @@ public static async Task<IotHubModuleClient> CreateFromEnvironmentAsync(IotHubCl
}

/// <summary>
/// Sends an event to IoT hub. IotHubModuleClient instance must be opened already.
/// Sends a message to IoT hub.
/// </summary>
/// <remarks>
/// IotHubModuleClient instance must be already open.
/// <para>
/// For more information on IoT Edge module routing <see href="https://docs.microsoft.com/azure/iot-edge/module-composition?view=iotedge-2018-06#declare-routes"/>.
/// </para>
/// <para>
/// In case of a transient issue, retrying the operation should work. In case of a non-transient issue, inspect the error details and take steps accordingly.
/// Please note that the above list is not exhaustive.
/// </para>
/// </remarks>
/// <param name="outputName">The output target for sending the given message.</param>
/// <param name="outputName">The named module route for sending the given message.</param>
/// <param name="message">The message to send.</param>
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
/// <exception cref="ArgumentNullException">Thrown when a required parameter is null.</exception>
/// <exception cref="OperationCanceledException">Thrown when the operation has been canceled.</exception>
/// <exception cref="InvalidOperationException">Thrown if ModuleClient instance is not opened already.</exception>
/// <exception cref="IotHubClientException">Thrown if an error occurs when communicating with IoT hub service.</exception>
public async Task SendTelemetryAsync(string outputName, TelemetryMessage message, CancellationToken cancellationToken = default)
public async Task SendMessageToRouteAsync(string outputName, TelemetryMessage message, CancellationToken cancellationToken = default)
{
if (Logging.IsEnabled)
Logging.Enter(this, outputName, message, nameof(SendTelemetryAsync));
Logging.Enter(this, outputName, message, nameof(SendMessageToRouteAsync));

Argument.AssertNotNullOrWhiteSpace(outputName, nameof(outputName));
Argument.AssertNotNull(message, nameof(message));

ValidateModuleTransportHandler("SendTelemetryAsync for a named output");

cancellationToken.ThrowIfCancellationRequested();

try
{
message.SystemProperties.Add(MessageSystemPropertyNames.OutputName, outputName);

await InnerHandler.SendTelemetryAsync(message, cancellationToken).ConfigureAwait(false);
}
catch (SocketException socketException)
{
throw new IotHubClientException(socketException.Message, IotHubClientErrorCode.NetworkErrors, socketException);
}
catch (WebSocketException webSocketException)
{
throw new IotHubClientException(webSocketException.Message, IotHubClientErrorCode.NetworkErrors, webSocketException);
message.OutputName = outputName;
await base.SendTelemetryAsync(message, cancellationToken).ConfigureAwait(false);
}
finally
{
if (Logging.IsEnabled)
Logging.Exit(this, outputName, message, nameof(SendTelemetryAsync));
Logging.Exit(this, outputName, message, nameof(SendMessageToRouteAsync));
}
}

/// <summary>
/// Sends a batch of events to IoT hub. Use AMQP or HTTPs for a true batch operation. MQTT will just send the messages one after the other.
/// IotHubModuleClient instance must be opened already.
/// </summary>
/// <remarks>
/// IotHubModuleClient instance must be already open.
/// <para>
/// For more information on IoT Edge module routing <see href="https://docs.microsoft.com/azure/iot-edge/module-composition?view=iotedge-2018-06#declare-routes"/>.
/// </para>
/// </remarks>
/// <param name="outputName">The output target for sending the given message.</param>
/// <param name="outputName">The named module route for sending the given message.</param>
/// <param name="messages">A list of one or more messages to send.</param>
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
/// <returns>The task containing the event</returns>
/// <returns>The task containing the event.</returns>
/// <exception cref="InvalidOperationException">Thrown if IotHubModuleClient instance is not opened already.</exception>
/// <exception cref="OperationCanceledException">Thrown when the operation has been canceled.</exception>
public async Task SendTelemetryBatchAsync(string outputName, IEnumerable<TelemetryMessage> messages, CancellationToken cancellationToken = default)
public async Task SendMessagesToRouteAsync(string outputName, IEnumerable<TelemetryMessage> messages, CancellationToken cancellationToken = default)
drwill-ms marked this conversation as resolved.
Show resolved Hide resolved
{
if (Logging.IsEnabled)
Logging.Enter(this, outputName, messages, nameof(SendTelemetryBatchAsync));
Logging.Enter(this, outputName, messages, nameof(SendMessagesToRouteAsync));

Argument.AssertNotNullOrWhiteSpace(outputName, nameof(outputName));

var messagesList = messages?.ToList();
Argument.AssertNotNullOrEmpty(messagesList, nameof(messages));

ValidateModuleTransportHandler("SendTelemetryBatchAsync for a named output");

try
{
messagesList.ForEach(m => m.SystemProperties.Add(MessageSystemPropertyNames.OutputName, outputName));
messagesList.ForEach(m => m.OutputName = outputName);

await InnerHandler.SendTelemetryBatchAsync(messagesList, cancellationToken).ConfigureAwait(false);
await base.SendTelemetryAsync(messagesList, cancellationToken).ConfigureAwait(false);
}
finally
{
if (Logging.IsEnabled)
Logging.Exit(this, outputName, messages, nameof(SendTelemetryBatchAsync));
Logging.Exit(this, outputName, messages, nameof(SendMessagesToRouteAsync));
}
}

/// <summary>
/// Interactively invokes a method from an edge module to an edge device.
/// Both the edge module and the edge device need to be connected to the same edge hub.
/// IotHubModuleClient instance must be opened already.
/// </summary>
/// <remarks>
/// IotHubModuleClient instance must be already open.
/// <para>
/// This API call is relevant only for IoT Edge modules.
/// </para>
/// </remarks>
/// <param name="deviceId">The unique identifier of the edge device to invoke the method on.</param>
/// <param name="methodRequest">The details of the method to invoke.</param>
Expand All @@ -218,10 +212,12 @@ public Task<DirectMethodResponse> InvokeMethodAsync(string deviceId, DirectMetho
/// <summary>
/// Interactively invokes a method from an edge module to a different edge module.
/// Both of the edge modules need to be connected to the same edge hub.
/// IotHubModuleClient instance must be opened already.
/// </summary>
/// <remarks>
/// IotHubModuleClient instance must be already open.
/// <para>
/// This API call is relevant only for IoT Edge modules.
/// </para>
/// </remarks>
/// <param name="deviceId">The unique identifier of the device.</param>
/// <param name="moduleId">The unique identifier of the edge module to invoke the method on.</param>
Expand All @@ -248,14 +244,6 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
}

private void ValidateModuleTransportHandler(string apiName)
{
if (IotHubConnectionCredentials.ModuleId.IsNullOrWhiteSpace())
{
throw new InvalidOperationException($"{apiName} is available for Modules only.");
}
}

private async Task<DirectMethodResponse> InvokeMethodAsync(Uri uri, DirectMethodRequest methodRequest, CancellationToken cancellationToken = default)
{
HttpClientHandler httpClientHandler = null;
Expand Down
2 changes: 0 additions & 2 deletions iothub/device/src/Messaging/MessageSystemPropertyNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ internal static class MessageSystemPropertyNames

internal const string UserId = "user-id";

internal const string Operation = "iothub-operation";

internal const string OutputName = "iothub-outputname";

internal const string InputName = "iothub-inputname";
Expand Down
12 changes: 12 additions & 0 deletions iothub/device/src/Messaging/TelemetryMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,18 @@ public string InputName
protected internal set => SystemProperties[MessageSystemPropertyNames.InputName] = value;
}

/// <summary>
/// Specifies the output name on which the message will be sent, if applicable.
/// </summary>
/// <remarks>
/// Used for message routes with IoT Edge.
/// </remarks>
public string OutputName
{
get => GetSystemProperty<string>(MessageSystemPropertyNames.OutputName);
set => SystemProperties[MessageSystemPropertyNames.OutputName] = value;
}

/// <summary>
/// True if the message is set as a security message
/// </summary>
Expand Down
Loading