DropsBrowse Pastes
Login with GitHub

1

June 30th, 2025Views: 15(1 unique)C#
using ModelContextProtocol.Server;
using Plugin;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Microsoft.Extensions.Logging;
using IoTGateway.Model;
using MQTTnet.Protocol;
using MQTTnet;
using Newtonsoft.Json;
using System.Threading.Tasks;
using System;
using MQTTnet.Extensions.ManagedClient;
using PluginInterface;

namespace IoTGateway.MCP
{
    /// <summary>
    /// 操作结果响应类
    /// </summary>
    public class OperationResult
    {
        /// <summary>
        /// 操作是否成功
        /// </summary>
        public bool Success { get; set; }

        /// <summary>
        /// 详细消息
        /// </summary>
        public string Message { get; set; }

        /// <summary>
        /// 创建成功的响应
        /// </summary>
        /// <param name="message">成功消息</param>
        /// <returns>操作结果</returns>
        public static OperationResult CreateSuccess(string message)
        {
            return new OperationResult { Success = true, Message = message };
        }

        /// <summary>
        /// 创建失败的响应
        /// </summary>
        /// <param name="message">错误消息</param>
        /// <returns>操作结果</returns>
        public static OperationResult CreateError(string message)
        {
            return new OperationResult { Success = false, Message = message };
        }
    }

    public class VariableInfo
    {
        public object Value { get; set; }
        public bool IsWritable { get; set; }
        public string ProtectType { get; set; }
        public string Expressions { get; set; }
    }

    [McpServerToolType]
    public sealed class DeviceTool
    {
        private readonly ILogger _logger;
        private readonly DeviceService _deviceService;
        private readonly IManagedMqttClient _mqttClient;

        public DeviceTool(
            DeviceService deviceService,
            IManagedMqttClient mqttClient,
            ILogger<DeviceTool> logger)
        {
            _deviceService = deviceService;
            _mqttClient = mqttClient;
            _logger = logger;

            // 记录工具类被创建,用于调试
            _logger.LogInformation("DeviceTool created successfully");
        }

        // 设备列表项结构体
        public class DeviceListItem
        {
            public string Name { get; set; } // 设备实例名
            public bool IsTypeName { get; set; } = false; // 固定为 false,表明是实例名
            public string Type { get; set; } // 设备类型(驱动名)
            public string InstanceId { get; set; }
            public string NameHint { get; set; } = "这是设备的实际名称,用户提到的设备名称就是设备本身,请直接使用此名称,不要猜测它是别的什么"; // 提示大模型直接使用设备名称
        }

        /// <summary>
        /// Get the list of sub-devices with meta info.
        /// </summary>
        /// <returns></returns>
        [McpServerTool(Name = "DevicesList"), Description("Get a list of all available device instances.")]
        public IEnumerable<DeviceListItem> DevicesList()
        {
            _logger.LogInformation("DevicesList method called");
            var devices = _deviceService.DeviceThreads.Select(x => new DeviceListItem
            {
                Name = x.Device.DeviceName,
                IsTypeName = false, //给大模型看的,防止将设备名称被大模型误判为设备类型导致其他问题
                Type = x.Device.Driver?.DriverName ?? "Unknown",
                InstanceId = x.Device.ID.ToString(),
                NameHint = "这是设备的实际名称,用户提到的设备名称就是设备本身,请直接使用此名称,不要猜测它是别的什么"
            }).ToList();
            _logger.LogInformation($"DevicesList returned {devices.Count} devices: {string.Join(", ", devices.Select(d => d.Name))}");
            return devices;
        }


        /// <summary>
        /// Get the current connection status of the sub-device.
        /// </summary>
        /// <param name="deviceName"></param>
        /// <returns></returns>
        [McpServerTool, Description("Get the current connection status of the sub-device.")]
        public bool? GetDeviceStatus(
            [Description("name of device")] string deviceName
            )
        {
            _logger.LogInformation($"GetDeviceStatus method called for device: {deviceName}");
            var status = _deviceService.DeviceThreads.FirstOrDefault(x => x.Device.DeviceName == deviceName)?.Driver.IsConnected;
            _logger.LogInformation($"GetDeviceStatus returned: {status}");
            return status;
        }



