Przeglądaj źródła

引入通用光源控制系统基础架构

新增光源控制相关接口与实现,包括 ILightController、ILightChannel、ICommunicationProtocol 及其串口和 TCP 协议适配。实现 KCS 型号光源控制器与通道,支持多控制器、多通道统一管理。引入光源配置模型、全局通道映射、事件通知等,完善依赖与项目结构,为后续扩展和界面集成奠定基础。
孝锋 徐 8 miesięcy temu
rodzic
commit
981bdefa1e

+ 38 - 0
TeamAAS-VM/Core/Lights/ICommunicationProtocol.cs

@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TeamAAS_VP.Enums;
+
+namespace TeamAAS_VP.Core.Lights
+{
+    public interface ICommunicationProtocol : IDisposable
+    {
+        bool IsConnected { get; }
+        Terminator Terminator { get; }
+        Encoding Encoding { get; }
+
+        //连接改变时间
+        event Action<object, bool> ConnectionChanged;
+        //接收事件
+        event Action<object, string> DataReceived;
+        //发送事件
+        event Action<object, string> DataSent;
+
+
+        Task<bool> ConnectAsync();
+        Task DisconnectAsync();
+        Task<byte[]> SendAndReceiveAsync(byte[] data, int timeout = 5000);
+        Task SendAsync(byte[] data);
+
+        Task<string> SendAndReceiveAsync(string data, int timeout = 5000);
+
+        string SendAndReceive(string data, int timeout = 5000);
+        byte[] SendAndReceive(byte[] data, int timeout = 5000);
+        void Send(byte[] data);
+        Task SendAsync(string data);
+        void Send(string data);
+    }
+
+}

+ 25 - 0
TeamAAS-VM/Core/Lights/ILightChannel.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TeamAAS_VP.Core.Lights
+{
+    /// <summary>
+    /// 光源通道接口
+    /// </summary>
+    public interface ILightChannel
+    {
+        int ChannelIndex { get; }
+        string ChannelName { get; set; }
+        int Brightness { get; }
+        bool IsOn { get; }
+
+        Task<bool> SetBrightnessAsync(int brightness);
+        Task<bool> TurnOnAsync();
+        Task<bool> TurnOffAsync();
+        Task<int> GetBrightnessAsync();
+        Task<bool> GetStatusAsync();
+    }
+}

+ 29 - 0
TeamAAS-VM/Core/Lights/ILightController.cs

@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TeamAAS_VP.Core.Lights
+{
+    /// <summary>
+    /// 光源控制器接口
+    /// </summary>
+    public interface ILightController : IDisposable
+    {
+        int Id { get; }
+        string Name { get; }
+        LightModel Model { get; }
+        int ChannelCount { get; }
+        bool IsConnected { get; }
+
+        IReadOnlyList<ILightChannel> Channels { get; }
+
+        Task<bool> ConnectAsync();
+        Task DisconnectAsync();
+        Task<bool> InitializeAsync();
+
+        Task<bool> TurnOnAllAsync();
+        Task<bool> TurnOffAllAsync();
+    }
+}

+ 66 - 0
TeamAAS-VM/Core/Lights/KCSLightChannel.cs

@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TeamAAS_VP.Core.Lights
+{
+    public class KCSLightChannel : ILightChannel
+    {
+        private readonly KCSLightController _controller;
+        private int _brightness;
+        private bool _isOn;
+
+        public int ChannelIndex { get; }
+        public string ChannelName { get; set; }
+        public int Brightness => _brightness;
+        public bool IsOn => _isOn;
+
+        internal int CurrentBrightness => _brightness;
+
+        public KCSLightChannel(int index, KCSLightController controller)
+        {
+            ChannelIndex = index;
+            ChannelName = $"Channel {index + 1}";
+            _controller = controller;
+        }
+
+        public async Task<bool> SetBrightnessAsync(int brightness)
+        {
+            if (brightness < 0 || brightness > 255)
+                throw new ArgumentOutOfRangeException(nameof(brightness));
+
+            var result = await _controller.SetChannelBrightnessInternal(ChannelIndex, brightness);
+            if (result)
+            {
+                _brightness = brightness;
+                _isOn = brightness > 0;
+            }
+            return result;
+        }
+
+        public async Task<bool> TurnOnAsync()
+        {
+            return await SetBrightnessAsync(_brightness > 0 ? _brightness : 100);
+        }
+
+        public async Task<bool> TurnOffAsync()
+        {
+            return await SetBrightnessAsync(0);
+        }
+
+        public async Task<int> GetBrightnessAsync()
+        {
+            _brightness = await _controller.GetChannelBrightnessInternal(ChannelIndex);
+            _isOn = _brightness > 0;
+            return _brightness;
+        }
+
+        public async Task<bool> GetStatusAsync()
+        {
+            await GetBrightnessAsync();
+            return _isOn;
+        }
+    }
+}

+ 116 - 0
TeamAAS-VM/Core/Lights/KCSLightController.cs

