< Summary

Information
Class: LGDXRobotCloud.API.Services.RobotClientsService
Assembly: LGDXRobotCloud.API
File(s): /builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/Services/RobotClientsService.cs
Line coverage
63%
Covered lines: 137
Uncovered lines: 78
Coverable lines: 215
Total lines: 298
Line coverage: 63.7%
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
 2103    var chassisInfo = await _robotService.GetRobotChassisInfoAsync(robotIdGuid);
 104
 105    // Generate Access Token
 2106    var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_lgdxRobotCloudSecretConfiguration.RobotClientsJwt
 2107    var credentials = new SigningCredentials(securityKey, _lgdxRobotCloudSecretConfiguration.RobotClientsJwtAlgorithm);
 2108    var secToken = new JwtSecurityToken(
 2109      _lgdxRobotCloudSecretConfiguration.RobotClientsJwtIssuer,
 2110      _lgdxRobotCloudSecretConfiguration.RobotClientsJwtIssuer,
 2111      [new Claim(ClaimTypes.NameIdentifier, robot.Id.ToString())],
 2112      DateTime.UtcNow,
 2113      DateTime.UtcNow.AddMinutes(_lgdxRobotCloudSecretConfiguration.RobotClientsJwtExpireMins),
 2114      credentials);
 2115    var token = new JwtSecurityTokenHandler().WriteToken(secToken);
 116
 2117    await _onlineRobotsService.AddRobotAsync(robotId);
 118
 2119    return new RobotClientsGreetRespond
 2120    {
 2121      Status = RobotClientsResultStatus.Success,
 2122      AccessToken = token,
 2123      IsRealtimeExchange = robot.IsRealtimeExchange,
 2124      ChassisInfo = new RobotClientsChassisInfo {
 2125        RobotTypeId = chassisInfo!.RobotTypeId,
 2126        ChassisLX = chassisInfo.ChassisLengthX,
 2127        ChassisLY = chassisInfo.ChassisLengthY,
 2128        ChassisWheelCount = chassisInfo.ChassisWheelCount,
 2129        ChassisWheelRadius = chassisInfo.ChassisWheelRadius,
 2130        BatteryCount = chassisInfo.BatteryCount,
 2131        BatteryMaxVoltage = chassisInfo.BatteryMaxVoltage,
 2132        BatteryMinVoltage = chassisInfo.BatteryMinVoltage,
 2133      }
 2134    };
 7135  }
 136
 137  [RobotClientShouldOnline]
 138  public override async Task<RobotClientsRespond> Exchange(RobotClientsExchange request, ServerCallContext context)
 3139  {
 3140    var robotId = GetRobotId(context);
 141
 3142    await _onlineRobotsService.UpdateRobotDataAsync(robotId, request);
 3143    var manualAutoTask = _onlineRobotsService.GetAutoTaskNextApi(robotId);
 3144    if (manualAutoTask != null)
 1145    {
 146      // Triggered by API
 1147      return new RobotClientsRespond {
 1148        Status = RobotClientsResultStatus.Success,
 1149        Commands = _onlineRobotsService.GetRobotCommands(robotId),
 1150        Task = await _autoTaskSchedulerService.AutoTaskNextConstructAsync(manualAutoTask)
 1151      };
 152    }
 153    else
 2154    {
 155      // Get AutoTask
 2156      RobotClientsAutoTask? task = null;
 2157      if (request.RobotStatus == RobotClientsRobotStatus.Idle)
 1158      {
 1159        task = await _autoTaskSchedulerService.GetAutoTaskAsync(robotId);
 1160      }
 2161      return new RobotClientsRespond {
 2162        Status = RobotClientsResultStatus.Success,
 2163        Commands = _onlineRobotsService.GetRobotCommands(robotId),
 2164        Task = task
 2165      };
 166    }
 3167  }
 168
 169  [RobotClientShouldOnline]
 170  public override async Task ExchangeStream(IAsyncStreamReader<RobotClientsExchange> requestStream, IServerStreamWriter<
 0171  {
 0172    var robotId = GetRobotId(context);
 173
 0174    _streamingRobotId = robotId;
 0175    _eventService.RobotCommandsUpdated += OnRobotCommandsUpdated;
 0176    _eventService.RobotHasNextTask += OnRobotHasNextTask;
 0177    _eventService.AutoTaskCreated += OnAutoTaskCreated;
 178
 0179    var clientToServer = ExchangeStreamClientToServerAsync(robotId, requestStream, context);
 0180    var serverToClient = ExchangeStreamServerToClientAsync(responseStream, context);
 0181    await Task.WhenAll(clientToServer, serverToClient);
 0182  }
 183
 184  private async Task ExchangeStreamClientToServerAsync(Guid robotId, IAsyncStreamReader<RobotClientsExchange> requestStr
 0185  {
 0186    while (await requestStream.MoveNext(CancellationToken.None) && !context.CancellationToken.IsCancellationRequested)
 0187    {
 0188      var request = requestStream.Current;
 0189      _streamingRobotStatus = request.RobotStatus;
 190      // Get auto task
 0191      if (request.RobotStatus == RobotClientsRobotStatus.Idle)
 0192      {
 0193        var task = await _autoTaskSchedulerService.GetAutoTaskAsync(robotId);
 0194        if (task != null)
 0195        {
 0196          await _streamMessageQueue.Writer.WriteAsync(new RobotClientsRespond {
 0197            Status = RobotClientsResultStatus.Success,
 0198            Commands = _onlineRobotsService.GetRobotCommands(_streamingRobotId),
 0199            Task = task
 0200          });
 0201        }
 0202      }
 0203      await _onlineRobotsService.UpdateRobotDataAsync(robotId, request);
 0204    }
 205
 206    // The reading stream is completed, stop wirting task
 0207    _eventService.RobotCommandsUpdated -= OnRobotCommandsUpdated;
 0208    _eventService.RobotHasNextTask -= OnRobotHasNextTask;
 0209    _eventService.AutoTaskCreated -= OnAutoTaskCreated;
 210
 211    // Assume the robot going offline
 0212    await _onlineRobotsService.RemoveRobotAsync(robotId);
 213
 0214    _streamMessageQueue.Writer.TryComplete();
 0215    await _streamMessageQueue.Reader.Completion;
 0216  }
 217
 218  private async Task ExchangeStreamServerToClientAsync(IServerStreamWriter<RobotClientsRespond> responseStream, ServerCa
 0219  {
 0220    await foreach (var message in _streamMessageQueue.Reader.ReadAllAsync(context.CancellationToken))
 0221    {
 0222      await responseStream.WriteAsync(message);
 0223    }
 0224  }
 225
 226  private async void OnRobotCommandsUpdated(object? sender, Guid robotId)
 0227  {
 0228    if (_streamingRobotId != robotId)
 0229      return;
 0230    await _streamMessageQueue.Writer.WriteAsync(new RobotClientsRespond {
 0231      Status = RobotClientsResultStatus.Success,
 0232      Commands = _onlineRobotsService.GetRobotCommands(_streamingRobotId),
 0233    });
 0234  }
 235
 236  private async void OnAutoTaskCreated(object? sender, EventArgs e)
 0237  {
 238    // Read
 0239    if (_streamingRobotStatus != RobotClientsRobotStatus.Idle)
 0240      return;
 0241    var autoTask = await _autoTaskSchedulerService.GetAutoTaskAsync(_streamingRobotId);
 0242    if (autoTask != null)
 0243    {
 0244      await _streamMessageQueue.Writer.WriteAsync(new RobotClientsRespond {
 0245        Status = RobotClientsResultStatus.Success,
 0246        Commands = _onlineRobotsService.GetRobotCommands(_streamingRobotId),
 0247        Task = autoTask
 0248      });
 0249    }
 0250  }
 251
 252  private async void OnRobotHasNextTask(object? sender, Guid robotId)
 0253  {
 254    // When an auto task is completed by API
 0255    if (_streamingRobotId != robotId)
 0256      return;
 0257    var manualAutoTask = _onlineRobotsService.GetAutoTaskNextApi(robotId);
 0258    RobotClientsAutoTask? task = null;
 0259    if (manualAutoTask != null)
 0260    {
 0261      task = await _autoTaskSchedulerService.AutoTaskNextConstructAsync(manualAutoTask);
 0262    }
 0263    await _streamMessageQueue.Writer.WriteAsync(new RobotClientsRespond {
 0264      Status = RobotClientsResultStatus.Success,
 0265      Commands = _onlineRobotsService.GetRobotCommands(robotId),
 0266      Task = task
 0267    });
 0268  }
 269
 270  [RobotClientShouldOnline]
 271  public override async Task<RobotClientsRespond> AutoTaskNext(RobotClientsNextToken request, ServerCallContext context)
 2272  {
 2273    var robotId = GetRobotId(context);
 274
 2275    var task = await _autoTaskSchedulerService.AutoTaskNextAsync(robotId, request.TaskId, request.NextToken);
 2276    return new RobotClientsRespond {
 2277      Status = task != null ? RobotClientsResultStatus.Success : RobotClientsResultStatus.Failed,
 2278      Commands = _onlineRobotsService.GetRobotCommands(robotId),
 2279      Task = task
 2280    };
 2281  }
 282
 283  [RobotClientShouldOnline]
 284  public override async Task<RobotClientsRespond> AutoTaskAbort(RobotClientsAbortToken request, ServerCallContext contex
 2285  {
 2286    var robotId = GetRobotId(context);
 287
 2288    await _onlineRobotsService.SetAbortTaskAsync(robotId, false);
 289
 2290    AutoTaskAbortReason autoTaskAbortReason = (AutoTaskAbortReason)(int)request.AbortReason;
 2291    var task = await _autoTaskSchedulerService.AutoTaskAbortAsync(robotId, request.TaskId, request.NextToken, autoTaskAb
 2292    return new RobotClientsRespond {
 2293      Status = task != null ? RobotClientsResultStatus.Success : RobotClientsResultStatus.Failed,
 2294      Commands = _onlineRobotsService.GetRobotCommands(robotId),
 2295      Task = task
 2296    };
 2297  }
 298}