diff --git a/gateway/IntegrationGateway.slnx b/gateway/IntegrationGateway.slnx index 9fea9e6..32fdb2d 100644 --- a/gateway/IntegrationGateway.slnx +++ b/gateway/IntegrationGateway.slnx @@ -1,5 +1,6 @@ + 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..789a761 --- /dev/null +++ b/gateway/src/IntegrationGateway.Adapters.MC4/Mc4Adapter.cs @@ -0,0 +1,203 @@ +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() + { + Id = n.Id, SourceId = n.Id.ToString(), Name = n.Name ?? n.Id.ToString(), + Type = 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 != null ? DateTime.Parse(v.Time) : null, 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, + string? level = null, string? state = 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 ?? "", + 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")); + } + + 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? Sid { get; set; } + 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 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..65b6762 --- /dev/null +++ b/gateway/src/IntegrationGateway.Adapters.MC4/Mc4AuthHelper.cs @@ -0,0 +1,41 @@ +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 e6369c2..4d669df 100644 --- a/gateway/src/IntegrationGateway.Host/IntegrationGateway.Host.csproj +++ b/gateway/src/IntegrationGateway.Host/IntegrationGateway.Host.csproj @@ -3,6 +3,7 @@ +