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 check for min pressure in robot when starting missions #1280

Merged
merged 10 commits into from
Jan 30, 2024
5 changes: 4 additions & 1 deletion backend/api.test/Mocks/RobotControllerMock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal class RobotControllerMock
public readonly Mock<RobotController> Mock;
public readonly Mock<IRobotModelService> RobotModelServiceMock;
public readonly Mock<IRobotService> RobotServiceMock;
public readonly Mock<IInstallationService> InstallationServiceMock;

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

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

Expand All @@ -29,7 +31,8 @@ public RobotControllerMock()
IsarServiceMock.Object,
MissionServiceMock.Object,
RobotModelServiceMock.Object,
AreaServiceMock.Object
AreaServiceMock.Object,
InstallationServiceMock.Object
)
{
CallBase = true
Expand Down
41 changes: 41 additions & 0 deletions backend/api/Controllers/MissionSchedulingController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ [FromBody] ScheduleMissionQuery scheduledMissionQuery
var robot = await robotService.ReadById(scheduledMissionQuery.RobotId);
if (robot is null) return NotFound($"Could not find robot with id {scheduledMissionQuery.RobotId}");

if (!robot.IsRobotPressureHighEnoughToStartMission())
{
return BadRequest($"The robot pressure on {robot.Name} is too low to start a mission");
}

if (!robot.IsRobotBatteryLevelHighEnoughToStartMissions())
{
return BadRequest($"The robot battery level on {robot.Name} is too low to start a mission");
}

var missionRun = await missionRunService.ReadById(missionRunId);
if (missionRun == null) return NotFound("Mission run not found");

Expand Down Expand Up @@ -117,6 +127,16 @@ [FromBody] ScheduleMissionQuery scheduledMissionQuery
return NotFound($"Could not find robot with id {scheduledMissionQuery.RobotId}");
}

if (!robot.IsRobotPressureHighEnoughToStartMission())
{
return BadRequest($"The robot pressure on {robot.Name} is too low to start a mission");
}

if (!robot.IsRobotBatteryLevelHighEnoughToStartMissions())
{
return BadRequest($"The robot battery level on {robot.Name} is too low to start a mission");
}

var missionDefinition = await missionDefinitionService.ReadById(missionDefinitionId);
if (missionDefinition == null)
{
Expand Down Expand Up @@ -183,6 +203,16 @@ [FromBody] ScheduledMissionQuery scheduledMissionQuery
return NotFound($"Could not find robot with id {scheduledMissionQuery.RobotId}");
}

if (!robot.IsRobotPressureHighEnoughToStartMission())
{
return BadRequest($"The robot pressure on {robot.Name} is too low to start a mission");
}

if (!robot.IsRobotBatteryLevelHighEnoughToStartMissions())
{
return BadRequest($"The robot battery level on {robot.Name} is too low to start a mission");
}

EchoMission? echoMission;
try
{
Expand Down Expand Up @@ -335,6 +365,16 @@ [FromBody] CustomMissionQuery customMissionQuery
var robot = await robotService.ReadById(customMissionQuery.RobotId);
if (robot is null) { return NotFound($"Could not find robot with id {customMissionQuery.RobotId}"); }

if (!robot.IsRobotPressureHighEnoughToStartMission())
{
return BadRequest($"The robot pressure on {robot.Name} is too low to start a mission");
}

if (!robot.IsRobotBatteryLevelHighEnoughToStartMissions())
{
return BadRequest($"The robot battery level on {robot.Name} is too low to start a mission");
}

var installation = await installationService.ReadByName(customMissionQuery.InstallationCode);
if (installation == null) { return NotFound($"Could not find installation with name {customMissionQuery.InstallationCode}"); }

Expand All @@ -351,6 +391,7 @@ [FromBody] CustomMissionQuery customMissionQuery

MissionRun? newMissionRun;
try { newMissionRun = await customMissionSchedulingService.QueueCustomMissionRun(customMissionQuery, customMissionDefinition.Id, robot.Id, missionTasks); }
catch (Exception e) when (e is RobotPressureTooLowException or RobotBatteryLevelTooLowException) { return BadRequest(e.Message); }
catch (Exception e) when (e is RobotNotFoundException or MissionNotFoundException) { return NotFound(e.Message); }

return CreatedAtAction(nameof(Create), new
Expand Down
13 changes: 13 additions & 0 deletions backend/api/Controllers/Models/UpdateRobotQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Api.Database.Models
{
public class UpdateRobotQuery
{
public string? InstallationId { get; set; }

public string? AreaId { get; set; }

public Pose? Pose { get; set; }

public string? MissionId { get; set; }
}
}
99 changes: 93 additions & 6 deletions backend/api/Controllers/RobotController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ namespace Api.Controllers
[ApiController]
[Route("robots")]
public class RobotController(
ILogger<RobotController> logger,
IRobotService robotService,
IIsarService isarService,
IMissionSchedulingService missionSchedulingService,
IRobotModelService robotModelService
) : ControllerBase
ILogger<RobotController> logger,
IRobotService robotService,
IIsarService isarService,
IMissionSchedulingService missionSchedulingService,
IRobotModelService robotModelService,
IAreaService areaService,
IInstallationService installationService
) : ControllerBase
{
/// <summary>
/// List all robots on the installation.
Expand Down Expand Up @@ -173,6 +175,91 @@ [FromBody] Robot robot
}
}