@@ -0,0 +1,116 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TeamAAS_VP.Core.Lights
+{
+    public class KCSLightController : LightControllerBase
+    {
+        private readonly ICommunicationProtocol _protocol;
+        private readonly Encoding _encoding;
+
+        public KCSLightController(int id, ICommunicationProtocol protocol,int channelCount)
+            : base(id, channelCount)
+        {
+            _protocol = protocol;
+            _encoding = Encoding.ASCII;
+        }
+
+        protected override ILightChannel CreateChannel(int index)
+        {
+            return new KCSLightChannel(index, this);
+        }
+
+        public override async Task<bool> ConnectAsync()
+        {
+            var connected = await _protocol.ConnectAsync();
+            if (connected)
+            {
+                IsConnected = true;
+                await InitializeAsync();
+            }
+            return connected;
+        }
+
+        public override async Task DisconnectAsync()
+        {
+            await _protocol.DisconnectAsync();
+            IsConnected = false;
+        }
+
+        public override async Task<bool> InitializeAsync()
+        {
+            try
+            {
+                // 初始化操作
+                await GetChannelBrightnessInternal(0); // 测试通信
+                return true;
+            }
+            catch
+            {
+                return false;
+            }
+        }
+
+        public override async Task<bool> TurnOnAllAsync()
+        {
+            StringBuilder commandBuilder = new StringBuilder("S");
+            for (int i = 0; i < ChannelCount; i++)
+            {
+                var channel = (KCSLightChannel)ChannelsInternal[i];
+                commandBuilder.Append(channel.CurrentBrightness.ToString("D3"));
+                commandBuilder.Append("T");
+            }
+            commandBuilder.Append("C#");
+
+            var response = await SendCommandAsync(commandBuilder.ToString());
+            return response?.Trim() == "!";
+        }
+
+        public override async Task<bool> TurnOffAllAsync()
+        {
+            StringBuilder commandBuilder = new StringBuilder("S");
+            for (int i = 0; i < ChannelCount; i++)
+            {
+                commandBuilder.Append("000");
+                commandBuilder.Append("F");
+            }
+            commandBuilder.Append("C#");
+
+            var response = await SendCommandAsync(commandBuilder.ToString());
+            return response?.Trim() == "!";
+        }
+
+        internal async Task<string> SendCommandAsync(string command)
+        {
+            var data = _encoding.GetBytes(command);
+            var response = await _protocol.SendAndReceiveAsync(data, 1000);
+            return _encoding.GetString(response);
+        }
+
+        internal async Task<int> GetChannelBrightnessInternal(int channelIndex)
+        {
+            string command = $"S{(char)('A' + channelIndex)}#";
+            var response = await SendCommandAsync(command);
+
+            if (response.Length == 5)
+            {
+                string valueStr = response.Substring(2, 3);
+                if (int.TryParse(valueStr, out int brightness))
+                {
+                    return brightness;
+                }
+            }
+            return 0;
+        }
+
+        internal async Task<bool> SetChannelBrightnessInternal(int channelIndex, int brightness)
+        {
+            string command = $"S{(char)('A' + channelIndex)}0{brightness.ToString("D3")}#";
+            var response = await SendCommandAsync(command);
+            return response?.Trim() == ((char)('A' + channelIndex)).ToString();
+        }
+    }
+}

+ 22 - 0
TeamAAS-VM/Core/Lights/LightChannelEventArgs.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TeamAAS_VP.Core.Lights
+{
+    public class LightChannelEventArgs : EventArgs
+    {
+        public ILightChannel Channel { get; }
+        public bool IsOn { get; }
+        public int Brightness { get; }
+
+        public LightChannelEventArgs(ILightChannel channel, bool isOn, int brightness)
+        {
+            Channel = channel;
+            IsOn = isOn;
+            Brightness = brightness;
+        }
+    }
+}

+ 65 - 0
TeamAAS-VM/Core/Lights/LightControllerBase.cs

@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TeamAAS_VP.Core.Lights
+{
+    /// <summary>
+    /// 光源控制器基类
+    /// </summary>
+    public abstract class LightControllerBase : ILightController
+    {
+        public int Id { get; }
+        public string Name { get; set; }    
+        public LightModel Model { get; }
+        public int ChannelCount { get; }
+        public bool IsConnected { get; protected set; }
+
+        protected List<ILightChannel> ChannelsInternal { get; }
+        public IReadOnlyList<ILightChannel> Channels => ChannelsInternal;
+
+        protected LightControllerBase(int id,int channelCount)
+        {
+            Id = id;
+            ChannelCount = channelCount;
+            ChannelsInternal = new List<ILightChannel>();
+            InitializeChannels();
+        }
+
+        private void InitializeChannels()
+        {
+            for (int i = 0; i < ChannelCount; i++)
+            {
+                ChannelsInternal.Add(CreateChannel(i));
+            }
+        }
+
+        protected abstract ILightChannel CreateChannel(int index);
+
+        public abstract Task<bool> ConnectAsync();
+        public abstract Task DisconnectAsync();
+        public abstract Task<bool> InitializeAsync();
+        public abstract Task<bool> TurnOnAllAsync();
+        public abstract Task<bool> TurnOffAllAsync();
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                foreach (var channel in ChannelsInternal)
+                {
+                    (channel as IDisposable)?.Dispose();
+                }
+                ChannelsInternal.Clear();
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+    }
+}

+ 18 - 0
TeamAAS-VM/Core/Lights/LightControllerEventArgs.cs

@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TeamAAS_VP.Core.Lights
+{
+    public class LightControllerEventArgs : EventArgs
+    {
+        public ILightController Controller { get; }
+
+        public LightControllerEventArgs(ILightController controller)
+        {
+            Controller = controller;
+        }
+    }
+}

+ 13 - 0
TeamAAS-VM/Core/Lights/LightModel.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TeamAAS_VP.Core.Lights
+{
+    public enum LightModel
+    {
+        KCS_KDC_12V60W_4T,
+    }
+}

+ 167 - 0
TeamAAS-VM/Core/Lights/SerialPortProtocol.cs

