From 9c3730c7bcbd26b924d56956d0162358ed2f4fe9 Mon Sep 17 00:00:00 2001 From: andchiind Date: Fri, 22 Nov 2024 09:59:16 +0100 Subject: [PATCH] Fetch media config from ISAR with API call --- backend/api.test/Mocks/IsarServiceMock.cs | 12 ++++ .../api/Controllers/MediaStreamController.cs | 51 +++++++++++++++ backend/api/EventHandlers/MqttEventHandler.cs | 22 ------- .../api/MQTT/MessageModels/IsarMediaConfig.cs | 25 -------- backend/api/MQTT/MqttService.cs | 5 -- backend/api/MQTT/MqttTopics.cs | 3 - backend/api/Services/IsarService.cs | 62 +++++++++++++++++++ .../api/Services/Models/IsarMediaConfig.cs | 18 ++++++ backend/api/Services/Models/MediaConfig.cs | 10 +-- 9 files changed, 144 insertions(+), 64 deletions(-) create mode 100644 backend/api/Controllers/MediaStreamController.cs delete mode 100644 backend/api/MQTT/MessageModels/IsarMediaConfig.cs create mode 100644 backend/api/Services/Models/IsarMediaConfig.cs diff --git a/backend/api.test/Mocks/IsarServiceMock.cs b/backend/api.test/Mocks/IsarServiceMock.cs index 476769f03..9bd3d797a 100644 --- a/backend/api.test/Mocks/IsarServiceMock.cs +++ b/backend/api.test/Mocks/IsarServiceMock.cs @@ -51,5 +51,17 @@ public async Task StartMoveArm(Robot robot, string position) ); return isarServiceMissionResponse; } + + public async Task GetMediaStreamConfig(Robot robot) + { + await Task.Run(() => Thread.Sleep(1)); + return new MediaConfig + { + Url = "mockURL", + Token = "mockToken", + RobotId = robot.Id, + MediaConnectionType = MediaConnectionType.LiveKit + }; + } } } diff --git a/backend/api/Controllers/MediaStreamController.cs b/backend/api/Controllers/MediaStreamController.cs new file mode 100644 index 000000000..963d00d23 --- /dev/null +++ b/backend/api/Controllers/MediaStreamController.cs @@ -0,0 +1,51 @@ +using Api.Controllers.Models; +using Api.Services; +using Api.Services.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers +{ + [ApiController] + [Route("media-stream")] + public class MediaStreamController( + ILogger logger, + IIsarService isarService, + IRobotService robotService + ) : ControllerBase + { + /// + /// Request the config for a new media stream connection from ISAR + /// + /// + /// This query gets a new media stream connection config from ISAR + /// + [HttpGet] + [Authorize(Roles = Role.Any)] + [Route("{id}")] + [ProducesResponseType(typeof(MediaConfig), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> GetMediaStreamConfig([FromRoute] string id) + { + try + { + var robot = await robotService.ReadById(id); + if (robot == null) + { + return NotFound($"Could not find robot with ID {id}"); + } + + var config = await isarService.GetMediaStreamConfig(robot); + return Ok(config); + } + catch (Exception e) + { + logger.LogError(e, "Error during GET of media stream config from ISAR"); + throw; + } + } + } +} diff --git a/backend/api/EventHandlers/MqttEventHandler.cs b/backend/api/EventHandlers/MqttEventHandler.cs index f668b27f2..51b2ba12c 100644 --- a/backend/api/EventHandlers/MqttEventHandler.cs +++ b/backend/api/EventHandlers/MqttEventHandler.cs @@ -59,7 +59,6 @@ public override void Subscribe() MqttService.MqttIsarPressureReceived += OnIsarPressureUpdate; MqttService.MqttIsarPoseReceived += OnIsarPoseUpdate; MqttService.MqttIsarCloudHealthReceived += OnIsarCloudHealthUpdate; - MqttService.MqttIsarMediaConfigReceived += OnIsarMediaConfigUpdate; } public override void Unsubscribe() @@ -72,7 +71,6 @@ public override void Unsubscribe() MqttService.MqttIsarPressureReceived -= OnIsarPressureUpdate; MqttService.MqttIsarPoseReceived -= OnIsarPoseUpdate; MqttService.MqttIsarCloudHealthReceived -= OnIsarCloudHealthUpdate; - MqttService.MqttIsarMediaConfigReceived -= OnIsarMediaConfigUpdate; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await stoppingToken; } @@ -538,25 +536,5 @@ private async void OnIsarCloudHealthUpdate(object? sender, MqttReceivedArgs mqtt TeamsMessageService.TriggerTeamsMessageReceived(new TeamsMessageEventArgs(message)); } - - private async void OnIsarMediaConfigUpdate(object? sender, MqttReceivedArgs mqttArgs) - { - var isarTelemetyUpdate = (IsarMediaConfigMessage)mqttArgs.Message; - - var robot = await RobotService.ReadByIsarId(isarTelemetyUpdate.IsarId); - if (robot == null) - { - _logger.LogInformation("Received message from unknown ISAR instance {Id} with robot name {Name}", isarTelemetyUpdate.IsarId, isarTelemetyUpdate.RobotName); - return; - } - await SignalRService.SendMessageAsync("Media stream config received", robot.CurrentInstallation, - new MediaConfig - { - Url = isarTelemetyUpdate.Url, - Token = isarTelemetyUpdate.Token, - RobotId = robot.Id, - MediaConnectionType = isarTelemetyUpdate.MediaConnectionType - }); - } } } diff --git a/backend/api/MQTT/MessageModels/IsarMediaConfig.cs b/backend/api/MQTT/MessageModels/IsarMediaConfig.cs deleted file mode 100644 index 214a4420a..000000000 --- a/backend/api/MQTT/MessageModels/IsarMediaConfig.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Text.Json.Serialization; -using Api.Services.Models; - -namespace Api.Mqtt.MessageModels -{ -#nullable disable - public class IsarMediaConfigMessage : MqttMessage - { - [JsonPropertyName("robot_name")] - public string RobotName { get; set; } - - [JsonPropertyName("isar_id")] - public string IsarId { get; set; } - - [JsonPropertyName("url")] - public string Url { get; set; } - - [JsonPropertyName("token")] - public string Token { get; set; } - - [JsonPropertyName("mediaConnectionType")] - public MediaConnectionType MediaConnectionType { get; set; } - - } -} diff --git a/backend/api/MQTT/MqttService.cs b/backend/api/MQTT/MqttService.cs index 0a5743318..480430f7e 100644 --- a/backend/api/MQTT/MqttService.cs +++ b/backend/api/MQTT/MqttService.cs @@ -91,7 +91,6 @@ public MqttService(ILogger logger, IConfiguration config) public static event EventHandler? MqttIsarPressureReceived; public static event EventHandler? MqttIsarPoseReceived; public static event EventHandler? MqttIsarCloudHealthReceived; - public static event EventHandler? MqttIsarMediaConfigReceived; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { @@ -153,9 +152,6 @@ private Task OnMessageReceived(MqttApplicationMessageReceivedEventArgs messageRe case Type type when type == typeof(IsarCloudHealthMessage): OnIsarTopicReceived(content); break; - case Type type when type == typeof(IsarMediaConfigMessage): - OnIsarTopicReceived(content); - break; default: _logger.LogWarning( "No callback defined for MQTT message type '{type}'", @@ -305,7 +301,6 @@ private void OnIsarTopicReceived(string content) where T : MqttMessage _ when type == typeof(IsarPressureMessage) => MqttIsarPressureReceived, _ when type == typeof(IsarPoseMessage) => MqttIsarPoseReceived, _ when type == typeof(IsarCloudHealthMessage) => MqttIsarCloudHealthReceived, - _ when type == typeof(IsarMediaConfigMessage) => MqttIsarMediaConfigReceived, _ => throw new NotImplementedException( $"No event defined for message type '{typeof(T).Name}'" diff --git a/backend/api/MQTT/MqttTopics.cs b/backend/api/MQTT/MqttTopics.cs index 9a499cc26..3d6fcbf42 100644 --- a/backend/api/MQTT/MqttTopics.cs +++ b/backend/api/MQTT/MqttTopics.cs @@ -42,9 +42,6 @@ public static class MqttTopics }, { "isar/+/cloud_health", typeof(IsarCloudHealthMessage) - }, - { - "isar/+/media_config", typeof(IsarMediaConfigMessage) } }; diff --git a/backend/api/Services/IsarService.cs b/backend/api/Services/IsarService.cs index 076be91fb..e883917a5 100644 --- a/backend/api/Services/IsarService.cs +++ b/backend/api/Services/IsarService.cs @@ -17,6 +17,8 @@ public interface IIsarService public Task ResumeMission(Robot robot); public Task StartMoveArm(Robot robot, string armPosition); + + public Task GetMediaStreamConfig(Robot robot); } public class IsarService(IDownstreamApi isarApi, ILogger logger) : IIsarService @@ -275,5 +277,65 @@ private static (string, int) GetErrorDescriptionFoFailedIsarRequest(HttpResponse return (description, (int)statusCode); } + + public async Task GetMediaStreamConfig(Robot robot) + { + string mediaStreamPath = $"/media/media-stream-config"; + var response = await CallApi( + HttpMethod.Get, + robot.IsarUri, + mediaStreamPath + ); + + if (!response.IsSuccessStatusCode) + { + (string message, _) = GetErrorDescriptionFoFailedIsarRequest(response); + string errorResponse = await response.Content.ReadAsStringAsync(); + logger.LogError("{Message}: {ErrorResponse}", message, errorResponse); + throw new ConfigException(message); + } + if (response.Content is null) + { + string errorMessage = "Could not read content from new robot media stream config"; + logger.LogError("{ErrorMessage}", errorMessage); + throw new ConfigException(errorMessage); + } + + IsarMediaConfigMessage? isarMediaConfigResponse; + try + { + isarMediaConfigResponse = await response.Content.ReadFromJsonAsync(); + } + catch (JsonException) + { + string errorMessage = $"Could not parse content from new robot media stream config. {await response.Content.ReadAsStringAsync()}"; + logger.LogError("{ErrorMessage}", errorMessage); + throw new ConfigException(errorMessage); + } + + if (isarMediaConfigResponse == null) + { + string errorMessage = $"Parsing of robot media stream config resulted in empty config. {await response.Content.ReadAsStringAsync()}"; + logger.LogError("{ErrorMessage}", errorMessage); + throw new ConfigException(errorMessage); + } + + bool parseSuccess = Enum.TryParse(isarMediaConfigResponse.MediaConnectionType, out MediaConnectionType connectionType); + + if (!parseSuccess) + { + string errorMessage = $"Could not parse connection type from new robot media stream config. {isarMediaConfigResponse.MediaConnectionType}"; + logger.LogError("{ErrorMessage}", errorMessage); + throw new ConfigException(errorMessage); + } + + return new MediaConfig + { + Url = isarMediaConfigResponse.Url, + Token = isarMediaConfigResponse.Token, + RobotId = robot.Id, + MediaConnectionType = connectionType + }; + } } } diff --git a/backend/api/Services/Models/IsarMediaConfig.cs b/backend/api/Services/Models/IsarMediaConfig.cs new file mode 100644 index 000000000..07f3d3a38 --- /dev/null +++ b/backend/api/Services/Models/IsarMediaConfig.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; + +namespace Api.Services.Models +{ +#nullable disable + public class IsarMediaConfigMessage + { + [JsonPropertyName("url")] + public string Url { get; set; } + + [JsonPropertyName("token")] + public string Token { get; set; } + + [JsonPropertyName("media_connection_type")] + public string MediaConnectionType { get; set; } + + } +} diff --git a/backend/api/Services/Models/MediaConfig.cs b/backend/api/Services/Models/MediaConfig.cs index 8d5a077f2..08804897b 100644 --- a/backend/api/Services/Models/MediaConfig.cs +++ b/backend/api/Services/Models/MediaConfig.cs @@ -1,18 +1,10 @@ -using System.Text.Json.Serialization; -namespace Api.Services.Models +namespace Api.Services.Models { public struct MediaConfig { - [JsonPropertyName("url")] public string? Url { get; set; } - - [JsonPropertyName("token")] public string? Token { get; set; } - - [JsonPropertyName("robotId")] public string? RobotId { get; set; } - - [JsonPropertyName("mediaConnectionType")] public MediaConnectionType MediaConnectionType { get; set; } }