Skip to content

Commit

Permalink
Name change for edge module client method to send message(s) (#3141)
Browse files Browse the repository at this point in the history
* Name change for edge module client method to send message(s)

* Add InputName to TelemetryMessage

* More docs and code tweaks

* public setter

* Feedback from Tim
  • Loading branch information
David R. Williamson authored Mar 3, 2023
1 parent 4a77f6d commit 317b9a3
Show file tree
Hide file tree
Showing 12 changed files with 118 additions and 120 deletions.
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)
{
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

0 comments on commit 317b9a3

Please sign in to comment.