@@ -0,0 +1,167 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TeamAAS_VP.Enums;
+using TeamAAS_VP.Models;
+using TouchSocket.Core;
+using TouchSocket.SerialPorts;
+using TouchSocket.Sockets;
+
+namespace TeamAAS_VP.Core.Lights
+{
+    public class SerialPortProtocol : ICommunicationProtocol
+    {
+        private SerialPortClient _client;
+        private IWaitingClient<ISerialPortClient, IReceiverResult> _waitClient;
+        private readonly SerialPortConfig _config;
+
+        public bool IsConnected => _client?.Online == true;
+        public Terminator Terminator { get; set; } = Terminator.None;
+        public Encoding Encoding { get; set; } = Encoding.ASCII;
+
+        public event Action<object, bool> ConnectionChanged;
+        public event Action<object, string> DataReceived;
+        public event Action<object, string> DataSent;
+
+        public SerialPortProtocol(SerialPortConfig config)
+        {
+            _config = config;
+        }
+
+        public async Task<bool> ConnectAsync()
+        {
+            try
+            {
+                _client = new SerialPortClient();
+                var config = new TouchSocketConfig()
+                    .SetSerialPortOption(new SerialPortOption()
+                    {
+                        PortName = _config.PortName,
+                        BaudRate = _config.BaudRate,
+                        DataBits = _config.DataBits,
+                        Parity = _config.Parity,
+                        StopBits = _config.StopBits
+                    })
+                    .SetSerialDataHandlingAdapter(() => new PeriodPackageAdapter()
+                    {
+                        CacheTimeout = TimeSpan.FromMilliseconds(100)
+                    });
+
+                _client.Setup(config);
+
+                var result = await _client.TryConnectAsync();
+                if (result.IsSuccess)
+                {
+                    _waitClient = _client.CreateWaitingClient(new WaitingOptions());
+                    ConnectionChanged?.Invoke(this, true);
+                    return true;
+                }
+                ConnectionChanged?.Invoke(this, false);
+                return false;
+            }
+            catch
+            {
+                ConnectionChanged?.Invoke(this, false);
+                return false;
+            }
+        }
+
+        public Task DisconnectAsync()
+        {
+            ConnectionChanged?.Invoke(this, false);
+            _client?.Close();
+            return Task.CompletedTask;
+        }
+
+        public async Task<byte[]> SendAndReceiveAsync(byte[] data, int timeout = 5000)
+        {
+            if (_waitClient == null)
+                throw new InvalidOperationException("Not connected");
+
+            DataSent?.Invoke(this, Encoding.GetString(data));
+            var response = await _waitClient.SendThenReturnAsync(data, timeout);
+            DataReceived?.Invoke(this, Encoding.GetString(response));
+            return response;
+        }
+
+        public Task SendAsync(byte[] data)
+        {
+            if (_client == null)
+                throw new InvalidOperationException("Not connected");
+
+            DataSent?.Invoke(this, Encoding.GetString(data));
+            _client.Send(data);
+            return Task.CompletedTask;
+        }
+
+        public async Task<string> SendAndReceiveAsync(string data, int timeout = 5000)
+        {
+
+            if (_waitClient == null)
+                throw new InvalidOperationException("Not connected");
+
+            DataSent?.Invoke(this, data);
+            var response = await SendAndReceiveAsync(Encoding.GetBytes(data), timeout);
+            DataReceived?.Invoke(this, Encoding.GetString(response));
+            return Encoding.GetString(response);
+        }
+
+        public string SendAndReceive(string data, int timeout = 5000)
+        {
+            if (_waitClient == null)
+                throw new InvalidOperationException("Not connected");
+
+            DataSent?.Invoke(this, data);
+            var response = _waitClient.SendThenReturn(Encoding.GetBytes(data), timeout);
+            DataReceived?.Invoke(this, Encoding.GetString(response));
+            return Encoding.GetString(response);
+        }
+
+        public byte[] SendAndReceive(byte[] data, int timeout = 5000)
+        {
+            if (_waitClient == null)
+                throw new InvalidOperationException("Not connected");
+
+            DataSent?.Invoke(this, Encoding.GetString(data));
+            var response = _waitClient.SendThenReturn(data, timeout);
+            DataReceived?.Invoke(this, Encoding.GetString(response));
+            return response;
+        }
+
+        public void Send(string data)
+        {
+            if (_client == null)
+                throw new InvalidOperationException("Not connected");
+
+            DataSent?.Invoke(this, data);
+            _client.Send(Encoding.GetBytes(data));
+        }
+
+        public async Task SendAsync(string data)
+        {
+            if (_client == null)
+                throw new InvalidOperationException("Not connected");
+
+            DataSent?.Invoke(this, data);
+            await _client.SendAsync(Encoding.GetBytes(data));
+        }
+
+        public void Send(byte[] data)
+        {
+            if (_client == null)
+                throw new InvalidOperationException("Not connected");
+
+            DataSent?.Invoke(this, Encoding.GetString(data));
+            _client.Send(data);
+        }
+
+
+        public void Dispose()
+        {
+            _client?.Dispose();
+            _waitClient = null;
+        }
+    }
+}

+ 188 - 0
TeamAAS-VM/Core/Lights/TcpProtocol.cs

