< Summary

Information
Class: LGDXRobotCloud.API.Services.RobotClientsService
Assembly: LGDXRobotCloud.API
File(s): /builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/Services/RobotClientsService.cs
Line coverage
61%
Covered lines: 125
Uncovered lines: 78
Coverable lines: 203
Total lines: 285
Line coverage: 61.5%
Branch coverage
50%
Covered branches: 22
Total branches: 44
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetRobotId(...)100%11100%
Greet()87.5%1616100%
Exchange()100%44100%
ExchangeStream()100%210%
ExchangeStreamClientToServerAsync()0%7280%
ExchangeStreamServerToClientAsync()0%620%
OnRobotCommandsUpdated()0%620%
OnAutoTaskCreated()0%2040%
OnRobotHasNextTask()0%2040%
AutoTaskNext()100%22100%
AutoTaskAbort()100%22100%

File(s)

/builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/Services/RobotClientsService.cs

#LineLine coverage
 1using Grpc.Core;
 2using LGDXRobotCloud.API.Configurations;
 3using LGDXRobotCloud.API.Services.Navigation;
 4using LGDXRobotCloud.Protos;
 5using LGDXRobotCloud.Utilities.Constants;
 6using LGDXRobotCloud.Utilities.Enums;
 7using Microsoft.AspNetCore.Authorization;
 8using Microsoft.Extensions.Options;
 9using Microsoft.IdentityModel.Tokens;
 10using static LGDXRobotCloud.Protos.RobotClientsService;
 11using System.IdentityModel.Tokens.Jwt;
 12using System.Security.Claims;
 13using System.Text;
 14using LGDXRobotCloud.Data.Models.Business.Navigation;
 15using LGDXRobotCloud.API.Services.Automation;
 16using System.Threading.Channels;
 17using LGDXRobotCloud.API.Services.Common;
 18using LGDXRobotCloud.API.Authorisation;
 19
 20namespace LGDXRobotCloud.API.Services;
 21
 22[Authorize(AuthenticationSchemes = LgdxRobotCloudAuthenticationSchemes.RobotClientsJwtScheme)]
 1423public class RobotClientsService(
 1424    IAutoTaskSchedulerService autoTaskSchedulerService,
 1425    IEventService eventService,
 1426    IOnlineRobotsService OnlineRobotsService,
 1427    IOptionsSnapshot<LgdxRobotCloudSecretConfiguration> lgdxRobotCloudSecretConfiguration,
 1428    IRobotService robotService
 1429  ) : RobotClientsServiceBase
 30{
 1431  private readonly IAutoTaskSchedulerService _autoTaskSchedulerService = autoTaskSchedulerService;
 1432  private readonly IEventService _eventService = eventService;
 1433  private readonly IOnlineRobotsService _onlineRobotsService = OnlineRobotsService;
 1434  private readonly LgdxRobotCloudSecretConfiguration _lgdxRobotCloudSecretConfiguration = lgdxRobotCloudSecretConfigurat
 1435  private readonly IRobotService _robotService = robotService;
 1436  private Guid _streamingRobotId = Guid.Empty;
 1437  private RobotClientsRobotStatus _streamingRobotStatus = RobotClientsRobotStatus.Offline;
 1438  private readonly Channel<RobotClientsRespond> _streamMessageQueue = Channel.CreateUnbounded<RobotClientsRespond>();
 39
 40  private static Guid GetRobotId(ServerCallContext context)
 741  {
 742    var robotClaim = context.GetHttpContext().User.FindFirst(ClaimTypes.NameIdentifier);
 743    return Guid.Parse(robotClaim!.Value);
 744  }
 45
 46  [Authorize(AuthenticationSchemes = LgdxRobotCloudAuthenticationSchemes.RobotClientsCertificateScheme)]
 47  public override async Task<RobotClientsGreetRespond> Greet(RobotClientsGreet request, ServerCallContext context)
 748  {
 749    var robotClaim = context.GetHttpContext().User.FindFirst(ClaimTypes.NameIdentifier);
 750    if (robotClaim == null || !Guid.TryParse(robotClaim.Value, out var robotId))
 251      return new RobotClientsGreetRespond {
 252        Status = RobotClientsResultStatus.Failed,
 253        AccessToken = string.Empty
 254      };
 55
 556    var robotIdGuid = robotId;
 557    var robot = await _robotService.GetRobotAsync(robotIdGuid);
 558    if (robot == null)
 159      return new RobotClientsGreetRespond {
 160        Status = RobotClientsResultStatus.Failed,
 161        AccessToken = string.Empty
 162      };
 63
 64    // Compare System Info
 465    var incomingSystemInfo = new RobotSystemInfoBusinessModel {
 466      Id = 0,
 467      Cpu = request.SystemInfo.Cpu,
 468      IsLittleEndian =  request.SystemInfo.IsLittleEndian,
 469      Motherboard = request.SystemInfo.Motherboard,
 470      MotherboardSerialNumber = request.SystemInfo.MotherboardSerialNumber,
 471      RamMiB =  request.SystemInfo.RamMiB,
 472      Gpu =  request.SystemInfo.Gpu,
 473      Os =  request.SystemInfo.Os,
 474      Is32Bit = request.SystemInfo.Is32Bit,
 475      McuSerialNumber = request.SystemInfo.McuSerialNumber,
 476    };
 477    var systemInfo = robot.RobotSystemInfo;
 478    if (systemInfo == null)
 179    {
 80      // Create Robot System Info for the first time
 181      await _robotService.CreateRobotSystemInfoAsync(robotIdGuid, incomingSystemInfo.ToCreateBusinessModel());
 182    }
 83    else
 384    {
 85      // Hardware Protection
 386      if (robot.IsProtectingHardwareSerialNumber && incomingSystemInfo.MotherboardSerialNumber != systemInfo.Motherboard
 187      {
 188        return new RobotClientsGreetRespond {
 189          Status = RobotClientsResultStatus.Failed,
 190          AccessToken = string.Empty
 191        };
 92      }
 293      if (robot.IsProtectingHardwareSerialNumber && incomingSystemInfo.McuSerialNumber != systemInfo.McuSerialNumber)
 194      {
 195        return new RobotClientsGreetRespond {
 196          Status = RobotClientsResultStatus.Failed,
 197          AccessToken = string.Empty
 198        };
 99      }
 1100      await _robotService.UpdateRobotSystemInfoAsync(robotIdGuid, incomingSystemInfo.ToUpdateBusinessModel());
 1101    }
 102
 103    // Generate Access Token
 2104    var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_lgdxRobotCloudSecretConfiguration.RobotClientsJwt
 2105    var credentials = new SigningCredentials(securityKey, _lgdxRobotCloudSecretConfiguration.RobotClientsJwtAlgorithm);
 2106    var secToken = new JwtSecurityToken(
 2107      _lgdxRobotCloudSecretConfiguration.RobotClientsJwtIssuer,
 2108      _lgdxRobotCloudSecretConfiguration.RobotClientsJwtIssuer,
 2109      [new Claim(ClaimTypes.NameIdentifier, robot.Id.ToString())],
 2110      DateTime.UtcNow,
 2111      DateTime.UtcNow.AddMinutes(_lgdxRobotCloudSecretConfiguration.RobotClientsJwtExpireMins),
 2112      credentials);
 2113    var token = new JwtSecurityTokenHandler().WriteToken(secToken);
 114
 2115    await _onlineRobotsService.AddRobotAsync(robotId);
 116
 2117    return new RobotClientsGreetRespond {
 2118      Status = RobotClientsResultStatus.Success,
 2119      AccessToken = token,
 2120      IsRealtimeExchange = robot.IsRealtimeExchange
 2121    };
 7122  }
 123
 124  [RobotClientShouldOnline]
 125  public override async Task<RobotClientsRespond> Exchange(RobotClientsExchange request, ServerCallContext context)
 3126  {
 3127    var robotId = GetRobotId(context);
 128
 3129    await _onlineRobotsService.UpdateRobotDataAsync(robotId, request);
 3130    var manualAutoTask = _onlineRobotsService.GetAutoTaskNextApi(robotId);
 3131    if (manualAutoTask != null)
 1132    {
 133      // Triggered by API
 1134      return new RobotClientsRespond {
 1135        Status = RobotClientsResultStatus.Success,
 1136        Commands = _onlineRobotsService.GetRobotCommands(robotId),
 1137        Task = await _autoTaskSchedulerService.AutoTaskNextConstructAsync(manualAutoTask)
 1138      };
 139    }
 140    else
 2141    {
 142      // Get AutoTask
 2143      RobotClientsAutoTask? task = null;
 2144      if (request.RobotStatus == RobotClientsRobotStatus.Idle)
 1145      {
 1146        task = await _autoTaskSchedulerService.GetAutoTaskAsync(robotId);
 1147      }
 2148      return new RobotClientsRespond {
 2149        Status = RobotClientsResultStatus.Success,
 2150        Commands = _onlineRobotsService.GetRobotCommands(robotId),
 2151        Task = task
 2152      };
 153    }
 3154  }
 155
 156  [RobotClientShouldOnline]
 157  public override async Task ExchangeStream(IAsyncStreamReader<RobotClientsExchange> requestStream, IServerStreamWriter<
 0158  {
 0159    var robotId = GetRobotId(context);
 160
 0161    _streamingRobotId = robotId;
 0162    _eventService.RobotCommandsUpdated += OnRobotCommandsUpdated;
 0163    _eventService.RobotHasNextTask += OnRobotHasNextTask;
 0164    _eventService.AutoTaskCreated += OnAutoTaskCreated;
 165
 0166    var clientToServer = ExchangeStreamClientToServerAsync(robotId, requestStream, context);
 0167    var serverToClient = ExchangeStreamServerToClientAsync(responseStream, context);
 0168    await Task.WhenAll(clientToServer, serverToClient);
 0169  }
 170
 171  private async Task ExchangeStreamClientToServerAsync(Guid robotId, IAsyncStreamReader<RobotClientsExchange> requestStr
 0172  {
 0173    while (await requestStream.MoveNext(CancellationToken.None) && !context.CancellationToken.IsCancellationRequested)
 0174    {
 0175      var request = requestStream.Current;
 0176      _streamingRobotStatus = request.RobotStatus;
 177      // Get auto task
 0178      if (request.RobotStatus == RobotClientsRobotStatus.Idle)
 0179      {
 0180        var task = await _autoTaskSchedulerService.GetAutoTaskAsync(robotId);
 0181        if (task != null)
 0182        {
 0183          await _streamMessageQueue.Writer.WriteAsync(new RobotClientsRespond {
 0184            Status = RobotClientsResultStatus.Success,
 0185            Commands = _onlineRobotsService.GetRobotCommands(_streamingRobotId),
 0186            Task = task
 0187          });
 0188        }
 0189      }
 0190      await _onlineRobotsService.UpdateRobotDataAsync(robotId, request);
 0191    }
 192
 193    // The reading stream is completed, stop wirting task
 0194    _eventService.RobotCommandsUpdated -= OnRobotCommandsUpdated;
 0195    _eventService.RobotHasNextTask -= OnRobotHasNextTask;
 0196    _eventService.AutoTaskCreated -= OnAutoTaskCreated;
 197
 198    // Assume the robot going offline
 0199    await _onlineRobotsService.RemoveRobotAsync(robotId);
 200
 0201    _streamMessageQueue.Writer.TryComplete();
 0202    await _streamMessageQueue.Reader.Completion;
 0203  }
 204
 205  private async Task ExchangeStreamServerToClientAsync(IServerStreamWriter<RobotClientsRespond> responseStream, ServerCa
 0206  {
 0207    await foreach (var message in _streamMessageQueue.Reader.ReadAllAsync(context.CancellationToken))
 0208    {
 0209      await responseStream.WriteAsync(message);
 0210    }
 0211  }
 212
 213  private async void OnRobotCommandsUpdated(object? sender, Guid robotId)
 0214  {
 0215    if (_streamingRobotId != robotId)
 0216      return;
 0217    await _streamMessageQueue.Writer.WriteAsync(new RobotClientsRespond {
 0218      Status = RobotClientsResultStatus.Success,
 0219      Commands = _onlineRobotsService.GetRobotCommands(_streamingRobotId),
 0220    });
 0221  }
 222
 223  private async void OnAutoTaskCreated(object? sender, EventArgs e)
 0224  {
 225    // Read
 0226    if (_streamingRobotStatus != RobotClientsRobotStatus.Idle)
 0227      return;
 0228    var autoTask = await _autoTaskSchedulerService.GetAutoTaskAsync(_streamingRobotId);
 0229    if (autoTask != null)
 0230    {
 0231      await _streamMessageQueue.Writer.WriteAsync(new RobotClientsRespond {
 0232        Status = RobotClientsResultStatus.Success,
 0233        Commands = _onlineRobotsService.GetRobotCommands(_streamingRobotId),
 0234        Task = autoTask
 0235      });
 0236    }
 0237  }
 238
 239  private async void OnRobotHasNextTask(object? sender, Guid robotId)
 0240  {
 241    // When an auto task is completed by API
 0242    if (_streamingRobotId != robotId)
 0243      return;
 0244    var manualAutoTask = _onlineRobotsService.GetAutoTaskNextApi(robotId);
 0245    RobotClientsAutoTask? task = null;
 0246    if (manualAutoTask != null)
 0247    {
 0248      task = await _autoTaskSchedulerService.AutoTaskNextConstructAsync(manualAutoTask);
 0249    }
 0250    await _streamMessageQueue.Writer.WriteAsync(new RobotClientsRespond {
 0251      Status = RobotClientsResultStatus.Success,
 0252      Commands = _onlineRobotsService.GetRobotCommands(robotId),
 0253      Task = task
 0254    });
 0255  }
 256
 257  [RobotClientShouldOnline]
 258  public override async Task<RobotClientsRespond> AutoTaskNext(RobotClientsNextToken request, ServerCallContext context)
 2259  {
 2260    var robotId = GetRobotId(context);
 261
 2262    var task = await _autoTaskSchedulerService.AutoTaskNextAsync(robotId, request.TaskId, request.NextToken);
 2263    return new RobotClientsRespond {
 2264      Status = task != null ? RobotClientsResultStatus.Success : RobotClientsResultStatus.Failed,
 2265      Commands = _onlineRobotsService.GetRobotCommands(robotId),
 2266      Task = task
 2267    };
 2268  }
 269
 270  [RobotClientShouldOnline]
 271  public override async Task<RobotClientsRespond> AutoTaskAbort(RobotClientsAbortToken request, ServerCallContext contex
 2272  {
 2273    var robotId = GetRobotId(context);
 274
 2275    await _onlineRobotsService.SetAbortTaskAsync(robotId, false);
 276
 2277    AutoTaskAbortReason autoTaskAbortReason = (AutoTaskAbortReason)(int)request.AbortReason;
 2278    var task = await _autoTaskSchedulerService.AutoTaskAbortAsync(robotId, request.TaskId, request.NextToken, autoTaskAb
 2279    return new RobotClientsRespond {
 2280      Status = task != null ? RobotClientsResultStatus.Success : RobotClientsResultStatus.Failed,
 2281      Commands = _onlineRobotsService.GetRobotCommands(robotId),
 2282      Task = task
 2283    };
 2284  }
 285}