282 lines
11 KiB
C#
282 lines
11 KiB
C#
using IntegrationGateway.Core.Abstractions;
|
||
using IntegrationGateway.Core.Infrastructure;
|
||
using IntegrationGateway.Core.Models;
|
||
using System.Text;
|
||
using System.Text.Json;
|
||
|
||
namespace IntegrationGateway.Adapters.MC4;
|
||
|
||
/// <summary>
|
||
/// MC4.0 动环监控子系统适配器。
|
||
///
|
||
/// 实现的能力接口:
|
||
/// - IHasOwnDeviceTree:对象树(区域→设备层级)
|
||
/// - IHasPoints:实时点位值读取 + 反向控制写值
|
||
/// - IHasAlarms:告警查询、确认、结束
|
||
///
|
||
/// 限流:2 QPS(MC4.0 API 推荐值)
|
||
/// 分页转换:网关 page/size ↔ MC4.0 skip/limit
|
||
/// </summary>
|
||
public class Mc4Adapter : IHasOwnDeviceTree, IHasPoints, IHasAlarms
|
||
{
|
||
private readonly HttpClient _http;
|
||
private readonly Mc4AuthHelper _auth;
|
||
/// <summary>令牌桶限流器(2 QPS)</summary>
|
||
private readonly RateLimiter _limiter = new(2);
|
||
|
||
/// <summary>适配器编码,格式 "MC4:实例名"</summary>
|
||
public string AdapterCode { get; }
|
||
/// <summary>人类可读的适配器名称</summary>
|
||
public string DisplayName => $"MC4 ({AdapterCode})";
|
||
/// <summary>适配器能力声明</summary>
|
||
public AdapterCapabilities Capabilities => new()
|
||
{
|
||
HasObjectTree = true, HasPoints = true, HasAlarms = true, AcceptsControl = true
|
||
};
|
||
|
||
/// <summary>创建 Mc4Adapter 实例</summary>
|
||
/// <param name="adapterCode">适配器编码</param>
|
||
/// <param name="http">HttpClient 实例</param>
|
||
/// <param name="baseUrl">MC4.0 服务地址</param>
|
||
public Mc4Adapter(string adapterCode, HttpClient http, string baseUrl)
|
||
{
|
||
AdapterCode = adapterCode;
|
||
_http = http;
|
||
_auth = new Mc4AuthHelper(http, baseUrl);
|
||
}
|
||
|
||
/// <summary>初始化适配器:获取 MC4.0 Token</summary>
|
||
public async Task InitializeAsync() => await _auth.GetTokenAsync();
|
||
|
||
/// <summary>健康检查:尝试调用 MC4.0 认证接口确认可达性</summary>
|
||
public async Task<bool> 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 实现
|
||
// ═══════════════════════════════════════════
|
||
|
||
/// <summary>
|
||
/// 获取 MC4.0 完整对象树。
|
||
/// Type=1 的节点为区域,Type=2 的节点为设备。
|
||
/// </summary>
|
||
public async Task<List<DeviceTreeNode>> 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<List<Mc4TreeNode>>(json)!;
|
||
return tree.Select(MapNode).ToList();
|
||
}
|
||
|
||
/// <summary>MC4.0 树节点 → DeviceTreeNode 映射</summary>
|
||
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<string, object?>(),
|
||
Children = n.Children?.Select(MapNode).ToList() ?? new()
|
||
};
|
||
|
||
// ═══════════════════════════════════════════
|
||
// IHasPoints 实现
|
||
// ═══════════════════════════════════════════
|
||
|
||
/// <summary>获取指定设备的所有实时点位值</summary>
|
||
public async Task<List<PointValue>> 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<List<Mc4PointValue>>(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();
|
||
}
|
||
|
||
/// <summary>向指定设备的指定点位写入控制值</summary>
|
||
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 实现
|
||
// ═══════════════════════════════════════════
|
||
|
||
/// <summary>
|
||
/// 分页查询告警列表。
|
||
/// 内部完成 page/size → skip/limit 转换。
|
||
/// </summary>
|
||
public async Task<PagedResult<StandardAlarm>> 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<Mc4AlarmQueryResult>(json)!;
|
||
return new PagedResult<StandardAlarm>
|
||
{
|
||
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
|
||
};
|
||
}
|
||
|
||
/// <summary>确认告警(同时写回 MC4.0)</summary>
|
||
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"));
|
||
}
|
||
|
||
/// <summary>结束告警(同时写回 MC4.0)</summary>
|
||
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"));
|
||
}
|
||
|
||
/// <summary>MC4.0 告警等级数字 → 中文映射</summary>
|
||
private static string MapAlarmLevel(int level) => level switch
|
||
{
|
||
1 => "提示", 2 => "普通", 3 => "重要", 4 => "紧急", _ => "提示"
|
||
};
|
||
|
||
/// <summary>MC4.0 告警状态数字 → 中文映射</summary>
|
||
private static string MapAlarmState(int state) => state switch
|
||
{
|
||
1 => "未确认", 2 => "已确认", 3 => "已结束", _ => "未确认"
|
||
};
|
||
}
|
||
|
||
// ═══════════════════════════════════════════
|
||
// MC4.0 JSON 反序列化模型(内部使用)
|
||
// ═══════════════════════════════════════════
|
||
|
||
/// <summary>MC4.0 对象树节点</summary>
|
||
public class Mc4TreeNode
|
||
{
|
||
public int Id { get; set; }
|
||
public string? Name { get; set; }
|
||
/// <summary>节点类型:1=区域,2=设备</summary>
|
||
public int Type { get; set; }
|
||
public int ObjectType { get; set; }
|
||
public string? Tag { get; set; }
|
||
public Dictionary<string, object?>? Option { get; set; }
|
||
public List<Mc4TreeNode>? Children { get; set; }
|
||
}
|
||
|
||
/// <summary>MC4.0 点位值</summary>
|
||
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; }
|
||
}
|
||
|
||
/// <summary>MC4.0 告警查询请求体</summary>
|
||
public class Mc4AlarmQuery
|
||
{
|
||
public string? Sid { get; set; }
|
||
public string From { get; set; } = "";
|
||
public string To { get; set; } = "";
|
||
/// <summary>跳过的记录数(= (page-1)*size)</summary>
|
||
public int Skip { get; set; }
|
||
/// <summary>每页条数</summary>
|
||
public int Limit { get; set; }
|
||
/// <summary>排序方式:1=时间降序</summary>
|
||
public int Sort { get; set; }
|
||
}
|
||
|
||
/// <summary>MC4.0 告警查询响应</summary>
|
||
public class Mc4AlarmQueryResult
|
||
{
|
||
public int Total { get; set; }
|
||
public List<Mc4AlarmItem>? List { get; set; }
|
||
}
|
||
|
||
/// <summary>MC4.0 告警条目</summary>
|
||
public class Mc4AlarmItem
|
||
{
|
||
public string? Id { get; set; }
|
||
/// <summary>设备 SID</summary>
|
||
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; }
|
||
/// <summary>告警触发时阈值信息</summary>
|
||
public Mc4Option? Soption { get; set; }
|
||
/// <summary>告警结束时阈值信息</summary>
|
||
public Mc4Option? Eoption { get; set; }
|
||
}
|
||
|
||
/// <summary>MC4.0 告警阈值信息</summary>
|
||
public class Mc4Option
|
||
{
|
||
public double? Value { get; set; }
|
||
public string? TypeName { get; set; }
|
||
}
|