@@ -0,0 +1,188 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.UI.WebControls.WebParts;
+using TeamAAS_VP.Enums;
+using TeamAAS_VP.Models.Robot;
+using TouchSocket.Core;
+using TouchSocket.Sockets;
+
+namespace TeamAAS_VP.Core.Lights
+{
+    public class TcpProtocol : ICommunicationProtocol
+    {
+        private readonly string _host;
+        private readonly int _port;
+        private TcpClient _client;
+
+        public bool IsConnected => _client?.Online == true;
+        public IWaitingClient<ITcpClient, IReceiverResult> _waitClient { get; private set; }
+        public Terminator Terminator { get; set; } = Terminator.None;
+        public Encoding Encoding { get; set; } = Encoding.ASCII;
+
+        public event Action<object, bool> ConnectionChanged;
+        public event Action<object, string> DataReceived;
+        public event Action<object, string> DataSent;
+
+        public TcpProtocol(string host, int port, Terminator terminator = Terminator.None)
+        {
+            _host = host;
+            _port = port;
+            Terminator = terminator;
+        }
+
+        public async Task<bool> ConnectAsync()
+        {
+            try
+            {
+                _client = new TouchSocket.Sockets.TcpClient();
+                var config = new TouchSocketConfig();
+                config.SetRemoteIPHost(new IPHost(IPAddress.Parse(_host), _port));
+                config.ConfigurePlugins(a => { a.UseTcpReconnection(); });       ////如需永远尝试连接,tryCount设置为-1即可。
+                                                                                 ////设置结束符
+                if (Terminator == Terminator.None)
+                {
+                    config.SetTcpDataHandlingAdapter(() => { return new NormalDataHandlingAdapter(); });       ////亦或者省略\r\n,但此时调用方不能高速调用,会粘包
+                }
+                else if (Terminator == Terminator.CR)
+                {
+                    config.SetTcpDataHandlingAdapter(() => { return new TerminatorPackageAdapter("\r"); });       //命令行中使用\r结尾 
+                }
+                else if (Terminator == Terminator.LF)
+                {
+                    config.SetTcpDataHandlingAdapter(() => { return new TerminatorPackageAdapter("\n"); });       //命令行中使用\n结尾 
+                }
+                else if (Terminator == Terminator.CRLF)
+                {
+                    config.SetTcpDataHandlingAdapter(() => { return new TerminatorPackageAdapter("\r\n"); });       //命令行中使用\r\n结尾 
+                }
+                //载入配置
+                _client.Setup(config);
+                ////调用CreateWaitingClient获取到IWaitingClient的对象。
+                _waitClient = _client.CreateWaitingClient(new WaitingOptions()
+                {
+                    FilterFunc = response => //设置用于筛选的fun委托,当返回为true时,才会响应返回
+                    {
+                        return true;
+
+                        //if (response.Data.Length == 1)
+                        //{
+                        //    return true;
+                        //}
+                        //return false;
+                    }
+                });
+                var result = await _client.TryConnectAsync();
+                if (result.IsSuccess)
+                {
+                    ConnectionChanged?.Invoke(this, true);
+                    return true;
+                }
+                ConnectionChanged?.Invoke(this, false);
+                return false;
+            }
+            catch
+            {
+                return false;
+            }
+        }
+
+        public Task DisconnectAsync()
+        {
+            ConnectionChanged?.Invoke(this, false);
+            _client?.Close();
+            return Task.CompletedTask;
+        }
+
+        public async Task<byte[]> SendAndReceiveAsync(byte[] data, int timeout = 5000)
+        {
+            if (_waitClient == null)
+                throw new InvalidOperationException("Not connected");
+
+            DataSent?.Invoke(this, Encoding.GetString(data));
+            var response = await _waitClient.SendThenReturnAsync(data, timeout);
+            DataReceived?.Invoke(this, Encoding.GetString(response));
+            return response;
+        }
+
+        public Task SendAsync(byte[] data)
+        {
+            if (_client == null)
+                throw new InvalidOperationException("Not connected");
+
+            DataSent?.Invoke(this, Encoding.GetString(data));
+            _client.Send(data);
+            return Task.CompletedTask;
+        }
+
+        public async Task<string> SendAndReceiveAsync(string data, int timeout = 5000)
+        {
+
+            if (_waitClient == null)
+                throw new InvalidOperationException("Not connected");
+
+            DataSent?.Invoke(this, data);
+            var response = await SendAndReceiveAsync(Encoding.GetBytes(data), timeout);
+            DataReceived?.Invoke(this, Encoding.GetString(response));
+            return Encoding.GetString(response);
+        }
+
+        public string SendAndReceive(string data, int timeout = 5000)
+        {
+            if (_waitClient == null)
+                throw new InvalidOperationException("Not connected");
+
+            DataSent?.Invoke(this, data);
+            var response = _waitClient.SendThenReturn(Encoding.GetBytes(data), timeout);
+            DataReceived?.Invoke(this, Encoding.GetString(response));
+            return Encoding.GetString(response);
+        }
+
+        public byte[] SendAndReceive(byte[] data, int timeout = 5000)
+        {
+            if (_waitClient == null)
+                throw new InvalidOperationException("Not connected");
+
+            DataSent?.Invoke(this, Encoding.GetString(data));
+            var response = _waitClient.SendThenReturn(data, timeout);
+            DataReceived?.Invoke(this, Encoding.GetString(response));
+            return response;
+        }
+
+        public void Send(string data)
+        {
+            if (_client == null)
+                throw new InvalidOperationException("Not connected");
+
+            DataSent?.Invoke(this, data);
+            _client.Send(Encoding.GetBytes(data));
+        }
+
+        public async Task SendAsync(string data)
+        {
+            if (_client == null)
+                throw new InvalidOperationException("Not connected");
+
+            DataSent?.Invoke(this, data);
+            await _client.SendAsync(Encoding.GetBytes(data));
+        }
+
+        public void Send(byte[] data)
+        {
+            if (_client == null)
+                throw new InvalidOperationException("Not connected");
+
+            DataSent?.Invoke(this, Encoding.GetString(data));
+            _client.Send(data);
+        }
+
+        public void Dispose()
+        {
+            _client?.Dispose();
+        }
+    }
+}

