< Summary

Information
Class: LGDXRobotCloud.API.Services.RobotClientsService
Assembly: LGDXRobotCloud.API
File(s): /builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/Services/RobotClientsService.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 240
Coverable lines: 240
Total lines: 323
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 60
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

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;
 18
 19namespace LGDXRobotCloud.API.Services;
 20
 21[Authorize(AuthenticationSchemes = LgdxRobotCloudAuthenticationSchemes.RobotClientsJwtScheme)]
 022public class RobotClientsService(
 023    IAutoTaskSchedulerService autoTaskSchedulerService,
 024    IEventService eventService,
 025    IOnlineRobotsService OnlineRobotsService,
 026    IOptionsSnapshot<LgdxRobotCloudSecretConfiguration> lgdxRobotCloudSecretConfiguration,
 027    IRobotService robotService
 028  ) : RobotClientsServiceBase
 29{
 030  private readonly IAutoTaskSchedulerService _autoTaskSchedulerService = autoTaskSchedulerService;
 031  private readonly IEventService _eventService = eventService;
 032  private readonly IOnlineRobotsService _onlineRobotsService = OnlineRobotsService;
 033  private readonly LgdxRobotCloudSecretConfiguration _lgdxRobotCloudSecretConfiguration = lgdxRobotCloudSecretConfigurat
 034  private readonly IRobotService _robotService = robotService;
 035  private Guid _streamingRobotId = Guid.Empty;
 036  private RobotClientsRobotStatus _streamingRobotStatus = RobotClientsRobotStatus.Offline;
 037  private readonly Channel<RobotClientsRespond> _streamMessageQueue = Channel.CreateUnbounded<RobotClientsRespond>();
 38
 39  private static Guid? ValidateRobotClaim(ServerCallContext context)
 040  {
 041    var robotClaim = context.GetHttpContext().User.FindFirst(ClaimTypes.NameIdentifier);
 042    if (robotClaim == null)
 043    {
 044      return null;
 45    }
 046    return Guid.Parse(robotClaim.Value);
 047  }
 48
 49  private static RobotClientsRespond ValidateRobotClaimFailed()
 050  {
 051    return new RobotClientsRespond {
 052      Status = RobotClientsResultStatus.Failed,
 053    };
 054  }
 55
 56  // TODO: Validate in authorisation
 57  private async Task<bool> ValidateOnlineRobotsAsync(Guid robotId)
 058  {
 059    return await _onlineRobotsService.IsRobotOnlineAsync(robotId);
 060  }
 61
 62  private static RobotClientsRespond ValidateOnlineRobotsFailed()
 063  {
 064    return new RobotClientsRespond {
 065      Status = RobotClientsResultStatus.Failed,
 066    };
 067  }
 68
 69  [Authorize(AuthenticationSchemes = LgdxRobotCloudAuthenticationSchemes.RobotClientsCertificateScheme)]
 70  public override async Task<RobotClientsGreetRespond> Greet(RobotClientsGreet request, ServerCallContext context)
 071  {
 072    var robotId = ValidateRobotClaim(context);
 073    if (robotId == null)
 074      return new RobotClientsGreetRespond {
 075        Status = RobotClientsResultStatus.Failed,
 076        AccessToken = string.Empty
 077      };
 78
 079    var robotIdGuid = (Guid)robotId;
 080    var robot = await _robotService.GetRobotAsync(robotIdGuid);
 081    if (robot == null)
 082      return new RobotClientsGreetRespond {
 083        Status = RobotClientsResultStatus.Failed,
 084        AccessToken = string.Empty
 085      };
 86
 87    // Compare System Info
 088    var incomingSystemInfo = new RobotSystemInfoBusinessModel {
 089      Id = 0,
 090      Cpu = request.SystemInfo.Cpu,
 091      IsLittleEndian =  request.SystemInfo.IsLittleEndian,
 092      Motherboard = request.SystemInfo.Motherboard,
 093      MotherboardSerialNumber = request.SystemInfo.MotherboardSerialNumber,
 094      RamMiB =  request.SystemInfo.RamMiB,
 095      Gpu =  request.SystemInfo.Gpu,
 096      Os =  request.SystemInfo.Os,
 097      Is32Bit = request.SystemInfo.Is32Bit,
 098      McuSerialNumber = request.SystemInfo.McuSerialNumber,
 099    };
 0100    var systemInfo = await _robotService.GetRobotSystemInfoAsync(robotIdGuid);
 0101    if (systemInfo == null)
 0102    {
 103      // Create Robot System Info for the first time
 0104      await _robotService.CreateRobotSystemInfoAsync(robotIdGuid, incomingSystemInfo.ToCreateBusinessModel());
 0105    }
 106    else
 0107    {
 108      // Hardware Protection
 0109      if (robot.IsProtectingHardwareSerialNumber && incomingSystemInfo.MotherboardSerialNumber != systemInfo.Motherboard
 0110      {
 0111        return new RobotClientsGreetRespond {
 0112          Status = RobotClientsResultStatus.Failed,
 0113          AccessToken = string.Empty
 0114        };
 115      }
 0116      if (robot.IsProtectingHardwareSerialNumber && incomingSystemInfo.McuSerialNumber != systemInfo.McuSerialNumber)
 0117      {
 0118        return new RobotClientsGreetRespond {
 0119          Status = RobotClientsResultStatus.Failed,
 0120          AccessToken = string.Empty
 0121        };
 122      }
 0123      await _robotService.UpdateRobotSystemInfoAsync(robotIdGuid, incomingSystemInfo.ToUpdateBusinessModel());
 0124    }
 125
 126    // Generate Access Token
 0127    var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_lgdxRobotCloudSecretConfiguration.RobotClientsJwt
 0128    var credentials = new SigningCredentials(securityKey, _lgdxRobotCloudSecretConfiguration.RobotClientsJwtAlgorithm);
 0129    var secToken = new JwtSecurityToken(
 0130      _lgdxRobotCloudSecretConfiguration.RobotClientsJwtIssuer,
 0131      _lgdxRobotCloudSecretConfiguration.RobotClientsJwtIssuer,
 0132      [new Claim(ClaimTypes.NameIdentifier, robot.Id.ToString())],
 0133      DateTime.UtcNow,
 0134      DateTime.UtcNow.AddMinutes(_lgdxRobotCloudSecretConfiguration.RobotClientsJwtExpireMins),
 0135      credentials);
 0136    var token = new JwtSecurityTokenHandler().WriteToken(secToken);
 137
 0138    await _onlineRobotsService.AddRobotAsync((Guid)robotId);
 139
 0140    return new RobotClientsGreetRespond {
 0141      Status = RobotClientsResultStatus.Success,
 0142      AccessToken = token,
 0143      IsRealtimeExchange = await _robotService.GetRobotIsRealtimeExchange((Guid)robotId)
 0144    };
 0145  }
 146
 147  public override async Task<RobotClientsRespond> Exchange(RobotClientsExchange request, ServerCallContext context)
 0148  {
 0149    var robotId = ValidateRobotClaim(context);
 0150    if (robotId == null)
 0151      return ValidateRobotClaimFailed();
 0152    if (!await ValidateOnlineRobotsAsync((Guid)robotId))
 0153      return ValidateOnlineRobotsFailed();
 154
 0155    await _onlineRobotsService.UpdateRobotDataAsync((Guid)robotId, request);
 0156    var manualAutoTask = _onlineRobotsService.GetAutoTaskNextApi((Guid)robotId);
 0157    if (manualAutoTask != null)
 0158    {
 159      // Triggered by API
 0160      return new RobotClientsRespond {
 0161        Status = RobotClientsResultStatus.Success,
 0162        Commands = _onlineRobotsService.GetRobotCommands((Guid)robotId),
 0163        Task = await _autoTaskSchedulerService.AutoTaskNextConstructAsync(manualAutoTask)
 0164      };
 165    }
 166    else
 0167    {
 168      // Get AutoTask
 0169      RobotClientsAutoTask? task = null;
 0170      if (request.RobotStatus == RobotClientsRobotStatus.Idle)
 0171      {
 0172        task = await _autoTaskSchedulerService.GetAutoTaskAsync((Guid)robotId);
 0173      }
 0174      return new RobotClientsRespond {
 0175        Status = RobotClientsResultStatus.Success,
 0176        Commands = _onlineRobotsService.GetRobotCommands((Guid)robotId),
 0177        Task = task
 0178      };
 179    }
 0180  }
 181
 182  public override async Task ExchangeStream(IAsyncStreamReader<RobotClientsExchange> requestStream, IServerStreamWriter<
 0183  {
 0184    var robotId = ValidateRobotClaim(context);
 0185    if (robotId == null)
 0186    {
 0187      var response = ValidateRobotClaimFailed();
 0188      await responseStream.WriteAsync(response);
 0189      return;
 190    }
 0191    if (!await ValidateOnlineRobotsAsync((Guid)robotId))
 0192    {
 0193      var response = ValidateOnlineRobotsFailed();
 0194      await responseStream.WriteAsync(response);
 0195      return;
 196    }
 0197    _streamingRobotId = (Guid)robotId;
 0198    _eventService.RobotCommandsUpdated += OnRobotCommandsUpdated;
 0199    _eventService.RobotHasNextTask += OnRobotHasNextTask;
 0200    _eventService.AutoTaskCreated += OnAutoTaskCreated;
 201
 0202    var clientToServer = ExchangeStreamClientToServerAsync((Guid)robotId, requestStream, context);
 0203    var serverToClient = ExchangeStreamServerToClientAsync(responseStream, context);
 0204    await Task.WhenAll(clientToServer, serverToClient);
 0205  }
 206
 207  private async Task ExchangeStreamClientToServerAsync(Guid robotId, IAsyncStreamReader<RobotClientsExchange> requestStr
 0208  {
 0209    while (await requestStream.MoveNext(CancellationToken.None) && !context.CancellationToken.IsCancellationRequested)
 0210    {
 0211      var request = requestStream.Current;
 0212      _streamingRobotStatus = request.RobotStatus;
 213      // Get auto task
 0214      if (request.RobotStatus == RobotClientsRobotStatus.Idle)
 0215      {
 0216        var task = await _autoTaskSchedulerService.GetAutoTaskAsync(robotId);
 0217        if (task != null)
 0218        {
 0219          await _streamMessageQueue.Writer.WriteAsync(new RobotClientsRespond {
 0220            Status = RobotClientsResultStatus.Success,
 0221            Commands = _onlineRobotsService.GetRobotCommands(_streamingRobotId),
 0222            Task = task
 0223          });
 0224        }
 0225      }
 0226      await _onlineRobotsService.UpdateRobotDataAsync(robotId, request);
 0227    }
 228
 229    // The reading stream is completed, stop wirting task
 0230    _eventService.RobotCommandsUpdated -= OnRobotCommandsUpdated;
 0231    _eventService.RobotHasNextTask -= OnRobotHasNextTask;
 0232    _eventService.AutoTaskCreated -= OnAutoTaskCreated;
 0233    _streamMessageQueue.Writer.TryComplete();
 0234    await _streamMessageQueue.Reader.Completion;
 0235  }
 236
 237  private async Task ExchangeStreamServerToClientAsync(IServerStreamWriter<RobotClientsRespond> responseStream, ServerCa
 0238  {
 0239    await foreach (var message in _streamMessageQueue.Reader.ReadAllAsync(context.CancellationToken))
 0240    {
 0241      await responseStream.WriteAsync(message);
 0242    }
 0243  }
 244
 245  private async void OnRobotCommandsUpdated(object? sender, Guid robotId)
 0246  {
 0247    if (_streamingRobotId != robotId)
 0248      return;
 0249    await _streamMessageQueue.Writer.WriteAsync(new RobotClientsRespond {
 0250      Status = RobotClientsResultStatus.Success,
 0251      Commands = _onlineRobotsService.GetRobotCommands(_streamingRobotId),
 0252    });
 0253  }
 254
 255  private async void OnAutoTaskCreated(object? sender, EventArgs e)
 0256  {
 257    // Read
 0258    if (_streamingRobotStatus != RobotClientsRobotStatus.Idle)
 0259      return;
 0260    var autoTask = await _autoTaskSchedulerService.GetAutoTaskAsync(_streamingRobotId);
 0261    if (autoTask != null)
 0262    {
 0263      await _streamMessageQueue.Writer.WriteAsync(new RobotClientsRespond {
 0264        Status = RobotClientsResultStatus.Success,
 0265        Commands = _onlineRobotsService.GetRobotCommands(_streamingRobotId),
 0266        Task = autoTask
 0267      });
 0268    }
 0269  }
 270
 271  private async void OnRobotHasNextTask(object? sender, Guid robotId)
 0272  {
 273    // When an auto task is completed by API
 0274    if (_streamingRobotId != robotId)
 0275      return;
 0276    var manualAutoTask = _onlineRobotsService.GetAutoTaskNextApi(robotId);
 0277    RobotClientsAutoTask? task = null;
 0278    if (manualAutoTask != null)
 0279    {
 0280      task = await _autoTaskSchedulerService.AutoTaskNextConstructAsync(manualAutoTask);
 0281    }
 0282    await _streamMessageQueue.Writer.WriteAsync(new RobotClientsRespond {
 0283      Status = RobotClientsResultStatus.Success,
 0284      Commands = _onlineRobotsService.GetRobotCommands(robotId),
 0285      Task = task
 0286    });
 0287  }
 288
 289  public override async Task<RobotClientsRespond> AutoTaskNext(RobotClientsNextToken request, ServerCallContext context)
 0290  {
 0291    var robotId = ValidateRobotClaim(context);
 0292    if (robotId == null)
 0293      return ValidateRobotClaimFailed();
 0294    if (!await ValidateOnlineRobotsAsync((Guid)robotId))
 0295      return ValidateOnlineRobotsFailed();
 296
 0297    var task = await _autoTaskSchedulerService.AutoTaskNextAsync((Guid)robotId, request.TaskId, request.NextToken);
 0298    return new RobotClientsRespond {
 0299      Status = task != null ? RobotClientsResultStatus.Success : RobotClientsResultStatus.Failed,
 0300      Commands = _onlineRobotsService.GetRobotCommands((Guid)robotId),
 0301      Task = task
 0302    };
 0303  }
 304
 305  public override async Task<RobotClientsRespond> AutoTaskAbort(RobotClientsAbortToken request, ServerCallContext contex
 0306  {
 0307    var robotId = ValidateRobotClaim(context);
 0308    if (robotId == null)
 0309      return ValidateRobotClaimFailed();
 0310    if (!await ValidateOnlineRobotsAsync((Guid)robotId))
 0311      return ValidateOnlineRobotsFailed();
 312
 0313    await _onlineRobotsService.SetAbortTaskAsync((Guid)robotId, false);
 314
 0315    AutoTaskAbortReason autoTaskAbortReason = (AutoTaskAbortReason)(int)request.AbortReason;
 0316    var task = await _autoTaskSchedulerService.AutoTaskAbortAsync((Guid)robotId, request.TaskId, request.NextToken, auto
 0317    return new RobotClientsRespond {
 0318      Status = task != null ? RobotClientsResultStatus.Success : RobotClientsResultStatus.Failed,
 0319      Commands = _onlineRobotsService.GetRobotCommands((Guid)robotId),
 0320      Task = task
 0321    };
 0322  }
 323}