< Summary

Line coverage
45%
Covered lines: 95
Uncovered lines: 113
Coverable lines: 208
Total lines: 354
Line coverage: 45.6%
Branch coverage
33%
Covered branches: 20
Total branches: 60
Branch coverage: 33.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/obj/Debug/net9.0/Microsoft.Extensions.Logging.Generators/Microsoft.Extensions.Logging.Generators.LoggerMessageGenerator/LoggerMessage.g.cs

File '/builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/obj/Debug/net9.0/Microsoft.Extensions.Logging.Generators/Microsoft.Extensions.Logging.Generators.LoggerMessageGenerator/LoggerMessage.g.cs' does not exist (any more).

/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 Microsoft.AspNetCore.Authorization;
 7using Microsoft.Extensions.Options;
 8using Microsoft.IdentityModel.Tokens;
 9using static LGDXRobotCloud.Protos.RobotClientsService;
 10using System.IdentityModel.Tokens.Jwt;
 11using System.Security.Claims;
 12using System.Text;
 13using LGDXRobotCloud.Data.Models.Business.Navigation;
 14using LGDXRobotCloud.API.Services.Automation;
 15using StackExchange.Redis;
 16using static StackExchange.Redis.RedisChannel;
 17using LGDXRobotCloud.Utilities.Helpers;
 18
 19namespace LGDXRobotCloud.API.Services;
 20
 21[Authorize(AuthenticationSchemes = LgdxRobotCloudAuthenticationSchemes.RobotClientsJwtScheme)]
 722public partial class RobotClientsService(
 723    IAutoTaskSchedulerService autoTaskSchedulerService,
 724    IConnectionMultiplexer redisConnection,
 725    ILogger<RobotClientsService> logger,
 726    IOnlineRobotsService OnlineRobotsService,
 727    IOptionsSnapshot<LgdxRobotCloudSecretConfiguration> lgdxRobotCloudSecretConfiguration,
 728    IRobotService robotService,
 729    ISlamService slamService
 730  ) : RobotClientsServiceBase
 31{
 732  private readonly IAutoTaskSchedulerService _autoTaskSchedulerService = autoTaskSchedulerService ?? throw new ArgumentN
 733  private readonly IConnectionMultiplexer _redisConnection = redisConnection ?? throw new ArgumentNullException(nameof(r
 734  private readonly IOnlineRobotsService _onlineRobotsService = OnlineRobotsService ?? throw new ArgumentNullException(na
 735  private readonly LgdxRobotCloudSecretConfiguration _lgdxRobotCloudSecretConfiguration = lgdxRobotCloudSecretConfigurat
 736  private readonly IRobotService _robotService = robotService ?? throw new ArgumentNullException(nameof(robotService));
 737  private readonly ISlamService _slamService = slamService ?? throw new ArgumentNullException(nameof(slamService));
 38
 39  [LoggerMessage(EventId = 0, Level = LogLevel.Error, Message = "gRPC RobotClientsService Exception: {Msg}")]
 40  public partial void LogException(string msg);
 41
 742  private bool pauseAutoTaskAssignmentEffective = false;
 43
 44  private static Guid GetRobotId(ServerCallContext context)
 045  {
 046    var robotClaim = context.GetHttpContext().User.FindFirst(ClaimTypes.NameIdentifier);
 047    return Guid.Parse(robotClaim!.Value);
 048  }
 49
 50  [Authorize(AuthenticationSchemes = LgdxRobotCloudAuthenticationSchemes.RobotClientsCertificateScheme)]
 51  public override async Task<RobotClientsGreetResponse> Greet(RobotClientsGreet request, ServerCallContext context)
 752  {
 753    var robotClaim = context.GetHttpContext().User.FindFirst(ClaimTypes.NameIdentifier);
 754    if (robotClaim == null || !Guid.TryParse(robotClaim.Value, out var robotId))
 255      return new RobotClientsGreetResponse
 256      {
 257        Status = RobotClientsResultStatus.Failed,
 258        AccessToken = string.Empty
 259      };
 60
 561    var robotIdGuid = robotId;
 562    var robot = await _robotService.GetRobotAsync(robotIdGuid);
 563    if (robot == null)
 164      return new RobotClientsGreetResponse
 165      {
 166        Status = RobotClientsResultStatus.Failed,
 167        AccessToken = string.Empty
 168      };
 69
 70    // Compare System Info
 471    var incomingSystemInfo = new RobotSystemInfoBusinessModel
 472    {
 473      Id = 0,
 474      Cpu = request.SystemInfo.Cpu,
 475      IsLittleEndian = request.SystemInfo.IsLittleEndian,
 476      Motherboard = request.SystemInfo.Motherboard,
 477      MotherboardSerialNumber = request.SystemInfo.MotherboardSerialNumber,
 478      RamMiB = request.SystemInfo.RamMiB,
 479      Gpu = request.SystemInfo.Gpu,
 480      Os = request.SystemInfo.Os,
 481      Is32Bit = request.SystemInfo.Is32Bit,
 482      McuSerialNumber = request.SystemInfo.McuSerialNumber,
 483    };
 484    var systemInfo = robot.RobotSystemInfo;
 485    if (systemInfo == null)
 186    {
 87      // Create Robot System Info for the first time
 188      await _robotService.CreateRobotSystemInfoAsync(robotIdGuid, incomingSystemInfo.ToCreateBusinessModel());
 189    }
 90    else
 391    {
 92      // Hardware Protection
 393      if (robot.IsProtectingHardwareSerialNumber && incomingSystemInfo.MotherboardSerialNumber != systemInfo.Motherboard
 194      {
 195        return new RobotClientsGreetResponse
 196        {
 197          Status = RobotClientsResultStatus.Failed,
 198          AccessToken = string.Empty
 199        };
 100      }
 2101      if (robot.IsProtectingHardwareSerialNumber && incomingSystemInfo.McuSerialNumber != systemInfo.McuSerialNumber)
 1102      {
 1103        return new RobotClientsGreetResponse
 1104        {
 1105          Status = RobotClientsResultStatus.Failed,
 1106          AccessToken = string.Empty
 1107        };
 108      }
 1109      await _robotService.UpdateRobotSystemInfoAsync(robotIdGuid, incomingSystemInfo.ToUpdateBusinessModel());
 1110    }
 111
 2112    var chassisInfo = await _robotService.GetRobotChassisInfoAsync(robotIdGuid);
 113
 114    // Generate Access Token
 2115    var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_lgdxRobotCloudSecretConfiguration.RobotClientsJwt
 2116    var credentials = new SigningCredentials(securityKey, _lgdxRobotCloudSecretConfiguration.RobotClientsJwtAlgorithm);
 2117    var secToken = new JwtSecurityToken(
 2118      _lgdxRobotCloudSecretConfiguration.RobotClientsJwtIssuer,
 2119      _lgdxRobotCloudSecretConfiguration.RobotClientsJwtIssuer,
 2120      [new Claim(ClaimTypes.NameIdentifier, robot.Id.ToString())],
 2121      DateTime.UtcNow,
 2122      DateTime.UtcNow.AddMinutes(_lgdxRobotCloudSecretConfiguration.RobotClientsJwtExpireMins),
 2123      credentials);
 2124    var token = new JwtSecurityTokenHandler().WriteToken(secToken);
 125
 2126    return new RobotClientsGreetResponse
 2127    {
 2128      Status = RobotClientsResultStatus.Success,
 2129      AccessToken = token,
 2130      ChassisInfo = new RobotClientsChassisInfo
 2131      {
 2132        RobotTypeId = chassisInfo!.RobotTypeId,
 2133        ChassisLX = chassisInfo.ChassisLengthX,
 2134        ChassisLY = chassisInfo.ChassisLengthY,
 2135        ChassisWheelCount = chassisInfo.ChassisWheelCount,
 2136        ChassisWheelRadius = chassisInfo.ChassisWheelRadius,
 2137        BatteryCount = chassisInfo.BatteryCount,
 2138        BatteryMaxVoltage = chassisInfo.BatteryMaxVoltage,
 2139        BatteryMinVoltage = chassisInfo.BatteryMinVoltage,
 2140      }
 2141    };
 7142  }
 143
 144  /*
 145   * Exchange
 146   */
 147  public override async Task Exchange(IAsyncStreamReader<RobotClientsExchange> requestStream, IServerStreamWriter<RobotC
 0148  {
 0149    var robotId = GetRobotId(context);
 0150    var clientToServer = ExchangeStreamClientToServerAsync(robotId, requestStream, context);
 0151    var serverToClient = ExchangeStreamServerToClientAsync(robotId, responseStream, context);
 0152    await Task.WhenAll(clientToServer, serverToClient);
 0153  }
 154
 155  private async Task ExchangeStreamClientToServerAsync(Guid robotId, IAsyncStreamReader<RobotClientsExchange> requestStr
 0156  {
 157    try
 0158    {
 0159      await _onlineRobotsService.AddRobotAsync(robotId);
 0160      await _autoTaskSchedulerService.RunSchedulerRobotNewJoinAsync(robotId);
 0161      while (await requestStream.MoveNext(CancellationToken.None) && !context.CancellationToken.IsCancellationRequested)
 0162      {
 163        // 1. Process Data
 0164        var request = requestStream.Current;
 0165        await _onlineRobotsService.UpdateRobotDataAsync(robotId, request.RobotData);
 0166        if (request.NextToken != null && request.NextToken.NextToken.Length > 0)
 0167        {
 0168          await _autoTaskSchedulerService.AutoTaskNextAsync(robotId, request.NextToken.TaskId, request.NextToken.NextTok
 0169        }
 0170        if (request.AbortToken != null && request.AbortToken.NextToken.Length > 0)
 0171        {
 0172          await _autoTaskSchedulerService.AutoTaskAbortAsync(robotId, request.AbortToken.TaskId, request.AbortToken.Next
 0173            (Utilities.Enums.AutoTaskAbortReason)(int)request.AbortToken.AbortReason);
 0174        }
 175        // 2. Process Pause Auto Task Assignment
 0176        if (pauseAutoTaskAssignmentEffective)
 0177        {
 0178          if (request.RobotData.RobotStatus != RobotClientsRobotStatus.Paused &&
 0179                !request.RobotData.PauseTaskAssignment)
 0180          {
 181            // Exit pause auto task assignment mode and request a task
 0182            pauseAutoTaskAssignmentEffective = false;
 0183            await _autoTaskSchedulerService.RunSchedulerRobotReadyAsync(robotId);
 0184          }
 0185        }
 0186        else if (request.RobotData.RobotStatus == RobotClientsRobotStatus.Paused &&
 0187                  request.RobotData.PauseTaskAssignment)
 0188        {
 189          // Enter pause auto task assignment mode
 0190          pauseAutoTaskAssignmentEffective = true;
 0191        }
 0192      }
 0193    }
 0194    catch (Exception ex)
 0195    {
 0196      LogException(ex.Message);
 0197    }
 198    finally
 0199    {
 200      // The reading stream is completed, stop wirting task
 0201      await _onlineRobotsService.RemoveRobotAsync(robotId);
 0202    }
 0203  }
 204
 205  private async Task ExchangeStreamServerToClientAsync(Guid robotId, IServerStreamWriter<RobotClientsResponse> responseS
 0206  {
 0207    await responseStream.WriteAsync(new RobotClientsResponse());
 208
 0209    var subscriber = _redisConnection.GetSubscriber();
 0210    await subscriber.SubscribeAsync(new RedisChannel(RedisHelper.GetRobotExchangeQueue(robotId), PatternMode.Literal), (
 0211    {
 0212      var response = SerialiserHelper.FromBase64<RobotClientsResponse>(value!);
 0213      if (response != null)
 0214      {
 0215        responseStream.WriteAsync(response);
 0216      }
 0217      _autoTaskSchedulerService.ReleaseRobotAsync(robotId);
 0218    });
 0219    context.CancellationToken.Register(() =>
 0220    {
 0221      subscriber.UnsubscribeAllAsync();
 0222    });
 0223  }
 224
 225  /*
 226   * SlamExchange
 227   */
 228  public override async Task SlamExchange(IAsyncStreamReader<RobotClientsSlamExchange> requestStream, IServerStreamWrite
 0229  {
 0230    var robotId = GetRobotId(context);
 0231    var realmId = await _robotService.GetRobotRealmIdAsync(robotId) ?? 0;
 232
 0233    var clientToServer = SlamExchangeClientToServerAsync(robotId, requestStream, context);
 0234    var serverToClient = SlamExchangeServerToClientAsync(realmId, responseStream, context);
 0235    await Task.WhenAll(clientToServer, serverToClient);
 0236  }
 237
 238  private async Task SlamExchangeClientToServerAsync(Guid robotId, IAsyncStreamReader<RobotClientsSlamExchange> requestS
 0239  {
 240    try
 0241    {
 0242      if (await _slamService.StartSlamAsync(robotId))
 0243      {
 244        // Only one robot can running SLAM at a time in a realm
 245        // The second robot will be ternimated
 0246        while (await requestStream.MoveNext(CancellationToken.None) && !context.CancellationToken.IsCancellationRequeste
 0247        {
 0248          var request = requestStream.Current;
 0249          await _slamService.UpdateSlamDataAsync(robotId, request.Status, request.MapData);
 0250          await _onlineRobotsService.UpdateRobotDataAsync(robotId, request.RobotData);
 0251        }
 0252      }
 0253    }
 0254    catch (Exception ex)
 0255    {
 0256      LogException(ex.Message);
 0257    }
 258    finally
 0259    {
 260      // The reading stream is completed, stop wirting task
 0261      await _slamService.StopSlamAsync(robotId);
 0262    }
 0263  }
 264
 265  private async Task SlamExchangeServerToClientAsync(int realmId, IServerStreamWriter<RobotClientsSlamCommands> response
 0266  {
 0267    await responseStream.WriteAsync(new RobotClientsSlamCommands());
 268
 0269    var subscriber = _redisConnection.GetSubscriber();
 0270    await subscriber.SubscribeAsync(new RedisChannel(RedisHelper.GetSlamExchangeQueue(realmId), PatternMode.Literal), (c
 0271    {
 0272      var response = SerialiserHelper.FromBase64<RobotClientsSlamCommands>(value!);
 0273      if (response != null)
 0274      {
 0275        responseStream.WriteAsync(response);
 0276      }
 0277    });
 0278    context.CancellationToken.Register(() =>
 0279    {
 0280      subscriber.UnsubscribeAllAsync();
 0281    });
 0282  }
 283}