+ 34 - 0
TeamAAS-VM/Interfaces/ILightManagerService.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TeamAAS_VP.Core.Lights;
+using TeamAAS_VP.Models;
+using TeamAAS_VP.Models.Lights;
+
+namespace TeamAAS_VP.Interfaces
+{
+    public interface ILightManagerService
+    {
+        event EventHandler<LightControllerEventArgs> ControllerAdded;
+        event EventHandler<LightControllerEventArgs> ControllerRemoved;
+        event EventHandler<LightChannelEventArgs> ChannelStatusChanged;
+
+        IReadOnlyDictionary<int, ILightController> Controllers { get; }
+        IReadOnlyDictionary<int, ILightChannel> GlobalChannels { get; }
+
+        Task<ILightController> RegisterControllerAsync(int id, LightControllerConfig configuration);
+        Task<bool> UnregisterControllerAsync(int controllerId);
+
+        Task<bool> SetGlobalChannelBrightnessAsync(int globalChannelId, int brightness);
+        Task<bool> TurnOnGlobalChannelAsync(int globalChannelId);
+        Task<bool> TurnOffGlobalChannelAsync(int globalChannelId);
+
+        Task<bool> ConnectAllAsync();
+        Task<bool> DisconnectAllAsync();
+
+        Task SaveConfigurationAsync(string filePath);
+        Task LoadConfigurationAsync(string filePath);
+    }
+}

+ 73 - 0
TeamAAS-VM/Models/Lights/ChannelConfig.cs

@@ -0,0 +1,73 @@
+using Prism.Mvvm;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TeamAAS_VP.Models.Lights
+{
+    public class ChannelConfig: BindableBase
+    {
+        private string _Name;
+        /// <summary>
+        /// 通道名称
+        /// </summary>
+        public string Name
+        {
+            get { return _Name; }
+            set { SetProperty(ref _Name, value); }
+        }
+
+        private string _Description;
+        /// <summary>
+        /// 通道描述
+        /// </summary>
+        public string Description
+        {
+            get { return _Description; }
+            set { SetProperty(ref _Description, value); }
+        }
+
+        private int _LocalIndex;
+        /// <summary>
+        /// 控制器内的通道索引
+        /// </summary>
+        public int LocalIndex
+        {
+            get { return _LocalIndex; }
+            set { SetProperty(ref _LocalIndex, value); }
+        }
+
+        private int _GlobalIndex;
+        /// <summary>
+        /// 系统中的全局通道索引
+        /// </summary>
+        public int GlobalIndex
+        {
+            get { return _GlobalIndex; }
+            set { SetProperty(ref _GlobalIndex, value); }
+        }
+
+        private int _DefaultBrightness=100;
+        public int DefaultBrightness
+        {
+            get { return _DefaultBrightness; }
+            set { SetProperty(ref _DefaultBrightness, value); }
+        }
+
+        public ChannelConfig()
+        {
+
+        }
+
+        public ChannelConfig(int localIndex, int globalIndex, string name = "", string description = "", int defaultBrightness=100)
+        {
+            LocalIndex = localIndex;
+            GlobalIndex = globalIndex;
+            Name = name;
+            Description = description;
+            DefaultBrightness = defaultBrightness;
+        }
+    }
+}

+ 129 - 0
TeamAAS-VM/Models/Lights/LightControllerConfig.cs

@@ -0,0 +1,129 @@
+using Prism.Mvvm;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TeamAAS_VP.Core.Lights;
+using TeamAAS_VP.Enums;
+
+namespace TeamAAS_VP.Models.Lights
+{
+    public class LightControllerConfig: BindableBase
+    {
+        private int _Id;
+        public int Id
+        {
+            get { return _Id; }
+            set { SetProperty(ref _Id, value); }
+        }
+        private string _Name;
+        public string Name
+        {
+            get { return _Name; }
+            set { SetProperty(ref _Name, value); }
+        }
+
+        private int _ChannelCount;
+        public int ChannelCount
+        {
+            get { return _ChannelCount; }
+            set { SetProperty(ref _ChannelCount, value); }
+        }
+
+        private LightModel _LightModel;
+        public LightModel LightModel
+        {
+            get { return _LightModel; }
+            set { SetProperty(ref _LightModel, value);
+                switch (value)
+                {
+                    case LightModel.KCS_KDC_12V60W_4T:
+                        ChannelCount=4;
+                        TcpConfig = null;
+                        if (SerialPortConfig==null)
+                        {
+                            SerialPortConfig = new SerialPortConfig();
+                        }
+                        ChannelConfigs = new ObservableCollection<ChannelConfig>();
+                        for (int i = 0; i < ChannelCount; i++)
+                        {
+                            ChannelConfigs.Add(new ChannelConfig()
+                            {
+                                LocalIndex = i,
+                                GlobalIndex = i,
+                                Name = $"Channel {i + 1}",
+                                Description = $"Description for Channel {i + 1}",
+                                DefaultBrightness = 100
+                            });
+                        }
+                        break;
+                    default:
+                        ChannelConfigs = new ObservableCollection<ChannelConfig>();
+                        SerialPortConfig = null;
+                        TcpConfig = null;
+                        break;
+                }
+            }
+        }
+
+        private ObservableCollection<ChannelConfig> _ChannelConfigs;
+        public ObservableCollection<ChannelConfig> ChannelConfigs
+        {
+            get { return _ChannelConfigs; }
+            set { SetProperty(ref _ChannelConfigs, value); }
+        }
+
+        private TcpConfig _TcpConfig;
+        public TcpConfig TcpConfig
+        {
+            get { return _TcpConfig; }
+            set { SetProperty(ref _TcpConfig, value); }
+        }
+
+        private SerialPortConfig _SerialPortConfig;
+        public SerialPortConfig SerialPortConfig
+        {
+            get { return _SerialPortConfig; }
+            set { SetProperty(ref _SerialPortConfig, value); }
+        }
+
+        public LightControllerConfig()
+        {
+            ChannelConfigs = new ObservableCollection<ChannelConfig>();
+        }
+
+        public LightControllerConfig(int id, string name, LightModel model)
+        {
+            Id = id;
+            Name = name;
+            LightModel = model;
+            switch (LightModel)
+            {
+                case LightModel.KCS_KDC_12V60W_4T:
+                    ChannelCount = 4;
+                    TcpConfig = null;
+                    SerialPortConfig = new SerialPortConfig();
+                    ChannelConfigs = new ObservableCollection<ChannelConfig>();
+                    for (int i = 0; i < ChannelCount; i++)
+                    {
+                        ChannelConfigs.Add(new ChannelConfig()
+                        {
+                            LocalIndex = i,
+                            GlobalIndex = i,
+                            Name = $"Channel {i + 1}",
+                            Description = $"Description for Channel {i + 1}",
+                            DefaultBrightness = 100
+                        });
+                    }
+                    break;
+                default:
+                    ChannelConfigs = new ObservableCollection<ChannelConfig>();
+                    SerialPortConfig = null;
+                    TcpConfig = null;
+                    break;
+            }
+        }
+    }
+}

