LanYinWsHostedService.cs 8.59 KB
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Rcs.Application.Services;
using Rcs.Cyaninetech.Models;
using Rcs.Cyaninetech.Services;
using Rcs.Domain.Entities;
using Rcs.Domain.Settings;
using Rcs.Shared.Utils;

namespace Rcs.Cyaninetech.BackgroundServices
{
    /// <summary>
    /// 蓝音 WebSocket 后台服务 - 自动连接并订阅消息
    /// @author zzy
    /// </summary>
    public class LanYinWsHostedService : BackgroundService
    {
        private readonly string DefaultManufacturer = "lanyin";
        private readonly ILogger<LanYinWsHostedService> _logger;
        private readonly ILanYinWsClientService _wsClient;
        private readonly LanYinWsSettings _settings;
        private readonly IServiceScopeFactory _scopeFactory;
        private readonly TimeSpan _reconnectInterval = TimeSpan.FromSeconds(5);

        public LanYinWsHostedService(
            ILogger<LanYinWsHostedService> logger,
            ILanYinWsClientService wsClient,
            IOptions<AppSettings> settings,
            IServiceScopeFactory scopeFactory)
        {
            _logger = logger;
            _wsClient = wsClient;
            _settings = settings.Value.LanYinSettings.WebSocket;
            _scopeFactory = scopeFactory;

            // 注册事件处理
            _wsClient.OnRobotStatusReceived += HandleRobotStatus;
            _wsClient.OnRobotInfoReceived += HandleRobotInfo;
            _wsClient.OnRobotRealtimePathReceived += HandleRobotRealtimePath;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation("[LanYin WS] 后台服务启动");

            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    if (!_wsClient.IsConnected)
                    {
                        await _wsClient.ConnectAsync(stoppingToken);

                        // 连接成功后发送订阅请求(使用配置)
                        await _wsClient.SubscribeAsync(_settings.Topics.ToList());
                    }

                    await Task.Delay(_reconnectInterval, stoppingToken);
                }
                catch (OperationCanceledException)
                {
                    break;
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "[LanYin WS] 连接异常,{Interval}秒后重试", _reconnectInterval.TotalSeconds);
                    await Task.Delay(_reconnectInterval, stoppingToken);
                }
            }
        }

        public override async Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("[LanYin WS] 后台服务停止");
            await _wsClient.DisconnectAsync();
            await base.StopAsync(cancellationToken);
        }

        /// <summary>
        /// 处理机器人状态数据(直接使用消息中的制造商和序列号更新缓存)
        /// @author zzy
        /// </summary>
        private void HandleRobotStatus(object? sender, List<LanYinRobotStatus> data)
        {
            _ = Task.Run(async () =>
            {
                using var scope = _scopeFactory.CreateScope();
                var cacheService = scope.ServiceProvider.GetRequiredService<IRobotCacheService>();

                foreach (var status in data)
                {
                    try
                    {
                        //更新操作模式
                        var operatingMode = status.OperatingMode.ToUpper()  switch
                        {
                            "AUTOMATIC" => OperatingMode.Automatic,
                            "SEMIAUTOMATIC" => OperatingMode.Semiautomatic,
                            "MANUAL" => OperatingMode.Manual,
                            "SERVICE" => OperatingMode.Service,
                            "TEACHIN" => OperatingMode.Teachin,
                            _ => OperatingMode.Manual
                        };
                        
                        await cacheService.UpdateStatusAsync(
                            status.Manufacturer,
                            status.SerialNumber,
                            null,
                            OnlineStatus.Online,
                            status.BatteryState?.BatteryCharge ?? 0,
                            status.Driving,
                            status.Paused,
                            status.BatteryState?.Charging,
                            operatingMode,
                            status.Errors.ToJsonWithChinese()
                            );
                        // 更新位置缓存
                        if (status.Position != null)
                        {
                            var basic = await cacheService.GetBasicAsync(status.Manufacturer, status.SerialNumber);
                            var scale = basic?.CoordinateScale ?? 1d;
                            await cacheService.UpdateLocationAsync(
                                status.Manufacturer,
                                status.SerialNumber,
                                null,
                                null,
                                status.Position.X * scale,
                                status.Position.Y * scale,
                                status.Position.Rad);
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "[LanYin WS] 更新机器人状态缓存失败: {Manufacturer}:{SerialNumber}",
                            status.Manufacturer, status.SerialNumber);
                    }
                }
            });
        }

        /// <summary>
        /// 处理机器人信息数据(直接使用消息中的序列号和固定制造商更新缓存)
        /// @author zzy
        /// </summary>
        private void HandleRobotInfo(object? sender, List<LanYinRobotInfo> data)
        {
            _ = Task.Run(async () =>
            {
                using var scope = _scopeFactory.CreateScope();
                var cacheService = scope.ServiceProvider.GetRequiredService<IRobotCacheService>();
                foreach (var info in data)
                {
                    var status = RobotStatus.Idle;
                    if (info.RunningStatus.ToUpper().Equals("FAULT") || info.RunningStatus.ToUpper().Equals("DISCONNECT"))
                    {
                        status = RobotStatus.Error;
                    }
                    else
                    {
                        // 根据 task_status 映射 RobotStatus
                        // free - 空闲(无任务), resting/prerest - 休息中 → Idle
                        // use/pre_use/precharge/charging → Busy
                        status = info.TaskStatus switch
                        {
                            "free" or "prerest" or "resting" or "charging" => RobotStatus.Idle,
                            "use" or "pre_use" or "precharge" => RobotStatus.Busy,
                            _ => RobotStatus.Busy
                        };
                    }
                    await cacheService.UpdateStatusAsync(
                        DefaultManufacturer,
                        info.Id,
                        status,
                        OnlineStatus.Online,
                        null,
                        null,
                        null,
                        null,
                        null,
                        null
                    );
                }
            });
        }

        /// <summary>
        /// 处理机器人实时路径数据
        /// @author zzy
        /// </summary>
        private void HandleRobotRealtimePath(object? sender, LanYinRobotRealtimePath data)
        {
            _ = Task.Run(async () =>
            {
                using var scope = _scopeFactory.CreateScope();
                var cacheService = scope.ServiceProvider.GetRequiredService<IRobotCacheService>();

                foreach (var (robotId, path) in data)
                {
                    var basic = await cacheService.GetBasicAsync(DefaultManufacturer, robotId);
                    var scale = basic?.CoordinateScale ?? 1d;

                    foreach (var point in path)
                    {
                        point[0] *= scale;
                        point[1] *= scale;
                    }

                    await cacheService.SetLocationValueAsync(DefaultManufacturer, robotId, "Path", path.ToJsonWithChinese());
                }
            });
        }
    }
}