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

Fetch media config from ISAR with API call #1805

Merged
merged 2 commits into from
Dec 3, 2024
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
12 changes: 12 additions & 0 deletions backend/api.test/Mocks/IsarServiceMock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,17 @@ public async Task<IsarMission> StartMoveArm(Robot robot, string position)
);
return isarServiceMissionResponse;
}

public async Task<MediaConfig> GetMediaStreamConfig(Robot robot)
{
await Task.Run(() => Thread.Sleep(1));
return new MediaConfig
{
Url = "mockURL",
Token = "mockToken",
RobotId = robot.Id,
MediaConnectionType = MediaConnectionType.LiveKit
};
}
}
}
51 changes: 51 additions & 0 deletions backend/api/Controllers/MediaStreamController.cs
Original file line number Diff line number Diff line change
@@ -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<MediaStreamController> logger,
IIsarService isarService,
IRobotService robotService
) : ControllerBase
{
/// <summary>
/// Request the config for a new media stream connection from ISAR
/// </summary>
/// <remarks>
/// <para> This query gets a new media stream connection config from ISAR </para>
/// </remarks>
[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<ActionResult<MediaConfig>> 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;
}
}
}
}
22 changes: 0 additions & 22 deletions backend/api/EventHandlers/MqttEventHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ public override void Subscribe()
MqttService.MqttIsarPressureReceived += OnIsarPressureUpdate;
MqttService.MqttIsarPoseReceived += OnIsarPoseUpdate;
MqttService.MqttIsarCloudHealthReceived += OnIsarCloudHealthUpdate;
MqttService.MqttIsarMediaConfigReceived += OnIsarMediaConfigUpdate;
}

public override void Unsubscribe()
Expand All @@ -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; }
Expand Down Expand Up @@ -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
});
}
}
}
25 changes: 0 additions & 25 deletions backend/api/MQTT/MessageModels/IsarMediaConfig.cs

This file was deleted.

5 changes: 0 additions & 5 deletions backend/api/MQTT/MqttService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ public MqttService(ILogger<MqttService> logger, IConfiguration config)
public static event EventHandler<MqttReceivedArgs>? MqttIsarPressureReceived;
public static event EventHandler<MqttReceivedArgs>? MqttIsarPoseReceived;
public static event EventHandler<MqttReceivedArgs>? MqttIsarCloudHealthReceived;
public static event EventHandler<MqttReceivedArgs>? MqttIsarMediaConfigReceived;

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
Expand Down Expand Up @@ -153,9 +152,6 @@ private Task OnMessageReceived(MqttApplicationMessageReceivedEventArgs messageRe
case Type type when type == typeof(IsarCloudHealthMessage):
OnIsarTopicReceived<IsarCloudHealthMessage>(content);
break;
case Type type when type == typeof(IsarMediaConfigMessage):
OnIsarTopicReceived<IsarMediaConfigMessage>(content);
break;
default:
_logger.LogWarning(
"No callback defined for MQTT message type '{type}'",
Expand Down Expand Up @@ -305,7 +301,6 @@ private void OnIsarTopicReceived<T>(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}'"
Expand Down
3 changes: 0 additions & 3 deletions backend/api/MQTT/MqttTopics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ public static class MqttTopics
},
{
"isar/+/cloud_health", typeof(IsarCloudHealthMessage)
},
{
"isar/+/media_config", typeof(IsarMediaConfigMessage)
}
};

Expand Down
62 changes: 62 additions & 0 deletions backend/api/Services/IsarService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public interface IIsarService
public Task<IsarControlMissionResponse> ResumeMission(Robot robot);

public Task<IsarMission> StartMoveArm(Robot robot, string armPosition);

public Task<MediaConfig> GetMediaStreamConfig(Robot robot);
}

public class IsarService(IDownstreamApi isarApi, ILogger<IsarService> logger) : IIsarService
Expand Down Expand Up @@ -275,5 +277,65 @@ private static (string, int) GetErrorDescriptionFoFailedIsarRequest(HttpResponse

return (description, (int)statusCode);
}

public async Task<MediaConfig> 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<IsarMediaConfigMessage>();
}
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
};
}
}
}
18 changes: 18 additions & 0 deletions backend/api/Services/Models/IsarMediaConfig.cs
Original file line number Diff line number Diff line change
@@ -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; }

}
}
10 changes: 1 addition & 9 deletions backend/api/Services/Models/MediaConfig.cs
Original file line number Diff line number Diff line change
@@ -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; }
}

Expand Down
7 changes: 7 additions & 0 deletions frontend/src/api/ApiCaller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { MissionDefinition, PlantInfo } from 'models/MissionDefinition'
import { MissionDefinitionUpdateForm } from 'models/MissionDefinitionUpdateForm'
import { Deck } from 'models/Deck'
import { ApiError, isApiError } from './ApiError'
import { MediaStreamConfig } from 'models/VideoStream'

/** Implements the request sent to the backend api. */
export class BackendAPICaller {
Expand Down Expand Up @@ -140,6 +141,12 @@ export class BackendAPICaller {
return result.content
}

static async getRobotMediaConfig(robotId: string): Promise<MediaStreamConfig> {
const path: string = 'media-stream/' + robotId
const result = await this.GET<MediaStreamConfig>(path).catch(BackendAPICaller.handleError('GET', path))
return result.content
}

static async getMissionRuns(parameters: MissionRunQueryParameters): Promise<PaginatedResponse<Mission>> {
let path: string = 'missions/runs?'

Expand Down
Loading
Loading