+ 44 - 0
TeamAAS-VM/Models/SerialPortConfig.cs

@@ -0,0 +1,44 @@
+using Prism.Mvvm;
+using System;
+using System.Collections.Generic;
+using System.IO.Ports;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TeamAAS_VP.Models
+{
+    public class SerialPortConfig: BindableBase
+    {
+        private string _PortName = "COM1";
+        public string PortName
+        {
+            get { return _PortName; }
+            set { SetProperty(ref _PortName, value); }
+        }
+        private int _BaudRate = 9600;
+        public int BaudRate
+        {
+            get { return _BaudRate; }
+            set { SetProperty(ref _BaudRate, value); }
+        }
+        private Parity _Parity = Parity.None;
+        public Parity Parity
+        {
+            get { return _Parity; }
+            set { SetProperty(ref _Parity, value); }
+        }
+        private StopBits _StopBits= StopBits.One;
+        public StopBits StopBits
+        {
+            get { return _StopBits; }
+            set { SetProperty(ref _StopBits, value); }
+        }
+        private int _DataBits=8;
+        public int DataBits
+        {
+            get { return _DataBits; }
+            set { SetProperty(ref _DataBits, value); }
+        }
+    }
+}

+ 25 - 0
TeamAAS-VM/Models/TcpConfig.cs

@@ -0,0 +1,25 @@
+using Prism.Mvvm;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TeamAAS_VP.Models
+{
+    public class TcpConfig : BindableBase
+    {
+        private string _IP = "127.0.0.1";
+        public string IP
+        {
+            get { return _IP; }
+            set { SetProperty(ref _IP, value); }
+        }
+        private int _Port = 5000;
+        public int Port
+        {
+            get { return _Port; }
+            set { SetProperty(ref _Port, value); }
+        }
+    }
+}

+ 182 - 0
TeamAAS-VM/Services/LightManagerService.cs