        /// <summary>
        /// Get the current value of a variable of a sub-device.
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        [McpServerTool, Description("Get all variables for a specific device.")]
        public Dictionary<string, VariableInfo> GetDeviceVariables(
            [Description("name of device")] string deviceName
            )
        {
            _logger.LogInformation($"GetDeviceVariables method called for device: {deviceName}");
            var device = _deviceService.DeviceThreads.FirstOrDefault(x => x.Device.DeviceName == deviceName)?.Device;
            if (device == null)
                return new Dictionary<string, VariableInfo>();

            var variables = device.DeviceVariables.ToDictionary(
                x => x.Name,
                x => new VariableInfo
                {
                    Value = x.CookedValue,
                    IsWritable = x.ProtectType != ProtectTypeEnum.ReadOnly,
                    ProtectType = x.ProtectType.ToString(),
                    Expressions = x.Expressions
                });

            _logger.LogInformation($"GetDeviceVariables returned {variables?.Count ?? 0} variables");
            return variables;
        }



        /// <summary>
        /// Get the current value of a variable of a sub-device.
        /// </summary>
        /// <param name="deviceName"></param>
        /// <param name="variableName"></param>
        /// <returns></returns>
        [McpServerTool, Description("Gets a single variable's details from a device.")]
        public VariableInfo GetDeviceVariable(
            [Description("name of device")] string deviceName,
            [Description("name of variable")] string variableName)
        {
            _logger.LogInformation($"GetDeviceVariable method called for device: {deviceName}, variable: {variableName}");
            var variable = _deviceService.DeviceThreads.FirstOrDefault(x => x.Device.DeviceName == deviceName)?.Device
                .DeviceVariables.FirstOrDefault(x => x.Name == variableName);

            if (variable == null)
                return null;

            var result = new VariableInfo
            {
                Value = variable.CookedValue,
                IsWritable = variable.ProtectType != ProtectTypeEnum.ReadOnly,
                ProtectType = variable.ProtectType.ToString(),
                Expressions = variable.Expressions
            };

            _logger.LogInformation($"GetDeviceVariable returned: {JsonConvert.SerializeObject(result)}");
            return result;
        }



        /// <summary>
        /// Set the value of a variable of a sub-device. This function sends the raw value directly.
        /// </summary>
        /// <param name="deviceName">设备名称</param>
        /// <param name="variableName">变量名称</param>
        /// <param name="value">The raw value to set. The system does not apply any expressions to this value before sending.</param>
        /// <returns>设置是否成功及详细信息</returns>
        [McpServerTool, Description("Set the value of a variable of a sub-device.")]
        public async Task<OperationResult> SetDeviceVariable(
            [Description("name of device")] string deviceName,
            [Description("name of variable")] string variableName,
            [Description("The raw value to set")] object value)
        {
            _logger.LogInformation($"SetDeviceVariable method called for device: {deviceName}, variable: {variableName}, value: {value}");
            try
            {
                var deviceThread = _deviceService.DeviceThreads.FirstOrDefault(x => x.Device.DeviceName == deviceName);
                if (deviceThread == null)
                {
                    var errorResult = OperationResult.CreateError($"Device '{deviceName}' not found");
                    _logger.LogWarning($"SetDeviceVariable failed: {errorResult.Message}");
                    return errorResult;
                }

                var deviceVariable = deviceThread.Device.DeviceVariables.FirstOrDefault(x => x.Name == variableName);
                if (deviceVariable == null || deviceVariable.ProtectType == ProtectTypeEnum.ReadOnly)
                {
                    var errorResult = OperationResult.CreateError($"Variable '{variableName}' not found or is read-only");
                    _logger.LogWarning($"SetDeviceVariable failed: {errorResult.Message}");
                    return errorResult;
                }

                // 创建RPC请求
                var rpcRequest = new RpcRequest
                {
                    RequestId = Guid.NewGuid().ToString(),
                    DeviceName = deviceName,
                    Method = "write",
                    Params = new Dictionary<string, object> { { variableName, value } }
                };

                // 触发RPC请求执行
                deviceThread.MyMqttClient_OnExcRpc(this, rpcRequest);

                // 等待一小段时间让RPC请求处理
                await Task.Delay(100);

                var successResult = OperationResult.CreateSuccess($"Successfully set {deviceName}.{variableName} = {value}");
                _logger.LogInformation($"SetDeviceVariable succeeded: {successResult.Message}");
                return successResult;
            }
            catch (Exception ex)
            {
                var errorResult = OperationResult.CreateError($"Failed to set variable: {ex.Message}");
                _logger.LogError(ex, $"SetDeviceVariable failed: {errorResult.Message}");
                return errorResult;
            }
        }

