From 206eaad42dfa65befa2657fa8bae58763952c351 Mon Sep 17 00:00:00 2001 From: g82tt Date: Sun, 17 May 2026 00:23:18 +0800 Subject: [PATCH] =?UTF-8?q?"=E7=AC=AC7=E5=A4=A9=EF=BC=9AMC4.0=E9=80=82?= =?UTF-8?q?=E9=85=8D=E5=99=A8=EF=BC=88=E5=AF=B9=E8=B1=A1=E6=A0=91+?= =?UTF-8?q?=E5=AE=9E=E6=97=B6=E6=95=B0=E6=8D=AE+=E5=91=8A=E8=AD=A6?= =?UTF-8?q?=EF=BC=89"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gateway/IntegrationGateway.sln | 15 ++ .../IntegrationGateway.Adapters.MC4.csproj | 13 + .../Mc4Adapter.cs | 249 ++++++++++++++++++ .../Mc4AuthHelper.cs | 43 +++ .../IntegrationGateway.Host.csproj | 1 + .../src/IntegrationGateway.Host/Program.cs | 12 + .../IntegrationGateway.Host/appsettings.json | 3 + 7 files changed, 336 insertions(+) create mode 100644 gateway/src/IntegrationGateway.Adapters.MC4/IntegrationGateway.Adapters.MC4.csproj create mode 100644 gateway/src/IntegrationGateway.Adapters.MC4/Mc4Adapter.cs create mode 100644 gateway/src/IntegrationGateway.Adapters.MC4/Mc4AuthHelper.cs diff --git a/gateway/IntegrationGateway.sln b/gateway/IntegrationGateway.sln index 59a7e01..0775afd 100644 --- a/gateway/IntegrationGateway.sln +++ b/gateway/IntegrationGateway.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationGateway.Core", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationGateway.Adapters.Owl", "src\IntegrationGateway.Adapters.Owl\IntegrationGateway.Adapters.Owl.csproj", "{8B0BD376-E1C5-4B62-AEC9-E2F4397C991E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationGateway.Adapters.MC4", "src\IntegrationGateway.Adapters.MC4\IntegrationGateway.Adapters.MC4.csproj", "{6C64C3EB-58ED-45A3-B86D-0B464081BFA0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -57,6 +59,18 @@ Global {8B0BD376-E1C5-4B62-AEC9-E2F4397C991E}.Release|x64.Build.0 = Release|Any CPU {8B0BD376-E1C5-4B62-AEC9-E2F4397C991E}.Release|x86.ActiveCfg = Release|Any CPU {8B0BD376-E1C5-4B62-AEC9-E2F4397C991E}.Release|x86.Build.0 = Release|Any CPU + {6C64C3EB-58ED-45A3-B86D-0B464081BFA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C64C3EB-58ED-45A3-B86D-0B464081BFA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C64C3EB-58ED-45A3-B86D-0B464081BFA0}.Debug|x64.ActiveCfg = Debug|Any CPU + {6C64C3EB-58ED-45A3-B86D-0B464081BFA0}.Debug|x64.Build.0 = Debug|Any CPU + {6C64C3EB-58ED-45A3-B86D-0B464081BFA0}.Debug|x86.ActiveCfg = Debug|Any CPU + {6C64C3EB-58ED-45A3-B86D-0B464081BFA0}.Debug|x86.Build.0 = Debug|Any CPU + {6C64C3EB-58ED-45A3-B86D-0B464081BFA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C64C3EB-58ED-45A3-B86D-0B464081BFA0}.Release|Any CPU.Build.0 = Release|Any CPU + {6C64C3EB-58ED-45A3-B86D-0B464081BFA0}.Release|x64.ActiveCfg = Release|Any CPU + {6C64C3EB-58ED-45A3-B86D-0B464081BFA0}.Release|x64.Build.0 = Release|Any CPU + {6C64C3EB-58ED-45A3-B86D-0B464081BFA0}.Release|x86.ActiveCfg = Release|Any CPU + {6C64C3EB-58ED-45A3-B86D-0B464081BFA0}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -65,5 +79,6 @@ Global {387BD4FC-725B-4948-B413-F50BC6BD605D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} {2055B7A5-418F-456C-8642-A99A68282561} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} {8B0BD376-E1C5-4B62-AEC9-E2F4397C991E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {6C64C3EB-58ED-45A3-B86D-0B464081BFA0} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} EndGlobalSection EndGlobal diff --git a/gateway/src/IntegrationGateway.Adapters.MC4/IntegrationGateway.Adapters.MC4.csproj b/gateway/src/IntegrationGateway.Adapters.MC4/IntegrationGateway.Adapters.MC4.csproj new file mode 100644 index 0000000..e0c9f9c --- /dev/null +++ b/gateway/src/IntegrationGateway.Adapters.MC4/IntegrationGateway.Adapters.MC4.csproj @@ -0,0 +1,13 @@ + + + + + + + + net8.0 + enable + enable + + + diff --git a/gateway/src/IntegrationGateway.Adapters.MC4/Mc4Adapter.cs b/gateway/src/IntegrationGateway.Adapters.MC4/Mc4Adapter.cs new file mode 100644 index 0000000..cb5eeed --- /dev/null +++ b/gateway/src/IntegrationGateway.Adapters.MC4/Mc4Adapter.cs @@ -0,0 +1,249 @@ +using IntegrationGateway.Core.Abstractions; +using IntegrationGateway.Core.Infrastructure; +using IntegrationGateway.Core.Models; +using System.Text; +using System.Text.Json; + +namespace IntegrationGateway.Adapters.MC4; + +public class Mc4Adapter : IHasOwnDeviceTree, IHasPoints, IHasAlarms +{ + private readonly HttpClient _http; + private readonly Mc4AuthHelper _auth; + private readonly RateLimiter _limiter = new(2); + + public string AdapterCode { get; } + public string DisplayName => $"MC4 ({AdapterCode})"; + public AdapterCapabilities Capabilities => new() + { + HasObjectTree = true, HasPoints = true, HasAlarms = true, AcceptsControl = true + }; + + public Mc4Adapter(string adapterCode, HttpClient http, string baseUrl) + { + AdapterCode = adapterCode; + _http = http; + _auth = new Mc4AuthHelper(http, baseUrl); + } + + public async Task InitializeAsync() => await _auth.GetTokenAsync(); + + public async Task HealthCheckAsync() + { + try + { + var client = await _auth.GetAuthenticatedClientAsync(); + var resp = await client.PostAsync("/api/central/auth/conf/get", null); + return resp.IsSuccessStatusCode; + } + catch { return false; } + } + + // ─── IHasOwnDeviceTree ─── + public async Task> GetObjectTreeAsync() + { + await _limiter.WaitAsync(); + var client = await _auth.GetAuthenticatedClientAsync(); + var resp = await client.PostAsync("/api/central/object/tree", null); + resp.EnsureSuccessStatusCode(); + var json = await resp.Content.ReadAsStringAsync(); + var tree = JsonSerializer.Deserialize>(json)!; + return tree.Select(MapNode).ToList(); + } + + private static DeviceTreeNode MapNode(Mc4TreeNode n) => new() + { + SourceId = n.Id, + Name = n.Name ?? n.Id.ToString(), + NodeType = n.Type, + ObjectType = n.ObjectType, + Tag = n.Tag, + Option = n.Option ?? new Dictionary(), + Children = n.Children?.Select(MapNode).ToList() ?? new() + }; + + // ─── IHasPoints ─── + public async Task> GetRealtimeValuesAsync(string sourceDeviceId) + { + await _limiter.WaitAsync(); + var client = await _auth.GetAuthenticatedClientAsync(); + var body = JsonSerializer.Serialize(new { id = int.Parse(sourceDeviceId) }); + var resp = await client.PostAsync("/api/central/device/point/value/get", + new StringContent(body, Encoding.UTF8, "application/json")); + resp.EnsureSuccessStatusCode(); + var json = await resp.Content.ReadAsStringAsync(); + var values = JsonSerializer.Deserialize>(json)!; + return values.Select(v => new PointValue + { + SourceDeviceId = sourceDeviceId, + PointIndex = v.Index, + Value = v.Value, + UpdateTime = v.Time, + Interval = v.Interval + }).ToList(); + } + + public async Task> GetMultiPointValuesAsync(List<(string DeviceId, int PointIndex)> points) + { + await _limiter.WaitAsync(); + var client = await _auth.GetAuthenticatedClientAsync(); + var body = JsonSerializer.Serialize(new + { + points = points.Select(p => new { id = int.Parse(p.DeviceId), index = p.PointIndex }) + }); + var resp = await client.PostAsync("/api/central/point/multi/value/get", + new StringContent(body, Encoding.UTF8, "application/json")); + resp.EnsureSuccessStatusCode(); + var json = await resp.Content.ReadAsStringAsync(); + var values = JsonSerializer.Deserialize>(json)!; + return values.Select(v => new PointValue + { + SourceDeviceId = v.Id.ToString(), + PointIndex = v.Index, + Value = v.Value, + UpdateTime = v.Time, + Interval = v.Interval + }).ToList(); + } + + public async Task SetPointValueAsync(string sourceDeviceId, int pointIndex, double value) + { + await _limiter.WaitAsync(); + var client = await _auth.GetAuthenticatedClientAsync(); + var body = JsonSerializer.Serialize(new { id = int.Parse(sourceDeviceId), index = pointIndex, value }); + await client.PostAsync("/api/central/point/value/set", + new StringContent(body, Encoding.UTF8, "application/json")); + } + + // ─── IHasAlarms ─── + public async Task> GetAlarmsAsync(int page, int size, DateTime from, DateTime to, + int? confirmState = null, int? endState = null, List? levels = null) + { + await _limiter.WaitAsync(); + var client = await _auth.GetAuthenticatedClientAsync(); + var body = JsonSerializer.Serialize(new Mc4AlarmQuery + { + From = from.ToString("yyyy-MM-dd HH:mm:ss"), + To = to.ToString("yyyy-MM-dd HH:mm:ss"), + Skip = (page - 1) * size, + Limit = size, + Sort = 1 + }); + var resp = await client.PostAsync("/api/central/alarm/query", + new StringContent(body, Encoding.UTF8, "application/json")); + resp.EnsureSuccessStatusCode(); + var json = await resp.Content.ReadAsStringAsync(); + var result = JsonSerializer.Deserialize(json)!; + + return new PagedResult + { + Items = result.List?.Select(a => new StandardAlarm + { + AlarmId = a.Id ?? "", + DeviceId = a.Sid?.ToString(), + AdapterCode = AdapterCode, + Level = MapAlarmLevel(a.Level), + Title = a.Desc ?? "", + Content = a.EngDesc, + OccurTime = DateTime.TryParse(a.Stime, out var st) ? st : DateTime.MinValue, + Status = MapAlarmState(a.State), + ActualValue = a.Soption?.Value, + ThresholdValue = a.Eoption?.Value + }).ToList() ?? new(), + Total = result.Total + }; + } + + public async Task ConfirmAlarmAsync(string alarmId) + { + await _limiter.WaitAsync(); + var client = await _auth.GetAuthenticatedClientAsync(); + var body = JsonSerializer.Serialize(new { id = alarmId, option = new { } }); + await client.PostAsync("/api/central/alarm/confirm", + new StringContent(body, Encoding.UTF8, "application/json")); + } + + public async Task EndAlarmAsync(string alarmId) + { + await _limiter.WaitAsync(); + var client = await _auth.GetAuthenticatedClientAsync(); + var body = JsonSerializer.Serialize(new { id = alarmId, option = new { } }); + await client.PostAsync("/api/central/alarm/end", + new StringContent(body, Encoding.UTF8, "application/json")); + } + + public async Task GetPendingAlarmCountAsync() + { + await _limiter.WaitAsync(); + var client = await _auth.GetAuthenticatedClientAsync(); + var body = JsonSerializer.Serialize(new { state = 1 }); + var resp = await client.PostAsync("/api/central/alarm/custom_query_count", + new StringContent(body, Encoding.UTF8, "application/json")); + resp.EnsureSuccessStatusCode(); + var json = await resp.Content.ReadAsStringAsync(); + return int.TryParse(json, out var count) ? count : 0; + } + + private static string MapAlarmLevel(int level) => level switch { 1 => "提示", 2 => "普通", 3 => "重要", 4 => "紧急", _ => "提示" }; + private static string MapAlarmState(int state) => state switch { 1 => "未确认", 2 => "已确认", 3 => "已结束", _ => "未确认" }; +} + +// ─── MC4 JSON Models ─── +public class Mc4TreeNode +{ + public int Id { get; set; } + public string? Name { get; set; } + public int Type { get; set; } + public int ObjectType { get; set; } + public string? Tag { get; set; } + public Dictionary? Option { get; set; } + public List? Children { get; set; } +} + +public class Mc4PointValue +{ + public int Id { get; set; } + public int Index { get; set; } + public double Value { get; set; } + public string? Time { get; set; } + public int Interval { get; set; } +} + +public class Mc4AlarmQuery +{ + public string From { get; set; } = ""; + public string To { get; set; } = ""; + public int Skip { get; set; } + public int Limit { get; set; } + public int Sort { get; set; } +} + +public class Mc4AlarmQueryResult +{ + public int Total { get; set; } + public List? List { get; set; } +} + +public class Mc4AlarmItem +{ + public string? Id { get; set; } + public int? Sid { get; set; } + public int Sno { get; set; } + public string? Desc { get; set; } + public string? EngDesc { get; set; } + public int Level { get; set; } + public int State { get; set; } + public string? Stime { get; set; } + public string? Etime { get; set; } + public string? Ctime { get; set; } + public string? Cuser { get; set; } + public int Type { get; set; } + public Mc4Option? Soption { get; set; } + public Mc4Option? Eoption { get; set; } +} + +public class Mc4Option +{ + public double? Value { get; set; } + public string? TypeName { get; set; } +} diff --git a/gateway/src/IntegrationGateway.Adapters.MC4/Mc4AuthHelper.cs b/gateway/src/IntegrationGateway.Adapters.MC4/Mc4AuthHelper.cs new file mode 100644 index 0000000..257d76a --- /dev/null +++ b/gateway/src/IntegrationGateway.Adapters.MC4/Mc4AuthHelper.cs @@ -0,0 +1,43 @@ +using System.Text.Json; + +namespace IntegrationGateway.Adapters.MC4; + +public class Mc4AuthHelper +{ + private readonly HttpClient _http; + private readonly string _baseUrl; + private string? _token; + private DateTime _tokenExpiry = DateTime.MinValue; + + public Mc4AuthHelper(HttpClient http, string baseUrl) + { + _http = http; + _baseUrl = baseUrl.TrimEnd('/'); + } + + public async Task GetTokenAsync() + { + if (!string.IsNullOrEmpty(_token) && DateTime.UtcNow < _tokenExpiry) + return _token; + + var resp = await _http.PostAsync($"{_baseUrl}/api/central/auth/conf/get", null); + resp.EnsureSuccessStatusCode(); + var json = await resp.Content.ReadAsStringAsync(); + var result = JsonSerializer.Deserialize(json); + _token = result?.Token ?? ""; + _tokenExpiry = DateTime.UtcNow.AddHours(8); + return _token!; + } + + public async Task GetAuthenticatedClientAsync() + { + var token = await GetTokenAsync(); + var client = new HttpClient { BaseAddress = new Uri(_baseUrl) }; + client.DefaultRequestHeaders.Add("token", token); + return client; + } + + public void Invalidate() => _token = null; + + public class Mc4AuthResponse { public string? Token { get; set; } } +} diff --git a/gateway/src/IntegrationGateway.Host/IntegrationGateway.Host.csproj b/gateway/src/IntegrationGateway.Host/IntegrationGateway.Host.csproj index 283b1a9..526dfbd 100644 --- a/gateway/src/IntegrationGateway.Host/IntegrationGateway.Host.csproj +++ b/gateway/src/IntegrationGateway.Host/IntegrationGateway.Host.csproj @@ -14,6 +14,7 @@ + diff --git a/gateway/src/IntegrationGateway.Host/Program.cs b/gateway/src/IntegrationGateway.Host/Program.cs index 48182a8..7f10585 100644 --- a/gateway/src/IntegrationGateway.Host/Program.cs +++ b/gateway/src/IntegrationGateway.Host/Program.cs @@ -1,5 +1,6 @@ using IntegrationGateway.Core.Infrastructure; using IntegrationGateway.Adapters.Owl; +using IntegrationGateway.Adapters.MC4; using IntegrationGateway.Host; var builder = WebApplication.CreateBuilder(args); @@ -37,6 +38,17 @@ var owlAdapter = new OwlAdapter( ); registry.Register(owlAdapter); +// 注册 MC4 适配器 +var mc4Cfg = builder.Configuration.GetSection("MC4"); +var mc4Http = app.Services.GetRequiredService().CreateClient("VolPro"); +var mc4Adapter = new Mc4Adapter( + "MC4:31ku", + mc4Http, + mc4Cfg["BaseUrl"] ?? "http://localhost:3000" +); +registry.Register(mc4Adapter); + + try { var result = await gw.RegisterAsync(); diff --git a/gateway/src/IntegrationGateway.Host/appsettings.json b/gateway/src/IntegrationGateway.Host/appsettings.json index ca1e83a..a1b3097 100644 --- a/gateway/src/IntegrationGateway.Host/appsettings.json +++ b/gateway/src/IntegrationGateway.Host/appsettings.json @@ -14,5 +14,8 @@ "BaseUrl": "http://owl_host:15123", "Username": "admin", "Password": "your_owl_password" + }, + "MC4": { + "BaseUrl": "http://mc4_host:3000" } } \ No newline at end of file