@@ -0,0 +1,182 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TeamAAS_VP.Core.Lights;
+using TeamAAS_VP.Interfaces;
+using TeamAAS_VP.Models;
+using TeamAAS_VP.Models.Lights;
+
+namespace TeamAAS_VP.Services
+{
+    public class LightManagerService : ILightManagerService
+    {
+        private readonly Dictionary<int, ILightController> _controllers;
+        private readonly Dictionary<int, ILightChannel> _globalChannels;
+        private readonly Dictionary<LightModel, Func<int, TeamAAS_VP.Models.Lights.LightControllerConfig, ILightController>> _controllerFactories;
+        private int _nextGlobalChannelId = 0;
+
+        public IReadOnlyDictionary<int, ILightController> Controllers => _controllers;
+        public IReadOnlyDictionary<int, ILightChannel> GlobalChannels => _globalChannels;
+
+        public event EventHandler<LightControllerEventArgs> ControllerAdded;
+        public event EventHandler<LightControllerEventArgs> ControllerRemoved;
+        public event EventHandler<LightChannelEventArgs> ChannelStatusChanged;
+
+        public LightManagerService()
+        {
+            _controllers = new Dictionary<int, ILightController>();
+            _globalChannels = new Dictionary<int, ILightChannel>();
+            _controllerFactories = new Dictionary<LightModel, Func<int, TeamAAS_VP.Models.Lights.LightControllerConfig, ILightController>>();
+
+            RegisterDefaultFactories();
+        }
+
+        private void RegisterDefaultFactories()
+        {
+            // 注册KCS控制器工厂
+            RegisterControllerFactory(LightModel.KCS_KDC_12V60W_4T, (id, config) =>
+            {
+                if (config == null) throw new ArgumentNullException(nameof(config));
+                var protocol = new SerialPortProtocol(config.SerialPortConfig);
+                return new KCSLightController(id, protocol, config.ChannelCount);
+            });
+        }
+
+        public void RegisterControllerFactory(LightModel model, Func<int, TeamAAS_VP.Models.Lights.LightControllerConfig, ILightController> factory)
+        {
+            _controllerFactories[model] = factory;
+        }
+
+        public async Task<ILightController> RegisterControllerAsync(int id, TeamAAS_VP.Models.Lights.LightControllerConfig configuration)
+        {
+            if (configuration == null) throw new ArgumentNullException(nameof(configuration));
+
+            if (_controllers.ContainsKey(id))
+                throw new ArgumentException($"Controller with id '{id}' already exists");
+
+            if (!_controllerFactories.TryGetValue(configuration.LightModel, out var factory))
+                throw new ArgumentException($"No factory registered for model '{configuration.LightModel}'");
+
+            var controller = factory(id, configuration);
+            if (controller == null)
+                throw new InvalidOperationException("Factory returned null controller");
+
+            _controllers[id] = controller;
+
+            // 映射全局通道
+            for (int i = 0; i < controller.Channels.Count; i++)
+            {
+                var channelConfig = configuration.ChannelConfigs[i];
+                int globalIndex = channelConfig.GlobalIndex;
+
+                // 检查该全局ID是否已被占用
+                if (_globalChannels.ContainsKey(globalIndex))
+                {
+                    // 自动重新分配全局ID
+                    globalIndex = _nextGlobalChannelId++;
+                }
+                else if (globalIndex >= _nextGlobalChannelId)
+                {
+                    // 更新下一个可用ID
+                    _nextGlobalChannelId = globalIndex + 1;
+                }
+
+                // 更新配置中的GlobalIndex(如果需要)
+                if (channelConfig.GlobalIndex != globalIndex)
+                {
+                    channelConfig.GlobalIndex = globalIndex;
+                }
+
+                // 建立映射
+                _globalChannels[globalIndex] = controller.Channels[i];
+            }
+
+            ControllerAdded?.Invoke(this, new LightControllerEventArgs(controller));
+            return controller;
+        }
+
+        public async Task<bool> UnregisterControllerAsync(int controllerId)
+        {
+            if (!_controllers.TryGetValue(controllerId, out var controller))
+                return false;
+
+            // 移除全局通道映射
+            var channelsToRemove = _globalChannels
+                .Where(kvp => controller.Channels.Contains(kvp.Value))
+                .Select(kvp => kvp.Key)
+                .ToList();
+
+            foreach (var channelId in channelsToRemove)
+            {
+                _globalChannels.Remove(channelId);
+            }
+
+            _controllers.Remove(controllerId);
+            await controller.DisconnectAsync();
+            controller.Dispose();
+
+            ControllerRemoved?.Invoke(this, new LightControllerEventArgs(controller));
+            return true;
+        }
+
+        public async Task<bool> SetGlobalChannelBrightnessAsync(int globalChannelId, int brightness)
+        {
+            if (!_globalChannels.TryGetValue(globalChannelId, out var channel))
+                return false;
+
+            var result = await channel.SetBrightnessAsync(brightness);
+            ChannelStatusChanged?.Invoke(this, new LightChannelEventArgs(channel, channel.IsOn, channel.Brightness));
+            return result;
+        }
+
+        public async Task<bool> TurnOnGlobalChannelAsync(int globalChannelId)
+        {
+            if (!_globalChannels.TryGetValue(globalChannelId, out var channel))
+                return false;
+
+            var result = await channel.TurnOnAsync();
+            ChannelStatusChanged?.Invoke(this, new LightChannelEventArgs(channel, channel.IsOn, channel.Brightness));
+            return result;
+        }
+
+        public async Task<bool> TurnOffGlobalChannelAsync(int globalChannelId)
+        {
+            if (!_globalChannels.TryGetValue(globalChannelId, out var channel))
+                return false;
+
+            var result = await channel.TurnOffAsync();
+            ChannelStatusChanged?.Invoke(this, new LightChannelEventArgs(channel, channel.IsOn, channel.Brightness));
+            return result;
+        }
+
+        public async Task<bool> ConnectAllAsync()
+        {
+            var tasks = _controllers.Values.Select(c => c.ConnectAsync());
+            var results = await Task.WhenAll(tasks);
+            return results.All(r => r);
+        }
+
+        public async Task<bool> DisconnectAllAsync()
+        {
+            foreach (var controller in _controllers.Values)
+            {
+                await controller.DisconnectAsync();
+            }
+            return true;
+        }
+
+        public Task SaveConfigurationAsync(string filePath)
+        {
+            // 实现配置保存
+            return Task.CompletedTask;
+        }
+
+        public Task LoadConfigurationAsync(string filePath)
+        {
+            // 实现配置加载
+            return Task.CompletedTask;
+        }
+    }
+}

+ 23 - 0
TeamAAS-VM/TeamAAS-VP.csproj

@@ -356,6 +356,9 @@
     <Reference Include="System.IO.Pipelines, Version=9.0.0.9, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
       <HintPath>..\packages\System.IO.Pipelines.9.0.9\lib\net462\System.IO.Pipelines.dll</HintPath>
     </Reference>
+    <Reference Include="System.IO.Ports, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.IO.Ports.8.0.0\lib\net462\System.IO.Ports.dll</HintPath>
+    </Reference>
     <Reference Include="System.Management" />
     <Reference Include="System.Memory, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
       <HintPath>..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll</HintPath>