        /// <summary>
        /// Set multiple variable values of a sub-device at once.
        /// </summary>
        /// <param name="deviceName">设备名称</param>
        /// <param name="variables">变量名和值的字典</param>
        /// <returns>设置是否成功及详细信息</returns>
        [McpServerTool, Description("Set multiple variable values of a sub-device at once.")]
        public async Task<OperationResult> SetDeviceVariables(
            [Description("name of device")] string deviceName,
            [Description("dictionary of variable names and values")] Dictionary<string, object> variables)
        {
            _logger.LogInformation($"SetDeviceVariables method called for device: {deviceName}, variables: {JsonConvert.SerializeObject(variables)}");
            try
            {
                var deviceThread = _deviceService.DeviceThreads.FirstOrDefault(x => x.Device.DeviceName == deviceName);
                if (deviceThread == null)
                {
                    var errorResult = OperationResult.CreateError($"Device '{deviceName}' not found");
                    _logger.LogWarning($"SetDeviceVariables failed: {errorResult.Message}");
                    return errorResult;
                }

                // 过滤只保留可写的变量
                var writeParams = new Dictionary<string, object>();
                var skippedVariables = new List<string>();
                foreach (var variable in variables)
                {
                    var deviceVariable = deviceThread.Device.DeviceVariables.FirstOrDefault(x => x.Name == variable.Key);
                    if (deviceVariable != null && deviceVariable.ProtectType != ProtectTypeEnum.ReadOnly)
                    {
                        writeParams.Add(variable.Key, variable.Value);
                    }
                    else
                    {
                        skippedVariables.Add(variable.Key);
                    }
                }

                if (writeParams.Count == 0)
                {
                    var errorResult = OperationResult.CreateError($"No writable variables found among: {string.Join(", ", variables.Keys)}");
                    _logger.LogWarning($"SetDeviceVariables failed: {errorResult.Message}");
                    return errorResult;
                }

                // 创建RPC请求
                var rpcRequest = new RpcRequest
                {
                    RequestId = Guid.NewGuid().ToString(),
                    DeviceName = deviceName,
                    Method = "write",
                    Params = writeParams
                };

                // 触发RPC请求执行
                deviceThread.MyMqttClient_OnExcRpc(this, rpcRequest);

                // 等待一小段时间让RPC请求处理
                await Task.Delay(100);

                var successMessage = $"Successfully set {writeParams.Count} variables for device {deviceName}: {string.Join(", ", writeParams.Keys)}";
                if (skippedVariables.Count > 0)
                {
                    successMessage += $". Skipped {skippedVariables.Count} read-only or non-existent variables: {string.Join(", ", skippedVariables)}";
                }

                var successResult = OperationResult.CreateSuccess(successMessage);
                _logger.LogInformation($"SetDeviceVariables succeeded: {successResult.Message}");
                return successResult;
            }
            catch (Exception ex)
            {
                var errorResult = OperationResult.CreateError($"Failed to set variables: {ex.Message}");
                _logger.LogError(ex, $"SetDeviceVariables failed: {errorResult.Message}");
                return errorResult;
            }
        }
    }
}