/// <summary>
/// Updates a specific field of a robot in the database
/// </summary>
/// <remarks>
/// </remarks>
/// <response code="200"> The robot was successfully updated </response>
/// <response code="400"> The robot data is invalid </response>
/// <response code="404"> There was no robot with the given ID in the database </response>
/// /// <response code="404"> The given field name is not valid </response>
[HttpPut]
[Authorize(Roles = Role.Admin)]
[Route("{id}/{fieldName}")]
[ProducesResponseType(typeof(RobotResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<RobotResponse>> UpdateRobotField(
[FromRoute] string id,
[FromRoute] string fieldName,
[FromBody] UpdateRobotQuery query
)
{
logger.LogInformation("Updating robot with id={Id}", id);

if (!ModelState.IsValid)
return BadRequest("Invalid data");

try
{
var robot = await robotService.ReadById(id);
if (robot == null)
{
string errorMessage = $"No robot with id: {id} could be found";
logger.LogError("{Message}", errorMessage);
return NotFound(errorMessage);
}

Robot updatedRobot;
switch (fieldName)
{
case "installationId":
if (query.InstallationId == null)
updatedRobot = await robotService.UpdateCurrentInstallation(id, null);
else
{
var installation = await installationService.ReadById(query.InstallationId);
if (installation == null) return NotFound($"No installation with ID {query.InstallationId} was found");
updatedRobot = await robotService.UpdateCurrentInstallation(id, installation);
}
break;
case "areaId":
if (query.AreaId == null)
updatedRobot = await robotService.UpdateCurrentArea(id, null);
else
{
var area = await areaService.ReadById(query.AreaId);
if (area == null) return NotFound($"No area with ID {query.AreaId} was found");
updatedRobot = await robotService.UpdateCurrentArea(id, area);
}
break;
case "pose":
if (query.Pose == null) return BadRequest("Cannot set robot pose to null");
updatedRobot = await robotService.UpdateRobotPose(id, query.Pose);
break;
case "missionId":
updatedRobot = await robotService.UpdateCurrentMissionId(id, query.MissionId);
break;
default:
return NotFound($"Could not find any field with name {fieldName}");
}

var robotResponse = new RobotResponse(updatedRobot);
logger.LogInformation("Successful PUT of robot to database");

return Ok(robotResponse);
}
catch (Exception e)
{
logger.LogError(e, "Error while updating robot with id={Id}", id);
throw;
}
}

/// <summary>
/// Deletes the robot with the specified id from the database
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions backend/api/Database/Context/InitDb.cs
Original file line number Diff line number Diff line change
Expand Up @@ -693,8 +693,8 @@ public static void AddRobotModelsToDatabase(FlotillaDbContext context)
new()
{
Type = type,
BatteryWarningThreshold = 20f,
LowerPressureWarningThreshold = 40f,
BatteryWarningThreshold = 0f,
LowerPressureWarningThreshold = 0f,
UpperPressureWarningThreshold = 80f
};
context.Add(model);
Expand Down
10 changes: 10 additions & 0 deletions backend/api/Database/Models/Robot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ public Robot(CreateRobotQuery createQuery, Installation installation, Area? area

public float? PressureLevel { get; set; }

public bool IsRobotPressureHighEnoughToStartMission()
{
return Model.LowerPressureWarningThreshold == null || PressureLevel == null || Model.LowerPressureWarningThreshold <= PressureLevel;
}

public bool IsRobotBatteryLevelHighEnoughToStartMissions()
{
return Model.BatteryWarningThreshold == null || Model.BatteryWarningThreshold <= BatteryLevel;
}

public IList<VideoStream> VideoStreams { get; set; }

[Required]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ public async Task<MissionRun> QueueCustomMissionRun(CustomMissionQuery customMis
throw new RobotNotFoundException(errorMessage);
}

if (!robot.IsRobotPressureHighEnoughToStartMission())
{
throw new RobotPressureTooLowException($"The robot pressure on {robot.Name} is too low to start a mission");
}

if (!robot.IsRobotBatteryLevelHighEnoughToStartMissions())
{
throw new RobotBatteryLevelTooLowException($"The robot battery level on {robot.Name} is too low to start a mission");
}

var scheduledMission = new MissionRun
{
Name = customMissionQuery.Name,
Expand Down
65 changes: 42 additions & 23 deletions backend/api/Services/RobotService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public interface IRobotService
public Task<Robot> UpdateCurrentMissionId(string robotId, string? missionId);
public Task<Robot> UpdateCurrentArea(string robotId, Area? area);
public Task<Robot> UpdateMissionQueueFrozen(string robotId, bool missionQueueFrozen);
public Task<Robot> UpdateCurrentInstallation(string robotId, Installation? installation);
public Task<Robot?> Delete(string id);
public Task SetRobotOffline(string robotId);
}
Expand Down Expand Up @@ -222,6 +223,24 @@ public async Task<Robot> UpdateCurrentMissionId(string robotId, string? currentM

public async Task<Robot> UpdateCurrentArea(string robotId, Area? area) { return await UpdateRobotProperty(robotId, "CurrentArea", area); }

public async Task<Robot> UpdateCurrentInstallation(string robotId, Installation? installation)
{
var robotQuery = context.Robots.Where(robot => robot.Id == robotId).Include(robot => robot.CurrentInstallation);
var robot = await robotQuery.FirstOrDefaultAsync();
ThrowIfRobotIsNull(robot, robotId);

await VerifyThatUserIsAuthorizedToUpdateDataForInstallation(robot!.CurrentInstallation);
await VerifyThatUserIsAuthorizedToUpdateDataForInstallation(installation);

await robotQuery.ExecuteUpdateAsync(robots => robots.SetProperty(r => r.CurrentInstallation, installation));
andchiind marked this conversation as resolved.
Show resolved Hide resolved

robot = await robotQuery.FirstOrDefaultAsync();
ThrowIfRobotIsNull(robot, robotId);
NotifySignalROfUpdatedRobot(robot!, robot!.CurrentInstallation!);

return robot;
}

public async Task<Robot> UpdateMissionQueueFrozen(string robotId, bool missionQueueFrozen)
{
var robotQuery = context.Robots.Where(robot => robot.Id == robotId).Include(robot => robot.CurrentInstallation);
Expand Down Expand Up @@ -320,29 +339,6 @@ public async Task SetRobotOffline(string robotId)
catch (RobotNotFoundException) { }
}

private async Task<Robot> UpdateRobotProperty(string robotId, string propertyName, object? value)
{
var robot = await ReadById(robotId);
if (robot is null)
{
string errorMessage = $"Robot with ID {robotId} was not found in the database";
logger.LogError("{Message}", errorMessage);
throw new RobotNotFoundException(errorMessage);
}

foreach (var property in typeof(Robot).GetProperties())
{
if (property.Name == propertyName)
{
logger.LogInformation("Setting {robotName} field {propertyName} from {oldValue} to {NewValue}", robot.Name, propertyName, property.GetValue(robot), value);
property.SetValue(robot, value);
}
}

robot = await Update(robot);
return robot;
}

private IQueryable<Robot> GetRobotsWithSubModels()
{
var accessibleInstallationCodes = accessRoleService.GetAllowedInstallationCodes();
Expand All @@ -367,6 +363,29 @@ private IQueryable<Robot> GetRobotsWithSubModels()
#pragma warning restore CA1304
}

private async Task<Robot> UpdateRobotProperty(string robotId, string propertyName, object? value)
{
var robot = await ReadById(robotId);
if (robot is null)
{
string errorMessage = $"Robot with ID {robotId} was not found in the database";
logger.LogError("{Message}", errorMessage);
throw new RobotNotFoundException(errorMessage);
}

foreach (var property in typeof(Robot).GetProperties())
{
if (property.Name == propertyName)
{
logger.LogInformation("Setting {robotName} field {propertyName} from {oldValue} to {NewValue}", robot.Name, propertyName, property.GetValue(robot), value);
property.SetValue(robot, value);
}
}

robot = await Update(robot);
return robot;
}

private async Task ApplyDatabaseUpdate(Installation? installation)
{
var accessibleInstallationCodes = await accessRoleService.GetAllowedInstallationCodes();
Expand Down
8 changes: 8 additions & 0 deletions backend/api/Utilities/Exceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ public class RobotInformationNotAvailableException(string message) : Exception(m
{
}

public class RobotPressureTooLowException(string message) : Exception(message)
{
}

public class RobotBatteryLevelTooLowException(string message) : Exception(message)
{
}

public class TagPositionNotFoundException(string message) : Exception(message)
{
}
Expand Down
Loading
Loading