Owl整改O1-O4: 设备通道展开+AI事件IHasAlarms+PTZ预设位+IAcceptsControl
This commit is contained in:
@@ -10,37 +10,29 @@ namespace IntegrationGateway.Adapters.Owl;
|
|||||||
/// Owl 视频监控子系统适配器。
|
/// Owl 视频监控子系统适配器。
|
||||||
///
|
///
|
||||||
/// 实现的能力接口:
|
/// 实现的能力接口:
|
||||||
/// - IHasFlatDevices:设备列表(NVR)和通道列表
|
/// - IHasFlatDevices:GET /devices/channels → 设备+通道联合映射
|
||||||
/// - IHasStreams:实时取流、录像回放、云台控制、截图
|
/// - IHasStreams:实时取流、录像回放、云台控制、截图
|
||||||
/// - IHasRecordings:录像文件查询
|
/// - IHasRecordings:录像文件查询
|
||||||
/// - IAcceptsMetadataPush:设备元数据回写(如改名)
|
/// - IAcceptsMetadataPush:设备元数据回写(如改名)
|
||||||
|
/// - IHasAlarms:AI 事件映射
|
||||||
|
/// - IAcceptsControl:AI 检测启停、远程控制
|
||||||
///
|
///
|
||||||
/// 限流:5 QPS(Owl 推荐值)
|
/// 限流:5 QPS
|
||||||
/// PTZ 限制:仅支持 continuous 方向移动 + stop,不支持预设位
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class OwlAdapter : IHasFlatDevices, IHasStreams, IHasRecordings, IAcceptsMetadataPush
|
public class OwlAdapter : IHasFlatDevices, IHasStreams, IHasRecordings, IAcceptsMetadataPush, IHasAlarms, IAcceptsControl
|
||||||
{
|
{
|
||||||
private readonly HttpClient _http;
|
private readonly HttpClient _http;
|
||||||
private readonly OwlAuthHelper _auth;
|
private readonly OwlAuthHelper _auth;
|
||||||
/// <summary>令牌桶限流器(5 QPS)</summary>
|
|
||||||
private readonly RateLimiter _limiter = new(5);
|
private readonly RateLimiter _limiter = new(5);
|
||||||
|
|
||||||
/// <summary>适配器编码,格式 "Owl:实例名"</summary>
|
|
||||||
public string AdapterCode { get; }
|
public string AdapterCode { get; }
|
||||||
/// <summary>人类可读的适配器名称</summary>
|
|
||||||
public string DisplayName => $"Owl ({AdapterCode})";
|
public string DisplayName => $"Owl ({AdapterCode})";
|
||||||
/// <summary>适配器能力声明</summary>
|
|
||||||
public AdapterCapabilities Capabilities => new()
|
public AdapterCapabilities Capabilities => new()
|
||||||
{
|
{
|
||||||
HasFlatDevices = true, HasStreams = true, HasPtz = true, HasRecordings = true, AcceptsMetadataPush = true
|
HasFlatDevices = true, HasStreams = true, HasPtz = true,
|
||||||
|
HasRecordings = true, AcceptsMetadataPush = true, HasAlarms = true
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>创建 OwlAdapter 实例</summary>
|
|
||||||
/// <param name="adapterCode">适配器编码</param>
|
|
||||||
/// <param name="http">HttpClient 实例</param>
|
|
||||||
/// <param name="baseUrl">Owl 服务地址</param>
|
|
||||||
/// <param name="username">登录用户名</param>
|
|
||||||
/// <param name="password">登录密码</param>
|
|
||||||
public OwlAdapter(string adapterCode, HttpClient http, string baseUrl, string username, string password)
|
public OwlAdapter(string adapterCode, HttpClient http, string baseUrl, string username, string password)
|
||||||
{
|
{
|
||||||
AdapterCode = adapterCode;
|
AdapterCode = adapterCode;
|
||||||
@@ -48,10 +40,12 @@ public class OwlAdapter : IHasFlatDevices, IHasStreams, IHasRecordings, IAccepts
|
|||||||
_auth = new OwlAuthHelper(http, baseUrl, username, password);
|
_auth = new OwlAuthHelper(http, baseUrl, username, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>初始化适配器:获取 Owl JWT Token</summary>
|
|
||||||
public async Task InitializeAsync() => await _auth.GetTokenAsync();
|
public async Task InitializeAsync() => await _auth.GetTokenAsync();
|
||||||
|
|
||||||
/// <summary>健康检查:尝试访问 Owl /health 端点</summary>
|
// ═══════════════════════════════════════════
|
||||||
|
// IGatewayAdapter — 健康检查
|
||||||
|
// ═══════════════════════════════════════════
|
||||||
|
|
||||||
public async Task<bool> HealthCheckAsync()
|
public async Task<bool> HealthCheckAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -64,30 +58,76 @@ public class OwlAdapter : IHasFlatDevices, IHasStreams, IHasRecordings, IAccepts
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════
|
// ═══════════════════════════════════════════
|
||||||
// IHasFlatDevices 实现
|
// IHasFlatDevices — GET /devices/channels 设备+通道联合映射
|
||||||
// ═══════════════════════════════════════════
|
// ═══════════════════════════════════════════
|
||||||
|
|
||||||
/// <summary>分页获取 NVR 设备列表</summary>
|
|
||||||
public async Task<PagedResult<StandardDevice>> GetDevicesAsync(int page, int size, string? keyword = null)
|
public async Task<PagedResult<StandardDevice>> GetDevicesAsync(int page, int size, string? keyword = null)
|
||||||
{
|
{
|
||||||
await _limiter.WaitAsync();
|
await _limiter.WaitAsync();
|
||||||
var client = await _auth.GetAuthenticatedClientAsync();
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
var url = $"/devices?page={page}&size={size}";
|
var url = $"/devices/channels?page={page}&size=1000";
|
||||||
if (!string.IsNullOrEmpty(keyword)) url += $"&key={Uri.EscapeDataString(keyword)}";
|
if (!string.IsNullOrEmpty(keyword)) url += $"&key={Uri.EscapeDataString(keyword)}";
|
||||||
var json = await client.GetStringAsync(url);
|
var json = await client.GetStringAsync(url);
|
||||||
var owl = JsonSerializer.Deserialize<OwlPagedResult<OwlDevice>>(json)!;
|
var result = JsonSerializer.Deserialize<OwlPagedResult<OwlDeviceChannel>>(json)!;
|
||||||
return new PagedResult<StandardDevice>
|
|
||||||
|
var devices = new List<StandardDevice>();
|
||||||
|
var deviceItems = result.Items.Where(x => x.Type == "DEVICE").ToList();
|
||||||
|
var channelItems = result.Items.Where(x => x.Type == "CHANNEL").ToList();
|
||||||
|
|
||||||
|
foreach (var d in deviceItems)
|
||||||
{
|
{
|
||||||
Items = owl.Items.Select(MapDevice).ToList(),
|
var childChannels = channelItems.Where(c => c.Did == d.Id).ToList();
|
||||||
Total = owl.Total
|
devices.Add(MapDevice(d, childChannels));
|
||||||
};
|
foreach (var ch in childChannels)
|
||||||
|
devices.Add(MapChannel(ch, d.Id));
|
||||||
|
}
|
||||||
|
return new PagedResult<StandardDevice> { Items = devices, Total = devices.Count };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static StandardDevice MapDevice(OwlDeviceChannel d, List<OwlDeviceChannel> channels) => new()
|
||||||
|
{
|
||||||
|
SourceId = d.Id ?? "",
|
||||||
|
Name = d.Name ?? d.Id ?? "",
|
||||||
|
Category = "硬盘录像机",
|
||||||
|
Group = "视频设备",
|
||||||
|
IsOnline = d.IsOnline == "1",
|
||||||
|
IsParent = true,
|
||||||
|
IpAddress = d.Address,
|
||||||
|
Port = int.TryParse(d.Port, out var p) ? p : null,
|
||||||
|
Extra = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["manufacturer"] = d.Manufacturer,
|
||||||
|
["model"] = d.Model,
|
||||||
|
["firmware"] = d.Firmware,
|
||||||
|
["longitude"] = d.Longitude,
|
||||||
|
["latitude"] = d.Latitude,
|
||||||
|
["protocol"] = d.Protocol ?? "GB28181",
|
||||||
|
["transport"] = d.Transport,
|
||||||
|
["channelCount"] = d.ChannelCount ?? channels.Count
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static StandardDevice MapChannel(OwlDeviceChannel ch, string? parentDeviceId) => new()
|
||||||
|
{
|
||||||
|
SourceId = ch.Id ?? "",
|
||||||
|
Name = ch.Name ?? $"通道{ch.Id}",
|
||||||
|
Category = "摄像机",
|
||||||
|
Group = "视频设备",
|
||||||
|
IsOnline = ch.IsOnline?.ToLower() == "true" || ch.IsOnline == "1",
|
||||||
|
IsParent = false,
|
||||||
|
ParentSourceId = parentDeviceId,
|
||||||
|
Extra = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["hasPtz"] = (ch.Ptztype ?? 0) > 0 ? "1" : "0",
|
||||||
|
["app"] = ch.App,
|
||||||
|
["streamId"] = ch.StreamId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ═══════════════════════════════════════════
|
// ═══════════════════════════════════════════
|
||||||
// IHasStreams 实现
|
// IHasStreams
|
||||||
// ═══════════════════════════════════════════
|
// ═══════════════════════════════════════════
|
||||||
|
|
||||||
/// <summary>获取通道实时视频流地址</summary>
|
|
||||||
public async Task<StreamUrls> GetLiveUrlAsync(string channelId)
|
public async Task<StreamUrls> GetLiveUrlAsync(string channelId)
|
||||||
{
|
{
|
||||||
await _limiter.WaitAsync();
|
await _limiter.WaitAsync();
|
||||||
@@ -99,39 +139,43 @@ public class OwlAdapter : IHasFlatDevices, IHasStreams, IHasRecordings, IAccepts
|
|||||||
return MapStreamUrls(play);
|
return MapStreamUrls(play);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>获取历史录像回放地址(HLS VOD 格式)</summary>
|
|
||||||
public async Task<StreamUrls> GetPlaybackUrlAsync(string channelId, DateTime start, DateTime end)
|
public async Task<StreamUrls> GetPlaybackUrlAsync(string channelId, DateTime start, DateTime end)
|
||||||
{
|
{
|
||||||
await _limiter.WaitAsync();
|
await _limiter.WaitAsync();
|
||||||
var client = await _auth.GetAuthenticatedClientAsync();
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
|
var token = await _auth.GetTokenAsync();
|
||||||
var startMs = new DateTimeOffset(start).ToUnixTimeMilliseconds();
|
var startMs = new DateTimeOffset(start).ToUnixTimeMilliseconds();
|
||||||
var endMs = new DateTimeOffset(end).ToUnixTimeMilliseconds();
|
var endMs = new DateTimeOffset(end).ToUnixTimeMilliseconds();
|
||||||
var token = await _auth.GetTokenAsync();
|
var baseUrl = (client.BaseAddress?.ToString() ?? "").TrimEnd('/');
|
||||||
return new StreamUrls
|
return new StreamUrls
|
||||||
{
|
{
|
||||||
Hls = $"{client.BaseAddress}recordings/channels/{channelId}/index.m3u8?start_ms={startMs}&end_ms={endMs}&token={token}"
|
Hls = $"{baseUrl}/recordings/channels/{channelId}/index.m3u8?start_ms={startMs}&end_ms={endMs}&token={token}"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>云台方向控制(continuous 模式,仅方向移动)</summary>
|
|
||||||
public async Task PtzControlAsync(string channelId, string direction, float speed)
|
public async Task PtzControlAsync(string channelId, string direction, float speed)
|
||||||
{
|
{
|
||||||
await _limiter.WaitAsync();
|
await _limiter.WaitAsync();
|
||||||
var client = await _auth.GetAuthenticatedClientAsync();
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
await client.PostAsJsonAsync($"/channels/{channelId}/ptz/control",
|
if (direction.StartsWith("preset_"))
|
||||||
new { action = "continuous", direction, speed });
|
{
|
||||||
|
var idx = int.Parse(direction.Replace("preset_", ""));
|
||||||
|
await client.PostAsJsonAsync($"/channels/{channelId}/ptz/control", new { action = "preset", preset = idx });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await client.PostAsJsonAsync($"/channels/{channelId}/ptz/control",
|
||||||
|
new { action = "continuous", direction, speed });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>云台停止</summary>
|
|
||||||
public async Task PtzStopAsync(string channelId)
|
public async Task PtzStopAsync(string channelId)
|
||||||
{
|
{
|
||||||
await _limiter.WaitAsync();
|
await _limiter.WaitAsync();
|
||||||
var client = await _auth.GetAuthenticatedClientAsync();
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
await client.PostAsJsonAsync($"/channels/{channelId}/ptz/control",
|
await client.PostAsJsonAsync($"/channels/{channelId}/ptz/control", new { action = "stop" });
|
||||||
new { action = "stop" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>获取通道实时截图</summary>
|
|
||||||
public async Task<StreamUrls> GetSnapshotAsync(string channelId)
|
public async Task<StreamUrls> GetSnapshotAsync(string channelId)
|
||||||
{
|
{
|
||||||
await _limiter.WaitAsync();
|
await _limiter.WaitAsync();
|
||||||
@@ -144,10 +188,9 @@ public class OwlAdapter : IHasFlatDevices, IHasStreams, IHasRecordings, IAccepts
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════
|
// ═══════════════════════════════════════════
|
||||||
// IHasRecordings 实现
|
// IHasRecordings
|
||||||
// ═══════════════════════════════════════════
|
// ═══════════════════════════════════════════
|
||||||
|
|
||||||
/// <summary>分页查询录像文件记录</summary>
|
|
||||||
public async Task<PagedResult<StandardRecording>> GetRecordingsAsync(
|
public async Task<PagedResult<StandardRecording>> GetRecordingsAsync(
|
||||||
string channelId, DateTime start, DateTime end, int page, int size)
|
string channelId, DateTime start, DateTime end, int page, int size)
|
||||||
{
|
{
|
||||||
@@ -171,10 +214,9 @@ public class OwlAdapter : IHasFlatDevices, IHasStreams, IHasRecordings, IAccepts
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════
|
// ═══════════════════════════════════════════
|
||||||
// IAcceptsMetadataPush 实现
|
// IAcceptsMetadataPush
|
||||||
// ═══════════════════════════════════════════
|
// ═══════════════════════════════════════════
|
||||||
|
|
||||||
/// <summary>回写设备元数据(如改名)到 Owl</summary>
|
|
||||||
public async Task<MetadataPushResult> PushMetadataAsync(string sourceDeviceId, MetadataChangeSet changes)
|
public async Task<MetadataPushResult> PushMetadataAsync(string sourceDeviceId, MetadataChangeSet changes)
|
||||||
{
|
{
|
||||||
var client = await _auth.GetAuthenticatedClientAsync();
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
@@ -185,29 +227,75 @@ public class OwlAdapter : IHasFlatDevices, IHasStreams, IHasRecordings, IAccepts
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════
|
// ═══════════════════════════════════════════
|
||||||
// 内部映射方法
|
// IHasAlarms — AI 事件
|
||||||
// ═══════════════════════════════════════════
|
// ═══════════════════════════════════════════
|
||||||
|
|
||||||
/// <summary>Owl 设备 → StandardDevice 映射</summary>
|
public async Task<PagedResult<StandardAlarm>> GetAlarmsAsync(
|
||||||
private static StandardDevice MapDevice(OwlDevice d) => new()
|
int page, int size, DateTime from, DateTime to, string? level = null, string? state = null)
|
||||||
{
|
{
|
||||||
SourceId = d.Id ?? "",
|
await _limiter.WaitAsync();
|
||||||
Name = d.Name ?? d.Id ?? "",
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
Category = "硬盘录像机",
|
var fromMs = new DateTimeOffset(from).ToUnixTimeMilliseconds();
|
||||||
Group = "视频设备",
|
var toMs = new DateTimeOffset(to).ToUnixTimeMilliseconds();
|
||||||
IsOnline = d.IsOnline == "1",
|
var json = await client.GetStringAsync(
|
||||||
IsParent = true,
|
$"/events?page={page}&size={size}&start_ms={fromMs}&end_ms={toMs}");
|
||||||
IpAddress = d.Address,
|
var result = JsonSerializer.Deserialize<OwlPagedResult<OwlAiEvent>>(json)!;
|
||||||
Port = int.TryParse(d.Port, out var port) ? port : null,
|
return new PagedResult<StandardAlarm>
|
||||||
Extra = new Dictionary<string, object?>
|
|
||||||
{
|
{
|
||||||
["owlDeviceId"] = d.Id,
|
Items = result.Items.Select(MapEventToAlarm).ToList(),
|
||||||
["protocol"] = d.Protocol ?? "GB28181",
|
Total = result.Total
|
||||||
["transport"] = d.Transport
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task ConfirmAlarmAsync(string alarmId) => Task.CompletedTask;
|
||||||
|
public Task EndAlarmAsync(string alarmId) => Task.CompletedTask;
|
||||||
|
|
||||||
|
private StandardAlarm MapEventToAlarm(OwlAiEvent e) => new()
|
||||||
|
{
|
||||||
|
AlarmId = $"owl-ai-{e.Id}",
|
||||||
|
AdapterCode = AdapterCode,
|
||||||
|
Level = e.Label switch { "person" => "重要", "car" => "重要", _ => "普通" },
|
||||||
|
Title = $"AI检测: {e.Label} (置信度 {e.Score:P0})",
|
||||||
|
Content = e.Zones ?? "",
|
||||||
|
OccurTime = e.StartedAt.HasValue
|
||||||
|
? DateTimeOffset.FromUnixTimeMilliseconds(e.StartedAt.Value).DateTime
|
||||||
|
: DateTime.MinValue,
|
||||||
|
Status = (e.EndedAt ?? 0) > 0 ? "已结束" : "未确认"
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>Owl 播放响应 → StreamUrls 映射(取第一个可用流)</summary>
|
// ═══════════════════════════════════════════
|
||||||
|
// IAcceptsControl — AI 启停 + 区域管理
|
||||||
|
// ═══════════════════════════════════════════
|
||||||
|
|
||||||
|
public async Task<ControlResult> SendControlAsync(string sourceDeviceId, string command, Dictionary<string, object?> parameters)
|
||||||
|
{
|
||||||
|
await _limiter.WaitAsync();
|
||||||
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (command)
|
||||||
|
{
|
||||||
|
case "ai-enable":
|
||||||
|
await client.PostAsync($"/channels/{sourceDeviceId}/ai/enable", null);
|
||||||
|
break;
|
||||||
|
case "ai-disable":
|
||||||
|
await client.PostAsync($"/channels/{sourceDeviceId}/ai/disable", null);
|
||||||
|
break;
|
||||||
|
case "zone-add":
|
||||||
|
await client.PostAsJsonAsync($"/channels/{sourceDeviceId}/zones", parameters!);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return new ControlResult { Success = false, Message = $"不支持的指令: {command}" };
|
||||||
|
}
|
||||||
|
return new ControlResult { Success = true };
|
||||||
|
}
|
||||||
|
catch (Exception ex) { return new ControlResult { Success = false, Message = ex.Message }; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════
|
||||||
|
// 内部工具
|
||||||
|
// ═══════════════════════════════════════════
|
||||||
|
|
||||||
private static StreamUrls MapStreamUrls(OwlPlayResponse play)
|
private static StreamUrls MapStreamUrls(OwlPlayResponse play)
|
||||||
{
|
{
|
||||||
var item = play.Items?.FirstOrDefault();
|
var item = play.Items?.FirstOrDefault();
|
||||||
@@ -218,61 +306,3 @@ public class OwlAdapter : IHasFlatDevices, IHasStreams, IHasRecordings, IAccepts
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════
|
|
||||||
// Owl JSON 反序列化模型(内部使用)
|
|
||||||
// ═══════════════════════════════════════════
|
|
||||||
|
|
||||||
/// <summary>Owl API 分页响应</summary>
|
|
||||||
public class OwlPagedResult<T>
|
|
||||||
{
|
|
||||||
public List<T> Items { get; set; } = new();
|
|
||||||
public int Total { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Owl 设备(NVR)</summary>
|
|
||||||
public class OwlDevice
|
|
||||||
{
|
|
||||||
public string? Id { get; set; }
|
|
||||||
public string? Name { get; set; }
|
|
||||||
public string? IsOnline { get; set; }
|
|
||||||
public string? Protocol { get; set; }
|
|
||||||
public string? Address { get; set; }
|
|
||||||
public string? Port { get; set; }
|
|
||||||
public string? Transport { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Owl 播放响应</summary>
|
|
||||||
public class OwlPlayResponse
|
|
||||||
{
|
|
||||||
public List<OwlPlayItem>? Items { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Owl 播放流条目</summary>
|
|
||||||
public class OwlPlayItem
|
|
||||||
{
|
|
||||||
public string? WsFlv { get; set; }
|
|
||||||
public string? HttpFlv { get; set; }
|
|
||||||
public string? Hls { get; set; }
|
|
||||||
public string? WebRtc { get; set; }
|
|
||||||
public string? Rtmp { get; set; }
|
|
||||||
public string? Rtsp { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Owl 截图响应</summary>
|
|
||||||
public class OwlSnapshotResponse
|
|
||||||
{
|
|
||||||
public string? Link { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Owl 录像记录</summary>
|
|
||||||
public class OwlRecording
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string? Cid { get; set; }
|
|
||||||
public DateTime StartedAt { get; set; }
|
|
||||||
public DateTime EndedAt { get; set; }
|
|
||||||
public double Duration { get; set; }
|
|
||||||
public string? Path { get; set; }
|
|
||||||
public long Size { get; set; }
|
|
||||||
}
|
|
||||||
|
|||||||
107
gateway/src/IntegrationGateway.Adapters.Owl/OwlModels.cs
Normal file
107
gateway/src/IntegrationGateway.Adapters.Owl/OwlModels.cs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/// <summary>
|
||||||
|
/// Owl/GoWVP API 响应模型。
|
||||||
|
/// 从 OwlAdapter.cs 分离,便于维护和扩展。
|
||||||
|
/// </summary>
|
||||||
|
namespace IntegrationGateway.Adapters.Owl;
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════
|
||||||
|
// 通用
|
||||||
|
// ═══════════════════════════════════════════
|
||||||
|
|
||||||
|
/// <summary>Owl API 分页响应</summary>
|
||||||
|
public class OwlPagedResult<T>
|
||||||
|
{
|
||||||
|
public List<T> Items { get; set; } = new();
|
||||||
|
public int Total { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════
|
||||||
|
// 设备+通道联合模型 (GET /devices/channels)
|
||||||
|
// ═══════════════════════════════════════════
|
||||||
|
|
||||||
|
/// <summary>Owl 设备或通道(联合接口返回)</summary>
|
||||||
|
public class OwlDeviceChannel
|
||||||
|
{
|
||||||
|
public string? Id { get; set; }
|
||||||
|
public string? Type { get; set; } // "DEVICE" | "CHANNEL"
|
||||||
|
public string? Did { get; set; } // 通道所属设备 ID
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public string? IsOnline { get; set; } // DEVICE: "1"/"0", CHANNEL: true/false
|
||||||
|
public string? Manufacturer { get; set; }
|
||||||
|
public string? Model { get; set; }
|
||||||
|
public string? Firmware { get; set; }
|
||||||
|
public string? Longitude { get; set; }
|
||||||
|
public string? Latitude { get; set; }
|
||||||
|
public int? ChannelCount { get; set; }
|
||||||
|
public int? Ptztype { get; set; } // 0=无云台, 1=方向, 2=预置位
|
||||||
|
public string? Protocol { get; set; }
|
||||||
|
public string? Address { get; set; }
|
||||||
|
public string? Port { get; set; }
|
||||||
|
public string? Transport { get; set; }
|
||||||
|
public string? App { get; set; } // 流应用名
|
||||||
|
public string? StreamId { get; set; } // 流ID
|
||||||
|
public string? Status { get; set; }
|
||||||
|
public string? RegisterWay { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════
|
||||||
|
// 播放/流
|
||||||
|
// ═══════════════════════════════════════════
|
||||||
|
|
||||||
|
/// <summary>Owl 播放响应</summary>
|
||||||
|
public class OwlPlayResponse
|
||||||
|
{
|
||||||
|
public List<OwlPlayItem>? Items { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Owl 播放流条目</summary>
|
||||||
|
public class OwlPlayItem
|
||||||
|
{
|
||||||
|
public string? WsFlv { get; set; }
|
||||||
|
public string? HttpFlv { get; set; }
|
||||||
|
public string? Hls { get; set; }
|
||||||
|
public string? WebRtc { get; set; }
|
||||||
|
public string? Rtmp { get; set; }
|
||||||
|
public string? Rtsp { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Owl 截图响应</summary>
|
||||||
|
public class OwlSnapshotResponse
|
||||||
|
{
|
||||||
|
public string? Link { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════
|
||||||
|
// 录像
|
||||||
|
// ═══════════════════════════════════════════
|
||||||
|
|
||||||
|
/// <summary>Owl 录像记录</summary>
|
||||||
|
public class OwlRecording
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? Cid { get; set; }
|
||||||
|
public DateTime StartedAt { get; set; }
|
||||||
|
public DateTime EndedAt { get; set; }
|
||||||
|
public double Duration { get; set; }
|
||||||
|
public string? Path { get; set; }
|
||||||
|
public long Size { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════
|
||||||
|
// AI 事件
|
||||||
|
// ═══════════════════════════════════════════
|
||||||
|
|
||||||
|
/// <summary>Owl AI 检测事件</summary>
|
||||||
|
public class OwlAiEvent
|
||||||
|
{
|
||||||
|
public long? Id { get; set; }
|
||||||
|
public string? Did { get; set; }
|
||||||
|
public string? Cid { get; set; }
|
||||||
|
public long? StartedAt { get; set; } // 毫秒时间戳
|
||||||
|
public long? EndedAt { get; set; }
|
||||||
|
public string? Label { get; set; } // person / car / ...
|
||||||
|
public float? Score { get; set; }
|
||||||
|
public string? Zones { get; set; }
|
||||||
|
public string? ImagePath { get; set; }
|
||||||
|
public string? Model { get; set; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user