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

Add stop all robots button #1012

Closed
wants to merge 11 commits into from
31 changes: 15 additions & 16 deletions backend/api.test/EndpointTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -558,27 +558,26 @@ public async Task SafePositionTest()
var area = await areaResponse.Content.ReadFromJsonAsync<Area>(_serializerOptions);
Assert.True(area != null);

// Arrange - Get a Robot
string url = "/robots";
var robotResponse = await _client.GetAsync(url);
Assert.True(robotResponse.IsSuccessStatusCode);
var robots = await robotResponse.Content.ReadFromJsonAsync<List<Robot>>(_serializerOptions);
Assert.True(robots != null);
var robot = robots[0];
string robotId = robot.Id;

// Act
string goToSafePositionUrl = $"/robots/{robotId}/{testInstallation}/{testArea}/go-to-safe-position";
string goToSafePositionUrl = $"/emergency-action/{testInstallation}/abort-current-missions-and-send-all-robots-to-safe-zone";
var missionResponse = await _client.PostAsync(goToSafePositionUrl, null);

// Assert
Assert.True(missionResponse.IsSuccessStatusCode);
var missionRun = await missionResponse.Content.ReadFromJsonAsync<MissionRun>(_serializerOptions);
Assert.True(missionRun != null);
Assert.True(
JsonSerializer.Serialize(missionRun.Tasks[0].RobotPose.Position) ==
JsonSerializer.Serialize(testPosition)
);

// The code below does not work since the request above runs in another thread which may not complete before the code below
/*
string filterQuery = $"?Statuses=Ongoing&Statuses=Pending&PageSize=100&OrderBy=DesiredStartTime";
var currentMissionResponse = await _client.GetAsync("/missions/runs" + filterQuery);
Assert.True(currentMissionResponse.IsSuccessStatusCode);

var missionRuns = await currentMissionResponse.Content.ReadFromJsonAsync<List<MissionRun>>(_serializerOptions);
Assert.True(missionRuns != null);
Assert.NotEmpty(missionRuns);
var newMission = missionRuns.Find(m => m.Tasks != null && JsonSerializer.Serialize(m.Tasks[0].RobotPose.Position) ==
JsonSerializer.Serialize(testPosition));
Assert.NotNull(newMission);
*/
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comments

}

[Fact]
Expand Down
17 changes: 17 additions & 0 deletions backend/api.test/EventHandlers/TestMissionEventHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,20 @@ public class TestMissionEventHandler : IDisposable
private readonly RobotControllerMock _robotControllerMock;
private readonly IRobotModelService _robotModelService;
private readonly IRobotService _robotService;
private readonly IMissionScheduling _missionSchedulingService;
private readonly IIsarService _isarServiceMock;
private readonly IInstallationService _installationService;
private readonly IPlantService _plantService;
private readonly IDeckService _deckService;
private readonly IAreaService _areaService;