@@ -424,6 +427,9 @@
     <Reference Include="TouchSocket.Core, Version=3.1.17.0, Culture=neutral, PublicKeyToken=d6c415a2f58eda72, processorArchitecture=MSIL">
       <HintPath>..\packages\TouchSocket.Core.3.1.17\lib\net472\TouchSocket.Core.dll</HintPath>
     </Reference>
+    <Reference Include="TouchSocket.SerialPorts, Version=3.1.17.0, Culture=neutral, PublicKeyToken=b0dfdf1c6b51c716, processorArchitecture=MSIL">
+      <HintPath>..\packages\TouchSocket.SerialPorts.3.1.17\lib\net472\TouchSocket.SerialPorts.dll</HintPath>
+    </Reference>
     <Reference Include="Unity.Abstractions, Version=5.11.7.0, Culture=neutral, PublicKeyToken=489b6accfaf20ef0, processorArchitecture=MSIL">
       <HintPath>..\packages\Unity.Abstractions.5.11.7\lib\net48\Unity.Abstractions.dll</HintPath>
     </Reference>
@@ -551,6 +557,17 @@
     <Compile Include="Core\BraceFoldingStrategy.cs" />
     <Compile Include="Core\Cameras\CameraBase.cs" />
     <Compile Include="Core\FocusAnalysisEngine.cs" />
+    <Compile Include="Core\Lights\ICommunicationProtocol.cs" />
+    <Compile Include="Core\Lights\ILightChannel.cs" />
+    <Compile Include="Core\Lights\ILightController.cs" />
+    <Compile Include="Core\Lights\KCSLightChannel.cs" />
+    <Compile Include="Core\Lights\KCSLightController.cs" />
+    <Compile Include="Core\Lights\LightChannelEventArgs.cs" />
+    <Compile Include="Core\Lights\LightControllerBase.cs" />
+    <Compile Include="Core\Lights\LightControllerEventArgs.cs" />
+    <Compile Include="Core\Lights\LightModel.cs" />
+    <Compile Include="Core\Lights\SerialPortProtocol.cs" />
+    <Compile Include="Core\Lights\TcpProtocol.cs" />
     <Compile Include="Core\Robots\Axis.cs" />
     <Compile Include="Core\Robots\XYZU_Robot.cs" />
     <Compile Include="Data\DatabaseInitializer.cs" />
@@ -562,6 +579,7 @@
     <Compile Include="Interfaces\ICameraService.cs" />
     <Compile Include="Interfaces\IConfigService.cs" />
     <Compile Include="Interfaces\IDatabaseInitializer.cs" />
+    <Compile Include="Interfaces\ILightManagerService.cs" />
     <Compile Include="Interfaces\IPlcService.cs" />
     <Compile Include="Interfaces\IProductService.cs" />
     <Compile Include="Interfaces\IRemoteCommandService.cs" />
@@ -572,11 +590,15 @@
     <Compile Include="Models\AnalysisResult.cs" />
     <Compile Include="Models\CheckerboardResult.cs" />
     <Compile Include="Models\DataPoint.cs" />
+    <Compile Include="Models\Lights\ChannelConfig.cs" />
+    <Compile Include="Models\Lights\LightControllerConfig.cs" />
     <Compile Include="Models\ProductionRecord.cs" />
     <Compile Include="Models\ProductionStatQuery.cs" />
     <Compile Include="Models\ProductionStatResult.cs" />
     <Compile Include="Models\Product\ProcedureUserDefineParam.cs" />
+    <Compile Include="Models\SerialPortConfig.cs" />
     <Compile Include="Models\SystemConfiguration.cs" />
+    <Compile Include="Models\TcpConfig.cs" />
     <Compile Include="Models\UserLoginRecord.cs" />
     <Compile Include="Services\CalibrationService.cs" />
     <Compile Include="Services\CameraService.cs" />
@@ -590,6 +612,7 @@
     <Compile Include="Core\IniConfigHelper.cs" />
     <Compile Include="Core\PLCs\OpcUaClientPLC.cs" />
     <Compile Include="Services\ConfigService.cs" />
+    <Compile Include="Services\LightManagerService.cs" />
     <Compile Include="Services\PlcService.cs" />
     <Compile Include="Core\Robots\FanucRobot.cs" />
     <Compile Include="Core\ImageHelper.cs" />

+ 2 - 0
TeamAAS-VM/packages.config

@@ -69,6 +69,7 @@
   <package id="System.Drawing.Common" version="9.0.9" targetFramework="net48" />
   <package id="System.Formats.Asn1" version="9.0.9" targetFramework="net48" />
   <package id="System.IO.Pipelines" version="9.0.9" targetFramework="net48" />
+  <package id="System.IO.Ports" version="8.0.0" targetFramework="net48" />
   <package id="System.Memory" version="4.6.3" targetFramework="net48" />
   <package id="System.Numerics.Vectors" version="4.6.1" targetFramework="net48" />
   <package id="System.Reflection.Metadata" version="9.0.9" targetFramework="net48" />
@@ -85,6 +86,7 @@
   <package id="System.ValueTuple" version="4.6.1" targetFramework="net48" />
   <package id="TouchSocket" version="3.1.17" targetFramework="net48" />
   <package id="TouchSocket.Core" version="3.1.17" targetFramework="net48" />
+  <package id="TouchSocket.SerialPorts" version="3.1.17" targetFramework="net48" />
   <package id="Unity.Abstractions" version="5.11.7" targetFramework="net48" />
   <package id="Unity.Container" version="5.11.11" targetFramework="net48" />
   <package id="ValueConverters" version="3.1.22" targetFramework="net48" />