< Summary

Information
Class: LGDXRobotCloud.API.Services.Navigation.OnlineRobotsService
Assembly: LGDXRobotCloud.API
File(s): /builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/Services/Navigation/OnlineRobotsService.cs
Line coverage
92%
Covered lines: 179
Uncovered lines: 15
Coverable lines: 194
Total lines: 285
Line coverage: 92.2%
Branch coverage
63%
Covered branches: 35
Total branches: 55
Branch coverage: 63.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/Services/Navigation/OnlineRobotsService.cs

#LineLine coverage
 1using LGDXRobotCloud.API.Services.Common;
 2using LGDXRobotCloud.Data.Contracts;
 3using LGDXRobotCloud.Data.Entities;
 4using LGDXRobotCloud.Protos;
 5using LGDXRobotCloud.Utilities.Enums;
 6using MassTransit;
 7using Microsoft.Extensions.Caching.Distributed;
 8using Microsoft.Extensions.Caching.Memory;
 9
 10namespace LGDXRobotCloud.API.Services.Navigation;
 11
 12public class RobotCommandsEventArgs : EventArgs
 13{
 14  public Guid RobotId { get; set; }
 15  public required RobotClientsRobotCommands Commands { get; set; }
 16}
 17
 18public interface IOnlineRobotsService
 19{
 20  Task AddRobotAsync(Guid robotId);
 21  Task RemoveRobotAsync(Guid robotId);
 22  Task UpdateRobotDataAsync(Guid robotId, RobotClientsExchange data, bool realtime = false);
 23  RobotClientsRobotCommands? GetRobotCommands(Guid robotId);
 24
 25  Task<bool> IsRobotOnlineAsync(Guid robotId);
 26  Task<bool> SetAbortTaskAsync(Guid robotId, bool enable);
 27  Task<bool> SetSoftwareEmergencyStopAsync(Guid robotId, bool enable);
 28  Task<bool> SetPauseTaskAssigementAsync(Guid robotId, bool enable);
 29  bool GetPauseAutoTaskAssignment(Guid robotId);
 30
 31  void SetAutoTaskNextApi(Guid robotId, AutoTask task);
 32  AutoTask? GetAutoTaskNextApi(Guid robotId);
 33}
 34
 2135public class OnlineRobotsService(
 2136    IBus bus,
 2137    IEmailService emailService,
 2138    IEventService eventService,
 2139    IMemoryCache memoryCache,
 2140    IRobotService robotService
 2141  ) : IOnlineRobotsService
 42{
 2143  private readonly IBus _bus = bus ?? throw new ArgumentNullException(nameof(bus));
 2144  private readonly IEmailService _emailService = emailService ?? throw new ArgumentNullException(nameof(emailService));
 2145  private readonly IEventService _eventService = eventService ?? throw new ArgumentNullException(nameof(eventService));
 2146  private readonly IMemoryCache _memoryCache = memoryCache ?? throw new ArgumentNullException(nameof(memoryCache));
 2147  private readonly IRobotService _robotService = robotService ?? throw new ArgumentNullException(nameof(robotService));
 648  private static string GetOnlineRobotsKey(int realmId) => $"OnlineRobotsService_OnlineRobots_{realmId}";
 1849  private static string GetRobotCommandsKey(Guid robotId) => $"OnlineRobotsService_RobotCommands_{robotId}";
 50  private const int robotAliveMinutes = 5;
 51
 52  private static RobotStatus ConvertRobotStatus(RobotClientsRobotStatus robotStatus)
 353  {
 354    return robotStatus switch
 355    {
 056      RobotClientsRobotStatus.Idle => RobotStatus.Idle,
 157      RobotClientsRobotStatus.Running => RobotStatus.Running,
 258      RobotClientsRobotStatus.Stuck => RobotStatus.Stuck,
 059      RobotClientsRobotStatus.Aborting => RobotStatus.Aborting,
 060      RobotClientsRobotStatus.Paused => RobotStatus.Paused,
 061      RobotClientsRobotStatus.Critical => RobotStatus.Critical,
 062      RobotClientsRobotStatus.Charging => RobotStatus.Charging,
 063      RobotClientsRobotStatus.Offline => RobotStatus.Offline,
 064      _ => RobotStatus.Offline,
 365    };
 366  }
 67
 68  static private bool GenerateUnresolvableCriticalStatus(RobotClientsRobotCriticalStatus criticalStatus)
 069  {
 070    if (criticalStatus.HardwareEmergencyStop ||
 071        criticalStatus.BatteryLow.Count > 0 ||
 072        criticalStatus.MotorDamaged.Count > 0)
 073    {
 074      return true;
 75    }
 076    return false;
 077  }
 78
 79  public async Task AddRobotAsync(Guid robotId)
 180  {
 181    var realmId = await _robotService.GetRobotRealmIdAsync(robotId) ?? 0;
 182    var OnlineRobotsIds = _memoryCache.Get<HashSet<Guid>>(GetOnlineRobotsKey(realmId)) ?? [];
 183    OnlineRobotsIds.Add(robotId);
 84    // Register the robot
 185    _memoryCache.Set(GetOnlineRobotsKey(realmId), OnlineRobotsIds);
 186    _memoryCache.Set(GetRobotCommandsKey(robotId), new RobotClientsRobotCommands());
 187    _memoryCache.Set($"OnlineRobotsService_RobotData_ConnectionAlive_{robotId}", true, TimeSpan.FromMinutes(robotAliveMi
 188  }
 89
 90  public async Task RemoveRobotAsync(Guid robotId)
 191  {
 192    var realmId = await _robotService.GetRobotRealmIdAsync(robotId) ?? 0;
 93    // Unregister the robot
 194    _memoryCache.TryGetValue(GetOnlineRobotsKey(realmId), out HashSet<Guid>? OnlineRobotsIds);
 195    if (OnlineRobotsIds != null && OnlineRobotsIds.Contains(robotId))
 196    {
 197      OnlineRobotsIds.Remove(robotId);
 198      _memoryCache.Set(GetOnlineRobotsKey(realmId), OnlineRobotsIds);
 199    }
 1100    _memoryCache.Remove(GetRobotCommandsKey(robotId));
 101
 102    // Publish the robot is offline
 1103    await _bus.Publish(new RobotDataContract
 1104    {
 1105      RobotId = robotId,
 1106      RealmId = realmId,
 1107      RobotStatus = RobotStatus.Offline,
 1108      CurrentTime = DateTime.MinValue
 1109    });
 1110  }
 111
 112  public async Task UpdateRobotDataAsync(Guid robotId, RobotClientsExchange data, bool realtime = false)
 4113  {
 4114    if (_memoryCache.TryGetValue<bool>($"OnlineRobotsService_RobotData_Pause_{robotId}", out var _))
 1115    {
 116      // Blocking too much data to rabbitmq
 1117      return;
 118    }
 3119    _memoryCache.Set($"OnlineRobotsService_RobotData_Pause_{robotId}", true, TimeSpan.FromSeconds(1));
 3120    _memoryCache.Set($"OnlineRobotsService_RobotData_ConnectionAlive_{robotId}", true, TimeSpan.FromMinutes(robotAliveMi
 121
 3122    var realmId = await _robotService.GetRobotRealmIdAsync(robotId) ?? 0;
 3123    var robotStatus = ConvertRobotStatus(data.RobotStatus);
 3124    await _bus.Publish(new RobotDataContract {
 3125      RobotId = robotId,
 3126      RealmId = realmId,
 3127      RobotStatus = robotStatus,
 3128      CriticalStatus = new RobotCriticalStatus {
 3129        HardwareEmergencyStop = data.CriticalStatus.HardwareEmergencyStop,
 3130        SoftwareEmergencyStop = data.CriticalStatus.SoftwareEmergencyStop,
 3131        BatteryLow = [.. data.CriticalStatus.BatteryLow],
 3132        MotorDamaged = [.. data.CriticalStatus.MotorDamaged]
 3133      },
 3134      Batteries = [.. data.Batteries],
 3135      Position = new RobotDof {
 3136        X = data.Position.X,
 3137        Y = data.Position.Y,
 3138        Rotation = data.Position.Rotation
 3139      },
 3140      NavProgress = new AutoTaskNavProgress {
 3141        Eta = data.NavProgress.Eta,
 3142        Recoveries = data.NavProgress.Recoveries,
 3143        DistanceRemaining = data.NavProgress.DistanceRemaining,
 3144        WaypointsRemaining = data.NavProgress.WaypointsRemaining
 3145      },
 3146      CurrentTime = realtime ? DateTime.MaxValue : DateTime.UtcNow
 3147    });
 148
 3149    if(_memoryCache.TryGetValue<RobotClientsRobotCommands>(GetRobotCommandsKey(robotId), out var robotCommands))
 1150    {
 1151      if (robotCommands != null)
 1152      {
 1153        await _bus.Publish(new RobotCommandsContract {
 1154          RobotId = robotId,
 1155          RealmId = realmId,
 1156          Commands = new RobotCommands {
 1157            AbortTask = robotCommands.AbortTask,
 1158            PauseTaskAssigement = robotCommands.PauseTaskAssigement,
 1159            SoftwareEmergencyStop = robotCommands.SoftwareEmergencyStop,
 1160            RenewCertificate = robotCommands.RenewCertificate
 1161          }
 1162        });
 1163      }
 1164    }
 165
 3166    if (robotStatus == RobotStatus.Stuck)
 2167    {
 2168      if (!_memoryCache.TryGetValue<bool>($"OnlineRobotsService_RobotStuck_{robotId}", out var _))
 1169      {
 170        // First stuck in 5 minutes, sending email
 1171        await _emailService.SendRobotStuckEmailAsync(robotId, data.Position.X, data.Position.Y);
 1172      }
 2173      _memoryCache.Set($"OnlineRobotsService_RobotStuck_{robotId}", true, TimeSpan.FromMinutes(5));
 2174    }
 4175  }
 176
 177  private async Task SetRobotCommandsAsync(Guid robotId, RobotClientsRobotCommands commands)
 3178  {
 3179    _memoryCache.Set(GetRobotCommandsKey(robotId), commands);
 3180    var realmId = await _robotService.GetRobotRealmIdAsync(robotId) ?? 0;
 3181    await _bus.Publish(new RobotCommandsContract {
 3182      RobotId = robotId,
 3183      RealmId = realmId,
 3184      Commands = new RobotCommands {
 3185        AbortTask = commands.AbortTask,
 3186        PauseTaskAssigement = commands.PauseTaskAssigement,
 3187        SoftwareEmergencyStop = commands.SoftwareEmergencyStop,
 3188        RenewCertificate = commands.RenewCertificate
 3189      }
 3190    });
 3191  }
 192
 193  public RobotClientsRobotCommands? GetRobotCommands(Guid robotId)
 10194  {
 10195    _memoryCache.TryGetValue(GetRobotCommandsKey(robotId), out RobotClientsRobotCommands? robotCommands);
 10196    return robotCommands;
 10197  }
 198
 199  public async Task<bool> IsRobotOnlineAsync(Guid robotId)
 2200  {
 2201    var realmId = await _robotService.GetRobotRealmIdAsync(robotId) ?? 0;
 2202    var onlineRobotsIds = _memoryCache.Get<HashSet<Guid>>(GetOnlineRobotsKey((int)realmId));
 2203    return onlineRobotsIds != null && onlineRobotsIds.Contains(robotId);
 2204  }
 205
 206  public async Task<bool> SetAbortTaskAsync(Guid robotId, bool enable)
 2207  {
 2208    var robotCommands = GetRobotCommands(robotId);
 2209    if (robotCommands != null)
 1210    {
 1211      robotCommands.AbortTask = enable;
 1212      await SetRobotCommandsAsync(robotId, robotCommands);
 1213      _eventService.RobotCommandsHasUpdated(robotId);
 1214      return true;
 215    }
 216    else
 1217    {
 1218      return false;
 219    }
 2220  }
 221
 222  public async Task<bool> SetSoftwareEmergencyStopAsync(Guid robotId, bool enable)
 2223  {
 2224    var robotCommands = GetRobotCommands(robotId);
 2225    if (robotCommands != null)
 1226    {
 1227      robotCommands.SoftwareEmergencyStop = enable;
 1228      await SetRobotCommandsAsync(robotId, robotCommands);
 1229      _eventService.RobotCommandsHasUpdated(robotId);
 1230      return true;
 231    }
 232    else
 1233    {
 1234      return false;
 235    }
 2236  }
 237
 238  public async Task<bool> SetPauseTaskAssigementAsync(Guid robotId, bool enable)
 2239  {
 2240    var robotCommands = GetRobotCommands(robotId);
 2241    if (robotCommands != null)
 1242    {
 1243      robotCommands.PauseTaskAssigement = enable;
 1244      await SetRobotCommandsAsync(robotId, robotCommands);
 1245      _eventService.RobotCommandsHasUpdated(robotId);
 1246      return true;
 247    }
 248    else
 1249    {
 1250      return false;
 251    }
 2252  }
 253
 254  public bool GetPauseAutoTaskAssignment(Guid robotId)
 2255  {
 2256    var robotCommands = GetRobotCommands(robotId);
 2257    if (robotCommands != null)
 1258    {
 1259      return robotCommands.PauseTaskAssigement;
 260    }
 261    else
 1262    {
 1263      return false;
 264    }
 2265  }
 266
 267  public void SetAutoTaskNextApi(Guid robotId, AutoTask autoTask)
 1268  {
 1269    _memoryCache.Set($"OnlineRobotsService_RobotHasNextTask_{robotId}", autoTask);
 1270    _eventService.RobotHasNextTaskTriggered(robotId);
 1271  }
 272
 273  public AutoTask? GetAutoTaskNextApi(Guid robotId)
 2274  {
 2275    if (_memoryCache.TryGetValue($"OnlineRobotsService_RobotHasNextTask_{robotId}", out AutoTask? autoTask))
 1276    {
 1277      _memoryCache.Remove($"OnlineRobotsService_RobotHasNextTask_{robotId}");
 1278      return autoTask;
 279    }
 280    else
 1281    {
 1282      return null;
 283    }
 2284  }
 285}