public TestMissionEventHandler(DatabaseFixture fixture)
{
var missionEventHandlerLogger = new Mock<ILogger<MissionEventHandler>>().Object;
var mqttServiceLogger = new Mock<ILogger<MqttService>>().Object;
var mqttEventHandlerLogger = new Mock<ILogger<MqttEventHandler>>().Object;
var missionLogger = new Mock<ILogger<MissionRunService>>().Object;
var missionSchedulingLogger = new Mock<ILogger<MissionScheduling>>().Object;

var configuration = WebApplication.CreateBuilder().Configuration;

Expand All @@ -64,6 +71,12 @@ public TestMissionEventHandler(DatabaseFixture fixture)
_robotService = new RobotService(_context);
_robotModelService = new RobotModelService(_context);
_robotControllerMock = new RobotControllerMock();
_isarServiceMock = new MockIsarService();
_installationService = new InstallationService(_context);
_plantService = new PlantService(_context, _installationService);
_deckService = new DeckService(_context, _installationService, _plantService);
_areaService = new AreaService(_context, _installationService, _plantService, _deckService);
_missionSchedulingService = new MissionScheduling(missionSchedulingLogger, _missionRunService, _isarServiceMock, _robotService, _robotControllerMock.Mock.Object, _areaService);

var mockServiceProvider = new Mock<IServiceProvider>();

Expand All @@ -74,6 +87,9 @@ public TestMissionEventHandler(DatabaseFixture fixture)
mockServiceProvider
.Setup(p => p.GetService(typeof(IRobotService)))
.Returns(_robotService);
mockServiceProvider
.Setup(p => p.GetService(typeof(IMissionScheduling)))
.Returns(_missionSchedulingService);
mockServiceProvider
.Setup(p => p.GetService(typeof(RobotController)))
.Returns(_robotControllerMock.Mock.Object);
Expand Down Expand Up @@ -118,6 +134,7 @@ public TestMissionEventHandler(DatabaseFixture fixture)
{
Name = "testMission",
MissionId = Guid.NewGuid().ToString(),
MissionRunPriority = MissionRunPriority.Normal,
Status = MissionStatus.Pending,
DesiredStartTime = DateTimeOffset.Now,
Area = NewArea,
Expand Down
5 changes: 1 addition & 4 deletions backend/api.test/Mocks/RobotControllerMock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ internal class RobotControllerMock
public Mock<IMissionRunService> MissionServiceMock;
public Mock<RobotController> Mock;
public Mock<IAreaService> AreaServiceMock;
public Mock<IEchoService> EchoServiceMock;

public RobotControllerMock()
{
Expand All @@ -22,7 +21,6 @@ public RobotControllerMock()
RobotServiceMock = new Mock<IRobotService>();
RobotModelServiceMock = new Mock<IRobotModelService>();
AreaServiceMock = new Mock<IAreaService>();
EchoServiceMock = new Mock<IEchoService>();

var mockLoggerController = new Mock<ILogger<RobotController>>();

Expand All @@ -32,8 +30,7 @@ public RobotControllerMock()
IsarServiceMock.Object,
MissionServiceMock.Object,
RobotModelServiceMock.Object,
AreaServiceMock.Object,
EchoServiceMock.Object
AreaServiceMock.Object
)
{
CallBase = true
Expand Down
178 changes: 178 additions & 0 deletions backend/api/Controllers/EmergencyActionController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
using System.Globalization;
using Api.Controllers.Models;
using Api.Database.Models;
using Api.Services;
using Api.Services.Events;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Api.Controllers
{
[ApiController]
[Route("emergency-action")]
public class EmergencyActionController : ControllerBase
{
private readonly IAreaService _areaService;
private readonly IEmergencyActionService _emergencyActionService;
private readonly ILogger<EmergencyActionController> _logger;
private readonly IRobotService _robotService;

public EmergencyActionController(ILogger<EmergencyActionController> logger, IRobotService robotService, IAreaService areaService, IEmergencyActionService emergencyActionService)
{
_logger = logger;
_robotService = robotService;
_areaService = areaService;
_emergencyActionService = emergencyActionService;
}

/// <summary>
/// This endpoint will abort the current running mission run and attempt to return the robot to a safe position in the
/// area. The mission run queue for the robot will be frozen and no further missions will run until the emergency
/// action has been reversed.
/// </summary>
/// <remarks>
/// <para> The endpoint fires an event which is then processed to stop the robot and schedule the next mission </para>
/// </remarks>
[HttpPost]
[Route("{robotId}/{installationCode}/{areaName}/abort-current-mission-and-go-to-safe-zone")]
[Authorize(Roles = Role.User)]
[ProducesResponseType(typeof(MissionRun), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult> AbortCurrentMissionAndGoToSafeZone(
[FromRoute] string robotId,
[FromRoute] string installationCode,
[FromRoute] string areaName)
{
var robot = await _robotService.ReadById(robotId);
if (robot == null)
{
_logger.LogWarning("Could not find robot with id {Id}", robotId);
return NotFound("Robot not found");
}

var area = await _areaService.ReadByInstallationAndName(installationCode, areaName);
if (area == null)
{
_logger.LogError("Could not find area {AreaName} for installation code {InstallationCode}", areaName, installationCode);
return NotFound("Area not found");
}

_emergencyActionService.TriggerEmergencyButtonPressedForRobot(new EmergencyButtonPressedForRobotEventArgs(robot.Id, area.Id));

return Ok("Request to abort current mission and move robot back to safe position received");
}

/// <summary>
/// This endpoint will abort the current running mission run and attempt to return the robot to a safe position in the
/// area. The mission run queue for the robot will be frozen and no further missions will run until the emergency
/// action has been reversed.
/// </summary>
/// <remarks>
/// <para> The endpoint fires an event which is then processed to stop the robot and schedule the next mission </para>
/// </remarks>
[HttpPost]
[Route("{installationCode}/abort-current-missions-and-send-all-robots-to-safe-zone")]
[Authorize(Roles = Role.User)]
[ProducesResponseType(typeof(MissionRun), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public ActionResult AbortCurrentMissionAndSendAllRobotsToSafeZone(
[FromRoute] string installationCode)
{

var robots = _robotService.ReadAll().Result.ToList().FindAll(a =>
a.CurrentInstallation.ToLower(CultureInfo.CurrentCulture).Equals(installationCode.ToLower(CultureInfo.CurrentCulture), StringComparison.Ordinal) &&
a.CurrentArea != null);

foreach (var robot in robots)
{
_emergencyActionService.TriggerEmergencyButtonPressedForRobot(new EmergencyButtonPressedForRobotEventArgs(robot.Id, robot.CurrentArea!.Id));

}

return Ok("Request to abort current mission and move all robots back to safe position received");
}

/// <summary>
/// This query will clear the emergency state that is introduced by aborting the current mission and returning to a
/// safe zone. Clearing the emergency state means that mission runs that may be in the robots queue will start."
/// </summary>
[HttpPost]
[Route("{robotId}/{installationCode}/{areaName}/clear-robot-emergency-state")]
[Authorize(Roles = Role.User)]
[ProducesResponseType(typeof(MissionRun), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult> ClearEmergencyState(
[FromRoute] string robotId,
[FromRoute] string installationCode,
[FromRoute] string areaName)
{
var robot = await _robotService.ReadById(robotId);
if (robot == null)
{
_logger.LogWarning("Could not find robot with id {Id}", robotId);
return NotFound("Robot not found");
}

var area = await _areaService.ReadByInstallationAndName(installationCode, areaName);
if (area == null)
{
_logger.LogError("Could not find area {AreaName} for installation code {InstallationCode}", areaName, installationCode);
return NotFound("Area not found");
}

_emergencyActionService.TriggerEmergencyButtonDepressedForRobot(new EmergencyButtonPressedForRobotEventArgs(robot.Id, area.Id));

return Ok("Request to clear emergency state for robot was received");
}

/// <summary>
/// This query will clear the emergency state that is introduced by aborting the current mission and returning to a
/// safe zone. Clearing the emergency state means that mission runs that may be in the robots queue will start."
/// </summary>
[HttpPost]
[Route("{robotId}/clear-emergency-state")]
[Authorize(Roles = Role.User)]
[ProducesResponseType(typeof(MissionRun), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult> ClearInstallationEmergencyState(
[FromRoute] string robotId)
{
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change this to clear emergency state for all robots

var robot = await _robotService.ReadById(robotId);

if (robot == null)
{
_logger.LogWarning("Could not find robot with id {Id}", robotId);
return NotFound("Robot not found");
}
if (robot.CurrentInstallation == null)
{
_logger.LogWarning("Could not find installation for robot with id {Id}", robotId);
return NotFound("Installation not found");
}
if (robot.CurrentArea == null)
{
_logger.LogWarning("Could not find area for robot with id {Id}", robotId);
return NotFound("Area not found");
}

_emergencyActionService.TriggerEmergencyButtonDepressedForRobot(new EmergencyButtonPressedForRobotEventArgs(robot.Id, robot.CurrentArea!.Id));

return Ok("Request to clear emergency state for robot was received");
}
}
}
7 changes: 5 additions & 2 deletions backend/api/Controllers/MissionController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ [FromBody] ScheduleMissionQuery scheduledMissionQuery
Name = missionDefinition.Name,
Robot = robot,
MissionId = missionDefinition.Id,
MissionRunPriority = MissionRunPriority.Normal,
Status = MissionStatus.Pending,
DesiredStartTime = scheduledMissionQuery.DesiredStartTime,
Tasks = missionTasks,
Expand Down Expand Up @@ -438,6 +439,7 @@ [FromBody] ScheduledMissionQuery scheduledMissionQuery
Name = echoMission.Name,
Robot = robot,
MissionId = scheduledMissionDefinition.Id,
MissionRunPriority = MissionRunPriority.Normal,
Status = MissionStatus.Pending,
DesiredStartTime = scheduledMissionQuery.DesiredStartTime,
Tasks = missionTasks,
Expand All @@ -455,7 +457,7 @@ [FromBody] ScheduledMissionQuery scheduledMissionQuery

if (existingMissionDefinition == null)
{
await _missionDefinitionService.Create(scheduledMissionDefinition);
var newMissionDefinition = await _missionDefinitionService.Create(scheduledMissionDefinition);
}

var newMissionRun = await _missionRunService.Create(missionRun);
Expand Down Expand Up @@ -549,6 +551,7 @@ [FromBody] CustomMissionQuery customMissionQuery
Name = customMissionQuery.Name,
Description = customMissionQuery.Description,
MissionId = customMissionDefinition.Id,
MissionRunPriority = MissionRunPriority.Normal,
Comment = customMissionQuery.Comment,
Robot = robot,
Status = MissionStatus.Pending,
Expand All @@ -568,7 +571,7 @@ [FromBody] CustomMissionQuery customMissionQuery

if (existingMissionDefinition == null)
{
await _missionDefinitionService.Create(customMissionDefinition);
var newMissionDefinition = await _missionDefinitionService.Create(customMissionDefinition);
}

var newMissionRun = await _missionRunService.Create(scheduledMission);
Expand Down
Loading